Skip to content

Make historical cache maintenance proportional to pending work - #8525

Merged
Amaury Chamayou (achamayou) merged 10 commits into
mainfrom
achamayou-upgraded-pancake
Oct 10, 2026
Merged

Amaury Chamayou (achamayou) merged 10 commits into
mainfrom
achamayou-upgraded-pancake

Conversation

@achamayou

@achamayou Amaury Chamayou (achamayou) commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Motivation

Every historical cache tick walked every retained request (to count down its expiry) and every store slot (to lock the weak pointer and look for pending fetches), whether or not anything needed doing. With the default 30 minute retention, a burst of historical queries keeps the cache scanning tens of thousands of entries per tick, under requests_lock, on the dispatch thread. In a receipt-load reproduction on 7.0.17 (~7,300 requests and ~21,600 store slots per node, 60 s of load then 65 s of observation), the tick ran ~100 times per second with a mean of ~3 ms and a maximum of ~8 ms after load had ended, about 27-30% of wall time in each one-second window. With only the first commit of this PR applied, the same profile still showed mean tick cost during load rising from ~0.25 ms to ~1.1 ms as retained store slots grew from ~3,900 to ~15,000, with the store sweep running on more than half of ticks. The scanning logic is unchanged on main.

Implementation summary

tick() now visits only entries with work to do, using three pieces of cache-internal bookkeeping, all mutated under requests_lock:

  • Expiry: each request stores an absolute deadline on a cache-local clock advanced by tick(). expiry_order is a deadline-ordered set of (expiry_at, handle), updated on creation, renewal and removal, so the expiry pass pops only due requests. This keeps the expiry pass from reverting to a retained-size scan once deadlines start falling due; the reproductions above are shorter than the 1,800 s default lifetime, so they never exercised that pass.
  • Pending fetches: StoreMaintenance::pending_fetches is a weak, seqno-ordered index of stores still being fetched. Every StoreDetails creation site tracks the wrapper; entries leave the index when the store becomes trusted, a no-entry reply arrives, or the wrapper has expired. The retry sweep iterates only this index, with the first-tick/1 s retry arithmetic and range coalescing unchanged.
  • Released stores: every site that drops a strong owner (request drop, expiry, LRU eviction, no-entry deletion, retargeting of a handle, supporting-signature replacement, and the ledger-secret fetch handle) records the seqno in released_seqnos. The next tick checks each recorded slot in all_stores and forgets it if the weak pointer has expired, leaving a live replacement for the same seqno alone. Cleanup therefore keeps its next-tick timing, after any temporaries held by the releasing call have been destroyed.

Request removal is centralised in erase_request, and last_tick_work exposes per-tick counters (requests_expired, requests_evicted, released_seqnos_checked, pending_fetches_visited) to the implementation and its tests. They are four plain integer increments per tick, always on and included in the benchmark figures below; no per-entry clocks, logging or verification scans run in production (the invariant checker lives in the test subclass).

Complexity, with P pending fetches, C released candidates, D due or evicted requests, R retained requests and S store slots: a tick is O(1) when P = C = D = 0, and otherwise O(P + C log S + sum over the D removed requests of (log R + K_i log S)), where K_i is the number of stores and supporting signatures owned by the removed request: pending traversal is linear in P, each release candidate is one all_stores lookup, and removing a request erases its expiry_order and LRU entries and releases its whole owned range. Each get adds O(log R) for the expiry_order update. The tick therefore no longer visits unrelated retained state; when every retained request is genuinely due, or every retained store is genuinely pending, the work is necessarily proportional to that population. Not addressed here: handling each fetched ledger entry still iterates all requests (deserialise_ledger_entry's awaiting-secrets check and process_deserialised_store's interested-request loop).

Deliberate behaviour changes versus stock: arithmetic that would overflow the millisecond clock throws std::overflow_error (documented on ExpiryDuration; needs 2^63 ms of uptime), and a request whose construction throws, e.g. for lack of an old enough ledger secret, now keeps its requested lifetime instead of being dropped at the next tick (both from the first commit). An LRU entry without a matching request now throws std::logic_error rather than std::out_of_range (this commit).

Opt-in microbenchmark (historical_queries_test --test-case='StateCache tick scaling benchmark' --no-skip, trusted wrappers without payloads, Clang 21 RelWithDebInfo, this host). Constant active set of 8 pending fetches plus one renewal and one retargeting get per tick, against retained state:

Retained requests / store slots Tick (us) Tick + mutations (us) Pending visited Released checked
0 / 0 0.40 0.89 9 1
7,300 / 21,900 0.47 2.07 9 1
14,600 / 43,800 0.47 2.23 9 1
29,200 / 87,600 0.59 2.65 9 1

At the previous PR head, one pending fetch or one renewal per tick at 7,300 / 21,900 cost ~3.8-4.3 ms per tick. At 7,300 retained: pending fetches scale at ~13 ns each (1,000 pending: 13 us), due expiries at ~2.5 us each (1,000 due requests of 3 stores: 2.5 ms, counted as 1,000 expired / 3,000 released), and the unchanged handle_ledger_entry path costs ~0.3 ms per fetched entry (3.8 ms at 29,200 retained). This microbenchmark measures the maintenance code only. Its active set is an assumption: the number of fetches actually pending per tick under load, dispatch CPU, throughput, latency and RSS are unmeasured at this head, and no new load experiment was run. Anyone instrumenting tick cost should count the entries actually visited (the last_tick_work counters) rather than container sizes.

Safety and compatibility

No API, ledger/snapshot format, receipt verification or consensus change; tick() still runs entirely under requests_lock. Client-controlled handles, ranges and expiry durations from application queries continue to reach request and index mutations through the existing get_state* entry points; their authentication, admission and validation (including the existing range and expiry checks) are unchanged, and no new unauthenticated entry point is added. The new indexes hold weak pointers and seqnos only, so wrapper lifetime, shared stores between handles, and returned StatePtrs (which copy payload pointers, not the wrapper) are unchanged. Correctness relies on two invariants, checked by the tests: every StoreDetails creation site calls track, and every strong-owner drop site calls release. A stale expiry_order entry is logged and skipped so the expiry loop always progresses.

New regression tests assert operation counts rather than timing: shared stores outliving their first owner, retargeting releasing only abandoned stores, a seqno re-requested before cleanup keeping its new store, tick work bounded by pending work with 20 retained requests, and a seeded random sequence of gets, drops, deliveries, no-entry replies, soft-limit changes and ticks with the index/slot/expiry invariants checked after every operation. The earlier tests cover exact deadline boundaries, default/zero/negative lifetimes, drop/expiry/LRU eviction while a returned state is alive, resuming fetches from idle, late no-entry replies, secret-fetch ownership with zero requests, and overflow.

Merged with main (#8527), which makes a no-entry reply drop only the requests interested in that seqno: delete_all_interested_requests now combines is_interested_in with this PR's erase_request, and the released secret-fetch handle goes through release_next_secret_fetch_handle so its slot is marked for cleanup. Pre-existing behaviour left unchanged: the first supporting-secret fetch is issued both immediately and at the next tick. The randomised test checks index consistency after no-entry replies without asserting which requests they remove.

Validation at HEAD (after the merge with main): historical_queries_test 33/33 including the two no-entry tests from #8527 (plus the opt-in benchmark), historical_query_cache_test e2e passed, scripts/ci-checks.sh clean. Not covered: long LTS compatibility (nightly only).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set a request's deadline and the expiry hint once, at lookup, instead
of giving new requests a temporary zero lifetime and recording the
previous hint to restore later. Recompute the hint into a local during
the expiry pass so a throw mid-pass cannot leave live requests without
a hint, and drop the now redundant reset when no requests remain.

Remove the redundant store sweep requests from handle_ledger_entry and
handle_no_entry_range: both only run while a fetch is pending, which
already keeps the sweep armed, and any request removal goes through
lru_evict which requests a sweep itself.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve the test insertion conflict in src/node/test/historical_queries.cpp
by keeping both the maintenance regression tests and the new periodic
tick test from #8445.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, any pending fetch, renewal or request change made tick()
walk every retained request (expiry) and every store slot (weak lock,
fetch retry), so busy ticks grew linearly with retained state.

The cache now keeps the bookkeeping needed to visit only entries with
work to do: a seqno-ordered weak index of stores still being fetched
(preserving range coalescing and the first-tick/1 s retry arithmetic),
the set of seqnos whose strong owner may have been released since the
previous tick (checked and cleared at the next tick, so weak cleanup
keeps its next-tick timing), and a deadline-ordered set of requests so
the expiry pass pops only due entries. Every StoreDetails creation site
tracks the wrapper and every strong-owner drop site records a release,
including supporting-signature stores and the secret-fetch handle.

Tick cost is O(1) when nothing is pending, due or released, and
otherwise proportional to that work rather than to the number of
retained requests or stores. Per-tick work counters are exposed for
tests and diagnostics; new regressions assert operation counts under
shared stores, retargeting, seqno reuse, random operation sequences,
and an opt-in benchmark scales retained state against a constant
active set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@achamayou Amaury Chamayou (achamayou) changed the title Skip quiescent historical cache maintenance scans Make historical cache maintenance proportional to pending work Oct 8, 2026
Comment thread CHANGELOG.md Outdated
State the user-visible effect rather than the maintenance design, as
requested in review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Description

Comparing 4 available runs from this branch (#8525) against the trend of the last 30 main runs.

Each chart plots every benchmark as an axis, with values normalized so 100 is the EWMA baseline of recent main runs, using a 7-run half-life. The 4 orange branch lines run from the oldest (faintest) to the latest (darkest and thickest); the darker blue band is the main baseline +/- 1 std dev and the lighter blue band around it is +/- 2 std dev.

Axis labels show the latest branch value and its difference from the main EWMA baseline, where 0% is on the baseline. They are coloured green where the latest run improves on the baseline, red where it regresses, and grey where the difference is within one std dev of the baseline (within noise). Higher is better for throughput and rate, lower for latency and memory.

A benchmark which does not exist on main yet has no baseline of its own, so its earliest available run from this branch is used as its reference and its band is measured across this branch's runs. Its axis is normalized, scaled and coloured like any other, but the comparison is against this branch rather than against main.

Throughput (tx/s)

---
config:
  radar:
    width: 620
    height: 620
    marginTop: 90
    marginRight: 220
    marginBottom: 60
    marginLeft: 220
    axisLabelFactor: 1.12
    curveTension: 0.08
  theme: base
  themeCSS: |
    .radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
    .radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.30!important}
    .radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.40!important}
    .radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.50!important}
    .radarCurve-7{stroke-width:1.75px!important;stroke-opacity:1.00!important}
    .radarAxisLabel:nth-of-type(1){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(2){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(3){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(4){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(5){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(6){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(7){fill:#808A94!important}
  themeVariables:
    cScale0: "#62B5E5"
    cScale1: "#62B5E5"
    cScale2: "#62B5E5"
    cScale3: "#62B5E5"
    cScale4: "#F97316"
    cScale5: "#F97316"
    cScale6: "#F97316"
    cScale7: "#F97316"
    radar:
      axisColor: "#9CA3AF"
      graticuleColor: "#E5E7EB"
      graticuleOpacity: 0
      axisStrokeWidth: 1
      curveOpacity: 0
---
radar-beta
  axis b0["Basic Blocking 100ms: 3,103 tx/s ▬ 0%"]
  axis b1["Basic Blocking 20ms: 15,300 tx/s ▬ 0%"]
  axis b2["Basic Blocking 2ms: 50,493 tx/s ▲ 4%"]
  axis b3["Basic JS: 16,244 tx/s ▬ +2%"]
  axis b4["Historical Queries: 990,156 tx/s ▲ 7%"]
  axis b5["L…g Certificate Blocking: 28,698 tx/s ▬ 0%"]
  axis b6["Logging JWT Blocking: 15,329 tx/s ▬ 0%"]
  curve stddev2_high["main EWMA + 2 std dev"]{100.38, 100.28, 108.37, 104.40, 110.81, 100.60, 100.30}
  curve stddev1_high["main EWMA + 1 std dev"]{100.19, 100.14, 104.19, 102.20, 105.41, 100.30, 100.15}
  curve stddev1_low["main EWMA - 1 std dev"]{99.81, 99.86, 95.81, 97.80, 94.59, 99.70, 99.85}
  curve stddev2_low["main EWMA - 2 std dev"]{99.62, 99.72, 91.63, 95.60, 89.19, 99.40, 99.70}
  curve branch_0["#8525 (3 runs earlier)"]{100.20, 100.13, 104.69, 101.00, 101.28, 100.40, 99.90}
  curve branch_1["#8525 (2 runs earlier)"]{100.26, 99.96, 102.43, 100.69, 101.58, 99.92, 100.03}
  curve branch_2["#8525 (1 run earlier)"]{100.21, 100.07, 102.64, 98.65, 102.26, 99.70, 100.10}
  curve branch_3["#8525"]{100.31, 99.81, 104.36, 101.88, 107.12, 99.95, 100.19}
  graticule polygon
  max 119
  min 81
  ticks 0
  showLegend false
Loading

Latency (ms)

---
config:
  radar:
    width: 620
    height: 620
    marginTop: 90
    marginRight: 220
    marginBottom: 60
    marginLeft: 220
    axisLabelFactor: 1.12
    curveTension: 0.08
  theme: base
  themeCSS: |
    .radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
    .radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.30!important}
    .radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.40!important}
    .radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.50!important}
    .radarCurve-7{stroke-width:1.75px!important;stroke-opacity:1.00!important}
    .radarAxisLabel:nth-of-type(1){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(2){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(3){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(4){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(5){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(6){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(7){fill:#808A94!important}
  themeVariables:
    cScale0: "#62B5E5"
    cScale1: "#62B5E5"
    cScale2: "#62B5E5"
    cScale3: "#62B5E5"
    cScale4: "#F97316"
    cScale5: "#F97316"
    cScale6: "#F97316"
    cScale7: "#F97316"
    radar:
      axisColor: "#9CA3AF"
      graticuleColor: "#E5E7EB"
      graticuleOpacity: 0
      axisStrokeWidth: 1
      curveOpacity: 0
---
radar-beta
  axis b0["Basic Blocking 100ms: 98 ms ▬ 0%"]
  axis b1["Basic Blocking 20ms: 19 ms ▬ 0%"]
  axis b2["Basic Blocking 2ms: 5 ms ▬ -5%"]
  axis b3["Basic JS: 19 ms ▬ -1%"]
  axis b4["Historical Queries: 31 ms ▬ -6%"]
  axis b5["Logging Certificate Blocking: 18 ms ▼ 3%"]
  axis b6["Logging JWT Blocking: 19 ms ▬ 0%"]
  curve stddev2_high["main EWMA + 2 std dev"]{100.69, 100.00, 118.67, 103.89, 111.67, 104.01, 100.00}
  curve stddev1_high["main EWMA + 1 std dev"]{100.35, 100.00, 109.34, 101.95, 105.84, 102.00, 100.00}
  curve stddev1_low["main EWMA - 1 std dev"]{99.65, 100.00, 90.66, 98.05, 94.16, 98.00, 100.00}
  curve stddev2_low["main EWMA - 2 std dev"]{99.31, 100.00, 81.33, 96.11, 88.33, 95.99, 100.00}
  curve branch_0["#8525 (3 runs earlier)"]{99.77, 100.00, 95.30, 99.20, 100.26, 102.10, 100.00}
  curve branch_1["#8525 (2 runs earlier)"]{99.77, 100.00, 95.30, 99.20, 94.18, 96.72, 100.00}
  curve branch_2["#8525 (1 run earlier)"]{99.77, 100.00, 95.30, 99.20, 100.26, 96.72, 100.00}
  curve branch_3["#8525"]{99.77, 100.00, 95.30, 99.20, 94.18, 96.72, 100.00}
  graticule polygon
  max 132
  min 68
  ticks 0
  showLegend false
Loading

Memory (bytes)

---
config:
  radar:
    width: 620
    height: 620
    marginTop: 90
    marginRight: 220
    marginBottom: 60
    marginLeft: 220
    axisLabelFactor: 1.12
    curveTension: 0.08
  theme: base
  themeCSS: |
    .radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
    .radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.30!important}
    .radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.40!important}
    .radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.50!important}
    .radarCurve-7{stroke-width:1.75px!important;stroke-opacity:1.00!important}
    .radarAxisLabel:nth-of-type(1){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(2){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(3){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(4){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(5){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(6){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(7){fill:#2DA44E!important}
  themeVariables:
    cScale0: "#62B5E5"
    cScale1: "#62B5E5"
    cScale2: "#62B5E5"
    cScale3: "#62B5E5"
    cScale4: "#F97316"
    cScale5: "#F97316"
    cScale6: "#F97316"
    cScale7: "#F97316"
    radar:
      axisColor: "#9CA3AF"
      graticuleColor: "#E5E7EB"
      graticuleOpacity: 0
      axisStrokeWidth: 1
      curveOpacity: 0
---
radar-beta
  axis b0["Basic Blocking 100ms: 56 MiB ▼ 30%"]
  axis b1["Basic Blocking 20ms: 56.3 MiB ▼ 31%"]
  axis b2["Basic Blocking 2ms: 59.1 MiB ▼ 30%"]
  axis b3["Basic JS: 65.4 MiB ▼ 26%"]
  axis b4["Historical Queries: 106 MiB ▼ 19%"]
  axis b5["Logging Certificate Blocking: 82.1 MiB ▼ 23%"]
  axis b6["Logging JWT Blocking: 56.9 MiB ▼ 28%"]
  curve stddev2_high["main EWMA + 2 std dev"]{124.64, 124.29, 123.25, 121.31, 115.47, 118.45, 124.83}
  curve stddev1_high["main EWMA + 1 std dev"]{112.32, 112.15, 111.62, 110.66, 107.73, 109.23, 112.42}
  curve stddev1_low["main EWMA - 1 std dev"]{87.68, 87.85, 88.38, 89.34, 92.27, 90.77, 87.58}
  curve stddev2_low["main EWMA - 2 std dev"]{75.36, 75.71, 76.75, 78.69, 84.53, 81.55, 75.17}
  curve branch_0["#8525 (3 runs earlier)"]{108.90, 110.30, 109.50, 110.68, 106.42, 105.58, 110.35}
  curve branch_1["#8525 (2 runs earlier)"]{68.65, 71.10, 73.96, 71.71, 80.80, 76.33, 70.28}
  curve branch_2["#8525 (1 run earlier)"]{69.92, 69.38, 69.65, 73.66, 81.47, 76.66, 71.08}
  curve branch_3["#8525"]{69.69, 69.30, 70.25, 73.53, 81.46, 76.82, 71.65}
  graticule polygon
  max 145
  min 48
  ticks 0
  showLegend false
Loading

Rate (ops/s)

---
config:
  radar:
    width: 620
    height: 620
    marginTop: 90
    marginRight: 220
    marginBottom: 60
    marginLeft: 220
    axisLabelFactor: 1.12
    curveTension: 0.08
  theme: base
  themeCSS: |
    .radarCurve-0{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-1{fill:color-mix(in srgb, #62B5E5 40%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-2{fill:color-mix(in srgb, #62B5E5 13%, var(--color-canvas-default,var(--bgColor-default,#fff)))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarCurve-3{fill:var(--color-canvas-default,var(--bgColor-default,#fff))!important;fill-opacity:1!important;stroke:none!important;stroke-width:0!important}
    .radarAxisLabel,.radarTitle{fill:var(--color-fg-default,var(--fgColor-default,#111827))!important;color:var(--color-fg-default,var(--fgColor-default,#111827))!important}
    .radarCurve-4{stroke-width:1.5px!important;stroke-opacity:0.30!important}
    .radarCurve-5{stroke-width:1.5px!important;stroke-opacity:0.40!important}
    .radarCurve-6{stroke-width:1.5px!important;stroke-opacity:0.50!important}
    .radarCurve-7{stroke-width:1.75px!important;stroke-opacity:1.00!important}
    .radarAxisLabel:nth-of-type(1){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(2){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(3){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(4){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(5){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(6){fill:#808A94!important}
    .radarAxisLabel:nth-of-type(7){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(8){fill:#2DA44E!important}
    .radarAxisLabel:nth-of-type(9){fill:#808A94!important}
  themeVariables:
    cScale0: "#62B5E5"
    cScale1: "#62B5E5"
    cScale2: "#62B5E5"
    cScale3: "#62B5E5"
    cScale4: "#F97316"
    cScale5: "#F97316"
    cScale6: "#F97316"
    cScale7: "#F97316"
    radar:
      axisColor: "#9CA3AF"
      graticuleColor: "#E5E7EB"
      graticuleOpacity: 0
      axisStrokeWidth: 1
      curveOpacity: 0
---
radar-beta
  axis b0["CCF c…n c…t lifecycle: 21,375 ops/s ▬ +2%"]
  axis b1["CCF fresh JS invocation: 19,851 ops/s ▲ 3%"]
  axis b2["CHAMP get: 63,764,867 ops/s ▬ +1%"]
  axis b3["CHAMP put: 7,994,192 ops/s ▬ -1%"]
  axis b4["KV deserialisation: 2,735,230 ops/s ▬ +1%"]
  axis b5["KV serialisation: 2,453,386 ops/s ▬ +2%"]
  axis b6["KV s…t deserialisation: 6,324 ops/s ▲ 4%"]
  axis b7["KV snapshot serialisation: 5,056 ops/s ▲ 7%"]
  axis b8["Q…S s…d c…t lifecycle: 26,365 ops/s ▬ +3%"]
  curve stddev2_high["main EWMA + 2 std dev"]{106.02, 105.83, 106.07, 105.36, 106.32, 105.75, 105.97, 105.92, 105.84}
  curve stddev1_high["main EWMA + 1 std dev"]{103.01, 102.91, 103.03, 102.68, 103.16, 102.88, 102.99, 102.96, 102.92}
  curve stddev1_low["main EWMA - 1 std dev"]{96.99, 97.09, 96.97, 97.32, 96.84, 97.12, 97.01, 97.04, 97.08}
  curve stddev2_low["main EWMA - 2 std dev"]{93.98, 94.17, 93.93, 94.64, 93.68, 94.25, 94.03, 94.08, 94.16}
  curve branch_0["#8525 (3 runs earlier)"]{103.01, 102.75, 100.82, 99.77, 99.22, 103.29, 105.14, 101.88, 102.38}
  curve branch_1["#8525 (2 runs earlier)"]{102.51, 101.88, 101.26, 101.61, 100.30, 102.02, 99.59, 103.20, 101.42}
  curve branch_2["#8525 (1 run earlier)"]{100.01, 101.58, 111.58, 101.42, 100.57, 101.27, 102.12, 102.65, 101.12}
  curve branch_3["#8525"]{102.03, 103.26, 101.04, 99.29, 100.55, 101.77, 103.97, 107.16, 102.58}
  graticule polygon
  max 118
  min 87
  ticks 0
  showLegend false
Loading

@achamayou
Amaury Chamayou (achamayou) added this pull request to stack #8544 October 8, 2026 17:02
@achamayou
Amaury Chamayou (achamayou) marked this pull request as ready for review October 9, 2026 07:08
@achamayou
Amaury Chamayou (achamayou) requested a review from a team as a code owner October 9, 2026 07:08
Copilot AI balanced review requested due to automatic review settings October 9, 2026 07:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The bookkeeping transitions are consistently handled and thoroughly covered by focused invariant and regression tests.

0 open findings

What changed in this PR

Optimizes historical query cache maintenance so each tick scales with pending work rather than retained cache size.

Changes:

  • Adds indexed expiry, pending-fetch, and released-store bookkeeping.
  • Centralizes request cleanup and adds overflow-safe deadline arithmetic.
  • Adds extensive invariant, regression, and scaling tests.

Custom instructions used: .github/copilot-instructions.md, .github/instructions/reviewing.instructions.md, .github/instructions/changelog.instructions.md, and the testing/formatting skills.

File Description
src/​node/​historical_queries.h Implements proportional cache maintenance.
src/​node/​test/​historical_queries.cpp Adds correctness, invariant, and benchmark coverage.
include/​ccf/​historical_queries_interface.h Documents deadline overflow behavior.
CHANGELOG.md Records the performance and overflow changes.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…ncake

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the UTC alias and native ISO timestamp parsing supported by the required Python version. Preserve timestamp semantics without disabling lint rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@achamayou
Amaury Chamayou (achamayou) merged commit 0db36a3 into main Oct 10, 2026
16 checks passed
@achamayou
Amaury Chamayou (achamayou) deleted the achamayou-upgraded-pancake branch October 10, 2026 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants