btindex: add M to file, reduce RAM usage of Build() - #21778
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the BtIndex on-disk format and build process to avoid mainnet-scale OOMs during B-tree index construction by (1) moving Elias–Fano (EF) build buffers off-heap and (2) streaming node records directly to the output instead of accumulating them in memory.
Changes:
- Switch EF construction in
BtIndexWriter.Build()toeliasfano32.NewEliasFanoOffHeap(...)(mmap-backed temp file) to eliminate large Go-heap spikes. - Change the index file layout to v1 (
[version][nodesCount][nodes...][EF]) so node entries can be streamed during ETL Load. - Update index open logic to support both v0 and v1 layouts, and tighten the test assertion to catch regressions where too many nodes are stored.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
db/datastruct/btindex/btree_index.go |
Writes v1 header + streams nodes during Build; reads v0/v1 formats on open; uses off-heap EF builder. |
db/datastruct/btindex/btree_index_test.go |
Adds an upper bound assertion for the number of cached nodes. |
db/datastruct/btindex/bps_tree.go |
Removes node-list encoding helper; updates node decoding helper to return bytes consumed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
AskAlexSharov
requested review from
bloxster,
domiwei,
lystopad,
mh0lt and
mriccobene
as code owners
June 16, 2026 07:52
M to file, reduce RAM usage of Build()
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
db/datastruct/btindex/btree_index.go:40
- btindex no longer uses ETL collectors, but it still depends on db/etl solely for etl.BufIOSize. If the intent is to remove ETL coupling here, consider defining a local buffer size constant in btindex (or moving BufIOSize to a smaller shared package) so this package doesn’t need to import etl just for one value.
"github.com/erigontech/erigon/common/dir"
"github.com/erigontech/erigon/common/log/v3"
"github.com/erigontech/erigon/common/murmur3"
"github.com/erigontech/erigon/db/datastruct/existence"
"github.com/erigontech/erigon/db/etl"
"github.com/erigontech/erigon/db/recsplit/eliasfano32"
"github.com/erigontech/erigon/db/seg"
Add a footer-based .bt layout: body (nodes + EF) followed by a metadata payload and a fixed 16-byte anchor (footer_len | flags | format_version | magic), with magic last so opens fail fast on a wrong/truncated file. Build the dense Elias-Fano offsets eagerly off-heap from KeyCount+MaxOffset instead of buffering every key through ETL, and recompute each node's data index as di=i*M rather than storing it. A non-zero leading byte distinguishes footer files from the legacy EF-first format.
AskAlexSharov
force-pushed
the
alex/bt_streaming_build3_36
branch
from
June 17, 2026 04:14
99128bc to
00ed5a3
Compare
M to file, reduce RAM usage of Build()M to file, reduce RAM usage of Build()
sudeepdino008
approved these changes
Jun 17, 2026
This was referenced Jun 17, 2026
AskAlexSharov
added a commit
that referenced
this pull request
Jun 18, 2026
) Cherry-pick of #21778 to `performance`. `.bt` file format change (backwards compatible): reduce `.Build()` RAM usage by building `ef_offsets` eagerly off-heap and streaming `nodes` straight to the file, add `M` to file metadata, drop stored `di` (recomputed as `i*M` on read), move metadata to a `Footer`, and remove ETL from the build path. ## performance-specific adaptations - `db/datastruct/btindex/bps_tree.go`: resolved a conflict with the branch-local `Node.off` field. Took #21778's new node format (`Node.Encode` writes `keyLen|key`; `decodeNodes`/`decodeListNodesV0` replace the old `encodeListNodes`/`decodeListNodes`/`Node.Decode`) while keeping the `off uint64` field and its population in `NewBpsTreeWithNodes`/`WarmUp`. `off` is recomputed via `offt.Get(di)`, so it is unaffected by the on-disk format change.
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.
based on: #21777
.btfile format changeIn PR:
ef_offsetseagerly off-heap and streamingnodesstraight to the file (no big in-mem structs)Minside file metadatadifrom file (nodeihasdi = i*M, recomputed on read)Footerof fileEFsection is 4kb-aligned (for mmap/SIMD-friendly reads);nodesare byte-parsed, so left unaligned; footer is 8-byte alignedETLusage: because nowAddKey()can write directly to file, alsoFooterwritten at the end (when all required fields calculated).I realized that for building
.btwe don't really need ETL (keys already always sorted, keys_count we know from decompressor, etc...). And I realized more - maybe other places in Erigon also can be more streaming-friendly (can build without ETL - in 1-pass).To generalize idea we need move "header metadata" to "footer" - then if metadata storing
keys_countwe can write it at the end:Footer: wins at write-once by append-only use-cases.Header: wins at forward-stream consumers (sockets, tar to tape), mutable page-addressed files (SQLite, Postgres, InnoDB — when mutate/extend file Footer will change its offset).Footerstyle file.bt:Build such files will be much more streaming-style-friendly. Don't need
etlall incoming keys only to calculatekeys_count(like we do now in.bt).On file open:
magicfirst (right format + not truncated) -> fail-fast. Not u16 - because probability of collision is high.format_versionis next to themagic- then it will allow change ANCHOR format/len in the future.(yes, exactly in our case: EF inside .bt requires to know key_count in advance - but we already have it - and maybe in future we can change it).
Also
Footerallowing easy to addchecksumto metadata fields in future, etc...