Skip to content

perf(sessions): bound projection queue-head scans per write window - #4793

Draft
ScriptedAlchemy wants to merge 9 commits into
masterfrom
fix/4756-bounded-projection-head-read
Draft

ScriptedAlchemy wants to merge 9 commits into
masterfrom
fix/4756-bounded-projection-head-read

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Scope

Partial remediation for the expensive session-history re-import in #4756. Refs #4756 (not Fixes): the reported multi-hour CPU saturation, cross-provider startup/restart recovery, and complete bounded-write acceptance criteria remain open.

The registered observation projection store previously executed the same MIN(observation_sequence) query for each queued item inside a transaction of up to 32 items. The projection queue deliberately retains permanently parked collision rows (next_retry_at_micros = i64::MAX). Thus every projected item could re-walk the same long parked prefix, adding avoidable CPU while the profile/project databases import history.

This patch reads one ordered, at-most-max candidate window under the same write transaction instead. It excludes permanently parked rows, stops at the first nonterminal row whose retry deadline has not arrived, respects the active rebuild fence, retains per-item projection/rollback logic and final has_more probe, and changes no schema or public API. The daemon's existing caller sets max <= 32; this removes repeated queue-head scans without increasing its transaction window.

Behavioral regression

New integration test batched_projection_resumes_after_restart_without_replaying_parked_or_deferred_heads seeds five durable observations, projects the first, inserts a real terminal-collision queue head, postpones sequence 4, and proves:

  • The first batch projects only sequences 2–3 and persists checkpoint 3.
  • A new profile runtime reopens the DB with the same checkpoint; it neither repeats projected rows nor jumps over the retry-deferred sequence 4.
  • Releasing the retry lets max=1 project exactly one observation, then the next batch finishes the fifth; terminal collisions do not report runnable backlog.
  • Repeated idle passes leave message count and checkpoint unchanged.

Follow-up: eliminate empty-queue writer transactions

Commit 7f8147b274c2 also skips the projection write transaction when the queue is truly empty, using a constant-work SELECT 1 ... LIMIT 1 reader probe. Active queues still enter the writer transaction, including terminal parked rows, preserving the ordered queue's behavior and avoiding another O(parked-prefix) readiness scan.

A behavioral regression holds a separate SQLite BEGIN IMMEDIATE writer reservation and requires an empty projection drain to finish without taking the writer lease; after rollback it persists a new observation and proves the next drain projects exactly one message. This is bounded idle write-transaction avoidance, not a demonstrated explanation for the reporter's 125–160 ms historical write transactions. Bazel compilation/tests and macOS end-to-end profile remain required.

Performance evidence and limits

Using SQLite's unique observation-sequence index in a synthetic 30,000-parked / 1,000-ready queue, an offline 32-item transaction simulation took approximately 59 ms with the per-item query versus 2.4 ms with the single-window query (~25× in this isolated query workload). This is illustrative, not a TraceDecay end-to-end benchmark or evidence the reporter had 30,000 parked heads.

No Cargo run. This execution environment has no Bazel binary or GitHub checkout, so Rust compile and tests remain unverified pending the repository's normal protected CI checks. Do not merge until Bazel tests, full applicable CI, and review succeed. In particular, this PR does not assert complete fix for #4756: Codex partial discovery position is still process-retained and does not persist its scan cursor across daemon restarts, and per-source admission/write amplification needs additional measured investigation.

No release/infra/credentials, branch-protection changes, or modifications to any peer PR.

Avoid repeating ordered scans over permanently parked collision rows
for every item in a 32-observation write transaction. Keep strict retry
fences, atomic rollback, bounded windows, and restart checkpoints.

Refs #4756
@changeset-bot

changeset-bot Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6f32ff2

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

Bound re-arm write batches to actually retryable rows instead of visiting
permanently parked collisions as empty batches on every fresh store mount.
Add behavioral coverage for interleaved terminal and deferred queue rows,
preserved retry history, and zero no-op batches.

Refs #4756

Copy link
Copy Markdown
Owner Author

Follow-up at d265e31 — Refs #4756, partial restart write-amplification reduction.

