feat: mmap-backed subtrees and disk-backed TxMap for block RAM reduction#520
feat: mmap-backed subtrees and disk-backed TxMap for block RAM reduction#520
Conversation
…M usage Move subtree Node arrays off the Go heap using file-backed mmap and replace the in-memory transaction map with a sharded cuckoo filter + multi-disk BadgerDB architecture. All changes are opt-in via new settings. go-subtree v1.1.9 adds mmap-backed Node storage: - NewTreeMmap / NewTreeByLeafCountMmap constructors - NewSubtreeFromReaderMmap for zero-heap deserialization - Close() for mmap cleanup, SubtreeIndex on TxInpoints Block Assembly (subtreeprocessor): - New DiskTxMap: 4096 sharded cuckoo filters for 35M ops/sec existence checks, multi-disk BadgerDB for TxInpoints persistence - Async writer goroutines (one per disk) keep Badger I/O off hot path - mmap-backed subtree creation via WithMmapDir option - Subtree Close() calls on all cleanup paths (Stop, Reset, reChain) Block Validation: - mmap-backed subtree loading in quick_validate and catchup pipelines - Subtree Close() in setTxMined cleanup path Settings (all opt-in, empty = original behavior): - blockassembly_subtreeMmapDir: directory for mmap Node files - blockassembly_txMapDirs: pipe-separated paths for multi-disk BadgerDB - blockvalidation_subtreeMmapDir: directory for mmap subtree loading Tempstore: added Delete() method to BadgerTempStore
|
🤖 Claude Code Review Status: Complete SummaryThis PR successfully implements off-heap memory optimization for block processing through mmap-backed subtrees and disk-backed transaction maps. The implementation is well-architected with appropriate opt-in configuration and maintains backward compatibility. Key FindingsArchitecture (Strong):
Concurrency Safety (Verified):
Configuration (Well Designed):
Testing:
Code Quality:
Previous Review ItemsThe race condition issues flagged in earlier reviews have been resolved through the addition of the slowMu lock pattern. The current implementation correctly serializes the slow path to prevent the "recent map cleared during disk check" scenario. No blocking issues found. The implementation is production-ready with appropriate safeguards and monitoring via Stats() methods. |
…y fixes - Add closeChainedSubtrees() helper and use it in all reset/reorg paths - Use newSubtree() (mmap-aware) in addNode, addNodePreValidated, reChainSubtrees, moveBackBlock, resetSubtreeState — previously only processCompleteSubtree was updated - Close old subtrees in reChainSubtrees after re-adding nodes - Close current subtree before replacing in reset/rechain/moveBack paths - Fix fmt.Errorf → errors.New/errors.NewServiceError (lint violations) - Fix gci import ordering - Flush disk shard before Delete to prevent pending write from re-creating entry - Copy BadgerDB-returned bytes before in-place modification in UpdateSubtreeIndex - Keep old Badger store on Clear recreation failure instead of leaving nil state - Add mmap fallback + warning logging in quick_validate.go readSubtree
- removeTxFromSubtrees O(1) lookup: use SubtreeIndex from DiskTxMap for direct subtree access instead of scanning all 1000 subtrees (falls back to linear scan when DiskTxMap is not active) - processCompleteSubtree: update SubtreeIndex in Badger for all txs when subtree is chained, enabling the O(1) lookup above - resetSubtreeState: use currentTxMap.Clear() instead of replacing with NewSplitTxInpointsMap, preserving DiskTxMap when active - lastValidatedBlocks cache: add WithEvictionFunction callback to close mmap-backed subtrees when blocks expire from the 2-minute cache - SubtreeProcessingBatch.Close(): new method to release mmap subtree resources; called after batch completion in both sequential and pipeline processing paths in quick_validate.go - clearRecentMapsForDisk: fix uint16 vs int type inconsistency
SubtreeIndex now uses 0 as default (Go zero value, safe for serialization roundtrip). The subtreeprocessor stores chainedIdx+1 so that 0 = unassigned and >0 = assigned to chainedSubtrees[SubtreeIndex-1].
The old code replaced currentTxMap with a new SplitTxInpointsMap object. processOwnBlockNodes captures a reference to the old map BEFORE reset and reads it AFTER. Clear() modifies the same object, breaking those reads. Fix: when DiskTxMap is not active, create a new SplitTxInpointsMap (original behavior). When DiskTxMap is active, call Clear() which is safe because DiskTxMap is only assigned once and not captured by reference elsewhere.
Replace in-memory txMap and parentSpendsMap with disk-backed alternatives during block validation when block_diskMapDirs is configured. Uses sharded cuckoo filters for fast existence checks and multi-disk BadgerDB for storage, reducing heap by ~150 GB for 1B-transaction blocks. - DiskTxMapUint64 implements txmap.TxMap with cuckoo+BadgerDB - DiskParentSpendsMap tracks inpoints with same architecture - Skip cuckoo filter in Get (presence-dominated, filter is overhead) - Per-shard bytesWritten tracking (zero contention, plain int64) - Prometheus gauges for entries, filter RAM, and disk bytes written - Single block_diskMapDirs setting, empty = in-memory (backward compat) - Disable gosec in golangci config (known v2.23.0 panic, matches Makefile)
|
🤖 Claude Code Review Status: Complete Current Review: After thorough analysis of the mmap-backed subtrees and disk-backed TxMap implementation, the code appears well-designed and functionally correct. The complex concurrency patterns using Note on existing inline comments: Three previous inline comments claim race conditions exist in
The implementation correctly handles cross-shard clearing through proper lock ordering and FIFO channel semantics. Positive observations:
Minor notes:
|
# Conflicts: # go.mod # go.sum
|
🤖 Claude Code Review Status: Complete Current Review: No Critical Issues FoundThis PR implements mmap-backed subtrees and disk-backed transaction maps to reduce block RAM usage at scale. The implementation is sound with all previously identified race conditions resolved. Key Findings:
Architecture:
Settings:
No blocking issues identified. The slowMu pattern effectively serializes the slow path to prevent the previously flagged race condition. History:
|
|


