Skip to content

btindex: add M to file, reduce RAM usage of Build() - #21778

Merged
AskAlexSharov merged 10 commits into
mainfrom
alex/bt_streaming_build3_36
Jun 17, 2026
Merged

AskAlexSharov merged 10 commits into
mainfrom
alex/bt_streaming_build3_36

Conversation

@AskAlexSharov

@AskAlexSharov AskAlexSharov commented Jun 13, 2026 •

Copy link
Copy Markdown
Collaborator

based on: #21777

  • .bt file format change
  • backwards compatible

In PR:

  • reduce .Build() RAM usage by building ef_offsets eagerly off-heap and streaming nodes straight to the file (no big in-mem structs)
  • Add M inside file metadata
  • Removed di from file (node i has di = i*M, recomputed on read)
  • metadata stored in Footer of file
  • only the EF section is 4kb-aligned (for mmap/SIMD-friendly reads); nodes are byte-parsed, so left unaligned; footer is 8-byte aligned
  • completely removed ETL usage: because now AddKey() can write directly to file, also Footer written at the end (when all required fields calculated).
  • but keep 1 leading byte - just for legacy format detection

I realized that for building .bt we 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_count we can write it at the end:

  • Store metadata at Footer: wins at write-once by append-only use-cases.
  • Store metadata at 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).

Footer style file .bt:

[ leading_byte:u8 ]                                                      # 1 byte: legacy-format detection
[ nodes: (key_len:u16 | key)* ]                                          # variable length. unaligned (byte-parsed)
[ EF: elias-fano of all key offsets ]                                    # variable length. 4kb-aligned
[ footer: keys_count:u64 | M:u64 | ef_offset:u64 ]                       # 24 bytes. 8-byte-aligned
[ ANCHOR: footer_len:u32 | flags:u16 | format_version:u16 | magic:u64 ]  # 16 bytes. Fixed length

Build such files will be much more streaming-style-friendly. Don't need etl all incoming keys only to calculate keys_count (like we do now in .bt).

On file open:

  • Validate the magic first (right format + not truncated) -> fail-fast. Not u16 - because probability of collision is high.
  • format_version is next to the magic - 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 Footer allowing easy to add checksum to metadata fields in future, etc...

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() to eliasfano32.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.

Comment thread db/datastruct/btindex/btree_index.go Outdated
Comment thread db/datastruct/btindex/bps_tree.go Outdated
@AskAlexSharov
AskAlexSharov requested a review from yperbasis as a code owner June 15, 2026 11:21
@AskAlexSharov AskAlexSharov changed the title [wip] db/datastruct/btindex: fix OOM in Build() — stream nodes, use off-heap EF [wip] btindex: add M to file, reduce RAM usage of Build() Jun 16, 2026
@AskAlexSharov
AskAlexSharov requested a review from Copilot June 16, 2026 09:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 21 changed files in this pull request and generated 9 comments.

Comment thread db/state/statecfg/versions.yaml
Comment thread db/state/statecfg/versions.yaml
Comment thread db/state/statecfg/versions.yaml
Comment thread db/state/statecfg/versions.yaml
Comment thread db/datastruct/btindex/btree_index.go
Comment thread db/datastruct/btindex/footer.go
Comment thread db/datastruct/btindex/footer.go
Comment thread db/datastruct/btindex/btree_index.go
Comment thread db/datastruct/btindex/btree_index.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Comment thread db/datastruct/btindex/btree_index.go
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread db/datastruct/btindex/bps_tree.go
Comment thread db/datastruct/btindex/btree_index_test.go
Comment thread db/datastruct/btindex/btree_index.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread db/datastruct/btindex/btree_index.go
Comment thread db/datastruct/btindex/footer.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread db/datastruct/btindex/footer.go
@AskAlexSharov
AskAlexSharov requested a review from Copilot June 17, 2026 05:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread db/datastruct/btindex/btree_index.go
Comment thread db/datastruct/btindex/footer.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread db/datastruct/btindex/btree_index.go
@AskAlexSharov AskAlexSharov changed the title [wip] btindex: add M to file, reduce RAM usage of Build() btindex: add M to file, reduce RAM usage of Build() Jun 17, 2026
@AskAlexSharov
AskAlexSharov added this pull request to the merge queue Jun 17, 2026
Merged via the queue into main with commit 2e1fc00 Jun 17, 2026
92 checks passed
@AskAlexSharov
AskAlexSharov deleted the alex/bt_streaming_build3_36 branch June 17, 2026 15:36
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants