Skip to content

[turbopack] Optimize compaction cpu usage#91468

Open
lukesandberg wants to merge 2 commits intocanaryfrom
compaction_sorting_and_iteration
Open

[turbopack] Optimize compaction cpu usage#91468
lukesandberg wants to merge 2 commits intocanaryfrom
compaction_sorting_and_iteration

Conversation

@lukesandberg
Copy link
Contributor

@lukesandberg lukesandberg commented Mar 16, 2026

Summary

Optimizes the turbo-persistence compaction and iteration paths with several targeted improvements:

Iterator optimizations

  • Flatten index block iteration — The iterator previously used a Vec<CurrentIndexBlock> stack, but SST files have exactly one index level. Inline the index block fields (index_entries, index_block_count, index_pos) directly into StaticSortedFileIter, eliminating the stack allocation and Option overhead.

  • Non-optional CurrentKeyBlock — Parse the first key block during try_into_iter() construction so current_key_block is always populated, removing the Option<CurrentKeyBlock> wrapper and its take()/Some() ceremony in the hot loop.

  • Replace ReadBytesExt with direct byte indexing — In handle_key_match, parse_key_block, and next_internal, replace val.read_u16::<BE>() etc. with u16::from_be_bytes(val[0..2].try_into().unwrap()). This eliminates the trait dispatch overhead, dead error-handling code, and mutable slice pointer advancement.

  • Extract read_offset_entry helper — Read type + offset from the key block offset table in a single u32 load + shift, replacing two separate ReadBytesExt calls.

Refcounting optimization

  • Introduce RcBytes — Thread-local byte slice type using Rc instead of Arc, eliminating atomic refcount overhead during single-threaded SST iteration. The iteration path (StaticSortedFileIter) now produces RcBytes slices backed by an Rc<Mmap>, so per-entry clone/drop operations are plain integer increments rather than atomic operations.

Merge iterator simplification

  • Optimize MergeIter::next h — Replaced the straightforwards pop/push pattern with PeekMut-based replace-top pattern, which means we only need to adjust the heap once per iteration instead of twice.

Benchmark results

Compaction benchmarks (key_8/value_4/entries_16.00Mi/commits_128), canary baseline vs optimized:

Benchmark Baseline Optimized Change
partial compaction 1.985 s 1.545 s -22%
full compaction 2.068 s 1.544 s -25%

On my machine, now we rarely hit 100% cpu usage during compaction (compaction is single threaded), so we can assume we are mostly IO bound.

Test plan

  • cargo test -p turbo-persistence — 60/60 tests passing
  • Compaction benchmarks run and compared against canary baseline

@nextjs-bot nextjs-bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels Mar 16, 2026
Copy link
Contributor Author

lukesandberg commented Mar 16, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lukesandberg lukesandberg mentioned this pull request Mar 16, 2026
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Mar 16, 2026

Failing test suites

Commit: 4a942b8 | About building and testing Next.js

pnpm test-dev test/development/app-dir/enabled-features-trace/enabled-features-trace.test.ts (job)

  • enabled features in trace > should denormalize inherited enabled features during upload (DD)
Expand output

● enabled features in trace › should denormalize inherited enabled features during upload

expect(received).toBeDefined()

Received: undefined

  106 |     )
  107 |
> 108 |     expect(renderPathEvent).toBeDefined()
      |                             ^
  109 |     expect(renderPathEvent.tags['feature.experimentalServerFastRefresh']).toBe(
  110 |       true
  111 |     )

  at Object.toBeDefined (development/app-dir/enabled-features-trace/enabled-features-trace.test.ts:108:29)

pnpm test-dev test/e2e/app-dir/use-cache/use-cache.test.ts (job)

  • use-cache > should cache results for cached functions imported from client components (DD)
  • use-cache > should cache results for cached functions passed to client components (DD)
  • use-cache > should update after revalidateTag correctly (DD)
Expand output

● use-cache › should cache results for cached functions imported from client components

expect(received).toBe(expected) // Object.is equality

Expected: "0 0 0"
Received: "0.5015421360926748 0.33010858422770295 0.21836613258769244"

  189 |
  190 |     await browser.elementById('reset-button').click()
> 191 |     expect(await browser.elementByCss('p').text()).toBe('0 0 0')
      |                                                    ^
  192 |
  193 |     await browser.elementById('submit-button').click()
  194 |

  at Object.toBe (e2e/app-dir/use-cache/use-cache.test.ts:191:52)

● use-cache › should cache results for cached functions passed to client components

expect(received).toBe(expected) // Object.is equality

Expected: "0 0 0"
Received: "100.82136933354006 100.76865221191873 100.13576271583308"

  211 |
  212 |     await browser.elementById('reset-button').click()
> 213 |     expect(await browser.elementByCss('p').text()).toBe('0 0 0')
      |                                                    ^
  214 |
  215 |     await browser.elementById('submit-button').click()
  216 |

  at Object.toBe (e2e/app-dir/use-cache/use-cache.test.ts:213:52)

● use-cache › should update after revalidateTag correctly

expect(received).not.toBe(expected) // Object.is equality

Expected: not "[a, c] 0.38283379387556105 0.5365682437393617"

  305 |     await browser.elementByCss('#revalidate-path').click()
  306 |     await retry(async () => {
> 307 |       expect(await browser.elementByCss('#a').text()).not.toBe(valueA)
      |                                                           ^
  308 |       expect(await browser.elementByCss('#b').text()).not.toBe(valueB)
  309 |       expect(await browser.elementByCss('#f1').text()).not.toBe(valueF1)
  310 |       expect(await browser.elementByCss('#f2').text()).not.toBe(valueF2)

  at toBe (e2e/app-dir/use-cache/use-cache.test.ts:307:59)
  at retry (lib/next-test-utils.ts:861:14)
  at Object.<anonymous> (e2e/app-dir/use-cache/use-cache.test.ts:306:5)

@nextjs-bot
Copy link
Collaborator

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 455ms 455ms ▅▅▅▁▁
Cold (Ready in log) 439ms 439ms ▃▃▄▁▁
Cold (First Request) 1.119s 1.158s ▃▃▃▁▁
Warm (Listen) 456ms 456ms ▅▅▅▁▁
Warm (Ready in log) 446ms 442ms ▄▃▃▁▁
Warm (First Request) 349ms 350ms ▃▃▃▁▁
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 456ms 455ms ▁▁▁█▁
Cold (Ready in log) 436ms 435ms ▃▃▃█▃
Cold (First Request) 1.874s 1.874s ▂▁▂█▂
Warm (Listen) 456ms 456ms ▁▁▁█▁
Warm (Ready in log) 435ms 435ms ▂▃▃█▃
Warm (First Request) 1.863s 1.883s ▂▂▂█▂

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 3.705s 3.786s ▄▄▄▁▁
Cached Build 3.822s 3.774s ▄▄▄▁▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 14.205s 14.208s ▁▁▁█▁
Cached Build 14.404s 14.429s ▁▁▁█▁
node_modules Size 483 MB 483 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0_lpxhkyog9-9.js gzip 162 B N/A -
0.-r71sd1j_zb.js gzip 7.61 kB N/A -
0~dh.pe76tp4s.js gzip 154 B N/A -
0~lwfcrlb4v_9.css gzip 115 B 115 B
00h0nz7r436~l.js gzip 13.3 kB N/A -
010veokj5t9nf.js gzip 169 B N/A -
02ku7edzc_wf7.js gzip 450 B N/A -
03~yq9q893hmn.js gzip 39.4 kB 39.4 kB
034~ea1.fumud.js gzip 48.5 kB N/A -
06ifmqyzqzamf.js gzip 157 B N/A -
092lcb3fqrrf9.js gzip 8.52 kB N/A -
0aj~xs1l1g8tg.js gzip 8.53 kB N/A -
0eg78sqvyqa0_.js gzip 13.7 kB N/A -
0ehfdrc23-o0h.js gzip 154 B N/A -
0h35gmp9u328z.js gzip 8.54 kB N/A -
0h6fkavebp.iz.js gzip 8.47 kB N/A -
0i1_a9rimqo25.js gzip 70.8 kB N/A -
0ino_yf1k3h6k.js gzip 10.4 kB N/A -
0k.sixc712dq1.js gzip 10.1 kB N/A -
0kkm7tesfinr6.js gzip 12.9 kB N/A -
0moy~uao4dl.m.js gzip 9.19 kB N/A -
0n~bmvrddbhmi.js gzip 154 B N/A -
0q50rtpusjy90.js gzip 2.28 kB N/A -
0smgy2grrrlka.js gzip 8.58 kB N/A -
0sv1h.1~4785f.js gzip 156 B N/A -
0syjr-z700d-h.js gzip 163 B N/A -
0t1dzhdfh0txh.js gzip 215 B 215 B
0w_gs_7ptie.q.js gzip 156 B N/A -
0y0~bkd6ge6gu.js gzip 65.7 kB N/A -
0zid7o0-vupvp.js gzip 225 B N/A -
109-8lzwgo3p0.js gzip 158 B N/A -
11yo3xfd6b147.js gzip 12.9 kB N/A -
13.84hqxl_1p7.js gzip 9.76 kB N/A -
14ob4-.af1o4f.js gzip 154 B N/A -
1554wr-t7p6z-.js gzip 8.55 kB N/A -
15tjst79~qy3_.js gzip 1.46 kB N/A -
15z_v00ne4ud0.js gzip 8.47 kB N/A -
16yzjq-v.qe0c.js gzip 156 B N/A -
17d_m3p4j9w6r.js gzip 5.62 kB N/A -
17yu~3yiu7d2m.js gzip 8.52 kB N/A -
1808zda6e6e_u.js gzip 160 B N/A -
turbopack-0d..8tpw.js gzip 4.17 kB N/A -
turbopack-0f..9a0w.js gzip 4.16 kB N/A -
turbopack-0i..zgu_.js gzip 4.16 kB N/A -
turbopack-0j..a72m.js gzip 4.16 kB N/A -
turbopack-0k..0-f4.js gzip 4.16 kB N/A -
turbopack-0n..f5~n.js gzip 4.16 kB N/A -
turbopack-0o..0-9y.js gzip 4.14 kB N/A -
turbopack-0q..m0x3.js gzip 4.16 kB N/A -
turbopack-0r..qsv_.js gzip 4.16 kB N/A -
turbopack-0y..4sn_.js gzip 4.16 kB N/A -
turbopack-14...pe_.js gzip 4.16 kB N/A -
turbopack-15..dibb.js gzip 4.16 kB N/A -
turbopack-16..ranj.js gzip 4.16 kB N/A -
turbopack-17..76x..js gzip 4.16 kB N/A -
012y56ntzw0t5.js gzip N/A 157 B -
02dx9nc6vmlnb.js gzip N/A 48.4 kB -
03t__~.5lvgeu.js gzip N/A 5.62 kB -
044nffvczz1qm.js gzip N/A 70.8 kB -
04d6ll75jqx3r.js gzip N/A 9.19 kB -
0583exyh-yhc7.js gzip N/A 9.76 kB -
05b07nana~4_..js gzip N/A 10.1 kB -
072lv63r8dcz~.js gzip N/A 8.58 kB -
07k6dcww5s4pu.js gzip N/A 13.7 kB -
09e5u94edfzfq.js gzip N/A 155 B -
0a2l7vlz.o~r3.js gzip N/A 155 B -
0ar1~bwpezfgw.js gzip N/A 13.3 kB -
0c99mq1ez2bke.js gzip N/A 450 B -
0cq-cmde_ws6u.js gzip N/A 8.47 kB -
0fwf102w10o9~.js gzip N/A 8.52 kB -
0g8327dwv5atv.js gzip N/A 157 B -
0gtmn.q_j1v5r.js gzip N/A 10.4 kB -
0kj2h8~mdlx-1.js gzip N/A 154 B -
0l~38duq8a8dv.js gzip N/A 65.7 kB -
0l4d~z7ixj3wz.js gzip N/A 12.9 kB -
0lfnovet50w7k.js gzip N/A 167 B -
0nclq9z6yzzm5.js gzip N/A 1.46 kB -
0nemy_y2k67sk.js gzip N/A 153 B -
0nzumcogektg7.js gzip N/A 8.55 kB -
0ptp-zz6dyc7o.js gzip N/A 157 B -
0s.c-cn5eebrx.js gzip N/A 8.47 kB -
0t2z1.qeavu-v.js gzip N/A 156 B -
0tna7lg6q4zne.js gzip N/A 12.9 kB -
0votdfxr5fb5u.js gzip N/A 2.28 kB -
0xkuhv202qqhu.js gzip N/A 7.6 kB -
0y-uo6cjo9tid.js gzip N/A 162 B -
0ykl9bs_qj.5..js gzip N/A 8.52 kB -
0zfen0tnxp4gh.js gzip N/A 8.55 kB -
10wkq1h9jzkg..js gzip N/A 225 B -
10xnkv79ab92c.js gzip N/A 155 B -
149ndfh8zfcaz.js gzip N/A 8.53 kB -
14dt9u0vmrrzt.js gzip N/A 156 B -
16brhmoc1r.wt.js gzip N/A 161 B -
turbopack-04..9nwf.js gzip N/A 4.16 kB -
turbopack-07..v.g8.js gzip N/A 4.16 kB -
turbopack-07..jpcn.js gzip N/A 4.16 kB -
turbopack-07..kqj5.js gzip N/A 4.16 kB -
turbopack-0f.._ski.js gzip N/A 4.16 kB -
turbopack-0f..frw~.js gzip N/A 4.16 kB -
turbopack-0h..j304.js gzip N/A 4.16 kB -
turbopack-0o..aso3.js gzip N/A 4.16 kB -
turbopack-0r..ud62.js gzip N/A 4.16 kB -
turbopack-0v..-.xx.js gzip N/A 4.18 kB -
turbopack-0v.._uuo.js gzip N/A 4.16 kB -
turbopack-12..mwvp.js gzip N/A 4.14 kB -
turbopack-12..v_26.js gzip N/A 4.16 kB -
turbopack-17..jaa_.js gzip N/A 4.16 kB -
Total 463 kB 463 kB ✅ -15 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 712 B 711 B
Total 712 B 711 B ✅ -1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 433 B 432 B
Total 433 B 432 B ✅ -1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.54 kB N/A -
6280-HASH.js gzip 60.3 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.59 kB N/A -
e8aec2e4-HASH.js gzip 62.7 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 255 B 254 B
main-HASH.js gzip 39.2 kB 39.2 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.59 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.55 kB -
6948ada0-HASH.js gzip N/A 62.7 kB -
9544-HASH.js gzip N/A 60.9 kB -
Total 234 kB 235 kB ⚠️ +654 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.51 kB 2.51 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.98 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 125 kB
page.js gzip 268 kB 268 kB
Total 393 kB 393 kB ✅ -284 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 615 B 616 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 44 kB 43.9 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.6 kB 45.5 kB ✅ -67 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 715 B 718 B
Total 715 B 718 B ⚠️ +3 B
Build Cache
Canary PR Change
0.pack gzip 4.27 MB 4.26 MB 🟢 9.15 kB (0%)
index.pack gzip 109 kB 108 kB 🟢 1.11 kB (-1%)
index.pack.old gzip 108 kB 108 kB
Total 4.48 MB 4.47 MB ✅ -10.2 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 333 kB 333 kB
app-page-exp..prod.js gzip 181 kB 181 kB
app-page-tur...dev.js gzip 332 kB 332 kB
app-page-tur..prod.js gzip 181 kB 181 kB
app-page-tur...dev.js gzip 329 kB 329 kB
app-page-tur..prod.js gzip 179 kB 179 kB
app-page.run...dev.js gzip 329 kB 329 kB
app-page.run..prod.js gzip 179 kB 179 kB
app-route-ex...dev.js gzip 76 kB 76 kB
app-route-ex..prod.js gzip 51.7 kB 51.7 kB
app-route-tu...dev.js gzip 76 kB 76 kB
app-route-tu..prod.js gzip 51.7 kB 51.7 kB
app-route-tu...dev.js gzip 75.6 kB 75.6 kB
app-route-tu..prod.js gzip 51.5 kB 51.5 kB
app-route.ru...dev.js gzip 75.6 kB 75.6 kB
app-route.ru..prod.js gzip 51.5 kB 51.5 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.3 kB 43.3 kB
pages-api-tu..prod.js gzip 33 kB 33 kB
pages-api.ru...dev.js gzip 43.3 kB 43.3 kB
pages-api.ru..prod.js gzip 33 kB 33 kB
pages-turbo....dev.js gzip 52.7 kB 52.7 kB
pages-turbo...prod.js gzip 38.6 kB 38.6 kB
pages.runtim...dev.js gzip 52.7 kB 52.7 kB
pages.runtim..prod.js gzip 38.6 kB 38.6 kB
server.runti..prod.js gzip 62.4 kB 62.4 kB
Total 2.95 MB 2.95 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/b32ce1703479d4ff94363c0e8cb06a320821b9f1/next

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 16, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing compaction_sorting_and_iteration (9800c36) with canary (fa32daa)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@lukesandberg lukesandberg changed the base branch from update_qfilter to graphite-base/91468 March 16, 2026 21:11
@lukesandberg lukesandberg force-pushed the compaction_sorting_and_iteration branch from a5f38eb to 3600f32 Compare March 16, 2026 21:12
@lukesandberg lukesandberg changed the base branch from graphite-base/91468 to canary March 16, 2026 21:12
@lukesandberg lukesandberg force-pushed the compaction_sorting_and_iteration branch from 3600f32 to 4a942b8 Compare March 16, 2026 21:12
@lukesandberg lukesandberg marked this pull request as ready for review March 16, 2026 23:00
@lukesandberg lukesandberg requested a review from a team March 16, 2026 23:01
@lukesandberg lukesandberg changed the title improve compaction cpu [turbopack] Optimize compaction cpu usage Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants