Skip to content

A photo was uploaded once. Why did the image service generate 40,000 files?

The File Left the Origin. Was It Safe to Deliver? · Part 2 of 6 · 5 min read

Read the worked explanation ↓

A product launches 1,000-pixel profile banners after previously supporting only 64- and 256-pixel avatars. Design the migration, cache identity, abuse limits and gradual format rollout.

Think first

A product launches 1,000-pixel profile banners after previously supporting only 64- and 256-pixel avatars. Design the migration, cache identity, abuse limits and gradual format rollout.

Take a few minutes to form an approach, then compare it with the worked explanation.

Reveal the worked explanation

Interview question. A social application stores one original for each uploaded image. A new service lets clients request arbitrary width, height, quality, crop, and output format through a URL. Storage and transformation CPU suddenly explode. Design image resizing for a billion photos without making every unique request a new expensive asset.

Start with the first request: a phone asks for photo-73 at 240 pixels wide. The image service fetches the original from object storage, decodes it, resizes it, encodes WebP and returns the result. That sounds efficient because nobody generated thumbnails before they were requested. Then a team adds a flexible query string. Clients request widths 239, 240, 241 and 242, two dozen quality settings, several aspect ratios, and random crop coordinates. Every slightly different URL becomes a separate candidate for a cache miss and a transformation.

The problem is not that a billion photos are inherently impossible to resize. It is that the API created an almost unbounded number of representations for each photo. If clients may pick 1,000 widths, 100 heights, 20 quality levels, four formats, and five crop modes, that is 40 million possible variants per original. Most will never be useful, but a small fraction of malicious or accidental requests can still exhaust CPU and storage. The product needs a representation policy before it needs more workers.

I would start with a small approved set of widths, perhaps 160, 320, 640, 1,024 and 1,600 pixels, and a few well-defined aspect-ratio families. Map near-identical requests onto a normalized preset, with a documented maximum image dimension and output size. Permit only supported encoders. The URL should refer to a canonical transformation specification, not an arbitrary serialized parameter object that can be rearranged into thousands of equivalent cache keys.

There are two genuinely different workload choices. A feed thumbnail viewed millions of times is worth generating proactively or keeping warm. An unusual image in an infrequently opened album can be generated on first request and cached. Many products use both. Pre-generation spends compute on variants that may never be read. On-demand generation trades a smaller initial bill for first-request latency and exposure to expensive cache misses. Measure actual demand by variant before assuming either option wins.

One immutable original fans out to a bounded number of canonical variants, with a transform service behind the CDN.
One immutable original fans out to a bounded number of canonical variants, with a transform service behind the CDN.

An effective cache key needs more than the photo ID. Consider original-version / crop-preset / width / encoder-format / transform-version. If the source photo changes but the URL does not, a cache may continue serving a variant derived from the old image. If the transformation library changes crop behavior, an old file with the same dimensions is not necessarily the correct new output. Version either the source or the transformation recipe, then publish immutable variant URLs. This avoids asking an entire CDN to instantly forget every prior image.

Do not let the viewer choose arbitrary source bytes. The transformation worker should resolve a permitted asset ID to an authorized original object. It should not accept any caller-supplied URL and fetch it, because that introduces server-side request forgery risks. Even when the source is public, restrict how much compressed input can expand in memory. A tiny compressed image may decode into hundreds of millions of pixels. Set decoded-pixel limits, memory budgets, timeouts and concurrency caps. Strip unnecessary metadata such as GPS coordinates when producing public variants.

Now suppose a popular post is shared and fifty edge locations miss on the same 640-pixel version. It can trigger repeated transformations unless intermediate caches and work coordination collapse identical requests. This resembles the cache-stampede mechanism from an earlier lesson, but the distinctive issue here is variant identity and the cost of a decode/resize/encode pipeline. Coalesce by canonical variant, limit per-original concurrent transforms, and optionally persist popular derivatives in object storage. Do not let one attacker generate ten thousand legitimate-looking preset combinations by modifying unrelated parameters.

For private photos, the cache and URL policy must respect the access contract. One safe design authorizes a request before producing a short-lived, scope-appropriate derivative URL. Another uses an authenticated image proxy that bypasses shared caching for sensitive results. A signed transform URL can restrict which operations are allowed, but signature validity alone does not decide whether the holder should retain access after a permission revocation. Work through that separately instead of assuming a CDN can solve authorization.

Operationally, measure variant cardinality per original, transform CPU per output megapixel, cold-hit latency, decoder failures, cache hit rate, source-byte fetch volume, and cost per thousand views. Keep an escape hatch for expensive formats and transformations. If a particular preset has negligible demand but dominates encoding cost, remove it or compute it asynchronously.

A strong interview candidate should be able to explain why the original object store can remain simple while the derivative layer handles a controlled number of representations. The crucial design is not a clever resizing function. It is controlling the set of representations, protecting the worker, and making cache identities match what users should actually see.

Go deeper

AWS's dynamic image transformation reference architecture uses CloudFront as the caching layer and an image-processing function behind an API endpoint. It includes request normalization to improve hit ratio and optional signed requests. In production, choose where authorization happens based on whether images are public or tenant-private. The image decoder's memory requirements can be much larger than compressed source size, and content-format negotiation through Accept can create another representation dimension. Either include a normalized format variant in the cache key or serve separate stable format-specific URLs. Image URL signatures should cover normalized transformation parameters and source identity.

Check your reasoning

  • Compute why unbounded width, height and format options create huge variant spaces.
  • Normalize variants to a limited approved representation policy.
  • Include source and transformation recipe identity in cache keys.
  • Distinguish on-demand generation from eager popular presets.
  • Protect decoding, authorization and storage against adversarial inputs.

Common mistake: The original photo is immutable, so every generated image can share one cache key.

Which representation policy prevents derivative explosion while keeping cache entries correct?

Read the answer explanation

Bound allowed transforms and key by source version, output and recipe version — Distinct output representations require distinct cache identity and an economically bounded transformation surface.

Apply it elsewhere

A video app creates storyboard contact sheets at many playback offsets. Decide which derivative parameters should be bounded and which should be precomputed.

Compare your approach

Bound transform options, distinguish proactive and on-demand generation, include source and recipe identity in keys, protect decoder resources, and define cache and authorization boundaries.

One more constraint

If the original image is immutable, does that alone guarantee all of its resized variants can share the same cache key?

Quick revision

On-demand transformations are useful until arbitrary parameters create unbounded variants. Normalize requests into a controlled set of dimensions and formats, then key derivatives by original version, crop, output format and transformation version. Cache and sometimes persist popular outputs. Limit decoded pixels, source origins, CPU, memory and transform concurrency. Keep security checks separate from caching. The best architecture serves the small collection of representations the product really needs, not every representation a URL can name.

References