Skip to content

[pull] canary from vercel:canary - #1363

Merged
pull[bot] merged 2 commits into
code:canaryfrom
vercel:canary
Sep 3, 2026
Merged

[pull] canary from vercel:canary#1363
pull[bot] merged 2 commits into
code:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Sep 3, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

lukesandberg and others added 2 commits September 2, 2026 21:51
## What?

Adds per-family compression configuration to `turbo-persistence` and
configures the Turbopack filesystem cache according to each keyspace's
access pattern:

| Family | Compression | Reason |
| --- | --- | --- |
| Infra | LZ4 HC level 4 | Preserve LZ4 decode performance while
improving write-time compression |
| TaskMeta | LZ4 HC level 4 | Preserve latency-sensitive metadata reads
|
| TaskCache | LZ4 HC level 4 | Preserve latency-sensitive task-cache
reads |
| TaskData | zstd level 3 | Prioritize the dominant disk-size
opportunity |

The low-level configuration exposes only the three presets this database
needs: LZ4, LZ4 HC4, and zstd3. Compression is applied consistently to
SST blocks, blob values, and compaction output. Each database owns a
`ThreadLocal` that lazily creates one lock-free zstd decompressor per
participating thread; the linked zstd reports each context as about 96
KiB, and all contexts are released when the database drops.

Each meta file stores its family's length-prefixed, bincode-encoded
compression preset once. SST and blob headers remain unchanged,
including the 12.5% minimum-savings fallback to uncompressed blocks. On
normal database open, the stored marker must match the runtime
`FamilyConfig`; a mismatch, invalid/truncated preset, or pre-marker meta
file is rejected before reads are served. This intentionally requires a
fresh/versioned cache after a codec change, consistent with
`turbo-persistence`'s existing no-cross-version-compatibility policy.

The marker also removes the duplicated keyspace-to-codec mapping from
`sst_inspect`: it reads the codec directly from metadata.
`turbo-persistence-tools` can inspect metadata without supplying a
Turbopack-specific family configuration.

## Why?

LZ4 decode performance is important for cache query latency, but using
one codec for every keyspace leaves a substantial disk-size opportunity
in TaskData. The families do not have the same tradeoff: metadata and
task-cache reads should stay on the LZ4 decoder, while TaskData can
spend modestly more CPU to reduce persistent cache size.

The final three-workload A/B reduced fresh cache directories by **20.61%
overall**, with no measured cold/write or warm/read regression (the
observed -3.42% / -2.39% should be treated as noise, not a speed claim).

## How?

### Real Next.js cache A/B

The complete A/B was re-run after the final fixed-preset,
bincode-marker, and database-owned thread-local revision. Both the
unchanged base worktree and final proposed working tree were
bootstrapped with `pnpm install --frozen-lockfile` and `pnpm build-all`,
using locally compiled native bindings on the same 8-vCPU Intel Xeon VM
(16.3 GiB RAM, Linux 6.18.40, Node 24.14.1). Each workload used three
fresh-cache builds and five warm-cache rebuilds; the table reports
medians.

```sh
TURBO_ENGINE_IGNORE_DIRTY=1 NEXT_TELEMETRY_DISABLED=1 \
  node packages/next/dist/bin/next build <fixture> --turbopack
```

`test/e2e/filesystem-cache` additionally used `ENABLE_CACHING=1`. The
other fixtures temporarily enabled
`experimental.turbopackFileSystemCacheForBuild`; those fixture changes
are not in this PR.

| Workload | Cache size | Cold/write | Warm/read |
| --- | ---: | ---:| ---: |
| `test/e2e/filesystem-cache` | **-20.62%** | -2.24% | -3.00% |
| `test/e2e/app-dir/app-rendering` | **-20.11%** | -6.04% | -1.47% |
| `test/e2e/app-dir/client-reference-chunking` | **-21.02%** | -1.45% |
-3.56% |
| Combined medians | **-20.61%** (96.810 → 76.862 MiB) | **-3.42%** |
**-2.39%** |

