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.
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.
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:
[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.
before handler import
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.