You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(rpc): read the chain-info figures off the block log instead of folding it
`Context::blocks` holds one record per applied block and grows for the life of
the process — ~963k entries on a mainnet node. `getblockchaininfo` and
`getchaintxstats` both walked all of it to produce five scalars, and
`getblockchaininfo` used exactly one of them: it folded 963k records to report
`size_on_disk` and discarded the rest.
The walk ran under the log read lock, which is the lock `apply_block` takes to
append the record for the block it just connected, so the call stalled block
application for its duration and got slower with every block.
`BlockLog` now maintains what the fold used to compute. `total_body_size` is a
running sum and answers `size_on_disk` outright. `cumulative_tx_count[i]` is the
sum over `records[..=i]` and answers both transaction counts as differences
across two boundaries. It is a type rather than a `Vec<BlockRecord>` with totals
kept beside it because the log is appended from `apply`, from
`Context::add_block` and from tests; a total any of those could forget to update
is a total that will drift. Reads are unchanged - it derefs to `[BlockRecord]`.
`chain_stats` replaces the fold for `getchaintxstats`. The log is appended in
height order and only ever popped from the tail, so it binary-searches the three
boundaries it needs - the same property `Context::block_at_height` already
relies on. Only the caller's window is then walked, and only for
`earliest_window_time`: block timestamps are not monotonic, so no prefix sum can
answer a minimum over them.
Measured over one fixture in one process, `before_fold` against `after_indexed`:
10,000 records: 19.32 us -> 3.83 us 5.0x
100,000 records: 552.70 us -> 3.94 us 140x
500,000 records: 4.214 ms -> 4.24 us 993x
963,124 records: 7.450 ms -> 3.77 us 1,977x
End to end the dispatch is flat: `getblockchaininfo` 2.79 us and
`getchaintxstats` 4.43 us at 963k records, against 2.66 us and 4.88 us at 10k.
Prefix sums rather than one more running total because the benchmark caught the
cheaper version being a cliff. A single total answers `txcount` only when the
applied tip is the log last record; anywhere else it subtracts the records above
the tip, and with no applied tip that tail is the whole log. `getchaintxstats`
measured 6.23 ms while its own reader measured 4.79 us. Prefix sums cost 8 bytes
per record, ~7.7 MB at a mainnet tip against the ~254 MB the records occupy.
`fold_block_records` is retained whole as the oracle and as the benchmark before
arm. `chain_stats_matches_the_fold_it_replaced` sweeps every applied height
against every window length over a log with a duplicate height and a backwards
timestamp. Ten mutations, all killed; see docs/benchmarks/chain-info-fold.md,
which also records that the first audit run misreported every kill as an invalid
mutation because the harness could not tell a red test from a broken build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments