#36000 validation: prefetch blocks while connecting
https://github.com/bitcoin/bitcoin/pull/36000 · · +191/-9 in 9 files, 6 commits · labels: Validation
Goal
- Overlap block read I/O with validation during initial sync, reindexing, and reorgs
- Significantly reduces sync and reindex times for node operators
Adds a Chainstate-owned BlockFetcher with a 2-worker thread pool that asynchronously reads and deserializes upcoming blocks from disk up to a depth of 4 while the validation thread is busy connecting earlier blocks. It also handles prefetching the first sibling block on a competing fork during reorgs while disconnecting the existing tip.
Problem: Block reading and deserialization from disk occur synchronously just before connecting each block, leaving CPU validation cores idle during disk I/O and disk readers idle during validation.
Category: Validation (#3 of 48)
P2 · speedup
- P2 because it brings double-digit performance gains to core block connection
- Reduces wall-clock time by 13-15% for IBD and 35-50% for reindexing for node operators
Delivers a substantial and empirically proven speedup for IBD and chain reindexing (e.g. 13% to 35% time reduction in benchmarks). Users directly feel faster synchronization times, and the abstraction also paves the way for index sync parallelization.
Membership: Directly modifies Chainstate::ActivateBestChainStep and block connection pipelines in src/validation.cpp.
Factors: security/stability 0, bug 0, performance 2, user value 2, leverage 1
Reviewability: Ready
- Ready for review
The implementation is complete, well tested, and stable; w0xlt's recent comment is a straightforward 2-line fix that does not invalidate review.
Author status: Active; addressed initial design feedback and provided comprehensive multi-hardware benchmarks, pending response to w0xlt's recent comment.
Open concerns:
- w0xlt pointed out that if a block fails validation, prefetched descendant blocks could remain in memory indefinitely unless Clear() is invoked on failure.
Resolved concerns:
- Thread pool creation spam on every ActivateBestChain call was fixed by moving BlockFetcher lifetime onto Chainstate.
- Potential circular dependency between node/blockfetcher, blockstorage, and validation was resolved using functional callback injection.
- Prefetch queue sizing and sliding window dynamics were tuned down to 2 threads and queue depth of 4 based on benchmarks.
Agreement: Mild
- Strong support with independent benchmarks verifying speedups (andrewtoth)
- Verified double-digit speedups for IBD and up to 35% on reindexing (andrewtoth)
- Minor suggestion to clear the queue when an invalid block is found (w0xlt)
Strong approval and benchmarks from andrewtoth; one open nonblocking memory retention edge case noted by w0xlt.
andrewtoth provided a full ACK backed by extensive benchmarks. An open nonblocking observation from w0xlt regarding clearing the queue on an invalid block remains unanswered.
- andrewtoth: 'ACK 3547915bfb... Managed to see speedup in IBD benchmarks... Nice speedups, especially for reindex-chainstate.'
- w0xlt: 'When a block was rejected, blocks read ahead for its branch could remain in memory indefinitely.'
Objections:
| Reviewer | Kind | Harm | Status | Blocking | Author replied | Quote |
|---|---|---|---|---|---|---|
| fanquake | maintenance | Log spamming from thread start/exit logs causing rate-limiting warnings | resolved | no | yes | 2026-08-18: 'Note that this spams blockread.* thread start/exit debug logs to the point that rate-limiting kicks' Settled: 2026-08-18: 'I’ve pushed a fix that keeps it alive on Chainstate' |
| w0xlt | correctness | Prefetched blocks on an invalid branch remain in memory indefinitely | open | no | no | 2026-09-16: 'When a block was rejected, blocks read ahead for its branch could remain in memory indefinitely.' |
Support:
- andrewtoth: Measured significant speedup on IBD (3.8% to 13.4%) and reindex-chainstate (12.7% to 35.8%) across machines
Participants: fanquake (objection), andrewtoth (support), w0xlt (objection)
State derived from the lists: nonblocking objection open (w0xlt)
Review verdicts (DrahtBot): 1
- ACK: andrewtoth
Dependencies
Enables:
- Parallel block reading in BaseIndex synchronization
Files
47 lines under test/bench/ci.
- src/node/blockfetcher.cpp +59/-0
- src/node/blockfetcher.h +52/-0
- test/functional/feature_reindex.py +44/-3
- src/validation.cpp +19/-5
- src/validation.h +6/-1
- doc/release-notes-36000.md +6/-0
- doc/developer-notes.md +3/-0
- src/CMakeLists.txt +1/-0
- src/kernel/CMakeLists.txt +1/-0
Card
Adds a background BlockFetcher that prefetches and deserializes disk blocks ahead of block connection during chain activation and reorgs. This resolves the bottleneck where CPU validation and disk I/O do not overlap during reindexing and IBD. Extensive benchmarks demonstrate substantial elapsed time speedups between 13% and 35%. The PR has an ACK from andrewtoth, with one minor nonblocking comment from w0xlt regarding memory clearing on invalid blocks pending author attention.