Skip to content

Cold-start follow-ups: remaining lazy-loading candidates from the ACP eager-closure audit #7264

Description

@doudouOUC

Body

Context

Follow-up to #4748. An esbuild-metafile audit of the ACP child process at de962a5ecf measured an eager static import closure of 17.24 MiB / 2420 modules — all of it parsed and evaluated before the child can answer initialize, on every cold start.

The largest single cluster (telemetry OTLP exporters + NodeSDK + instrumentations, 2.16 MiB / 429 modules) is addressed by the lazy-telemetry-SDK change (facade + dynamically imported implementation). Verified on the 2C4G benchmark host with 30 paired serial cold starts against the same baseline:

Scenario Metric (P50) Baseline Lazy SDK Delta
telemetry off (default) process → first session 1922.4 ms 1778.0 ms −144.4 ms (−7.5%)
telemetry off (default) cold POST /session 1237.2 ms 1154.8 ms −82.4 ms
telemetry off (default) daemon listener ready 687.5 ms 627.4 ms −60.2 ms
telemetry on (outfile) channel.initialize 1119.6 ms 1107.3 ms −12.3 ms

The telemetry-on delta is small by design: the 2.16 MiB still loads, just concurrently instead of on the blocking import chain — and on a 2-core box the concurrent load competes for CPU with session setup (import phase −50 ms, config/bootstrap phase +50 ms). That observation motivates candidates 1 and 2 below.

This issue tracks the remaining candidates from the audit so they aren't lost. Ordered by estimated payoff:

Candidates

  1. ✅ Completed — protocol-split the telemetry implementation module. Landed in perf(telemetry): lazy-load the SDK and split OTLP exporter chains by protocol #7276. Telemetry now dynamically loads only the configured OTLP protocol chain; outfile mode loads neither. On the 2C4G reference host, telemetry-enabled process → first session P50 improved by 50.8 ms and config construction dropped from 45.4 ms to 23.9 ms.
  2. ✅ Completed — defer ACP-child telemetry init until after the initialize response is sent. Landed in perf(cli): Defer ACP telemetry initialization #7558. In the final metrics-preserving rerun of 30 alternating paired cold starts, channel.initialize P50 improved by 43.8 ms and the paired median by 44.2 ms; cold process → first session did not show a statistically demonstrated change. Cost: a wider init window during which early spans and one-shot bootstrap events are dropped (same gate semantics as today, just longer).
  3. ✅ Completed — lazy @google/genai SDK import. Landed in perf(startup): lazy-load Google GenAI SDK on first use #7512. The SDK now loads on first real use instead of during ACP bootstrap. Maintainer A/B verification measured @google/genai in the ACP static closure at 755,788 → 0 bytes and a total closure reduction of 999,320 bytes; 2C4G validation measured process → first session P50 improving by 65.9 ms and peak RSS by 8 MiB.
  4. ✅ Completed — lazy undici loading and proxy-dispatcher assembly. Landed in perf(startup): Load undici lazily behind package-local dynamic imports #7455. All eight value-import sites now load undici through package-local single-flight dynamic imports while preserving proxy installation ordering. The ACP eager closure dropped by 2,057 KiB, process → first session P50 improved by 89.5 ms, and RSS after the first session dropped by 8.1 MB on the 2C4G reference host.
  5. ✅ Completed — first-use loading for iconv-lite encoding tables, @xterm/headless, and simple-git (~890 KiB combined). Landed in perf(core): Lazy-load first-use dependencies #7686. On the recorded prototype artifact, the ACP static closure fell from 13,405,027 to 12,314,617 bytes and all three packages reached zero static-closure bytes; on the 2-vCPU reference host, 30 alternating cold pairs improved process → first session P50 from 1877.7 ms to 1733.3 ms, channel.initialize P50 from 896.2 ms to 831.5 ms, and peak process-tree RSS P50 from 417.0 MB to 408.1 MB. The final merged commit was rebased, rebuilt, and guarded locally but did not receive a fresh remote 30-pair rerun; synchronous package-root encoding helpers remain compatible and CI locks these dependencies out of the ACP static closure.
  6. ✅ Completed — replace comment-json/esprima with a lightweight jsonc-parser editor. Landed in perf(cli): replace comment-json settings parser #7747. The ACP static closure fell from 12,449,869 to 12,145,099 bytes (−304,770 bytes, −2.45%); on the 2-vCPU reference host, 30 alternating cold pairs improved channel.initialize P50 by 35.39 ms, process → first session response P50 by 38.00 ms, and process → first session complete P50 by 48.51 ms, with 28/30 paired wins and paired-mean bootstrap intervals entirely below zero. Preheated process → session complete behavior was statistically neutral, the 10-second steady-state RSS interval spanned zero, and CI locks comment-json, esprima, and the jsonc-parser UMD build out of the ACP static closure.
  7. ✅ Completed — propagate the production serve compile cache to ACP children. Landed in perf(cli): Propagate compile cache to ACP children #7594. On the 2C4G reference host, 30 warm-cache pairs improved median channel.initialize by 176.6 ms and median process → first session by 199.0 ms, with 30/30 paired wins for both. The first empty-cache startup was 117.2 ms slower at the median, so the measured workload recovers the one-time cost on the second ACP startup; median peak process-tree RSS increased by 8.6 MB and the stable cache used 9.4 MB. User-provided, disabled, failed, and unsupported cache configurations remain unchanged, and live-daemon E2E verification confirmed the configured cache reaches ACP children without leaking the internal handoff variable.

Enabler for every step: extend the fast-path bundle guard's forbidden-package lists (as done for the OTLP/gRPC packages) so each newly lazified cluster is locked out of the eager closure by CI.

Acceptance discipline

Bytes do not convert to milliseconds — per the discipline established in #4748, every step must individually pass the 2C4G gate: 30 paired serial cold starts, reporting channel.initialize P50/P95, process → first session, preheated/warm behavior, concurrent first sessions, telemetry on/off compatibility, and peak RSS. Reference points: #7182 removed 1.16 MiB for −63 ms; the lazy telemetry SDK removed 2.08 MiB from the eager closure for −144 ms P50 (telemetry off).

Candidates should land as separate PRs so each one's regression surface stays reviewable and its gain is individually attributable.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions