Skip to content

feat(module-postgres): configurable snapshot socket timeout - #739

Open
henriquekraemer wants to merge 2 commits into
powersync-ja:mainfrom
henriquekraemer:feat/snapshot-socket-timeout
Open

feat(module-postgres): configurable snapshot socket timeout#739
henriquekraemer wants to merge 2 commits into
powersync-ja:mainfrom
henriquekraemer:feat/snapshot-socket-timeout

Conversation

@henriquekraemer

Copy link
Copy Markdown
Contributor

Adds a snapshot_socket_timeout connection option (in seconds) for the idle timeout on snapshot connection sockets, defaulting to the current fixed 30 seconds. Same pattern as the replication_socket_timeout option proposed in #715, applied to the snapshot connection instead.

replication:
  connections:
    - type: postgresql
      # ...
      snapshot_socket_timeout: 90

Background

The snapshot socket timeout is an idle timeout: it fires when the snapshot loop stops consuming the source stream for 30 seconds. The main way that happens is storage backpressure, since a storage flush blocks the chunk loop. We hit this reliably while re-replicating a deployment where the new sync rules instance snapshots while the previous instance keeps streaming on the same storage: on wide tables, a flush under that combined load can take longer than 30s, and the idle timeout kills the source connection mid-snapshot. It shows up either as a socket timeout inside the chunk query or as postgres query failed on the next chunk, when the timeout fired between queries and destroyed the connection.

With resumable snapshots the table resumes from the last key, so no work is lost, but each kill costs the retry delay plus re-reading the current chunk. Snapshot concurrency (#731) makes the window easier to hit, but the exposure exists in the sequential path as well, since flushes have always run between chunk reads.

The default stays at 30 seconds, so behavior is unchanged unless the option is set.

Tests

Added config normalization tests (seconds to milliseconds, unset by default, invalid values ignored), mirroring the tests in #715.

Adds a snapshot_socket_timeout connection option (seconds) for the idle
timeout on snapshot connection sockets, defaulting to the previous fixed
30 seconds.

When the storage cannot keep up with the snapshot, a storage flush can
stall the snapshot loop for longer than the timeout, and the idle timeout
kills the source connection mid-snapshot. The snapshot resumes, but each
kill costs a retry delay plus re-reading the current chunk. This follows
the same pattern as the replication_socket_timeout option proposed in
powersync-ja#715, for the snapshot connection.
@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8d30fca

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 Minor
@powersync/service-schema Patch
@powersync/service-image Patch
@powersync/service-core Patch
@powersync/service-module-convex Patch
@powersync/service-module-core Patch
@powersync/service-module-mongodb-storage Patch
@powersync/service-module-mongodb Patch
@powersync/service-module-mssql Patch
@powersync/service-module-mysql Patch
@powersync/service-module-postgres-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

# Conflicts:
#	modules/module-postgres/src/types/types.ts
#	modules/module-postgres/test/src/config.test.ts
@henriquekraemer

henriquekraemer commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Small update from staging: we found a second way to hit this timeout that has nothing to do with storage backpressure. Running with initial snapshot filters (a Postgres port of #502) on a large sparse table without a supporting index, a single chunk query (WHERE <filter> ORDER BY id LIMIT 10000) can take minutes on the server before it returns the first row. The socket sits idle the whole time, so the fixed 30s timeout kills the connection mid snapshot and the table gets stuck retrying. On a 13.6M row table with ~2% selectivity our first chunk took 8m38s.

The real fix in that case was a partial index matching the filter predicate (2.6s afterwards), but the configurable timeout is what turned an endless retry loop into something we could see and diagnose. We're running with snapshot_socket_timeout: 90.

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