Serving dynamically resized images at scale using Serverless

At zulily, we take pride in helping our customers discover amazing products at tremendous value. We rely heavily on highly curated images and videos to narrate these stories. These highly curated images, in particular, form the bulk of the content on our site and apps.

zulily-events.png

Two of our popular events from weekend of Oct 20th 2018

Today, we will talk about how zulily leverages serverless technologies for serving optimized images on the fly on a variety of devices with varying resolutions and screen sizes

In any given month, we serve over 23 billion image requests using our CDN partners. This results in over a Petabyte per month of data transferred to our users around the globe.

We use AWS Simple Storage Service (S3) bucket as the origin for all our images. Our in-house studio and merchandising teams upload rich images using internal tools to S3 buckets. As you can imagine, these images are pretty huge in terms of file size. Downloading and displaying these images as-is would result in sub-optimal experience for our customers and waste of bandwidth.

Architecture

Dynamic image resizing on the fly

Dynamic image resizing on the fly

Workflow:

Step 1: Full size images are uploaded by zulily studio to s3 using internal tools. We will talk about this in a future blog post

Step 2: Customers visit www.zulily.com in their browsers or open zulily app on their mobile device. zulily web servers return HTML markup which amongst other things contains image tags like the one shown below:

<img src="https://cfcdn.zulily.com/images/cache/event/237x237/319512/
5bc62d2cb29d8500012677e6/
319512_DesignWest_HP_2018_1018_BW3_202059.c2b31b5e-9e5e-4b2d-936b-
3ae03f1e8231.jpg" />

Notice that these image urls also contain preferred image resolution which varies from device to device.

Step 3: User’s device makes subsequent calls to CDN to download images like the one mentioned above.

Step 4: The first time a request is made to CDN, there is a cache-miss so the CDN sends the request back to the origin server. zulily s3 bucket is configured as a static website along with the following reroute rule:

<RoutingRules>
     <RoutingRule>
       <Condition>
         <KeyPrefixEquals/>
         <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
       </Condition>
       <Redirect>
          <Protocol>https</Protocol>
          <HostName><zuEndpoint>.<aws-region>.amazonaws.com</HostName>
          <ReplaceKeyPrefixWith>prod/Resize?key=</ReplaceKeyPrefixWith>
          <HttpRedirectCode>307</HttpRedirectCode>
       </Redirect>
     </RoutingRule>
</RoutingRules>

Step 5: Since the resized image is initially missing in s3 bucket, it results in 404 and request gets redirected to API Gateway with status code of 307.

Step 6: API Gateway proxies the call to Lambda function. Our Resize lambda function is written in node.js and it does the following:

  • Extract link to the full size image URI – mask out the resolution from the image uri eg 237×237 in the example above
  • Resize image using sharp module
  • Persist the resized image back into the s3 bucket
  • Return 301 with location header to the resized image url

We evaluated python and java but finally settled on node.js as our language of choice for lambda. In our experiments, node.js had the lowest memory footprint as well as startup time.

Step 7: Browser follows the HTTP redirect status code and displays the resized image.

During peak, we generate over 60k resized images per minute with average end to end latency under 50 milliseconds.

Serverless technologies have enabled us to scale our workflows such as the one mentioned above while significantly reducing our total cost of ownership.