Improve loadtxoutset performance #36603

pull ajtowns wants to merge 12 commits into bitcoin:master from ajtowns:202610-loadtxoutset-speedy changing 27 files +1323 −472
  1. ajtowns commented at 10:25 AM on October 22, 2026: contributor

    Significantly improves loadtxoutset performance via a number of changes:

    • parallel threads for decoding, hashing, serializing and writing to leveldb
    • no fsync() gating writes
    • no redundant loading into CoinsView

    Also adds a subcommand to bitcoin-util for getting a snapshot's hash_serialized_3, muhash, coin count and total amount values without importing to leveldb first.

    On my systems, performance ends up bottlenecked by leveldb CPU usage.

  2. DrahtBot commented at 10:25 AM on October 22, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36633 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36633.svg"></sub> (validation: bound snapshot cache growth by bytes, not coin count by fametrano)
    • #36629 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36629.svg"></sub> (Add output script index and corresponding script history RPC by andrewtoth)
    • #36601 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36601.svg"></sub> (test: avoid reusing AssumeUTXO coin insertion method in test fixtures by l0rinc)
    • #36598 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36598.svg"></sub> ([Demo] validation: remove AssumeUTXO by l0rinc)
    • #36543 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36543.svg"></sub> (validation: check the most recent AssumeUTXO commitment during IBD by l0rinc)
    • #36542 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36542.svg"></sub> (AssumeUTXO: allow retrying loadtxoutset after an interrupted load by l0rinc)
    • #36500 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36500.svg"></sub> (AssumeUTXO: reject duplicate coins and empty snapshot groups by l0rinc)
    • #36346 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36346.svg"></sub> (validation: don't accept txs to the mempool while loading a snapshot by 0xShadowX)
    • #36326 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36326.svg"></sub> (kernel: Use typed errors for fatal and flush error notifications by arejula27)
    • #36167 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36167.svg"></sub> ([RFC] Enable -Wunused by fanquake)
    • #36074 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36074.svg"></sub> (scripted-diff: [test] Add util/check.h includes for assertions by maflcko)
    • #36000 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36000.svg"></sub> (validation: prefetch blocks while connecting by l0rinc)
    • #35788 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/35788.svg"></sub> (bitcoin-util: Add evalscript subcommand by ajtowns)
    • #35511 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/35511.svg"></sub> (RFC: consensus: Make CAmount a class by hodlinator)
    • #34864 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/34864.svg"></sub> (coins: tighten cache entry state invariants by l0rinc)
    • #33117 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/33117.svg"></sub> (Interfaces: Expose UTXO Snapshot Loading and Add Progress Notifications by D33r-Gee)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • src/test/validation_chainstatemanager_tests.cpp: cache.AddCoin(item.outpoint, Coin{item.coin}, false)

    <sup>2026-10-28 02:47:34</sup>

  3. ajtowns commented at 10:30 AM on October 22, 2026: contributor

    The performance change I see on a Ryzen 9 3900X system with RAID HDD storage is ~11minutes with to ~1m35s for a load, consisting of ~1m15s of CPU time followed by ~20s of disk flushing; with SSD and a slightly faster CPU, total time is reduced from ~5m30s to ~50s (no significant flush delay after the CPU work is completed here). These numbers are with the block 965000 utxo set.

  4. DrahtBot added the label CI failed on Oct 22, 2026
  5. DrahtBot commented at 11:37 AM on October 22, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/39762952091/job/119771350806</sub> <sub>LLM reason (✨ experimental): CI failed because clang-tidy flagged a mismatched argument comment (moveout instead of moveto) in test/coinsviewoverlay_tests.cpp; warnings are treated as errors.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  6. ajtowns force-pushed on Oct 23, 2026
  7. ajtowns force-pushed on Oct 23, 2026
  8. ajtowns force-pushed on Oct 23, 2026
  9. l0rinc commented at 7:03 AM on October 23, 2026: contributor

    Can you please give us more context? Why do you consider an 11-minute load too slow? Is there a reason to think this is what's preventing people from using it - enough to justify yet more complexity in the most "load-bearing" parts of the code? Or is this just a demo to explore the possibilities?

    Over the past years we've discussed how relying on someone else's UTXO set makes us uncomfortable (see #32377 and #36598). This change adds even more AssumeUTXO-specific complexity, which is exactly why we're considering removing it completely. I wouldn't object to adding it back later through IPC and the Kernel if there's still demand and doesn't add complexity - but there's no indication of that, especially since #36476 (possibly combined with SwiftSync) would already speed up actual UTXO set construction by an order of magnitude on resource-constrained nodes. Quickly loading someone else's UTXO set could still be a useful academic pursuit, but many of us are not comfortable polluting the code with multiple chainstates, which make consensus-critical paths harder to reason about.

  10. ajtowns force-pushed on Oct 23, 2026
  11. ajtowns commented at 11:35 AM on October 23, 2026: contributor

    Can you please give us more context?

    In bips#2137 Sjors suggested splitting up the snapshot by coin to improve performance, which didn't seem sensible to me.

    Why do you consider an 11-minute load too slow?

    I don't consider it too slow; though it's possible it could be on slower systems than the ones I test on. But the reason it is that slow is because the database gets compacted multiple times, and it also seems to use excessive amounts of memory while loading, and there isn't really a good reason to do that. The difference between checking whether better performance is possible and actually making it available to users isn't that large.

    This change adds even more AssumeUTXO-specific complexity, which is exactly why we're considering removing it completely.

    As I've said on the list, I think the assumeutxo approach is a much better approach to resolving the "fast startup of a new node" challenge than trying to get a few extra percent out of IBD performance (which introduces its own large sets of complexities). If it's been decided that that's not one of this project's goals, that's fine I guess, but I haven't seen a project statement along those lines.

    Making assumeutxo a reasonable default, rather than something for experts, does require significantly more complexity (ie, the various stuff for the BIP above, cf #35054). Likewise if we want to do FEC encoding so pruned nodes can help share utxo snapshots or support IBD.

  12. DrahtBot removed the label CI failed on Oct 23, 2026
  13. ajtowns force-pushed on Oct 25, 2026
  14. ajtowns force-pushed on Oct 25, 2026
  15. DrahtBot added the label CI failed on Oct 25, 2026
  16. DrahtBot commented at 5:08 AM on October 25, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task iwyu: https://github.com/bitcoin/bitcoin/actions/runs/40163823354/job/121080686219</sub> <sub>LLM reason (✨ experimental): CI failed because the IWYU check modified includes in src/node/utxo_snapshot.cpp, leaving a diff that caused the check to exit with code 1.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  17. DrahtBot removed the label CI failed on Oct 25, 2026
  18. DrahtBot added the label Needs rebase on Oct 26, 2026
  19. refactor: Move SnapshotMetadata out of node/
    The snapshot file format is not node state: reading a snapshot needs no
    chainstate, so move it out. This prepares the way for adding snapshot
    parsing to bitcoin-util in a future commit.
    
    Mostly move-only. The commit can be reviewed with the
    --color-moved=dimmed-zebra option for an easier review.
    52583fba0d
  20. refactor: Check the snapshot network in the caller
    SnapshotMetadata took the expected network in its constructor and compared
    it while parsing, throwing on mismatch. Split that check out as a separate
    `CheckNetwork` method, so that callers have the option to read snapshots
    for arbitrary networks.
    
    Also replace the per-object `const std::set` for checking supported
    versions with an inline function.
    19fa00eb2d
  21. util: Add DirectoryFilesCommit for syncing the contents of a directory 21bef34248
  22. dbwrapper: Add nosync option for quickly populating a db
    Normally, we want updates to the database to be fsynced to disk so that
    the database is in a consistent state even after a crash. This adds an
    option to drop that guarantee, in favour of faster bulk writes, relying
    on the caller to make other arrangements to ensure the resulting database
    is flushed to disk.
    f7de839fc9
  23. txdb: Add RawCoinDataDBBatch for directly populating a chainstate
    This populates a chainstate leveldb (as a CDBWrapper) directly from a
    batch (generated from Coin objects). This avoids the overhead of going
    through a CoinsView map, and allows splitting the batch creation and
    the batch write/flush across different threads.
    a73f679f33
  24. util: Add BlockingQueue
    Multi-producer, multi-consumer thread-safe queue with maximum capacity
    for handing out jobs to threads.
    bbe85cc3dd
  25. utxo_snapshot: More efficient snapshot imports
    Allows importing snapshots directly into leveldb (dbwrapper), without
    passing through Chainstate or CoinsView first. Supports multithreaded
    processing, with one thread reading, one thread hashing, one thread
    serializing for the database, and one thread populating the database.
    
    Adds an additional check that the coins in the snapshot are sorted in
    txid order.
    
    Note that because v2 dump follow the database key order (which encodes
    output indices as VARINTs), but the UTXO commitment requires outputs
    to be ordered numerically by output index, a sorting step is required
    (eg VARINT(16512) sorts less than VARINT(16511). We bound the total
    serialized transaction outputs by the consensus block weight limit to
    bound the amount of date being sorted in case of corrupt snapshot files.
    
    Nothing calls it yet, so behaviour is unchanged.
    
    Co-Authored-By: Sjors Provoost <sjors@sprovoost.nl>
    8810862d1d
  26. validation: Make ActivateSnapshot faster
    ActivateSnapshot and, therefore, the loadtxoutset RPC now use the new
    utxo_snapshot.cpp pipeline.
    
    The dump is no longer staged in a CCoinsViewCache and then hashed and
    flushed out of it, so `-dbcache` worth of cached coins during a mainnet
    load are gone along with the flush cycles. In addition, the hashing
    of the dump takes place in parallel with the import, rather than after
    it completes.
    
    The old loader is left in place until the next commit removes it;
    nothing calls it any more.
    49aa42ccaa
  27. validation/utxo_snapshot: Remove the old snapshot loader
    After the previous two commits, this code is no longer used, so remove it.
    
    This also removes the circular dependency between node/utxo_snapshot
    and validation.
    ce01e3c30b
  28. coins: Drop EmplaceCoinInternalDANGER which is no longer used outside of tests 03fec5e401
  29. utxo_snapshot: Add support for MuHash calculations 65caae102d
  30. bitcoin-util: Add txoutsetsummary subcommand
    Adds a subcommand for quickly generating the summary of a serialized
    utxo set for verification purposes (including various hashes, coin count
    and value).
    fbda3acde3
  31. ajtowns force-pushed on Oct 28, 2026
  32. DrahtBot removed the label Needs rebase on Oct 28, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-30 09:40 UTC