@rocicorp/zero 1.12.0-canary.4 → 1.12.0-canary.6

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.
Files changed (164) hide show
  1. package/out/analyze-query/src/analyze-cli.d.ts.map +1 -1
  2. package/out/analyze-query/src/analyze-cli.js +2 -1
  3. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  4. package/out/analyze-query/src/bin-transform.js +1 -1
  5. package/out/analyze-query/src/zql-chain.d.ts +23 -0
  6. package/out/analyze-query/src/zql-chain.d.ts.map +1 -0
  7. package/out/analyze-query/src/zql-chain.js +162 -0
  8. package/out/analyze-query/src/zql-chain.js.map +1 -0
  9. package/out/ast-to-zql/src/bin.js +1 -1
  10. package/out/replicache/src/btree/node.js +1 -1
  11. package/out/replicache/src/deleted-clients.d.ts.map +1 -1
  12. package/out/replicache/src/deleted-clients.js +2 -1
  13. package/out/replicache/src/deleted-clients.js.map +1 -1
  14. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  15. package/out/replicache/src/kv/sqlite-store.js +7 -3
  16. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  17. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  18. package/out/replicache/src/replicache-impl.js +17 -14
  19. package/out/replicache/src/replicache-impl.js.map +1 -1
  20. package/out/z2s/src/compiler.js +3 -3
  21. package/out/z2s/src/compiler.js.map +1 -1
  22. package/out/zero/package.js +22 -23
  23. package/out/zero/package.js.map +1 -1
  24. package/out/zero-cache/src/auth/load-permissions.js +1 -1
  25. package/out/zero-cache/src/auth/write-authorizer.js +1 -1
  26. package/out/zero-cache/src/config/zero-config.js +2 -2
  27. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  28. package/out/zero-cache/src/scripts/decommission.js +1 -1
  29. package/out/zero-cache/src/scripts/deploy-permissions.js +1 -1
  30. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  31. package/out/zero-cache/src/server/change-streamer.js +33 -10
  32. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  33. package/out/zero-cache/src/server/replicator.js +1 -1
  34. package/out/zero-cache/src/server/syncer.js +3 -3
  35. package/out/zero-cache/src/services/analyze.js +2 -2
  36. package/out/zero-cache/src/services/change-source/change-source.d.ts +7 -0
  37. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/common/backfill-manager.js +1 -1
  39. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts +25 -0
  40. package/out/zero-cache/src/services/change-source/common/replica-restore.d.ts.map +1 -1
  41. package/out/zero-cache/src/services/change-source/common/replica-restore.js.map +1 -1
  42. package/out/zero-cache/src/services/change-source/common/replica-schema.js +1 -1
  43. package/out/zero-cache/src/services/change-source/custom/change-source.js +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
  45. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts +8 -1
  46. package/out/zero-cache/src/services/change-source/pg/change-source-init.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-source/pg/change-source-init.js +35 -18
  48. package/out/zero-cache/src/services/change-source/pg/change-source-init.js.map +1 -1
  49. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts +12 -0
  50. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/change-source/pg/change-source.js +66 -9
  52. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  53. package/out/zero-cache/src/services/change-source/pg/fork-request.d.ts +57 -0
  54. package/out/zero-cache/src/services/change-source/pg/fork-request.d.ts.map +1 -0
  55. package/out/zero-cache/src/services/change-source/pg/fork-request.js +54 -0
  56. package/out/zero-cache/src/services/change-source/pg/fork-request.js.map +1 -0
  57. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +4 -4
  58. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +2 -2
  59. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -1
  60. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +6 -0
  61. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -1
  62. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +1 -1
  63. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -1
  64. package/out/zero-cache/src/services/change-source/pg/schema/published.js +1 -1
  65. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
  66. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +1 -0
  67. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  68. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +3 -0
  69. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
  70. package/out/zero-cache/src/services/change-source/protocol/current/status.js +2 -1
  71. package/out/zero-cache/src/services/change-source/protocol/current/status.js.map +1 -1
  72. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
  73. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts +1 -3
  75. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.d.ts.map +1 -1
  76. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js +6 -6
  77. package/out/zero-cache/src/services/change-streamer/backup-cleanup-monitor-factory.js.map +1 -1
  78. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +41 -2
  79. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  80. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +55 -20
  81. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  82. package/out/zero-cache/src/services/change-streamer/change-log-initializer.js +1 -1
  83. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +9 -0
  84. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  85. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +27 -8
  86. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  87. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +1 -12
  88. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  89. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  90. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts +29 -2
  91. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.d.ts.map +1 -1
  92. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js +80 -11
  93. package/out/zero-cache/src/services/change-streamer/litestream-sync-requester.js.map +1 -1
  94. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts +2 -2
  95. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.d.ts.map +1 -1
  96. package/out/zero-cache/src/services/change-streamer/litestream3-prometheus-poller.js.map +1 -1
  97. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts +2 -2
  98. package/out/zero-cache/src/services/change-streamer/replica-poller.d.ts.map +1 -1
  99. package/out/zero-cache/src/services/change-streamer/replica-poller.js +1 -1
  100. package/out/zero-cache/src/services/change-streamer/replica-poller.js.map +1 -1
  101. package/out/zero-cache/src/services/change-streamer/server-start-metrics.d.ts +33 -0
  102. package/out/zero-cache/src/services/change-streamer/server-start-metrics.d.ts.map +1 -0
  103. package/out/zero-cache/src/services/change-streamer/server-start-metrics.js +42 -0
  104. package/out/zero-cache/src/services/change-streamer/server-start-metrics.js.map +1 -0
  105. package/out/zero-cache/src/services/change-streamer/sqlite-change-log-writer.js +2 -2
  106. package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
  107. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +17 -6
  108. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  109. package/out/zero-cache/src/services/change-streamer/subscriber.js +5 -4
  110. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  111. package/out/zero-cache/src/services/life-cycle.js +1 -1
  112. package/out/zero-cache/src/services/litestream/commands.js +2 -2
  113. package/out/zero-cache/src/services/plannerz.js +2 -2
  114. package/out/zero-cache/src/services/replicator/change-log-db.js +1 -1
  115. package/out/zero-cache/src/services/replicator/incremental-sync.js +3 -3
  116. package/out/zero-cache/src/services/replicator/schema/backfilling.js +1 -1
  117. package/out/zero-cache/src/services/replicator/write-worker.js +3 -3
  118. package/out/zero-cache/src/services/run-ast.js +1 -1
  119. package/out/zero-cache/src/services/statz.js +2 -2
  120. package/out/zero-cache/src/services/view-syncer/cvr-store.js +1 -1
  121. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  122. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +5 -5
  123. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +1 -1
  124. package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
  125. package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -6
  126. package/out/zero-cache/src/types/profiler.js +1 -1
  127. package/out/zero-client/src/client/version.js +1 -1
  128. package/out/zero-protocol/src/ast.d.ts.map +1 -1
  129. package/out/zero-protocol/src/ast.js +2 -1
  130. package/out/zero-protocol/src/ast.js.map +1 -1
  131. package/out/zero-protocol/src/down.d.ts +27 -27
  132. package/out/zql/src/ivm/cap.d.ts +1 -1
  133. package/out/zql/src/ivm/cap.d.ts.map +1 -1
  134. package/out/zql/src/ivm/cap.js +10 -0
  135. package/out/zql/src/ivm/cap.js.map +1 -1
  136. package/out/zql/src/ivm/data.d.ts +15 -1
  137. package/out/zql/src/ivm/data.d.ts.map +1 -1
  138. package/out/zql/src/ivm/data.js +15 -1
  139. package/out/zql/src/ivm/data.js.map +1 -1
  140. package/out/zql/src/ivm/exists.d.ts.map +1 -1
  141. package/out/zql/src/ivm/exists.js +12 -4
  142. package/out/zql/src/ivm/exists.js.map +1 -1
  143. package/out/zql/src/ivm/filter-operators.d.ts +5 -0
  144. package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
  145. package/out/zql/src/ivm/filter-operators.js.map +1 -1
  146. package/out/zql/src/ivm/join.d.ts.map +1 -1
  147. package/out/zql/src/ivm/join.js +6 -3
  148. package/out/zql/src/ivm/join.js.map +1 -1
  149. package/out/zql/src/ivm/memory-source.js +1 -1
  150. package/out/zql/src/ivm/operator.d.ts +6 -0
  151. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  152. package/out/zql/src/ivm/operator.js.map +1 -1
  153. package/out/zql/src/ivm/take.d.ts.map +1 -1
  154. package/out/zql/src/ivm/take.js +4 -3
  155. package/out/zql/src/ivm/take.js.map +1 -1
  156. package/out/zqlite/src/table-source.js +1 -1
  157. package/package.json +22 -23
  158. package/out/analyze-query/src/bin-analyze.d.ts +0 -2
  159. package/out/analyze-query/src/bin-analyze.d.ts.map +0 -1
  160. package/out/analyze-query/src/bin-analyze.js +0 -285
  161. package/out/analyze-query/src/bin-analyze.js.map +0 -1
  162. package/out/zero/src/analyze-query.d.ts +0 -3
  163. package/out/zero/src/analyze-query.d.ts.map +0 -1
  164. package/out/zero/src/analyze-query.js +0 -3
