Skip to content

docs(code-index): serving-current latency after seat keep - #1567

Closed
ScriptedAlchemy wants to merge 4 commits into
masterfrom
cursor/graph-rebuild-current-latency-fb0d
Closed

ScriptedAlchemy wants to merge 4 commits into
masterfrom
cursor/graph-rebuild-current-latency-fb0d

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

Investigation only. Draft. Do not merge. Does not change the 90s receipt timeout.

Stacks on #1562 (b80dd58). The new commits are the note in docs/code-index-serving-current-latency.md.

Local receipt: after the seat-candidate keep, the serving seat matches and search can hit inside ~90s, but wait_for_current_generation still hits RECEIPT_TIMEOUT. Timeout status has seated_generation_age_seconds ≈ 89. A mid-run sample puts the test thread in that helper at graph_rebuild_status_test.rs:174 (the timeout(...).await around the loop at 148-172), inside status/tool MCP calls. The prior fail did ~16k status polls. Search is not that park: it runs only after status already looks current (153-160).

Age is seal time (status.rs:293-296), so the replacement sealed about a second into the wait. Estimates are structural, not a profiled re-run.

Wait helper vs production current

The helper (graph_rebuild_status_test.rs:140-179) matches production current (status.rs:451-489): coverage complete, staleness fresh, graph Ready, seat match (registry.rs:1205-1231). Search only needs the seated exact/lexical owners. Do not treat seat match plus hits as current, and do not raise RECEIPT_TIMEOUT (graph_rebuild_status_test.rs:26). Do not add a sleep to thin the polls.

Status poll hits capture and project

tracedecay_status always snapshots the generation census (crates/tracedecay/src/mcp/tools/handlers/dispatch_groups.rs:533-541, read at status.rs:257) even though this test already disables branch diagnostics, storage health, session ingest, and staleness (graph_rebuild_status_test.rs:103-109). The census reader is ProjectCodeGraphServingAuthorityV1::project (project_reads.rs:86-139).

project tries the ready-decoded seat first (serving_reads.rs:965-988). That calls ready_without_stat (serving_reads.rs:928) → GitMetadataFingerprintV1::capture (reconcile.rs:540-561, identity.rs:167-176) and, on a miss, note_wake_if_idle (serving_reads.rs:924-930). The next arm, retained_text_owner_freshness_for_scope, captures again (serving_reads.rs:1291-1308). Only then does it use the O(1) seat (serving_reads.rs:1342-1364), which neither captures nor wakes.

capture is a few git-metadata stats (identity.rs:167-176). ~16k of them are about 1–5s of filesystem, not the 89s. The activation coupling is the wake. note_wake_if_idle coalesces (registry.rs:2133-2151), but once a pass drains the arrival the next poll posts another. The proof expires at 30s (code_index_scheduler.rs:36, checked at reconcile.rs:562). After that, an unchanged seat still fails ready_without_stat, and the census keeps requeueing the worker into ReconcilePassGuard (mount.rs:539). Freshness stays off fresh while search can already hit. The freshness ladder the helper reads does not call capture (serving_reads.rs:324-333).

Post-seal serialization

Published pass, one worker:

  1. Await text until query owners are ready (mount.rs:1109-1141, stop at registry.rs:2330-2332). Clone pages are not in that await.
  2. That same advance starts the clone successor (serving.rs:3204-3217 and 2650-2658) after dropping the text reservation (serving.rs:3188). Graph then runs under the 128 MiB clone reservation. Full overlap with the text reservation is the RSS failure at mount.rs:1052-1057.
  3. Sealed decode is already off the scheduler mutex (mount.rs:1463-1490).
  4. activate is awaited before the swap (mount.rs:1601-1706, swap at 1818). Retryable failure keeps the candidate (1676-1681). Resident-memory BudgetExhausted is a refusal (reconcile.rs:360-367), so dashboard_generation_is_ready stays false.
  5. A second source proof sits between activation and the swap (mount.rs:1747-1782). The swap already binds pass_proves_latest (1862-1867).
  6. The seat-keep continuation (mount.rs:1735-1740) contradicts the post-swap idle rule (1978-1991) and re-enters the pass guard before source reconcile (539-541, drop at 1172-1174).
  7. The swap may load_active_shared under the scheduler mutex and the serving write lock (mount.rs:1854-1870, ignored_dependencies.rs:158-165).

