Skip to content

perf(kernel): load only dependency-reachable rows when rebuilding the alignment projection - #205

Merged
ahrav merged 2 commits into
mainfrom
perf/kr-08-alignment-reachable-load
Sep 4, 2026
Merged

ahrav merged 2 commits into
mainfrom
perf/kr-08-alignment-reachable-load

Conversation

@ahrav

@ahrav ahrav commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Every commit that inserts a decision rebuilds the alignment projection, and load_alignment_input loaded and hashed every decision and every observation in the store to do it: 48% of a 128-operation commit after the statement cache, and growing with the store. Only observations named by an implements dependency and the decisions reachable from those dependencies through supersession can produce rows, so the loader now reads the dependencies first, then the reachable decisions in frontier rounds over json_each, then the named observations. derive_alignment, the generation watermark, and the corrupt-row detection for referenced observations are unchanged; a store with no alignment dependency loads no decision at all.

Measured (marginal): commit/1 1.46 → 0.64 ms, commit/16 7.9 → 2.5 ms, commit/128 29.4 → 16.8 ms, replay 144 → 123 µs. Cumulative: commit/1 2.5×, /16 3.4×, /128 1.7×, replay 1.8×; ingest/finish unchanged; 4 improved, 0 regressed.

Stack (8/13): base perf/kr-07-positional-decided-row → head perf/kr-08-alignment-reachable-load.

Method. Numbers come from crates/mc-module/benches/kernel_routes.rs (added in the first PR of this stack): a baseline binary and a candidate binary run as separate processes in 10 ABBA blocks pinned to one core; each cell is median_baseline / median_candidate with a paired bootstrap 95% CI over the blocks. Baseline is the suite commit on stack/kernel-routes-03-routes before any optimization; ratios are therefore cumulative through this PR, and the marginal effect of this PR alone is called out. A/A control on the same host: all CIs include 1.0, half-widths about 1%. Host: AMD EPYC 9R14, SQLite 3.51.3 (bundled).

Guard. cargo test -p mc-module --features test-support --test kernel_routes (45), cargo test -p mc-kernel --features test-support (all suites), cargo clippy -p mc-module -p mc-kernel --all-targets -- -D warnings. No file under tests/ changed; the wire contract, receipts, and serving semantics are unchanged.

…ing the alignment projection

Every commit that inserts a decision rebuilds the projection, and the
rebuild loaded and hashed every decision and observation in the store,
which was half of a 128-operation commit and grew with the store. Only the
observations an implements dependency names and the decisions reachable
from it through supersession contribute rows, so those are the rows read.
@coderabbitai

coderabbitai Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 107 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Essentials

Run ID: 050e83db-11be-4eed-8778-f399169f48a7

📥 Commits

Reviewing files that changed from the base of the PR and between 4e102a0 and 65ab5d1.

📒 Files selected for processing (1)
  • crates/mc-kernel/src/slice/alignment.rs

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

Comment on lines 199 to +246
@@ -178,11 +210,12 @@ fn load_alignment_input(
AND (e.invalidated_commit_seq IS NULL OR ?1<e.invalidated_commit_seq)
)
FROM decisions d
WHERE created_commit_seq<=?1",
WHERE created_commit_seq<=?1
AND object_id IN (SELECT value FROM json_each(?2))",
)
.map_err(|_| KernelError::Io)?;
let rows = statement
.query_map([requested], |row| {
let loaded = statement
.query_map(rusqlite::params![requested, ids], |row| {
Ok((
row.get::<_, String>(1)?,
DecisionHistory {
@@ -194,11 +227,33 @@ fn load_alignment_input(
))
})
.map_err(|_| KernelError::Io)?
.collect::<rusqlite::Result<HashMap<_, _>>>()
.collect::<rusqlite::Result<Vec<_>>>()
.map_err(|_| KernelError::Io)?;
rows
};
let observations = {
let mut next = Vec::new();
for (object_id, history) in loaded {
if let Some(successor) = history.superseded_by.as_deref() {
if !decisions.contains_key(successor) && !frontier.iter().any(|id| id == successor)
{
next.push(successor.to_string());
}
}
decisions.insert(object_id, history);
}
next.sort_unstable();
next.dedup();
next.retain(|id| !decisions.contains_key(id));
frontier = next;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit / design consideration (not a correctness issue — I traced the frontier expansion against resolve_decision's traversal and it matches, including cycle handling and the invalidated_commit_seq<=?1 gating).

This walks the supersession chain with one round trip per level (prepare_cached + query_map per iteration of while !frontier.is_empty()). For a dependency whose decision has been revised many times, that's O(chain depth) sequential statement executions instead of the single query the old code used. Since SQLite is embedded this is cheap per call, but it's worth confirming the benchmark fixtures (commit/128 etc.) actually exercise a deep supersession chain rather than only wide/shallow dependency sets — otherwise this cost is invisible in the measured numbers but could show up on a long-lived store where a hot decision has been superseded dozens of times.

If that turns out to matter, a WITH RECURSIVE CTE over decisions could compute the reachable set in one query while still only touching dependency-reachable rows.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified, and the gap is wider than suspected: the bench suite doesn't just lack deep chains — kernel_routes.rs creates no implements dependencies and no supersessions at all (insert_decision_op/insert_observation_op emit neither, and rg supersede crates/mc-module/benches/ is empty). So the frontier loop runs zero iterations in every measured scenario; the reported wins come entirely from skipping the full-table load when the dependency set is empty or small.

Declining the WITH RECURSIVE rewrite in this PR, for two reasons:

  1. Cost shape. Each frontier round is one cached statement over json_each, batched across all chains — rounds = max chain depth, not total chain nodes. An in-process cached statement execution is ~µs, so even depth 50 costs tens of µs against the ~12.6 ms this PR shaves off commit/128. The old code's single query was O(store size); the new per-depth cost only loses to it on a pathological store (tiny table, very long chain).
  2. Methodology. The stack's ABBA baseline pins the bench suite at the first PR of the stack; adding a deep-chain fixture here would break cumulative-ratio comparability. And rewriting the query now would re-encode the invalidated_commit_seq<=?1 CASE gating and the evidence_meta EXISTS eligibility check inside a recursive CTE with no measurement showing it's needed.

Filed a follow-up (beads P2) to add a deep-chain + wide/shallow-control commit fixture after the stack lands, and to evaluate the recursive CTE if depth shows up in the numbers.

@claude

claude Bot commented Sep 4, 2026

Copy link
Copy Markdown

Review

Focused on crates/mc-kernel/src/slice/alignment.rs — the only changed file. This narrows load_alignment_input to only load decisions/observations reachable from implements dependencies instead of the full table scan.

Correctness: I traced the new frontier-expansion loop against resolve_decision's traversal (the consumer in derive_alignment). They agree:

  • The loop only follows superseded_by when invalidated_commit_seq<=requested (same CASE WHEN gating used in the SQL), matching resolve_decision's "only follow the chain past an invalidated decision" behavior.
  • object_id is UNIQUE in the decisions schema, so keying the HashMap by object_id is safe (no silent overwrite of distinct rows).
  • A genuine supersession cycle still gets fully loaded into the map (the loop terminates via the decisions.contains_key check, not by detecting the cycle itself) and resolve_decision's existing visited check still catches it and returns Conflict, so cycle-detection semantics are unchanged.
  • The dependencies.is_empty() short-circuit for observations matches the doc comment's claim that a store with no alignment dependency loads no decision at all.
  • All dynamic ID lists go through rusqlite::params! / json_each(?2) bind parameters rather than string interpolation, so no SQL-injection concern from building the JSON id lists.

Performance consideration (left as an inline comment): the supersession-chain walk now issues one query per chain level instead of one query total. For deep chains this trades the old single full-table load for several small round trips — likely still a net win given SQLite is in-process, but worth confirming the benchmarked scenarios include a deep chain, not just wide dependency sets, since that's the case this loop is most sensitive to.

No security issues found (read-only projection rebuild, transaction-scoped, no user-controlled SQL text). No test files changed, consistent with the PR description that this is a pure internal read-path optimization with unchanged wire/receipt semantics.

@ahrav
ahrav marked this pull request as ready for review September 4, 2026 20:12
Base automatically changed from perf/kr-07-positional-decided-row to main September 4, 2026 21:12
… search

The first frontier round holds every distinct decision an implements
dependency names, and each superseded row scanned the whole frontier to
decide whether its successor was already queued. The frontier is sorted and
deduplicated before each round, so a binary search answers the same question
without a cost that grows with the product of the two counts.
@ahrav
ahrav merged commit c5bc1fa into main Sep 4, 2026
20 of 22 checks passed
@ahrav
ahrav deleted the perf/kr-08-alignment-reachable-load branch September 4, 2026 21:31
@kilo-code-bot

kilo-code-bot Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • crates/mc-kernel/src/slice/alignment.rs

Reviewed by gemini-3.8-flash · Input: 126.8K · Output: 68.8K · Cached: 582.8K

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