merge-script da6f041e39
Merge bitcoin/bitcoin#31645: [IBD] coins: increase default UTXO flush batch size to 32 MiB
b6f8c48946cbfceb066de660c485ae1bd2c27cc1 coins: increase default `dbbatchsize` to 32 MiB (Lőrinc)
8bbb7b8bf8e3b2b6465f318ec102cc5275e5bf8c refactor: Extract default batch size into kernel (Lőrinc)

Pull request description:

  This change is part of [[IBD] - Tracking PR for speeding up Initial Block Download](https://github.com/bitcoin/bitcoin/pull/32043)

  ### Summary

  When the in-memory UTXO set is flushed to LevelDB (after IBD or AssumeUTXO load), it does so in batches to manage memory usage during the flush.
  A hidden `-dbbatchsize` config option exists to modify this value. This PR only changes the default from `16` MiB to `32` MiB.
  Using a larger default reduces the overhead of many small writes and improves I/O efficiency (especially on HDDs). It may also help LevelDB optimize writes more effectively (e.g., via internal ordering).
  The change is meant to speed up a critical part of IBD: dumping the accumulated work to disk.

  ### Context

  The UTXO set has grown significantly since [2017](https://github.com/bitcoin/bitcoin/pull/10148/files#diff-d102b6032635ce90158c1e6e614f03b50e4449aa46ce23370da5387a658342fdR26-R27), when the original fixed 16 MiB batch size was chosen.

  With the current multi-gigabyte UTXO set and the common practice of using larger `-dbcache` values, the fixed 16 MiB batch size leads to several inefficiencies:

  * Flushing the entire UTXO set often requires thousands of separate 16 MiB write operations.
  * Particularly on HDDs, the cumulative disk seek time and per-operation overhead from numerous small writes significantly slow down the flushing process.
  * Each `WriteBatch` call incurs internal LevelDB overhead (e.g., MemTable handling, compaction triggering logic). More frequent, smaller batches amplify this cumulative overhead.

  Flush times of 20-30 minutes are not uncommon, even on capable hardware.

  ### Considerations

  As [noted by sipa](https://github.com/bitcoin/bitcoin/pull/31645#issuecomment-2587500105), flushing involves a temporary memory usage increase as the batch is prepared. A larger batch size naturally leads to a larger peak during this phase. Crashing due to OOM during a flush is highly undesirable, but now that [#30611](https://github.com/bitcoin/bitcoin/pull/30611) is merged, the most we'd lose is the first hour of IBD.

  Increasing the LevelDB write batch size from 16 to 32 MiB raised the measured peaks by ~70 MiB in my tests during UTXO dump. The option remains hidden, and users can always override it.

  The increased peak memory usage (detailed below) is primarily attributed to LevelDB's `leveldb::Arena` (backing MemTables) and the temporary storage of serialized batch data (e.g., `std::string` in `CDBBatch::WriteImpl`).

  Performance gains are most pronounced on systems with slower I/O (HDDs), but some SSDs also show measurable improvements.

  ### Measurements:

  AssumeUTXO proxy, multiple runs with error bars (flushing time is faster that the measured loading + flushing):
  * Raspberry Pi, dbcache=500: ~30% faster with 32 MiB vs 16 MiB, peak +~75 MiB and still < 1 GiB.
  * i7 + HDD: results vary by dbcache, but 32 MiB usually beats 16 MiB and tracks close to 64 MiB without the larger peak.
  * i9 + fast NVMe: roughly flat across 16/32/64 MiB. The goal here is to avoid regressions, which holds.

  ### Reproducer:

  ```bash
  # Set up a clean demo environment
  rm -rfd demo && mkdir -p demo

  # Build Bitcoin Core
  cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc)

  # Start bitcoind with minimal settings without mempool and internet connection
  build/bin/bitcoind -datadir=demo -stopatheight=1
  build/bin/bitcoind -datadir=demo -blocksonly=1 -connect=0 -dbcache=3000 -daemon

  # Load the AssumeUTXO snapshot, making sure the path is correct
  # Expected output includes `"coins_loaded": 184821030`
  build/bin/bitcoin-cli -datadir=demo -rpcclienttimeout=0 loadtxoutset ~/utxo-880000.dat

  # Stop the daemon and verify snapshot flushes in the logs
  build/bin/bitcoin-cli -datadir=demo stop
  grep "FlushSnapshotToDisk: completed" demo/debug.log
  ```

  ---

  This PR originally proposed 64 MiB, then a dynamic size, but both were dropped: 64 MiB increased peaks more than desired on low-RAM systems, and the dynamic variant underperformed across mixed hardware. 32 MiB is a simpler default that captures most of the gains with a modest peak increase.

  For more details see: https://github.com/bitcoin/bitcoin/pull/31645#issuecomment-3234329502
  ---

  While the PR isn't about IBD in general, rather about a critical section of it, I have measured a reindex-chainstate until 900k blocks, showing a 1% overall speedup:

  <details>
  <summary>Details</summary>

  ```python
  COMMITS="e6bfd95d5012fa1d91f83bf4122cb292afd6277f af653f321b135a59e38794b537737ed2f4a0040b"; \
  STOP=900000; DBCACHE=10000; \
  CC=gcc; CXX=g++; \
  BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
  (echo ""; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done; echo "") && \
  hyperfine \
    --sort command \
    --runs 1 \
    --export-json "$BASE_DIR/rdx-$(sed -E 's/(\w{8})\w+ ?/\1-/g;s/-$//'<<<"$COMMITS")-$STOP-$DBCACHE-$CC.json" \
    --parameter-list COMMIT ${COMMITS// /,} \
    --prepare "killall bitcoind 2>/dev/null; rm -f $DATA_DIR/debug.log; git checkout {COMMIT}; git clean -fxd; git reset --hard && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && ninja -C build bitcoind && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 10" \
    --cleanup "cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log" \
    "COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0"

  e6bfd95d50 Merge bitcoin-core/gui#881: Move `FreespaceChecker` class into its own module
  af653f321b coins: derive `batch_write_bytes` from `-dbcache` when unspecified

  Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=900000 -dbcache=10000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = e6bfd95d5012fa1d91f83bf4122cb292afd6277f)
    Time (abs ≡):        25016.346 s               [User: 30333.911 s, System: 826.463 s]

  Benchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=900000 -dbcache=10000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = af653f321b135a59e38794b537737ed2f4a0040b)
    Time (abs ≡):        24801.283 s               [User: 30328.665 s, System: 834.110 s]

  Relative speed comparison
          1.01          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=900000 -dbcache=10000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = e6bfd95d5012fa1d91f83bf4122cb292afd6277f)
          1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=900000 -dbcache=10000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = af653f321b135a59e38794b537737ed2f4a0040b)
  ```

  </details>

ACKs for top commit:
  laanwj:
    Concept and code review ACK b6f8c48946cbfceb066de660c485ae1bd2c27cc1
  TheCharlatan:
    ACK b6f8c48946cbfceb066de660c485ae1bd2c27cc1
  andrewtoth:
    ACK b6f8c48946cbfceb066de660c485ae1bd2c27cc1

Tree-SHA512: a72008feca866e658f0cb4ebabbeee740f9fb13680e517b9d95eaa136e627a9dd5ee328456a2bf040401f4a1977ffa7446ad13f66b286b3419ff0c35095a3521
2025-10-31 13:13:53 -04:00
..
2025-09-01 15:10:24 -07:00
2025-05-19 16:40:33 +01:00
2025-09-03 11:23:30 +01:00
2025-05-20 09:30:41 +01:00
2025-05-19 16:40:33 +01:00
2025-09-30 11:06:43 -07:00
2025-08-12 11:28:36 -04:00
2025-08-12 11:28:36 -04:00
2025-09-30 11:06:43 -07:00
2024-07-08 11:12:01 +02:00
2025-09-30 11:06:43 -07:00
2025-04-09 15:59:59 +01:00
2025-04-09 15:59:59 +01:00
2023-11-16 11:36:22 +10:00