Skip to content

node: scale default -dbcache with system RAM#34641

Open
l0rinc wants to merge 4 commits intobitcoin:masterfrom
l0rinc:l0rinc/dynamic-dbcache
Open

node: scale default -dbcache with system RAM#34641
l0rinc wants to merge 4 commits intobitcoin:masterfrom
l0rinc:l0rinc/dynamic-dbcache

Conversation

@l0rinc
Copy link
Contributor

@l0rinc l0rinc commented Feb 20, 2026

Problem

-dbcache controls how much RAM Bitcoin Core uses for the in-memory view of the UTXO set, while the authoritative chainstate remains on disk.
The default is still the fixed 450 MiB value, chosen almost a decade ago, even though chainstate size and typical hardware profiles have changed significantly since then.

In practice, a fixed 450 MiB default is often too conservative on modern systems, where larger cache sizes can reduce flush churn and disk roundtrips during validation, especially in IBD. At the same time, memory pressure remains important on smaller systems.

Historical changes

Date dbcache change PR
2016-07-06 100 -> 300 #8273: Bump -dbcache default to 300MiB
2017-04-05 300 -> 450 #10133: Clean up calculations of pcoinsTip memory usage

IRC

The discussion converged on avoiding a single new fixed -dbcache value and moving to RAM-aware defaults instead, balancing performance gains on modern hardware against OOM risk on lower-memory systems.

IRC log links

Node provider defaults

  • RaspiBlitz sets a higher dbcache during IBD (when no other apps are running), then lowers it afterward.
  • Start9 can set the default dynamically based on total RAM; currently creates a user task to lower dbcache after IBD if it was increased.
  • MyNode automatically adjusts based on system memory (4 GB RAM → 1 GB cache, 8 GB RAM → 2.5 GB cache, 16 GB RAM → 4 GB cache) and also scales max mempool size.
  • Umbrel used to default to 50% RAM during IBD then reduce to 200 MiB post-IBD, but removed that logic because dedicating that much RAM caused issues when users ran other apps; now ships Core defaults.

Containerization

Container memory constraints may or may not be reflected by RAM detection.
We have considered adding container-detection but decided against it. Startup logs show detected/assumed system memory so users can set -dbcache explicitly when needed.

Fix

Measuring -reindex-chainstate on different machines indicates that 450-1024-2048 jumps are all very relevant after which the effects taper off.

Replace the fixed default with a RAM-aware policy:

$$\text{default\_dbcache}=\mathrm{clamp}\left(100\,\mathrm{MiB},\ 0.25\cdot\left(\text{system RAM}-2\,\mathrm{GiB}\right),\ 2\,\mathrm{GiB}\right)$$

If system RAM cannot be determined, assume 4 GiB on 64-bit builds and 2 GiB on 32-bit builds.
Manual -dbcache values are unchanged and still override the automatic default.

Warning threshold update

Oversized -dbcache warnings are kept and aligned with the new policy - warn if:

  • dbcache > default-dbcache when RAM < fallback RAM (4 GiB on 64-bit, 2 GiB on 32-bit)
  • dbcache > 75% of total RAM, otherwise

Performance measurements

reindex-chainstate

image
Machine 450 MiB 1000 MiB 1536 MiB 2000 MiB 3000 MiB
M4 Max 16c, 64Gi, SSD 5h 37m 3h 12m 2h 17m 1h 57m
i7-7700 HDD 8c, 62Gi, HDD 13h 09m 10h 24m 8h 42m 8h 30m
rpi5-8 4c, 7.7Gi, SSD 52h 06m 36h 24m
rpi5-16 4c, 15Gi, SSD 13h 35m 12h 10m 10h 54m 10h 45m
i9-9900K 16c, 62Gi, SSD 6h 07m 5h 28m 5h 05m 4h 57m
Xeon E5 Win 8c, 31Gi, SSD 10h 28m 9h 30m 8h 36m 8h 24m
umbrel N150 4c, 15Gi, SSD 9h 20m 8h 19m 7h 52m 7h 45m

N150 (Umbrel) - 1.18x faster
for DBCACHE in 450 1000 2000 3000; do \
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \
  STOP=936639; 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 "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; 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 -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
                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";
done

097c182 Merge #34385: subprocess: Fix -Wunused-private-field when building with clang-cl on Windows

2026-02-19 | reindex-chainstate | 936639 blocks | dbcache 450 | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        33619.332 s               [User: 61450.140 s, System: 5671.449 s]

2026-02-25 | reindex-chainstate | 936639 blocks | dbcache 1000 | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)

  Time (abs ≡):        29948.973 s               [User: 51470.950 s, System: 3405.477 s]

2026-02-25 | reindex-chainstate | 936639 blocks | dbcache 2000 | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        28352.766 s               [User: 46633.422 s, System: 2298.085 s]

2026-02-20 | reindex-chainstate | 936639 blocks | dbcache 3000 | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        27910.440 s               [User: 44917.351 s, System: 2090.958 s]
Rpi5 (16) - 1.24x faster
for DBCACHE in 450 1000 2000 3000; do \
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \
  STOP=936639; 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 "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; 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 -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
                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";
done

097c182 Merge #34385: subprocess: Fix -Wunused-private-field when building with clang-cl on Windows

2026-02-19 | reindex-chainstate | 936639 blocks | dbcache 450 | rpi5-16-2 | aarch64 | Cortex-A76 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        48933.904 s               [User: 94560.562 s, System: 7823.574 s]

2026-02-24 | reindex-chainstate | 936639 blocks | dbcache 1000 | rpi5-16-2 | aarch64 | Cortex-A76 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        43856.539 s               [User: 78451.084 s, System: 5183.434 s]

2026-02-25 | reindex-chainstate | 936639 blocks | dbcache 2000 | rpi5-16-2 | aarch64 | Cortex-A76 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        39259.025 s               [User: 69044.492 s, System: 3227.006 s]

2026-02-26 | reindex-chainstate | 936639 blocks | dbcache 3000 | rpi5-16-2 | aarch64 | Cortex-A76 | 4 cores | 15Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        38756.884 s               [User: 65947.478 s, System: 2887.489 s]
Rpi5 (8) - 1.43x faster
for DBCACHE in 450 1000 1536 2000; do \
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \
  STOP=936639; 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 "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; 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 -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
                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";
done

097c182 Merge #34385: subprocess: Fix -Wunused-private-field when building with clang-cl on Windows

2026-02-19 | reindex-chainstate | 936639 blocks | dbcache 450 | rpi5-8 | aarch64 | Cortex-A76 | 4 cores | 7.7Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        187564.724 s               [User: 98927.942 s, System: 19100.209 s]

2026-02-25 | reindex-chainstate | 936639 blocks | dbcache 1000 | rpi5-8 | aarch64 | Cortex-A76 | 4 cores | 7.7Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        132856.721 s               [User: 77471.886 s, System: 13535.639 s]

2026-02-22 | reindex-chainstate | 936639 blocks | dbcache 1536 | rpi5-8 | aarch64 | Cortex-A76 | 4 cores | 7.7Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1536 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        131061.887 s               [User: 71355.478 s, System: 12038.086 s]

2026-02-27 | reindex-chainstate | 936639 blocks | dbcache 2000 | rpi5-8 | aarch64 | Cortex-A76 | 4 cores | 7.7Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        125837.682 s               [User: 69422.152 s, System: 11621.279 s]
Apple M4 Max - 2.46x faster
for DBCACHE in 450 1000 2000 3000; do \
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \
  STOP=900000; \
  DATA_DIR="$HOME/Library/Application Support/Bitcoin"; LOG_DIR="$HOME/bitcoin-reindex-logs"; mkdir -p "$LOG_DIR"; \
  COMMA_COMMITS=${COMMITS// /,}; \
  (echo ""; for c in $(echo $COMMITS); do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \
  (echo "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(sysctl -n machdep.cpu.brand_string) | $(nproc) cores | $(printf '%.1fGiB' "$(( $(sysctl -n hw.memsize)/1024/1024/1024 ))") RAM | SSD | $(sw_vers -productName) $(sw_vers -productVersion) $(sw_vers -buildVersion) | $(xcrun clang --version | head -1)"; echo "") && \
  hyperfine \
    --sort command \
    --runs 1 \
    --export-json "$LOG_DIR/rdx-$(echo "$COMMITS" | sed -E 's/([a-f0-9]{8})[a-f0-9]+ ?/\1-/g;s/-$//')-$STOP-$DBCACHE-appleclang.json" \
    --parameter-list COMMIT "$COMMA_COMMITS" \
    --prepare "killall -9 bitcoind 2>/dev/null || true; rm -f \"$DATA_DIR\"/debug.log; git checkout {COMMIT}; git reset --hard && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && ninja -C build bitcoind -j2 && \
      ./build/bin/bitcoind -datadir=\"$DATA_DIR\" -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20" \
    --conclude "killall bitcoind 2>/dev/null || true; sleep 5; grep -q 'height=0' \"$DATA_DIR\"/debug.log && grep -q 'Disabling script verification at block #1' \"$DATA_DIR\"/debug.log && grep -q \"height=$STOP\" \"$DATA_DIR\"/debug.log || { echo 'debug.log assertions failed'; exit 1; }; \
                cp \"$DATA_DIR\"/debug.log \"$LOG_DIR\"/debug-{COMMIT}-\$(date +%s).log 2>/dev/null || true" \
    "./build/bin/bitcoind -datadir=\"$DATA_DIR\" -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0";
done

097c182 Merge #34385: subprocess: Fix -Wunused-private-field when building with clang-cl on Windows

2026-02-21 | reindex-chainstate | 900000 blocks | dbcache 450 | M4-Max.local | arm64 | Apple M4 Max | 16 cores | 64.0GiB RAM | SSD | macOS 26.2 25C56 | Apple clang version 17.0.0 (clang-1700.6.3.2)

Benchmark 1: ./build/bin/bitcoind -datadir="/Users/lorinc/Library/Application Support/Bitcoin" -stopatheight=900000 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        20261.315 s               [User: 18768.521 s, System: 4271.673 s]

2026-02-27 | reindex-chainstate | 900000 blocks | dbcache 1000 | M4-Max.local | arm64 | Apple M4 Max | 16 cores | 64.0GiB RAM | SSD | macOS 26.3 25D125 | Apple clang version 17.0.0 (clang-1700.6.3.2)

Benchmark 1: ./build/bin/bitcoind -datadir="/Users/lorinc/Library/Application Support/Bitcoin" -stopatheight=900000 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        11574.620 s               [User: 12147.446 s, System: 2417.914 s]

2026-02-26 | reindex-chainstate | 900000 blocks | dbcache 2000 | M4-Max.local | arm64 | Apple M4 Max | 16 cores | 64.0GiB RAM | SSD | macOS 26.3 25D125 | Apple clang version 17.0.0 (clang-1700.6.3.2)

Benchmark 1: ./build/bin/bitcoind -datadir="/Users/lorinc/Library/Application Support/Bitcoin" -stopatheight=900000 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        8226.676 s               [User: 9083.949 s, System: 1179.971 s]

2026-02-21 | reindex-chainstate | 900000 blocks | dbcache 3000 | M4-Max.local | arm64 | Apple M4 Max | 16 cores | 64.0GiB RAM | SSD | macOS 26.2 25C56 | Apple clang version 17.0.0 (clang-1700.6.3.2)

Benchmark 1: ./build/bin/bitcoind -datadir="/Users/lorinc/Library/Application Support/Bitcoin" -stopatheight=900000 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        7064.184 s               [User: 7701.391 s, System: 668.060 s]
i7 HDD - 1.51x faster
for DBCACHE in 450 1000 2000 3000; do \
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \
  STOP=936639; 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 "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; 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 -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
                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";
done

097c182 Merge #34385: subprocess: Fix -Wunused-private-field when building with clang-cl on Windows

2026-02-21 | reindex-chainstate | 936639 blocks | dbcache 450 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        47384.896 s               [User: 54460.024 s, System: 3388.557 s]

2026-02-24 | reindex-chainstate | 936639 blocks | dbcache 1000 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        37486.190 s               [User: 46563.682 s, System: 2189.609 s]

2026-02-25 | reindex-chainstate | 936639 blocks | dbcache 2000 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        31379.903 s               [User: 41546.866 s, System: 1383.556 s]

2026-02-21 | reindex-chainstate | 936639 blocks | dbcache 3000 | i7-hdd | x86_64 | Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz | 8 cores | 62Gi RAM | HDD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        30633.603 s               [User: 39511.869 s, System: 1116.371 s]
Windows Xeon - 1.24x faster
for DBCACHE in 450 1000 2000 3000; do \
  COMMITS="5401e673d56198f2c0bad366581e70d5d9cd765c"; \
  STOP=933339; \
  HOST=x86_64-w64-mingw32; \
  XPACK="/home/win/xpack-mingw-w64-gcc-15.2.0-2"; \
  BASE_DIR="/mnt/c/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
  WIN_DATA_DIR='C:\\my_storage\\BitcoinData'; \
  export PATH="$XPACK/bin:$PATH"; \
  mkdir -p "$LOG_DIR"; \
  (echo ""; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \
  (echo "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | win64-gcc15"; echo "") && \
  hyperfine \
    --sort command \
    --runs 1 \
    --export-json "$BASE_DIR/rdx-$(sed -E 's/(\w{8})\w+ ?/\1-/g;s/-$//'<<<"$COMMITS")-$STOP-$DBCACHE-win64.json" \
    --parameter-list COMMIT ${COMMITS// /,} \
    --prepare "taskkill.exe /IM bitcoind.exe /F 2>/dev/null; rm -f ./build/bin/bitcoind.exe; rm -f $DATA_DIR/debug.log; git clean -fxd -e depends/built -e depends/sources -e depends/$HOST; git reset --hard {COMMIT} && \
      make -C depends HOST=$HOST NO_QT=1 NO_ZMQ=1 CC=\"$XPACK/bin/x86_64-w64-mingw32-gcc\" CXX=\"$XPACK/bin/x86_64-w64-mingw32-g++\" -j\$(nproc) && \
      cmake -B build -G Ninja --toolchain depends/$HOST/toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_GUI=OFF -DWITH_ZMQ=OFF -DBUILD_TESTS=OFF -DBUILD_BENCH=OFF && \
      ninja -C build bitcoind -j\$(nproc) && \
      ./build/bin/bitcoind.exe -datadir=\"$WIN_DATA_DIR\" -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "taskkill.exe /IM bitcoind.exe /F 2>/dev/null; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
      cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-\$(date +%s).log" \
    "./build/bin/bitcoind.exe -datadir=\"$WIN_DATA_DIR\" -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0"; \
done

5401e67 Merge #33604: p2p: Allow block downloads from peers without snapshot block after assumeutxo validation

2026-02-10 | reindex-chainstate | 933339 blocks | dbcache 450 | WIN-A2EHOAU4JET | x86_64 | Intel(R) Xeon(R) CPU E5-2637 v2 @ 3.50GHz | 8 cores | 31Gi RAM | win64-gcc15

Benchmark 1: ./build/bin/bitcoind.exe -datadir="C:\\my_storage\\BitcoinData" -stopatheight=933339 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 5401e673d56198f2c0bad366581e70d5d9cd765c)
  Time (abs ≡):        37691.648 s               [User: 0.000 s, System: 0.001 s]

2026-02-24 | reindex-chainstate | 933339 blocks | dbcache 1000 | WIN-A2EHOAU4JET | x86_64 | Intel(R) Xeon(R) CPU E5-2637 v2 @ 3.50GHz | 8 cores | 31Gi RAM | win64-gcc15

Benchmark 1: ./build/bin/bitcoind.exe -datadir="C:\\my_storage\\BitcoinData" -stopatheight=933339 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 5401e673d56198f2c0bad366581e70d5d9cd765c)
  Time (abs ≡):        34250.996 s               [User: 0.002 s, System: 0.000 s]

2026-02-26 | reindex-chainstate | 933339 blocks | dbcache 2000 | WIN-A2EHOAU4JET | x86_64 | Intel(R) Xeon(R) CPU E5-2637 v2 @ 3.50GHz | 8 cores | 31Gi RAM | win64-gcc15

Benchmark 1: ./build/bin/bitcoind.exe -datadir="C:\\my_storage\\BitcoinData" -stopatheight=933339 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 5401e673d56198f2c0bad366581e70d5d9cd765c)
  Time (abs ≡):        31001.091 s               [User: 0.000 s, System: 0.002 s]

2026-02-21 | reindex-chainstate | 933339 blocks | dbcache 3000 | WIN-A2EHOAU4JET | x86_64 | Intel(R) Xeon(R) CPU E5-2637 v2 @ 3.50GHz | 8 cores | 31Gi RAM | win64-gcc15

Benchmark 2: ./build/bin/bitcoind.exe -datadir="C:\\my_storage\\BitcoinData" -stopatheight=933339 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 5401e673d56198f2c0bad366581e70d5d9cd765c)
  Time (abs ≡):        30276.311 s               [User: 0.001 s, System: 0.000 s]
