@rocicorp/zero 1.9.1-canary.1 → 1.10.0-canary.1
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/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- 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/zero/package.js +2 -2
- 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 +43 -1
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +139 -20
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/db/migration.js +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +5 -0
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +15 -1
- 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 +90 -30
- 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 +5 -15
- 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 +46 -3
- 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/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 +16 -7
- 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 +75 -23
- 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 +34 -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.js +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 +1 -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 +1 -12
- 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 +3 -3
- 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-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 +15 -20
- 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 +330 -63
- 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 +55 -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 +22 -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 +93 -117
- 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 +3 -2
- 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 +50 -140
- 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/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/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.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +44 -17
- 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 +11 -4
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +2 -2
- package/out/zero-cache/src/services/replicator/replicator.js.map +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 +9 -6
- 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 +9 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +9 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -1
- 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/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 +48 -7
- 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 +10 -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/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 +25 -3
- 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-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-types/src/schema.d.ts +11 -0
- package/out/zero-types/src/schema.d.ts.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/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 +20 -13
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/package.json +2 -2
- 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 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\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 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 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 callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n copyMetrics.flush();\n flush();\n callback();\n } catch (e) {\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 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 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\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 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 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 callback();\n } catch (e) {\n callback(e instanceof Error ? e : new Error(String(e)));\n }\n },\n\n final: (callback: (error?: Error) => void) => {\n try {\n copyMetrics.flush();\n flush();\n callback();\n } catch (e) {\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 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 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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"}
|
|
@@ -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";
|
|
@@ -2,6 +2,7 @@ import type { LogContext } from '@rocicorp/logger';
|
|
|
2
2
|
import type postgres from 'postgres';
|
|
3
3
|
import { type PostgresDB } from '../../../types/pg.ts';
|
|
4
4
|
import { type ShardID } from '../../../types/shards.ts';
|
|
5
|
+
import { type BackupOptions } from './schema/shard.ts';
|
|
5
6
|
export type ReplicationSlot = {
|
|
6
7
|
slot_name: string;
|
|
7
8
|
consistent_point: string;
|
|
@@ -11,9 +12,10 @@ export type ReplicationSlot = {
|
|
|
11
12
|
export type CreateSlotSpec = {
|
|
12
13
|
slotName: string;
|
|
13
14
|
failover?: boolean;
|
|
15
|
+
temporary?: boolean;
|
|
14
16
|
lockTimeout?: number;
|
|
15
17
|
};
|
|
16
|
-
export declare function createReplicationSlot(lc: LogContext, session: postgres.Sql, { slotName, failover, lockTimeout }: CreateSlotSpec): Promise<ReplicationSlot>;
|
|
18
|
+
export declare function createReplicationSlot(lc: LogContext, session: postgres.Sql, { slotName, failover, temporary, lockTimeout, }: CreateSlotSpec): Promise<ReplicationSlot>;
|
|
17
19
|
/**
|
|
18
20
|
* Replica and slot creation involves two sessions for proper coordination
|
|
19
21
|
* with other replica management logic:
|
|
@@ -37,7 +39,7 @@ export declare function createReplicationSlot(lc: LogContext, session: postgres.
|
|
|
37
39
|
* rank) will not delete the new slot during their cleanup logic, since
|
|
38
40
|
* the slot will belong to a replica of a higher rank.
|
|
39
41
|
*/
|
|
40
|
-
export declare function createReplicaAndSlot(lc: LogContext, sql: PostgresDB, replicationSession: postgres.Sql, shard: ShardID, replicaID: string, failover: boolean): Promise<ReplicationSlot>;
|
|
42
|
+
export declare function createReplicaAndSlot(lc: LogContext, sql: PostgresDB, replicationSession: postgres.Sql, shard: ShardID, replicaID: string, failover: boolean, backupOptions: BackupOptions): Promise<ReplicationSlot>;
|
|
41
43
|
/**
|
|
42
44
|
* Deletes "old" replicas (i.e. those with a lower rank than the current)
|
|
43
45
|
* and attempts to drop replication slots that are not associated with any
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replication-slots.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/replication-slots.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAErC,OAAO,EAAkB,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAiB,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"replication-slots.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/replication-slots.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAErC,OAAO,EAAkB,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAiB,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;AAGtE,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAInB,SAAS,CAAC,EAAE,OAAO,CAAC;IAGpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAoBF,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,QAAQ,CAAC,GAAG,EACrB,EACE,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,WAAoC,GACrC,EAAE,cAAc,GAChB,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,UAAU,EACf,kBAAkB,EAAE,QAAQ,CAAC,GAAG,EAChC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,eAAe,CAAC,CAoE1B;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC,CAY9D;AA+DD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,UAUvC"}
|
|
@@ -8,9 +8,12 @@ import { PG_CONFIGURATION_LIMIT_EXCEEDED, PG_INSUFFICIENT_PRIVILEGE } from "@drd
|
|
|
8
8
|
//#region ../zero-cache/src/services/change-source/pg/replication-slots.ts
|
|
9
9
|
var CREATE_REPLICATION_SLOT_TIMEOUT_MS = 3e4;
|
|
10
10
|
var SERVER_LOCK_TIMEOUT_MS = CREATE_REPLICATION_SLOT_TIMEOUT_MS - 1e3;
|
|
11
|
-
async function createReplicationSlot(lc, session, { slotName, failover, lockTimeout = SERVER_LOCK_TIMEOUT_MS }) {
|
|
11
|
+
async function createReplicationSlot(lc, session, { slotName, failover, temporary, lockTimeout = SERVER_LOCK_TIMEOUT_MS }) {
|
|
12
12
|
await session.unsafe(`SET lock_timeout = ${lockTimeout}`);
|
|
13
|
-
const
|
|
13
|
+
const maybeTemporary = temporary ? "TEMPORARY" : "";
|
|
14
|
+
const options = failover ? "(FAILOVER)" : "";
|
|
15
|
+
const raced = await orTimeout(session.unsafe(`
|
|
16
|
+
CREATE_REPLICATION_SLOT "${slotName}" ${maybeTemporary} LOGICAL pgoutput ${options}`), CREATE_REPLICATION_SLOT_TIMEOUT_MS);
|
|
14
17
|
if (raced === "timed-out") {
|
|
15
18
|
session.end().catch((e) => lc.warn?.(`Error closing timed out replication slot session`, e));
|
|
16
19
|
throw new Error(`Timed out after ${CREATE_REPLICATION_SLOT_TIMEOUT_MS} ms creating replication slot ${slotName}. Crashing to force a clean restart.`);
|
|
@@ -42,7 +45,7 @@ async function createReplicationSlot(lc, session, { slotName, failover, lockTime
|
|
|
42
45
|
* rank) will not delete the new slot during their cleanup logic, since
|
|
43
46
|
* the slot will belong to a replica of a higher rank.
|
|
44
47
|
*/
|
|
45
|
-
async function createReplicaAndSlot(lc, sql, replicationSession, shard, replicaID, failover) {
|
|
48
|
+
async function createReplicaAndSlot(lc, sql, replicationSession, shard, replicaID, failover, backupOptions) {
|
|
46
49
|
const lockName = replicationSlotManagementLock(shard);
|
|
47
50
|
const slotPoolPrefix = replicationSlotPrefix(shard);
|
|
48
51
|
for (let first = true;; first = false) {
|
|
@@ -67,7 +70,7 @@ async function createReplicaAndSlot(lc, sql, replicationSession, shard, replicaI
|
|
|
67
70
|
slotName,
|
|
68
71
|
failover
|
|
69
72
|
});
|
|
70
|
-
await createReplica(tx, shard, replicaID, slot.slot_name, toStateVersionString(slot.consistent_point));
|
|
73
|
+
await createReplica(tx, shard, replicaID, slot.slot_name, toStateVersionString(slot.consistent_point), backupOptions);
|
|
71
74
|
return slot;
|
|
72
75
|
});
|
|
73
76
|
} catch (e) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replication-slots.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/replication-slots.ts"],"sourcesContent":["import {\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n PG_INSUFFICIENT_PRIVILEGE,\n} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport {isPostgresError, type PostgresDB} from '../../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../../types/shards.ts';\nimport {orTimeout} from '../../../types/timeout.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {\n createReplica,\n replicationSlotExpression,\n replicationSlotPrefix,\n} from './schema/shard.ts';\n\n// Record returned by `CREATE_REPLICATION_SLOT`\nexport type ReplicationSlot = {\n slot_name: string;\n consistent_point: string;\n snapshot_name: string;\n output_plugin: string;\n};\n\nexport type CreateSlotSpec = {\n slotName: string;\n\n // Note: must be false if pgVersion < PG_17. Caller must verify.\n failover?: boolean;\n\n // For overriding in tests.\n lockTimeout?: number;\n};\n\n// When creating a replication slot, Postgres waits for open transactions\n// to complete before reserving a consistent_point (LSN) in the WAL and creating\n// a matching transaction snapshot. As such, it can technically take an arbitrary\n// amount of time (e.g. DDL operations, table-wide operations, etc.).\n//\n// However, to detect pathological situations, bound the amount of time that\n// the server waits for replication slot creation, so that a continual failure to\n// create a replication slot is surfaced by errors / alerts.\nconst CREATE_REPLICATION_SLOT_TIMEOUT_MS = 30_000;\n\n// The lock_timeout is set 1s before the client-side orTimeout so that\n// Postgres reliably aborts first and tears down the walsender cleanly.\n// The client-side timeout remains as a fallback for network-level failures.\nconst SERVER_LOCK_TIMEOUT_MS = CREATE_REPLICATION_SLOT_TIMEOUT_MS - 1_000;\n\n// Note: The replication connection does not support the extended query protocol,\n// so all commands must be sent using sql.unsafe(). This is technically safe\n// because all placeholder values are under our control (i.e. \"slotName\").\nexport async function createReplicationSlot(\n lc: LogContext,\n session: postgres.Sql,\n {slotName, failover, lockTimeout = SERVER_LOCK_TIMEOUT_MS}: CreateSlotSpec,\n): Promise<ReplicationSlot> {\n // CREATE_REPLICATION_SLOT can hang indefinitely waiting for long-running\n // transactions to finish: internally it calls SnapBuildWaitSnapshot →\n // XactLockTableWait → LockAcquire on each running XID. statement_timeout\n // does NOT apply to replication commands, but lock_timeout does (it governs\n // the heavyweight lock wait inside LockAcquire). Setting it here causes\n // Postgres to raise ERRCODE_LOCK_NOT_AVAILABLE and cleanly tear down the\n // walsender, rather than relying solely on the client-side orTimeout\n // which can leave an orphaned backend.\n //\n // An orphaned walsender is actively harmful: by this point the replication\n // slot has already been created and is pinning WAL retention and catalog_xmin.\n // Worse, the slot is marked `active` (the walsender PID is still alive), so\n // the existing cleanup code (which drops inactive slots on retry) can't\n // reclaim it. Without lock_timeout the orphan persists until TCP keepalive\n // fires (~2h default) or the blocking transaction finishes.\n await session.unsafe(`SET lock_timeout = ${lockTimeout}`);\n\n const createSlot = failover\n ? session.unsafe<ReplicationSlot[]>(\n /*sql*/ `CREATE_REPLICATION_SLOT \"${slotName}\" LOGICAL pgoutput (FAILOVER)`,\n )\n : session.unsafe<ReplicationSlot[]>(\n /*sql*/ `CREATE_REPLICATION_SLOT \"${slotName}\" LOGICAL pgoutput`,\n );\n const raced = await orTimeout(createSlot, CREATE_REPLICATION_SLOT_TIMEOUT_MS);\n if (raced === 'timed-out') {\n // Create slot can block indefinitely waiting for old transactions. End\n // this connection in the background and fail fast so the process restarts.\n void session\n .end()\n .catch(e =>\n lc.warn?.(`Error closing timed out replication slot session`, e),\n );\n throw new Error(\n `Timed out after ${CREATE_REPLICATION_SLOT_TIMEOUT_MS} ms creating replication slot ${slotName}. ` +\n `Crashing to force a clean restart.`,\n );\n }\n const [slot] = raced;\n lc.info?.(`Created replication slot ${slotName}`, slot);\n return slot;\n}\n\n/**\n * Replica and slot creation involves two sessions for proper coordination\n * with other replica management logic:\n *\n * * A normal transaction is started and acquires an advisory lock for\n * replica slot management. This is the same lock that cleanup logic\n * acquires before cleaning up replication slots.\n * * With the lock held, a new replication slot is created in a\n * replication session. The API of CREATE_REPLICATION_SLOT is such\n * that it cannot be done in a transaction, and cannot be followed by\n * any writes, or else its snapshot (which is needed for initial sync)\n * would be invalidated.\n * * Once the slot is created, the slot and replica information are recorded\n * in the `replicas` table before releasing the lock.\n *\n * This locking ensures that:\n * 1. multiple replication managers attempting to create a replication slot\n * will not use the same name for the replication slot (which is selected\n * from a pool of reused names).\n * 2. Running replication managers (which use an earlier replica of a lower\n * rank) will not delete the new slot during their cleanup logic, since\n * the slot will belong to a replica of a higher rank.\n */\nexport async function createReplicaAndSlot(\n lc: LogContext,\n sql: PostgresDB,\n replicationSession: postgres.Sql,\n shard: ShardID,\n replicaID: string,\n failover: boolean,\n): Promise<ReplicationSlot> {\n const lockName = replicationSlotManagementLock(shard);\n const slotPoolPrefix = replicationSlotPrefix(shard);\n for (let first = true; ; first = false) {\n await dropUnclaimedSlots(lc, sql, shard);\n try {\n return await runTx(sql, async tx => {\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n // Pick an available slotName from the slotPoolPrefix pool.\n let slotName: string;\n const names = await tx<{name: string}[]> /*sql*/ `\n SELECT slot_name as name FROM pg_replication_slots\n WHERE slot_name LIKE ${slotPoolPrefix + '%'};\n `.values();\n const inUse = new Set(names.flat());\n for (let next = 0; ; next++) {\n const candidateName = `${slotPoolPrefix}${slotPoolSuffix(next)}`;\n if (!inUse.has(candidateName)) {\n slotName = candidateName;\n break;\n }\n }\n\n const slot = await createReplicationSlot(lc, replicationSession, {\n slotName,\n failover,\n });\n\n await createReplica(\n tx,\n shard,\n replicaID,\n slot.slot_name,\n toStateVersionString(slot.consistent_point),\n );\n\n return slot;\n });\n } catch (e) {\n if (first && isPostgresError(e, PG_INSUFFICIENT_PRIVILEGE)) {\n // Some Postgres variants (e.g. Google Cloud SQL) require that\n // the user have the REPLICATION role in order to create a slot.\n // Note that this must be done by the upstreamDB connection, and\n // does not work in the replicationSession itself.\n await sql`ALTER ROLE current_user WITH REPLICATION`;\n lc.info?.(`Added the REPLICATION role to database user`);\n continue;\n }\n // Note: This is currently manually tested since max_replication_slots\n // is a PG startup parameter that other tests depend on.\n // TODO: Figure out a way to unit test this (with the full PG setup).\n if (first && isPostgresError(e, PG_CONFIGURATION_LIMIT_EXCEEDED)) {\n lc.warn?.(\n `Reached max replication slots. Attempting to clean up unused slots`,\n e,\n );\n // Drop any inactive replicas from failed initial syncs (e.g. inactive slots).\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n await sql`\n DELETE FROM ${sql(replicasTable)} USING pg_replication_slots slots\n WHERE replicas.slot = slots.slot_name AND NOT slots.active`;\n continue; // then let dropUnclaimedSlots() perform its cleanup\n }\n throw e;\n }\n }\n}\n\n/**\n * Deletes \"old\" replicas (i.e. those with a lower rank than the current)\n * and attempts to drop replication slots that are not associated with any\n * replica.\n *\n * If a slot could not be dropped because there is still an active subscriber,\n * it will be reflected in the `draining` count that is returned. When there\n * are draining slots, the method should be retried until all orphaned slots\n * have been dropped.\n */\nexport async function dropOldReplicasAndSlots(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardID,\n beforeRank: bigint,\n): Promise<{dropped: number; active: number; draining: number}> {\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n const oldReplicas = await sql`\n SELECT id, rank::float8, slot, version, \"initialSyncContext\", \"subscriberContext\"\n FROM ${sql(replicasTable)} WHERE rank < ${beforeRank};\n `;\n if (oldReplicas.length) {\n lc.info?.(`Deleting ${oldReplicas.length} old replica(s)`, {oldReplicas});\n await sql`DELETE FROM ${sql(replicasTable)} WHERE rank < ${beforeRank}`;\n }\n\n return dropUnclaimedSlots(lc, sql, shard);\n}\n\nfunction dropUnclaimedSlots(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardID,\n): Promise<{dropped: number; active: number; draining: number}> {\n // The slot / replica cleanup happens within a transaction while holding\n // the replication slot management lock for this shard, to ensure that no\n // slot that belongs to a newer replica is dropped.\n const lockName = replicationSlotManagementLock(shard);\n const slotExpression = replicationSlotExpression(shard);\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n\n return runTx(sql, async tx => {\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n const dropped = await tx /*sql*/ `\n SELECT slot_name as slot, pg_drop_replication_slot(slot_name) \n FROM pg_replication_slots\n LEFT JOIN ${tx(replicasTable)} replica on slot_name = slot\n WHERE slot_name LIKE ${slotExpression} \n AND NOT active\n AND replica.id IS NULL;\n `;\n if (dropped.length) {\n lc.info?.(`dropped inactive replication slots`, {dropped});\n }\n\n const remaining = await tx<\n {slot: string; pid: number | null; id: string | null}[]\n > /*sql*/ `\n SELECT slot_name as slot, active_pid as pid, replica.id as id\n FROM pg_replication_slots\n LEFT JOIN ${tx(replicasTable)} replica on slot_name = slot\n WHERE slot_name LIKE ${slotExpression};\n `;\n if (remaining.length) {\n lc.info?.(`remaining replication slots`, {remaining});\n }\n\n let active = 0;\n let draining = 0;\n for (const {id} of remaining) {\n if (id === null) {\n draining++;\n } else {\n active++;\n }\n }\n\n return {\n dropped: dropped.length,\n active,\n draining,\n };\n });\n}\n\nconst ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n\n// Alphabetic notation is used as the slot pool suffix to distinguish\n// it from the (numeric) shard num that's also encoded in the slot name.\nexport function slotPoolSuffix(n: number) {\n n++; // Adjust for 0-based indexing\n\n let suffix = '';\n while (n > 0) {\n n--;\n suffix = ALPHABET[n % 26] + suffix;\n n = Math.floor(n / 26);\n }\n return suffix;\n}\n\nfunction replicationSlotManagementLock(shard: ShardID) {\n return `replication-slot-management:${shard.appID}_${shard.shardNum}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"replication-slots.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/replication-slots.ts"],"sourcesContent":["import {\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n PG_INSUFFICIENT_PRIVILEGE,\n} from '@drdgvhbh/postgres-error-codes';\nimport type {LogContext} from '@rocicorp/logger';\nimport type postgres from 'postgres';\nimport {runTx} from '../../../db/run-transaction.ts';\nimport {isPostgresError, type PostgresDB} from '../../../types/pg.ts';\nimport {upstreamSchema, type ShardID} from '../../../types/shards.ts';\nimport {orTimeout} from '../../../types/timeout.ts';\nimport {toStateVersionString} from './lsn.ts';\nimport {\n createReplica,\n replicationSlotExpression,\n replicationSlotPrefix,\n type BackupOptions,\n} from './schema/shard.ts';\n\n// Record returned by `CREATE_REPLICATION_SLOT`\nexport type ReplicationSlot = {\n slot_name: string;\n consistent_point: string;\n snapshot_name: string;\n output_plugin: string;\n};\n\nexport type CreateSlotSpec = {\n slotName: string;\n\n // Note: must be false if pgVersion < PG_17. Caller must verify.\n failover?: boolean;\n\n // Create a temporary slot (i.e. not persisted, and automatically\n // cleaned up when the replication session ends).\n temporary?: boolean;\n\n // For overriding in tests.\n lockTimeout?: number;\n};\n\n// When creating a replication slot, Postgres waits for open transactions\n// to complete before reserving a consistent_point (LSN) in the WAL and creating\n// a matching transaction snapshot. As such, it can technically take an arbitrary\n// amount of time (e.g. DDL operations, table-wide operations, etc.).\n//\n// However, to detect pathological situations, bound the amount of time that\n// the server waits for replication slot creation, so that a continual failure to\n// create a replication slot is surfaced by errors / alerts.\nconst CREATE_REPLICATION_SLOT_TIMEOUT_MS = 30_000;\n\n// The lock_timeout is set 1s before the client-side orTimeout so that\n// Postgres reliably aborts first and tears down the walsender cleanly.\n// The client-side timeout remains as a fallback for network-level failures.\nconst SERVER_LOCK_TIMEOUT_MS = CREATE_REPLICATION_SLOT_TIMEOUT_MS - 1_000;\n\n// Note: The replication connection does not support the extended query protocol,\n// so all commands must be sent using sql.unsafe(). This is technically safe\n// because all placeholder values are under our control (i.e. \"slotName\").\nexport async function createReplicationSlot(\n lc: LogContext,\n session: postgres.Sql,\n {\n slotName,\n failover,\n temporary,\n lockTimeout = SERVER_LOCK_TIMEOUT_MS,\n }: CreateSlotSpec,\n): Promise<ReplicationSlot> {\n // CREATE_REPLICATION_SLOT can hang indefinitely waiting for long-running\n // transactions to finish: internally it calls SnapBuildWaitSnapshot →\n // XactLockTableWait → LockAcquire on each running XID. statement_timeout\n // does NOT apply to replication commands, but lock_timeout does (it governs\n // the heavyweight lock wait inside LockAcquire). Setting it here causes\n // Postgres to raise ERRCODE_LOCK_NOT_AVAILABLE and cleanly tear down the\n // walsender, rather than relying solely on the client-side orTimeout\n // which can leave an orphaned backend.\n //\n // An orphaned walsender is actively harmful: by this point the replication\n // slot has already been created and is pinning WAL retention and catalog_xmin.\n // Worse, the slot is marked `active` (the walsender PID is still alive), so\n // the existing cleanup code (which drops inactive slots on retry) can't\n // reclaim it. Without lock_timeout the orphan persists until TCP keepalive\n // fires (~2h default) or the blocking transaction finishes.\n await session.unsafe(`SET lock_timeout = ${lockTimeout}`);\n\n const maybeTemporary = temporary ? 'TEMPORARY' : '';\n const options = failover ? '(FAILOVER)' : '';\n const createSlot = session.unsafe<ReplicationSlot[]>(/*sql*/ `\n CREATE_REPLICATION_SLOT \"${slotName}\" ${maybeTemporary} LOGICAL pgoutput ${options}`);\n\n const raced = await orTimeout(createSlot, CREATE_REPLICATION_SLOT_TIMEOUT_MS);\n if (raced === 'timed-out') {\n // Create slot can block indefinitely waiting for old transactions. End\n // this connection in the background and fail fast so the process restarts.\n void session\n .end()\n .catch(e =>\n lc.warn?.(`Error closing timed out replication slot session`, e),\n );\n throw new Error(\n `Timed out after ${CREATE_REPLICATION_SLOT_TIMEOUT_MS} ms creating replication slot ${slotName}. ` +\n `Crashing to force a clean restart.`,\n );\n }\n const [slot] = raced;\n lc.info?.(`Created replication slot ${slotName}`, slot);\n return slot;\n}\n\n/**\n * Replica and slot creation involves two sessions for proper coordination\n * with other replica management logic:\n *\n * * A normal transaction is started and acquires an advisory lock for\n * replica slot management. This is the same lock that cleanup logic\n * acquires before cleaning up replication slots.\n * * With the lock held, a new replication slot is created in a\n * replication session. The API of CREATE_REPLICATION_SLOT is such\n * that it cannot be done in a transaction, and cannot be followed by\n * any writes, or else its snapshot (which is needed for initial sync)\n * would be invalidated.\n * * Once the slot is created, the slot and replica information are recorded\n * in the `replicas` table before releasing the lock.\n *\n * This locking ensures that:\n * 1. multiple replication managers attempting to create a replication slot\n * will not use the same name for the replication slot (which is selected\n * from a pool of reused names).\n * 2. Running replication managers (which use an earlier replica of a lower\n * rank) will not delete the new slot during their cleanup logic, since\n * the slot will belong to a replica of a higher rank.\n */\nexport async function createReplicaAndSlot(\n lc: LogContext,\n sql: PostgresDB,\n replicationSession: postgres.Sql,\n shard: ShardID,\n replicaID: string,\n failover: boolean,\n backupOptions: BackupOptions,\n): Promise<ReplicationSlot> {\n const lockName = replicationSlotManagementLock(shard);\n const slotPoolPrefix = replicationSlotPrefix(shard);\n for (let first = true; ; first = false) {\n await dropUnclaimedSlots(lc, sql, shard);\n try {\n return await runTx(sql, async tx => {\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n // Pick an available slotName from the slotPoolPrefix pool.\n let slotName: string;\n const names = await tx<{name: string}[]> /*sql*/ `\n SELECT slot_name as name FROM pg_replication_slots\n WHERE slot_name LIKE ${slotPoolPrefix + '%'};\n `.values();\n const inUse = new Set(names.flat());\n for (let next = 0; ; next++) {\n const candidateName = `${slotPoolPrefix}${slotPoolSuffix(next)}`;\n if (!inUse.has(candidateName)) {\n slotName = candidateName;\n break;\n }\n }\n\n const slot = await createReplicationSlot(lc, replicationSession, {\n slotName,\n failover,\n });\n\n await createReplica(\n tx,\n shard,\n replicaID,\n slot.slot_name,\n toStateVersionString(slot.consistent_point),\n backupOptions,\n );\n\n return slot;\n });\n } catch (e) {\n if (first && isPostgresError(e, PG_INSUFFICIENT_PRIVILEGE)) {\n // Some Postgres variants (e.g. Google Cloud SQL) require that\n // the user have the REPLICATION role in order to create a slot.\n // Note that this must be done by the upstreamDB connection, and\n // does not work in the replicationSession itself.\n await sql`ALTER ROLE current_user WITH REPLICATION`;\n lc.info?.(`Added the REPLICATION role to database user`);\n continue;\n }\n // Note: This is currently manually tested since max_replication_slots\n // is a PG startup parameter that other tests depend on.\n // TODO: Figure out a way to unit test this (with the full PG setup).\n if (first && isPostgresError(e, PG_CONFIGURATION_LIMIT_EXCEEDED)) {\n lc.warn?.(\n `Reached max replication slots. Attempting to clean up unused slots`,\n e,\n );\n // Drop any inactive replicas from failed initial syncs (e.g. inactive slots).\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n await sql`\n DELETE FROM ${sql(replicasTable)} USING pg_replication_slots slots\n WHERE replicas.slot = slots.slot_name AND NOT slots.active`;\n continue; // then let dropUnclaimedSlots() perform its cleanup\n }\n throw e;\n }\n }\n}\n\n/**\n * Deletes \"old\" replicas (i.e. those with a lower rank than the current)\n * and attempts to drop replication slots that are not associated with any\n * replica.\n *\n * If a slot could not be dropped because there is still an active subscriber,\n * it will be reflected in the `draining` count that is returned. When there\n * are draining slots, the method should be retried until all orphaned slots\n * have been dropped.\n */\nexport async function dropOldReplicasAndSlots(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardID,\n beforeRank: bigint,\n): Promise<{dropped: number; active: number; draining: number}> {\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n const oldReplicas = await sql`\n SELECT id, rank::float8, slot, version, \"initialSyncContext\", \"subscriberContext\"\n FROM ${sql(replicasTable)} WHERE rank < ${beforeRank};\n `;\n if (oldReplicas.length) {\n lc.info?.(`Deleting ${oldReplicas.length} old replica(s)`, {oldReplicas});\n await sql`DELETE FROM ${sql(replicasTable)} WHERE rank < ${beforeRank}`;\n }\n\n return dropUnclaimedSlots(lc, sql, shard);\n}\n\nfunction dropUnclaimedSlots(\n lc: LogContext,\n sql: PostgresDB,\n shard: ShardID,\n): Promise<{dropped: number; active: number; draining: number}> {\n // The slot / replica cleanup happens within a transaction while holding\n // the replication slot management lock for this shard, to ensure that no\n // slot that belongs to a newer replica is dropped.\n const lockName = replicationSlotManagementLock(shard);\n const slotExpression = replicationSlotExpression(shard);\n const replicasTable = `${upstreamSchema(shard)}.replicas`;\n\n return runTx(sql, async tx => {\n await tx`SELECT pg_advisory_xact_lock(hashtext(${lockName}))`;\n\n const dropped = await tx /*sql*/ `\n SELECT slot_name as slot, pg_drop_replication_slot(slot_name) \n FROM pg_replication_slots\n LEFT JOIN ${tx(replicasTable)} replica on slot_name = slot\n WHERE slot_name LIKE ${slotExpression} \n AND NOT active\n AND replica.id IS NULL;\n `;\n if (dropped.length) {\n lc.info?.(`dropped inactive replication slots`, {dropped});\n }\n\n const remaining = await tx<\n {slot: string; pid: number | null; id: string | null}[]\n > /*sql*/ `\n SELECT slot_name as slot, active_pid as pid, replica.id as id\n FROM pg_replication_slots\n LEFT JOIN ${tx(replicasTable)} replica on slot_name = slot\n WHERE slot_name LIKE ${slotExpression};\n `;\n if (remaining.length) {\n lc.info?.(`remaining replication slots`, {remaining});\n }\n\n let active = 0;\n let draining = 0;\n for (const {id} of remaining) {\n if (id === null) {\n draining++;\n } else {\n active++;\n }\n }\n\n return {\n dropped: dropped.length,\n active,\n draining,\n };\n });\n}\n\nconst ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n\n// Alphabetic notation is used as the slot pool suffix to distinguish\n// it from the (numeric) shard num that's also encoded in the slot name.\nexport function slotPoolSuffix(n: number) {\n n++; // Adjust for 0-based indexing\n\n let suffix = '';\n while (n > 0) {\n n--;\n suffix = ALPHABET[n % 26] + suffix;\n n = Math.floor(n / 26);\n }\n return suffix;\n}\n\nfunction replicationSlotManagementLock(shard: ShardID) {\n return `replication-slot-management:${shard.appID}_${shard.shardNum}`;\n}\n"],"mappings":"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"}
|
|
@@ -5,10 +5,5 @@ import { type ShardConfig } from '../../../../types/shards.ts';
|
|
|
5
5
|
* Ensures that a shard is set up for initial sync.
|
|
6
6
|
*/
|
|
7
7
|
export declare function ensureShardSchema(lc: LogContext, db: PostgresDB, shard: ShardConfig): Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
* Updates the schema for an existing shard.
|
|
10
|
-
*/
|
|
11
|
-
export declare function updateShardSchema(lc: LogContext, db: PostgresDB, shard: ShardConfig, replicaVersion: string): Promise<void>;
|
|
12
8
|
export declare const CURRENT_SCHEMA_VERSION: number;
|
|
13
|
-
export declare function decommissionLegacyShard(lc: LogContext, db: PostgresDB, shard: ShardConfig): Promise<void>;
|
|
14
9
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAQjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,6BAA6B,CAAC;AAY7E;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAgBf;AA4MD,eAAO,MAAM,sBAAsB,QAMwB,CAAC"}
|
|
@@ -4,9 +4,8 @@ import { upstreamSchema } from "../../../../types/shards.js";
|
|
|
4
4
|
import { id } from "../../../../types/sql.js";
|
|
5
5
|
import { publishedSchema } from "./published.js";
|
|
6
6
|
import { getMutationsTableDefinition, legacyReplicationSlot, metadataPublicationName, setupTablesAndReplication, setupTriggers } from "./shard.js";
|
|
7
|
-
import { decommissionShard } from "../decommission.js";
|
|
8
7
|
import { AutoResetSignal } from "../../../change-streamer/schema/tables.js";
|
|
9
|
-
import {
|
|
8
|
+
import { runSchemaMigrations } from "../../../../db/migration.js";
|
|
10
9
|
//#region ../zero-cache/src/services/change-source/pg/schema/init.ts
|
|
11
10
|
/**
|
|
12
11
|
* Ensures that a shard is set up for initial sync.
|
|
@@ -15,41 +14,13 @@ async function ensureShardSchema(lc, db, shard) {
|
|
|
15
14
|
await runSchemaMigrations(lc, `upstream-shard-${shard.appID}`, upstreamSchema(shard), db, {
|
|
16
15
|
migrateSchema: (lc, tx) => setupTablesAndReplication(lc, tx, shard),
|
|
17
16
|
minSafeVersion: 1
|
|
18
|
-
}, getIncrementalMigrations(shard
|
|
17
|
+
}, getIncrementalMigrations(shard));
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
* Updates the schema for an existing shard.
|
|
22
|
-
*/
|
|
23
|
-
async function updateShardSchema(lc, db, shard, replicaVersion) {
|
|
24
|
-
await runSchemaMigrations(lc, `upstream-shard-${shard.appID}`, upstreamSchema(shard), db, { migrateSchema: () => {
|
|
25
|
-
throw new AutoResetSignal(`upstream shard ${upstreamSchema(shard)} is not initialized`);
|
|
26
|
-
} }, getIncrementalMigrations(shard, replicaVersion));
|
|
27
|
-
await decommissionLegacyShard(lc, db, shard);
|
|
28
|
-
}
|
|
29
|
-
function getIncrementalMigrations(shard, replicaVersion) {
|
|
19
|
+
function getIncrementalMigrations(shard) {
|
|
30
20
|
const shardConfigTable = `${upstreamSchema(shard)}.shardConfig`;
|
|
31
21
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new AutoResetSignal("resetting to upgrade shard schema");
|
|
35
|
-
},
|
|
36
|
-
minSafeVersion: 3
|
|
37
|
-
},
|
|
38
|
-
6: { migrateSchema: async (lc, sql) => {
|
|
39
|
-
assert(replicaVersion, `replicaVersion is always passed for incremental migrations`);
|
|
40
|
-
await Promise.all([sql`
|
|
41
|
-
ALTER TABLE ${sql(shardConfigTable)} ADD "replicaVersion" TEXT`, sql`
|
|
42
|
-
UPDATE ${sql(shardConfigTable)} SET ${sql({ replicaVersion })}`]);
|
|
43
|
-
lc.info?.(`Recorded replicaVersion ${replicaVersion} in upstream shardConfig`);
|
|
44
|
-
} },
|
|
45
|
-
7: { migrateSchema: async (lc, sql) => {
|
|
46
|
-
const [{ publications }] = await sql`
|
|
47
|
-
SELECT publications FROM ${sql(shardConfigTable)}`;
|
|
48
|
-
await setupTriggers(lc, sql, {
|
|
49
|
-
...shard,
|
|
50
|
-
publications
|
|
51
|
-
});
|
|
52
|
-
lc.info?.(`Upgraded to v2 event triggers`);
|
|
22
|
+
6: { migrateSchema: () => {
|
|
23
|
+
throw new AutoResetSignal("resetting to upgrade shard schema");
|
|
53
24
|
} },
|
|
54
25
|
8: { migrateSchema: async (lc, sql) => {
|
|
55
26
|
const legacyShardConfigSchema = valita_exports.object({
|
|
@@ -135,6 +106,29 @@ function getIncrementalMigrations(shard, replicaVersion) {
|
|
|
135
106
|
publications
|
|
136
107
|
});
|
|
137
108
|
lc.info?.(`Upgraded DDL event triggers`);
|
|
109
|
+
} },
|
|
110
|
+
24: {
|
|
111
|
+
migrateSchema: async (_, sql) => {
|
|
112
|
+
await sql`
|
|
113
|
+
ALTER TABLE ${sql(upstreamSchema(shard))}.replicas
|
|
114
|
+
ADD COLUMN "backupPath" TEXT;
|
|
115
|
+
`;
|
|
116
|
+
await sql`
|
|
117
|
+
ALTER TABLE ${sql(upstreamSchema(shard))}.replicas
|
|
118
|
+
ADD COLUMN "generation" TEXT;
|
|
119
|
+
`;
|
|
120
|
+
},
|
|
121
|
+
migrateData: async (_, sql) => {
|
|
122
|
+
await sql`
|
|
123
|
+
UPDATE ${sql(upstreamSchema(shard))}.replicas SET "generation" = "version";
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
25: { migrateSchema: async (_, sql) => {
|
|
128
|
+
await sql`
|
|
129
|
+
ALTER TABLE ${sql(upstreamSchema(shard))}.replicas
|
|
130
|
+
ADD COLUMN "backupV5" BOOL DEFAULT false;
|
|
131
|
+
`;
|
|
138
132
|
} }
|
|
139
133
|
};
|
|
140
134
|
}
|
|
@@ -143,13 +137,7 @@ Object.keys(getIncrementalMigrations({
|
|
|
143
137
|
shardNum: 0,
|
|
144
138
|
publications: ["foo"]
|
|
145
139
|
})).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);
|
|
146
|
-
async function decommissionLegacyShard(lc, db, shard) {
|
|
147
|
-
if (shard.appID !== "zero") {
|
|
148
|
-
const versionHistory = await getVersionHistory(db, `zero_${shard.appID}`);
|
|
149
|
-
if (versionHistory !== null && versionHistory.schemaVersion < 5) await decommissionShard(lc, db, "zero", shard.appID);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
140
|
//#endregion
|
|
153
|
-
export { ensureShardSchema
|
|
141
|
+
export { ensureShardSchema };
|
|
154
142
|
|
|
155
143
|
//# sourceMappingURL=init.js.map
|