Summary
Both
blockvalidationandblockassembly/subtreeprocessorhold subtree Node arrays and transaction tracking maps entirely in Go heap memory. At scale (1000 subtrees × 1M transactions = 1 billion tx per block), this consumes ~376 GB of heap and creates severe GC pressure.This PR moves the two largest memory consumers off-heap:
Node arrays (48 GB) → file-backed mmap: The
Nodestruct has zero pointer fields ([32]byte+uint64+uint64= 48 bytes), making it safe for mmap.[]Nodeslices backed by mmap are indistinguishable from heap-backed — all existing code works unchanged. The OS manages paging between RAM and disk transparently.currentTxMap (200 GB) → sharded cuckoo filters + multi-disk BadgerDB: The hot path (SetIfNotExists) only needs an existence check. 4096 independent cuckoo filter shards provide 35M ops/sec for dedup with ~1 GB memory. TxInpoints data is persisted to BadgerDB across multiple physical disks via dedicated writer goroutines — one per disk for linear I/O scaling.
All changes are fully opt-in via new settings. When unconfigured (empty strings), behavior is identical to before.
Architecture
mmap-backed Node arrays (go-subtree v1.1.9)
DiskTxMap (replaces SplitTxInpointsMap when configured)
Benchmarks
New Settings
blockassembly_subtreeMmapDir""blockassembly_txMapDirs""blockvalidation_subtreeMmapDir""Example production configuration:
Files Changed
go.modstores/tempstore/badger.goDelete()methodsubtreeprocessor/disk_tx_map.gosubtreeprocessor/disk_tx_map_test.gosubtreeprocessor/disk_tx_map_benchmark_test.gosubtreeprocessor/SubtreeProcessor.gosubtreeprocessor/options.goWithMmapDir,WithTxMapDirsoptionsblockassembly/BlockAssembler.goblockvalidation/BlockValidation.goblockvalidation/quick_validate.goblockvalidation/get_blocks.gosettings/blockassembly_settings.goSubtreeMmapDir,TxMapDirssettings/blockvalidation_settings.goSubtreeMmapDirTest plan