Skip to content

perf(runtime): deduplicate concurrent client manifest loads - #528

Merged
Brooooooklyn merged 2 commits into
voidzero-dev:mainfrom
yangshun:perf/single-flight-asset-manifest
Oct 21, 2026
Merged

Brooooooklyn merged 2 commits into
voidzero-dev:mainfrom
yangshun:perf/single-flight-asset-manifest

Conversation

@yangshun

Copy link
Copy Markdown
Contributor

Issue

createManifestLoader() caches the client manifest after fetching and parsing it. Concurrent HTML renders on a fresh instance can all miss that cache and load separate copies.

This duplicates work during overlapping cold loads. Warm requests already reuse the cached manifest.

Fix

Share one pending promise across concurrent manifest loads. Cache successful results and clear the pending promise when it settles, allowing missing manifests and failed loads to be retried.

Verification

Added unit coverage for concurrent loads sharing one fetch and manifest object, completed-cache reuse, and retries after a missing response or rejected fetch.

  • vp test run packages/void/test/unit/routing-runtime.test.ts: 40 tests passed on Node 24.21.0.

Benchmarked eight simultaneous cold SSR requests using real generated manifests, with no artificial asset delay. Median burst completion times across nine shuffled runs on Node 24.21.0:

Generated pages Before After Manifest reads/parses
1,000 28.52 ms 21.27 ms 8 → 1
10,000 120.55 ms 49.43 ms 8 → 1

Single-request controls performed one load on both revisions; warm bursts performed zero additional loads. HTML output matched, and browser hydration and navigation checks passed.

These results describe concurrent cold bursts. Production frequency and full SSG build impact have not been measured.

yangshun and others added 2 commits October 21, 2026 14:54
## Problem

`createManifestLoader()` caches the parsed `/.vite/manifest.json` only
after the fetch and parse finish. Requests that arrive together on a
cold process or isolate all miss the cache, so each one fetches the
manifest from `ASSETS` and parses it again. Nothing sets
`globalThis.__VITE_MANIFEST__`, so production always takes this path.

The manifest grows with the app. In the 5,000-page benchmark app it is
2,056,949 B. With 8 concurrent cold calls, the loader made 8 fetches
and 8 `response.json()` parses totalling ~28 ms, and returned 8
distinct manifest objects. The parses run on the same thread, so they
also delay the other requests. Later calls hit the cache.

## Fix

Concurrent cold calls now share one in-flight load. The shared promise
is cleared when it settles, so failures behave as before:

- A non-ok response returns `null` and is not cached. The next call
  fetches again.
- A rejected fetch rejects every caller that shared the attempt. The
  next call fetches again.

## Impact

Measured with a probe that calls the built loader 8 times at once on a
cold instance. A stub `ASSETS.fetch` serves the 5,000-page benchmark
manifest verbatim after 5 ms. Node 24.14, 7 fresh processes per
variant, median (range):

| | Before | After |
|---|---:|---:|
| `ASSETS.fetch` calls | 8 | 1 |
| Total `response.json()` time | 27.9 ms (25.9–30.3) | 3.6 ms (3.5–3.7) |
| Wall time for the 8 calls | 44.2 ms (42.9–47.4) | 11.5 ms (11.2–11.7) |
| Distinct manifest objects | 8 | 1 |

Only the first burst of requests on each cold process or isolate
changes. Warm requests already hit the cache.

## Verification

- `vp test run packages/void/test/unit/routing-runtime.test.ts`: 40
  passed. The new concurrency test fails on the old loader (8 fetches).
- `vp run check`, after `vp run build:all`: 33/33 passed.
- `vp run test --base origin/main`: 508 files selected, 507 passed and
  1 skipped; 10,744 tests passed and 31 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn self-requested a review October 21, 2026 12:15
@Brooooooklyn
Brooooooklyn merged commit 6ab6559 into voidzero-dev:main Oct 21, 2026
18 checks passed
@yangshun
yangshun deleted the perf/single-flight-asset-manifest branch October 21, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants