Skip to content

Server P11b-iii: shared result records, one context digest, a per-activation read table and an indexed restore (G30, G31, G32, G34) - #168

Merged
sujitn merged 1 commit into
mainfrom
server-p11b-iii-per-record
Oct 30, 2026
Merged

sujitn merged 1 commit into
mainfrom
server-p11b-iii-per-record

Conversation

@sujitn

@sujitn sujitn commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

P11b-iii: the per-record costs of a Live tick (G30, G31, G32, G34). The step needed no new decisions.

P11a's timers put these at burst1/256 (one bond valued in 256):

Stage ms
Merge: a deep copy of every record (G30) 0.36
The results swap, most likely the drop of the previous copy (G30) 0.25
changed: a full valuation context for each of 256 pins (G31) 0.34

At burst512/256 (about 204 bonds valued per tick), calculate is 60% of the tick at 47 µs a bond. Each valued bond computed its context digest (context JSON and SHA-256, about 3 µs) about eight times (G32).

Change

  • G30. RuntimeCheckpoint.results and outbox hold Arc<PublishedValuation>. serde's rc feature is already on in the workspace, so stored JSON is unchanged. A Live merge copies 256 pointers, and a tick that values no bond keeps the results it has. runtime_results() and the update stream still hand out PublishedValuation, so the server is unchanged.
  • G31. changed reads the curves each pin's valuation reads from a table kept in the Live baseline. It is built through context at the first tick of an activation or a new frame, so a pin that context refuses is still refused. It is reused while the frame holds and the snapshot shares the baseline's reference section (SnapshotReferences::ptr_eq). Quality is still derived for each pin at every tick.
  • same_inputs. When two snapshots share one reference section, the reference compare is skipped, since it is the same section. A section copied to change it (make_mut) is compared record by record, as before.
  • G32. ValuationContext derives its cache key once, in new, from the same bytes as before: its own serialization, which carries no key. cache_key() returns &str. Equality and Debug read the spec alone. A deserialized context derives its key again, so a tampered SnapshotValuationResult.cache_key is still refused. The graph commit shares the valued result (Arc) instead of deep-cloning it.
  • G34. restore_results indexes the stored records by subject once (by_subject), so a restart is linear in the bonds. It refuses the same checkpoints: one record per pin, no duplicate subject, and as many records as pins.
  • RUNTIME.md describes the shared records and the read table.

Behaviour that changes

None. Ids, keys, stored bytes, refusals and their errors are unchanged.

Tests

  • a_context_key_is_the_digest_of_its_serialization: the key equals SHA-256 over the context's own serialization, the bytes it always hashed, and that serialization is the spec's alone. A context read back has the same key and is equal.
  • The restart test now checks that a stored checkpoint's results serialize as the plain records and deserialize equal.
  • an_instrument_reads_the_same_inputs_until_its_own_change now covers a shared section: it is the same, and a copy changed in place is not. The existing cases cover separately built but equal sections.
  • The existing tamper tests (results_account_for_each_measure_and_cannot_claim_another_context_or_generation, the restart context_key tamper) pass unchanged.

Numbers

Criterion medians, --profile ci, WSL. Two interleaved rounds against main (d90c5ef), swapping in its convex-engine and convex-ports sources and test support:

Bench main this PR
live_throughput/burst1/256 (one-quote tick) 2.09, 2.11 ms 1.39, 1.35 ms
live_throughput/burst512/256 24.96, 24.58 ms 23.52, 22.97 ms
live_tick 267, 261 µs 262, 249 µs
snapshot/value_uncached 83.1, 82.4 µs 72.8, 74.4 µs
snapshot/value_built 77.2, 77.5 µs 66.4, 69.7 µs
snapshot/value_cached 5.0, 5.0 µs 1.0, 1.0 µs
snapshot/verify 11.6, 11.2 µs 11.3, 11.7 µs
snapshot_assemble/256 (Captured/Replay resolve) 4.52, 4.48 ms 4.43, 4.44 ms
  • The one-quote tick at 256 bonds falls by 0.70–0.76 ms (34–36%). P11a's timers put the merge, the results swap and changed at 0.95 ms, so the rest of the tick holds about 0.2 ms of them. That is the 256 pointer copies, quality derived per pin, and the input compares.
  • burst512/256 falls by 1.4–1.6 ms (6%). The digests saved run inside the valuation jobs, which run concurrently, so the wall-clock gain is smaller than the work saved. The one digest left (in context) runs on the actor, as the request id did before.
  • The snapshot/value_* benches make their context once, outside the loop. A valuation there hashed it seven times on a miss and three on a hit, and now hashes it none. So value_cached falls from 5.0 to 1.0 µs, and value_uncached is 9–11 µs below main. P11b-ii's unexplained 4–6 µs value_uncached regression no longer shows. On the Live path a context is made for each valued bond, so eight digests become one.
  • verify, curve_build and snapshot_assemble do not touch these paths, and are unchanged.
  • live_tick completed in all four runs.

Not done here

  • The store's RuntimeCheckpoint::validate serializes every result subject, so a stored Captured or Replay commit is O(N) in the bonds. A Live tick stores only its reservation, which holds no results, so this is not on the Live path.
  • Comparing each required curve once per tick, not once per pin. The bench's bonds request no curve measures, so no curve is compared there.
  • Multi-instrument jobs (not in P11b).

Verification