This test compiles the 50ms activation floor (registry.rs:87-90), not the production 30s.

Proposals

#1562-eligible

  1. Do not hold reconcile_in_progress across source reconcile for a successor-only continuation (mount.rs:539-541 vs 1172-1174). About 2–15s of post-seat non-current. Not the whole 89s.
  2. Keep the clone wake (mount.rs:1735-1740), but yield once after the swap with the pass guard down. Fixing only this wake is not enough: the status census posts the same wake (serving_reads.rs:924-930 and 1296-1308).

Wait helper

  1. Stop status/census from using the query-admission ready probe. project (project_reads.rs:86-96) should take graph statistics from the seated generation (serving_reads.rs:1342-1364) and must not call ready_without_stat, GitMetadataFingerprintV1::capture, or note_wake_if_idle. Query admission can keep the wake. Up to ~30–60s of verifying after the 30s proof expiry (code_index_scheduler.rs:36), which is the activation stall the sample explains. The 1–5s of fingerprint syscalls are not the 89s by themselves.
  2. Gate the census the way the other status sections are gated (dispatch_groups.rs:533-541). This test already turns the other sections off (graph_rebuild_status_test.rs:103-109) and still pays project. The helper does not read graph_statistics. Combined with (3), the test thread stops parking in capture and project at graph_rebuild_status_test.rs:174.

Follow-up

  1. Do not begin_clone_successor in the owner-installing advance (serving.rs:3212-3217 and 2654-2658). If the dump shows a resident-memory refusal, this is never-current versus one publish. Otherwise a few seconds.
  2. Drop the pre-swap source reconcile (mount.rs:1754-1782). About 1–8s.
  3. Overlap only the in-memory manifest (builder.rs:32-70) with text advances. Keep claim_build (code_graph.rs:1244-1316) after serving.rs:3188. About 5–20s. Do not fully overlap without an RSS measurement.
  4. Compare the publication pointer in the swap; call load_active_shared only on a cache miss. About 0, or 5–20s on a miss.

Do not change

  • Do not raise the 90s timeout.
  • Do not sleep in the wait helper. A slower poll still runs capture and project, and project still wakes the worker.
  • Do not treat search hits plus a matching seat as current.
  • Do not shrink the production activation backoff.

Checklist

  • No secrets
  • No timeout or budget change
  • Not a behavior change; no journey re-run
Open in Web Open in Cursor 

cursoragent and others added 2 commits September 17, 2026 23:11
A retryable graph activation used to erase the prepared serving
candidate, and an unfinished clone-fingerprint successor withheld the
same seat after exact and lexical owners were ready. Keep the candidate
in both cases so search can move off the predecessor while graph retries.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4797015

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Cross-link

Agrees with keeping current strict and not raising the 90s timeout.

Also see:

Flag: if status-census wakes requeue reconcile for ~30–60s after proof expiry, land that cut even if #1568 removes successor from the receipt — otherwise search-can-hit / status-not-fresh can still strand the helper. No merge from this comment.

@github-actions

Copy link
Copy Markdown
Contributor

Performance Comparison master → cursor/graph-rebuild-current-latency-fb0d

Total Elapsed Time: 8.13s → 7.63s (-6.2%)
CPU Baseline: 80.10µs → 86.63µs (+8.2%)
Benchmark ID: index-bench-timing

timing - Execution duration of functions.

