Commit 1983bf1
fix(client): don't skip live changes after transaction ID wraparound (#4761)
Fixes subset snapshot filtering when PostgreSQL 32-bit transaction IDs
wrap across an xid8 epoch. Clients now keep live collections current
instead of discarding valid changes after wraparound, including on quiet
SSE streams that have not received another up-to-date boundary.
## Root Cause
Change messages carry 32-bit transaction IDs, while subset snapshot
metadata uses 64-bit epoch-aware IDs. Comparing them directly loses the
epoch and can classify a new wrapped transaction as visible in an old
snapshot.
Reconstructing the epoch also has a half-range constraint: the 32-bit
xid and its 64-bit reference must be within `2^31` transactions. A quiet
stream could retain a snapshot past that bound because its next change
may arrive before another `up-to-date` message.
## Approach
- Resolve every wire xid into the epoch nearest the snapshot `xmax`,
then use the latest resolved xid for snapshot eviction and duplicate
filtering.
- Advance snapshot retirement in message order from both
`global_last_seen_lsn` control messages and each change message `lsn`.
- Expand the model-based tests across xid8 epochs, wrap boundaries, and
multi-xid messages.
- Add regressions for wrapped xid eviction and for a quiet stream whose
first later change passes the snapshot WAL position.
This mirrors the xid reconstruction strategy already used by the sync
service in [#2320](#2320).
## Key Invariants
- An active snapshot never outlives the point where the stream WAL
position passes its `database_lsn`.
- Changes before that boundary still receive snapshot duplicate
filtering; later changes do not.
- Each wire xid is resolved before the latest contributing transaction
is chosen.
## Non-goals
- No sync-service or wire-protocol changes.
- No changes to subset snapshot semantics outside transaction ID
resolution and retirement.
## Trade-offs
Using `xmax` as the epoch reference keeps the fix local and matches
PostgreSQL wraparound arithmetic. Its half-range requirement is safe
because LSN-based retirement bounds each snapshot lifetime without
adding protocol state.
## Verification
```bash
pnpm --dir packages/typescript-client test --run
pnpm --dir packages/typescript-client typecheck
pnpm --dir packages/typescript-client stylecheck
```
The full TypeScript client unit suite passed (440 tests), along with
focused regression tests, type checking, linting, and formatting checks.
## Files changed
- `packages/typescript-client/src/client.ts` — retires snapshots from
control and change LSNs in stream order.
- `packages/typescript-client/src/snapshot-tracker.ts` — reconstructs
epoch-aware transaction IDs before filtering.
- `packages/typescript-client/test/pbt-micro.test.ts` — adds cross-epoch
property coverage and stream retirement regression.
- `packages/typescript-client/test/snapshot-tracker.test.ts` — adds a
wrapped xid retirement regression.
- `.changeset/tidy-xids-wrap.md` — records the client patch.
---
## Related
Related: [#2320](#2320)
_AI disclosure: Codex helped investigate, test, and prepare this
change._
---------
Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com>1 parent 8cabe9b commit 1983bf1
5 files changed
Lines changed: 349 additions & 34 deletions
File tree
- .changeset
- packages/typescript-client
- src
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1571 | 1571 | | |
1572 | 1572 | | |
1573 | 1573 | | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
1574 | 1581 | | |
1575 | 1582 | | |
| 1583 | + | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
| 1590 | + | |
| 1591 | + | |
| 1592 | + | |
| 1593 | + | |
1576 | 1594 | | |
1577 | 1595 | | |
1578 | 1596 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
94 | 119 | | |
95 | 120 | | |
96 | 121 | | |
| |||
99 | 124 | | |
100 | 125 | | |
101 | 126 | | |
102 | | - | |
103 | | - | |
104 | 127 | | |
105 | | - | |
| 128 | + | |
| 129 | + | |
106 | 130 | | |
107 | 131 | | |
108 | 132 | | |
109 | 133 | | |
110 | 134 | | |
111 | 135 | | |
112 | | - | |
113 | | - | |
114 | | - | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
115 | 141 | | |
116 | 142 | | |
117 | 143 | | |
| |||
0 commit comments