WSL, --locked:

  • cargo fmt --check; cargo clippy --all-targets -- -D warnings for convex-ports, convex-engine, convex-server, convex-ext-redb, convex-ext-file; RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --all-features for convex-ports and convex-engine.
  • cargo test -p convex-ports: 43 passed.
  • cargo test -p convex-engine: 208 passed, 1 ignored (one new test).
  • cargo test -p convex-ext-redb -p convex-ext-file -p convex-server -- --test-threads=1: 353 passed.
  • tools/assurance/check.py, alone and with --base d90c5ef1: passed.

Summary by CodeRabbit

  • Performance

    • Unchanged valuation results are retained across runtime updates, and unchanged valuation inputs can be reused. This reduces unnecessary recalculation when only some instruments or market data change.
  • Reliability

    • Restored valuation results are checked for missing, duplicate, or mismatched records, helping ensure runtime state is reconstructed consistently after a restart.

…ivation read table and an indexed restore (G30, G31, G32, G34)

- RuntimeCheckpoint.results and outbox hold Arc<PublishedValuation>; stored JSON is
  unchanged. A Live merge copies pointers, and a tick that values none keeps its results.
- changed reads each pin's curves from a table built once per activation and frame,
  reused while the reference section is shared; same_inputs skips the record compare
  for a shared section.
- ValuationContext derives its cache key once, when it is made; equality and Debug
  read the spec. The graph commit shares the result instead of deep-cloning it.
- restore_results indexes records by subject once.
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3057bb8a-46be-40ab-abc6-2b266aa738b9
Commits

Reviewing files that changed from the base of the PR and between d90c5ef and eb78043.

Files selected for processing: 13
  • crates/convex-engine/RUNTIME.md
  • crates/convex-engine/src/cache.rs
  • crates/convex-engine/src/context.rs
  • crates/convex-engine/src/lib.rs
  • crates/convex-engine/src/runtime/actor.rs
  • crates/convex-engine/src/runtime/mod.rs
  • crates/convex-engine/src/runtime/worker.rs
  • crates/convex-engine/src/snapshot.rs
  • crates/convex-engine/tests/runtime.rs
  • crates/convex-engine/tests/snapshots.rs
  • crates/convex-ports/src/runtime.rs
  • crates/convex-ports/src/snapshot.rs
  • crates/convex-ports/tests/snapshot_contracts.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The runtime now stores published valuation records behind Arc and retains records for instruments that are not revalued. Valuation contexts compute cache keys during construction. Live input comparisons retain read curves and recognize shared reference sections.

Changes: Live valuation flow

Layer / File(s) Summary
Stored valuation cache keys
crates/convex-engine/src/context.rs, crates/convex-engine/src/cache.rs, crates/convex-engine/src/lib.rs, crates/convex-engine/src/snapshot.rs, crates/convex-engine/tests/snapshots.rs
ValuationContext stores its cache key at construction, and callers use the borrowed key directly. Snapshot-result validation and cache-key tests use the stored key.
Live input change detection
crates/convex-ports/src/snapshot.rs, crates/convex-ports/tests/snapshot_contracts.rs, crates/convex-engine/src/runtime/worker.rs, crates/convex-engine/src/snapshot.rs, crates/convex-engine/RUNTIME.md
SnapshotReferences::ptr_eq identifies shared reference sections, and same_inputs skips selected-reference comparisons when sections are shared. Baselines retain per-pin read curves for input checks. The Live tick documentation describes this behavior.
Shared valuation result lifecycle
crates/convex-ports/src/runtime.rs, crates/convex-engine/src/runtime/actor.rs, crates/convex-engine/src/runtime/mod.rs, crates/convex-engine/src/runtime/worker.rs, crates/convex-engine/tests/runtime.rs
Runtime results and checkpoint records use Arc<PublishedValuation>. Calculation retains unchanged records, publication clones records for updates, and restoration indexes records by instrument and version. Tests check result equality and checkpoint JSON round-tripping.

Priority: Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant LiveTick
  participant ActorCalculate
  participant WorkerCalculate
  participant WorkerChanged
  participant ValuationCache
  LiveTick->>ActorCalculate: Process tick
  ActorCalculate->>WorkerCalculate: Calculate selected valuations
  WorkerCalculate->>WorkerChanged: Compare baseline inputs
  WorkerCalculate->>ValuationCache: Look up using context cache key
  WorkerCalculate->>ActorCalculate: Return shared PublishedValuation records
Loading

Possibly related PRs

  • sujitn/convex#163: Defines per-instrument valuation inputs and quality, which the changed baseline comparison uses.
  • sujitn/convex#161: Introduces shared checkpoint result storage and Live inputs pinned across ticks, both extended by this change.
  • sujitn/convex#164: Adds changed-only Live valuation and baseline comparison, which this change extends with shared result records and retained read curves.
Merge Risk: Minimal · up to eb780

The change shares valuation records and reuses cached inputs. No material merge-blocking issue was established.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main changes: shared result records, one context digest, per-activation read tables, and indexed restore. It is specific and related to the pull request, although it is…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title clearly describes the main changes: shared result records, one context digest, per-activation read tables, and indexed restore. It is specific and related to the pull request, although it is longer than necessary.

Full details: Docstring Coverage

Explanation

Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 17 functions across 12 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sujitn
sujitn merged commit 276c4d3 into main Oct 30, 2026
16 checks passed
@sujitn
sujitn deleted the server-p11b-iii-per-record branch October 30, 2026 07:36
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.

1 participant