Conversation
…t always in docker `./run.sh <scenario> --flame` builds the bench with the `cpu-profile` feature (pprof, 99 Hz SIGPROF sampler) and writes flamegraph.svg. `--memory-snapshot` builds with `heap-profile` (jemalloc as the global allocator, sampling heap profiling) and writes heap-peak.svg: the live heap at the highest RSS of the run. A thread re-dumps the profile each time VmRSS grows by 5 % and only the last dump is rendered, after the sync. Without the flags neither profiler is compiled in. perf is not usable on the benchmark host (perf_event_paranoid=4) nor reliably under Docker Desktop, and jeprof needed path and placeholder workarounds, so both profilers now run inside the process. peak_rss_mib is read before the profiles are rendered: rendering the CPU flamegraph of a 20-minute run allocates ~2 GB, which otherwise showed up as a 4703 MiB peak and filled half of the heap snapshot. The client now always runs in the client container, shaped or not, and the binary is always built inside the Rust image, so it links against the container's glibc (a host build fails to start on hosts with a newer glibc than bookworm's). This drops the host-run path, the perf/perl/FlameGraph/OS checks and the policer fallback for kernels without ifb: a run whose download rate cannot be enforced now fails instead of producing a result that cannot be compared. `--wallets` with a relative path now resolves against the caller's directory instead of dash-spv-bench/. run.sh goes from 664 to 366 lines. Mainnet restore, mainnet.100mbi.100ms, on dev, --flame --memory-snapshot: 20.3 min, peak_rss_mib 2280, heap snapshot at 2243 MiB RSS (1.88 GB live), 14114383 sat, 13389 addresses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Reverts ae2a5d6 (#946). Segments were released from memory only when a sync manager remembered to call `set_committed_height` on the block and filter storages, and only on the next timed persist. The resident set should be bounded by the segment cache itself, not by callers reporting progress to storage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Every segment request now logs at trace level whether it was served from a resident segment or, on a miss, where the segment came from (`evicted`, `disk` or `new`). Enable it per module with RUST_LOG=dash_spv::storage::segments=trace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
With the committed-height release gone, the segment cache itself has to bound the resident set. Ten 50 000-item segments per cache kept most of a restore in memory; two keep the peak below what the release achieved, without costing time. Mainnet restore, mainnet.100mbi.100ms, #1015/#1016/#1014 applied, jemalloc heap profiling, wallet identical in every run (14114383 sat, 7112 records, 13389 addresses): segments peak RSS time segment loads from disk (headers/filter headers/filters/blocks) 10 + #946 982 MiB 8.2 min - 10 1490 MiB 8.8 min 84 / 2 / 28 / 0 2 930 MiB 8.8 min 212 / 103 / 74 / 0 1 1000 MiB 7.7 min 427 / 742 / 156 / 33 Each cache served ~2.34M requests from memory in every run. One segment starts reloading block segments of up to 87 MB from disk, which raises the peak again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
A dirty segment leaving the resident set used to wait in `evicted`, in memory, until the next 5 s storage tick wrote it. The resident limit was therefore not a limit: every segment evicted between two ticks stayed in memory. Now eviction writes the segment first and only then drops it; if the write fails the segment stays resident and the error is returned. The `evicted` map is gone, and the tick only persists the resident segments. Mainnet restore, mainnet.100mbi.100ms, #1015/#1016/#1014 applied, two resident segments: 7.7 and 7.9 min, peak RSS 943 and 948 MiB, wallet identical (14114383 sat, 7112 records, 13389 addresses). No cache misses served from `evicted` any more; header, filter header and filter segments are reloaded from disk about as often as they used to come back from `evicted` (~270 / ~150 / ~90), block segments never. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
… one buffer `Segment::persist` encoded the whole segment into a `Vec` before handing it to `atomic_write`. For a block segment near the tip (up to 87 MB on disk) that `Vec` grew to 128 MiB, and since dirty segments are now written on eviction this happened in the middle of the sync. `atomic_write_items` encodes one item at a time through a 1 MiB `BufWriter` into the temporary file, then syncs and renames it as before. `atomic_write` keeps its behaviour and shares the temporary file and rename logic. Mainnet restore, mainnet.100mbi.100ms, #1015/#1016/#1014 applied, two resident segments, wallet identical in every run (14114383 sat, 7112 records, 13389 addresses): whole-segment buffer peak RSS 943 / 948 MiB 7.7 / 7.9 min streamed peak RSS 845 / 855 / 856 MiB 9.4 / 7.7 / 6.5 min The persist buffer, 68–133 MiB in the earlier heap snapshots, no longer shows up. The 9.4 min run lost time to peers in the header and filter header phases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Every segment cache held 50 000 items, whatever an item costs. Near the tip a block segment holds tens of MB of decoded blocks, while a header segment spanning the same heights is 5.6 MB and a filter header one 1.6 MB. `Persistable::ITEMS_PER_SEGMENT` lets each type choose: headers 10 000 (~1.1 MB per segment), filter headers 50 000 (~1.6 MB), filters 2 000 (~2 MB near the tip) and blocks 1 000. This changes the on-disk layout of the header, filter and block segments. Storage written with 50 000-item segments is misread by this layout and has to be deleted until a migration or a versioned folder lands. Mainnet restore, mainnet.100mbi.100ms, #1015/#1016/#1014 applied, two resident segments, jemalloc heap profiling, wallet identical in every run (14114383 sat, 7112 records, 13389 addresses): segment items time peak RSS segment caches 50 000 for every type 7.0 min 818 MiB 332 MiB 5 000 for every type 7.3-8.3 min 554-687 MiB 23-106 MiB 1 000 for every type 9.2-12.2 min 453-577 MiB 2-66 MiB per type (this commit) 7.5 min 538 MiB 38 MiB With 1 000 items everywhere, the header and filter header phases paid an fsync per evicted segment (105-141 s and 254-332 s instead of ~60 s and ~150 s). Blocks at 500 items saved ~18 MiB of cache but reloaded 50 % more block segments. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Nothing outside `storage` implements or names the trait, and the `segments` module it lives in is private to `storage` already. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #1024 +/- ##
==========================================
- Coverage 77.23% 77.17% -0.07%
==========================================
Files 329 329
Lines 83862 83686 -176
==========================================
- Hits 64768 64581 -187
- Misses 19094 19105 +11
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reduce dash-spv RAM usage
Draft: the measurements below were taken with #1015, #1016 and #1014 applied (the committed-range sweep removal and its prerequisites). This branch does not depend on them to build or pass tests, but it stays a draft until they merge and until the on-disk format change below is handled.
Peak RAM, mainnet restore
dash-spv-bench, scenariomainnet.100mbi.100ms, one wallet. Every run ends with the same wallet state: 14114383 sat, 7112 transaction records, 13389 addresses.dev(committed-range sweep still in)dev+ #1015 + #1016 + #1014dev+ #1015 + #1016 + #1014 + this branchWith the sync fixes applied, this branch roughly halves the peak with no cost in sync time.
jemalloc numbers come from
--memory-snapshotruns, which link jemalloc with heap profiling. glibc numbers come from plain runs with the default allocator.Changes, in commit order
Bench: in-process profiling, client always in docker.
run.sh --flamebuilds with thecpu-profilefeature (pprof) and writesflamegraph.svg.--memory-snapshotbuilds with theheap-profilefeature (jemalloc) and writesheap-peak.svg, the live heap at the highest RSS of the run.Neither profiler is compiled in without its flag.
The client always runs in its container, built inside the Rust image.
Download shaping requires
ifb; the policer fallback is gone.run.shgoes from 664 to 366 lines.Drop the committed-height segment release (reverts fix(dash-spv): release clean storage segments below the committed height during long scans #946). Memory was only freed when a sync manager called
set_committed_height, and only on the next timed persist. The segment cache now bounds its resident set by itself.Trace segment cache hits and misses. One trace-level line per segment request says whether it was a hit, or where a miss came from (
diskornew). Enable it withRUST_LOG=dash_spv::storage::segments=trace.Keep at most two segments resident per storage cache (was ten). Each cache still serves ~2.34M requests from memory per restore.
Persist a dirty segment when it is evicted. Dirty segments no longer wait in an in-memory
evictedmap for the 5 s storage tick, so the resident limit is a real limit.Stream segments to disk.
Segment::persistused to encode the whole segment into oneVec, up to 128 MiB for a block segment near the tip. It now writes item by item through aBufWriterinto the temporary file, which is then synced and renamed as before.Size storage segments per item type (
Persistable::ITEMS_PER_SEGMENT, previously 50 000 for every type):Persistableinside the storage module.On-disk format
The per-type segment sizes change the layout of the header, filter and block segment files. With this branch, storage written with 50 000-item segments is misread. Before this leaves draft it needs either a versioned folder (forcing a re-sync) or a migration on open.
Testing
cargo clippy --all-features --all-targets -D warnings.dash-spvlib tests, thedash-spvdashd integration suite, anddash-spv-ffidashd_sync.What remains in the heap
From the jemalloc heap profile at the peak: