Repository navigation
builder: trees in slabs; too big by what a replay holds; pooled pages given back - #13
Merged
Merged
Conversation
…inflated into
zlib's inflateSync returns a small object as a view of its 16 KB output buffer, and a
delta result under 4 KB is a slice of Node's shared 8 KB pool. The object store kept
trees as they came, so a 300-byte tree held 16 KB. Indexing the window packs of two
repos production refused as too big today, from the same bytes:
anomalyco/opencode, 172 MB pack: 129 MB of trees held in 684 MB of buffers;
peak live 1,041 MB before, 468 MB after
affaan-m/hermes-agent, 236 MB pack: 464 MB of trees held in 766 MB;
peak live 1,151 MB before, 841 MB after
Kept trees are now copied into shared 1 MB slabs. A tree already in a buffer of its own
size stays as it is, and one over 64 KB gets a buffer of its own size. Indexing got
faster, not slower: 15.6 s before, 12.2 s after for opencode's pack.
…pooled pages back From production's function logs today: - google/dawn was refused as too big at 1,244 MB live. Its first fetch read 1,196 MB live there, and 484 MB replayed alone: the guard judged the whole process, so other requests' memory counted against it. - google/site-kit-wp was refused as busy at 299 MB live and 2,083 MB resident. After a big replay, V8 keeps the heap pages it freed in a pool, and a normal collection does not give them back: one instance sat at 1,341 MB resident with 30 to 60 MB live. - 8 instances were killed for running out of memory since 2026-10-11, the last at 13:18 UTC today. Nothing was checked while a pack downloaded, and for two seconds after any collection the guard ignored a full instance. The guard moves to memory.ts and changes in three ways: - Too big counts what the replay holds: live memory beyond what was live when it began. Each big replay's turn opens with a thorough collection, so that starting point is not another replay's garbage. - A full instance (resident size past 1.9 GB) gets V8's last-resort collection, the one that gives pooled pages back, and is busy only if that does not make room. Replayed locally, google/dawn left 1,002 MB resident for 57 MB live after two normal collections, and 427 MB after the last resort. - Downloads are checked as each pkt-line arrives. The check keeps room for the copy a pack takes when its chunks are joined. buildRepo logs what each big turn's collection freed.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The builder refused buildable repos today, and memory kills took down whole instances. There were three causes, and each one was measured.
From production's function logs (2026-10-13):
/api/build/reposince 2026-10-11. The last was at 13:18 UTC today.1. Trees held 16 KB each (
fda4196)zlib returns a small inflated object as a view of its 16 KB output buffer. The object store kept trees as they came, so a 300-byte tree held 16 KB. Here are window packs indexed from the same saved bytes:
Kept trees are now copied into shared 1 MB slabs. Indexing opencode's pack got faster: 15.6 s before, 12.2 s after.
2. Too big judged the whole process (
b94bfef)Other requests' memory counted against a replay, as with dawn above. The guard now counts what the replay itself holds: live memory beyond what was live when it began. Each big replay's turn opens with a thorough collection, so that starting point is not another replay's garbage.
3. Pooled pages stayed resident (
b94bfef)After a big replay, V8 keeps the heap pages it freed in a pool, and two normal collections don't give them back. V8's last-resort collection does. Replayed locally, google/dawn left 1,002 MB resident for 57 MB live after two normal collections, and 427 MB after the last resort.
A full instance (resident past 1.9 GB) now gets the last-resort collection, and is busy only if that doesn't make room. Before, for two seconds after any collection, the guard ignored a full instance.
4. Downloads weren't checked (
b94bfef)The guard now runs as each pkt-line arrives. It keeps room for the copy a pack takes when its chunks are joined: 236 MB more for hermes-agent's first pack.
I also tried rewriting the receive loop to copy into doubling blocks. At the 16 KB chunks HTTPS delivers, it measured the same as the current loop: 285 vs 293 MB peak for the 236 MB pack. It's not in this PR.
This branch on a warm instance
Big repos replayed one after another in one local process, under production's limits, each turn opened as
buildReponow opens it. Peak figures are what the guard read, garbage included.hermes-agent took 814 s here, 503 s of it in one fetch of 4,000 blobs over my connection. Production fetches far faster, but its 240 s deadline is the open question for that repo. On main, dawn alone left 1,002 MB resident after two normal collections.
Not fixed here. google/site-kit-wp needs more than the 400 MB pack budget: 394 MB in five fetches, leaving 6 MB for the sixth. It stays too big, correctly.
Tests
After deploy:
big replay's turn after … collected: … rss X -> Y MB. That confirms the last-resort collection on Node 24; it was measured locally on Node 22.