Skip to content

A newly created function instance stalls its event loop for ~24s before any application work #1124

Description

@teetangh

What was measured

A newly created Netlify function instance stalls its event loop for roughly 24 seconds on its first invocation, before any application work. This is the whole of the ~30 s tail on public routes, and it is what made the Prisma connect budget look broken in #1120 — a setTimeout cannot fire on a blocked loop, so pg's 3 s connect timer reported at ~26 s.

Measured on deploy preview 1123 with a temporary diagnostic route that ran 400 ms of pure idle await, touching no database at all, while sampling event-loop lag every 50 ms. Forty invocations across two 20-way concurrent rounds:

instance age process.uptime() invocation 400 ms idle phase took max loop lag first DB attempt
40 ms 3,363 ms 1 23,905 ms 23,746 ms ok in 862 ms
39 ms 3,361 ms 1 24,634 ms 24,534 ms ok in 866 ms
100 ms 3,355 ms 1 24,823 ms 24,655 ms ok in 1,037 ms

The remaining 37, every one of them on an instance at least 47 s old, completed the same idle phase in 400–453 ms with 1–70 ms of lag. So the stall is bimodal, it is confined to brand-new instances serving invocation 1, and the database is fast on both sides of it.

Why it matters

It is the only remaining cause of 30-second page loads on the public routes, and it scales with instance creation, which means it fires exactly when traffic arrives in a burst. It cannot be mitigated from application code: #1123 established that no timeout value can bound it, and shipped the only two things that help — retrying past it, and serving from the ISR cache so the function is not invoked at all.

What is not yet known

The cause of the stall is not identified. The obvious candidate is cold-instance JavaScript evaluation and GC at Lambda's memory-proportional CPU share, but that is inference. The facts in hand:

  • AWS_LAMBDA_FUNCTION_MEMORY_SIZE is 1024, so the function gets roughly 0.58 vCPU.
  • V8 reports a heap limit of 1,018 MB, and RSS at rest is 675–795 MB. The instance is running close to its container.
  • process.env.NODE_OPTIONS is null inside the function, so netlify.toml's --max-old-space-size=6144 is not leaking into the runtime and letting V8 overrun the container. That hypothesis is ruled out.

Worth trying, in rough order of expected value

  1. Raise the function's memory, which raises its CPU with it. Netlify does not expose this on the current plan, so this is a conversation with Netlify rather than a config change, but it is the most direct lever if the stall is CPU-bound.
  2. Reduce what the server handler has to evaluate on a cold instance. perf: trim optimizePackageImports to packages Next does not already handle #1109 already trimmed optimizePackageImports; a bundle analysis of the server handler specifically would say whether there is more to take out.
  3. Confirm the cause before optimising anything. A CPU profile of a cold invocation would settle it, and the diagnostic-route technique in fix: stop caching degraded renders, and correct the root cause of the 30s tail #1123 is the way to get data out — the function log strips our console.* and does not expose instance identity, so the response body is the only reliable channel.

Follow-up to #1120. Related: #1117 (PG_POOL_MAX=1), #1122 (console.* is stripped in production).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions