@rocicorp/zero 1.10.0-canary.0 → 1.10.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replicache/src/db/commit.js +1 -4
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.d.ts +1 -0
- package/out/replicache/src/db/read.d.ts.map +1 -1
- package/out/replicache/src/db/read.js +1 -4
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/write.d.ts +2 -1
- package/out/replicache/src/db/write.d.ts.map +1 -1
- package/out/replicache/src/db/write.js +4 -51
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts +1 -0
- package/out/replicache/src/persist/clients.d.ts.map +1 -1
- package/out/replicache/src/persist/clients.js +4 -31
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/process-env.d.ts +28 -0
- package/out/replicache/src/process-env.d.ts.map +1 -0
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js +2 -14
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/subscriptions.d.ts +1 -0
- package/out/replicache/src/subscriptions.d.ts.map +1 -1
- package/out/replicache/src/subscriptions.js +2 -10
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/transactions.d.ts +1 -0
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +2 -4
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +1 -22
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero/package.js +3 -3
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/normalize.d.ts +1 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +10 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +52 -1
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +171 -22
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/db/delete-lite-db.js +2 -1
- package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +5 -2
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration.js +4 -2
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
- package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +6 -1
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +20 -2
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/server/backup-watermark-poller.js +31 -0
- package/out/zero-cache/src/server/backup-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +91 -31
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +6 -16
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +48 -5
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +1 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -2
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +35 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js +34 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +73 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts +2 -6
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +14 -5
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -7
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +7 -7
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +82 -25
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +9 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +12 -4
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +45 -2
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +59 -8
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +7 -4
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +0 -5
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +29 -41
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +35 -16
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +77 -7
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +5 -13
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +6 -3
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts +6 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js +21 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +5 -6
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +32 -9
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +15 -5
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +66 -0
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +68 -78
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +84 -92
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +50 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +141 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +270 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js +39 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +12 -17
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +69 -24
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +333 -66
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +56 -13
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +8 -0
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +14 -3
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +41 -11
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.d.ts → litestream3-prometheus-poller.d.ts} +19 -45
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.js → litestream3-prometheus-poller.js} +57 -171
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +15 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +55 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +2 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +14 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +86 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +4 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +39 -14
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +75 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +261 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +121 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js +391 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +62 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +159 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +109 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +258 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +28 -12
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +116 -121
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +61 -10
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +101 -8
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +86 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -0
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +4 -3
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +12 -11
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +107 -148
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +42 -0
- package/out/zero-cache/src/services/litestream/config.yml +3 -2
- package/out/zero-cache/src/services/litestream/metrics.d.ts +4 -4
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +7 -10
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +57 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +137 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +4 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts +169 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +434 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts +358 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js +477 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +55 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +150 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +70 -25
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +19 -6
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
- package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js +7 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +17 -2
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +9 -3
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +119 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +214 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +3 -3
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +9 -5
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +131 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +413 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +120 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +283 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -0
- package/out/zero-cache/src/services/replicator/write-worker.js +23 -12
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +2 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/running-state.d.ts +1 -1
- package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/running-state.js +1 -0
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +53 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +68 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -3
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +64 -6
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +1 -2
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -25
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +21 -0
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +25 -4
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +11 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +27 -0
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +1 -0
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +24 -2
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts +25 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +33 -1
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts +12 -0
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +75 -0
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +6 -7
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +156 -101
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-schema/src/builder/table-builder.d.ts +23 -0
- package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +18 -0
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-server/src/custom.js +10 -3
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +8 -0
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +63 -24
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-types/src/schema.d.ts +11 -0
- package/out/zero-types/src/schema.d.ts.map +1 -1
- package/out/zql/src/mutate/crud.d.ts +10 -8
- package/out/zql/src/mutate/crud.d.ts.map +1 -1
- package/out/zql/src/mutate/crud.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +5 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +2 -2
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +83 -14
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/static-query.d.ts +1 -1
- package/out/zql/src/query/static-query.d.ts.map +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts +15 -0
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +33 -14
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.js +3 -3
- package/package.json +3 -3
- package/out/replicache/src/db/index.js +0 -91
- package/out/replicache/src/db/index.js.map +0 -1
- package/out/replicache/src/lazy.d.ts +0 -2
- package/out/replicache/src/lazy.d.ts.map +0 -1
- package/out/replicache/src/lazy.js +0 -12
- package/out/replicache/src/lazy.js.map +0 -1
- package/out/zero-cache/src/server/backup-watermark-reader.js +0 -35
- package/out/zero-cache/src/server/backup-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts +0 -16
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js +0 -54
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts +0 -25
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +0 -159
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts +0 -44
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +0 -65
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts +0 -11
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js +0 -74
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts +0 -42
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js +0 -117
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial-sync.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"sourcesContent":["import {mkdtemp, rm} from 'node:fs/promises';\nimport {platform, tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {Writable} from 'node:stream';\nimport {pipeline} from 'node:stream/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport type {DownloadStatus} from '../../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n} from '../../../db/create.ts';\nimport {listIndexes, listTables} from '../../../db/lite-tables.ts';\nimport * as Mode from '../../../db/mode-enum.ts';\nimport {\n BinaryCopyParser,\n hasBinaryDecoder,\n makeBinaryDecoder,\n textCastDecoder,\n} from '../../../db/pg-copy-binary.ts';\nimport {TsvParser} from '../../../db/pg-copy.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteIndex,\n} from '../../../db/pg-to-lite.ts';\nimport {getTypeParsers} from '../../../db/pg-type-parser.ts';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport type {IndexSpec, PublishedTableSpec} from '../../../db/specs.ts';\nimport {importSnapshot, TransactionPool} from '../../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../../observability/metrics.ts';\nimport {\n JSON_STRINGIFIED,\n liteValue,\n type LiteValueType,\n} from '../../../types/lite.ts';\nimport {liteTableName} from '../../../types/names.ts';\nimport {PG_15, PG_17} from '../../../types/pg-versions.ts';\nimport {\n connectPgClient,\n pgClient,\n type PostgresDB,\n type PostgresTransaction,\n type PostgresValueType,\n} from '../../../types/pg.ts';\nimport {CpuProfiler} from '../../../types/profiler.ts';\nimport type {ShardConfig} from '../../../types/shards.ts';\nimport {ALLOWED_APP_ID_CHARACTERS} from '../../../types/shards.ts';\nimport {id} from '../../../types/sql.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {ColumnMetadataStore} from '../../replicator/schema/column-metadata.ts';\nimport {initReplicationState} from '../../replicator/schema/replication-state.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {createReplicaAndSlot} from './replication-slots.ts';\nimport {ensureShardSchema} from './schema/init.ts';\nimport {getPublicationInfo} from './schema/published.ts';\nimport {\n dropShard,\n getInternalShardConfig,\n initReplica,\n validatePublications,\n} from './schema/shard.ts';\n\nexport type InitialSyncOptions = {\n tableCopyWorkers: number;\n profileCopy?: boolean | undefined;\n textCopy?: boolean | undefined;\n replicationSlotFailover?: boolean | undefined;\n /**\n * When set, run initial sync in \"shadow\" mode for verification: skip all\n * upstream mutations (no replication slot, no addReplica, no dropShard, no\n * slot drop on failure), suppress status events, and optionally sample\n * rows from each table via TABLESAMPLE BERNOULLI + LIMIT. The caller is\n * responsible for providing (and discarding) a throwaway SQLite `tx`.\n */\n shadow?:\n | {\n /** 0 < rate <= 1. When 1, no TABLESAMPLE clause is added. */\n sampleRate: number;\n /**\n * LIMIT N cap appended after TABLESAMPLE. Required: shadow sync is\n * for verification only, so every run must commit to a row budget.\n */\n maxRowsPerTable: number;\n }\n | undefined;\n};\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n syncOptions: InitialSyncOptions,\n context: ServerContext,\n) {\n if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) {\n throw new Error(\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character',\n );\n }\n const {\n tableCopyWorkers,\n profileCopy,\n textCopy = false,\n replicationSlotFailover = false,\n shadow,\n } = syncOptions;\n const syncMode: InitialSyncMode = shadow ? 'shadow' : 'initial';\n const copyFormat: CopyFormat = textCopy ? 'text' : 'binary';\n const start = performance.now();\n let sql: PostgresDB | undefined;\n let replicationSession: PostgresDB | undefined;\n const replicaID = Date.now().toString();\n let slotName: string | undefined; // undefined === shadow\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(\n tx,\n shadow ? async () => {} : undefined,\n ).publish(lc, 'Initializing');\n let releaseShadowSnapshot: (() => Promise<void>) | undefined;\n try {\n const copyProfiler = profileCopy ? await CpuProfiler.connect() : null;\n sql = await connectPgClient(lc, upstreamURI, 'initial-sync');\n // Replication session is only needed to create a replication slot in the\n // real path. In shadow mode we export a snapshot on a normal connection\n // instead, so no replication session is opened.\n replicationSession = shadow\n ? undefined\n : pgClient(lc, upstreamURI, 'initial-sync-replication-session', {\n ['fetch_types']: false, // Necessary for the streaming protocol\n connection: {replication: 'database'}, // https://www.postgresql.org/docs/current/protocol-replication.html\n });\n\n const pgVersion = await checkUpstreamConfig(sql);\n\n // In shadow mode we assume the shard is already initialized and just\n // read back the existing publications. `ensurePublishedTables` would\n // otherwise run DDL and potentially call `dropShard`, which must never\n // happen during a shadow run.\n const {publications} = shadow\n ? await getInternalShardConfig(sql, shard)\n : await ensurePublishedTables(lc, sql, shard);\n lc.info?.(`Upstream is setup with publications [${publications}]`);\n\n const {database, host} = sql.options;\n lc.info?.(\n shadow\n ? `acquiring exported snapshot on ${database}@${host} (shadow mode)`\n : `opening replication session to ${database}@${host}`,\n );\n\n let snapshot: string;\n let lsn: string;\n\n if (shadow) {\n const acquired = await acquireExportedSnapshotForShadowSync(\n lc,\n upstreamURI,\n );\n snapshot = acquired.snapshot;\n lsn = acquired.lsn;\n releaseShadowSnapshot = acquired.release;\n } else {\n const slot = await createReplicaAndSlot(\n lc,\n sql,\n must(replicationSession),\n shard,\n replicaID,\n replicationSlotFailover && pgVersion >= PG_17,\n );\n snapshot = slot.snapshot_name;\n lsn = slot.consistent_point;\n slotName = slot.slot_name;\n }\n\n const initialVersion = toStateVersionString(lsn);\n\n initReplicationState(tx, publications, initialVersion, context);\n\n // Run up to MAX_WORKERS to copy of tables at the replication slot's snapshot.\n // Retrieve the published schema at the consistent_point.\n const published = await runTx(\n sql,\n async tx => {\n await tx.unsafe(/* sql*/ `SET TRANSACTION SNAPSHOT '${snapshot}'`);\n return getPublicationInfo(tx, publications);\n },\n {mode: Mode.READONLY},\n );\n // Note: If this throws, initial-sync is aborted.\n validatePublications(lc, published);\n\n // Now that tables have been validated, kick off the copiers.\n const {tables, indexes} = published;\n const numTables = tables.length;\n if (platform() === 'win32' && tableCopyWorkers < numTables) {\n lc.warn?.(\n `Increasing the number of copy workers from ${tableCopyWorkers} to ` +\n `${numTables} to work around a Node/Postgres connection bug`,\n );\n }\n const numWorkers =\n platform() === 'win32'\n ? numTables\n : Math.min(tableCopyWorkers, numTables);\n\n const copyPool = await connectPgClient(\n lc,\n upstreamURI,\n 'initial-sync-copy-worker',\n {\n max: numWorkers,\n ['max_lifetime']: 120 * 60, // set a long (2h) limit for COPY streaming\n },\n );\n const copiers = startTableCopyWorkers(\n lc,\n copyPool,\n snapshot,\n numWorkers,\n numTables,\n );\n try {\n createLiteTables(tx, tables, initialVersion);\n const sampleRate = shadow?.sampleRate;\n const maxRowsPerTable = shadow?.maxRowsPerTable;\n const downloads = await Promise.all(\n tables.map(spec =>\n copiers.processReadTask((db, lc) =>\n getInitialDownloadState(lc, db, spec, shadow !== undefined),\n ),\n ),\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying ${numTables} upstream tables at version ${initialVersion}`,\n 5000,\n () => ({downloadStatus: downloads.map(({status}) => status)}),\n );\n\n void copyProfiler?.start();\n const copyStart = performance.now();\n const copyResults = await Promise.all(\n downloads.map(table =>\n copiers.processReadTask((db, lc) =>\n copy(\n lc,\n table,\n copyPool,\n db,\n tx,\n textCopy,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n ),\n ),\n ),\n );\n const copyElapsed = performance.now() - copyStart;\n void copyProfiler?.stopAndDispose(lc, 'initial-copy');\n copiers.setDone();\n\n const copySummary = initialSyncCopySummary(copyResults, copyElapsed);\n\n statusPublisher.publish(\n lc,\n 'Indexing',\n `Creating ${indexes.length} indexes`,\n 5000,\n );\n const indexStart = performance.now();\n createLiteIndices(lc, tx, indexes);\n const index = performance.now() - indexStart;\n lc.info?.(`Created indexes (${index.toFixed(3)} ms)`);\n\n if (slotName && replicaID) {\n await initReplica(sql, shard, replicaID, published, context);\n } else {\n assert(shadow, 'expected to be in shadow sync if there is no slotName');\n const rowsByTable = new Map<string, number>();\n for (let i = 0; i < downloads.length; i++) {\n rowsByTable.set(downloads[i].status.table, copyResults[i].rows);\n }\n verifyShadowReplica(lc, tx, published, rowsByTable);\n }\n\n const elapsed = performance.now() - start;\n const copyOtherMs = Math.max(0, elapsed - copySummary.flushMs - index);\n recordInitialSyncRunMetrics(\n {\n durationMs: elapsed,\n rows: copySummary.rows,\n copyBytes: copySummary.copyBytes,\n copyMs: copySummary.copyMs,\n copyOtherMs,\n flushMs: copySummary.flushMs,\n indexMs: index,\n },\n {\n result: 'success',\n syncMode,\n copyFormat,\n },\n );\n lc.info?.(\n `Synced ${copySummary.rows.toLocaleString()} rows of ${numTables} tables in ${publications} up to ${lsn} ` +\n `(flush: ${copySummary.flushMs.toFixed(3)}, index: ${index.toFixed(3)}, total: ${elapsed.toFixed(3)} ms)`,\n {\n syncMode,\n copyFormat,\n publications,\n lsn,\n ...copySummary,\n indexes: indexes.length,\n indexMs: index,\n copyOtherMs,\n totalMs: elapsed,\n },\n );\n } finally {\n // All meaningful errors are handled at the processReadTask() call site.\n void copyPool.end().catch(e => lc.warn?.(`Error closing copyPool`, e));\n }\n } catch (e) {\n recordInitialSyncRunMetrics(\n {durationMs: performance.now() - start},\n {\n result: 'error',\n syncMode,\n copyFormat,\n },\n );\n if (slotName && sql) {\n // If initial-sync did not succeed, make a best effort to drop the\n // orphaned replication slot to avoid running out of slots in\n // pathological cases that result in repeated failures.\n lc.warn?.(`dropping replication slot ${slotName}`, e);\n await sql`\n SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots\n WHERE slot_name = ${slotName};\n `.catch(e => lc.warn?.(`Unable to drop replication slot ${slotName}`, e));\n }\n await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n if (releaseShadowSnapshot) {\n await releaseShadowSnapshot().catch(e =>\n lc.warn?.(`Error releasing shadow snapshot`, e),\n );\n }\n if (replicationSession) {\n await replicationSession.end();\n }\n if (sql) {\n await sql.end();\n }\n }\n}\n\nexport type ShadowSyncOptions = {\n sampleRate: number;\n maxRowsPerTable: number;\n /**\n * Parent directory for the throwaway SQLite replica. Defaults to the OS\n * tmpdir. Primarily for tests that need to isolate the scratch directory.\n */\n parentDir?: string | undefined;\n};\n\n/**\n * Exercises the initial-sync code path against a sample of rows from every\n * published table, writing into a throwaway SQLite database that is deleted\n * when the run ends. Produces zero upstream mutations: no replication slot,\n * no `addReplica`, no `dropShard`, no status events.\n *\n * Intended to be invoked periodically so that if a customer ever needs a\n * full reset, we have recent confidence that `initialSync` still works.\n * The shard must already be initialized upstream.\n */\nexport async function shadowInitialSync(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n shadow: ShadowSyncOptions,\n context: ServerContext,\n syncOptions?: Pick<InitialSyncOptions, 'textCopy'>,\n): Promise<void> {\n const dir = await mkdtemp(\n join(shadow.parentDir ?? tmpdir(), 'zero-shadow-sync-'),\n );\n const dbPath = join(dir, 'shadow-replica.db');\n const db = new Database(lc, dbPath);\n try {\n await initialSync(\n lc,\n shard,\n db,\n upstreamURI,\n {\n // Shadow sync copies small samples, so one worker is plenty —\n // no reason to burn additional upstream connections.\n tableCopyWorkers: 1,\n textCopy: syncOptions?.textCopy,\n shadow,\n },\n context,\n );\n } finally {\n try {\n db.close();\n } catch (e) {\n lc.warn?.(`Error closing shadow replica db`, e);\n }\n await rm(dir, {recursive: true, force: true}).catch(e =>\n lc.warn?.(`Error cleaning up shadow replica dir ${dir}`, e),\n );\n }\n}\n\nasync function checkUpstreamConfig(sql: PostgresDB) {\n const {walLevel, version} = (\n await sql<{walLevel: string; version: number}[]>`\n SELECT current_setting('wal_level') as \"walLevel\", \n current_setting('server_version_num') as \"version\";\n `\n )[0];\n\n if (walLevel !== 'logical') {\n throw new Error(\n `Postgres must be configured with \"wal_level = logical\" (currently: \"${walLevel})`,\n );\n }\n if (version < PG_15) {\n throw new Error(\n `Must be running Postgres 15 or higher (currently: \"${version}\")`,\n );\n }\n return version;\n}\n\nasync function ensurePublishedTables(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardConfig,\n validate = true,\n): Promise<{publications: string[]}> {\n const {database, host} = sql.options;\n lc.info?.(`Ensuring upstream PUBLICATION on ${database}@${host}`);\n\n await ensureShardSchema(lc, sql, shard);\n const {publications} = await getInternalShardConfig(sql, shard);\n\n if (validate) {\n let valid = false;\n const nonInternalPublications = publications.filter(\n p => !p.startsWith('_'),\n );\n const exists = await sql`\n SELECT pubname FROM pg_publication WHERE pubname IN ${sql(publications)}\n `.values();\n if (exists.length !== publications.length) {\n lc.warn?.(\n `some configured publications [${publications}] are missing: ` +\n `[${exists.flat()}]. resyncing`,\n );\n } else if (\n !equals(new Set(shard.publications), new Set(nonInternalPublications))\n ) {\n lc.warn?.(\n `requested publications [${shard.publications}] differ from previous` +\n `publications [${nonInternalPublications}]. resyncing`,\n );\n } else {\n valid = true;\n }\n if (!valid) {\n await sql.unsafe(dropShard(shard.appID, shard.shardNum));\n return ensurePublishedTables(lc, sql, shard, false);\n }\n }\n return {publications};\n}\n\nfunction startTableCopyWorkers(\n lc: LogContext,\n db: PostgresDB,\n snapshot: string,\n numWorkers: number,\n numTables: number,\n): TransactionPool {\n const {init} = importSnapshot(snapshot);\n const tableCopiers = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n initialWorkers: numWorkers,\n });\n tableCopiers.run(db);\n\n lc.info?.(`Started ${numWorkers} workers to copy ${numTables} tables`);\n\n if (parseInt(process.versions.node) < 22) {\n lc.warn?.(\n `\\n\\n\\n` +\n `Older versions of Node have a bug that results in an unresponsive\\n` +\n `Postgres connection after running certain combinations of COPY commands.\\n` +\n `If initial sync hangs, run zero-cache with Node v22+. This has the additional\\n` +\n `benefit of being consistent with the Node version run in the production container image.` +\n `\\n\\n\\n`,\n );\n }\n return tableCopiers;\n}\n\n/**\n * Shadow-mode alternative to `createReplicationSlot`: opens a dedicated\n * READ ONLY REPEATABLE READ transaction on a normal connection, exports the\n * snapshot and captures the current WAL LSN, then holds the transaction\n * open until `release()` is called. The held transaction keeps the snapshot\n * importable by the table-copy workers for the duration of the COPY phase.\n *\n * Idle-in-transaction timeout is disabled locally so the exporter doesn't\n * get killed while workers are still importing.\n */\nasync function acquireExportedSnapshotForShadowSync(\n lc: LogContext,\n upstreamURI: string,\n): Promise<{\n snapshot: string;\n lsn: string;\n release: () => Promise<void>;\n}> {\n const holder = await connectPgClient(\n lc,\n upstreamURI,\n 'shadow-initial-sync-snapshot',\n {\n max: 1,\n },\n );\n const ready = resolver<{snapshot: string; lsn: string}>();\n const release = resolver<void>();\n const held = holder\n .begin(Mode.READONLY, async tx => {\n await tx`SET LOCAL idle_in_transaction_session_timeout = 0`.execute();\n const [row] = await tx<{snapshot: string; lsn: string}[]>`\n SELECT pg_export_snapshot() AS snapshot,\n pg_current_wal_lsn()::text AS lsn`;\n ready.resolve(row);\n await release.promise;\n })\n .catch(e => ready.reject(e));\n\n let snapshot: string;\n let lsn: string;\n try {\n ({snapshot, lsn} = await ready.promise);\n } catch (e) {\n await holder\n .end()\n .catch(err =>\n lc.warn?.(`Error ending shadow snapshot holder after failure`, err),\n );\n throw e;\n }\n lc.info?.(\n `Exported snapshot ${snapshot} at LSN ${lsn} (shadow initial sync)`,\n );\n return {\n snapshot,\n lsn,\n release: async () => {\n release.resolve();\n try {\n await held;\n } catch (e) {\n lc.warn?.(`snapshot holder transaction ended with error`, e);\n }\n await holder.end();\n },\n };\n}\n\nfunction createLiteTables(\n tx: Database,\n tables: PublishedTableSpec[],\n initialVersion: string,\n) {\n // TODO: Figure out how to reuse the ChangeProcessor here to avoid\n // duplicating the ColumnMetadata logic.\n const columnMetadata = must(ColumnMetadataStore.getInstance(tx));\n for (const t of tables) {\n tx.exec(createLiteTableStatement(mapPostgresToLite(t, initialVersion)));\n const tableName = liteTableName(t);\n for (const [colName, colSpec] of Object.entries(t.columns)) {\n columnMetadata.insert(tableName, colName, colSpec);\n }\n }\n}\n\nfunction createLiteIndices(lc: LogContext, tx: Database, indices: IndexSpec[]) {\n for (const [i, index] of indices.entries()) {\n const stmt = createLiteIndexStatement(mapPostgresToLiteIndex(index));\n lc.info?.(`Creating index ${i + 1}/${indices.length}: ${stmt}`);\n const start = performance.now();\n tx.exec(stmt);\n lc.info?.(\n `Created index ${i + 1}/${indices.length} ` +\n `(${(performance.now() - start).toFixed(3)} ms): ${stmt}`,\n );\n }\n}\n\n/**\n * Runs structural assertions over a just-synced replica and throws if any\n * fail. Only called in shadow mode — a successful return means the replica\n * is schema-complete, row-count consistent, and its column metadata is in\n * sync with its lite schema.\n *\n * Note: this intentionally does NOT verify ZQL-queryability. Tables that\n * `computeZqlSpecs` drops (no PK / no all-NOT-NULL unique index, unsupported\n * column types, etc.) are silently skipped in production too — there's\n * nothing shadow-specific about them, so failing here would diverge from\n * prod over an upstream-schema condition prod accepts.\n *\n * Exported for testing.\n */\nexport function verifyShadowReplica(\n lc: LogContext,\n db: Database,\n published: {tables: PublishedTableSpec[]; indexes: IndexSpec[]},\n rowsByTable: ReadonlyMap<string, number>,\n): void {\n const issues: string[] = [];\n let columnsChecked = 0;\n let rowsChecked = 0;\n\n // 1. Schema completeness: every published table exists in the replica\n // with at least the expected column set.\n const liteTables = listTables(db);\n const liteTableByName = new Map(liteTables.map(t => [t.name, t]));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const lite = liteTableByName.get(name);\n if (!lite) {\n issues.push(`missing table in replica: ${name}`);\n continue;\n }\n for (const col of Object.keys(pt.columns)) {\n columnsChecked++;\n if (!(col in lite.columns)) {\n issues.push(`column missing in replica table ${name}: ${col}`);\n }\n }\n }\n\n // Every published index exists in the replica.\n const liteIndexNames = new Set(listIndexes(db).map(i => i.name));\n for (const ix of published.indexes) {\n const mapped = mapPostgresToLiteIndex(ix);\n if (!liteIndexNames.has(mapped.name)) {\n issues.push(\n `missing index in replica: ${mapped.name} on ${mapped.tableName}`,\n );\n }\n }\n\n // 2. Row counts: SQLite COUNT(*) matches the in-memory copy counter.\n for (const [table, expected] of rowsByTable) {\n try {\n const [row] = db\n .prepare(`SELECT COUNT(*) as count FROM \"${table}\"`)\n .all<{count: number}>();\n if (row.count !== expected) {\n issues.push(\n `row count mismatch for table ${table}: ` +\n `copy counter reported ${expected}, replica has ${row.count}`,\n );\n } else {\n rowsChecked += row.count;\n }\n } catch (e) {\n issues.push(`could not count rows in table ${table}: ${String(e)}`);\n }\n }\n\n // 3. Column metadata: every published column has a _zero.column_metadata row.\n const meta = must(ColumnMetadataStore.getInstance(db));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const rows = meta.getTable(name);\n for (const col of Object.keys(pt.columns)) {\n if (!rows.has(col)) {\n issues.push(`missing column_metadata row for ${name}.${col}`);\n }\n }\n }\n\n if (issues.length) {\n throw new Error(\n `Shadow replica verification failed (${issues.length} issue(s)):\\n` +\n issues.map(i => ` - ${i}`).join('\\n'),\n );\n }\n\n lc.info?.(\n `Shadow replica verification passed: ` +\n `${published.tables.length} tables, ` +\n `${published.indexes.length} indexes, ` +\n `${columnsChecked} columns, ` +\n `${rowsChecked.toLocaleString()} rows`,\n );\n}\n\n// Verified empirically that batches of 50 seem to be the sweet spot,\n// similar to the report in https://sqlite.org/forum/forumpost/8878a512d3652655\n//\n// Exported for testing.\nexport const INSERT_BATCH_SIZE = 50;\n\nconst MB = 1024 * 1024;\nconst MAX_BUFFERED_ROWS = 10_000;\nconst BUFFERED_SIZE_THRESHOLD = 8 * MB;\n\nexport type DownloadStatements = {\n select: string;\n getTotalRows: string;\n getTotalBytes: string;\n};\n\n/**\n * Produces ` TABLESAMPLE BERNOULLI(n)` when `sampleRate` is < 1, else `''`.\n * Row-level Bernoulli sampling is used (rather than SYSTEM) because it\n * produces a more uniform sample and, unlike SYSTEM, still returns rows\n * for small tables at low rates.\n */\nfunction tableSampleClause(sampleRate: number | undefined): string {\n if (sampleRate === undefined || sampleRate >= 1) {\n return '';\n }\n // Round away float noise (e.g. 0.3 * 100 = 30.000000000000004) while still\n // preserving sub-integer rates like 0.001 (= 0.1%).\n const pct = parseFloat((sampleRate * 100).toFixed(6));\n return /*sql*/ ` TABLESAMPLE BERNOULLI(${pct})`;\n}\n\nfunction limitClause(maxRowsPerTable: number | undefined): string {\n return maxRowsPerTable !== undefined\n ? /*sql*/ ` LIMIT ${maxRowsPerTable}`\n : '';\n}\n\n/**\n * Returns the SELECT column expressions for binary COPY, casting columns\n * without a known binary decoder to `::text`.\n */\nexport function makeBinarySelectExprs(\n table: PublishedTableSpec,\n cols: string[],\n): string[] {\n return cols.map(col => {\n const spec = table.columns[col];\n return hasBinaryDecoder(spec) ? id(col) : `${id(col)}::text`;\n });\n}\n\nexport function makeDownloadStatements(\n table: PublishedTableSpec,\n cols: string[],\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n selectExprs?: string[] | undefined,\n): DownloadStatements {\n const filterConditions = Object.values(table.publications)\n .map(({rowFilter}) => rowFilter)\n .filter(f => !!f); // remove nulls\n const where =\n filterConditions.length === 0\n ? ''\n : /*sql*/ `WHERE ${filterConditions.join(' OR ')}`;\n const sample = tableSampleClause(sampleRate);\n const limit = limitClause(maxRowsPerTable);\n const fromTable = /*sql*/ `FROM ${id(table.schema)}.${id(table.name)}${sample} ${where}`;\n const select = /*sql*/ `SELECT ${(selectExprs ?? cols.map(id)).join(',')} ${fromTable}${limit}`;\n if (limit) {\n // With LIMIT, wrap counts/sums in a subquery so they reflect the\n // capped rowset rather than the full (sampled) table.\n const bytesExpr = cols\n .map(col => `COALESCE(pg_column_size(${id(col)}), 0)`)\n .join(' + ');\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*)::bigint AS \"totalRows\" FROM (SELECT 1 AS _ ${fromTable}${limit}) s`,\n getTotalBytes: /*sql*/ `SELECT COALESCE(SUM(b), 0)::bigint AS \"totalBytes\" FROM (SELECT (${bytesExpr}) AS b ${fromTable}${limit}) s`,\n };\n }\n const totalBytes = `(${cols.map(col => `SUM(COALESCE(pg_column_size(${id(col)}), 0))`).join(' + ')})`;\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*) AS \"totalRows\" ${fromTable}`,\n getTotalBytes: /*sql*/ `SELECT ${totalBytes} AS \"totalBytes\" ${fromTable}`,\n };\n}\n\ntype DownloadState = {\n spec: PublishedTableSpec;\n status: DownloadStatus;\n};\n\ntype CopyFormat = 'binary' | 'text';\ntype InitialSyncMode = 'initial' | 'shadow';\n\ntype InitialSyncMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n};\n\ntype InitialSyncRunMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n result: 'success' | 'error';\n};\n\ntype CopyResult = {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n columnCount: number;\n rows: number;\n estimatedRows: number;\n estimatedBytes: number | undefined;\n flushMs: number;\n elapsedMs: number;\n sourceWaitMs: number;\n processingMs: number;\n copyBytes: number;\n};\n\nconst INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\nexport const COPY_METRIC_BATCH_BYTES = 8 * 1024 * 1024;\nconst SLOW_COPY_FLUSH_MS = 10_000;\n\n// change-streamer imports this module before startOtelAuto() runs, so create\n// instruments lazily to avoid binding them to OTel's no-op meter provider.\nfunction initialSyncRuns() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_runs',\n 'Initial sync runs, labeled by result.',\n );\n}\n\nfunction initialSyncDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_duration',\n 'Wall-clock duration of an initial sync run, labeled by result.',\n );\n}\n\nfunction initialSyncCopyDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_duration',\n 'Wall-clock duration of the COPY phase for a successful initial sync run.',\n );\n}\n\nfunction initialSyncCopyOtherDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_other_duration',\n 'Initial sync total duration excluding SQLite flush and index time for a successful run.',\n );\n}\n\nfunction initialSyncFlushDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_flush_duration',\n 'Total SQLite flush time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncIndexDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_index_duration',\n 'SQLite index creation time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncRows() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_rows',\n 'Rows copied during successful initial sync runs.',\n );\n}\n\nfunction initialSyncCopyStream() {\n return getOrCreateCounter('replication', 'initial_sync_copy_stream', {\n description:\n 'PostgreSQL COPY stream bytes processed during initial sync, including in-progress and failed runs.',\n unit: 'bytes',\n });\n}\n\nfunction initialSyncCompletedCopyStream() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_completed_copy_stream',\n {\n description:\n 'PostgreSQL COPY stream bytes processed during successful initial sync runs.',\n unit: 'bytes',\n },\n );\n}\n\nfunction initialSyncCopyChunks() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_copy_chunks',\n 'PostgreSQL COPY stream chunks processed during initial sync.',\n );\n}\n\nexport function createCopyMetricBatcher(\n record: (bytes: number, chunks: number) => void,\n) {\n let bytes = 0;\n let chunks = 0;\n\n function flush() {\n if (chunks === 0) {\n return;\n }\n record(bytes, chunks);\n bytes = 0;\n chunks = 0;\n }\n\n return {\n add(chunkBytes: number) {\n bytes += chunkBytes;\n chunks++;\n if (bytes >= COPY_METRIC_BATCH_BYTES) {\n flush();\n }\n },\n flush,\n };\n}\n\nexport function initialSyncCopyMetrics(attrs: InitialSyncMetricAttrs) {\n const labels = initialSyncMetricAttrs(attrs);\n const copyStreamMetric = initialSyncCopyStream();\n const copyChunksMetric = initialSyncCopyChunks();\n return createCopyMetricBatcher((bytes, chunks) => {\n copyStreamMetric.add(bytes, labels);\n copyChunksMetric.add(chunks, labels);\n });\n}\n\nfunction initialSyncDurationHistogram(name: string, description: string) {\n return getOrCreateHistogram('replication', name, {\n description,\n unit: 's',\n bucketBoundaries: INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nfunction initialSyncMetricAttrs(attrs: InitialSyncMetricAttrs) {\n return {\n sync_mode: attrs.syncMode,\n copy_format: attrs.copyFormat,\n };\n}\n\nfunction initialSyncRunMetricAttrs(attrs: InitialSyncRunMetricAttrs) {\n return {\n ...initialSyncMetricAttrs(attrs),\n result: attrs.result,\n };\n}\n\nfunction recordInitialSyncRunMetrics(\n stats: {\n durationMs: number;\n rows?: number | undefined;\n copyBytes?: number | undefined;\n copyMs?: number | undefined;\n copyOtherMs?: number | undefined;\n flushMs?: number | undefined;\n indexMs?: number | undefined;\n },\n attrs: InitialSyncRunMetricAttrs,\n) {\n const labels = initialSyncRunMetricAttrs(attrs);\n initialSyncRuns().add(1, labels);\n initialSyncDuration().recordMs(stats.durationMs, labels);\n if (attrs.result === 'success') {\n if (stats.copyMs !== undefined) {\n initialSyncCopyDuration().recordMs(stats.copyMs, labels);\n }\n if (stats.copyOtherMs !== undefined) {\n initialSyncCopyOtherDuration().recordMs(stats.copyOtherMs, labels);\n }\n if (stats.flushMs !== undefined) {\n initialSyncFlushDuration().recordMs(stats.flushMs, labels);\n }\n if (stats.indexMs !== undefined) {\n initialSyncIndexDuration().recordMs(stats.indexMs, labels);\n }\n if (stats.rows !== undefined && stats.rows > 0) {\n initialSyncRows().add(stats.rows, labels);\n }\n if (stats.copyBytes !== undefined && stats.copyBytes > 0) {\n initialSyncCompletedCopyStream().add(stats.copyBytes, labels);\n }\n }\n}\n\nfunction initialSyncCopySummary(\n results: readonly CopyResult[],\n copyMs: number,\n) {\n const totals = results.reduce(\n (acc, curr) => {\n acc.rows += curr.rows;\n acc.flushMs += curr.flushMs;\n acc.copyBytes += curr.copyBytes;\n return acc;\n },\n {\n rows: 0,\n flushMs: 0,\n copyBytes: 0,\n },\n );\n return {\n tables: results.length,\n rows: totals.rows,\n copyMs,\n flushMs: totals.flushMs,\n copyBytes: totals.copyBytes,\n };\n}\n\nfunction logSlowCopyFlush(\n lc: LogContext,\n details: {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n elapsedMs: number;\n flushedRows: number;\n flushedBytes: number;\n rows: number;\n copyBytes: number;\n },\n) {\n if (details.elapsedMs < SLOW_COPY_FLUSH_MS || details.flushedRows === 0) {\n return;\n }\n lc.info?.('initial-sync table copy slow flush', details);\n}\n\n// Exported for testing.\nexport async function getInitialDownloadState(\n lc: LogContext,\n sql: PostgresDB,\n spec: PublishedTableSpec,\n skipTotals: boolean,\n): Promise<DownloadState> {\n const start = performance.now();\n const table = liteTableName(spec);\n const columns = Object.keys(spec.columns);\n if (skipTotals) {\n // Shadow sync suppresses status events, so the pg_class\n // estimates would be computed and thrown away.\n return {\n spec,\n status: {table, columns, rows: 0, totalRows: 0, totalBytes: 0},\n };\n }\n // Use pg_class estimates instead of expensive COUNT(*) and\n // SUM(pg_column_size(...)) full table scans. The exact values are only\n // used for progress reporting, so estimates are sufficient.\n const qualifiedName = `${id(spec.schema)}.${id(spec.name)}`;\n const estimateResult = await sql<\n {totalRows: number; totalBytes: number}[]\n >`SELECT GREATEST(reltuples, 0)::float8 AS \"totalRows\",\n pg_table_size(oid)::float8 AS \"totalBytes\"\n FROM pg_class\n WHERE oid = ${qualifiedName}::regclass`;\n\n const {totalRows, totalBytes} = estimateResult[0] ?? {\n totalRows: 0,\n totalBytes: 0,\n };\n\n const state: DownloadState = {\n spec,\n status: {\n table,\n columns,\n rows: 0,\n totalRows,\n totalBytes,\n },\n };\n const elapsed = (performance.now() - start).toFixed(3);\n lc.info?.(`Computed initial download state for ${table} (${elapsed} ms)`, {\n state: state.status,\n });\n return state;\n}\n\nfunction copy(\n lc: LogContext,\n {spec: table, status}: DownloadState,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n textCopy: boolean,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n if (textCopy) {\n return copyText(\n lc,\n table,\n status,\n dbClient,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n }\n return copyBinary(\n lc,\n table,\n status,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n}\n\nasync function copyBinary(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'binary';\n const copyMetrics = initialSyncCopyMetrics({syncMode, copyFormat});\n let flushMs = 0;\n let copyBytes = 0;\n let processingMs = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n // Build SELECT with ::text casts for columns without a known binary decoder.\n const select = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n makeBinarySelectExprs(table, columnNames),\n ).select;\n\n const decoders = orderedColumns.map(([, spec]) =>\n hasBinaryDecoder(spec) ? makeBinaryDecoder(spec) : textCastDecoder,\n );\n\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n const binaryParser = new BinaryCopyParser();\n let col = 0;\n\n lc.info?.(`Starting binary copy stream of ${tableName}:`, select);\n\n const copyStreamStart = performance.now();\n await pipeline(\n await from\n .unsafe(`COPY (${select}) TO STDOUT WITH (FORMAT binary)`)\n .readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n const processingStart = performance.now();\n try {\n copyBytes += chunk.length;\n copyMetrics.add(chunk.length);\n for (const fieldBuf of binaryParser.parse(chunk)) {\n const fieldSize = fieldBuf === null ? 4 : fieldBuf.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n fieldBuf === null ? null : decoders[col](fieldBuf);\n\n if (++col === decoders.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n const processingStart = performance.now();\n try {\n copyMetrics.flush();\n flush();\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n destroy(error, callback) {\n copyMetrics.flush();\n callback(error);\n },\n }),\n );\n\n const sourceWaitMs = Math.max(\n 0,\n performance.now() - copyStreamStart - processingMs,\n );\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n sourceWaitMs,\n processingMs,\n copyBytes,\n } satisfies CopyResult;\n\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n\nasync function copyText(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'text';\n const copyMetrics = initialSyncCopyMetrics({syncMode, copyFormat});\n let flushMs = 0;\n let copyBytes = 0;\n let processingMs = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n const {select} = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n );\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n lc.info?.(`Starting text copy stream of ${tableName}:`, select);\n const pgParsers = await getTypeParsers(dbClient, {returnJsonAsString: true});\n const parsers = columnSpecs.map(c => {\n const pgParse = pgParsers.getTypeParser(c.typeOID);\n return (val: string) =>\n liteValue(\n pgParse(val) as PostgresValueType,\n c.dataType,\n JSON_STRINGIFIED,\n );\n });\n\n const tsvParser = new TsvParser();\n let col = 0;\n\n const copyStreamStart = performance.now();\n await pipeline(\n await from.unsafe(`COPY (${select}) TO STDOUT`).readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n const processingStart = performance.now();\n try {\n copyBytes += chunk.length;\n copyMetrics.add(chunk.length);\n for (const text of tsvParser.parse(chunk)) {\n const fieldSize = text === null ? 4 : text.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n text === null ? null : parsers[col](text);\n\n if (++col === parsers.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n const processingStart = performance.now();\n try {\n copyMetrics.flush();\n flush();\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n destroy(error, callback) {\n copyMetrics.flush();\n callback(error);\n },\n }),\n );\n\n const sourceWaitMs = Math.max(\n 0,\n performance.now() - copyStreamStart - processingMs,\n );\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n sourceWaitMs,\n processingMs,\n copyBytes,\n } satisfies CopyResult;\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"initial-sync.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/initial-sync.ts"],"sourcesContent":["import {mkdtemp, rm} from 'node:fs/promises';\nimport {platform, tmpdir} from 'node:os';\nimport {join} from 'node:path';\nimport {Writable} from 'node:stream';\nimport {pipeline} from 'node:stream/promises';\nimport type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {assert} from '../../../../../shared/src/asserts.ts';\nimport type {JSONObject} from '../../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../../shared/src/must.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport type {DownloadStatus} from '../../../../../zero-events/src/status.ts';\nimport {Database} from '../../../../../zqlite/src/db.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n} from '../../../db/create.ts';\nimport {listIndexes, listTables} from '../../../db/lite-tables.ts';\nimport * as Mode from '../../../db/mode-enum.ts';\nimport {\n BinaryCopyParser,\n hasBinaryDecoder,\n makeBinaryDecoder,\n textCastDecoder,\n} from '../../../db/pg-copy-binary.ts';\nimport {TsvParser} from '../../../db/pg-copy.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteIndex,\n} from '../../../db/pg-to-lite.ts';\nimport {getTypeParsers} from '../../../db/pg-type-parser.ts';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport type {IndexSpec, PublishedTableSpec} from '../../../db/specs.ts';\nimport {importSnapshot, TransactionPool} from '../../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../../observability/metrics.ts';\nimport {\n JSON_STRINGIFIED,\n liteValue,\n type LiteValueType,\n} from '../../../types/lite.ts';\nimport {liteTableName} from '../../../types/names.ts';\nimport {PG_15, PG_17} from '../../../types/pg-versions.ts';\nimport {\n connectPgClient,\n pgClient,\n type PostgresDB,\n type PostgresTransaction,\n type PostgresValueType,\n} from '../../../types/pg.ts';\nimport {CpuProfiler} from '../../../types/profiler.ts';\nimport type {ShardConfig} from '../../../types/shards.ts';\nimport {ALLOWED_APP_ID_CHARACTERS} from '../../../types/shards.ts';\nimport {id} from '../../../types/sql.ts';\nimport {ReplicationStatusPublisher} from '../../replicator/replication-status.ts';\nimport {ColumnMetadataStore} from '../../replicator/schema/column-metadata.ts';\nimport {initReplicationState} from '../../replicator/schema/replication-state.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {createReplicaAndSlot} from './replication-slots.ts';\nimport {ensureShardSchema} from './schema/init.ts';\nimport {getPublicationInfo} from './schema/published.ts';\nimport {\n dropShard,\n getInternalShardConfig,\n getReplicaState,\n initReplica,\n validatePublications,\n type ReplicaState,\n} from './schema/shard.ts';\n\nexport type InitialSyncOptions = {\n tableCopyWorkers: number;\n profileCopy?: boolean | undefined;\n textCopy?: boolean | undefined;\n replicationSlotFailover?: boolean | undefined;\n /**\n * When set, run initial sync in \"shadow\" mode for verification: skip all\n * upstream mutations (no replication slot, no addReplica, no dropShard, no\n * slot drop on failure), suppress status events, and optionally sample\n * rows from each table via TABLESAMPLE BERNOULLI + LIMIT. The caller is\n * responsible for providing (and discarding) a throwaway SQLite `tx`.\n */\n shadow?:\n | {\n /** 0 < rate <= 1. When 1, no TABLESAMPLE clause is added. */\n sampleRate: number;\n /**\n * LIMIT N cap appended after TABLESAMPLE. Required: shadow sync is\n * for verification only, so every run must commit to a row budget.\n */\n maxRowsPerTable: number;\n }\n | undefined;\n};\n\nexport type ReplicaOptions = {\n backupV5: boolean;\n};\n\n/** Server context to store with the initial sync metadata for debugging. */\nexport type ServerContext = JSONObject;\n\n/**\n * @returns The {@link ReplicaState} of the initialized replica, or `undefined`\n * for a shadow sync.\n */\nexport async function initialSync(\n lc: LogContext,\n shard: ShardConfig,\n tx: Database,\n upstreamURI: string,\n syncOptions: InitialSyncOptions,\n context: ServerContext,\n {backupV5}: ReplicaOptions = {backupV5: true},\n): Promise<ReplicaState | undefined> {\n if (!ALLOWED_APP_ID_CHARACTERS.test(shard.appID)) {\n throw new Error(\n 'The App ID may only consist of lower-case letters, numbers, and the underscore character',\n );\n }\n const {\n tableCopyWorkers,\n profileCopy,\n textCopy = false,\n replicationSlotFailover = false,\n shadow,\n } = syncOptions;\n const syncMode: InitialSyncMode = shadow ? 'shadow' : 'initial';\n const copyFormat: CopyFormat = textCopy ? 'text' : 'binary';\n const start = performance.now();\n let sql: PostgresDB | undefined;\n let replicationSession: PostgresDB | undefined;\n const replicaID = Date.now().toString();\n let slotName: string | undefined; // undefined === shadow\n const statusPublisher = ReplicationStatusPublisher.forRunningTransaction(\n tx,\n shadow ? async () => {} : undefined,\n ).publish(lc, 'Initializing');\n let releaseShadowSnapshot: (() => Promise<void>) | undefined;\n try {\n const copyProfiler = profileCopy ? await CpuProfiler.connect() : null;\n sql = await connectPgClient(lc, upstreamURI, 'initial-sync');\n // Replication session is only needed to create a replication slot in the\n // real path. In shadow mode we export a snapshot on a normal connection\n // instead, so no replication session is opened.\n replicationSession = shadow\n ? undefined\n : pgClient(lc, upstreamURI, 'initial-sync-replication-session', {\n ['fetch_types']: false, // Necessary for the streaming protocol\n connection: {replication: 'database'}, // https://www.postgresql.org/docs/current/protocol-replication.html\n });\n\n const pgVersion = await checkUpstreamConfig(sql);\n\n // In shadow mode we assume the shard is already initialized and just\n // read back the existing publications. `ensurePublishedTables` would\n // otherwise run DDL and potentially call `dropShard`, which must never\n // happen during a shadow run.\n const {publications} = shadow\n ? await getInternalShardConfig(sql, shard)\n : await ensurePublishedTables(lc, sql, shard);\n lc.info?.(`Upstream is setup with publications [${publications}]`);\n\n const {database, host} = sql.options;\n lc.info?.(\n shadow\n ? `acquiring exported snapshot on ${database}@${host} (shadow mode)`\n : `opening replication session to ${database}@${host}`,\n );\n\n let snapshot: string;\n let lsn: string;\n\n if (shadow) {\n const acquired = await acquireExportedSnapshotForShadowSync(\n lc,\n upstreamURI,\n );\n snapshot = acquired.snapshot;\n lsn = acquired.lsn;\n releaseShadowSnapshot = acquired.release;\n } else {\n const slot = await createReplicaAndSlot(\n lc,\n sql,\n must(replicationSession),\n shard,\n replicaID,\n replicationSlotFailover && pgVersion >= PG_17,\n {\n // When backing up with litestream v5, a unique backup path is\n // required since the LTX format does not tolerate multiple writers\n // (and there are no v3 \"generations\" to workaround it).\n backupPath: backupV5 ? replicaID : null,\n backupV5,\n },\n );\n snapshot = slot.snapshot_name;\n lsn = slot.consistent_point;\n slotName = slot.slot_name;\n }\n\n const initialVersion = toStateVersionString(lsn);\n\n initReplicationState(tx, publications, initialVersion, context);\n\n // Run up to MAX_WORKERS to copy of tables at the replication slot's snapshot.\n // Retrieve the published schema at the consistent_point.\n const published = await runTx(\n sql,\n async tx => {\n await tx.unsafe(/* sql*/ `SET TRANSACTION SNAPSHOT '${snapshot}'`);\n return getPublicationInfo(tx, publications);\n },\n {mode: Mode.READONLY},\n );\n // Note: If this throws, initial-sync is aborted.\n validatePublications(lc, published);\n\n // Now that tables have been validated, kick off the copiers.\n const {tables, indexes} = published;\n const numTables = tables.length;\n if (platform() === 'win32' && tableCopyWorkers < numTables) {\n lc.warn?.(\n `Increasing the number of copy workers from ${tableCopyWorkers} to ` +\n `${numTables} to work around a Node/Postgres connection bug`,\n );\n }\n const numWorkers =\n platform() === 'win32'\n ? numTables\n : Math.min(tableCopyWorkers, numTables);\n\n const copyPool = await connectPgClient(\n lc,\n upstreamURI,\n 'initial-sync-copy-worker',\n {\n max: numWorkers,\n ['max_lifetime']: 120 * 60, // set a long (2h) limit for COPY streaming\n },\n );\n const copiers = startTableCopyWorkers(\n lc,\n copyPool,\n snapshot,\n numWorkers,\n numTables,\n );\n try {\n createLiteTables(tx, tables, initialVersion);\n const sampleRate = shadow?.sampleRate;\n const maxRowsPerTable = shadow?.maxRowsPerTable;\n const downloads = await Promise.all(\n tables.map(spec =>\n copiers.processReadTask((db, lc) =>\n getInitialDownloadState(lc, db, spec, shadow !== undefined),\n ),\n ),\n );\n statusPublisher.publish(\n lc,\n 'Initializing',\n `Copying ${numTables} upstream tables at version ${initialVersion}`,\n 5000,\n () => ({downloadStatus: downloads.map(({status}) => status)}),\n );\n\n void copyProfiler?.start();\n const copyStart = performance.now();\n const copyResults = await Promise.all(\n downloads.map(table =>\n copiers.processReadTask((db, lc) =>\n copy(\n lc,\n table,\n copyPool,\n db,\n tx,\n textCopy,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n ),\n ),\n ),\n );\n const copyElapsed = performance.now() - copyStart;\n void copyProfiler?.stopAndDispose(lc, 'initial-copy');\n copiers.setDone();\n\n const copySummary = initialSyncCopySummary(copyResults, copyElapsed);\n\n statusPublisher.publish(\n lc,\n 'Indexing',\n `Creating ${indexes.length} indexes`,\n 5000,\n );\n const indexStart = performance.now();\n createLiteIndices(lc, tx, indexes);\n const index = performance.now() - indexStart;\n lc.info?.(`Created indexes (${index.toFixed(3)} ms)`);\n\n if (slotName && replicaID) {\n await initReplica(sql, shard, replicaID, published, context);\n } else {\n assert(shadow, 'expected to be in shadow sync if there is no slotName');\n const rowsByTable = new Map<string, number>();\n for (let i = 0; i < downloads.length; i++) {\n rowsByTable.set(downloads[i].status.table, copyResults[i].rows);\n }\n verifyShadowReplica(lc, tx, published, rowsByTable);\n }\n\n const elapsed = performance.now() - start;\n const copyOtherMs = Math.max(0, elapsed - copySummary.flushMs - index);\n recordInitialSyncRunMetrics(\n {\n durationMs: elapsed,\n rows: copySummary.rows,\n copyBytes: copySummary.copyBytes,\n copyMs: copySummary.copyMs,\n copyOtherMs,\n flushMs: copySummary.flushMs,\n indexMs: index,\n },\n {\n result: 'success',\n syncMode,\n copyFormat,\n },\n );\n lc.info?.(\n `Synced ${copySummary.rows.toLocaleString()} rows of ${numTables} tables in ${publications} up to ${lsn} ` +\n `(flush: ${copySummary.flushMs.toFixed(3)}, index: ${index.toFixed(3)}, total: ${elapsed.toFixed(3)} ms)`,\n {\n syncMode,\n copyFormat,\n publications,\n lsn,\n ...copySummary,\n indexes: indexes.length,\n indexMs: index,\n copyOtherMs,\n totalMs: elapsed,\n },\n );\n\n return slotName && replicaID\n ? must(\n await getReplicaState(sql, shard, replicaID),\n `Missing replica with id ${replicaID}`,\n )\n : undefined; // shadow sync only\n } finally {\n // All meaningful errors are handled at the processReadTask() call site.\n void copyPool.end().catch(e => lc.warn?.(`Error closing copyPool`, e));\n }\n } catch (e) {\n recordInitialSyncRunMetrics(\n {durationMs: performance.now() - start},\n {\n result: 'error',\n syncMode,\n copyFormat,\n },\n );\n if (slotName && sql) {\n // If initial-sync did not succeed, make a best effort to drop the\n // orphaned replication slot to avoid running out of slots in\n // pathological cases that result in repeated failures.\n lc.warn?.(`dropping replication slot ${slotName}`, e);\n await sql`\n SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots\n WHERE slot_name = ${slotName};\n `.catch(e => lc.warn?.(`Unable to drop replication slot ${slotName}`, e));\n }\n throw await statusPublisher.publishAndThrowError(lc, 'Initializing', e);\n } finally {\n statusPublisher.stop();\n if (releaseShadowSnapshot) {\n await releaseShadowSnapshot().catch(e =>\n lc.warn?.(`Error releasing shadow snapshot`, e),\n );\n }\n if (replicationSession) {\n await replicationSession.end();\n }\n if (sql) {\n await sql.end();\n }\n }\n}\n\nexport type ShadowSyncOptions = {\n sampleRate: number;\n maxRowsPerTable: number;\n /**\n * Parent directory for the throwaway SQLite replica. Defaults to the OS\n * tmpdir. Primarily for tests that need to isolate the scratch directory.\n */\n parentDir?: string | undefined;\n};\n\n/**\n * Exercises the initial-sync code path against a sample of rows from every\n * published table, writing into a throwaway SQLite database that is deleted\n * when the run ends. Produces zero upstream mutations: no replication slot,\n * no `addReplica`, no `dropShard`, no status events.\n *\n * Intended to be invoked periodically so that if a customer ever needs a\n * full reset, we have recent confidence that `initialSync` still works.\n * The shard must already be initialized upstream.\n */\nexport async function shadowInitialSync(\n lc: LogContext,\n shard: ShardConfig,\n upstreamURI: string,\n shadow: ShadowSyncOptions,\n context: ServerContext,\n syncOptions?: Pick<InitialSyncOptions, 'textCopy'>,\n): Promise<void> {\n const dir = await mkdtemp(\n join(shadow.parentDir ?? tmpdir(), 'zero-shadow-sync-'),\n );\n const dbPath = join(dir, 'shadow-replica.db');\n const db = new Database(lc, dbPath);\n try {\n await initialSync(\n lc,\n shard,\n db,\n upstreamURI,\n {\n // Shadow sync copies small samples, so one worker is plenty —\n // no reason to burn additional upstream connections.\n tableCopyWorkers: 1,\n textCopy: syncOptions?.textCopy,\n shadow,\n },\n context,\n );\n } finally {\n try {\n db.close();\n } catch (e) {\n lc.warn?.(`Error closing shadow replica db`, e);\n }\n await rm(dir, {recursive: true, force: true}).catch(e =>\n lc.warn?.(`Error cleaning up shadow replica dir ${dir}`, e),\n );\n }\n}\n\nasync function checkUpstreamConfig(sql: PostgresDB) {\n const {walLevel, version} = (\n await sql<{walLevel: string; version: number}[]>`\n SELECT current_setting('wal_level') as \"walLevel\", \n current_setting('server_version_num') as \"version\";\n `\n )[0];\n\n if (walLevel !== 'logical') {\n throw new Error(\n `Postgres must be configured with \"wal_level = logical\" (currently: \"${walLevel})`,\n );\n }\n if (version < PG_15) {\n throw new Error(\n `Must be running Postgres 15 or higher (currently: \"${version}\")`,\n );\n }\n return version;\n}\n\nasync function ensurePublishedTables(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardConfig,\n validate = true,\n): Promise<{publications: string[]}> {\n const {database, host} = sql.options;\n lc.info?.(`Ensuring upstream PUBLICATION on ${database}@${host}`);\n\n // Technically, ensureShardSchema() is already called by\n // initializePostgresChangeSource, so the call here is redundant\n // but harmless. On the contrary, it makes initial sync more\n // self-contained (for test setup), and also plays a role in\n // recovering from corruption (below) after the shard is dropped.\n await ensureShardSchema(lc, sql, shard);\n const {publications} = await getInternalShardConfig(sql, shard);\n\n if (validate) {\n let valid = false;\n const nonInternalPublications = publications.filter(\n p => !p.startsWith('_'),\n );\n const exists = await sql`\n SELECT pubname FROM pg_publication WHERE pubname IN ${sql(publications)}\n `.values();\n if (exists.length !== publications.length) {\n lc.warn?.(\n `some configured publications [${publications}] are missing: ` +\n `[${exists.flat()}]. resyncing`,\n );\n } else if (\n !equals(new Set(shard.publications), new Set(nonInternalPublications))\n ) {\n lc.warn?.(\n `requested publications [${shard.publications}] differ from previous` +\n `publications [${nonInternalPublications}]. resyncing`,\n );\n } else {\n valid = true;\n }\n if (!valid) {\n await sql.unsafe(dropShard(shard.appID, shard.shardNum));\n return ensurePublishedTables(lc, sql, shard, false);\n }\n }\n return {publications};\n}\n\nfunction startTableCopyWorkers(\n lc: LogContext,\n db: PostgresDB,\n snapshot: string,\n numWorkers: number,\n numTables: number,\n): TransactionPool {\n const {init} = importSnapshot(snapshot);\n const tableCopiers = new TransactionPool(lc, {\n mode: Mode.READONLY,\n init,\n initialWorkers: numWorkers,\n });\n tableCopiers.run(db);\n\n lc.info?.(`Started ${numWorkers} workers to copy ${numTables} tables`);\n\n if (parseInt(process.versions.node) < 22) {\n lc.warn?.(\n `\\n\\n\\n` +\n `Older versions of Node have a bug that results in an unresponsive\\n` +\n `Postgres connection after running certain combinations of COPY commands.\\n` +\n `If initial sync hangs, run zero-cache with Node v22+. This has the additional\\n` +\n `benefit of being consistent with the Node version run in the production container image.` +\n `\\n\\n\\n`,\n );\n }\n return tableCopiers;\n}\n\n/**\n * Shadow-mode alternative to `createReplicationSlot`: opens a dedicated\n * READ ONLY REPEATABLE READ transaction on a normal connection, exports the\n * snapshot and captures the current WAL LSN, then holds the transaction\n * open until `release()` is called. The held transaction keeps the snapshot\n * importable by the table-copy workers for the duration of the COPY phase.\n *\n * Idle-in-transaction timeout is disabled locally so the exporter doesn't\n * get killed while workers are still importing.\n */\nasync function acquireExportedSnapshotForShadowSync(\n lc: LogContext,\n upstreamURI: string,\n): Promise<{\n snapshot: string;\n lsn: string;\n release: () => Promise<void>;\n}> {\n const holder = await connectPgClient(\n lc,\n upstreamURI,\n 'shadow-initial-sync-snapshot',\n {\n max: 1,\n },\n );\n const ready = resolver<{snapshot: string; lsn: string}>();\n const release = resolver<void>();\n const held = holder\n .begin(Mode.READONLY, async tx => {\n await tx`SET LOCAL idle_in_transaction_session_timeout = 0`.execute();\n const [row] = await tx<{snapshot: string; lsn: string}[]>`\n SELECT pg_export_snapshot() AS snapshot,\n pg_current_wal_lsn()::text AS lsn`;\n ready.resolve(row);\n await release.promise;\n })\n .catch(e => ready.reject(e));\n\n let snapshot: string;\n let lsn: string;\n try {\n ({snapshot, lsn} = await ready.promise);\n } catch (e) {\n await holder\n .end()\n .catch(err =>\n lc.warn?.(`Error ending shadow snapshot holder after failure`, err),\n );\n throw e;\n }\n lc.info?.(\n `Exported snapshot ${snapshot} at LSN ${lsn} (shadow initial sync)`,\n );\n return {\n snapshot,\n lsn,\n release: async () => {\n release.resolve();\n try {\n await held;\n } catch (e) {\n lc.warn?.(`snapshot holder transaction ended with error`, e);\n }\n await holder.end();\n },\n };\n}\n\nfunction createLiteTables(\n tx: Database,\n tables: PublishedTableSpec[],\n initialVersion: string,\n) {\n // TODO: Figure out how to reuse the ChangeProcessor here to avoid\n // duplicating the ColumnMetadata logic.\n const columnMetadata = must(ColumnMetadataStore.getInstance(tx));\n for (const t of tables) {\n tx.exec(createLiteTableStatement(mapPostgresToLite(t, initialVersion)));\n const tableName = liteTableName(t);\n for (const [colName, colSpec] of Object.entries(t.columns)) {\n columnMetadata.insert(tableName, colName, colSpec);\n }\n }\n}\n\nfunction createLiteIndices(lc: LogContext, tx: Database, indices: IndexSpec[]) {\n for (const [i, index] of indices.entries()) {\n const stmt = createLiteIndexStatement(mapPostgresToLiteIndex(index));\n lc.info?.(`Creating index ${i + 1}/${indices.length}: ${stmt}`);\n const start = performance.now();\n tx.exec(stmt);\n lc.info?.(\n `Created index ${i + 1}/${indices.length} ` +\n `(${(performance.now() - start).toFixed(3)} ms): ${stmt}`,\n );\n }\n}\n\n/**\n * Runs structural assertions over a just-synced replica and throws if any\n * fail. Only called in shadow mode — a successful return means the replica\n * is schema-complete, row-count consistent, and its column metadata is in\n * sync with its lite schema.\n *\n * Note: this intentionally does NOT verify ZQL-queryability. Tables that\n * `computeZqlSpecs` drops (no PK / no all-NOT-NULL unique index, unsupported\n * column types, etc.) are silently skipped in production too — there's\n * nothing shadow-specific about them, so failing here would diverge from\n * prod over an upstream-schema condition prod accepts.\n *\n * Exported for testing.\n */\nexport function verifyShadowReplica(\n lc: LogContext,\n db: Database,\n published: {tables: PublishedTableSpec[]; indexes: IndexSpec[]},\n rowsByTable: ReadonlyMap<string, number>,\n): void {\n const issues: string[] = [];\n let columnsChecked = 0;\n let rowsChecked = 0;\n\n // 1. Schema completeness: every published table exists in the replica\n // with at least the expected column set.\n const liteTables = listTables(db);\n const liteTableByName = new Map(liteTables.map(t => [t.name, t]));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const lite = liteTableByName.get(name);\n if (!lite) {\n issues.push(`missing table in replica: ${name}`);\n continue;\n }\n for (const col of Object.keys(pt.columns)) {\n columnsChecked++;\n if (!(col in lite.columns)) {\n issues.push(`column missing in replica table ${name}: ${col}`);\n }\n }\n }\n\n // Every published index exists in the replica.\n const liteIndexNames = new Set(listIndexes(db).map(i => i.name));\n for (const ix of published.indexes) {\n const mapped = mapPostgresToLiteIndex(ix);\n if (!liteIndexNames.has(mapped.name)) {\n issues.push(\n `missing index in replica: ${mapped.name} on ${mapped.tableName}`,\n );\n }\n }\n\n // 2. Row counts: SQLite COUNT(*) matches the in-memory copy counter.\n for (const [table, expected] of rowsByTable) {\n try {\n const [row] = db\n .prepare(`SELECT COUNT(*) as count FROM \"${table}\"`)\n .all<{count: number}>();\n if (row.count !== expected) {\n issues.push(\n `row count mismatch for table ${table}: ` +\n `copy counter reported ${expected}, replica has ${row.count}`,\n );\n } else {\n rowsChecked += row.count;\n }\n } catch (e) {\n issues.push(`could not count rows in table ${table}: ${String(e)}`);\n }\n }\n\n // 3. Column metadata: every published column has a _zero.column_metadata row.\n const meta = must(ColumnMetadataStore.getInstance(db));\n for (const pt of published.tables) {\n const name = liteTableName(pt);\n const rows = meta.getTable(name);\n for (const col of Object.keys(pt.columns)) {\n if (!rows.has(col)) {\n issues.push(`missing column_metadata row for ${name}.${col}`);\n }\n }\n }\n\n if (issues.length) {\n throw new Error(\n `Shadow replica verification failed (${issues.length} issue(s)):\\n` +\n issues.map(i => ` - ${i}`).join('\\n'),\n );\n }\n\n lc.info?.(\n `Shadow replica verification passed: ` +\n `${published.tables.length} tables, ` +\n `${published.indexes.length} indexes, ` +\n `${columnsChecked} columns, ` +\n `${rowsChecked.toLocaleString()} rows`,\n );\n}\n\n// Verified empirically that batches of 50 seem to be the sweet spot,\n// similar to the report in https://sqlite.org/forum/forumpost/8878a512d3652655\n//\n// Exported for testing.\nexport const INSERT_BATCH_SIZE = 50;\n\nconst MB = 1024 * 1024;\nconst MAX_BUFFERED_ROWS = 10_000;\nconst BUFFERED_SIZE_THRESHOLD = 8 * MB;\n\nexport type DownloadStatements = {\n select: string;\n getTotalRows: string;\n getTotalBytes: string;\n};\n\n/**\n * Produces ` TABLESAMPLE BERNOULLI(n)` when `sampleRate` is < 1, else `''`.\n * Row-level Bernoulli sampling is used (rather than SYSTEM) because it\n * produces a more uniform sample and, unlike SYSTEM, still returns rows\n * for small tables at low rates.\n */\nfunction tableSampleClause(sampleRate: number | undefined): string {\n if (sampleRate === undefined || sampleRate >= 1) {\n return '';\n }\n // Round away float noise (e.g. 0.3 * 100 = 30.000000000000004) while still\n // preserving sub-integer rates like 0.001 (= 0.1%).\n const pct = parseFloat((sampleRate * 100).toFixed(6));\n return /*sql*/ ` TABLESAMPLE BERNOULLI(${pct})`;\n}\n\nfunction limitClause(maxRowsPerTable: number | undefined): string {\n return maxRowsPerTable !== undefined\n ? /*sql*/ ` LIMIT ${maxRowsPerTable}`\n : '';\n}\n\n/**\n * Returns the SELECT column expressions for binary COPY, casting columns\n * without a known binary decoder to `::text`.\n */\nexport function makeBinarySelectExprs(\n table: PublishedTableSpec,\n cols: string[],\n): string[] {\n return cols.map(col => {\n const spec = table.columns[col];\n return hasBinaryDecoder(spec) ? id(col) : `${id(col)}::text`;\n });\n}\n\nexport function makeDownloadStatements(\n table: PublishedTableSpec,\n cols: string[],\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n selectExprs?: string[] | undefined,\n): DownloadStatements {\n const filterConditions = Object.values(table.publications)\n .map(({rowFilter}) => rowFilter)\n .filter(f => !!f); // remove nulls\n const where =\n filterConditions.length === 0\n ? ''\n : /*sql*/ `WHERE ${filterConditions.join(' OR ')}`;\n const sample = tableSampleClause(sampleRate);\n const limit = limitClause(maxRowsPerTable);\n const fromTable = /*sql*/ `FROM ${id(table.schema)}.${id(table.name)}${sample} ${where}`;\n const select = /*sql*/ `SELECT ${(selectExprs ?? cols.map(id)).join(',')} ${fromTable}${limit}`;\n if (limit) {\n // With LIMIT, wrap counts/sums in a subquery so they reflect the\n // capped rowset rather than the full (sampled) table.\n const bytesExpr = cols\n .map(col => `COALESCE(pg_column_size(${id(col)}), 0)`)\n .join(' + ');\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*)::bigint AS \"totalRows\" FROM (SELECT 1 AS _ ${fromTable}${limit}) s`,\n getTotalBytes: /*sql*/ `SELECT COALESCE(SUM(b), 0)::bigint AS \"totalBytes\" FROM (SELECT (${bytesExpr}) AS b ${fromTable}${limit}) s`,\n };\n }\n const totalBytes = `(${cols.map(col => `SUM(COALESCE(pg_column_size(${id(col)}), 0))`).join(' + ')})`;\n return {\n select,\n getTotalRows: /*sql*/ `SELECT COUNT(*) AS \"totalRows\" ${fromTable}`,\n getTotalBytes: /*sql*/ `SELECT ${totalBytes} AS \"totalBytes\" ${fromTable}`,\n };\n}\n\ntype DownloadState = {\n spec: PublishedTableSpec;\n status: DownloadStatus;\n};\n\ntype CopyFormat = 'binary' | 'text';\ntype InitialSyncMode = 'initial' | 'shadow';\n\ntype InitialSyncMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n};\n\ntype InitialSyncRunMetricAttrs = {\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n result: 'success' | 'error';\n};\n\ntype CopyResult = {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n columnCount: number;\n rows: number;\n estimatedRows: number;\n estimatedBytes: number | undefined;\n flushMs: number;\n elapsedMs: number;\n sourceWaitMs: number;\n processingMs: number;\n copyBytes: number;\n};\n\nconst INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\nexport const COPY_METRIC_BATCH_BYTES = 8 * 1024 * 1024;\nconst SLOW_COPY_FLUSH_MS = 10_000;\n\n// change-streamer imports this module before startOtelAuto() runs, so create\n// instruments lazily to avoid binding them to OTel's no-op meter provider.\nfunction initialSyncRuns() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_runs',\n 'Initial sync runs, labeled by result.',\n );\n}\n\nfunction initialSyncDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_duration',\n 'Wall-clock duration of an initial sync run, labeled by result.',\n );\n}\n\nfunction initialSyncCopyDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_duration',\n 'Wall-clock duration of the COPY phase for a successful initial sync run.',\n );\n}\n\nfunction initialSyncCopyOtherDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_copy_other_duration',\n 'Initial sync total duration excluding SQLite flush and index time for a successful run.',\n );\n}\n\nfunction initialSyncFlushDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_flush_duration',\n 'Total SQLite flush time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncIndexDuration() {\n return initialSyncDurationHistogram(\n 'initial_sync_index_duration',\n 'SQLite index creation time for a successful initial sync run.',\n );\n}\n\nfunction initialSyncRows() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_rows',\n 'Rows copied during successful initial sync runs.',\n );\n}\n\nfunction initialSyncCopyStream() {\n return getOrCreateCounter('replication', 'initial_sync_copy_stream', {\n description:\n 'PostgreSQL COPY stream bytes processed during initial sync, including in-progress and failed runs.',\n unit: 'bytes',\n });\n}\n\nfunction initialSyncCompletedCopyStream() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_completed_copy_stream',\n {\n description:\n 'PostgreSQL COPY stream bytes processed during successful initial sync runs.',\n unit: 'bytes',\n },\n );\n}\n\nfunction initialSyncCopyChunks() {\n return getOrCreateCounter(\n 'replication',\n 'initial_sync_copy_chunks',\n 'PostgreSQL COPY stream chunks processed during initial sync.',\n );\n}\n\nexport function createCopyMetricBatcher(\n record: (bytes: number, chunks: number) => void,\n) {\n let bytes = 0;\n let chunks = 0;\n\n function flush() {\n if (chunks === 0) {\n return;\n }\n record(bytes, chunks);\n bytes = 0;\n chunks = 0;\n }\n\n return {\n add(chunkBytes: number) {\n bytes += chunkBytes;\n chunks++;\n if (bytes >= COPY_METRIC_BATCH_BYTES) {\n flush();\n }\n },\n flush,\n };\n}\n\nexport function initialSyncCopyMetrics(attrs: InitialSyncMetricAttrs) {\n const labels = initialSyncMetricAttrs(attrs);\n const copyStreamMetric = initialSyncCopyStream();\n const copyChunksMetric = initialSyncCopyChunks();\n return createCopyMetricBatcher((bytes, chunks) => {\n copyStreamMetric.add(bytes, labels);\n copyChunksMetric.add(chunks, labels);\n });\n}\n\nfunction initialSyncDurationHistogram(name: string, description: string) {\n return getOrCreateHistogram('replication', name, {\n description,\n unit: 's',\n bucketBoundaries: INITIAL_SYNC_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nfunction initialSyncMetricAttrs(attrs: InitialSyncMetricAttrs) {\n return {\n sync_mode: attrs.syncMode,\n copy_format: attrs.copyFormat,\n };\n}\n\nfunction initialSyncRunMetricAttrs(attrs: InitialSyncRunMetricAttrs) {\n return {\n ...initialSyncMetricAttrs(attrs),\n result: attrs.result,\n };\n}\n\nfunction recordInitialSyncRunMetrics(\n stats: {\n durationMs: number;\n rows?: number | undefined;\n copyBytes?: number | undefined;\n copyMs?: number | undefined;\n copyOtherMs?: number | undefined;\n flushMs?: number | undefined;\n indexMs?: number | undefined;\n },\n attrs: InitialSyncRunMetricAttrs,\n) {\n const labels = initialSyncRunMetricAttrs(attrs);\n initialSyncRuns().add(1, labels);\n initialSyncDuration().recordMs(stats.durationMs, labels);\n if (attrs.result === 'success') {\n if (stats.copyMs !== undefined) {\n initialSyncCopyDuration().recordMs(stats.copyMs, labels);\n }\n if (stats.copyOtherMs !== undefined) {\n initialSyncCopyOtherDuration().recordMs(stats.copyOtherMs, labels);\n }\n if (stats.flushMs !== undefined) {\n initialSyncFlushDuration().recordMs(stats.flushMs, labels);\n }\n if (stats.indexMs !== undefined) {\n initialSyncIndexDuration().recordMs(stats.indexMs, labels);\n }\n if (stats.rows !== undefined && stats.rows > 0) {\n initialSyncRows().add(stats.rows, labels);\n }\n if (stats.copyBytes !== undefined && stats.copyBytes > 0) {\n initialSyncCompletedCopyStream().add(stats.copyBytes, labels);\n }\n }\n}\n\nfunction initialSyncCopySummary(\n results: readonly CopyResult[],\n copyMs: number,\n) {\n const totals = results.reduce(\n (acc, curr) => {\n acc.rows += curr.rows;\n acc.flushMs += curr.flushMs;\n acc.copyBytes += curr.copyBytes;\n return acc;\n },\n {\n rows: 0,\n flushMs: 0,\n copyBytes: 0,\n },\n );\n return {\n tables: results.length,\n rows: totals.rows,\n copyMs,\n flushMs: totals.flushMs,\n copyBytes: totals.copyBytes,\n };\n}\n\nfunction logSlowCopyFlush(\n lc: LogContext,\n details: {\n schema: string;\n table: string;\n replicaTable: string;\n syncMode: InitialSyncMode;\n copyFormat: CopyFormat;\n elapsedMs: number;\n flushedRows: number;\n flushedBytes: number;\n rows: number;\n copyBytes: number;\n },\n) {\n if (details.elapsedMs < SLOW_COPY_FLUSH_MS || details.flushedRows === 0) {\n return;\n }\n lc.info?.('initial-sync table copy slow flush', details);\n}\n\n// Exported for testing.\nexport async function getInitialDownloadState(\n lc: LogContext,\n sql: PostgresDB,\n spec: PublishedTableSpec,\n skipTotals: boolean,\n): Promise<DownloadState> {\n const start = performance.now();\n const table = liteTableName(spec);\n const columns = Object.keys(spec.columns);\n if (skipTotals) {\n // Shadow sync suppresses status events, so the pg_class\n // estimates would be computed and thrown away.\n return {\n spec,\n status: {table, columns, rows: 0, totalRows: 0, totalBytes: 0},\n };\n }\n // Use pg_class estimates instead of expensive COUNT(*) and\n // SUM(pg_column_size(...)) full table scans. The exact values are only\n // used for progress reporting, so estimates are sufficient.\n const qualifiedName = `${id(spec.schema)}.${id(spec.name)}`;\n const estimateResult = await sql<\n {totalRows: number; totalBytes: number}[]\n >`SELECT GREATEST(reltuples, 0)::float8 AS \"totalRows\",\n pg_table_size(oid)::float8 AS \"totalBytes\"\n FROM pg_class\n WHERE oid = ${qualifiedName}::regclass`;\n\n const {totalRows, totalBytes} = estimateResult[0] ?? {\n totalRows: 0,\n totalBytes: 0,\n };\n\n const state: DownloadState = {\n spec,\n status: {\n table,\n columns,\n rows: 0,\n totalRows,\n totalBytes,\n },\n };\n const elapsed = (performance.now() - start).toFixed(3);\n lc.info?.(`Computed initial download state for ${table} (${elapsed} ms)`, {\n state: state.status,\n });\n return state;\n}\n\nfunction copy(\n lc: LogContext,\n {spec: table, status}: DownloadState,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n textCopy: boolean,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n if (textCopy) {\n return copyText(\n lc,\n table,\n status,\n dbClient,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n }\n return copyBinary(\n lc,\n table,\n status,\n from,\n to,\n syncMode,\n sampleRate,\n maxRowsPerTable,\n );\n}\n\nasync function copyBinary(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'binary';\n const copyMetrics = initialSyncCopyMetrics({syncMode, copyFormat});\n let flushMs = 0;\n let copyBytes = 0;\n let processingMs = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n // Build SELECT with ::text casts for columns without a known binary decoder.\n const select = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n makeBinarySelectExprs(table, columnNames),\n ).select;\n\n const decoders = orderedColumns.map(([, spec]) =>\n hasBinaryDecoder(spec) ? makeBinaryDecoder(spec) : textCastDecoder,\n );\n\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n const binaryParser = new BinaryCopyParser();\n let col = 0;\n\n lc.info?.(`Starting binary copy stream of ${tableName}:`, select);\n\n const copyStreamStart = performance.now();\n await pipeline(\n await from\n .unsafe(`COPY (${select}) TO STDOUT WITH (FORMAT binary)`)\n .readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n const processingStart = performance.now();\n try {\n copyBytes += chunk.length;\n copyMetrics.add(chunk.length);\n for (const fieldBuf of binaryParser.parse(chunk)) {\n const fieldSize = fieldBuf === null ? 4 : fieldBuf.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n fieldBuf === null ? null : decoders[col](fieldBuf);\n\n if (++col === decoders.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n const processingStart = performance.now();\n try {\n copyMetrics.flush();\n flush();\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n destroy(error, callback) {\n copyMetrics.flush();\n callback(error);\n },\n }),\n );\n\n const sourceWaitMs = Math.max(\n 0,\n performance.now() - copyStreamStart - processingMs,\n );\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n sourceWaitMs,\n processingMs,\n copyBytes,\n } satisfies CopyResult;\n\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n\nasync function copyText(\n lc: LogContext,\n table: PublishedTableSpec,\n status: DownloadStatus,\n dbClient: PostgresDB,\n from: PostgresTransaction,\n to: Database,\n syncMode: InitialSyncMode,\n sampleRate?: number | undefined,\n maxRowsPerTable?: number | undefined,\n): Promise<CopyResult> {\n const start = performance.now();\n const copyFormat: CopyFormat = 'text';\n const copyMetrics = initialSyncCopyMetrics({syncMode, copyFormat});\n let flushMs = 0;\n let copyBytes = 0;\n let processingMs = 0;\n\n const tableName = liteTableName(table);\n const orderedColumns = Object.entries(table.columns);\n\n const columnNames = orderedColumns.map(([c]) => c);\n const columnSpecs = orderedColumns.map(([_name, spec]) => spec);\n const insertColumnList = columnNames.map(c => id(c)).join(',');\n\n const valuesSql =\n columnNames.length > 0 ? `(${'?,'.repeat(columnNames.length - 1)}?)` : '()';\n const insertSql = /*sql*/ `\n INSERT INTO \"${tableName}\" (${insertColumnList}) VALUES ${valuesSql}`;\n const insertStmt = to.prepare(insertSql);\n const insertBatchStmt = to.prepare(\n insertSql + `,${valuesSql}`.repeat(INSERT_BATCH_SIZE - 1),\n );\n\n const {select} = makeDownloadStatements(\n table,\n columnNames,\n sampleRate,\n maxRowsPerTable,\n );\n const valuesPerRow = columnSpecs.length;\n const valuesPerBatch = valuesPerRow * INSERT_BATCH_SIZE;\n\n const pendingValues: LiteValueType[] = Array.from({\n length: MAX_BUFFERED_ROWS * valuesPerRow,\n });\n let pendingRows = 0;\n let pendingSize = 0;\n\n function flush() {\n const flushStart = performance.now();\n const flushedRows = pendingRows;\n const flushedSize = pendingSize;\n\n let l = 0;\n for (; pendingRows > INSERT_BATCH_SIZE; pendingRows -= INSERT_BATCH_SIZE) {\n insertBatchStmt.run(pendingValues.slice(l, (l += valuesPerBatch)));\n }\n for (; pendingRows > 0; pendingRows--) {\n insertStmt.run(pendingValues.slice(l, (l += valuesPerRow)));\n }\n const flushedValues = flushedRows * valuesPerRow;\n for (let i = 0; i < flushedValues; i++) {\n pendingValues[i] = undefined as unknown as LiteValueType;\n }\n pendingSize = 0;\n status.rows += flushedRows;\n\n const elapsed = performance.now() - flushStart;\n flushMs += elapsed;\n lc.debug?.(\n `flushed ${flushedRows} ${tableName} rows (${flushedSize} bytes) in ${elapsed.toFixed(3)} ms`,\n );\n logSlowCopyFlush(lc, {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n elapsedMs: elapsed,\n flushedRows,\n flushedBytes: flushedSize,\n rows: status.rows,\n copyBytes,\n });\n }\n\n lc.info?.(`Starting text copy stream of ${tableName}:`, select);\n const pgParsers = await getTypeParsers(dbClient, {returnJsonAsString: true});\n const parsers = columnSpecs.map(c => {\n const pgParse = pgParsers.getTypeParser(c.typeOID);\n return (val: string) =>\n liteValue(\n pgParse(val) as PostgresValueType,\n c.dataType,\n JSON_STRINGIFIED,\n );\n });\n\n const tsvParser = new TsvParser();\n let col = 0;\n\n const copyStreamStart = performance.now();\n await pipeline(\n await from.unsafe(`COPY (${select}) TO STDOUT`).readable(),\n new Writable({\n highWaterMark: BUFFERED_SIZE_THRESHOLD,\n\n write(\n chunk: Buffer,\n _encoding: string,\n callback: (error?: Error) => void,\n ) {\n const processingStart = performance.now();\n try {\n copyBytes += chunk.length;\n copyMetrics.add(chunk.length);\n for (const text of tsvParser.parse(chunk)) {\n const fieldSize = text === null ? 4 : text.length;\n pendingSize += fieldSize;\n pendingValues[pendingRows * valuesPerRow + col] =\n text === null ? null : parsers[col](text);\n\n if (++col === parsers.length) {\n col = 0;\n if (\n ++pendingRows >= MAX_BUFFERED_ROWS - valuesPerRow ||\n pendingSize >= BUFFERED_SIZE_THRESHOLD\n ) {\n flush();\n }\n }\n }\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n const processingStart = performance.now();\n try {\n copyMetrics.flush();\n flush();\n processingMs += performance.now() - processingStart;\n callback();\n } catch (e) {\n processingMs += performance.now() - processingStart;\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n destroy(error, callback) {\n copyMetrics.flush();\n callback(error);\n },\n }),\n );\n\n const sourceWaitMs = Math.max(\n 0,\n performance.now() - copyStreamStart - processingMs,\n );\n const elapsed = performance.now() - start;\n const result = {\n schema: table.schema,\n table: table.name,\n replicaTable: tableName,\n syncMode,\n copyFormat,\n columnCount: columnNames.length,\n rows: status.rows,\n estimatedRows: status.totalRows,\n estimatedBytes: status.totalBytes,\n flushMs,\n elapsedMs: elapsed,\n sourceWaitMs,\n processingMs,\n copyBytes,\n } satisfies CopyResult;\n lc.info?.(\n `Finished copying ${status.rows} rows into ${tableName} ` +\n `(flush: ${flushMs.toFixed(3)} ms) (total: ${elapsed.toFixed(3)} ms) `,\n result,\n );\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ export type StreamMessage = [
|
|
|
12
12
|
type SourceWithPendingQueue<T> = Source<T> & {
|
|
13
13
|
queued: number;
|
|
14
14
|
};
|
|
15
|
-
export declare function subscribe(lc: LogContext, db: PostgresDB, slot: string, publications: string[], lsn: bigint, retriesIfReplicationSlotActive?: number, applicationName?: string): Promise<{
|
|
15
|
+
export declare function subscribe(lc: LogContext, db: PostgresDB, slot: string, publications: string[], lsn: bigint, retriesIfReplicationSlotActive?: number, applicationName?: string, inboundTimeoutOverrideMs?: number | undefined): Promise<{
|
|
16
16
|
messages: SourceWithPendingQueue<StreamMessage>;
|
|
17
17
|
acks: Sink<bigint>;
|
|
18
18
|
}>;
|
|
@@ -29,13 +29,56 @@ export declare function subscribe(lc: LogContext, db: PostgresDB, slot: string,
|
|
|
29
29
|
* first tick and destroy the stream, while the timer itself would busy-spin at
|
|
30
30
|
* a 0ms interval — together producing a continuous reconnect storm.
|
|
31
31
|
*
|
|
32
|
+
* An `inboundTimeoutOverrideMs` (when positive) replaces the default
|
|
33
|
+
* `2x wal_sender_timeout` inbound threshold. The two timeouts protect
|
|
34
|
+
* different parties with asymmetric false-positive costs: `wal_sender_timeout`
|
|
35
|
+
* is the server reaping dead clients (a false positive there costs a cheap
|
|
36
|
+
* reconnect), while the inbound watchdog tears down the stream and replays the
|
|
37
|
+
* in-flight transaction — expensive, and self-defeating when the wal sender is
|
|
38
|
+
* legitimately silent for long stretches (e.g. decoding through WAL from
|
|
39
|
+
* unpublished tables, or assembling a large transaction that pgoutput only
|
|
40
|
+
* sends at commit). Operators who don't own the server setting (e.g. managed
|
|
41
|
+
* or operator-provisioned Postgres with an aggressive cluster-wide
|
|
42
|
+
* `wal_sender_timeout`) can widen the client-side fuse independently.
|
|
43
|
+
*
|
|
44
|
+
* The override has no effect when `wal_sender_timeout` is disabled (0): the
|
|
45
|
+
* server then sends no periodic keepalives, so inbound silence is normal even
|
|
46
|
+
* on an idle healthy connection and any silence-based watchdog would
|
|
47
|
+
* false-positive.
|
|
48
|
+
*
|
|
32
49
|
* https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT
|
|
33
50
|
*/
|
|
34
|
-
export declare function computeLivenessTimings(walSenderTimeoutMs: number): {
|
|
51
|
+
export declare function computeLivenessTimings(walSenderTimeoutMs: number, inboundTimeoutOverrideMs?: number | undefined): {
|
|
35
52
|
enabled: boolean;
|
|
36
53
|
manualKeepaliveTimeout: number;
|
|
37
54
|
inboundTimeoutMs: number;
|
|
38
55
|
timerIntervalMs: number;
|
|
39
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Pure decision for the inbound-liveness watchdog, given the time since the
|
|
59
|
+
* last inbound message, the timeout threshold, and the number of messages
|
|
60
|
+
* currently queued downstream (see {@link SourceWithPendingQueue.queued}).
|
|
61
|
+
*
|
|
62
|
+
* A non-empty downstream queue means we have received data we haven't yet
|
|
63
|
+
* consumed: the reason we aren't receiving *more* is our own back-pressure, not
|
|
64
|
+
* upstream silence. In that case we `resetWindow` (treat the backlog as
|
|
65
|
+
* liveness evidence) rather than counting the stall against the connection.
|
|
66
|
+
* This both suppresses false timeouts during back-pressure and — crucially —
|
|
67
|
+
* keeps `lastReceivedTime` fresh so the drain transition (queue emptying after
|
|
68
|
+
* a long stall) doesn't fire the watchdog against a stale timestamp on the very
|
|
69
|
+
* next tick.
|
|
70
|
+
*
|
|
71
|
+
* Only when the queue is empty (consumption has caught up, so reading — and
|
|
72
|
+
* thus keepalives — should be flowing again) does an over-threshold gap
|
|
73
|
+
* indicate a genuinely dead inbound half.
|
|
74
|
+
*/
|
|
75
|
+
export declare function evaluateInboundLiveness({ sinceLastReceived, inboundTimeoutMs, queued, }: {
|
|
76
|
+
sinceLastReceived: number;
|
|
77
|
+
inboundTimeoutMs: number;
|
|
78
|
+
queued: number;
|
|
79
|
+
}): {
|
|
80
|
+
resetWindow: boolean;
|
|
81
|
+
timedOut: boolean;
|
|
82
|
+
};
|
|
40
83
|
export {};
|
|
41
84
|
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/logical-replication/stream.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAKjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAO,KAAK,IAAI,EAAE,KAAK,MAAM,EAAC,MAAM,8BAA8B,CAAC;AAK1E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,qBAAqB,CAAC;AAIjD,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM;IACX,OAAO,GAAG;QAAC,GAAG,EAAE,WAAW,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAC;CACrD,CAAC;AAKF,KAAK,sBAAsB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,SAAS,CAC7B,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,EACtB,GAAG,EAAE,MAAM,EACX,8BAA8B,SAA6C,EAC3E,eAAe,SAAoB,
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/logical-replication/stream.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAKjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAExD,OAAO,EAAO,KAAK,IAAI,EAAE,KAAK,MAAM,EAAC,MAAM,8BAA8B,CAAC;AAK1E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,qBAAqB,CAAC;AAIjD,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM;IACX,OAAO,GAAG;QAAC,GAAG,EAAE,WAAW,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAC;CACrD,CAAC;AAKF,KAAK,sBAAsB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,SAAS,CAC7B,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,EACtB,GAAG,EAAE,MAAM,EACX,8BAA8B,SAA6C,EAC3E,eAAe,SAAoB,EACnC,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5C,OAAO,CAAC;IACT,QAAQ,EAAE,sBAAsB,CAAC,aAAa,CAAC,CAAC;IAChD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;CACpB,CAAC,CAyJD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,kBAAkB,EAAE,MAAM,EAC1B,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5C;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CA6BA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,GACP,EAAE;IACD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAC,CAK5C"}
|
|
@@ -2,9 +2,9 @@ import { sleep } from "../../../../../../shared/src/sleep.js";
|
|
|
2
2
|
import { id, lit } from "../../../../types/sql.js";
|
|
3
3
|
import "../../../../types/pg.js";
|
|
4
4
|
import { fromBigInt } from "../lsn.js";
|
|
5
|
-
import { AutoResetSignal } from "../../../change-streamer/schema/tables.js";
|
|
6
5
|
import { Subscription } from "../../../../types/subscription.js";
|
|
7
6
|
import { pipe } from "../../../../types/streams.js";
|
|
7
|
+
import { AutoResetSignal } from "../../../change-streamer/schema/tables.js";
|
|
8
8
|
import { getTypeParsers } from "../../../../db/pg-type-parser.js";
|
|
9
9
|
import { PgoutputParser } from "./pgoutput-parser.js";
|
|
10
10
|
import { defu } from "defu";
|
|
@@ -12,7 +12,7 @@ import postgres from "postgres";
|
|
|
12
12
|
import { PG_ADMIN_SHUTDOWN, PG_OBJECT_IN_USE, PG_OBJECT_NOT_IN_PREREQUISITE_STATE } from "@drdgvhbh/postgres-error-codes";
|
|
13
13
|
//#region ../zero-cache/src/services/change-source/pg/logical-replication/stream.ts
|
|
14
14
|
var DEFAULT_RETRIES_IF_REPLICATION_SLOT_ACTIVE = 5;
|
|
15
|
-
async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSlotActive = DEFAULT_RETRIES_IF_REPLICATION_SLOT_ACTIVE, applicationName = "zero-replicator") {
|
|
15
|
+
async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSlotActive = DEFAULT_RETRIES_IF_REPLICATION_SLOT_ACTIVE, applicationName = "zero-replicator", inboundTimeoutOverrideMs) {
|
|
16
16
|
const session = postgres(defu({
|
|
17
17
|
max: 1,
|
|
18
18
|
["fetch_types"]: false,
|
|
@@ -26,8 +26,8 @@ async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSl
|
|
|
26
26
|
const [{ walSenderTimeoutMs }] = await session`SELECT EXTRACT(EPOCH FROM (setting || unit)::interval) * 1000
|
|
27
27
|
AS "walSenderTimeoutMs" FROM pg_settings
|
|
28
28
|
WHERE name = 'wal_sender_timeout'`.simple();
|
|
29
|
-
const { enabled: livenessEnabled, manualKeepaliveTimeout, inboundTimeoutMs, timerIntervalMs } = computeLivenessTimings(walSenderTimeoutMs);
|
|
30
|
-
lc.info?.(livenessEnabled ? `wal_sender_timeout: ${walSenderTimeoutMs}ms. Ensuring manual keepalives at least every ${manualKeepaliveTimeout}ms` : "wal_sender_timeout is disabled (0); skipping manual keepalives and inbound liveness detection.");
|
|
29
|
+
const { enabled: livenessEnabled, manualKeepaliveTimeout, inboundTimeoutMs, timerIntervalMs } = computeLivenessTimings(walSenderTimeoutMs, inboundTimeoutOverrideMs);
|
|
30
|
+
lc.info?.(livenessEnabled ? `wal_sender_timeout: ${walSenderTimeoutMs}ms. Ensuring manual keepalives at least every ${manualKeepaliveTimeout}ms. Inbound timeout: ${inboundTimeoutMs}ms` + (inboundTimeoutMs === walSenderTimeoutMs * 2 ? "" : " (configured override)") : "wal_sender_timeout is disabled (0); skipping manual keepalives and inbound liveness detection.");
|
|
31
31
|
const [readable, writable] = await startReplicationStream(lc, session, slot, publications, lsn, retriesIfReplicationSlotActive + 1);
|
|
32
32
|
let lastAckTime = Date.now();
|
|
33
33
|
function sendAck(lsn) {
|
|
@@ -42,8 +42,14 @@ async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSl
|
|
|
42
42
|
lc.debug?.(`sent manual keepalive`);
|
|
43
43
|
}
|
|
44
44
|
const sinceLastReceived = now - lastReceivedTime;
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const { resetWindow, timedOut } = evaluateInboundLiveness({
|
|
46
|
+
sinceLastReceived,
|
|
47
|
+
inboundTimeoutMs,
|
|
48
|
+
queued: messages.queued
|
|
49
|
+
});
|
|
50
|
+
if (resetWindow) lastReceivedTime = now;
|
|
51
|
+
else if (timedOut && !readable.destroyed) {
|
|
52
|
+
lc.warn?.(`no message received from ${db.options.host} in ${sinceLastReceived}ms (> inbound timeout ${inboundTimeoutMs}ms). Destroying replication stream to force reconnect.`);
|
|
47
53
|
readable.destroy(/* @__PURE__ */ new Error(`replication stream inbound timeout after ${sinceLastReceived}ms`));
|
|
48
54
|
}
|
|
49
55
|
}, timerIntervalMs) : void 0;
|
|
@@ -84,9 +90,26 @@ async function subscribe(lc, db, slot, publications, lsn, retriesIfReplicationSl
|
|
|
84
90
|
* first tick and destroy the stream, while the timer itself would busy-spin at
|
|
85
91
|
* a 0ms interval — together producing a continuous reconnect storm.
|
|
86
92
|
*
|
|
93
|
+
* An `inboundTimeoutOverrideMs` (when positive) replaces the default
|
|
94
|
+
* `2x wal_sender_timeout` inbound threshold. The two timeouts protect
|
|
95
|
+
* different parties with asymmetric false-positive costs: `wal_sender_timeout`
|
|
96
|
+
* is the server reaping dead clients (a false positive there costs a cheap
|
|
97
|
+
* reconnect), while the inbound watchdog tears down the stream and replays the
|
|
98
|
+
* in-flight transaction — expensive, and self-defeating when the wal sender is
|
|
99
|
+
* legitimately silent for long stretches (e.g. decoding through WAL from
|
|
100
|
+
* unpublished tables, or assembling a large transaction that pgoutput only
|
|
101
|
+
* sends at commit). Operators who don't own the server setting (e.g. managed
|
|
102
|
+
* or operator-provisioned Postgres with an aggressive cluster-wide
|
|
103
|
+
* `wal_sender_timeout`) can widen the client-side fuse independently.
|
|
104
|
+
*
|
|
105
|
+
* The override has no effect when `wal_sender_timeout` is disabled (0): the
|
|
106
|
+
* server then sends no periodic keepalives, so inbound silence is normal even
|
|
107
|
+
* on an idle healthy connection and any silence-based watchdog would
|
|
108
|
+
* false-positive.
|
|
109
|
+
*
|
|
87
110
|
* https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT
|
|
88
111
|
*/
|
|
89
|
-
function computeLivenessTimings(walSenderTimeoutMs) {
|
|
112
|
+
function computeLivenessTimings(walSenderTimeoutMs, inboundTimeoutOverrideMs) {
|
|
90
113
|
if (!(walSenderTimeoutMs > 0)) return {
|
|
91
114
|
enabled: false,
|
|
92
115
|
manualKeepaliveTimeout: 0,
|
|
@@ -97,11 +120,39 @@ function computeLivenessTimings(walSenderTimeoutMs) {
|
|
|
97
120
|
return {
|
|
98
121
|
enabled: true,
|
|
99
122
|
manualKeepaliveTimeout,
|
|
100
|
-
inboundTimeoutMs: walSenderTimeoutMs * 2,
|
|
123
|
+
inboundTimeoutMs: inboundTimeoutOverrideMs !== void 0 && inboundTimeoutOverrideMs > 0 ? inboundTimeoutOverrideMs : walSenderTimeoutMs * 2,
|
|
101
124
|
timerIntervalMs: manualKeepaliveTimeout / 5
|
|
102
125
|
};
|
|
103
126
|
}
|
|
104
127
|
/**
|
|
128
|
+
* Pure decision for the inbound-liveness watchdog, given the time since the
|
|
129
|
+
* last inbound message, the timeout threshold, and the number of messages
|
|
130
|
+
* currently queued downstream (see {@link SourceWithPendingQueue.queued}).
|
|
131
|
+
*
|
|
132
|
+
* A non-empty downstream queue means we have received data we haven't yet
|
|
133
|
+
* consumed: the reason we aren't receiving *more* is our own back-pressure, not
|
|
134
|
+
* upstream silence. In that case we `resetWindow` (treat the backlog as
|
|
135
|
+
* liveness evidence) rather than counting the stall against the connection.
|
|
136
|
+
* This both suppresses false timeouts during back-pressure and — crucially —
|
|
137
|
+
* keeps `lastReceivedTime` fresh so the drain transition (queue emptying after
|
|
138
|
+
* a long stall) doesn't fire the watchdog against a stale timestamp on the very
|
|
139
|
+
* next tick.
|
|
140
|
+
*
|
|
141
|
+
* Only when the queue is empty (consumption has caught up, so reading — and
|
|
142
|
+
* thus keepalives — should be flowing again) does an over-threshold gap
|
|
143
|
+
* indicate a genuinely dead inbound half.
|
|
144
|
+
*/
|
|
145
|
+
function evaluateInboundLiveness({ sinceLastReceived, inboundTimeoutMs, queued }) {
|
|
146
|
+
if (queued > 0) return {
|
|
147
|
+
resetWindow: true,
|
|
148
|
+
timedOut: false
|
|
149
|
+
};
|
|
150
|
+
return {
|
|
151
|
+
resetWindow: false,
|
|
152
|
+
timedOut: sinceLastReceived > inboundTimeoutMs
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
105
156
|
* Formats publication names for the START_REPLICATION command.
|
|
106
157
|
* The replication protocol expects format: publication_names 'pub1,pub2'
|
|
107
158
|
* Each name is escaped to handle any quotes that may have passed validation.
|