Manan Vaghasiya

On the fly image conversion at Edge

28 Sept 2019

Running a large CloudFront distribution can be costly if the assets aren't optimized. Additional users might see blank placeholders everywhere while images are loading and increased data usage. In the developing market having most users on mobile devices using mobile data, this can be a deal-breaker.

There are several ways this problem can be approached.

  1. Convert images to needed sizes immediately after they are uploaded.
  2. Convert images as they are requested on the fly thanks to Lambda@Edge.

Option 2 seems more efficient and more convenient as there are no changes required on your application back-end and no need to worry about scaling problems.

Let's get into the implementation. CloudFront provides several times in request LifeCycle when a Lambda@Edge function can be invoked. Looking at the documentation, those would be

  • After CloudFront receives a request from a viewer (viewer request)
  • Before CloudFront forwards the request to the origin (origin request)
  • After CloudFront receives the response from the origin (origin response)
  • Before CloudFront forwards the response to the viewer (viewer response)
Lambda@Edge lifecycle

For our simple use case of creating couple of different versions of image, we are most interested in "origin response" point of the request life-cycle.

Here is how complete flow of process will work.

  1. We have a image file in S3, with Key something like /assets/test.jpeg
  2. The client will make a request for a URL that we sent it from application server, which looks something like https://xxxxx.cloudfront.net/assets/test-small.jpeg
  3. For initial request, our "origin response" Lambda function will be invoked with arguments implying that file was not found in the origin bucket. In this case we extract the file path from the URL and remove -small from it and request the original file using that URL. Then we compress/resize it and save it into the origin bucket and send it as a response to the request.
  4. The subsequent times user requests that "small" image, it will already be present in the origin bucket and you won't need to compress it again, just return the existing image from Lambda function.

In my current implementation, I have used a handy Node package called Sharp to manipulate images. It can be some work to make it work on Lambda, but the effort is worth the time as the package is excellent to work with.

Note that the Lambda function will still be invoked event though the image is already processed, in that case the image will just be returned as is.

Costs

After having similar implementation in production, having more than 18M invocations and 0.1M seconds of duration, costs around ~38$, not that bad. Note that these are just Lambda costs.

Where to go from here?

The solution describe in this post is pretty simple, yet it can be customized into doing more than just resizing the images. For example, here we have used -small in the request URL as an indicator as to what would we like to do with the image, the URL can be used to do other things like dynamic resize, filters like blur etc. Such solutions are readily available too.