Per-family inspection of a final filesystem-cache build, using the
marker-derived codec:

| Family | Total file size | Value-block savings |
| --- | ---: | ---: |
| Infra | 52 B | n/a |
| TaskMeta | 6.31 MB | 29% |
| TaskData | 18.17 MB | 66% |
| TaskCache | 1.65 MB | n/a |

The originally planned `app` and `app-static` fixtures could not run as
standalone builds: `app` hit the existing Edge-runtime `process.cwd()`
build failure, and `app-static` required an external data endpoint
during page-data collection. They were replaced with the two successful
repository fixtures shown above under the same base/proposed
methodology.

### Temporary crate benchmark instrumentation

The codec-specific Criterion cases were used locally to gather receipts,
then removed from the delivered patch per review.

| Codec | Synthetic write | vs LZ4 | Uncached get | Cached get | DB size
|
| --- | ---: | ---: | ---: | ---: | ---: |
| LZ4 | 38.173 ms | baseline | 7.3004 µs | 10.735 µs | 21.59 MiB |
| LZ4 HC4 | 256.31 ms | **+571.7%** | 3.4853 µs | 8.5467 µs | 21.57 MiB
|
| zstd3 | 57.790 ms | **+51.4%** | 6.2460 µs | 9.5323 µs | 21.49 MiB |

The synthetic workload applies one codec to the entire database and
barely separates codecs by size, so it is not representative of the real
TaskData distribution. The all-HC write regression is real but confined
to smaller families in the actual configuration. Cached reads do not
decompress blocks, and the read samples contain substantial
ordering/outlier noise. The real-cache A/B is the read-regression guard.

## Vercel Site

### Size

2.9G	canary
2.2G	this PR

a 25% savings

### Performance

Cold build (n=5), warm build (n=3), medians:

| Scenario | Metric | Canary | Compression | Δ |
|---|---|---|---|---|
| **Cold** | wall (s) | 71.28 | 72.86 | +2.2% |
| | user (s) | 555.21 | 570.23 | +2.7% |
| | sys (s) | 94.49 | 95.00 | +0.5% |
| | maxRSS (GB) | 17.95 | 17.98 | +0.2% |
| **Warm** | wall (s) | 15.00 | 15.32 | +2.1% |
| | user (s) | 7.59 | 9.98 | +31% |
| | sys (s) | 12.79 | 12.87 | +0.6% |
| | maxRSS (GB) | 3.78 | 3.86 | +2.0% |

maxRSS converted at 1 GB = 2^30 bytes.

Looking at tracing data i see in a cold build `persist` span went from
5.06s duration using 63s cpu time to 6.04s using 73s cpu time, a ~20%
regression. Which explains the cpu regression and the time progression.

Because the time regression is in the `persist` span during shutdown the
added latency is somewhat hidden.

Of course we also see a small wall/user time regression in warm builds
due to the extra decompression costs for zstd.

### Verification

- `cargo test -p turbo-persistence` (79 tests)
- `cargo test -p turbo-tasks-backend` (99 unit tests plus integration
suites)
- `cargo test -p turbo-persistence-tools`
- `cargo test -p turbo-persistence --all-targets --no-run`
- `cargo fmt --all -- --check`
- `cargo clippy -p turbo-persistence -p turbo-persistence-tools -p
turbo-tasks-backend --all-targets -- -D warnings`
- `cargo check -p turbo-persistence -p turbo-persistence-tools -p
turbo-tasks-backend`
- `pnpm lint-ast-grep`
- `pnpm build-all`

<!-- NEXT_JS_LLM -->


<!-- fleet 621238c3-5a53-431b-8661-8c5b2920d55d -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Sep 3, 2026
@pull pull Bot added the ⤵️ pull label Sep 3, 2026
@pull
pull Bot merged commit 69ec884 into code:canary Sep 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant