@rocicorp/zero 1.10.0-canary.0 → 1.10.0-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replicache/src/db/commit.js +1 -4
- package/out/replicache/src/db/commit.js.map +1 -1
- package/out/replicache/src/db/read.d.ts +1 -0
- package/out/replicache/src/db/read.d.ts.map +1 -1
- package/out/replicache/src/db/read.js +1 -4
- package/out/replicache/src/db/read.js.map +1 -1
- package/out/replicache/src/db/write.d.ts +2 -1
- package/out/replicache/src/db/write.d.ts.map +1 -1
- package/out/replicache/src/db/write.js +4 -51
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/persist/clients.d.ts +1 -0
- package/out/replicache/src/persist/clients.d.ts.map +1 -1
- package/out/replicache/src/persist/clients.js +4 -31
- package/out/replicache/src/persist/clients.js.map +1 -1
- package/out/replicache/src/process-env.d.ts +28 -0
- package/out/replicache/src/process-env.d.ts.map +1 -0
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/scan-iterator.js +2 -14
- package/out/replicache/src/scan-iterator.js.map +1 -1
- package/out/replicache/src/subscriptions.d.ts +1 -0
- package/out/replicache/src/subscriptions.d.ts.map +1 -1
- package/out/replicache/src/subscriptions.js +2 -10
- package/out/replicache/src/subscriptions.js.map +1 -1
- package/out/replicache/src/transactions.d.ts +1 -0
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +2 -4
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/z2s/src/compiler.d.ts.map +1 -1
- package/out/z2s/src/compiler.js +1 -22
- package/out/z2s/src/compiler.js.map +1 -1
- package/out/zero/package.js +3 -3
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/normalize.d.ts +1 -0
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +10 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +52 -1
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +171 -22
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +1 -1
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/db/delete-lite-db.js +2 -1
- package/out/zero-cache/src/db/delete-lite-db.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +5 -2
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration.js +4 -2
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
- package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
- package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
- package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +6 -1
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +20 -2
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/server/backup-watermark-poller.js +31 -0
- package/out/zero-cache/src/server/backup-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +91 -31
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +6 -16
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +48 -5
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/run-worker.js +2 -1
- package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +1 -1
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -2
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +35 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js +34 -0
- package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +73 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts +2 -6
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +14 -5
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -7
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +7 -7
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +82 -25
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +9 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +12 -4
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +45 -2
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +59 -8
- package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +4 -2
- package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js +7 -4
- package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts +0 -5
- package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +29 -41
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +35 -16
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +77 -7
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +2 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +5 -13
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +6 -3
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts +6 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js +21 -0
- package/out/zero-cache/src/services/change-source/protocol/current/schema-change-tags.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +5 -6
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +32 -9
- package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +15 -5
- package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js +66 -0
- package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +68 -78
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +84 -92
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js +50 -0
- package/out/zero-cache/src/services/change-streamer/change-log-codec.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts +141 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +270 -0
- package/out/zero-cache/src/services/change-streamer/change-log-initializer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts +19 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js +39 -0
- package/out/zero-cache/src/services/change-streamer/change-log-transaction-hash.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +12 -17
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +69 -24
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +333 -66
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +56 -13
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts +8 -0
- package/out/zero-cache/src/services/change-streamer/error-type-enum.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +14 -3
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +41 -11
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.d.ts → litestream3-prometheus-poller.d.ts} +19 -45
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/{litestream3-backup-monitor.js → litestream3-prometheus-poller.js} +57 -171
- package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +15 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js +55 -0
- package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +2 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts +14 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js +86 -0
- package/out/zero-cache/src/services/change-streamer/snapshot-reservations.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts +4 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/snapshot.js +39 -14
- package/out/zero-cache/src/services/change-streamer/snapshot.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts +75 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js +261 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-catchup.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts +121 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js +391 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-purge-scheduler.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts +62 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js +159 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-reader.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts +109 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +258 -0
- package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/storer.d.ts +28 -12
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +116 -121
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +61 -10
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +101 -8
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts +34 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js +86 -0
- package/out/zero-cache/src/services/change-streamer/upstream-acker.js.map +1 -0
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +4 -3
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts +12 -11
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +107 -148
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +42 -0
- package/out/zero-cache/src/services/litestream/config.yml +3 -2
- package/out/zero-cache/src/services/litestream/metrics.d.ts +4 -4
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +7 -10
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts +57 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.d.ts.map +1 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js +137 -0
- package/out/zero-cache/src/services/litestream/vfs-watermark-poller.js.map +1 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +4 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts +169 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +434 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts +358 -0
- package/out/zero-cache/src/services/replicator/change-log-db.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js +477 -0
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts +55 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js +150 -0
- package/out/zero-cache/src/services/replicator/change-log-stream-writer.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +70 -25
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +19 -6
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
- package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/notifier.js +7 -1
- package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.d.ts +17 -2
- package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js +9 -3
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
- package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
- package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +119 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +214 -0
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -0
- package/out/zero-cache/src/services/replicator/schema/change-log.d.ts +3 -3
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/replication-state.js +9 -5
- package/out/zero-cache/src/services/replicator/schema/replication-state.js.map +1 -1
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts +131 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js +413 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-observability.js.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts +120 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.d.ts.map +1 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js +283 -0
- package/out/zero-cache/src/services/replicator/sqlite-change-log-purger.js.map +1 -0
- package/out/zero-cache/src/services/replicator/write-worker.js +23 -12
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +2 -1
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/running-state.d.ts +1 -1
- package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
- package/out/zero-cache/src/services/running-state.js +1 -0
- package/out/zero-cache/src/services/running-state.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +53 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +68 -0
- package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +18 -3
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +64 -6
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +1 -2
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +1 -25
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +21 -0
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +25 -4
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts +11 -0
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +27 -0
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts +1 -0
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +24 -2
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/replicator.d.ts +25 -0
- package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/workers/replicator.js +33 -1
- package/out/zero-cache/src/workers/replicator.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts +12 -0
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +75 -0
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +6 -7
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +156 -101
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-schema/src/builder/table-builder.d.ts +23 -0
- package/out/zero-schema/src/builder/table-builder.d.ts.map +1 -1
- package/out/zero-schema/src/builder/table-builder.js +18 -0
- package/out/zero-schema/src/builder/table-builder.js.map +1 -1
- package/out/zero-server/src/custom.js +10 -3
- package/out/zero-server/src/custom.js.map +1 -1
- package/out/zero-server/src/schema.d.ts +8 -0
- package/out/zero-server/src/schema.d.ts.map +1 -1
- package/out/zero-server/src/schema.js +63 -24
- package/out/zero-server/src/schema.js.map +1 -1
- package/out/zero-types/src/schema.d.ts +11 -0
- package/out/zero-types/src/schema.d.ts.map +1 -1
- package/out/zql/src/mutate/crud.d.ts +10 -8
- package/out/zql/src/mutate/crud.d.ts.map +1 -1
- package/out/zql/src/mutate/crud.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +5 -1
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +2 -2
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query.d.ts +83 -14
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/static-query.d.ts +1 -1
- package/out/zql/src/query/static-query.d.ts.map +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts +15 -0
- package/out/zqlite/src/resolve-scalar-subqueries.d.ts.map +1 -1
- package/out/zqlite/src/resolve-scalar-subqueries.js +33 -14
- package/out/zqlite/src/resolve-scalar-subqueries.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.js +3 -3
- package/package.json +3 -3
- package/out/replicache/src/db/index.js +0 -91
- package/out/replicache/src/db/index.js.map +0 -1
- package/out/replicache/src/lazy.d.ts +0 -2
- package/out/replicache/src/lazy.d.ts.map +0 -1
- package/out/replicache/src/lazy.js +0 -12
- package/out/replicache/src/lazy.js.map +0 -1
- package/out/zero-cache/src/server/backup-watermark-reader.js +0 -35
- package/out/zero-cache/src/server/backup-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/litestream3-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts +0 -16
- package/out/zero-cache/src/services/change-streamer/replica-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js +0 -54
- package/out/zero-cache/src/services/change-streamer/replica-monitor.js.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts +0 -25
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js +0 -159
- package/out/zero-cache/src/services/change-streamer/vfs-backup-monitor.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts +0 -44
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +0 -65
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts +0 -11
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js +0 -74
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker-source.js.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts +0 -42
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.d.ts.map +0 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js +0 -117
- package/out/zero-cache/src/services/litestream/vfs-watermark-worker.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forwarder.js","names":["#lc","#progressMonitorOptions","#active","#queued","#flowControlWaits","#flowControlWaitTime","#subscriberStats","#progressMonitor","#trackProgress","#currentBroadcast","#inTransaction","#updateActiveSubscribers"],"sources":["../../../../../../zero-cache/src/services/change-streamer/forwarder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {joinIterables, wrapIterable} from '../../../../shared/src/iterables.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {Broadcast} from './broadcast.ts';\nimport type {ChangeTag, WatermarkedChange} from './change-streamer-service.ts';\nimport type {Status} from './change-streamer.ts';\nimport type {Subscriber} from './subscriber.ts';\n\nexport type ProgressMonitorOptions = {\n flowControlConsensusPaddingSeconds: number;\n};\n\nexport class Forwarder {\n readonly #lc: LogContext;\n readonly #progressMonitorOptions: ProgressMonitorOptions;\n readonly #active = new Set<Subscriber>();\n readonly #queued = new Set<Subscriber>();\n readonly #flowControlWaits = getOrCreateCounter(\n 'replication',\n 'flow_control.waits',\n 'Flow-control checkpoints completed, labeled by release mode.',\n );\n readonly #flowControlWaitTime = getOrCreateLatencyHistogram(\n 'replication',\n 'flow_control.wait_duration',\n 'Time replication waits at flow-control checkpoints.',\n );\n #inTransaction = false;\n\n #currentBroadcast: Broadcast | undefined;\n #progressMonitor: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n opts: ProgressMonitorOptions = {flowControlConsensusPaddingSeconds: 1},\n ) {\n this.#lc = lc.withContext('component', 'progress-monitor');\n this.#progressMonitorOptions = opts;\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.active_subscribers',\n 'Active change-stream subscribers receiving live changes.',\n ).addCallback(result => result.observe(this.#active.size));\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.queued_subscribers',\n 'Change-stream subscribers waiting for the current transaction to finish before activation.',\n ).addCallback(result => result.observe(this.#queued.size));\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.pending_messages',\n 'Downstream change-stream messages not yet acked by subscribers.',\n ).addCallback(result =>\n result.observe(this.#subscriberStats().pendingMessages),\n );\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.backlog_messages',\n 'Live change-stream messages buffered while subscribers catch up.',\n ).addCallback(result =>\n result.observe(this.#subscriberStats().backlogMessages),\n );\n\n getOrCreateGauge('replication', 'flow_control.backlog_bytes', {\n description:\n 'Live change-stream bytes buffered while subscribers catch up.',\n unit: 'By',\n }).addCallback(result =>\n result.observe(this.#subscriberStats().backlogBytes),\n );\n\n getOrCreateGauge('replication', 'flow_control.max_backlog_bytes', {\n description:\n 'Maximum live change-stream bytes buffered by a single subscriber.',\n unit: 'By',\n }).addCallback(result =>\n result.observe(this.#subscriberStats().maxBacklogBytes),\n );\n }\n\n startProgressMonitor() {\n clearInterval(this.#progressMonitor);\n this.#progressMonitor = setInterval(this.#trackProgress, 1000);\n }\n\n readonly #trackProgress = () => {\n const now = performance.now();\n for (const sub of this.#active) {\n sub.sampleProcessRate(now);\n }\n\n const {flowControlConsensusPaddingSeconds} = this.#progressMonitorOptions;\n // A negative number disables early flow control release.\n if (flowControlConsensusPaddingSeconds >= 0) {\n this.#currentBroadcast?.checkProgress(\n this.#lc,\n flowControlConsensusPaddingSeconds * 1000,\n now,\n );\n }\n };\n\n stopProgressMonitor() {\n clearInterval(this.#progressMonitor);\n }\n\n /**\n * `add()` is called in lock step with `Storer.catchup()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n */\n add(sub: Subscriber) {\n if (this.#inTransaction) {\n this.#queued.add(sub);\n } else {\n this.#active.add(sub);\n }\n }\n\n remove(sub: Subscriber) {\n this.#active.delete(sub);\n this.#queued.delete(sub);\n sub.close();\n }\n\n /**\n * `forward()` is called in lockstep with `Storer.store()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n *\n * This version of forward is fire-and-forget, with no flow control. The\n * change-streamer should call and await {@link forwardWithFlowControl()}\n * occasionally to avoid memory blowup.\n */\n forward(entry: WatermarkedChange) {\n Broadcast.withoutTracking(this.#active.values(), entry);\n this.#updateActiveSubscribers(entry[1]);\n }\n\n sendStatus(status: Status) {\n for (const sub of this.#active.values()) {\n sub.sendStatus(status);\n }\n }\n\n /**\n * The flow-control-aware equivalent of {@link forward()}, returning a\n * Promise that resolves when replication should continue.\n */\n async forwardWithFlowControl(entry: WatermarkedChange) {\n const start = performance.now();\n const broadcast = new Broadcast(this.#active.values(), entry);\n this.#updateActiveSubscribers(entry[1]);\n\n // set for progress tracking\n this.#currentBroadcast = broadcast;\n\n try {\n await broadcast.done;\n const attributes = {'release.mode': broadcast.releaseMode};\n this.#flowControlWaits.add(1, attributes);\n this.#flowControlWaitTime.recordMs(performance.now() - start, attributes);\n } finally {\n // Technically #currentBroadcast may have changed, so only\n // unset if it if is still the same.\n if (this.#currentBroadcast === broadcast) {\n this.#currentBroadcast = undefined;\n }\n }\n }\n\n #subscriberStats() {\n let pendingMessages = 0;\n let backlogMessages = 0;\n let backlogBytes = 0;\n let maxBacklogBytes = 0;\n\n for (const sub of joinIterables(this.#active, this.#queued)) {\n const stats = sub.getStats();\n pendingMessages += stats.pending;\n backlogMessages += stats.backlog;\n backlogBytes += stats.backlogBytes;\n maxBacklogBytes = Math.max(maxBacklogBytes, stats.backlogBytes);\n }\n\n return {pendingMessages, backlogMessages, backlogBytes, maxBacklogBytes};\n }\n\n #updateActiveSubscribers(tag: ChangeTag) {\n switch (tag) {\n case 'begin':\n // While in a Transaction, all added subscribers are \"queued\" so that no\n // messages are forwarded to them. This state corresponds to being queued\n // for catchup in the Storer, which will retrieve historic changes\n // and call catchup() once the current transaction is committed.\n this.#inTransaction = true;\n break;\n case 'commit':\n case 'rollback':\n // Upon commit or rollback, all queued subscribers are transferred to\n // the active set. This means that they can receive messages starting\n // from the next transaction.\n //\n // Note that if catchup is still in progress (in the Storer), these messages\n // will be buffered in the backlog until catchup completes.\n this.#inTransaction = false;\n for (const sub of this.#queued.values()) {\n this.#active.add(sub);\n }\n this.#queued.clear();\n break;\n }\n }\n\n getAcks(): Set<string> {\n return new Set(\n joinIterables(\n wrapIterable(this.#active).map(s => s.acked),\n wrapIterable(this.#queued).map(s => s.acked),\n ),\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"forwarder.js","names":["#lc","#setTimeout","#clearTimeout","#active","#queued","#earlyReleaseOptions","#flowControlSlowSubscriberGracePeriodMs","#flowControlWaits","#flowControlWaitTime","#subscriberStats","#progressMonitor","#currentBroadcast","#inTransaction","#updateActiveSubscribers"],"sources":["../../../../../../zero-cache/src/services/change-streamer/forwarder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {joinIterables, wrapIterable} from '../../../../shared/src/iterables.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateLatencyHistogram,\n} from '../../observability/metrics.ts';\nimport {Broadcast, type EarlyReleaseOptions} from './broadcast.ts';\nimport type {ChangeTag, Status, WatermarkedChange} from './change-streamer.ts';\nimport {StreamTooFarBehind} from './error-type-enum.ts';\nimport type {Subscriber, SubscriberStats} from './subscriber.ts';\n\nexport type ProgressMonitorOptions = {\n flowControlConsensusTimeoutProportion: number;\n flowControlSlowSubscriberGracePeriodMs?: number | undefined;\n // Injectable timers, for deterministic testing.\n setTimeoutFn?: typeof setTimeout | undefined;\n clearTimeoutFn?: typeof clearTimeout | undefined;\n};\n\nexport class Forwarder {\n readonly #lc: LogContext;\n readonly #setTimeout: typeof setTimeout;\n readonly #clearTimeout: typeof clearTimeout;\n readonly #active = new Set<Subscriber>();\n readonly #queued = new Set<Subscriber>();\n readonly #earlyReleaseOptions: EarlyReleaseOptions | undefined;\n readonly #flowControlSlowSubscriberGracePeriodMs: number | undefined;\n\n readonly #flowControlWaits = getOrCreateCounter(\n 'replication',\n 'flow_control.waits',\n 'Flow-control checkpoints completed, labeled by release mode.',\n );\n readonly #flowControlWaitTime = getOrCreateLatencyHistogram(\n 'replication',\n 'flow_control.wait_duration',\n 'Time replication waits at flow-control checkpoints.',\n );\n #inTransaction = false;\n\n #currentBroadcast: Broadcast | undefined;\n #progressMonitor: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n opts: ProgressMonitorOptions = {\n flowControlConsensusTimeoutProportion: 2.0,\n },\n ) {\n this.#lc = lc.withContext('component', 'progress-monitor');\n this.#setTimeout = opts.setTimeoutFn ?? setTimeout;\n this.#clearTimeout = opts.clearTimeoutFn ?? clearTimeout;\n\n this.#earlyReleaseOptions =\n opts.flowControlConsensusTimeoutProportion < 0\n ? undefined\n : {\n consensusTimeoutProportion:\n opts.flowControlConsensusTimeoutProportion,\n setTimeoutFn: this.#setTimeout,\n clearTimeoutFn: this.#clearTimeout,\n };\n this.#flowControlSlowSubscriberGracePeriodMs =\n opts.flowControlSlowSubscriberGracePeriodMs;\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.active_subscribers',\n 'Active change-stream subscribers receiving live changes.',\n ).addCallback(result => result.observe(this.#active.size));\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.queued_subscribers',\n 'Change-stream subscribers waiting for the current transaction to finish before activation.',\n ).addCallback(result => result.observe(this.#queued.size));\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.pending_messages',\n 'Downstream change-stream messages not yet acked by subscribers.',\n ).addCallback(result =>\n result.observe(this.#subscriberStats().pendingMessages),\n );\n\n getOrCreateGauge(\n 'replication',\n 'flow_control.backlog_messages',\n 'Live change-stream messages buffered while subscribers catch up.',\n ).addCallback(result =>\n result.observe(this.#subscriberStats().backlogMessages),\n );\n\n getOrCreateGauge('replication', 'flow_control.backlog_bytes', {\n description:\n 'Live change-stream bytes buffered while subscribers catch up.',\n unit: 'By',\n }).addCallback(result =>\n result.observe(this.#subscriberStats().backlogBytes),\n );\n\n getOrCreateGauge('replication', 'flow_control.max_backlog_bytes', {\n description:\n 'Maximum live change-stream bytes buffered by a single subscriber.',\n unit: 'By',\n }).addCallback(result =>\n result.observe(this.#subscriberStats().maxBacklogBytes),\n );\n }\n\n startProgressMonitor() {\n clearInterval(this.#progressMonitor);\n this.#progressMonitor = setInterval(\n () => this.checkSubscriberProgress(performance.now()),\n 1000,\n );\n }\n\n /**\n * Runs one progress-monitor tick: samples each active subscriber's change\n * rate, logs slow broadcasts, and disconnects subscribers that have been\n * continuously lagging for longer than the configured grace period.\n *\n * Invoked on an interval by {@link startProgressMonitor()}; exposed (rather\n * than private) so tests can drive a single tick with an explicit `now`.\n */\n checkSubscriberProgress(now: number) {\n let timedOut: Map<Subscriber, SubscriberStats> | undefined;\n let slowestOnTime: SubscriberStats | undefined;\n\n for (const sub of this.#active) {\n const stats = sub.sampleProcessRate(now).getStats();\n if (stats.missedLastTimeout) {\n (timedOut ??= new Map()).set(sub, stats);\n } else if (\n stats.processRate <= (slowestOnTime?.processRate ?? Infinity)\n ) {\n slowestOnTime = stats;\n }\n }\n this.#currentBroadcast?.logSlowRequests(now);\n\n if (\n this.#flowControlSlowSubscriberGracePeriodMs &&\n timedOut?.size &&\n slowestOnTime\n ) {\n for (const [sub, stats] of timedOut.entries()) {\n const rate =\n stats.processRate < slowestOnTime.processRate\n ? 'lagging'\n : 'catching-up';\n const laggingDuration = sub.reportChangeRate(now, rate);\n if (laggingDuration >= this.#flowControlSlowSubscriberGracePeriodMs) {\n this.#lc.warn?.(\n `subscriber ${sub.id} has lagged for ${laggingDuration}ms. disconnecting`,\n {stats},\n );\n sub.close(\n StreamTooFarBehind,\n `lagging for over ${this.#flowControlSlowSubscriberGracePeriodMs}ms (${laggingDuration}ms)`,\n );\n }\n }\n }\n }\n\n stopProgressMonitor() {\n clearInterval(this.#progressMonitor);\n }\n\n /**\n * `add()` is called in lock step with `Storer.catchup()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n */\n add(sub: Subscriber) {\n if (this.#inTransaction) {\n this.#queued.add(sub);\n } else {\n this.#active.add(sub);\n }\n }\n\n remove(sub: Subscriber) {\n this.#active.delete(sub);\n this.#queued.delete(sub);\n sub.close();\n }\n\n /**\n * `forward()` is called in lockstep with `Storer.store()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n *\n * This version of forward is fire-and-forget, with no flow control. The\n * change-streamer should call and await {@link forwardWithFlowControl()}\n * occasionally to avoid memory blowup.\n */\n forward(entry: WatermarkedChange) {\n Broadcast.withoutTracking(this.#active.values(), entry);\n this.#updateActiveSubscribers(entry[1]);\n }\n\n sendStatus(status: Status) {\n for (const sub of this.#active.values()) {\n sub.sendStatus(status);\n }\n }\n\n /**\n * The flow-control-aware equivalent of {@link forward()}, returning a\n * Promise that resolves when replication should continue.\n */\n async forwardWithFlowControl(entry: WatermarkedChange) {\n const start = performance.now();\n const broadcast = new Broadcast(\n this.#lc,\n this.#active.values(),\n entry,\n this.#earlyReleaseOptions,\n );\n this.#updateActiveSubscribers(entry[1]);\n\n // set for progress tracking\n this.#currentBroadcast = broadcast;\n\n try {\n await broadcast.done;\n const attributes = {'release.mode': broadcast.releaseMode};\n this.#flowControlWaits.add(1, attributes);\n this.#flowControlWaitTime.recordMs(performance.now() - start, attributes);\n } finally {\n // Technically #currentBroadcast may have changed, so only\n // unset if it if is still the same.\n if (this.#currentBroadcast === broadcast) {\n this.#currentBroadcast = undefined;\n }\n }\n }\n\n #subscriberStats() {\n let pendingMessages = 0;\n let backlogMessages = 0;\n let backlogBytes = 0;\n let maxBacklogBytes = 0;\n\n for (const sub of joinIterables(this.#active, this.#queued)) {\n const stats = sub.getStats();\n pendingMessages += stats.pending;\n backlogMessages += stats.backlog;\n backlogBytes += stats.backlogBytes;\n maxBacklogBytes = Math.max(maxBacklogBytes, stats.backlogBytes);\n }\n\n return {pendingMessages, backlogMessages, backlogBytes, maxBacklogBytes};\n }\n\n #updateActiveSubscribers(tag: ChangeTag) {\n switch (tag) {\n case 'begin':\n // While in a Transaction, all added subscribers are \"queued\" so that no\n // messages are forwarded to them. This state corresponds to being queued\n // for catchup in the Storer, which will retrieve historic changes\n // and call catchup() once the current transaction is committed.\n this.#inTransaction = true;\n break;\n case 'commit':\n case 'rollback':\n // Upon commit or rollback, all queued subscribers are transferred to\n // the active set. This means that they can receive messages starting\n // from the next transaction.\n //\n // Note that if catchup is still in progress (in the Storer), these messages\n // will be buffered in the backlog until catchup completes.\n this.#inTransaction = false;\n for (const sub of this.#queued.values()) {\n this.#active.add(sub);\n }\n this.#queued.clear();\n break;\n }\n }\n\n getAcks(): Set<string> {\n return new Set(\n joinIterables(\n wrapIterable(this.#active).map(s => s.acked),\n wrapIterable(this.#queued).map(s => s.acked),\n ),\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
import type { SnapshotMessage } from './snapshot.ts';
|
|
6
|
-
export declare const CHECK_INTERVAL_MS = 60000;
|
|
2
|
+
import type { Source } from '../../types/streams.ts';
|
|
3
|
+
import type { BackedUpWatermark } from './backup-monitor.ts';
|
|
4
|
+
export declare const CHECK_INTERVAL_MS = 10000;
|
|
7
5
|
/**
|
|
8
6
|
* Allowance for clock skew between the machine reporting litestream metrics
|
|
9
7
|
* and the timestamps reported by the backup destination (e.g. S3).
|
|
@@ -62,53 +60,29 @@ export declare const RESTORABLE_BACKUP_POLL_INTERVAL_MS = 10000;
|
|
|
62
60
|
*/
|
|
63
61
|
export type BackupStateVerifier = () => Promise<Date>;
|
|
64
62
|
/**
|
|
65
|
-
* The
|
|
63
|
+
* The Litestream3PrometheusPoller polls the litestream "/metrics" endpoint to
|
|
66
64
|
* track the watermark (label) value of the `litestream_replica_progress` gauge
|
|
67
|
-
* and
|
|
65
|
+
* and emits verified watermarks to a stream of {@link BackedUpWatermark}s.
|
|
68
66
|
*
|
|
69
67
|
* See: https://github.com/rocicorp/litestream/pull/3
|
|
70
68
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* purging changelog records as soon as those watermarks are replicated would
|
|
77
|
-
* result in the zero-cache not being able to catch up from minute 0 once it
|
|
78
|
-
* has finished restoring the replica.
|
|
79
|
-
*
|
|
80
|
-
* The `/snapshot` reservation protocol is used to prevent premature change
|
|
81
|
-
* log cleanup:
|
|
82
|
-
* - Clients restoring a snapshot initiate a `/snapshot` request and hold that
|
|
83
|
-
* request open while it restores its snapshot, prepares it, and
|
|
84
|
-
* starts its subscription to the change stream. During this time, no
|
|
85
|
-
* cleanups are scheduled.
|
|
86
|
-
* - When the subscription is started, the interval since the beginning of
|
|
87
|
-
* of the reservation is tracked to increase the background cleanup delay
|
|
88
|
-
* interval if needed. The reservation is ended (and request closed), and
|
|
89
|
-
* cleanup scheduling is resumed with the current delay interval.
|
|
90
|
-
*
|
|
91
|
-
* Note that the reservation request is the primary mechanism by which
|
|
92
|
-
* premature change log cleanup is prevented. The cleanup delay interval is
|
|
93
|
-
* a secondary safeguard.
|
|
94
|
-
*
|
|
95
|
-
* Additionally, because the watermarks reported by litestream metrics
|
|
96
|
-
* reflect what litestream *believes* has been backed up (which has been
|
|
97
|
-
* observed to diverge from reality when uploads silently fail), the cleanup
|
|
98
|
-
* watermark is only advanced after verifying it against the actual backup
|
|
99
|
-
* state in the replica destination via a {@link BackupStateVerifier}. If the
|
|
69
|
+
* Because the watermarks reported by litestream metrics reflect what
|
|
70
|
+
* litestream *believes* has been backed up (which has been observed to
|
|
71
|
+
* diverge from reality when uploads silently fail), the cleanup watermark is
|
|
72
|
+
* only published after verifying it against the actual backup state in the
|
|
73
|
+
* replica destination via a {@link BackupStateVerifier}. If the
|
|
100
74
|
* actual backup state stays behind the claimed progress for longer than
|
|
101
75
|
* {@link WEDGED_SHUTDOWN_GRACE_MS}, the backup is treated as wedged and the
|
|
102
76
|
* process is shut down rather than risk corrupting the backup.
|
|
103
77
|
*/
|
|
104
|
-
export declare class
|
|
78
|
+
export declare class Litestream3PrometheusPoller {
|
|
105
79
|
#private;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
80
|
+
constructor(lc: LogContext, replicaFile: string, backupURL: string, metricsEndpoint: string, verifyBackupState: BackupStateVerifier);
|
|
81
|
+
/**
|
|
82
|
+
* Starts polling for watermarks and pushes new ones to returned stream.
|
|
83
|
+
* Must only be called once.
|
|
84
|
+
*/
|
|
85
|
+
start(): Source<BackedUpWatermark>;
|
|
86
|
+
readonly checkVerifyAndPublishWatermarks: () => Promise<void>;
|
|
113
87
|
}
|
|
114
|
-
//# sourceMappingURL=litestream3-
|
|
88
|
+
//# sourceMappingURL=litestream3-prometheus-poller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"litestream3-prometheus-poller.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAKjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAOnD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,wBAAwB,QAAc,CAAC;AAIpD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gCAAgC,QAAc,CAAC;AAG5D;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,QAAS,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,2BAA2B;;gBAwCpC,EAAE,EAAE,UAAU,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,mBAAmB;IAexC;;;OAGG;IACH,KAAK,IAAI,MAAM,CAAC,iBAAiB,CAAC;IA8BlC,QAAQ,CAAC,+BAA+B,sBAgBtC;CA2RH"}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { sleep } from "../../../../shared/src/sleep.js";
|
|
1
|
+
import { assert } from "../../../../shared/src/asserts.js";
|
|
3
2
|
import { must } from "../../../../shared/src/must.js";
|
|
4
|
-
import {
|
|
5
|
-
import { getOrCreateCounter, getOrCreateGauge } from "../../observability/metrics.js";
|
|
3
|
+
import { getOrCreateCounter } from "../../observability/metrics.js";
|
|
6
4
|
import { RunningState, UnrecoverableError } from "../running-state.js";
|
|
7
5
|
import { Subscription } from "../../types/subscription.js";
|
|
8
|
-
import { litestreamBackupVerificationDuration, litestreamMonitorMetricAttrs
|
|
9
|
-
import { resolver } from "@rocicorp/resolver";
|
|
6
|
+
import { litestreamBackupVerificationDuration, litestreamMonitorMetricAttrs } from "../litestream/metrics.js";
|
|
10
7
|
import { statSync } from "node:fs";
|
|
11
8
|
import parsePrometheusTextFormat from "parse-prometheus-text-format";
|
|
12
|
-
//#region ../zero-cache/src/services/change-streamer/litestream3-
|
|
13
|
-
var CHECK_INTERVAL_MS =
|
|
14
|
-
var MIN_CLEANUP_DELAY_MS = 3e4;
|
|
9
|
+
//#region ../zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts
|
|
10
|
+
var CHECK_INTERVAL_MS = 1e4;
|
|
15
11
|
/**
|
|
16
12
|
* Allowance for clock skew between the machine reporting litestream metrics
|
|
17
13
|
* and the timestamps reported by the backup destination (e.g. S3).
|
|
@@ -57,126 +53,58 @@ var GiB = 1024 ** 3;
|
|
|
57
53
|
var INITIAL_BACKUP_GRACE_MS_PER_UNIT = 60 * 6e4;
|
|
58
54
|
var INITIAL_BACKUP_GRACE_UNIT_BYTES = 50 * GiB;
|
|
59
55
|
/**
|
|
60
|
-
*
|
|
61
|
-
* appeared while a view-syncer holds a snapshot reservation open during a cold
|
|
62
|
-
* start.
|
|
63
|
-
*/
|
|
64
|
-
var RESTORABLE_BACKUP_POLL_INTERVAL_MS = 1e4;
|
|
65
|
-
/**
|
|
66
|
-
* The Litestream3BackupMonitor polls the litestream "/metrics" endpoint to
|
|
56
|
+
* The Litestream3PrometheusPoller polls the litestream "/metrics" endpoint to
|
|
67
57
|
* track the watermark (label) value of the `litestream_replica_progress` gauge
|
|
68
|
-
* and
|
|
58
|
+
* and emits verified watermarks to a stream of {@link BackedUpWatermark}s.
|
|
69
59
|
*
|
|
70
60
|
* See: https://github.com/rocicorp/litestream/pull/3
|
|
71
61
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* purging changelog records as soon as those watermarks are replicated would
|
|
78
|
-
* result in the zero-cache not being able to catch up from minute 0 once it
|
|
79
|
-
* has finished restoring the replica.
|
|
80
|
-
*
|
|
81
|
-
* The `/snapshot` reservation protocol is used to prevent premature change
|
|
82
|
-
* log cleanup:
|
|
83
|
-
* - Clients restoring a snapshot initiate a `/snapshot` request and hold that
|
|
84
|
-
* request open while it restores its snapshot, prepares it, and
|
|
85
|
-
* starts its subscription to the change stream. During this time, no
|
|
86
|
-
* cleanups are scheduled.
|
|
87
|
-
* - When the subscription is started, the interval since the beginning of
|
|
88
|
-
* of the reservation is tracked to increase the background cleanup delay
|
|
89
|
-
* interval if needed. The reservation is ended (and request closed), and
|
|
90
|
-
* cleanup scheduling is resumed with the current delay interval.
|
|
91
|
-
*
|
|
92
|
-
* Note that the reservation request is the primary mechanism by which
|
|
93
|
-
* premature change log cleanup is prevented. The cleanup delay interval is
|
|
94
|
-
* a secondary safeguard.
|
|
95
|
-
*
|
|
96
|
-
* Additionally, because the watermarks reported by litestream metrics
|
|
97
|
-
* reflect what litestream *believes* has been backed up (which has been
|
|
98
|
-
* observed to diverge from reality when uploads silently fail), the cleanup
|
|
99
|
-
* watermark is only advanced after verifying it against the actual backup
|
|
100
|
-
* state in the replica destination via a {@link BackupStateVerifier}. If the
|
|
62
|
+
* Because the watermarks reported by litestream metrics reflect what
|
|
63
|
+
* litestream *believes* has been backed up (which has been observed to
|
|
64
|
+
* diverge from reality when uploads silently fail), the cleanup watermark is
|
|
65
|
+
* only published after verifying it against the actual backup state in the
|
|
66
|
+
* replica destination via a {@link BackupStateVerifier}. If the
|
|
101
67
|
* actual backup state stays behind the claimed progress for longer than
|
|
102
68
|
* {@link WEDGED_SHUTDOWN_GRACE_MS}, the backup is treated as wedged and the
|
|
103
69
|
* process is shut down rather than risk corrupting the backup.
|
|
104
70
|
*/
|
|
105
|
-
var
|
|
106
|
-
id = "backup-monitor";
|
|
71
|
+
var Litestream3PrometheusPoller = class {
|
|
107
72
|
#lc;
|
|
108
73
|
#replicaFile;
|
|
109
74
|
#backupURL;
|
|
110
75
|
#metricsEndpoint;
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#reservations = /* @__PURE__ */ new Map();
|
|
76
|
+
#state = new RunningState("litestream3-prometheus-poller");
|
|
77
|
+
#stream;
|
|
114
78
|
#watermarks = /* @__PURE__ */ new Map();
|
|
115
79
|
#verifyBackupState;
|
|
116
80
|
#purgesBlocked = getOrCreateCounter("replica", "purge_blocked", { description: "Number of change-log purges blocked because the actual backup state (as listed from the replica destination) could not be verified, or is older than the backup progress claimed by litestream metrics. A steadily increasing value indicates a wedged or failing backup. The \"backup-wedged\" reason is emitted once just before the process shuts itself down due to a persistently stale backup." });
|
|
117
|
-
#fatal = resolver();
|
|
118
81
|
#lastWatermark = "";
|
|
119
82
|
#latestBackupTime = null;
|
|
120
83
|
#lastVerifiedUploadTime = null;
|
|
121
84
|
#backupStaleSince = null;
|
|
122
85
|
#runStartTime = null;
|
|
123
|
-
#cleanupDelayMs;
|
|
124
86
|
#checkMetricsTimer;
|
|
125
|
-
constructor(lc, replicaFile, backupURL, metricsEndpoint,
|
|
126
|
-
this.#lc = lc.withContext("component",
|
|
87
|
+
constructor(lc, replicaFile, backupURL, metricsEndpoint, verifyBackupState) {
|
|
88
|
+
this.#lc = lc.withContext("component", "litestream3-prometheus-poller");
|
|
127
89
|
this.#replicaFile = replicaFile;
|
|
128
90
|
this.#backupURL = backupURL;
|
|
129
91
|
this.#metricsEndpoint = metricsEndpoint;
|
|
130
|
-
this.#changeStreamer = changeStreamer;
|
|
131
92
|
this.#verifyBackupState = verifyBackupState;
|
|
132
|
-
this.#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.#runStartTime = Date.now();
|
|
137
|
-
this.#lc.info?.(`monitoring backups at ${this.#metricsEndpoint} with ${this.#cleanupDelayMs} ms cleanup delay`);
|
|
138
|
-
this.#checkMetricsTimer = setInterval(this.checkWatermarksAndScheduleCleanup, CHECK_INTERVAL_MS);
|
|
139
|
-
this.#initBackupLagMetric();
|
|
140
|
-
return Promise.race([this.#state.stopped(), this.#fatal.promise]);
|
|
141
|
-
}
|
|
142
|
-
startSnapshotReservation(taskID) {
|
|
143
|
-
this.#lc.info?.(`pausing change-log cleanup while ${taskID} snapshots`);
|
|
144
|
-
this.#reservations.get(taskID)?.sub.cancel();
|
|
145
|
-
const sub = Subscription.create({ cleanup: () => this.endReservation(taskID, false) });
|
|
146
|
-
this.#reservations.set(taskID, {
|
|
147
|
-
start: /* @__PURE__ */ new Date(),
|
|
148
|
-
sub
|
|
149
|
-
});
|
|
150
|
-
this.#pushStatusWhenRestorable(taskID, sub);
|
|
151
|
-
return sub;
|
|
93
|
+
this.#stream = Subscription.create({ cleanup: () => {
|
|
94
|
+
clearInterval(this.#checkMetricsTimer);
|
|
95
|
+
this.#state.stop(this.#lc);
|
|
96
|
+
} });
|
|
152
97
|
}
|
|
153
98
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* cold start (a fresh stack whose first backup is still being produced) the
|
|
157
|
-
* reservation is held open — kept alive by websocket liveness pings — and the
|
|
158
|
-
* view-syncer stays unready until the first backup lands, rather than being
|
|
159
|
-
* told to restore a backup that does not yet exist.
|
|
99
|
+
* Starts polling for watermarks and pushes new ones to returned stream.
|
|
100
|
+
* Must only be called once.
|
|
160
101
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
await sleep(RESTORABLE_BACKUP_POLL_INTERVAL_MS, this.#state.signal);
|
|
168
|
-
}
|
|
169
|
-
const changeLogState = await this.#changeStreamer.getChangeLogState();
|
|
170
|
-
if (sub.active) sub.push(["status", {
|
|
171
|
-
tag: "status",
|
|
172
|
-
backupURL: this.#backupURL,
|
|
173
|
-
...changeLogState
|
|
174
|
-
}]);
|
|
175
|
-
} catch (e) {
|
|
176
|
-
if (this.#state.signal.aborted) return;
|
|
177
|
-
this.#lc.warn?.(`failing snapshot reservation`, e);
|
|
178
|
-
sub.fail(e instanceof Error ? e : new Error(String(e)));
|
|
179
|
-
}
|
|
102
|
+
start() {
|
|
103
|
+
assert(this.#runStartTime === null, `Already called start()`);
|
|
104
|
+
this.#runStartTime = Date.now();
|
|
105
|
+
this.#lc.info?.(`monitoring backups at ${this.#metricsEndpoint}`);
|
|
106
|
+
this.#checkMetricsTimer = setInterval(this.checkVerifyAndPublishWatermarks, CHECK_INTERVAL_MS);
|
|
107
|
+
return this.#stream;
|
|
180
108
|
}
|
|
181
109
|
/**
|
|
182
110
|
* Verifies whether a restorable backup actually exists in the destination
|
|
@@ -195,33 +123,14 @@ var Litestream3BackupMonitor = class {
|
|
|
195
123
|
return false;
|
|
196
124
|
}
|
|
197
125
|
}
|
|
198
|
-
|
|
199
|
-
const res = this.#reservations.get(taskID);
|
|
200
|
-
if (res === void 0) return;
|
|
201
|
-
this.#reservations.delete(taskID);
|
|
202
|
-
const { start, sub } = res;
|
|
203
|
-
sub.cancel();
|
|
204
|
-
const duration = Date.now() - start.getTime();
|
|
205
|
-
litestreamSnapshotReservationDuration().recordMs(duration, {
|
|
206
|
-
...litestreamMonitorMetricAttrs(this.#backupURL, "legacy", "view_syncer"),
|
|
207
|
-
result: updateCleanupDelay ? "success" : "cancelled"
|
|
208
|
-
});
|
|
209
|
-
if (updateCleanupDelay) {
|
|
210
|
-
this.#lc.info?.(`snapshot initialized by ${taskID} in ${duration} ms`);
|
|
211
|
-
if (duration > this.#cleanupDelayMs) {
|
|
212
|
-
this.#cleanupDelayMs = duration;
|
|
213
|
-
this.#lc.info?.(`increased cleanup delay to ${duration} ms`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
checkWatermarksAndScheduleCleanup = async () => {
|
|
126
|
+
checkVerifyAndPublishWatermarks = async () => {
|
|
218
127
|
try {
|
|
219
128
|
await this.#checkWatermarks();
|
|
220
129
|
} catch (e) {
|
|
221
130
|
this.#lc.warn?.(`unable to fetch metrics at ${this.#metricsEndpoint}`, e);
|
|
222
131
|
}
|
|
223
132
|
try {
|
|
224
|
-
await this.#
|
|
133
|
+
await this.#publishVerifiedWatermarks();
|
|
225
134
|
} catch (e) {
|
|
226
135
|
this.#lc.warn?.(`error scheduling cleanup`, e);
|
|
227
136
|
}
|
|
@@ -266,15 +175,10 @@ var Litestream3BackupMonitor = class {
|
|
|
266
175
|
}
|
|
267
176
|
return this.#latestBackupTime;
|
|
268
177
|
}
|
|
269
|
-
async #
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
const latestCleanupTime = Date.now() - this.#cleanupDelayMs;
|
|
275
|
-
const maxWatermark = this.#maxWatermarkUpTo(latestCleanupTime);
|
|
276
|
-
if (maxWatermark.length === 0) return;
|
|
277
|
-
const claimedTime = must(this.#watermarks.get(maxWatermark));
|
|
178
|
+
async #publishVerifiedWatermarks() {
|
|
179
|
+
const maxBackup = this.#maxBackupBefore(Date.now());
|
|
180
|
+
if (!maxBackup) return;
|
|
181
|
+
const { watermark: maxWatermark, backupTime: claimedTime } = maxBackup;
|
|
278
182
|
if (!this.#confirmedDurable(claimedTime)) try {
|
|
279
183
|
this.#lastVerifiedUploadTime = await this.#verifyBackupStateTimed();
|
|
280
184
|
} catch (e) {
|
|
@@ -283,8 +187,8 @@ var Litestream3BackupMonitor = class {
|
|
|
283
187
|
return;
|
|
284
188
|
}
|
|
285
189
|
const lastUpload = must(this.#lastVerifiedUploadTime);
|
|
286
|
-
const
|
|
287
|
-
if (
|
|
190
|
+
const verifiedBackup = this.#maxBackupBefore(lastUpload.getTime() + BACKUP_VERIFICATION_SLACK_MS);
|
|
191
|
+
if (!verifiedBackup) {
|
|
288
192
|
this.#purgesBlocked.add(1, { reason: "backup-stale" });
|
|
289
193
|
const now = Date.now();
|
|
290
194
|
if (this.#backupStaleSince === null) this.#backupStaleSince = now;
|
|
@@ -294,8 +198,12 @@ var Litestream3BackupMonitor = class {
|
|
|
294
198
|
return;
|
|
295
199
|
}
|
|
296
200
|
this.#backupStaleSince = null;
|
|
297
|
-
|
|
201
|
+
const { watermark: verifiedWatermark, backupTime } = verifiedBackup;
|
|
298
202
|
for (const watermark of this.#watermarks.keys()) if (watermark <= verifiedWatermark) this.#watermarks.delete(watermark);
|
|
203
|
+
this.#stream.push({
|
|
204
|
+
watermark: verifiedWatermark,
|
|
205
|
+
backupTimeMs: backupTime.getTime()
|
|
206
|
+
});
|
|
299
207
|
this.#lastWatermark = verifiedWatermark;
|
|
300
208
|
}
|
|
301
209
|
async #verifyBackupStateTimed() {
|
|
@@ -324,8 +232,7 @@ var Litestream3BackupMonitor = class {
|
|
|
324
232
|
this.#purgesBlocked.add(1, { reason: "backup-wedged" });
|
|
325
233
|
const err = new UnrecoverableError(`backup at ${this.#backupURL} is wedged: litestream claims a backup at ${claimedTime.toISOString()}, but the last object actually uploaded was at ${lastUpload.toISOString()}, and the backup has not advanced for ${staleForMs} ms (grace period ${WEDGED_SHUTDOWN_GRACE_MS} ms). Shutting down to avoid corrupting the backup; investigate the backup destination.`);
|
|
326
234
|
this.#lc.error?.(err.message);
|
|
327
|
-
|
|
328
|
-
this.#fatal.reject(err);
|
|
235
|
+
this.#stream.fail(err);
|
|
329
236
|
}
|
|
330
237
|
/**
|
|
331
238
|
* On a fresh stack, fails loudly if no restorable backup has appeared within
|
|
@@ -365,17 +272,24 @@ var Litestream3BackupMonitor = class {
|
|
|
365
272
|
#shutDownOnMissingInitialBackup(elapsedMs) {
|
|
366
273
|
const err = new UnrecoverableError(`no restorable backup has appeared at ${this.#backupURL} within ${elapsedMs} ms of startup (grace period ${this.#initialBackupGraceMs()} ms). The initial backup pipeline appears to be broken; shutting down so that the replication-manager is flagged as the cause rather than the view-syncers waiting on it. Investigate litestream replication to ${this.#backupURL}.`);
|
|
367
274
|
this.#lc.error?.(err.message);
|
|
368
|
-
|
|
369
|
-
this.#fatal.reject(err);
|
|
275
|
+
this.#stream.fail(err);
|
|
370
276
|
}
|
|
371
277
|
/**
|
|
372
278
|
* Returns the newest watermark whose backup time is at or before `cutoff`
|
|
373
279
|
* (epoch ms), or `''` if there is none.
|
|
374
280
|
*/
|
|
375
|
-
#
|
|
281
|
+
#maxBackupBefore(cutoff) {
|
|
376
282
|
let max = "";
|
|
377
|
-
|
|
378
|
-
|
|
283
|
+
let maxBackupTime;
|
|
284
|
+
for (const [watermark, backupTime] of this.#watermarks) if (backupTime.getTime() <= cutoff && watermark > max) {
|
|
285
|
+
max = watermark;
|
|
286
|
+
maxBackupTime = backupTime;
|
|
287
|
+
}
|
|
288
|
+
if (!maxBackupTime) return null;
|
|
289
|
+
return {
|
|
290
|
+
watermark: max,
|
|
291
|
+
backupTime: maxBackupTime
|
|
292
|
+
};
|
|
379
293
|
}
|
|
380
294
|
/**
|
|
381
295
|
* Returns `true` if the actual backup state, as last verified against the
|
|
@@ -388,36 +302,8 @@ var Litestream3BackupMonitor = class {
|
|
|
388
302
|
const lastUpload = this.#lastVerifiedUploadTime;
|
|
389
303
|
return lastUpload !== null && claimedTime.getTime() <= lastUpload.getTime() + 6e4;
|
|
390
304
|
}
|
|
391
|
-
stop() {
|
|
392
|
-
clearInterval(this.#checkMetricsTimer);
|
|
393
|
-
for (const { sub } of this.#reservations.values()) sub.cancel();
|
|
394
|
-
this.#state.stop(this.#lc);
|
|
395
|
-
return promiseVoid;
|
|
396
|
-
}
|
|
397
|
-
#initBackupLagMetric() {
|
|
398
|
-
getOrCreateGauge("replica", "backup_lag", {
|
|
399
|
-
description: "Latency from when a change is written to the replica to when it is backed up to litestream. It is expected to create a saw pattern from 0 to the configured ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES.",
|
|
400
|
-
unit: "millisecond"
|
|
401
|
-
}).addCallback(async (o) => {
|
|
402
|
-
const latestBackup = await this.#checkWatermarks();
|
|
403
|
-
if (!latestBackup) {
|
|
404
|
-
this.#lc.warn?.(`no backed up watermarks. unable to report replica.backup_lag`);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const db = new Database(this.#lc, this.#replicaFile, { readonly: true });
|
|
408
|
-
try {
|
|
409
|
-
const { writeTimeMs } = db.prepare(`SELECT writeTimeMs FROM "_zero.replicationState"`).get();
|
|
410
|
-
const backupLag = Math.max(0, writeTimeMs - latestBackup.getTime());
|
|
411
|
-
o.observe(backupLag);
|
|
412
|
-
} catch (e) {
|
|
413
|
-
this.#lc.warn?.(`error measuring replica.backup_lag metric`, e);
|
|
414
|
-
} finally {
|
|
415
|
-
db.close();
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
305
|
};
|
|
420
306
|
//#endregion
|
|
421
|
-
export {
|
|
307
|
+
export { Litestream3PrometheusPoller };
|
|
422
308
|
|
|
423
|
-
//# sourceMappingURL=litestream3-
|
|
309
|
+
//# sourceMappingURL=litestream3-prometheus-poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"litestream3-prometheus-poller.js","names":["#lc","#replicaFile","#backupURL","#metricsEndpoint","#state","#stream","#watermarks","#verifyBackupState","#purgesBlocked","#checkMetricsTimer","#runStartTime","#confirmRestorableBackup","#lastVerifiedUploadTime","#verifyBackupStateTimed","#checkWatermarks","#publishVerifiedWatermarks","#checkInitialBackupDeadline","#fetchWatermarks","#lastWatermark","#latestBackupTime","#maxBackupBefore","#confirmedDurable","#backupStaleSince","#shutDownOnWedgedBackup","#initialBackupGraceMs","#shutDownOnMissingInitialBackup"],"sources":["../../../../../../zero-cache/src/services/change-streamer/litestream3-prometheus-poller.ts"],"sourcesContent":["import {statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport parsePrometheusTextFormat from 'parse-prometheus-text-format';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {getOrCreateCounter} from '../../observability/metrics.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport {\n litestreamBackupVerificationDuration,\n litestreamMonitorMetricAttrs,\n} from '../litestream/metrics.ts';\nimport {RunningState, UnrecoverableError} from '../running-state.ts';\nimport type {BackedUpWatermark} from './backup-monitor.ts';\n\nexport const CHECK_INTERVAL_MS = 10_000;\n\n/**\n * Allowance for clock skew between the machine reporting litestream metrics\n * and the timestamps reported by the backup destination (e.g. S3).\n */\nexport const BACKUP_VERIFICATION_SLACK_MS = 60_000;\n\n/**\n * How long the actual backup state may remain *continuously* behind the\n * backup progress claimed by litestream metrics before the backup is\n * considered genuinely wedged and the process is shut down.\n *\n * A wedged backup is dangerous: litestream believes it is making durable\n * progress when it is not, which can silently corrupt the backup (the WAL\n * format only makes *some* such gaps detectable). Once that is confirmed to\n * be the persistent state, continuing to run is worse than crashing, so the\n * process exits loudly (non-zero, with a logged error) and the wedged backup\n * destination becomes the priority to investigate.\n *\n * The change-log is conservatively *not* purged for the entire time the\n * backup is stale, so the only cost of a generous grace period is unbounded\n * change-log growth. We therefore err well on the side of slack: the backup\n * must stay stuck across many {@link CHECK_INTERVAL_MS} checks before we tear\n * the server down, so that a transient hiccup (a slow or briefly unreachable\n * destination, or litestream restarting) does not trigger a shutdown. The\n * staleness clock is reset the moment a purge is confirmed against a real\n * upload.\n */\nexport const WEDGED_SHUTDOWN_GRACE_MS = 15 * 60_000; // 15 minutes\n\nconst GiB = 1024 ** 3;\n\n/**\n * How long the BackupMonitor waits for the *first* restorable backup to appear\n * before concluding that the backup pipeline is broken and shutting the process\n * down (see {@link BackupMonitor.#shutDownOnMissingInitialBackup}).\n *\n * On a fresh stack the first backup is not restorable until the initial\n * Postgres->replica sync completes and litestream finishes uploading the\n * initial snapshot — a multipart upload that is not listable in the destination\n * until it is committed. The producer cannot observe that sub-snapshot progress,\n * so this is necessarily a generous timeout. It is scaled by replica size to\n * mirror the platform's storage-scaled startup-probe allowance (~1 hour per\n * 50GB) so that it never fires before the platform would restart a view-syncer\n * that is waiting on the backup.\n */\nexport const INITIAL_BACKUP_GRACE_MS_PER_UNIT = 60 * 60_000; // 1 hour ...\nconst INITIAL_BACKUP_GRACE_UNIT_BYTES = 50 * GiB; // ... per 50 GiB\n\n/**\n * How often the BackupMonitor re-checks whether the first restorable backup has\n * appeared while a view-syncer holds a snapshot reservation open during a cold\n * start.\n */\nexport const RESTORABLE_BACKUP_POLL_INTERVAL_MS = 10_000;\n\n/**\n * Returns the time of the most recent object actually uploaded to the\n * backup replica destination (e.g. as determined by listing the snapshots\n * and WAL segments in S3). Rejects if the backup state cannot be determined.\n *\n * See `getLastBackupTime()` in `../litestream/commands.ts` for the\n * production implementation.\n */\nexport type BackupStateVerifier = () => Promise<Date>;\n\n/**\n * The Litestream3PrometheusPoller polls the litestream \"/metrics\" endpoint to\n * track the watermark (label) value of the `litestream_replica_progress` gauge\n * and emits verified watermarks to a stream of {@link BackedUpWatermark}s.\n *\n * See: https://github.com/rocicorp/litestream/pull/3\n *\n * Because the watermarks reported by litestream metrics reflect what\n * litestream *believes* has been backed up (which has been observed to\n * diverge from reality when uploads silently fail), the cleanup watermark is\n * only published after verifying it against the actual backup state in the\n * replica destination via a {@link BackupStateVerifier}. If the\n * actual backup state stays behind the claimed progress for longer than\n * {@link WEDGED_SHUTDOWN_GRACE_MS}, the backup is treated as wedged and the\n * process is shut down rather than risk corrupting the backup.\n */\nexport class Litestream3PrometheusPoller {\n readonly #lc: LogContext;\n readonly #replicaFile: string;\n readonly #backupURL: string;\n readonly #metricsEndpoint: string;\n readonly #state = new RunningState('litestream3-prometheus-poller');\n\n readonly #stream: Subscription<BackedUpWatermark>;\n readonly #watermarks = new Map<string, Date>();\n\n readonly #verifyBackupState: BackupStateVerifier;\n readonly #purgesBlocked = getOrCreateCounter('replica', 'purge_blocked', {\n description:\n 'Number of change-log purges blocked because the actual backup state ' +\n '(as listed from the replica destination) could not be verified, or ' +\n 'is older than the backup progress claimed by litestream metrics. ' +\n 'A steadily increasing value indicates a wedged or failing backup. ' +\n 'The \"backup-wedged\" reason is emitted once just before the process ' +\n 'shuts itself down due to a persistently stale backup.',\n });\n\n #lastWatermark: string = '';\n #latestBackupTime: Date | null = null;\n // The time of the most recent object actually verified in the backup\n // destination, or `null` if none has ever been verified. Only assigned from a\n // successful verifyBackupState() and never cleared, so a non-null value also\n // means a restorable backup has been confirmed to exist (which is what gates\n // the snapshot `status` signal and satisfies the initial-backup deadline).\n #lastVerifiedUploadTime: Date | null = null;\n // Epoch ms at which the actual backup state was first observed to be\n // continuously behind the watermark litestream claims to have backed up,\n // or `null` while the backup is keeping up. Reset whenever a purge is\n // confirmed against a real upload.\n #backupStaleSince: number | null = null;\n // Epoch ms at which run() started, used to bound how long to wait for the\n // first restorable backup to appear. `null` until run() is called.\n #runStartTime: number | null = null;\n #checkMetricsTimer: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n replicaFile: string,\n backupURL: string,\n metricsEndpoint: string,\n verifyBackupState: BackupStateVerifier,\n ) {\n this.#lc = lc.withContext('component', 'litestream3-prometheus-poller');\n this.#replicaFile = replicaFile;\n this.#backupURL = backupURL;\n this.#metricsEndpoint = metricsEndpoint;\n this.#verifyBackupState = verifyBackupState;\n this.#stream = Subscription.create<BackedUpWatermark>({\n cleanup: () => {\n clearInterval(this.#checkMetricsTimer);\n this.#state.stop(this.#lc);\n },\n });\n }\n\n /**\n * Starts polling for watermarks and pushes new ones to returned stream.\n * Must only be called once.\n */\n start(): Source<BackedUpWatermark> {\n assert(this.#runStartTime === null, `Already called start()`);\n this.#runStartTime = Date.now();\n this.#lc.info?.(`monitoring backups at ${this.#metricsEndpoint}`);\n this.#checkMetricsTimer = setInterval(\n this.checkVerifyAndPublishWatermarks,\n CHECK_INTERVAL_MS,\n );\n return this.#stream;\n }\n\n /**\n * Verifies whether a restorable backup actually exists in the destination\n * (as opposed to what litestream metrics merely claim). On success the\n * verified upload time is cached — seeding the cleanup-verification fast path\n * ({@link #confirmedDurable}) — and the initial-backup deadline is satisfied.\n * Returns false if no backup is listable yet (e.g. the initial snapshot is\n * still uploading).\n */\n async #confirmRestorableBackup(): Promise<boolean> {\n try {\n this.#lastVerifiedUploadTime = await this.#verifyBackupStateTimed();\n return true;\n } catch (e) {\n this.#lc.info?.(`backup not yet restorable at ${this.#backupURL}`, e);\n return false;\n }\n }\n\n // Exported for testing\n readonly checkVerifyAndPublishWatermarks = async () => {\n try {\n await this.#checkWatermarks();\n } catch (e) {\n this.#lc.warn?.(`unable to fetch metrics at ${this.#metricsEndpoint}`, e);\n }\n try {\n await this.#publishVerifiedWatermarks();\n } catch (e) {\n this.#lc.warn?.(`error scheduling cleanup`, e);\n }\n try {\n await this.#checkInitialBackupDeadline();\n } catch (e) {\n this.#lc.warn?.(`error checking initial backup deadline`, e);\n }\n };\n\n async *#fetchWatermarks(): AsyncGenerator<{\n watermark: string;\n time: Date;\n name?: string | undefined;\n }> {\n const metricsEndpoint = this.#metricsEndpoint;\n const signal = this.#state.signal;\n let resp;\n try {\n resp = await fetch(metricsEndpoint, {signal});\n } catch (e) {\n if (signal.aborted) {\n // not an error.\n return;\n }\n // Treat exceptions from fetch (e.g. network errors) as non-fatal, and simply\n // log them and skip the watermark check until the next interval.\n this.#lc.warn?.(`unable to fetch metrics at ${this.#metricsEndpoint}`, e);\n return;\n }\n if (!resp.ok) {\n this.#lc.warn?.(\n `unable to fetch metrics at ${this.#metricsEndpoint}: ${await resp.text()}`,\n );\n return;\n }\n\n const families = parsePrometheusTextFormat(await resp.text());\n for (const family of families) {\n if (\n family.type === 'GAUGE' &&\n family.name === 'litestream_replica_progress'\n ) {\n for (const metric of family.metrics) {\n const watermark = metric.labels?.watermark;\n const name = metric.labels?.name;\n const time = new Date(parseFloat(metric.value) * 1000);\n\n if (watermark) {\n yield {watermark, time, name};\n }\n }\n }\n }\n }\n\n async #checkWatermarks() {\n for await (const {watermark, name, time} of this.#fetchWatermarks()) {\n if (watermark > this.#lastWatermark && !this.#watermarks.has(watermark)) {\n this.#lc.info?.(\n `replicated watermark=${watermark} to ${name}` +\n ` at ${time.toISOString()}.`,\n );\n this.#watermarks.set(watermark, time);\n this.#latestBackupTime = time;\n }\n }\n return this.#latestBackupTime;\n }\n\n async #publishVerifiedWatermarks() {\n const maxBackup = this.#maxBackupBefore(Date.now());\n if (!maxBackup) {\n return;\n }\n const {watermark: maxWatermark, backupTime: claimedTime} = maxBackup;\n // Purge guard: the watermarks (and their backup times) come from\n // litestream metrics, which are exported when litestream *believes*\n // an upload succeeded, and have been observed to advance even when\n // nothing was actually written to the backup destination. Purging the\n // change-log based on a falsely advancing watermark permanently breaks\n // the ability to restore + catch up. Before advancing the cleanup\n // watermark, verify it against the actual backup state: a claimed\n // backup time is only trusted if an object was actually uploaded to\n // the replica destination at (or after) that time, modulo clock skew.\n if (!this.#confirmedDurable(claimedTime)) {\n try {\n this.#lastVerifiedUploadTime = await this.#verifyBackupStateTimed();\n } catch (e) {\n this.#purgesBlocked.add(1, {reason: 'verification-failed'});\n // Skipping the purge is safe: the change-log just grows.\n this.#lc.warn?.(\n `unable to verify backup state. skipping change-log cleanup ` +\n `up to watermark ${maxWatermark} ` +\n `(claimed backup time ${claimedTime.toISOString()})`,\n e,\n );\n return;\n }\n }\n // Watermarks whose backup time isn't yet confirmed durable remain in the\n // map and are re-evaluated at the next check.\n const lastUpload = must(this.#lastVerifiedUploadTime);\n const verifiedBackup = this.#maxBackupBefore(\n lastUpload.getTime() + BACKUP_VERIFICATION_SLACK_MS,\n );\n if (!verifiedBackup) {\n this.#purgesBlocked.add(1, {reason: 'backup-stale'});\n const now = Date.now();\n if (this.#backupStaleSince === null) {\n this.#backupStaleSince = now;\n }\n const staleForMs = now - this.#backupStaleSince;\n this.#lc.warn?.(\n `blocked change-log cleanup up to watermark ${maxWatermark}: ` +\n `litestream claims it was backed up at ` +\n `${claimedTime.toISOString()}, but the last object actually ` +\n `uploaded to ${this.#backupURL} was at ` +\n `${lastUpload.toISOString()}. ` +\n `The backup has been stale for ${staleForMs} ms.`,\n );\n if (staleForMs >= WEDGED_SHUTDOWN_GRACE_MS) {\n this.#shutDownOnWedgedBackup(staleForMs, claimedTime, lastUpload);\n }\n return;\n }\n // The cleanup watermark advanced past a real upload, so the backup is\n // keeping up: clear the staleness clock.\n this.#backupStaleSince = null;\n const {watermark: verifiedWatermark, backupTime} = verifiedBackup;\n for (const watermark of this.#watermarks.keys()) {\n if (watermark <= verifiedWatermark) {\n this.#watermarks.delete(watermark);\n }\n }\n this.#stream.push({\n watermark: verifiedWatermark,\n backupTimeMs: backupTime.getTime(),\n });\n this.#lastWatermark = verifiedWatermark;\n }\n\n async #verifyBackupStateTimed(): Promise<Date> {\n const start = performance.now();\n let result: 'success' | 'error' = 'error';\n try {\n const ret = await this.#verifyBackupState();\n result = 'success';\n return ret;\n } finally {\n litestreamBackupVerificationDuration().recordMs(\n performance.now() - start,\n {\n ...litestreamMonitorMetricAttrs(\n this.#backupURL,\n 'legacy',\n 'replication_manager',\n ),\n result,\n },\n );\n }\n }\n\n /**\n * Called when the backup has been continuously stale for longer than\n * {@link WEDGED_SHUTDOWN_GRACE_MS}. Continuing to run risks corrupting the\n * backup, so the process is shut down by rejecting the {@link run()}\n * promise. The exit is non-zero and logged at ERROR for alerting; on\n * restart the monitor re-verifies, so a still-wedged backup keeps the\n * process down until the destination is fixed.\n */\n #shutDownOnWedgedBackup(\n staleForMs: number,\n claimedTime: Date,\n lastUpload: Date,\n ) {\n this.#purgesBlocked.add(1, {reason: 'backup-wedged'});\n const err = new UnrecoverableError(\n `backup at ${this.#backupURL} is wedged: litestream claims a backup ` +\n `at ${claimedTime.toISOString()}, but the last object actually ` +\n `uploaded was at ${lastUpload.toISOString()}, and the backup has ` +\n `not advanced for ${staleForMs} ms (grace period ` +\n `${WEDGED_SHUTDOWN_GRACE_MS} ms). Shutting down to avoid corrupting ` +\n `the backup; investigate the backup destination.`,\n );\n this.#lc.error?.(err.message);\n this.#stream.fail(err);\n }\n\n /**\n * On a fresh stack, fails loudly if no restorable backup has appeared within\n * a generous, replica-size-scaled grace period (see\n * {@link INITIAL_BACKUP_GRACE_MS_PER_UNIT}). This moves the \"give up\" decision\n * onto the replication-manager — the producer responsible for creating the\n * backup — instead of leaving view-syncers to wait (and the platform to keep\n * restarting them) while the real fault is here. Does nothing once a\n * restorable backup has been confirmed.\n */\n async #checkInitialBackupDeadline() {\n if (this.#lastVerifiedUploadTime !== null || this.#runStartTime === null) {\n return;\n }\n const elapsed = Date.now() - this.#runStartTime;\n if (elapsed < this.#initialBackupGraceMs()) {\n return;\n }\n // The deadline has elapsed without a confirmed backup. Do one definitive\n // check against the destination before giving up, in case a backup landed\n // but was never exercised by a reservation or a cleanup verification.\n if (await this.#confirmRestorableBackup()) {\n return;\n }\n this.#shutDownOnMissingInitialBackup(elapsed);\n }\n\n /**\n * The grace period for the first restorable backup, scaled by replica size to\n * mirror the platform's storage-scaled startup-probe allowance. Falls back to\n * the minimum (one unit) if the replica file size cannot be determined.\n */\n #initialBackupGraceMs(): number {\n let bytes = 0;\n try {\n bytes = statSync(this.#replicaFile).size;\n } catch {\n // Replica file not present/readable yet; fall back to the minimum grace.\n }\n const units = Math.max(\n 1,\n Math.ceil(bytes / INITIAL_BACKUP_GRACE_UNIT_BYTES),\n );\n return units * INITIAL_BACKUP_GRACE_MS_PER_UNIT;\n }\n\n /**\n * Called when no restorable backup has appeared within\n * {@link #initialBackupGraceMs}. Continuing to run is pointless — no\n * view-syncer can restore — and the fault is the backup pipeline, so the\n * process exits non-zero with a logged error for alerting, mirroring\n * {@link #shutDownOnWedgedBackup}.\n */\n #shutDownOnMissingInitialBackup(elapsedMs: number) {\n const err = new UnrecoverableError(\n `no restorable backup has appeared at ${this.#backupURL} within ` +\n `${elapsedMs} ms of startup (grace period ` +\n `${this.#initialBackupGraceMs()} ms). The initial backup pipeline ` +\n `appears to be broken; shutting down so that the replication-manager ` +\n `is flagged as the cause rather than the view-syncers waiting on it. ` +\n `Investigate litestream replication to ${this.#backupURL}.`,\n );\n this.#lc.error?.(err.message);\n this.#stream.fail(err);\n }\n\n /**\n * Returns the newest watermark whose backup time is at or before `cutoff`\n * (epoch ms), or `''` if there is none.\n */\n #maxBackupBefore(\n cutoff: number,\n ): {watermark: string; backupTime: Date} | null {\n let max = '';\n let maxBackupTime: Date | undefined;\n for (const [watermark, backupTime] of this.#watermarks) {\n if (backupTime.getTime() <= cutoff && watermark > max) {\n max = watermark;\n maxBackupTime = backupTime;\n }\n }\n if (!maxBackupTime) {\n return null;\n }\n return {watermark: max, backupTime: maxBackupTime};\n }\n\n /**\n * Returns `true` if the actual backup state, as last verified against the\n * backup destination, confirms that data claimed to be backed up at\n * `claimedTime` is durable (i.e. an object was actually uploaded at or\n * after `claimedTime`, allowing {@link BACKUP_VERIFICATION_SLACK_MS} of\n * clock skew).\n */\n #confirmedDurable(claimedTime: Date): boolean {\n const lastUpload = this.#lastVerifiedUploadTime;\n return (\n lastUpload !== null &&\n claimedTime.getTime() <=\n lastUpload.getTime() + BACKUP_VERIFICATION_SLACK_MS\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LogContext } from '@rocicorp/logger';
|
|
2
|
+
import type { Source } from '../../types/streams.ts';
|
|
3
|
+
import type { BackedUpWatermark } from './backup-monitor.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The single-node equivalent of a backup monitor polls the replica file every
|
|
6
|
+
* 30 seconds and schedules cleanup when the watermark
|
|
7
|
+
* (i.e. stateVersion) moves forward.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ReplicaPoller {
|
|
10
|
+
#private;
|
|
11
|
+
readonly id = "replica-poller";
|
|
12
|
+
constructor(lc: LogContext, replicaFile: string);
|
|
13
|
+
start(): Source<BackedUpWatermark>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=replica-poller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replica-poller.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/replica-poller.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAGnD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAI3D;;;;GAIG;AACH,qBAAa,aAAa;;IACxB,QAAQ,CAAC,EAAE,oBAAoB;gBAQnB,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM;IAQ/C,KAAK,IAAI,MAAM,CAAC,iBAAiB,CAAC;CAyBnC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Database } from "../../../../zqlite/src/db.js";
|
|
2
|
+
import { getReplicationState } from "../replicator/schema/replication-state.js";
|
|
3
|
+
import { StatementRunner } from "../../db/statements.js";
|
|
4
|
+
import { Subscription } from "../../types/subscription.js";
|
|
5
|
+
import { assert } from "console";
|
|
6
|
+
import { clearInterval } from "timers";
|
|
7
|
+
//#region ../zero-cache/src/services/change-streamer/replica-poller.ts
|
|
8
|
+
var CHECK_INTERVAL_MS = 10 * 1e3;
|
|
9
|
+
/**
|
|
10
|
+
* The single-node equivalent of a backup monitor polls the replica file every
|
|
11
|
+
* 30 seconds and schedules cleanup when the watermark
|
|
12
|
+
* (i.e. stateVersion) moves forward.
|
|
13
|
+
*/
|
|
14
|
+
var ReplicaPoller = class {
|
|
15
|
+
id = "replica-poller";
|
|
16
|
+
#lc;
|
|
17
|
+
#replicaFile;
|
|
18
|
+
#stream;
|
|
19
|
+
#pollReplicaTimer;
|
|
20
|
+
#lastWatermark = "";
|
|
21
|
+
constructor(lc, replicaFile) {
|
|
22
|
+
this.#lc = lc.withContext("component", this.id);
|
|
23
|
+
this.#replicaFile = replicaFile;
|
|
24
|
+
this.#stream = Subscription.create({ cleanup: () => clearInterval(this.#pollReplicaTimer) });
|
|
25
|
+
}
|
|
26
|
+
start() {
|
|
27
|
+
assert(this.#pollReplicaTimer === void 0, `Already called start()`);
|
|
28
|
+
this.#lc.info?.(`starting replica monitor`);
|
|
29
|
+
this.#pollReplica();
|
|
30
|
+
this.#pollReplicaTimer = setInterval(this.#pollReplica, CHECK_INTERVAL_MS);
|
|
31
|
+
return this.#stream;
|
|
32
|
+
}
|
|
33
|
+
#pollReplica = () => {
|
|
34
|
+
const db = new Database(this.#lc, this.#replicaFile);
|
|
35
|
+
try {
|
|
36
|
+
const { stateVersion } = getReplicationState(new StatementRunner(db));
|
|
37
|
+
if (stateVersion !== this.#lastWatermark) {
|
|
38
|
+
this.#lastWatermark = stateVersion;
|
|
39
|
+
this.#lc.debug?.(`replicated up to watermark ${stateVersion}`);
|
|
40
|
+
this.#stream.push({
|
|
41
|
+
watermark: stateVersion,
|
|
42
|
+
backupTimeMs: Date.now()
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
this.#lc.error?.(`Unable to read watermark from replica`, e);
|
|
47
|
+
} finally {
|
|
48
|
+
db.close();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ReplicaPoller };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=replica-poller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replica-poller.js","names":["#lc","#replicaFile","#stream","#pollReplicaTimer","#pollReplica","#lastWatermark"],"sources":["../../../../../../zero-cache/src/services/change-streamer/replica-poller.ts"],"sourcesContent":["import {assert} from 'console';\nimport {clearInterval} from 'timers';\nimport type {LogContext} from '@rocicorp/logger';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {StatementRunner} from '../../db/statements.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport {getReplicationState} from '../replicator/schema/replication-state.ts';\nimport type {BackedUpWatermark} from './backup-monitor.ts';\n\nconst CHECK_INTERVAL_MS = 10 * 1000;\n\n/**\n * The single-node equivalent of a backup monitor polls the replica file every\n * 30 seconds and schedules cleanup when the watermark\n * (i.e. stateVersion) moves forward.\n */\nexport class ReplicaPoller {\n readonly id = 'replica-poller';\n readonly #lc: LogContext;\n readonly #replicaFile: string;\n readonly #stream: Subscription<BackedUpWatermark>;\n #pollReplicaTimer: NodeJS.Timeout | undefined;\n\n #lastWatermark: string = '';\n\n constructor(lc: LogContext, replicaFile: string) {\n this.#lc = lc.withContext('component', this.id);\n this.#replicaFile = replicaFile;\n this.#stream = Subscription.create<BackedUpWatermark>({\n cleanup: () => clearInterval(this.#pollReplicaTimer),\n });\n }\n\n start(): Source<BackedUpWatermark> {\n assert(this.#pollReplicaTimer === undefined, `Already called start()`);\n this.#lc.info?.(`starting replica monitor`);\n // Perform the first poll immediately to avoid unnecessarily waiting\n // during single-node development.\n this.#pollReplica();\n this.#pollReplicaTimer = setInterval(this.#pollReplica, CHECK_INTERVAL_MS);\n return this.#stream;\n }\n\n readonly #pollReplica = () => {\n const db = new Database(this.#lc, this.#replicaFile);\n try {\n const {stateVersion} = getReplicationState(new StatementRunner(db));\n if (stateVersion !== this.#lastWatermark) {\n this.#lastWatermark = stateVersion;\n this.#lc.debug?.(`replicated up to watermark ${stateVersion}`);\n this.#stream.push({watermark: stateVersion, backupTimeMs: Date.now()});\n }\n } catch (e) {\n this.#lc.error?.(`Unable to read watermark from replica`, e);\n } finally {\n db.close();\n }\n };\n}\n"],"mappings":";;;;;;;AAUA,IAAM,oBAAoB,KAAK;;;;;;AAO/B,IAAa,gBAAb,MAA2B;CACzB,KAAc;CACd;CACA;CACA;CACA;CAEA,iBAAyB;CAEzB,YAAY,IAAgB,aAAqB;EAC/C,KAAKA,MAAM,GAAG,YAAY,aAAa,KAAK,EAAE;EAC9C,KAAKC,eAAe;EACpB,KAAKC,UAAU,aAAa,OAA0B,EACpD,eAAe,cAAc,KAAKC,iBAAiB,EACrD,CAAC;CACH;CAEA,QAAmC;EACjC,OAAO,KAAKA,sBAAsB,KAAA,GAAW,wBAAwB;EACrE,KAAKH,IAAI,OAAO,0BAA0B;EAG1C,KAAKI,aAAa;EAClB,KAAKD,oBAAoB,YAAY,KAAKC,cAAc,iBAAiB;EACzE,OAAO,KAAKF;CACd;CAEA,qBAA8B;EAC5B,MAAM,KAAK,IAAI,SAAS,KAAKF,KAAK,KAAKC,YAAY;EACnD,IAAI;GACF,MAAM,EAAC,iBAAgB,oBAAoB,IAAI,gBAAgB,EAAE,CAAC;GAClE,IAAI,iBAAiB,KAAKI,gBAAgB;IACxC,KAAKA,iBAAiB;IACtB,KAAKL,IAAI,QAAQ,8BAA8B,cAAc;IAC7D,KAAKE,QAAQ,KAAK;KAAC,WAAW;KAAc,cAAc,KAAK,IAAI;IAAC,CAAC;GACvE;EACF,SAAS,GAAG;GACV,KAAKF,IAAI,QAAQ,yCAAyC,CAAC;EAC7D,UAAU;GACR,GAAG,MAAM;EACX;CACF;AACF"}
|