+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| Function                                   | Calls                      | Avg                              | P95                              | Total                           | % Total                      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| tracedecay-index-bench                     | 1 → 1 (+0.0%)              | 8.13s → 7.63s (-6.2%)            | 8.14s → 7.63s (-6.3%)            | 8.13s → 7.63s (-6.2%)           | 100.00% → 100.00% (+0.0%)    |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| domain.canonical.sha256                    | 72891 → 72891 (+0.0%)      | 38.59µs → 32.99µs (-14.5%)       | 108.35µs → 108.48µs (+0.1%)      | 2.81s → 2.40s (-14.6%)          | 34.58% → 31.53% (-8.8%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.workers.install                 | 30 → 30 (+0.0%)            | 93.49ms → 77.70ms (-16.9%)       | 272.63ms → 204.34ms (-25.0%) 🚀  | 2.80s → 2.33s (-16.8%)          | 34.49% → 30.57% (-11.4%)     |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| query.artifact.batch.sqlite                | 5 → 5 (+0.0%)              | 367.69ms → 367.88ms (+0.1%)      | 407.90ms → 409.47ms (+0.4%)      | 1.84s → 1.84s (+0.0%)           | 22.60% → 24.12% (+6.7%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| query.artifact.finalization.advance_wake   | 28 → 28 (+0.0%)            | 51.48ms → 51.73ms (+0.5%)        | 399.25ms → 411.30ms (+3.0%)      | 1.44s → 1.45s (+0.7%)           | 17.72% → 18.99% (+7.2%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| query.artifact.batch.postings              | 5 → 5 (+0.0%)              | 271.12ms → 272.30ms (+0.4%)      | 307.23ms → 310.38ms (+1.0%)      | 1.36s → 1.36s (+0.0%)           | 16.67% → 17.85% (+7.1%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.extract.parser_artifact         | 276 → 276 (+0.0%)          | 4.75ms → 4.19ms (-11.8%)         | 9.95ms → 7.02ms (-29.4%) 🚀      | 1.31s → 1.16s (-11.5%)          | 16.11% → 15.17% (-5.8%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.build.and_publish               | 2 → 2 (+0.0%)              | 617.40ms → 568.46ms (-7.9%)      | 1.15s → 1.06s (-7.8%)            | 1.23s → 1.14s (-7.3%)           | 15.18% → 14.91% (-1.8%)      |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| query.artifact.batch.parallel_prepare      | 5 → 5 (+0.0%)              | 231.95ms → 185.41ms (-20.1%) 🚀  | 272.63ms → 204.34ms (-25.0%) 🚀  | 1.16s → 927.07ms (-20.1%) 🚀    | 14.26% → 12.16% (-14.7%)     |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| domain.canonical.json_bytes                | 206681 → 206681 (+0.0%)    | 5.31µs → 4.22µs (-20.5%) 🚀      | 4.40µs → 4.44µs (+0.9%)          | 1.10s → 871.61ms (-20.8%) 🚀    | 13.50% → 11.43% (-15.3%)     |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.restore.file_admit              | 260 → 260 (+0.0%)          | 4.13ms → 3.11ms (-24.7%) 🚀      | 10.35ms → 5.05ms (-51.2%) 🚀     | 1.07s → 809.84ms (-24.3%) 🚀    | 13.19% → 10.62% (-19.5%)     |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| query.artifact.batch.postings.ngram_rows   | 5 → 5 (+0.0%)              | 154.91ms → 155.89ms (+0.6%)      | 174.98ms → 177.21ms (+1.3%)      | 774.53ms → 779.47ms (+0.6%)     | 9.52% → 10.22% (+7.4%)       |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.build.materialize_full          | 1 → 1 (+0.0%)              | 815.89ms → 751.01ms (-8.0%)      | 816.32ms → 751.30ms (-8.0%)      | 815.89ms → 751.01ms (-8.0%)     | 10.03% → 9.85% (-1.8%)       |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| code_index.collect.materialize_full        | 1 → 1 (+0.0%)              | 809.29ms → 744.10ms (-8.1%)      | 809.50ms → 744.49ms (-8.0%)      | 809.29ms → 744.10ms (-8.1%)     | 9.95% → 9.76% (-1.9%)        |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| 🆕 query.artifact.batch.postings.term_rows | 0 → 5 (+100.0%) ⚠️         | 0.00ns → 104.14ms (+100.0%) ⚠️   | 0.00ns → 119.08ms (+100.0%) ⚠️   | 0.00ns → 520.69ms (+100.0%) ⚠️  | 0.00% → 6.83% (+100.0%) ⚠️   |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+
| 🗑️ code_index.restore.file_decode          | 260 → 0 (-100.0%) 🚀       | 2.49ms → 0.00ns (-100.0%) 🚀     | 5.84ms → 0.00ns (-100.0%) 🚀     | 648.51ms → 0.00ns (-100.0%) 🚀  | 7.97% → 0.00% (-100.0%) 🚀   |
+--------------------------------------------+----------------------------+----------------------------------+----------------------------------+---------------------------------+------------------------------+

Generated with hotpath-rs

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Closing as stale.

Evidence from the triage: git diff --stat origin/master...origin/cursor/graph-rebuild-current-latency-fb0d = docs/code-index-serving-current-latency.md +83 PLUS the 3 files of b80dd58; doc-only once #1562's fix is attributed to #1562; merge-base 918 behind

Reopen with a rebase if the remaining value is wanted.

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.

2 participants