#26966 index: initial sync speedup, parallelize process
| Reviewability | Reviews | Agreement | Size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| #26966 index: initial sync speedup, parallelize process | Author silent 272d | 0 (+1) -1 | Disputed | 569 + 462 tests | ||||||
|
|
|
| |||||||
Reviewability: ReadyStalePaused Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs. Size: lines added or modified outside tests, then in tests; greener = smaller. | ||||||||||
Goal
Splits block index synchronization into parallel block digestion and sequential post-processing phases, and introduces a general-purpose ThreadPool utility. Indexes that implement concurrent digestion (currently blockfilterindex and txindex) can process batches of blocks across multiple worker threads during initial sync when enabled via the new `-indexworkers=<n>` startup option.
Problem: Constructing block filter and transaction indexes during initial sync is slow because blocks, undo data, and headers are read from disk and processed sequentially one block at a time on a single thread.
Evidence of importance:
- Author benchmark from the PR description: 'with the node in debug mode and on IBD, with -indexworkers=4, the block filter index generation took less than an hour. While, in master, the sync took more than 7 hours.'
Scope: The PR introduces a new general-purpose `ThreadPool` class in `src/util/threadpool.h` with dedicated unit and fuzz tests, in addition to the indexing changes. Part of the `BaseIndex` sync loop refactor in `src/index/base.cpp` was truncated from the patch snippet.
Reviewability
Stale · Author silent 272d
No author activity for 272 days; a review may wait a long time for a response.
Waiting on (from the thread read):
- author: extract ThreadPool via #33689 and rebase this PR once included 2026-02-01
- author: answer maflcko's question regarding AssumeUTXO compatibility 2023-10-25
- author: splitting ThreadPool into #33689 and rebasing before continuing with index parallel sync 2026-02-01
- author: answering maflcko's question about how this works with AssumeUTXO 2023-10-25
Agreement
Disputed
objection that needs a change, the objector pushed back after a reply (l0rinc)
Thread notes: Added unrecorded suggestions and questions from mzumsande, w0xlt, Sjors, maflcko, ryanofsky, and Eunovo under schema 2 rules. l0rinc's Approach NACK was contested following pushback on review complexity and deadlocks. The PR has been marked draft by the author while the ThreadPool is decoupled into #33689. / Updated claim classifications under current schema rules. Evaluated l0rinc's Approach NACK as contested following their pushback on deadlock risks and chunking. Added open question from mafl
Objections, suggestions, and questions:
| Reviewer | Type | Harm, request, or question | Status | Must be | Author replied | Quote |
|---|---|---|---|---|---|---|
| mzumsande (member) | suggestion, approach | implement parallelization in a general way so other indices like txindex can benefit | fixed · fix pushed · author implemented generic workers pool and txindex parallelization | answered | yes | 2023-01-29: 'did you attempt to implement parallelization in a more general way so that other indices could benefit from it as well?' Status from: 2023-02-28: furszy: 'Implemented generic workers pool.' |
| w0xlt (contributor) | suggestion, approach | separate parallelization and index logic so it can be reused across other indexes | fixed · fix pushed · author separated generic ThreadPool in later push | answered | no | 2023-01-30: 'Perhaps it could have separate parallelization and index logic. So it could be reused in other indexes.' Status from: 2023-02-28: furszy: 'Implemented generic workers pool.' |
| maflcko (member) | question, approach | how parallel indexing interacts with AssumeUTXO (AU) | open | answered | no | 2023-10-25: 'Also, how would this work with AU?' |
| Sjors (member) | suggestion, maintenance | spin down or destroy worker threads once initial index sync is complete | fixed · fix pushed · author agreed and destructed thread pool once initial sync completes | answered | yes | 2024-03-21: 'Maybe the threads should spin down if there's not much work.' Status from: 2024-03-21: furszy: 'The thread pool can be destructed once all indexes initial sync finish' |
| ryanofsky (member) | objection, safety | taking a reference to a temporary CBlockUndo object at block height 0 causes undefined behavior | fixed · fix pushed · author agreed and simplified to Assert(block.undo_data) | changed | yes | 2025-04-09: 'I'm not sure this is safe. It looks like if block height is 0 this is taking a reference to a temporary object that will go out scope.' Status from: 2025-06-05: furszy: 'yeah, great. Done as suggested.' |
| ryanofsky (member) | suggestion, approach | opportunistically run post-processing in worker threads to prevent idle threads | fixed · fix pushed · implemented opportunistic post-processing pipeline in worker threads | answered | yes | 2025-04-09: 'It seems like this idleness could be avoided by moving the CustomPostProcessBlocks calls into the worker threads. So that whenever each worker thread finishes processing blocks, it then opportunistically calls CustomPostProcessBlocks to post-process any blocks that are available' Status from: 2025-06-23: furszy: 'Spent a few days implementing this suggestion... The process runs faster now.' |
| ismaelsadeeq (member) | objection, safety | intermittent segfault during index sync caused by worker threads accessing an index Sync() local variable post-destruction | fixed · fix pushed · author fixed subtle race condition where worker thread accessed destroyed local variable | changed | yes | 2025-08-12: 'Fwiw I encountered a segfault previously while testing on master on previous PR HEAD' Status from: 2025-08-12: furszy: 'There was a very subtle bug on which the worker threads might have accessed an index Sync() local variable post-destruction.' |
| sedited (member) | objection, correctness | thread pool fuzzer times out after 1608 seconds during thread destruction and joining | fixed · fix pushed · author added fuzz harness and fixed shutdown synchronization bug | changed | yes | 2025-09-16: 'It seems to have found something: ... ERROR: libFuzzer: timeout after 1608 seconds' Status from: 2025-10-09: furszy: 'Updated with a fuzz test case from @TheCharlatan. Thanks!' |
| mzumsande (member) | objection, interface | parallel indexing on HDDs causes a 2x slowdown compared to sequential master due to disk seek penalties without warning in help | fixed · fix pushed · author updated -indexworkers help documentation warning against HDD use | changed | yes | 2025-10-07: 'observed a slowdown on a HDD compared to master (by a factor 2)... Should it be mentioned in the -indexworkers help' Status from: 2025-10-07: furszy: 'Updated, thanks for testing!' |
| Eunovo (member) | question, approach | whether ThreadPool should support assigning tasks in batches to threads | answered · author explained task granularity should be handled by caller before submission | answered | yes | 2025-10-08: 'Is it not beneficial for a generic ThreadPool to support assigning tasks in batches to threads?' Status from: 2025-10-08: furszy: 'I don't think that's thread pool responsibility. Generic thread pools are designed to execute tasks efficiently, but they don't dictate their granularity' |
| mzumsande (member) | suggestion, approach | extract ThreadPool into a separate pull request first | answered · author agreed to split ThreadPool to #33689 and set this PR to draft | answered | yes | 2025-10-10: 'It could make sense to introduce the threadpool in an extra PR first, since it is very generic and can be used for multiple things' Status from: 2025-10-21: furszy: 'will push the thread pool on an isolated PR in the coming week' |
| mzumsande (member) | objection, correctness | specifying -indexworkers=0 triggers an assertion failure in ThreadPool on startup | fixed · fix pushed · author fixed initialization check when indexworkers is 0 | changed | yes | 2025-10-10: '-indexworkers=0 is allowed according to the doc, but it will trigger the assert num_workers > 0 in the ThreadPool.' Status from: 2025-10-11: furszy: 'nice catch. Fixed.' |
| ryanofsky (member) | suggestion, approach | avoid std::shared_ptr and std::function overhead in ThreadPool by using std::packaged_task<void()> | fixed · fix pushed · author adopted packaged_task<void()> wrapper in ThreadPool queue | answered | yes | 2025-10-10: 'Would be nice to avoid shared_ptr here. Apparently it is needed because std::function objects are required to be copyable and packaged tasks aren't copyable. Could fix this by avoiding std::function' Status from: 2025-10-11: furszy: 'ha, that's awesome... I see the trick now... That's elegant.' |
| l0rinc (member) | objection, approach | txindex parallelization causes a 3-13% slowdown on SSDs, and combining a generic shared thread pool with complex out-of-order index synchronization introduces excessive complexity | contested · l0rinc reiterated deadlock and review complexity concerns after author's reply; author later agreed to split ThreadPool to #33689 and put PR into draft | changed | yes | 2025-10-12: 'The txindex parallelization appears to be broken (showing 3-13% slowdowns rather than speedups)... Approach NACK from me.' Status from: 2025-10-15: l0rinc: 'The review effort would be the same for 2 or nproc threads, but the implications would be limited with the former' |
| andrewtoth (member) | objection, approach | txindex sync on SSD is ~7% slower than master due to LevelDB write contention across worker threads offsetting parallel digest gains | answered · author demonstrated 2.6x speedup on Signet and offered to investigate SSD slowdown or disable parallel sync for txindex | changed | yes | 2025-10-13: 'master was ~7% faster than this PR with 5 workers on an SSD. Maybe we don't enable parallel sync for txindex?' Status from: 2025-10-14: furszy: 'I'm still working on the current approach but synced the tx index on the same environment as before... Parallel Mode (5 workers): ~5 minutes... Sequential Mode: 13 minutes.' |
Support:
- w0xlt (contributor) (Concept ACK, 2023-01-30) [read]: supportive of general concept and suggested separating parallelization and index logic so it can be reused
- Sjors (member) (2023-02-07) [tested]: tested building the block filter index on SSD, confirming speedup from 35m15s down to 4m26s with 32 workers
- sedited (member) (Concept ACK, 2023-11-25) [read: src/index/base.cpp, src/test/threadpool_tests.cpp, src/util/threadpool.h]: promising approach to parallelization, provided thorough first pass review
- ryanofsky (member) (Approach ACK, 2025-04-09) [read: src/index/base.cpp, src/util/threadpool.h]: seems like a nice design and good approach after code review
- ismaelsadeeq (member) (Concept ACK, 2025-07-21) [both]: reviewed and performed mainnet testing
- pinheadmz (member) (ACK, 2025-07-25) [both: src/index/base.cpp, src/index/blockfilterindex.cpp, src/util/threadpool.h]: tested on macos/arm64 and debian/x86, praised ThreadPool utility for broader reuse across the codebase
- pinheadmz (member) (ACK, 2025-07-29) [both]: re-ACK after testing on mainnet node (>900k blocks) with -indexworkers=16
- mzumsande (member) (2025-10-07) [tested]: tested signet parallel sync on SSD showing significant speedups
- brunoerg (member) (2025-10-10) [tested: src/test/fuzz/threadpool.cpp]: tested the ThreadPool fuzz harness overnight, confirming reasonable performance and no leaks
Participants:
- Sjors (member, support, 7 statements 2023-01-26 to 2025-07-17): tested block filter index sync performance on SSD and HDD, confirming major speedup on SSD
- mzumsande (member, support, 7 statements 2023-01-29 to 2025-10-10): tested on signet SSD, reported HDD slowdown and ThreadPool zero-worker assertion bug, both fixed
- w0xlt (contributor, support, 1 statements 2023-01-30 to 2023-01-30): Concept ACK; suggested separating parallelization and index logic
- maflcko (member, question, 1 statements 2023-10-25 to 2023-10-25): asked about CI failure and AssumeUTXO compatibility
- sedited (member, support, 20 statements 2023-11-24 to 2025-09-16): Concept ACK, code review, contributed fuzz harness uncovering timeout bug that was fixed
- andrewtoth (member, objection, 26 statements 2024-10-23 to 2025-10-15): measured txindex slowdown on SSD and suggested disabling parallel sync for txindex or splitting out ThreadPool
- yancyribbens (contributor, neutral, 1 statements 2025-02-26 to 2025-02-26): suggested comment nit in unit tests
- ryanofsky (member, support, 14 statements 2025-04-09 to 2025-10-10): Approach ACK, suggested opportunistic post-processing pipeline and packaged_task queue simplification
- ismaelsadeeq (member, support, 9 statements 2025-07-21 to 2025-08-12): Concept ACK, tested on mainnet, reported intermittent shutdown crash which was fixed
- pinheadmz (member, support, 15 statements 2025-07-24 to 2025-07-29): ACK, benchmarked mainnet speedup with 16 workers, suggested ThreadPool naming and logging improvements
- brunoerg (member, support, 2 statements 2025-09-17 to 2025-10-10): ran ThreadPool fuzz target overnight and generated coverage report
- Eunovo (member, question, 4 statements 2025-10-08 to 2025-10-08): asked about task batching and contributed test for recursive task submission
- l0rinc (member, objection, 8 statements 2025-10-12 to 2025-10-15): Approach NACK; measured txindex slowdowns and requested breaking into smaller PRs
Dependencies
Enables:
Files
539 lines under test/bench/ci.
- src/index/base.cpp +265/-62
- src/test/threadpool_tests.cpp +267/-0
- src/util/threadpool.h +192/-0
- src/test/blockfilter_index_tests.cpp +51/-77
- src/test/fuzz/threadpool.cpp +100/-0
- src/index/base.h +55/-2
- src/test/txindex_tests.cpp +42/-0
- src/index/blockfilterindex.cpp +17/-9
- src/init.cpp +22/-1
- src/index/blockfilterindex.h +8/-2
- src/index/txindex.h +7/-0
- src/node/context.h +3/-0
- src/test/CMakeLists.txt +1/-0
- src/test/fuzz/CMakeLists.txt +1/-0
Card
The short text the ranking pass compares across PRs.
Parallelizes initial block indexing across multiple worker threads for the compact block filter and transaction indexes, configured by a new `-indexworkers=<n>` flag. Today, initial index builds are strictly single-threaded and read blocks and undo data sequentially from disk, causing initial sync to take many hours. The author reports that building the block filter index took under 1 hour with 4 workers compared to over 7 hours on master. Work is related to and proceeding alongside PR #34489.
Data
thread read through 2026-02-01 (213 statements), head c5dad86523
labels: UTXO Db and Indexes, Needs rebase · draft
Categories
Indexes
P2 · speedup
Parallelizing block digestion dramatically speeds up initial index synchronization for block filters. Benchmarks cited by the author showed block filter index generation dropping from over 7 hours to under 1 hour with 4 workers, and Sjors confirmed a drop from 35m down to 4m26s with 32 workers. Although txindex exhibited minor write contention slowdowns on SSDs, drastically accelerating filter builds provides substantial, durable performance value for index users.
Membership: Modifies src/index/base.cpp and index implementations to parallelize initial index sync.
Factors: security/stability 1, bug 0, performance 2, user value 2, leverage 1
Utilities (logging, arguments, libraries)
P3 · new feature (ranking pass: Moved up: a 7h-to-1h initial index build is the largest user-visible performance claim in this band.)
The PR adds a generic ThreadPool utility intended for concurrent task execution across node components. Reviewers recognized its potential for broader reuse, though concerns over synchronization complexity and deadlocks indicate it serves primarily as a worthwhile building block rather than solving an urgent utility deficiency.
Membership: Introduces a general-purpose ThreadPool utility in src/util/threadpool.h.
Factors: security/stability 1, bug 0, performance 2, user value 2, leverage 1