Skip to content
Focal
Blog
  • image-processing
  • capacity-planning

What an image processing worker is, and how to size concurrency for your traffic

An image worker runs one transformation at a time. Combine your cache hit ratio, peak request rate and processing time with Little's law to size concurrency.

By Focal team 5 min read

An on-request image service does its expensive work on cache misses. The unit of that work is a worker, a slot that transforms one image at a time. Too few workers and requests queue during a sale. Too many and you pay for capacity that sits idle. This post explains what a worker does, how cache hits change the maths, and how to size concurrency from three numbers you can pull from your logs.

What a worker does

A worker handles one cache miss from start to finish. For each miss it runs these steps in order:

  1. It fetches the source image from the origin.
  2. It decodes the source into pixels.
  3. It crops, resizes and runs any filters.
  4. It encodes the result in the output format.
  5. It returns the result, which the cache then stores.

While a worker runs those steps, it cannot start another image. The number of workers is therefore the number of images the service can transform at the same moment.

Only cache misses need a worker

A cache hit returns a stored result and never reaches a worker. The service’s own cache gives one layer of hits, and a content delivery network (CDN) in front of the service adds a second.

The load on your workers is the miss rate, not the request rate:

misses per second = requests per second × (1 - cache hit ratio)

The hit ratio has a large effect. At 400 requests per second, these are the miss rates:

Cache hit ratio Misses per second
90% 40
95% 20
99% 4

Moving from 95% to 99% cuts the work by a factor of five. Check the hit ratio before you add workers.

Little’s law turns misses into busy workers

Little’s law states that L = λW. L is the average number of items in a system, λ is the average arrival rate, and W is the average time each item spends in the system. The law holds whatever the arrival pattern, the service time distribution or the order of service.

For an image service, the terms map directly:

  • L is the average number of busy workers.
  • λ is the number of cache misses per second.
  • W is the average time one transformation takes, including the fetch from the origin.

Take 20 misses per second and an average of 0.25 seconds per transformation. On average, 20 × 0.25 = 5 workers are busy. These inputs are an example. Your own numbers will differ.

Why the average is not enough

A pool that is busy 100% of the time on average has no room for bursts, and its queue grows without limit. Queueing models make this precise. In the M/M/1 model, a single server with random arrivals, the mean time in the system is 1/(μ - λ), where μ is the service rate. That formula rewrites as the plain processing time divided by (1 - utilisation):

Utilisation Time in system, as a multiple of processing time
50%
80%
90% 10×

Those multiples describe a single worker. Pools of several workers follow the M/M/c model, which gives different numbers but has the same stability condition. Utilisation has to stay below 100%. The practical rule is the same in both cases. Size for peak traffic and leave headroom.

Where misses come from

Misses do not arrive evenly. Plan for these sources of bursts:

  • A new site or a new image host starts with an empty cache, so every URL misses once.
  • A new width in srcset or a changed quality setting creates new URLs, and each one misses on first request.
  • A catalogue import adds thousands of new images at once.
  • A sale or launch sends visitors to product pages nobody viewed recently.
  • CDNs cache per location. Cloudflare’s documentation explains that without tiered caching, each data centre contacts the origin on its own miss.
  • Caches evict rarely requested items early. Cloudflare evicts least recently used content before its time to live (TTL) expires, and retention is not configurable.

What makes a transformation slow

The value of W depends on the work per image. Four inputs change it.

The source size sets the fetch time and the decode work. A 6000 by 4000 camera original holds 24 million pixels. A 2000 by 1333 copy holds about 2.7 million. Store sources no larger than the largest size you deliver.

The output format sets the encode time. The Next.js documentation states that AVIF generally takes 50% longer to encode than WebP.

The origin’s response time is part of W. A worker that waits for a slow bucket is a busy worker.

Filters add work. A blur or a sharpen pass touches every pixel of the output.

A worked sizing

This example uses illustrative inputs. Replace each one with a figure from your logs.

Step Value
Peak requests per second 500
Cache hit ratio at peak 96%
Misses per second 500 × 0.04 = 20
Average time per transformation 0.35 s
Busy workers at peak (Little’s law) 20 × 0.35 = 7
Target peak utilisation 70%
Workers needed 7 ÷ 0.7 = 10

The target utilisation is a choice. A lower target gives shorter queues and costs more.

Measure these three numbers

You need the peak request rate, the cache hit ratio at that peak, and the average transformation time. Take the rate from your busiest minute, not a daily average, because a daily average hides the burst you are sizing for. Read the hit ratio from your CDN’s cache status field for the same minute. Take the transformation time from the image service’s logs, and check that it includes the origin fetch.

How Focal counts workers

Focal prices by workers. The worker count is how many images Focal processes at the same time for your account, and cache hits use no worker. Plans start at $449 per month for 8 workers, and Focal includes bandwidth under fair use. The 14-day free trial lets you run real traffic through Focal before you pick a plan. The pricing page lists the plans.

Start with last month’s busiest minute. Multiply its request rate by one minus its hit ratio, multiply again by your average transformation time, and divide by the utilisation you are willing to run at. The result is the worker count to plan for.

Sources

More from the blog

All posts