Root cause: the background store-mount retry re-arm paginated every queue row with a positive deadline, including terminal collision rows whose deadline is i64::MAX. The UPDATE intentionally left those terminal rows untouched, but each such page still incurred its own writer transaction and commit on every restart. The selection now includes only genuinely retryable deferred rows, so parked collisions neither count against the fixed re-arm batch size nor cause empty write batches.

The new behavioral regression seeds 12 persisted queue entries (six terminal, three deferred, three already due), asserts exactly three re-arms in two write batches of size two, verifies that terminal rows and error history remain unchanged, and checks that another fresh re-arm finds zero write batches. An independent SQLite execution of the same selection/update showed old = 5 batches / 3 updated rows, new = 2 batches / 3 updated rows, and repeat = 0 batches / 0 rows. This is SQL-level corroboration only, not a Bazel Rust test pass.

No checkout or Bazel executable is available in this execution environment, so the Rust regression remains uncompiled here; exact-head CI was queued at review time. This does not establish resolution of the reported 125–160 ms projection writes or four-hour CPU saturation; no macOS end-to-end restart profile is claimed. Keep this PR draft and #4756 open pending that evidence. No changes to #4776, release #4763, other owner branches, or CI policy.

Capture the last retryable deadline through the reader and stop after its
bounded writer window, rather than taking an empty writer transaction at
startup or after every completed re-arm. Reject nonpositive batch sizes.

Exercise registered writer transaction counters, durable daemon remount,
later retries, terminal preservation, and invalid limits.

Refs #4756
Use the queue retry deadline index for an inexpensive nonterminal
readiness probe. Parked collisions must not acquire a writer on idle
drains; new observations still project after such a probe.

Refs #4756

Copy link
Copy Markdown
Owner Author

Published 5466c909 as a narrow additional Refs #4756 improvement (not full issue acceptance).

Remaining hot path: after a projection becomes a terminal collision, its queue row remains parked at i64::MAX. The previous empty-queue fast path only checked whether any row existed, so every later terminal-only drain still opened and committed an otherwise idle writer transaction, reread the parked prefix, and could contend with foreground writes.

Fix: the pre-writer probe now checks for nonterminal work and uses a separately installed performance index on next_retry_at_micros. The actual write transaction remains authoritative when fresh work races the probe. The existing restart regression now holds an unrelated SQLite BEGIN IMMEDIATE writer while draining a queue containing only a persisted terminal collision: the drain must finish without acquiring that lease. After releasing the writer, the same reopened profile admits a sixth observation and must advance the durable checkpoint from 5 to 6, proving the skip is not cached.

Independent checks: SQLite WAL reproduction confirmed that a terminal-only probe returns no work while another connection holds a writer, the new covering index is selected by SQLite, and a subsequently inserted ready row is visible. Synthetic 30K-parked-row query-plan checks showed a covering-index probe rather than a full queue scan; these are not end-to-end TraceDecay CPU measurements.

Only observation_projection/rebuild.rs, observation_projection/schema.rs, and the existing session_suite/observation_projection/queue.rs regression changed. The commit is a non-force fast-forward from the independently checked old head 35da211f, with an exact expected-head lease. Hosted Bazel CI for this head is queued. No Bazel executable/checkout is present in this session; Rust compilation and nonvacuous tests are not yet verified. Do not merge pending that evidence and review.

This does not prove the reported 125–160 ms historical writes, multi-hour 1.7-core CPU usage, or durable Codex discovery continuation across daemon restarts are resolved. Leave #4756 open and this PR draft. The overlapping #4776 draft, decision-model work, and release #4763 were untouched.

Use indexed deadline readiness to avoid empty writer transactions while
all nonterminal rows await their retry clock. Preserve pending coverage,
strict sequence authority, and newly admitted work after the probe.

Exercise a persisted terminal + deferred queue across reopening with a
concurrent SQLite writer reservation and subsequent durable continuation.

Refs #4756

Copy link
Copy Markdown
Owner Author

Follow-up at 1c08ac0c — Refs #4756, partial bounded-write improvement.

