#36000 validation: prefetch blocks while connecting

full analysis

https://github.com/bitcoin/bitcoin/pull/36000 · l0rinc · +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:

ReviewerKindHarmStatusBlockingAuthor repliedQuote
fanquakemaintenanceLog spamming from thread start/exit logs causing rate-limiting warningsresolvednoyes2026-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'
w0xltcorrectnessPrefetched blocks on an invalid branch remain in memory indefinitelyopennono2026-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

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.

Data

dossier JSON · extract JSON · model openrouter/google/gemini-3.8-flash, generated 2026-09-17T21:45, confidence high, input hash ba4c578fd1f473c1