← Writing
INFRA18 AUG 2026

Cold starts are lying to you

A function that answers in 40 ms and a function that answers in 1.9 s are the same function. The difference lives somewhere your dashboard is not looking.

A. Rivas14 min read · 5 figures

Every serverless postmortem I have read starts with the same number: p99 latency. It is the least useful number in the building. It averages together two entirely different machines — one that already exists and one that is being built while your user waits. One that already exists and one that is being built is not a distribution. It is two populations wearing the same label.

Split them and the picture gets boring in the best way. Warm invocations are flat and fast and uninteresting. Cold invocations are a build system pretending to be a request handler.

FIG 1 · COLD PATH
download
runtime
import PIL
handler
76% of the cold path is one import.

Where the time goes

The runtime does four things before it does yours: it fetches your archive, unpacks it, boots the interpreter, and runs every top-level statement in your module. Only the last one is yours, and only the last one is what you profiled locally, where the shared objects were already in the page cache.2

The Pillow trap

This is where I lost a weekend. A wheel built on my laptop links against libraries the runtime does not have, and the failure arrives dressed as a Python problem rather than a linker problem:

CloudWatch · /aws/lambda/thumbnailer
[ERROR]Runtime.ImportModuleError: Unable to import module 'handler':
  cannot import name '_imaging' from 'PIL'
START RequestId: 8f3c… Init Duration: 1 907.44 ms

Layers change the shape

A layer is not a package manager. It is a second archive the runtime unpacks into /opt before your import path is built — compiled against the runtime's own image, so the linker stops arguing.

FIG 2 · WHERE THE BYTES COME FROMMOUNT ORDER
LAYER · KLAYERS-PYTHON312-PILLOW
PIL/_imaging.so2.1 MB
libjpeg.so.62436 KB
libz.so.192 KB
UNZIP
RUNTIME · /opt/python
PIL/_imaging.sook
libjpeg.so.62ok
libz.so.1ok
sys.path gains /opt/python
before handler import
Your archive shrinks by 46 MB; the import still costs 1.3 s. You moved the bytes, not the work.

What to measure

Stop charting p99 of everything. Chart init duration as its own series, alarm on the ratio of cold to warm, and treat every top-level import as a line item in a budget you actually own.

A cold start is not slowness. It is construction work you scheduled inside a request.