fix(sessions): fold the refresh begin into the first batch commit - #3080
devin-ai-integration[bot] wants to merge 14 commits into
Conversation
A streamed message committed four times inside temporal refresh: the begin's operation row, the first projection batch, the pending receipt, and activation. The begin now replays inside a rolled-back write transaction to produce the same recovery a durable begin would have left, the projector builds the first batch against it, and one commit writes the begin and the batch together. A replayed begin that no longer matches the projected batch commits alone and durable recovery resumes the operation next pass, the state a crash between the two commits already left behind. The shared cursor key is provisioned in its own transaction first so both replays read the same key, and the operation's created_at stays the plan's accepted_at so progress rows written between plan and commit stay ordered. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
A pending reset deletes the base rows the first batch would project from, so a batch projected before the begin commits can never match the replayed begin's post-reset state; the fold always fell back to BeganOnly and cost an extra pass. Plan now checks for a pending reset inside the first transaction and commits the begin when one exists, so durable recovery picks the operation up in the same pass and projects the post-reset base. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Correct — a pending reset deletes the base rows inside the begin's transaction, so a batch projected before that commit can never match the replayed begin's post-reset state; the fold deterministically fell back to Fixed in |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…gin-commit' into fix/pr-3080-ci
Summary
Fixes #2939
Motivation
Issue #2939 reports that a streamed message commits 9 times and writes 246 WAL frames for 193 pages. Earlier changes folded the capture span, drain convergence, and receipt into activation. Refresh still committed four times per message: the begin operation row, the first projection batch, the pending relation receipt, and activation. The begin and batch can share a commit without redesigning the write-ahead receipt protocol. This PR implements the state-machine change identified in #3017.
Changes
crates/tracedecay-session-temporal-store/src/refresh.rs:plan_session_refresh_begin_resultreplays the begin in a write transaction that is rolled back, then returns aSessionRefreshBeginPlanV1::Preparedrecovery.commit_session_refresh_begin_batch_resultreplays the same begin in the transaction that commits the changes. It persists the first batch only when the replayed binding still matches the batch. If the binding diverges or the batch is refused, the begin commits alone asBeganOnly. This leaves the same durable running operation that a crash between the two old commits would have left. The shared cursor key is provisioned in a separate transaction before planning because minting the key in a rolled-back transaction would desynchronize the two replays. After a key exists, the provision commit appends no WAL frames.SessionRefreshRecoveryV1now carriesaccepted_at, which keeps the operation'screated_atordered beforeprogress.recorded_at.crates/tracedecay-session-temporal-store/src/handle.rs,tracedecay-global-db, andtest_registered_impls.rs: addSessionTemporalWriteTxn::rollbackso that the plan transaction can be abandoned without committing.crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs:PreparedSessionRefreshcarries the planned recovery through the pass.apply_prepared_refresh_effectfolds the projection effect throughcommit_session_refresh_begin_batchand preserves Fail, Deferred, and error accounting from the durable paths.session_store_read_costboundary expectation,(4,3,4) → (4,3,3). Thetemporal_refreshsuite tail now checks the folded recovery pass.Test plan
bash scripts/require-exact-test.sh cargo test -p tracedecay-session-runtime --features test-helpers --test session_store_read_cost streamed_message_commits_once_per_durability_boundary -- --exact, 1 passed (red before: measured(4,3,4), now(4,3,3))cargo test -p tracedecay --features test-helpers --test session_suite session_runtime::temporal_refresh, 24 passedcargo test -p tracedecay-session-temporal-store --lib, 152 passedcargo test -p tracedecay-session-runtime --lib, 129 passedcargo clippy -p tracedecay-session-temporal-store|tracedecay-global-db|tracedecay-session-runtime|tracedecay --all-targets -- -D warnings, cleancargo fmt --all -- --check, cleanChecklist
.envfiles includedLink to Devin session: https://app.devin.ai/sessions/0bf7d9f457e7462784c8852594601226
Open in Devin Desktop: https://app.devin.ai/desktop/session/0bf7d9f457e7462784c8852594601226?variant=devin
Requested by: @ScriptedAlchemy