Dragonfly

Image/asset management for winners

GitHub Repository

Caching and Performance

Dragonfly serves content on-the-fly, that is, when a request is made to a url given by

url = Dragonfly.app.fetch('my/stored/image').thumb('300x200').url

then Dragonfly will do exactly what the job says, i.e.

  1. fetch the content stored with uid "my/stored/image"
  2. process it to make a thumbnail with dimensions 300x200

Doing this on every request is expensive, so it's wise to use a caching proxy, so that after the first request the server simply returns the cached response.

Dragonfly sends Cache-Control and ETag headers in its responses to let us do this (for a well-written discussion of this, see this blog post).

Simply put a proxy like Rack::Cache, Varnish or Squid in front of the app and subsequent requests will be served super-quickly straight out of the cache.

For simple use of Rack::Cache with rails see Rails.

To customize Dragonfly's response headers, inside a configure block, you can use something like

response_header "Cache-Control", "public, max-age=3600"

or

response_header "Cache-Control" do |job, request, headers|
  if job.fetch_step && job.fetch_step.uid =~ /2012/
    "public, max-age=100000"
  else
    "private"
  end
end

Setting the header to nil removes the header from the response.

Using a CDN

Some CDNs are configured to make a request to your domain on the first request and return the cached version thereafter, using the same url path.

You can use url_host to point urls to the CDN instead of your local server.

For example, a url

Dragonfly.app.fetch('some/uid').thumb('300x200').url

normally returns something like

"/W1siZiIsInNvbWUvdWlkIl0sWyJwIiwidGh1bWIiLCIzMDB4MjAwIl1d"

Adding in a configure block

url_host 'http://some.cdn'

makes the url

"http://some.cdn/W1siZiIsInNvbWUvdWlkIl0sWyJwIiwidGh1bWIiLCIzMDB4MjAwIl1d"

NOTE: some CDNs may not forward GET parameters, in which case you may need to make sure the sha parameter is part of the path, i.e. in a configure block

url_format "/media/:job/:sha/:name"

Serving remotely (directly from the datastore)

See URLs

Performing processing on upload

See Up-front processing

Processing on-the-fly and serving remotely

Serving processed versions of content such as thumbnails remotely is a bit more tricky as we need to upload the thumbnail to the datastore in the on-the-fly manner.

Dragonfly provides a way of doing this using define_url and before_serve methods.

The details of keeping track of/expiring these thumbnails is up to you.

We need to keep track of which thumbnails have been already created, by storing a uid for each one. Below is an example using an ActiveRecord Thumb table to keep track of already created thumbnail uids. It has two string columns; signature and uid.

Dragonfly.app.configure do

  # Override the .url method...
  define_url do |app, job, opts|
    thumb = Thumb.find_by_signature(job.signature)
    # If (fetch 'some_uid' then resize to '40x40') has been stored already, give the datastore's remote url ...
    if thumb
      app.datastore.url_for(thumb.uid)
    # ...otherwise give the local Dragonfly server url
    else
      app.server.url_for(job)
    end
  end

  # Before serving from the local Dragonfly server...
  before_serve do |job, env|
    # ...store the thumbnail in the datastore...
    uid = job.store

    # ...keep track of its uid so next time we can serve directly from the datastore
    Thumb.create!(uid: uid, signature: job.signature)
  end

end

This would give

app.fetch('some_uid').thumb('40x40').url    # normal Dragonfly url e.g. /media/WhbBls...

then from the second time onwards

app.fetch('some_uid').thumb('40x40').url    # http://my-bucket.s3.amazonaws.com/2011...

The above is just an example - there are a number of things you could do with before_serve and define_url - you could use e.g. Redis or some key-value store to keep track of thumbnails.

You'd also probably want a way of expiring the thumbnails or destroying them when the original is destroyed, but this is left up to you as it's outside of the scope of Dragonfly.