@@ -1,15 +1,23 @@
1
1
  import { promiseVoid } from "../../../../shared/src/resolved-promises.js";
2
2
  import { Database } from "../../../../zqlite/src/db.js";
3
3
  import { getOrCreateGauge } from "../../observability/metrics.js";
4
+ import { Subscription } from "../../types/subscription.js";
4
5
  import { resolver } from "@rocicorp/resolver";
5
6
  //#region ../zero-cache/src/services/change-streamer/backup-monitor.ts
7
+ /**
8
+ * Relays the watermarks backed up by a {@link BackupPoller} to the
9
+ * change-streamer, which consumes them (see {@link backups}) and requests
10
+ * prompt backups (see {@link requestSync}) as needed.
11
+ */
6
12
  var BackupMonitor = class {
7
13
  id = "backup-monitor";
8
14
  #lc;
9
- #watermarks;
10
- #changeStreamer;
15
+ #poller;
11
16
  #replicaFile;
12
17
  #firstBackupReceived = resolver();
18
+ #backups = Subscription.create({ coalesce: (curr) => curr });
19
+ #watermarks;
20
+ #stopped = false;
13
21
  #latestBackup;
14
22
  #firstBackupResolved = false;
15
23
  /**
@@ -19,36 +27,61 @@ var BackupMonitor = class {
19
27
  * "any watermark" rather than hanging startup forever.
20
28
  */
21
29
  #coverageTarget;
22
- constructor(lc, watermarks, changeStreamer, replicaFile) {
30
+ constructor(lc, poller, replicaFile) {
23
31
  this.#lc = lc;
24
- this.#watermarks = watermarks;
25
- this.#changeStreamer = changeStreamer;
32
+ this.#poller = poller;
26
33
  this.#replicaFile = replicaFile;
27
34
  }
28
35
  firstBackupReceived() {
29
36
  return this.#firstBackupReceived.promise;
30
37
  }
38
+ /**
39
+ * The stream of backed-up watermarks, each later than the last, coalesced
40
+ * to the latest one that has not been consumed. It ends when the monitor
41
+ * stops.
42
+ */
43
+ backups() {
44
+ return this.#backups;
45
+ }
46
+ /**
47
+ * Requests that the replica be backed up through `watermark` promptly, if
48
+ * the poller supports it (see {@link BackupPoller.requestSync}). A no-op
49
+ * unless the monitor is running.
50
+ */
51
+ requestSync(watermark) {
52
+ if (!this.#watermarks || this.#stopped || this.#latestBackup && this.#latestBackup.watermark >= watermark) return;
53
+ if (this.#poller.requestSync) {
54
+ this.#lc.info?.(`requesting backup through ${watermark}`);
55
+ this.#poller.requestSync(watermark);
56
+ }
57
+ }
31
58
  async run() {
59
+ if (this.#stopped) return;
32
60
  this.#lc.info?.("starting backup monitor");
33
61
  this.#initBackupLagMetric();
34
62
  this.#coverageTarget = this.#readReplicaStateVersion();
35
- for await (const backedUp of this.#watermarks) {
36
- if (this.#latestBackup) {
37
- if (backedUp.watermark < this.#latestBackup.watermark) {
38
- this.#lc.warn?.(`ignoring earlier backup watermark ${backedUp.watermark}`, { backedUp });
39
- continue;
40
- }
41
- if (backedUp.watermark === this.#latestBackup.watermark) {
42
- this.#lc.debug?.(`ignoring redundant backup watermark ${backedUp.watermark}`, { backedUp });
43
- continue;
63
+ this.#watermarks = this.#poller.start();
64
+ try {
65
+ for await (const backedUp of this.#watermarks) {
66
+ if (this.#latestBackup) {
67
+ if (backedUp.watermark < this.#latestBackup.watermark) {
68
+ this.#lc.warn?.(`ignoring earlier backup watermark ${backedUp.watermark}`, { backedUp });
69
+ continue;
70
+ }
71
+ if (backedUp.watermark === this.#latestBackup.watermark) {
72
+ this.#lc.debug?.(`ignoring redundant backup watermark ${backedUp.watermark}`, { backedUp });
73
+ continue;
74
+ }
44
75
  }
76
+ this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, { backedUp });
77
+ this.#latestBackup = backedUp;
78
+ this.#backups.push(backedUp);
79
+ this.#checkFirstBackupCovers(backedUp);
45
80
  }
46
- this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, { backedUp });
47
- this.#latestBackup = backedUp;
48
- this.#changeStreamer.trackBackupWatermark(backedUp.watermark);
49
- this.#checkFirstBackupCovers(backedUp);
81
+ this.#lc.info?.("watermark stream closed. BackupMonitor stopped.");
82
+ } finally {
83
+ this.#backups.cancel();
50
84
  }
51
- this.#lc.info?.("watermark stream closed. BackupMonitor stopped.");
52
85
  }
53
86
  /**
54
87
  * Resolves {@link firstBackupReceived} once the backup actually covers the
@@ -90,7 +123,9 @@ var BackupMonitor = class {
90
123
  }
91
124
  }
92
125
  stop() {
93
- this.#watermarks.cancel();
126
+ this.#stopped = true;
127
+ this.#watermarks?.cancel();
128
+ this.#backups.cancel();
94
129
  return promiseVoid;
95
130
  }
96
131
  #initBackupLagMetric() {
@@ -1 +1 @@
1
- {"version":3,"file":"backup-monitor.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/backup-monitor.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {getOrCreateGauge} from '../../observability/metrics.ts';\nimport type {Source} from '../../types/streams.ts';\nimport type {SingletonService} from '../service.ts';\nimport type {ChangeStreamerService} from './change-streamer.ts';\n\nexport type BackedUpWatermark = {\n watermark: string;\n writeTimeMs?: number | undefined; // optional for debugging; available on v5\n backupTimeMs: number;\n};\n\nexport class BackupMonitor implements SingletonService {\n readonly id = 'backup-monitor';\n readonly #lc: LogContext;\n readonly #watermarks: Source<BackedUpWatermark>;\n readonly #changeStreamer: ChangeStreamerService;\n readonly #replicaFile: string;\n readonly #firstBackupReceived = resolver();\n\n #latestBackup: BackedUpWatermark | undefined;\n #firstBackupResolved = false;\n /**\n * The replica's `stateVersion` when the monitor started, i.e. the point the\n * backup has to reach before it covers what this task is about to serve.\n * `undefined` when the replica could not be read, which degrades the gate to\n * \"any watermark\" rather than hanging startup forever.\n */\n #coverageTarget: string | undefined;\n\n constructor(\n lc: LogContext,\n watermarks: Source<BackedUpWatermark>,\n changeStreamer: ChangeStreamerService,\n replicaFile: string,\n ) {\n this.#lc = lc;\n this.#watermarks = watermarks;\n this.#changeStreamer = changeStreamer;\n this.#replicaFile = replicaFile;\n }\n\n firstBackupReceived() {\n return this.#firstBackupReceived.promise;\n }\n\n async run() {\n this.#lc.info?.('starting backup monitor');\n this.#initBackupLagMetric();\n this.#coverageTarget = this.#readReplicaStateVersion();\n\n for await (const backedUp of this.#watermarks) {\n if (this.#latestBackup) {\n if (backedUp.watermark < this.#latestBackup.watermark) {\n this.#lc.warn?.(\n `ignoring earlier backup watermark ${backedUp.watermark}`,\n {backedUp},\n );\n continue;\n }\n if (backedUp.watermark === this.#latestBackup.watermark) {\n this.#lc.debug?.(\n `ignoring redundant backup watermark ${backedUp.watermark}`,\n {backedUp},\n );\n continue;\n }\n }\n this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, {\n backedUp,\n });\n this.#latestBackup = backedUp;\n this.#changeStreamer.trackBackupWatermark(backedUp.watermark);\n this.#checkFirstBackupCovers(backedUp);\n }\n this.#lc.info?.('watermark stream closed. BackupMonitor stopped.');\n }\n\n /**\n * Resolves {@link firstBackupReceived} once the backup actually covers the\n * replica, rather than on the first watermark of any value.\n *\n * A new backup destination starts empty and is filled by re-uploading the\n * local LTX chain, so the first watermarks read back out of it are real but\n * far behind the replica. Releasing the readiness gate on one of those lets\n * the task serve against a backup that does not yet cover it, which is what\n * demotes a restoring view-syncer to Postgres catchup.\n *\n * The target is pinned at startup rather than compared against the replica's\n * current position, which keeps the gate satisfiable under sustained writes.\n */\n #checkFirstBackupCovers(backedUp: BackedUpWatermark) {\n if (this.#firstBackupResolved) {\n return;\n }\n const target = this.#coverageTarget;\n if (target !== undefined && backedUp.watermark < target) {\n this.#lc.info?.(`backup does not yet cover the replica`, {\n backedUp,\n coverageTarget: target,\n });\n return;\n }\n this.#firstBackupResolved = true;\n this.#firstBackupReceived.resolve();\n }\n\n #readReplicaStateVersion(): string | undefined {\n let db;\n try {\n db = new Database(this.#lc, this.#replicaFile, {readonly: true});\n const {stateVersion} = db\n .prepare(/*sql*/ `SELECT stateVersion FROM \"_zero.replicationState\"`)\n .get<{stateVersion: string}>();\n return stateVersion;\n } catch (e) {\n // Without a target the gate degrades to its previous behavior. That is\n // strictly better than blocking startup on a replica we cannot read.\n this.#lc.warn?.(\n `unable to read the replica's stateVersion; ` +\n `the initial backup gate will accept the first watermark`,\n e,\n );\n return undefined;\n } finally {\n db?.close();\n }\n }\n\n stop(): Promise<void> {\n this.#watermarks.cancel();\n return promiseVoid;\n }\n\n #initBackupLagMetric() {\n getOrCreateGauge('replica', 'backup_lag', {\n description:\n 'Latency from when a change is written to the replica ' +\n 'to when it is backed up to litestream. It is expected to create a saw ' +\n 'pattern from 0 to the configured ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES.',\n unit: 'millisecond',\n }).addCallback(o => {\n const latestBackup = this.#latestBackup;\n if (!latestBackup) {\n this.#lc.warn?.(\n `no backed up watermarks. unable to report replica.backup_lag`,\n );\n return;\n }\n const db = new Database(this.#lc, this.#replicaFile, {readonly: true});\n try {\n const {writeTimeMs} = db\n .prepare(/*sql*/ `SELECT writeTimeMs FROM \"_zero.replicationState\"`)\n .get<{writeTimeMs: number}>();\n const backupLag = Math.max(0, writeTimeMs - latestBackup.backupTimeMs);\n o.observe(backupLag);\n } catch (e) {\n this.#lc.warn?.(`error measuring replica.backup_lag metric`, e);\n } finally {\n db.close();\n }\n });\n }\n}\n"],"mappings":";;;;;AAeA,IAAa,gBAAb,MAAuD;CACrD,KAAc;CACd;CACA;CACA;CACA;CACA,uBAAgC,SAAS;CAEzC;CACA,uBAAuB;;;;;;;CAOvB;CAEA,YACE,IACA,YACA,gBACA,aACA;EACA,KAAK,MAAM;EACX,KAAK,cAAc;EACnB,KAAK,kBAAkB;EACvB,KAAK,eAAe;CACtB;CAEA,sBAAsB;EACpB,OAAO,KAAK,qBAAqB;CACnC;CAEA,MAAM,MAAM;EACV,KAAK,IAAI,OAAO,yBAAyB;EACzC,KAAK,qBAAqB;EAC1B,KAAK,kBAAkB,KAAK,yBAAyB;EAErD,WAAW,MAAM,YAAY,KAAK,aAAa;GAC7C,IAAI,KAAK,eAAe;IACtB,IAAI,SAAS,YAAY,KAAK,cAAc,WAAW;KACrD,KAAK,IAAI,OACP,qCAAqC,SAAS,aAC9C,EAAC,SAAQ,CACX;KACA;IACF;IACA,IAAI,SAAS,cAAc,KAAK,cAAc,WAAW;KACvD,KAAK,IAAI,QACP,uCAAuC,SAAS,aAChD,EAAC,SAAQ,CACX;KACA;IACF;GACF;GACA,KAAK,IAAI,OAAO,6BAA6B,SAAS,aAAa,EACjE,SACF,CAAC;GACD,KAAK,gBAAgB;GACrB,KAAK,gBAAgB,qBAAqB,SAAS,SAAS;GAC5D,KAAK,wBAAwB,QAAQ;EACvC;EACA,KAAK,IAAI,OAAO,iDAAiD;CACnE;;;;;;;;;;;;;;CAeA,wBAAwB,UAA6B;EACnD,IAAI,KAAK,sBACP;EAEF,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,KAAA,KAAa,SAAS,YAAY,QAAQ;GACvD,KAAK,IAAI,OAAO,yCAAyC;IACvD;IACA,gBAAgB;GAClB,CAAC;GACD;EACF;EACA,KAAK,uBAAuB;EAC5B,KAAK,qBAAqB,QAAQ;CACpC;CAEA,2BAA+C;EAC7C,IAAI;EACJ,IAAI;GACF,KAAK,IAAI,SAAS,KAAK,KAAK,KAAK,cAAc,EAAC,UAAU,KAAI,CAAC;GAC/D,MAAM,EAAC,iBAAgB,GACpB,QAAgB,mDAAmD,CAAC,CACpE,IAA4B;GAC/B,OAAO;EACT,SAAS,GAAG;GAGV,KAAK,IAAI,OACP,sGAEA,CACF;GACA;EACF,UAAU;GACR,IAAI,MAAM;EACZ;CACF;CAEA,OAAsB;EACpB,KAAK,YAAY,OAAO;EACxB,OAAO;CACT;CAEA,uBAAuB;EACrB,iBAAiB,WAAW,cAAc;GACxC,aACE;GAGF,MAAM;EACR,CAAC,CAAC,CAAC,aAAY,MAAK;GAClB,MAAM,eAAe,KAAK;GAC1B,IAAI,CAAC,cAAc;IACjB,KAAK,IAAI,OACP,8DACF;IACA;GACF;GACA,MAAM,KAAK,IAAI,SAAS,KAAK,KAAK,KAAK,cAAc,EAAC,UAAU,KAAI,CAAC;GACrE,IAAI;IACF,MAAM,EAAC,gBAAe,GACnB,QAAgB,kDAAkD,CAAC,CACnE,IAA2B;IAC9B,MAAM,YAAY,KAAK,IAAI,GAAG,cAAc,aAAa,YAAY;IACrE,EAAE,QAAQ,SAAS;GACrB,SAAS,GAAG;IACV,KAAK,IAAI,OAAO,6CAA6C,CAAC;GAChE,UAAU;IACR,GAAG,MAAM;GACX;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"backup-monitor.js","names":[],"sources":["../../../../../../zero-cache/src/services/change-streamer/backup-monitor.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {Database} from '../../../../zqlite/src/db.ts';\nimport {getOrCreateGauge} from '../../observability/metrics.ts';\nimport type {Source} from '../../types/streams.ts';\nimport {Subscription} from '../../types/subscription.ts';\nimport type {SingletonService} from '../service.ts';\n\nexport type BackedUpWatermark = {\n watermark: string;\n writeTimeMs?: number | undefined; // optional for debugging; available on v5\n backupTimeMs: number;\n};\n\n/**\n * Determines which watermarks of the replica are backed up, by whatever means\n * the backup (or its absence) allows.\n */\nexport interface BackupPoller {\n /**\n * Starts polling, returning the stream of backed-up watermarks. Must only\n * be called once.\n */\n start(): Source<BackedUpWatermark>;\n\n /**\n * Requests that the replica be backed up through `watermark` promptly,\n * rather than at the poller's regular cadence, once the replica has reached\n * it. The resulting watermark is emitted on the stream as usual. Pollers\n * that cannot initiate backups do not implement this.\n */\n requestSync?(watermark: string): void;\n}\n\n/**\n * The interface through which the change-streamer consumes backed-up\n * watermarks and requests prompt backups (see {@link BackupMonitor}).\n */\nexport type BackupSource = Pick<BackupMonitor, 'backups' | 'requestSync'>;\n\n/**\n * Relays the watermarks backed up by a {@link BackupPoller} to the\n * change-streamer, which consumes them (see {@link backups}) and requests\n * prompt backups (see {@link requestSync}) as needed.\n */\nexport class BackupMonitor implements SingletonService {\n readonly id = 'backup-monitor';\n readonly #lc: LogContext;\n readonly #poller: BackupPoller;\n readonly #replicaFile: string;\n readonly #firstBackupReceived = resolver();\n // Backed-up watermarks only move forward, so the consumer only needs the\n // latest one. Coalescing keeps at most one outstanding, so that the monitor\n // neither waits on, nor queues for, the consumer.\n readonly #backups = Subscription.create<BackedUpWatermark>({\n coalesce: curr => curr,\n });\n\n #watermarks: Source<BackedUpWatermark> | undefined;\n #stopped = false;\n #latestBackup: BackedUpWatermark | undefined;\n #firstBackupResolved = false;\n /**\n * The replica's `stateVersion` when the monitor started, i.e. the point the\n * backup has to reach before it covers what this task is about to serve.\n * `undefined` when the replica could not be read, which degrades the gate to\n * \"any watermark\" rather than hanging startup forever.\n */\n #coverageTarget: string | undefined;\n\n constructor(lc: LogContext, poller: BackupPoller, replicaFile: string) {\n this.#lc = lc;\n this.#poller = poller;\n this.#replicaFile = replicaFile;\n }\n\n firstBackupReceived() {\n return this.#firstBackupReceived.promise;\n }\n\n /**\n * The stream of backed-up watermarks, each later than the last, coalesced\n * to the latest one that has not been consumed. It ends when the monitor\n * stops.\n */\n backups(): Source<BackedUpWatermark> {\n return this.#backups;\n }\n\n /**\n * Requests that the replica be backed up through `watermark` promptly, if\n * the poller supports it (see {@link BackupPoller.requestSync}). A no-op\n * unless the monitor is running.\n */\n requestSync(watermark: string) {\n if (\n !this.#watermarks ||\n this.#stopped ||\n (this.#latestBackup && this.#latestBackup.watermark >= watermark)\n ) {\n return;\n }\n if (this.#poller.requestSync) {\n this.#lc.info?.(`requesting backup through ${watermark}`);\n this.#poller.requestSync(watermark);\n }\n }\n\n async run() {\n if (this.#stopped) {\n return;\n }\n this.#lc.info?.('starting backup monitor');\n this.#initBackupLagMetric();\n this.#coverageTarget = this.#readReplicaStateVersion();\n\n this.#watermarks = this.#poller.start();\n try {\n for await (const backedUp of this.#watermarks) {\n if (this.#latestBackup) {\n if (backedUp.watermark < this.#latestBackup.watermark) {\n this.#lc.warn?.(\n `ignoring earlier backup watermark ${backedUp.watermark}`,\n {backedUp},\n );\n continue;\n }\n if (backedUp.watermark === this.#latestBackup.watermark) {\n this.#lc.debug?.(\n `ignoring redundant backup watermark ${backedUp.watermark}`,\n {backedUp},\n );\n continue;\n }\n }\n this.#lc.info?.(`received backup watermark ${backedUp.watermark}`, {\n backedUp,\n });\n this.#latestBackup = backedUp;\n this.#backups.push(backedUp);\n this.#checkFirstBackupCovers(backedUp);\n }\n this.#lc.info?.('watermark stream closed. BackupMonitor stopped.');\n } finally {\n this.#backups.cancel();\n }\n }\n\n /**\n * Resolves {@link firstBackupReceived} once the backup actually covers the\n * replica, rather than on the first watermark of any value.\n *\n * A new backup destination starts empty and is filled by re-uploading the\n * local LTX chain, so the first watermarks read back out of it are real but\n * far behind the replica. Releasing the readiness gate on one of those lets\n * the task serve against a backup that does not yet cover it, which is what\n * demotes a restoring view-syncer to Postgres catchup.\n *\n * The target is pinned at startup rather than compared against the replica's\n * current position, which keeps the gate satisfiable under sustained writes.\n */\n #checkFirstBackupCovers(backedUp: BackedUpWatermark) {\n if (this.#firstBackupResolved) {\n return;\n }\n const target = this.#coverageTarget;\n if (target !== undefined && backedUp.watermark < target) {\n this.#lc.info?.(`backup does not yet cover the replica`, {\n backedUp,\n coverageTarget: target,\n });\n return;\n }\n this.#firstBackupResolved = true;\n this.#firstBackupReceived.resolve();\n }\n\n #readReplicaStateVersion(): string | undefined {\n let db;\n try {\n db = new Database(this.#lc, this.#replicaFile, {readonly: true});\n const {stateVersion} = db\n .prepare(/*sql*/ `SELECT stateVersion FROM \"_zero.replicationState\"`)\n .get<{stateVersion: string}>();\n return stateVersion;\n } catch (e) {\n // Without a target the gate degrades to its previous behavior. That is\n // strictly better than blocking startup on a replica we cannot read.\n this.#lc.warn?.(\n `unable to read the replica's stateVersion; ` +\n `the initial backup gate will accept the first watermark`,\n e,\n );\n return undefined;\n } finally {\n db?.close();\n }\n }\n\n stop(): Promise<void> {\n this.#stopped = true;\n this.#watermarks?.cancel();\n this.#backups.cancel();\n return promiseVoid;\n }\n\n #initBackupLagMetric() {\n getOrCreateGauge('replica', 'backup_lag', {\n description:\n 'Latency from when a change is written to the replica ' +\n 'to when it is backed up to litestream. It is expected to create a saw ' +\n 'pattern from 0 to the configured ZERO_LITESTREAM_INCREMENTAL_BACKUP_INTERVAL_MINUTES.',\n unit: 'millisecond',\n }).addCallback(o => {\n const latestBackup = this.#latestBackup;\n if (!latestBackup) {\n this.#lc.warn?.(\n `no backed up watermarks. unable to report replica.backup_lag`,\n );\n return;\n }\n const db = new Database(this.#lc, this.#replicaFile, {readonly: true});\n try {\n const {writeTimeMs} = db\n .prepare(/*sql*/ `SELECT writeTimeMs FROM \"_zero.replicationState\"`)\n .get<{writeTimeMs: number}>();\n const backupLag = Math.max(0, writeTimeMs - latestBackup.backupTimeMs);\n o.observe(backupLag);\n } catch (e) {\n this.#lc.warn?.(`error measuring replica.backup_lag metric`, e);\n } finally {\n db.close();\n }\n });\n }\n}\n"],"mappings":"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"}
@@ -4,10 +4,10 @@ import { BigIntJSON } from "../../../../shared/src/bigint-json.js";
4
4
  import { schemaChangeTags } from "../change-source/protocol/current/schema-change-tags.js";
5
5
  import { Database } from "../../../../zqlite/src/db.js";
6
6
  import { getOrCreateCounter } from "../../observability/metrics.js";
7
+ import { StatementRunner } from "../../db/statements.js";
7
8
  import { backfillRequestsFrom, foldCookies } from "../replicator/change-log-cookies.js";
8
9
  import { readBackfillRequests, readReplicaCookies } from "../replicator/schema/backfilling.js";
9
10
  import { getReplicationState } from "../replicator/schema/replication-state.js";
10
- import { StatementRunner } from "../../db/statements.js";
11
11
  import { CHANGE_LOG_STREAM_TABLE } from "../replicator/change-log-db.js";
12
12
  import { SQLITE_CHANGE_LOG_BOUNDARY_SQL } from "./sqlite-change-log-reader.js";
13
13
  //#region ../zero-cache/src/services/change-streamer/change-log-initializer.ts
@@ -5,6 +5,7 @@ import type { ChangeSource } from '../change-source/change-source.ts';
5
5
  import type { LitestreamVersion } from '../litestream/metrics.ts';
6
6
  import { type ReplicationStatusPublisher } from '../replicator/replication-status.ts';
7
7
  import type { SubscriptionState } from '../replicator/schema/replication-state.ts';
8
+ import type { BackupSource } from './backup-monitor.ts';
8
9
  import { type ChangeStreamerService, type SubscriberContext } from './change-streamer.ts';
9
10
  import { type SQLiteChangeLogCleanupGuard } from './sqlite-change-log-catchup.ts';
10
11
  import { type SQLiteChangeLogCompareOptions } from './sqlite-change-log-comparator.ts';
@@ -125,6 +126,14 @@ export type TuningOptions = StorerOptions & {
125
126
  }) | undefined;
126
127
  /** Supplied only in `serve` mode. A zero percentage keeps every read on PG. */
127
128
  sqliteChangeLogServe?: SQLiteChangeLogServeOptions | undefined;
129
+ /**
130
+ * The source of backed-up watermarks (i.e. the `BackupMonitor`), which the
131
+ * change-streamer tracks from construction, and from which it requests
132
+ * prompt backups when the change source asks for them (e.g. for a replica
133
+ * fork). Its lifecycle (e.g. the monitor's run() and stop()) is managed by
134
+ * the caller. Absent, no backups are tracked.
135
+ */
136
+ backupMonitor?: BackupSource | undefined;
128
137
  };
129
138
  /**
130
139
  * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"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"}
@@ -202,6 +202,7 @@ var ChangeStreamerImpl = class {
202
202
  #purgeScheduler;
203
203
  #comparator;
204
204
  #acker;
205
+ #backupMonitor;
205
206
  #initializer;
206
207
  #readRouter;
207
208
  #autoReset;
@@ -216,7 +217,7 @@ var ChangeStreamerImpl = class {
216
217
  #reservationDemotions = getOrCreateCounter("replication", "sqlite_change_log.reservation_demotions", "Snapshot reservations demoted from SQLite to PG because the change log could not cover the backup being restored. With PG retired these followers have no fallback, so this is the rate at which one would instead have to wait for a later backup.");
217
218
  #reservationConfirmDelays = getOrCreateCounter("replication", "sqlite_change_log.reservation_confirm_delays", "Snapshot reservations whose confirmation was deferred because the selected source's change-log minimum was later than the backup watermark. Counted once per reservation, by that source.");
218
219
  #latestLagReportCommitTimeMs = 0;
219
- #lagReportingDisabled = resolver();
220
+ #lagReports = resolver();
220
221
  #backupWatermark;
221
222
  #pgPurgedWatermark = "";
222
223
  /**
@@ -330,6 +331,11 @@ var ChangeStreamerImpl = class {
330
331
  this.#purgeLock = initialPurgeLock;
331
332
  this.#autoReset = autoReset;
332
333
  this.#state = new RunningState(this.id, void 0, setTimeoutFn);
334
+ this.#backupMonitor = opts.backupMonitor;
335
+ if (this.#backupMonitor) this.#trackBackups(this.#backupMonitor);
336
+ }
337
+ async #trackBackups(monitor) {
338
+ for await (const { watermark } of monitor.backups()) this.#trackBackupWatermark(watermark);
333
339
  }
334
340
  /**
335
341
  * Whether the legacy Postgres change log is enabled, which is determined
@@ -342,7 +348,7 @@ var ChangeStreamerImpl = class {
342
348
  this.#lc.info?.("starting change stream");
343
349
  this.#forwarder.startProgressMonitor();
344
350
  const lagReportInit = await this.#source.startLagReporter();
345
- this.#lagReportingDisabled.resolve(!lagReportInit);
351
+ this.#lagReports.resolve(lagReportInit ? { request: () => this.#source.requestLagReport?.() } : void 0);
346
352
  if (lagReportInit) this.#latestLagReportCommitTimeMs = lagReportInit.firstCommitTimeMs;
347
353
  const storer = this.#storer;
348
354
  let takeoverPending = storer !== null;
@@ -372,13 +378,16 @@ var ChangeStreamerImpl = class {
372
378
  if (this.#state.resetBackoff() > REPLICATION_STATUS_ERROR_DELAY_THRESHOLD_MS) this.#replicationStatusPublisher.publish(this.#lc, "Replicating", `Replicating from ${lastWatermark}`);
373
379
  watermark = null;
374
380
  this.#acker.reset(stream.acks, lastWatermark);
375
- for await (const change of stream.changes) {
381
+ streamLoop: for await (const change of stream.changes) {
376
382
  this.#acker.trackDownstream(change);
377
383
  const [type, msg] = change;
378
- if (type === "status" && watermark === null && this.#mayRestart()) break;
379
384
  switch (type) {
380
385
  case "status":
381
- if (watermark === null) storer?.status(change[2].watermark);
386
+ if (msg.syncBackupThrough) this.#backupMonitor?.requestSync(msg.syncBackupThrough);
387
+ if (watermark === null) {
388
+ if (this.#mayRestart()) break streamLoop;
389
+ storer?.status(change[2].watermark);
390
+ }
382
391
  if (msg.lagReport && msg.lagReport.lastTimings.commitTimeMs >= this.#latestLagReportCommitTimeMs) {
383
392
  this.#latestLagReportCommitTimeMs = msg.lagReport.lastTimings.commitTimeMs;
384
393
  this.#forwarder.sendStatus({
@@ -478,7 +487,7 @@ var ChangeStreamerImpl = class {
478
487
  const lc = this.#lc.withContext("subscriber", id);
479
488
  const subscriber = new Subscriber(protocolVersion, id, mode, watermark, downstream, {
480
489
  wsBatched,
481
- lagReportingDisabled: this.#lagReportingDisabled.promise,
490
+ lagReports: this.#lagReports.promise,
482
491
  drainable: mode === "serving" && ctx.drainable,
483
492
  ...this.#backfills.subscriberOptions(lc, ctx)
484
493
  });
@@ -616,7 +625,17 @@ var ChangeStreamerImpl = class {
616
625
  drain() {
617
626
  return this.#drainTracker.drain();
618
627
  }
619
- trackBackupWatermark(watermark) {
628
+ /**
629
+ * Tracks the watermark up to which the replica has been backed up. This
630
+ * serves two purposes:
631
+ * - It serves as the maximum watermark up to which the change-log can be
632
+ * purged. Note that the change-streamer also takes into account the
633
+ * positions of its subscribers and snapshot reservations when purging
634
+ * changes in the change-log.
635
+ * - In RMv2, this ACK's the upstream change-source to allow its buffer of
636
+ * changes (e.g. the replication_slot) to advance.
637
+ */
638
+ #trackBackupWatermark(watermark) {
620
639
  this.#backupWatermark = watermark;
621
640
  this.#acker.trackBackup(watermark);
622
641
  this.#requestSQLitePurge("deferred");
@@ -759,7 +778,7 @@ var ChangeStreamerImpl = class {
759
778
  async stop(err) {
760
779
  this.#state.stop(this.#lc, err);
761
780
  this.#drainTracker.stop();
762
- this.#lagReportingDisabled.resolve(false);
781
+ this.#lagReports.resolve({ request: () => {} });
763
782
  this.#stream?.changes.cancel();
764
783
  this.#purgeScheduler?.stop();
765
784
  this.#comparator?.stop();