Skip to content

fix(sync-service): Fix keepalive acks advancing past unflushed small transactions#4731

Merged
alco merged 2 commits into
mainfrom
alco/flush-ack-bypasses-flush-tracker
Jul 22, 2026
Merged

fix(sync-service): Fix keepalive acks advancing past unflushed small transactions#4731
alco merged 2 commits into
mainfrom
alco/flush-ack-bypasses-flush-tracker

Conversation

@alco

@alco alco commented Jul 20, 2026

Copy link
Copy Markdown
Member

Keepalives could acknowledge WAL past a transaction whose storage flush hadn't been confirmed yet: flush_up_to_date? was only cleared while the decoder was mid-transaction, which never happens for a transaction that fits into a single commit-carrying fragment (fewer changes than max_batch_size, no Relation message). A crash in the window between such a transaction's commit and its async storage flush loses it permanently — the slot's confirmed_flush_lsn is already past it. It also means a stuck flush pipeline gets acked past instead of retaining WAL under small-transaction-only traffic.

Introduced by the operation batching refactor in #3483 (33fbc857f, first shipped in @core/sync-service@1.2.7): before it, the flag was cleared on every transaction's first protocol message; the refactor's buffering path skips the flag update, leaving dispatch-at-commit as the only touchpoint — when the decoder is already out of the transaction.

The fix makes the flag derived state: the client is up to speed exactly when the flush tracker has confirmed the last seen transaction, recomputed whenever either LSN moves. The first commit adds a regression test that fails on main; the second commit fixes it and adds a test for the confirmation-during-processing ordering that keeps no-shape transactions from stalling the keepalive-driven slot advance.

🤖 Generated with Claude Code

@alco alco added the claude label Jul 20, 2026
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.05%. Comparing base (7b8fecc) to head (a40ee2c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4731      +/-   ##
==========================================
+ Coverage   60.04%   60.05%   +0.01%     
==========================================
  Files         397      397              
  Lines       43766    43766              
  Branches    12588    12590       +2     
==========================================
+ Hits        26278    26283       +5     
+ Misses      17407    17401       -6     
- Partials       81       82       +1     
Flag Coverage Δ
packages/agents 72.64% <ø> (ø)
packages/agents-mcp 77.70% <ø> (ø)
packages/agents-mobile 80.67% <ø> (ø)
packages/agents-runtime 83.73% <ø> (-0.05%) ⬇️
packages/agents-server 75.67% <ø> (+0.20%) ⬆️
packages/agents-server-ui 8.32% <ø> (ø)
packages/electric-ax 51.06% <ø> (ø)
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 91.78% <ø> (-0.12%) ⬇️
packages/y-electric 56.05% <ø> (ø)
typescript 60.05% <ø> (+0.01%) ⬆️
unit-tests 60.05% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

alco added 2 commits July 21, 2026 00:10
Failing regression test for a durability hole: flush_up_to_date? is only
cleared when an event is applied while the decoder is mid-transaction, which
never happens for a transaction that fits into a single commit-carrying
fragment (fewer changes than max_batch_size, no Relation message). With the
flag stale-true, the keepalive handler advances flushed_wal to the
keepalive's wal_end, acknowledging WAL that no shape has durably stored — a
crash before the async storage flush then loses the transaction, and a stuck
flush pipeline gets acked past instead of retaining WAL. Regression from the
operation-batching refactor (33fbc85); the original implementation
(871d4e9) cleared the flag as soon as any transaction data started
accumulating.

The existing "flushes are not advancing" test misses this because its only
relevant transaction is the session's first write to the table: that txn
carries a Relation message, dispatched as a standalone mid-transaction
event, which clears the flag by accident.
The flag was only cleared when an event was applied while the
MessageConverter was still mid-transaction, which never happens for a
transaction decoded into a single commit-carrying fragment (fewer changes
than max_batch_size, no Relation message). With the flag stale-true, the
keepalive handler advanced flushed_wal to the keepalive's wal_end,
acknowledging WAL that no shape has durably stored — see the regression
test in the previous commit.

Track the flush tracker's confirmations in the new monotonic
last_confirmed_flush_lsn field and recompute the flag whenever either side
moves: the client is up to speed exactly when the tracker has confirmed
the last seen transaction. The recompute after acknowledge_transaction
matters for transactions that affect no shapes: their confirmation is
emitted during event processing and reaches the client before the reply
that updates last_seen_txn_lsn, so recomputing only on notification
arrival would leave the flag false and stall the keepalive-driven slot
advance. Covered by the new "confirmed during processing" test.
@alco
alco force-pushed the alco/flush-ack-bypasses-flush-tracker branch from 42985e8 to a40ee2c Compare July 20, 2026 22:35
@alco alco changed the title Keepalive acks bypass the flush tracker for small transactions Fix keepalive acks advancing past unflushed small transactions Jul 20, 2026
@alco alco changed the title Fix keepalive acks advancing past unflushed small transactions fix(sync-service): Fix keepalive acks advancing past unflushed small transactions Jul 20, 2026
@alco
alco marked this pull request as ready for review July 20, 2026 22:45

@magnetised magnetised left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice

@alco
alco merged commit bb69764 into main Jul 22, 2026
72 of 73 checks passed
@alco
alco deleted the alco/flush-ack-bypasses-flush-tracker branch July 22, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants