P99 spikes that jump 5–50× during unpredictable traffic bursts kill conversions and push costs up. Operators running APIs or SSR often see clean medians but intermittent tail latency tied to environment init or cache churn. Telling a 500–2000ms spike apart as a cold start, miss, or provider init step remains critical for uptime, SLOs, and budgeting.
Serverless cold starts cause sporadic high-latency spikes when execution environments initialize. Edge caches give consistent low-latency hits but add cache-miss complexity. The article describes a reproducible benchmark design, a CI flow, and lists required artifacts such as repo layout, k6 scripts, Terraform modules, and OpenTelemetry configs. The article does not publish the actual benchmark CSVs, graphs, or runnable example scripts yet.
A corrected statement should reflect what is present: it describes a reproducible benchmark suite with repo layout, CI steps, and the exact artifact schema (CSV columns and trace span names) required to compare cold-start-dominated origin fetches versus edge-hits; it should include or link to a reference run's CSV and graphs so results are immediately reproducible.
Comparative quick
This table summarizes the main trade-offs across architectures and providers.
| Architecture |
Typical median TTFB |
Typical p99 |
Primary latency source |
Best for |
Example providers |
| Edge cache only |
5–50 ms |
5–100 ms |
PoP network + cache miss to origin |
Static SSR, CDNable APIs |
Cloudflare, Fastly, Akamai |
| Serverless on-demand |
50–200 ms |
200 ms–2 s |
Runtime init, cold start |
Low steady traffic, infrequent compute |
AWS Lambda, GCP Cloud Run, Azure Functions |
| Serverless provisioned |
50–150 ms |
50–300 ms |
Handler execution, DB latency |
Personalized APIs, strict p99 SLOs |
AWS Lambda w/ provisioned, Azure Premium |
| Edge compute (WASM/V8) |
10–80 ms |
20–200 ms |
Cold init of isolate or WASM module |
Light dynamic logic, auth at edge |
Cloudflare Workers, Fastly Compute@Edge |
When to pick edge cache
Edge caching works best when most responses are identical across users. High cache-hit ratio gives stable p99 and lowers origin cost. Edge removes origin compute from the main path, which reduces variation.
A single test run will reveal cache-hit ratios and p99 bands.
When to pick serverless provisioned
Provisioned serverless fits dynamic work with strict tail latency needs. A provisioned pool removes cold starts at a steady price. This option suits authenticated APIs with per-request state.
A small provisioned pool plus edge cache can cut cost and preserve p99.
Serverless on-demand: when and why
This section explains real causes of cold-start latency and what to measure. Cold starts happen when the execution environment is not warm and must initialize. The init cost combines container restore, runtime startup, and connection setup.
Init sources and observable signals
Runtime startup includes language VM, JIT, and dependency loads. Container snapshot restore adds time for microVMs or containers to become runnable. Connection setup shows in traces as DB or TLS spans during init.
Common measurement mistakes
The most frequent error is blaming slow responses solely on cold starts. Many slow requests come from cache misses, network retries, or backend spikes. Use detailed spans to avoid misattribution in alerts and postmortems.
A careful trace plan prevents false conclusions.
Provider init differences
AWS uses Firecracker microVMs, introduced to provide isolation and control cold-start behavior. Cloudflare relies on V8 isolates and WASM at the edge, giving smaller init times. GCP and Azure use container-based models where image size and concurrency matter.
Measuring cold-start vs cache-miss reliably
This section gives a trace and metrics plan that separates misses from cold starts. The method uses span markers, synthetic probes, and boolean tags in traces. Collect both CDN logs and function traces to correlate PoP-level misses with init spikes.
Span design and attributes
Design spans for cache_lookup, origin_fetch, init, and handler execution, and tag each span with cache.hit=true or false. Emit an init span at process startup with init=true and init_ms. The recommended approach marks cold_start=true when a trace contains an init span or when no prior init span for the same instance_id was observed within a warm window.
This replaces the unsupported claim that cold_start can be set arbitrarily and provides a reproducible method for setting the attribute. Query traces by these tags to compute split latencies for each path.
Synthetic probes to force conditions
Issue controlled requests that bust the cache to create deterministic misses. Run warmers that exercise the function without network calls to measure pure init. Compare forced-miss timings with normal traffic distributions.
Example observability queries
Calculate cold-start rate by counting spans where cold_start=true divided by total. Compute p99 by grouping traces by cache_hit and cold_start tags. A useful query: SELECT percentile(latency,99) BY cache_hit, cold_start.
Measure the pure init time by deploying a no-op handler that performs zero network I/O and records init end time. Use that value as the baseline init cost to subtract from origin_fetch timings when diagnosing slow traces.
Instrument the handler to emit these spans in order: cache_lookup (attribute: cache.hit=true/false, cache.key), origin_fetch (attribute: origin.status, origin.addr), init (attribute: init=true, init_ms), and handler (attribute: handler_ms). To infer cold_start reliably, use a heuristic such as: cold_start=true when an init span exists for a trace and no prior init span with the same process.resource 'instance_id' has been observed within the warm window (e.g., 5 minutes).
Example aggregation: compute p99 origin_fetch latency for traces where cache.hit=false and cold_start=true, then compare with cache.hit=false and cold_start=false to separate miss-only origin latency from miss+cold-start latency.
For commonly used backends, provide sample queries, e.g., in an APM that supports tag grouping: SELECT percentile(origin_fetch_ms, 99) BY cache.hit, cold_start, and include an example trace excerpt showing the span timestamps and attributes so teams can copy the attribute names directly.
Reproducible benchmark suite and CI
This section outlines a benchmark repo, CI flow, and scenario list to reproduce results. A reproducible suite must include infrastructure, load scripts, and trace collection. Use GitHub Actions to run scenarios and preserve raw traces and CSV metrics.
The repo should include Terraform for infra, k6 for load, and an OpenTelemetry collector config. Provide scripts to deploy functions, set cache-control, and fetch CDN logs. Store artifacts as CSV: timestamp, region, cache_hit, cold_start, total_ms.
Benchmark scenarios to run
Scenario A tests single-user cold-start p99 with low concurrency. Scenario B simulates burst concurrency with 50 to 500 concurrent requests. Scenario C alternates cache fills and TTL-busted requests to compare miss paths.
CI workflow example
The CI deploys infra, seeds caches, runs scenarios, and collects traces. Artifacts upload to storage and a script aggregates p50, p95, and p99 per tag. Include a baseline warm-only run to capture pure init numbers for each provider.
ProcessCache Hit: client → PoP
Miss pathCache miss → origin fetch → init → handler
Cold StartInit-only measured by no-op handler
Visual guide: bars below show relative durations captured during benchmarks.
To make the analysis actionable, include a published, versioned CSV and a short summary of a reference run that compares cold-start-dominated origin fetches against edge-hits across regions. For example, a reproducible result set might include columns like timestamp, region, provider, route, cache_hit (true/false), cold_start (true/false), total_ms, init_ms, origin_fetch_ms, and mem_mb. From a single controlled run you can publish aggregated numbers such as p50/p95/p99 by (provider, region, cache_hit, cold_start).
A sample summary row could show that for one run in us-east-1 the edge cache p99 for cache_hit=true was ~45 ms. The same run might show origin fetches that incurred an AWS Lambda cold start had p99 around 1.2 s. Publishing those CSVs and a short graph allows teams to calibrate expectations and reproduce the analysis against their own origins and regions.
Recommend providing ready-to-run examples of the infra and load-generator steps so teams can reproduce the cold-start interaction immediately. A minimal reproducible bundle should include a Terraform module that deploys a small origin (no-op endpoint and a heavyweight endpoint that sleeps to emulate DB I/O), a k6 scenario that issues steady traffic plus configured bursts (example CLI: k6 run --vus 50 --duration 2m burst-test.js), and a warmers script that performs a simple invocation to record init end-time (for example, a no-op handler that emits an OTLP span named init.end with an attribute init_ms).
Provide a sample OpenTelemetry collector config and an example CSV output layout. Embedding these concrete pieces in the article removes guesswork. Readers can clone the repo, deploy the exact infra, and run the same k6 job to generate comparable p99, cache_hit ratio, and cold_start_rate outputs.
Cost model: compute cost-per-ms and trade-offs
This section gives a numeric model to compare provisioned cost versus on-demand cold-start risk. The model uses provider price, memory allocation, and expected saved milliseconds. Apply the model to decide whether provisioned concurrency pays off for a given SLO.
Provisioned cost equals provisioned units times memory_gb times price_per_gb_s times seconds in month. On-demand cost equals requests times avg_duration_ms times price_per_gb_ms. Example: AWS Lambda provisioned for 100 concurrent 512MB functions costs more than on-demand at low traffic.
Heuristics to provision
Provision when the cost of user-visible latency exceeds monthly provisioned cost. A rule: if cold_start_rate times traffic times p99_latency_penalty_cost exceeds provisioned_cost, provision. Consider a hybrid approach combining a small provisioned pool with edge caching for reads.
Pricing reference and sources
AWS announced Lambda SnapStart to reduce Java init times, while Firecracker was introduced earlier. Cloudflare Workers launched years ago and uses V8 isolates for lower init overhead. For provider docs see AWS Lambda docs and Cloudflare Workers docs.
Provider differences and recommendations
This section recommends provider-specific tweaks to reduce cold starts and improve cache behavior. Recommendations include runtime choices, memory tuning, and connection reuse patterns. Different providers map init sources to distinct mitigation levers.
AWS specifics and tactics
AWS cold starts often include Firecracker microVM restore cost and Java init work. Use provisioned concurrency or SnapStart for high-p99 Java workloads. Open DB connections lazily or use a connection pooler to avoid per-init DB cost.
Cloudflare and edge compute tips
Edge workers use V8 isolates or WASM modules with small init footprints. Push auth and personalization logic to edge where safe to reduce origin hits. Use cache keys that include headers only when needed to keep hit ratios high.
The evidence points to using edge compute for light dynamic logic and provisioned serverless for heavy per-request state. This works well in practice for mixed workloads where cacheable assets sit at the PoP. A common case: SSR site with 80% static content and 20% user widgets leads to lower cost using edge cache plus small provisioned pool for widgets.
What nobody tells you about behavior at scale
This section lists non-obvious effects that influence real-world latency and cost. Many guides miss how invalidation causes origin bursts that look like cold-start storms. Small differences in runtime init or dependency I/O can multiply p99 during invalidation events.
Cache invalidation and thundering herd
Global invalidation can produce a sudden spike of misses across PoPs. Origin shielding and rolling invalidation reduce simultaneous origin load. Consider versioned keys to avoid mass-expire events.
Runtime warm-up and JIT effects
JIT warm-up can raise CPU during the first minutes of execution and distort p95 estimates. Measuring pure init with a no-op handler reveals actual runtime start cost. Most teams tune memory only and ignore JIT or dependency initialization costs.
Next steps: run the benchmark suite against candidate providers in the regions that matter for your users, compare p99 and cold_start_rate, and compute a cost-per-ms delta against your SLO. This single run will expose whether edge caching, provisioned concurrency, or a hybrid provides better value for your workload.
Frequently asked questions
What causes serverless cold starts?
Cold start occurs when the execution environment must be initialized before running code. Initialization includes creating or restoring a container or microVM, starting the language runtime, and loading dependencies. Measure init-only functions to isolate that time from network latency and DB setup.
How to tell a cache miss from a cold start in traces
Look for a cache_lookup span with cache.hit=false immediately before origin_fetch. A cold start shows an init span and first-use flags like cold_start=true and DB connect spans during init. Correlate CDN logs and function traces by request ID.
What are the best edge-cache patterns for SSR?
Use cache-control headers with stale-while-revalidate for user-tolerant content. Version keys on deployment to avoid mass invalidation and use origin shielding to limit origin load. Keep personalization out of the main cache key when possible.
How much can provisioned concurrency reduce p99?
Provisioned concurrency removes cold starts for the provisioned units and greatly narrows p99 for those requests. Its cost-effectiveness depends on traffic volume and the monetary value of saved milliseconds. Run the cost model with actual traffic numbers to decide.
How to benchmark cold starts across multiple providers
Use a reproducible suite with Terraform, k6, and OpenTelemetry to deploy and measure each provider. Run identical scenarios per region, collect traces, and compare p50, p95, p99, cold_start_rate, and cache_hit ratio.
Final recommendation and next steps
Pick edge for mostly cacheable workloads that need predictable p99 and lower origin cost. Pick provisioned serverless for heavy personalization or strict per-request latency SLOs where cold starts are unacceptable. Run the provided benchmark scenarios on your stack and compute cost per saved millisecond to make the final choice.
When an application is fully static and served entirely by a CDN, the guidance here on serverless cold-starts does not apply. Also skip complex caching when team resources cannot maintain invalidation and coherence safely.
To decide quickly, run the benchmark suite described here against your real origin and compare p99, cold_start_rate, cache_hit ratio, and effective cost per millisecond before choosing architecture.
Contact the repo maintainers for support or suggested next steps and to obtain reference artifacts if they are published.
Closing note
The data and steps above let a team move from opinion to evidence when choosing between edge caches, on-demand serverless, and provisioned serverless.
Which providers have the lowest cold-starts today?
Edge-focused platforms with V8 isolates or WASM generally show the smallest init times. Cloudflare Workers and Fastly Compute@Edge are examples of low-init platforms, while container-based providers vary by image size and concurrency.