Skip to content

fix(module-postgres-storage): only resnapshot rows with missing TOAST values - #788

Open
henriquekraemer wants to merge 1 commit into
powersync-ja:mainfrom
henriquekraemer:fix/postgres-storage-resnapshot-toasted-rows
Open

fix(module-postgres-storage): only resnapshot rows with missing TOAST values#788
henriquekraemer wants to merge 1 commit into
powersync-ja:mainfrom
henriquekraemer:fix/postgres-storage-resnapshot-toasted-rows

Conversation

@henriquekraemer

Copy link
Copy Markdown
Contributor

Problem

PostgresBucketBatch.saveOperation() handles a streamed UPDATE for a row with no current_data entry by calling markRecordUnavailable() whenever storeCurrentData is true, which queues a targeted resnapshot of that row. It does this even when the row arrived complete from the WAL. The complete row is evaluated and written to current_data on the same pass (the isCompleteRow gate before evaluation, then the unconditional upsertCurrentData), so the resnapshot re-reads a row the service already has: a query on a separate snapshot connection, a keepalive, and a wait for the next consistent checkpoint before confirmed_flush_lsn moves again.

The MongoDB storage only resnapshots when the row is incomplete (!isCompleteRow(storeCurrentData, after) in MongoBucketBatch). That guard came with #655, which also touched PostgresBucketBatch but left this branch on the old condition. The same reasoning appears in the #655 review: a row that arrives complete needs no targeted resnapshot (#655 (comment)).

We hit this with a backfill that updated about a million rows on tables whose snapshot had not reached them yet. Every one of those rows queued a resnapshot; the queue drained in batches over close to an hour, and the slot's confirmed_flush_lsn stayed put until it did. The cost is paid once per row (the first UPDATE while the row is missing from storage), so it self-limits, but it repeats on every large backfill or onboarding.

Fix

Gate the resnapshot on isCompleteRow(storeCurrentData, after), matching the MongoDB storage. Rows with missing TOAST values still go through resnapshot as before; complete rows take the normal path and are stored directly. With storeCurrentData false the guard is never entered, same as today.

Tests

New test in the shared storage suite (register-data-storage-data-tests.ts), so it runs against both storages: an UPDATE with a complete row for a record not in storage must not call markRecordUnavailable, a following partial UPDATE on the same record must be filled from the stored copy, and a partial UPDATE for another missing record must be marked. It fails on Postgres storage v1 and v2 without the fix and passes with it; it passes unchanged on the MongoDB storage suite. The module-postgres-storage suite passes against real Postgres (174 tests). Changeset included (patch).

… values

saveOperation() marked every UPDATE without a current_data entry for
resnapshot, even when the row arrived complete from the WAL. The complete
row is evaluated and written to current_data on the same pass, so the
resnapshot re-read a row the service already had, on a separate snapshot
connection, followed by a keepalive and a wait for the next consistent
checkpoint. A backfill touching rows not yet snapshotted queued one
resnapshot per row (about a million rows on one deployment, close to an
hour before confirmed_flush_lsn moved again).

Gate the resnapshot on isCompleteRow() instead, matching the MongoDB
storage since powersync-ja#655. Rows with missing TOAST values still go through
resnapshot; complete rows take the normal path.
@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5f2df1d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@powersync/service-module-postgres-storage Patch
@powersync/service-schema Patch
@powersync/service-module-convex Patch
@powersync/service-module-mongodb Patch
@powersync/service-module-mssql Patch
@powersync/service-module-mysql Patch
@powersync/service-module-postgres Patch
@powersync/service-image Patch
@powersync/service-core Patch
@powersync/service-module-core Patch
@powersync/service-module-mongodb-storage Patch
test-client Patch

Not sure what this means? Click here to learn what changesets are.

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

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