i9 - 1.20x faster
for DBCACHE in 450 1000 2000 3000; do \                                                                                                                                        
  COMMITS="097c18239b58a8ee03794b3ebc6e722a0da30d8d"; \                                                   
  STOP=936639; 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 "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; 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 -9 bitcoind 2>/dev/null; rm -f ./build/bin/bitcoind; git clean -fxd; git reset --hard {COMMIT} && \
      cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \
      ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \
    --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q "$(printf %.12s {COMMIT})"; \
                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";
done

5401e67 Merge #33604: p2p: Allow block downloads from peers without snapshot block after assumeutxo validation

2026-02-26 | reindex-chainstate | 936639 blocks | dbcache 450 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=450 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        22076.658 s               [User: 53940.221 s, System: 3111.267 s]

2026-02-26 | reindex-chainstate | 936639 blocks | dbcache 1000 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=1000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        19724.533 s               [User: 45709.253 s, System: 2066.725 s]

2026-02-26 | reindex-chainstate | 936639 blocks | dbcache 2000 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=2000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        18302.184 s               [User: 40157.899 s, System: 1250.246 s]

2026-02-27 | reindex-chainstate | 936639 blocks | dbcache 3000 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=936639 -dbcache=3000 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 097c18239b58a8ee03794b3ebc6e722a0da30d8d)
  Time (abs ≡):        17858.670 s               [User: 38277.112 s, System: 1008.751 s]

Quick reference (64 bit)

System RAM Automatic default Warning fires above
1 GiB 100 MiB 100 MiB
2 GiB 100 MiB 100 MiB
4 GiB 512 MiB 3071 MiB
8 GiB 1536 MiB 6143 MiB
16 GiB 2048 MiB 12287 MiB
32 GiB 2048 MiB 24575 MiB
image

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 20, 2026

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK Bortlesboat
Concept ACK andrewtoth, willcl-ark, openoms

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #34448 (ci, iwyu: Fix warnings in src/util and treat them as errors by hebasto)
  • #34435 (refactor: use _MiB/_GiB consistently for byte conversions by l0rinc)
  • #34038 (logging: replace -loglevel with -trace, various API improvements by ajtowns)
  • #32427 ((RFC) kernel: Replace leveldb-based BlockTreeDB with flat-file based store by sedited)
  • #28690 (build: Introduce internal kernel library by sedited)
  • #26022 (Add util::ResultPtr class by ryanofsky)
  • #25665 (refactor: Add util::Result failure types and ability to merge result values by ryanofsky)

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.

@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch 3 times, most recently from 30bfcdf to 0f93881 Compare February 21, 2026 08:59
@l0rinc l0rinc marked this pull request as ready for review February 21, 2026 13:20
@l0rinc
Copy link
Contributor Author

l0rinc commented Feb 21, 2026

It took a few rounds to tame (I had a Kernel Panic myself when I saw missing <PLATFORM_ID:Windows>:ws2_32 errors), but it's ready for review.
I will add new measurements to the PR description after I have more instances.
If someone could benchmark it on 8 and 4 GB machines, I'd appreciate it.
For the record, the bitcoind startup looks like this now:

2026-02-21T13:24:38Z * Using 2.0 MiB for block index database
2026-02-21T13:24:38Z * Using 8.0 MiB for chain state database
2026-02-21T13:24:38Z * Using 2990.0 MiB for in-memory UTXO set (plus up to 286.1 MiB of unused mempool space)

Copy link
Contributor

@andrewtoth andrewtoth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

- `-dbcache=<n>` - the UTXO database cache size, this defaults to `450`. The unit is MiB (1024).
- The minimum value for `-dbcache` is 4.
- A lower `-dbcache` makes initial sync time much longer. After the initial sync, the effect is less pronounced for most use-cases, unless fast validation of blocks is important, such as for mining.
- `-dbcache=<n>` - UTXO database cache size in MiB (minimum `4`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we changing this so much?
I think we should just remove this defaults to 450`` from this line, and add the below point:
- Automatic default scales with system RAM and can be as high as 1000` MiB.`
Then keep the rest the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, simplified, thanks.

----------------------------------

- When `-dbcache` is not set explicitly, Bitcoin Core now chooses a RAM-aware default that can be as high as `3000` MiB.
Explicit `-dbcache` values continue to override the automatic default. (#34641)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also say something like:
"To maintain the same behavior of previous releases, set dbcache to 450."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

//! Automatic -dbcache floor (bytes)
static constexpr size_t MIN_DEFAULT_DBCACHE{100_MiB};
//! Automatic -dbcache cap (bytes)
static constexpr size_t MAX_DEFAULT_DBCACHE{3000_MiB};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be more conservative and set this to 1GB as discussed in the IRC meeting. Such a large bump in memory might be unexpected, especially when running bitcoind alongside other applications and not a dedicated node machine.

Suggested change
static constexpr size_t MAX_DEFAULT_DBCACHE{3000_MiB};
static constexpr size_t MAX_DEFAULT_DBCACHE{1000_MiB};

Copy link
Contributor Author

@l0rinc l0rinc Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 1 GB limit was a suggestion for a constant dbcache, which I opposed because it was too high for cheaper nodes (and too low for performant ones).
But since we can scale with total memory now, it makes sense to go higher, especially since 3-4 GB already achieves most of the speedup we saw.
This way, we have a simple formula instead of custom values for each memory.

System RAM Automatic default
1 GiB 100 MiB
2 GiB 100 MiB
4 GiB 512 MiB
8 GiB 1536 MiB
16 GiB 3000 MiB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too worried about this, because I suspect that most low-memory systems have explicit configurations anyway (either because they need it already, or because they're managed by node-in-a-box / distributions). And we should have loud and clear release notes for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really about low memory systems. We don't clear the cache anymore, so if we sync and then go to steady state the node will use lots of cache for pretty much its lifetime. For instance it finishes IBD at 2GB, it will take maybe a year to fill up to 3GB and finally clear. That's just being a bad OS citizen using that much memory when we don't need it.

Copy link
Member

@sipa sipa Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that sort of calls for a dbcache value that is different between IBD and later, which I guess makes sense if you're planning to run other things post-IBD. But on the other hand, a 1 GiB difference seems tiny for sufficiently large systems (which I suspect most non-dedicated setups are).

Assuming we don't want to do that in this PR, reducing to 2 GiB or 1 GiB default makes sense perhaps, but that reduces the benefit too.

Copy link
Contributor

@andrewtoth andrewtoth Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference in benefit? I think more than doubling the default (450->1000) is a pretty big speedup. What's going from 1->2->3?

I would think the initial bump would have the lions share of speedup. Could be wrong though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be right. I haven't actually used a dbcache setting below 8 GiB or so in years, so 🤷‍♂️.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm measuring with 1 and 2 cap so that we can compare.
Other that that, does the formula make sense?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There still looks like a substantial benefit to bumping to 1GB.

Another user posted the same concern as I https://x.com/lukechilds/status/2026875795080359955.

Perhaps it's worth doing a conservative bump for now, and work on a separate steady state dbcache value for v32. We could even have a lower general steady state value than 450MB, so running normally uses less RAM. Then we can bump to 3GB or more for IBD in v32.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, now that a few more measurements have come in, it's clear that the 2 GiB to 3 GiB speedup is negligible.
The 1 GiB to 2 GiB is still very much relevant - and affect 16 GiB machines only, 8 GiB machines were already capped at 1536. Changed the upper cap to 2 GiB for now.

I will push the steady state dbcache change after this change, separately, it makes more sense to restrict that once your parallelization PR lands and we don't need so much memory anyway.

@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from 4e68c80 to b67bb06 Compare February 25, 2026 10:03
@l0rinc
Copy link
Contributor Author

l0rinc commented Feb 25, 2026

Q: Now that the total memory is needed for more than just the warning, should we mandate that this method works for the whole CI infra (cc: @hebasto), see

if (!total) {
BOOST_WARN_MESSAGE(false, "skipping total_ram: total RAM unknown");
return;
}

I have addresses a few nits, I'm still measuring 1 and 2 GB dbcache values for comparison, added a few ones already to the PR description. The difference between 1 and 3 still seems relevant. I don't think a 16 GB machine should be surprised by that amount and an 8 GB is already capped at 1536 MB. Let's see the results and we can decide.

@hebasto
Copy link
Member

hebasto commented Feb 25, 2026

Q: Now that the total memory is needed for more than just the warning, should we mandate that this method works for the whole CI infra...

A related discussion happened here: #33435 (review).

We could make the test mandatory. If it fails in an unsupported environment, users can still explicitly disable it.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the new source files common/system_ram.cpp and node/dbcache.cpp be added to FILES_WITH_ENFORCED_IWYU?

Copy link
Member

@willcl-ark willcl-ark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

Seems like a reasonable first step towards something more like #8437, which I've wanted to see for a long time... In the case that never happens, this seems perfectly worthwhile on it's own, too.

Just one comment so far, re. handling the implementation-defined truncation, but mostly LGTM.

@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from b67bb06 to 7385c5b Compare February 25, 2026 12:05
@l0rinc
Copy link
Contributor Author

l0rinc commented Feb 25, 2026

Thanks @hebasto & @willcl-ark, addressed your concerns, added both as coauthor.

@hebasto
Copy link
Member

hebasto commented Feb 25, 2026

Could the new source files common/system_ram.cpp and node/dbcache.cpp be added to FILES_WITH_ENFORCED_IWYU?

The IWYU warnings in the CI seem to be legit.

@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from 7385c5b to 0348bda Compare February 25, 2026 12:20
@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from 0348bda to c4077b3 Compare February 25, 2026 12:27
//! -dbcache default (bytes)
static constexpr size_t DEFAULT_DB_CACHE{DEFAULT_KERNEL_CACHE};
//! Automatic -dbcache floor (bytes)
static constexpr size_t MIN_DEFAULT_DBCACHE{100_MiB};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure about reducing the default dbcache. The project has defaulted to 450 for nearly a decade. Looking through the issue tracker, I have not found too many users complaining about bitcoind oom-ing on low-RAM devices. Even the most popular raspberry pi installation guide bumps cache to 2GB: https://raspibolt.org/guide/bitcoin/bitcoin-client.html#configuration (I realize though that they are targeting devices with 4+GB of RAM). Do we really need to change this on the minimum side?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not found too many users complaining about bitcoind oom-ing on low-RAM devices

I have tried it and we can't do it with 2 GB total memory currently with default dbcache. The memory usage grows with threads, with mempool, with sigcache, connection count, memory buffers, etc.
With 4 GB ram we're already increasing the dbcache in this PR, so reduction only happens for extremely constrained machines. A 2 GB machine can barely finish an IBD and only if we explicitly reduce the dbcache to 100 (and even this OOM-ed on some flushes). That's what we're doing here automatically now.
A 2 GB machine can't even compile the source code without explicit swapping, so it makes sense to make these more realistic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the same reasoning apply to virtualizations where e.g. 2GB are allocated to running bitcoind in a jail? Reading through some of the issues in the tracker that seems a likely deployment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am running nodes in 2GB AWS EC2 instances just fine. Although mempool is fairly empty. I don't think IBD in such an instance is feasible though, with or without dbcache. Reducing this for steady state seems fine. I doubt users will notice.
Not sure why anyone would want to build on such an instance either.

#ifdef WIN32
if (MEMORYSTATUSEX m{}; (m.dwLength = sizeof(m), GlobalMemoryStatusEx(&m))) return clamp(m.ullTotalPhys);
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__illumos__) || defined(__linux__)
if (long p{sysconf(_SC_PHYS_PAGES)}, s{sysconf(_SC_PAGESIZE)}; p > 0 && s > 0) return clamp(1ULL * p * s);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this will work correctly in a containerized environment. E.g. what happens if you create a docker container with bounded memory? From what I am gathering it will attempt to measure the hosts memory, not whatever the system administrator allocated for the container. I'm not sure if we can account for all these scenarios, meaning this change is likely to break a few deployments. A log message on init indicating what was measured and how much db cache allocation was scaled up would already go a ways to at least make this a bit more transparent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this was exactly what I was working on after talking to @openoms.

We could extend the total memory calculator to take cgroups and BSD jails into consideration, but for now (to avoid bikeshedding here), I only added a warning if /sys/fs/cgroup exists.
That extra warning also made me realize that we are not displaying the cache sizes in a uniform way, and there is no point in pretending we care about decimals for MiB, but we do for GiB. Since GiB is now the expected unit for -dbcache, I unified the display of the cache units.

Total RAM is also cached now so that we can call it multiple times, and it was renamed to TryGetTotalRam to differentiate it from the alternative that falls back to the default value.

Also made GetDefaultCache more testable by reverting to system ran if the optional parameter is not provided.

And lastly I regrouped the tests (data separated from logic) to simplify the RAM-aware commit diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted many of these based on followup reviews

@rustaceanrob
Copy link
Contributor

rustaceanrob commented Feb 28, 2026

Reindex chainstate benchmark:

A/B test bash script
#!/usr/bin/env bash
set -euo pipefail

SRC_DIR="${SRC_DIR:-$HOME/bitcoin-core/review}"
COMMIT_A="${COMMIT_A:-701b8d714861874449c3360f31bdb01512f10644}"
COMMIT_B="${COMMIT_B:-fbbc6162c7e9ad4c13c99442849fc279b98803d3}"
STOP="${STOP:-930000}"
# DBCACHE="${DBCACHE:-450}"
DATA_DIR="${DATA_DIR:-/data1}"
JOBS="${JOBS:-$(nproc)}"

git reset --hard $COMMIT_A
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_GUI=OFF -DWITH_ZMQ=OFF -DBUILD_TESTS=OFF -DBUILD_BENCH=OFF --log-level=ERROR
ninja -C build bitcoind -j $JOBS
time ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -daemon=0

git reset --hard $COMMIT_B
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_GUI=OFF -DWITH_ZMQ=OFF -DBUILD_TESTS=OFF -DBUILD_BENCH=OFF --log-level=ERROR
ninja -C build bitcoind -j $JOBS
time ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 -daemon=0
Hardware
$ lscpu
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  ...
CPU(s):                   16
  ...
  Model name:             AMD Ryzen 7 7700 8-Core Processor
$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi       5.3Gi       1.4Gi        16Mi        56Gi        56Gi

Before

HEAD is now at 701b8d7148 Merge bitcoin/bitcoin#34609: test: remove appveyor reference in comment
...
real	217m15.813s
user	425m36.988s
sys	52m50.304s

After

HEAD is now at fbbc6162c7 doc: add release notes for updated `dbcache` defaults
...
real	176m13.200s
user	325m36.977s
sys	23m54.572s

@optout21
Copy link
Contributor

optout21 commented Mar 4, 2026

This is a "low-hanging fruit" to improve IBD performance on default config nodes. The RAM-dependent default value for dbcache makes a lot of sense, and it's great to see the emprical data points collected! Some comments follow.

@optout21
Copy link
Contributor

optout21 commented Mar 4, 2026

A relevant data point is the current size of the UTXO set. This info could be added, mainly for reference for the future. The message of the commit where the default is computed would be a good place for that.

@optout21
Copy link
Contributor

optout21 commented Mar 4, 2026

To add a bit more structure, the 3 values of recommended default, recommended minimum and recommended maximum could be computed in the very same location (the latter two are used only for potential warnings in case a config override is supplied).

@optout21
Copy link
Contributor

optout21 commented Mar 4, 2026

Since 32/64-bit system distinction is made sometimes around memory size checks (e.g. in tests in this PR), it would make sense to formalize that as well, as a property of the system, similar to memory size. A common method in system (there are several different ways employed), and maybe also returned by GetDefaultCache and logged. But this may be out of the scope of this PR.

BOOST_CHECK(!ShouldWarnOversizedDbCache(/*dbcache=*/1500_MiB, /*total_ram=*/2048_MiB)); // Under cap
BOOST_CHECK( ShouldWarnOversizedDbCache(/*dbcache=*/1600_MiB, /*total_ram=*/2048_MiB)); // Over cap
constexpr size_t total_ram{3072_MiB};
BOOST_CHECK_EQUAL(GetDefaultCache(total_ram), (total_ram - RESERVED_RAM) / 4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: A concrete expected value would make more sense here IMHO, instead of repeating the implementation formula.

achow101 added a commit that referenced this pull request Mar 6, 2026
4ae9a10 doc: add release notes for dbcache bump (Andrew Toth)
c510d12 doc: update dbcache default in reduce-memory.md (Andrew Toth)
027cac8 qt: show GetDefaultDBCache() in settings (Andrew Toth)
5b34f25 dbcache: bump default from 450MB -> 1024MB if enough memory (Andrew Toth)

Pull request description:

  Alternative to #34641

  This increases the default `dbcache` value from `450MiB` to `1024MiB` if:
  - `dbcache` is unset
  - The system is 64 bit
  - At least 4GiB of RAM is detected

  Otherwise fallback to previous `450MiB` default.

  This should be simple enough to get into v31. The bump to 1GiB shows significant performance increases in #34641. It also alleviates concerns of too high default for steady state, and of lowering the current dbcache for systems with less RAM.

  This change only changes bitcoind behavior, while kernel still defaults to 450 MiB.

ACKs for top commit:
  ajtowns:
    ACK 4ae9a10
  kevkevinpal:
    reACK [4ae9a10](4ae9a10)
  svanstaa:
    ACK [4ae9a10](4ae9a10)
  achow101:
    ACK 4ae9a10
  sipa:
    ACK 4ae9a10

Tree-SHA512: ee3acf1fb08523ac80e37ec8f0caca226ffde6667f3a75ae6f4f4f54bc905a883ebcf1bf0e8a8a15c7cfabff96c23225825b3fff4506b9ab9936bf2c0a2c2513
Move the OS-dependent total RAM query from `common/system.cpp` into a dedicated `common/system_ram.cpp` translation unit and declare it in `common/system_ram.h`.

Update call sites to include `common/system_ram.h` and rename `GetTotalRAM()` to `TryGetTotalRam()`.
Add `common/system_ram` to `FILES_WITH_ENFORCED_IWYU` so this new source is checked at error level.
Since we use it for more than just warnings now, make `system_ram_tests` require RAM detection instead of skipping when unavailable.

Co-authored-by: sipa <sipa@bitcoincore.org>
Co-authored-by: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com>
@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from a6ec8c9 to 363c043 Compare March 6, 2026 21:33
@luke-jr
Copy link
Member

luke-jr commented Mar 7, 2026

The rebase was unclean, and reverts part of #34692 in d619cbd rather than just refactoring as the commit description claims.

argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", DEFAULT_DB_CACHE_BATCH), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (minimum %d, default: %d). Make sure you have enough RAM. In addition, unused memory allocated to the mempool is shared with this cache (see -maxmempool).", MIN_DB_CACHE >> 20, node::GetDefaultDBCache() >> 20), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why users suddenly don't need to make sure they have enough RAM..

Copy link
Contributor Author

@l0rinc l0rinc Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have exact warnings for when that's the case, instead of a general warning, see the commit message:

And now that we have warnings for excessive dbcache in the logs, we can remove the warning from -dbcache arg doc.

l0rinc and others added 3 commits March 7, 2026 23:01
The current `dbcache` policy is split across `node`, `qt`, and `kernel`.
The node startup path already chooses between `450 MiB` and `1024 MiB` based on detected RAM, while `bitcoinkernel` and the Qt settings migration still use fixed `450 MiB` values.

Move the default selection and oversized warning helpers into dedicated `node/dbcache` code and use them from all current callers.
This keeps the existing default selection logic in one place and makes the other users follow that same policy.

This also changes the low-memory oversized warning to use the shared default helper instead of a fixed `450 MiB` cap.
It changes `bitcoinkernel` and the Qt migration on 64-bit systems with at least 4 GiB RAM, where they now use the same `1024 MiB` default as the node path instead of a fixed `450 MiB`.

Co-authored-by: Luke Dashjr <luke-jr+git@utopios.org>
The fixed `-dbcache` default of 450 MiB is too high for small systems and too conservative for larger ones.
Compute the automatic default as `std::clamp((max(total_ram - 2 GiB, 0) / 4), 100 MiB, 2 GiB)`.
The 2 GB upper limit was chosen because it's still a lot faster than 1 GiB, but barely faster than 3 GiB.

When RAM detection is unavailable, fall back to 4 GiB on 64-bit and 2 GiB on 32-bit.
Use that fallback for default calculation and logging.

Align oversized `-dbcache` warnings with this policy on low-memory systems.
For total RAM below `FALLBACK_RAM_BYTES`, warn when the configured value exceeds the automatic default.
Keep the 75% of total RAM warning cap for larger systems.

Log the automatically selected default when `-dbcache` is not explicitly set.

And now that we have warnings for excessive dbcache in the logs, we can remove the warning from `-dbcache` arg doc.

Also assert the default `dbcache` never triggers the oversized warning.

Co-authored-by: Pieter Wuille <pieter@wuille.net>
Co-authored-by: willcl-ark <will8clark@gmail.com>
Co-authored-by: ismaelsadeeq <abubakarsadiqismail@proton.me>
Also update related docs.

Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
@l0rinc l0rinc force-pushed the l0rinc/dynamic-dbcache branch from 363c043 to 8ff5e8a Compare March 7, 2026 23:01
@l0rinc
Copy link
Contributor Author

l0rinc commented Mar 7, 2026

reverts [...] rather than just refactoring as the commit description claims.

Yes, it was because the other change ignored kernel and the warning test failed, so it was easier to just revert it at the beginning.
But you're right, it's not a pure refactor because of the kernel and warning unification, and it's cleaner to do the algorithm change in a single commit. This way, the move and cleanup commits preserve current behavior, though the test commit needed to be merged with it to avoid needlessly exercising the old behavior.
The end result is the same, but the commits might be structured more simply.

Copy link

@Bortlesboat Bortlesboat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK 8ff5e8a. Clean separation into system_ram.h and dbcache.h, and the (total_ram - RESERVED) / 4 formula is intuitive. Left one question about the warning threshold.


bool ShouldWarnOversizedDbCache(size_t dbcache, size_t total_ram) noexcept
{
return (total_ram < FALLBACK_RAM_BYTES) ? dbcache > GetDefaultDBCache(total_ram)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the warning threshold discontinuity at FALLBACK_RAM_BYTES intentional? At 4095 MiB the warning trips at >511 MiB (the auto default), but at 4096 MiB it jumps to >3072 MiB (75% of RAM) — a 6x jump from 1 MiB of extra RAM. Would it make sense to use the 75% path unconditionally, or smooth the transition?

@Bortlesboat
Copy link

One follow-up question: now that #34692 merged with a fixed 1 GiB default for the kernel, this PR replaces DEFAULT_KERNEL_CACHE with GetDefaultDBCache() in bitcoinkernel.cpp. Is there a concern that kernel library consumers in constrained environments (e.g. embedded, no RAM detection) silently get a different default than the 1 GiB they might now expect?


std::optional<size_t> GetTotalRAM()
{
[[maybe_unused]] auto clamp{[](uint64_t v) { return size_t(std::min(v, uint64_t{std::numeric_limits<size_t>::max()})); }};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should be a global utility function (templated?) somewhere else

Consider renaming it to clamp_to_type or something that can't be confused with std::clamp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was just moved in this PR, the naming was deliberate - but I'll consider it next time I push, thanks for the hint

Comment on lines -116 to -126
[[maybe_unused]] auto clamp{[](uint64_t v) { return size_t(std::min(v, uint64_t{std::numeric_limits<size_t>::max()})); }};
#ifdef WIN32
if (MEMORYSTATUSEX m{}; (m.dwLength = sizeof(m), GlobalMemoryStatusEx(&m))) return clamp(m.ullTotalPhys);
#elif defined(__APPLE__) || \
defined(__FreeBSD__) || \
defined(__NetBSD__) || \
defined(__OpenBSD__) || \
defined(__illumos__) || \
defined(__linux__)
if (long p{sysconf(_SC_PHYS_PAGES)}, s{sysconf(_SC_PAGESIZE)}; p > 0 && s > 0) return clamp(1ULL * p * s);
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all-on-one-line style is annoying to read. Suggest formatting it normally.

Copy link

@Bortlesboat Bortlesboat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK 8ff5e8a. Built and ran caches_tests + system_ram_tests locally, all 5 cases pass. The test coverage is solid — default_dbcache_never_warns is a nice invariant check, and the CheckDbCacheWarnThreshold helper makes the boundary tests much cleaner than the old inline checks. The system_ram_tests simplification (Assert instead of optional+early-return) makes sense given this should never fail on CI hardware.

@l0rinc
Copy link
Contributor Author

l0rinc commented Mar 8, 2026

makes sense given this should never fail on CI hardware

Please stop these useless bot responses, we need real reviews

@Bortlesboat
Copy link

Apologies

Comment on lines +22 to +26
static constexpr size_t FALLBACK_RAM_BYTES{SIZE_MAX == UINT64_MAX ? 4096_MiB : 2048_MiB};
//! Reserved non-dbcache memory usage.
static constexpr size_t RESERVED_RAM{2048_MiB};
//! Maximum dbcache size on current architecture.
static constexpr size_t MAX_DBCACHE_BYTES{SIZE_MAX == UINT64_MAX ? std::numeric_limits<size_t>::max() : 1024_MiB};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== seems too strict for this. Probably should be SIZE_MAX > UINT32_MAX

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How so? My understanding is that we only support 32-bit and 64-bit architectures.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about sizeof(void*) == 8, I've got the impression that this is used more often in the codebase (and it's also my weak personal preference).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use that as well. I deliberately chose the version where the constant we compare against contains 64 explicitly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using sizeof(void*), on grounds of consistency. A quick search on sizeof(void*) yielded 10 instances where it is used to differentiate 32 vs. 64-bit systems. For SIZE_MAX I found only 2 (both in tests).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.