Root cause: even after the terminal-only fast path, a queue containing no due observations but one or more deferred retries still entered a SQLite writer transaction on every projection poll. A terminal collision plus deferred nonterminal row is sufficient to reproduce the needless writer lease; repeated polls can contend with foreground session writes without advancing the checkpoint.

Fix: query the earliest nonterminal deadline through the PR's existing next_retry_at_micros index before acquiring the writer. No nonterminal rows returns an empty settled batch; all-deferred rows return an empty pending batch (has_more=true) without a writer; any due row still enters the unchanged transactional, sequence-ordered projection path. No new index, schema shape, or public API was added by this commit.

Falsifiable regression: reopen a real profile SQLite store containing a persisted terminal collision and a deferred observation; hold an unrelated BEGIN IMMEDIATE writer reservation and require two projection polls to finish without waiting, while still reporting pending coverage and leaving the durable checkpoint unchanged. Release the writer, make the retry due, and require exactly one projected message and checkpoint advancement. The old pre-writer SELECT 1 path acquires the writer and fails this test.

Independent check: an isolated SQLite WAL rehearsal confirmed the read-only earliest-deadline query works while another writer holds a reservation, uses the covering retry-deadline index, and sees a subsequently eligible row. This is SQL-level evidence, not Rust/Bazel or host CPU verification.

This is a two-file diff, published via a single-parent, exact expected-old-head, non-force fast-forward from 5466c909. Exact-head Bazel CI is queued; no Rust test execution can be claimed yet because Bazel is unavailable locally. The 4-hour CPU/125–160 ms write incident and durable cross-restart Codex discovery continuation remain unproven, so keep this PR draft and #4756 open. No changes to mixed #4776, Kev/decision-model work, release #4763, CI gates, or other owners' branches.

Probe the lowest nonterminal observation sequence, not the earliest retry
clock anywhere in the queue. A later due row must not acquire a writer
while an earlier deferred row fences projection. Keep the probe and batched
reads on a partial sequence index excluding permanently parked collisions.

Extend the durable reopen regression to prove a later due row cannot bypass
the deferred head under another SQLite writer reservation; then project
both observations in bounded one-item windows.

Refs #4756

Copy link
Copy Markdown
Owner Author

Published 6f32ff2f as a focused Refs #4756 follow-up, not full incident acceptance.

Root cause: the previous read-only projection probe used the minimum retry deadline over all nonterminal rows, not the first nonterminal observation sequence. If sequence 2 was deferred but sequence 3 was already due, each idle poll still took a SQLite writer lease and committed an empty projection window despite sequence 2 fencing the later row. Many terminal parked collisions also imposed repeated ordered-prefix work.

Fix: probe the lowest nonterminal sequence and compare its deadline. Use an indexed partial active-sequence lookup excluding permanent i64::MAX collisions both for the probe and for the bounded transactional queue window. When no head is ready, report pending coverage without a writer; when it becomes ready, keep the existing sequence-ordered transactional authority.

Regression: extended the real profile-store restart test with a third, already-due observation behind an earlier deferred one. Two polls while an unrelated connection holds BEGIN IMMEDIATE must complete without acquiring the writer. After releasing the reservation and rearming the deferred head, one-item windows must advance the durable checkpoint from 1→2→3 without jumping forward or duplicating message rows. This would fail on the preceding 1c08ac0c head.

An independent SQLite rehearsal with 30,000 parked rows confirmed the old earliest-deadline probe returned the later due row, the sequence-head probe returned the deferred row, and EXPLAIN QUERY PLAN selected the covering partial sequence index. The synthetic query-only timing is not an end-to-end CPU or writer-latency result.

This single-parent commit was applied by exact expected-old-head, non-force fast-forward, changing only observation_projection/rebuild.rs, observation_projection/schema.rs, and session_suite/observation_projection/queue.rs. Exact-head hosted CI is queued; Bazel is not installed in this runtime, so no nonvacuous Rust test pass or macOS restart profile can yet be claimed. #4756 and this draft remain open. Mixed draft #4776, other owners' PRs, Kev/decision-model work, release #4763, and CI infrastructure were untouched.

This branch has not been deployed

No deployments
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