@rocicorp/zero 1.9.0-canary.2 → 1.9.0-canary.4

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 (94) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/zero/package.js +1 -1
  4. package/out/zero/package.js.map +1 -1
  5. package/out/zero-cache/src/config/zero-config.d.ts +5 -0
  6. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  7. package/out/zero-cache/src/config/zero-config.js +5 -0
  8. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  9. package/out/zero-cache/src/db/sqlite-corruption.d.ts +3 -2
  10. package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -1
  11. package/out/zero-cache/src/db/sqlite-corruption.js +13 -8
  12. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -1
  13. package/out/zero-cache/src/observability/metrics.d.ts +1 -1
  14. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  15. package/out/zero-cache/src/observability/metrics.js +5 -1
  16. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  17. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  18. package/out/zero-cache/src/server/change-streamer.js +2 -2
  19. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  20. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  21. package/out/zero-cache/src/server/main.js +1 -1
  22. package/out/zero-cache/src/server/main.js.map +1 -1
  23. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  24. package/out/zero-cache/src/server/replicator.js +2 -2
  25. package/out/zero-cache/src/server/replicator.js.map +1 -1
  26. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  27. package/out/zero-cache/src/server/syncer.js +1 -1
  28. package/out/zero-cache/src/server/syncer.js.map +1 -1
  29. package/out/zero-cache/src/services/change-source/change-source.d.ts +2 -0
  30. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  31. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  32. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/change-source.js +7 -3
  35. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  36. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +1 -0
  37. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +15 -3
  39. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  40. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +1 -0
  41. package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-source/protocol/current/data.js +4 -1
  43. package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
  44. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +3 -0
  45. package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts +1 -0
  47. package/out/zero-cache/src/services/change-source/protocol/current/status.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +1 -0
  49. package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
  51. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  52. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  53. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +23 -2
  54. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  55. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +1 -0
  56. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  57. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  58. package/out/zero-cache/src/services/life-cycle.js +1 -1
  59. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  60. package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
  61. package/out/zero-cache/src/services/replicator/change-processor.d.ts +6 -0
  62. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/replicator/change-processor.js +11 -31
  64. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  65. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  66. package/out/zero-cache/src/services/replicator/incremental-sync.js +8 -2
  67. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  68. package/out/zero-cache/src/services/replicator/notifier.d.ts +0 -22
  69. package/out/zero-cache/src/services/replicator/notifier.d.ts.map +1 -1
  70. package/out/zero-cache/src/services/replicator/notifier.js +7 -1
  71. package/out/zero-cache/src/services/replicator/notifier.js.map +1 -1
  72. package/out/zero-cache/src/services/replicator/replicator.d.ts +16 -1
  73. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/replicator/replicator.js +7 -1
  75. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  76. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts +27 -0
  77. package/out/zero-cache/src/services/replicator/reporter/recorder.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/replicator/reporter/recorder.js +32 -0
  79. package/out/zero-cache/src/services/replicator/reporter/recorder.js.map +1 -1
  80. package/out/zero-cache/src/services/replicator/reporter/report-schema.d.ts +1 -1
  81. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts +44 -0
  82. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.d.ts.map +1 -0
  83. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js +59 -0
  84. package/out/zero-cache/src/services/view-syncer/e2e-serving-lag.js.map +1 -0
  85. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  86. package/out/zero-cache/src/services/view-syncer/view-syncer.js +18 -1
  87. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  88. package/out/zero-cache/src/workers/connection.js +2 -2
  89. package/out/zero-cache/src/workers/connection.js.map +1 -1
  90. package/out/zero-client/src/client/version.js +1 -1
  91. package/out/zqlite/src/db.js +2 -2
  92. package/out/zqlite/src/sqlite-cost-model.js +5 -5
  93. package/out/zqlite/src/table-source.js +3 -3
  94. package/package.json +1 -1
@@ -34,6 +34,10 @@ var ReplicationReportRecorder = class {
34
34
  description: "Latency from sending the most recently received upstream replication report to its reaching the replica. This is an alias of replication.total_lag retained for dashboards that explicitly want the non-extrapolated value.",
35
35
  unit: "millisecond"
36
36
  }).addCallback(this.reportLastTotalLag);
37
+ getOrCreateGauge("replication", "upstream_clock_skew", {
38
+ description: "Estimated offset of the upstream database clock relative to zero-cache's. Positive means upstream is running ahead. Derived from the lag report round-trip, whose send and receive times are stamped on our clock either side of the upstream commit timestamp. This matters because sync.e2e_serving_lag subtracts an upstream commit timestamp from a local clock, so a skewed upstream clock biases it directly -- and an upstream clock running ahead biases it *low*, which looks like healthy serving rather than a broken measurement.",
39
+ unit: "millisecond"
40
+ }).addCallback(this.reportUpstreamClockSkew);
37
41
  }
38
42
  }
39
43
  reportUpstreamLag = (o) => {
@@ -52,7 +56,35 @@ var ReplicationReportRecorder = class {
52
56
  const last = this.#last?.lastTimings;
53
57
  if (last) o.observe(last.replicateTimeMs - last.sendTimeMs);
54
58
  };
59
+ reportUpstreamClockSkew = (o) => {
60
+ const last = this.#last?.lastTimings;
61
+ if (last) o.observe(estimateUpstreamClockSkewMs(last));
62
+ };
55
63
  };
64
+ /**
65
+ * Estimates how far the upstream database's clock is from ours, in
66
+ * milliseconds, positive meaning upstream is ahead.
67
+ *
68
+ * `sendTimeMs` and `receiveTimeMs` are both stamped in the
69
+ * replication-manager, either side of `commitTimeMs`, which upstream stamps.
70
+ * Assuming the upstream commit fell midway through that round trip, the
71
+ * difference between where it actually landed and that midpoint is the clock
72
+ * offset -- the same round-trip estimate NTP uses.
73
+ *
74
+ * The estimate is biased by any asymmetry between the two legs: the outbound
75
+ * leg is a SQL round trip, while the return leg additionally waits on a WAL
76
+ * flush and the walsender (which the pre-PG-17 path notes can add 50~100ms on
77
+ * an idle database). So treat this as a detector of gross skew -- the kind
78
+ * that invalidates sync.e2e_serving_lag -- and not as a sub-100ms correction.
79
+ *
80
+ * The rigorous bound, if a tighter reading is ever needed: the commit
81
+ * genuinely happened between send and receive, so a `commitTimeMs` outside
82
+ * that window proves skew of at least the amount by which it falls outside,
83
+ * independent of any latency assumption.
84
+ */
85
+ function estimateUpstreamClockSkewMs({ sendTimeMs, commitTimeMs, receiveTimeMs }) {
86
+ return commitTimeMs - (sendTimeMs + receiveTimeMs) / 2;
87
+ }
56
88
  //#endregion
57
89
  export { ReplicationReportRecorder };
58
90
 
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.js","names":["#lc","#last"],"sources":["../../../../../../../zero-cache/src/services/replicator/reporter/recorder.ts"],"sourcesContent":["import type {ObservableResult} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {getOrCreateGauge} from '../../../observability/metrics.ts';\nimport type {ReplicationReport} from './report-schema.ts';\n\n// Hook for sanity checking lag reports in development.\nconst LOG_ALL_REPLICATION_REPORTS_AT_DEBUG =\n process.env.ZERO_LOG_ALL_REPLICATION_REPORTS_AT_DEBUG === '1';\n\nexport class ReplicationReportRecorder {\n readonly #lc: LogContext;\n #last: ReplicationReport | null = null;\n\n constructor(lc: LogContext) {\n this.#lc = lc;\n }\n\n record(report: ReplicationReport) {\n const first = this.#last === null;\n this.#last = report;\n\n const {lastTimings} = report;\n if (lastTimings) {\n const total = lastTimings.replicateTimeMs - lastTimings.sendTimeMs;\n if (total > 10_000) {\n this.#lc.warn?.(`high replication lag: ${total} ms`, report);\n } else if (total > 1_000) {\n this.#lc.info?.(`replication lag: ${total} ms`, report);\n }\n if (LOG_ALL_REPLICATION_REPORTS_AT_DEBUG) {\n this.#lc.debug?.(`replication lag ${total} ms`, report);\n }\n }\n\n if (first) {\n getOrCreateGauge('replication', 'upstream_lag', {\n description:\n 'Latency from sending an upstream replication report ' +\n 'to receiving it in the replication stream',\n unit: 'millisecond',\n }).addCallback(this.reportUpstreamLag);\n\n getOrCreateGauge('replication', 'replica_lag', {\n description:\n 'Latency from receiving an upstream replication report ' +\n 'to its reaching the replica',\n unit: 'millisecond',\n }).addCallback(this.reportReplicaLag);\n\n getOrCreateGauge('replication', 'total_lag', {\n description:\n 'Latency from sending an upstream replication report to its ' +\n 'reaching the replica. This reflects the actual measured ' +\n 'round-trip of the most recently received report and does not ' +\n 'grow when reports stop arriving.',\n unit: 'millisecond',\n }).addCallback(this.reportTotalLag);\n\n getOrCreateGauge('replication', 'last_total_lag', {\n description:\n 'Latency from sending the most recently received upstream ' +\n 'replication report to its reaching the replica. This is an alias ' +\n 'of replication.total_lag retained for dashboards that explicitly ' +\n 'want the non-extrapolated value.',\n unit: 'millisecond',\n }).addCallback(this.reportLastTotalLag);\n }\n }\n\n readonly reportUpstreamLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.receiveTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportReplicaLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.receiveTimeMs);\n }\n };\n\n readonly reportTotalLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportLastTotalLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.sendTimeMs);\n }\n };\n}\n"],"mappings":";;AAMA,IAAM,uCACJ,QAAQ,IAAI,8CAA8C;AAE5D,IAAa,4BAAb,MAAuC;CACrC;CACA,QAAkC;CAElC,YAAY,IAAgB;EAC1B,KAAKA,MAAM;CACb;CAEA,OAAO,QAA2B;EAChC,MAAM,QAAQ,KAAKC,UAAU;EAC7B,KAAKA,QAAQ;EAEb,MAAM,EAAC,gBAAe;EACtB,IAAI,aAAa;GACf,MAAM,QAAQ,YAAY,kBAAkB,YAAY;GACxD,IAAI,QAAQ,KACV,KAAKD,IAAI,OAAO,yBAAyB,MAAM,MAAM,MAAM;QACtD,IAAI,QAAQ,KACjB,KAAKA,IAAI,OAAO,oBAAoB,MAAM,MAAM,MAAM;GAExD,IAAI,sCACF,KAAKA,IAAI,QAAQ,mBAAmB,MAAM,MAAM,MAAM;EAE1D;EAEA,IAAI,OAAO;GACT,iBAAiB,eAAe,gBAAgB;IAC9C,aACE;IAEF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,iBAAiB;GAErC,iBAAiB,eAAe,eAAe;IAC7C,aACE;IAEF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,gBAAgB;GAEpC,iBAAiB,eAAe,aAAa;IAC3C,aACE;IAIF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,cAAc;GAElC,iBAAiB,eAAe,kBAAkB;IAChD,aACE;IAIF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,kBAAkB;EACxC;CACF;CAEA,qBAA8B,MAAwB;EACpD,MAAM,OAAO,KAAKC,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,gBAAgB,KAAK,UAAU;CAElD;CAEA,oBAA6B,MAAwB;EACnD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,aAAa;CAEvD;CAEA,kBAA2B,MAAwB;EACjD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,UAAU;CAEpD;CAEA,sBAA+B,MAAwB;EACrD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,UAAU;CAEpD;AACF"}
1
+ {"version":3,"file":"recorder.js","names":["#lc","#last"],"sources":["../../../../../../../zero-cache/src/services/replicator/reporter/recorder.ts"],"sourcesContent":["import type {ObservableResult} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {getOrCreateGauge} from '../../../observability/metrics.ts';\nimport type {ReplicationReport} from './report-schema.ts';\n\n// Hook for sanity checking lag reports in development.\nconst LOG_ALL_REPLICATION_REPORTS_AT_DEBUG =\n process.env.ZERO_LOG_ALL_REPLICATION_REPORTS_AT_DEBUG === '1';\n\nexport class ReplicationReportRecorder {\n readonly #lc: LogContext;\n #last: ReplicationReport | null = null;\n\n constructor(lc: LogContext) {\n this.#lc = lc;\n }\n\n record(report: ReplicationReport) {\n const first = this.#last === null;\n this.#last = report;\n\n const {lastTimings} = report;\n if (lastTimings) {\n const total = lastTimings.replicateTimeMs - lastTimings.sendTimeMs;\n if (total > 10_000) {\n this.#lc.warn?.(`high replication lag: ${total} ms`, report);\n } else if (total > 1_000) {\n this.#lc.info?.(`replication lag: ${total} ms`, report);\n }\n if (LOG_ALL_REPLICATION_REPORTS_AT_DEBUG) {\n this.#lc.debug?.(`replication lag ${total} ms`, report);\n }\n }\n\n if (first) {\n getOrCreateGauge('replication', 'upstream_lag', {\n description:\n 'Latency from sending an upstream replication report ' +\n 'to receiving it in the replication stream',\n unit: 'millisecond',\n }).addCallback(this.reportUpstreamLag);\n\n getOrCreateGauge('replication', 'replica_lag', {\n description:\n 'Latency from receiving an upstream replication report ' +\n 'to its reaching the replica',\n unit: 'millisecond',\n }).addCallback(this.reportReplicaLag);\n\n getOrCreateGauge('replication', 'total_lag', {\n description:\n 'Latency from sending an upstream replication report to its ' +\n 'reaching the replica. This reflects the actual measured ' +\n 'round-trip of the most recently received report and does not ' +\n 'grow when reports stop arriving.',\n unit: 'millisecond',\n }).addCallback(this.reportTotalLag);\n\n getOrCreateGauge('replication', 'last_total_lag', {\n description:\n 'Latency from sending the most recently received upstream ' +\n 'replication report to its reaching the replica. This is an alias ' +\n 'of replication.total_lag retained for dashboards that explicitly ' +\n 'want the non-extrapolated value.',\n unit: 'millisecond',\n }).addCallback(this.reportLastTotalLag);\n\n getOrCreateGauge('replication', 'upstream_clock_skew', {\n description:\n 'Estimated offset of the upstream database clock relative to ' +\n \"zero-cache's. Positive means upstream is running ahead. \" +\n 'Derived from the lag report round-trip, whose send and receive ' +\n 'times are stamped on our clock either side of the upstream ' +\n 'commit timestamp. This matters because ' +\n 'sync.e2e_serving_lag subtracts an upstream commit timestamp from ' +\n 'a local clock, so a skewed upstream clock biases it directly -- ' +\n 'and an upstream clock running ahead biases it *low*, which looks ' +\n 'like healthy serving rather than a broken measurement.',\n unit: 'millisecond',\n }).addCallback(this.reportUpstreamClockSkew);\n }\n }\n\n readonly reportUpstreamLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.receiveTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportReplicaLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.receiveTimeMs);\n }\n };\n\n readonly reportTotalLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportLastTotalLag = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(last.replicateTimeMs - last.sendTimeMs);\n }\n };\n\n readonly reportUpstreamClockSkew = (o: ObservableResult) => {\n const last = this.#last?.lastTimings;\n if (last) {\n o.observe(estimateUpstreamClockSkewMs(last));\n }\n };\n}\n\n/**\n * Estimates how far the upstream database's clock is from ours, in\n * milliseconds, positive meaning upstream is ahead.\n *\n * `sendTimeMs` and `receiveTimeMs` are both stamped in the\n * replication-manager, either side of `commitTimeMs`, which upstream stamps.\n * Assuming the upstream commit fell midway through that round trip, the\n * difference between where it actually landed and that midpoint is the clock\n * offset -- the same round-trip estimate NTP uses.\n *\n * The estimate is biased by any asymmetry between the two legs: the outbound\n * leg is a SQL round trip, while the return leg additionally waits on a WAL\n * flush and the walsender (which the pre-PG-17 path notes can add 50~100ms on\n * an idle database). So treat this as a detector of gross skew -- the kind\n * that invalidates sync.e2e_serving_lag -- and not as a sub-100ms correction.\n *\n * The rigorous bound, if a tighter reading is ever needed: the commit\n * genuinely happened between send and receive, so a `commitTimeMs` outside\n * that window proves skew of at least the amount by which it falls outside,\n * independent of any latency assumption.\n */\nexport function estimateUpstreamClockSkewMs({\n sendTimeMs,\n commitTimeMs,\n receiveTimeMs,\n}: {\n sendTimeMs: number;\n commitTimeMs: number;\n receiveTimeMs: number;\n}): number {\n return commitTimeMs - (sendTimeMs + receiveTimeMs) / 2;\n}\n"],"mappings":";;AAMA,IAAM,uCACJ,QAAQ,IAAI,8CAA8C;AAE5D,IAAa,4BAAb,MAAuC;CACrC;CACA,QAAkC;CAElC,YAAY,IAAgB;EAC1B,KAAKA,MAAM;CACb;CAEA,OAAO,QAA2B;EAChC,MAAM,QAAQ,KAAKC,UAAU;EAC7B,KAAKA,QAAQ;EAEb,MAAM,EAAC,gBAAe;EACtB,IAAI,aAAa;GACf,MAAM,QAAQ,YAAY,kBAAkB,YAAY;GACxD,IAAI,QAAQ,KACV,KAAKD,IAAI,OAAO,yBAAyB,MAAM,MAAM,MAAM;QACtD,IAAI,QAAQ,KACjB,KAAKA,IAAI,OAAO,oBAAoB,MAAM,MAAM,MAAM;GAExD,IAAI,sCACF,KAAKA,IAAI,QAAQ,mBAAmB,MAAM,MAAM,MAAM;EAE1D;EAEA,IAAI,OAAO;GACT,iBAAiB,eAAe,gBAAgB;IAC9C,aACE;IAEF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,iBAAiB;GAErC,iBAAiB,eAAe,eAAe;IAC7C,aACE;IAEF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,gBAAgB;GAEpC,iBAAiB,eAAe,aAAa;IAC3C,aACE;IAIF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,cAAc;GAElC,iBAAiB,eAAe,kBAAkB;IAChD,aACE;IAIF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,kBAAkB;GAEtC,iBAAiB,eAAe,uBAAuB;IACrD,aACE;IASF,MAAM;GACR,CAAC,EAAE,YAAY,KAAK,uBAAuB;EAC7C;CACF;CAEA,qBAA8B,MAAwB;EACpD,MAAM,OAAO,KAAKC,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,gBAAgB,KAAK,UAAU;CAElD;CAEA,oBAA6B,MAAwB;EACnD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,aAAa;CAEvD;CAEA,kBAA2B,MAAwB;EACjD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,UAAU;CAEpD;CAEA,sBAA+B,MAAwB;EACrD,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,KAAK,kBAAkB,KAAK,UAAU;CAEpD;CAEA,2BAAoC,MAAwB;EAC1D,MAAM,OAAO,KAAKA,OAAO;EACzB,IAAI,MACF,EAAE,QAAQ,4BAA4B,IAAI,CAAC;CAE/C;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,4BAA4B,EAC1C,YACA,cACA,iBAKS;CACT,OAAO,gBAAgB,aAAa,iBAAiB;AACvD"}
@@ -21,8 +21,8 @@ export declare const replicationTimingsSchema: v.ObjectType<Omit<{
21
21
  }, undefined>;
22
22
  export declare const replicationReportSchema: v.ObjectType<{
23
23
  lastTimings: v.Optional<{
24
- sendTimeMs: number;
25
24
  commitTimeMs: number;
25
+ sendTimeMs: number;
26
26
  receiveTimeMs: number;
27
27
  replicateTimeMs: number;
28
28
  }>;
@@ -0,0 +1,44 @@
1
+ import type { ReplicaState } from '../replicator/replicator.ts';
2
+ export type PendingUpstreamCommit = {
3
+ readonly watermark: string;
4
+ readonly commitTimeMs: number;
5
+ };
6
+ /**
7
+ * Pairs the upstream commit timestamps carried by `version-ready`
8
+ * notifications with the moment the corresponding change is poked to clients,
9
+ * yielding the end-to-end serving lag.
10
+ *
11
+ * This measures completion, not backlog: an observation is produced only when
12
+ * work actually reaches clients, so the resulting histogram is a latency
13
+ * distribution rather than a periodic snapshot of how far behind things are.
14
+ * A ViewSyncer that is stuck contributes nothing until it recovers, instead of
15
+ * re-reporting its age on every sample tick.
16
+ */
17
+ export declare class E2EServingLagTracker {
18
+ #private;
19
+ get pending(): PendingUpstreamCommit | null;
20
+ /**
21
+ * Records the upstream commit behind a `version-ready` notification.
22
+ *
23
+ * Notifications coalesce when the ViewSyncer is busy, so one state may stand
24
+ * in for several commits. The *oldest* commit time is kept, since it bounds
25
+ * the lag of everything the notification subsumed, while the watermark
26
+ * advances to the newest — that is the one that must be served for all of
27
+ * the subsumed commits to have been delivered.
28
+ */
29
+ onVersionReady({ watermark, upstreamCommitTimeMs }: ReplicaState): void;
30
+ /**
31
+ * Called once a version has been poked to clients.
32
+ *
33
+ * @return the observation to record, or `null` if the served version does
34
+ * not yet cover an outstanding upstream commit.
35
+ */
36
+ onVersionServed(servedVersion: string, nowMs: number): Observation | null;
37
+ }
38
+ export type Observation = {
39
+ /** End-to-end lag in milliseconds, clamped to be non-negative. */
40
+ readonly lagMs: number;
41
+ /** Whether the clamp was applied, i.e. the raw measurement was negative. */
42
+ readonly clamped: boolean;
43
+ };
44
+ //# sourceMappingURL=e2e-serving-lag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e-serving-lag.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/e2e-serving-lag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAE9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;;IAG/B,IAAI,OAAO,IAAI,qBAAqB,GAAG,IAAI,CAE1C;IAED;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAC,SAAS,EAAE,oBAAoB,EAAC,EAAE,YAAY,GAAG,IAAI;IAcrE;;;;;OAKG;IACH,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;CAoB1E;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC"}
@@ -0,0 +1,59 @@
1
+ //#region ../zero-cache/src/services/view-syncer/e2e-serving-lag.ts
2
+ /**
3
+ * Pairs the upstream commit timestamps carried by `version-ready`
4
+ * notifications with the moment the corresponding change is poked to clients,
5
+ * yielding the end-to-end serving lag.
6
+ *
7
+ * This measures completion, not backlog: an observation is produced only when
8
+ * work actually reaches clients, so the resulting histogram is a latency
9
+ * distribution rather than a periodic snapshot of how far behind things are.
10
+ * A ViewSyncer that is stuck contributes nothing until it recovers, instead of
11
+ * re-reporting its age on every sample tick.
12
+ */
13
+ var E2EServingLagTracker = class {
14
+ #pending = null;
15
+ get pending() {
16
+ return this.#pending;
17
+ }
18
+ /**
19
+ * Records the upstream commit behind a `version-ready` notification.
20
+ *
21
+ * Notifications coalesce when the ViewSyncer is busy, so one state may stand
22
+ * in for several commits. The *oldest* commit time is kept, since it bounds
23
+ * the lag of everything the notification subsumed, while the watermark
24
+ * advances to the newest — that is the one that must be served for all of
25
+ * the subsumed commits to have been delivered.
26
+ */
27
+ onVersionReady({ watermark, upstreamCommitTimeMs }) {
28
+ if (watermark === void 0 || upstreamCommitTimeMs === void 0) return;
29
+ const pending = this.#pending;
30
+ this.#pending = {
31
+ watermark,
32
+ commitTimeMs: pending === null ? upstreamCommitTimeMs : Math.min(pending.commitTimeMs, upstreamCommitTimeMs)
33
+ };
34
+ }
35
+ /**
36
+ * Called once a version has been poked to clients.
37
+ *
38
+ * @return the observation to record, or `null` if the served version does
39
+ * not yet cover an outstanding upstream commit.
40
+ */
41
+ onVersionServed(servedVersion, nowMs) {
42
+ const pending = this.#pending;
43
+ if (pending === null || servedVersion < pending.watermark) return null;
44
+ this.#pending = null;
45
+ const lagMs = nowMs - pending.commitTimeMs;
46
+ if (lagMs >= 0) return {
47
+ lagMs,
48
+ clamped: false
49
+ };
50
+ return {
51
+ lagMs: 0,
52
+ clamped: true
53
+ };
54
+ }
55
+ };
56
+ //#endregion
57
+ export { E2EServingLagTracker };
58
+
59
+ //# sourceMappingURL=e2e-serving-lag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e-serving-lag.js","names":["#pending"],"sources":["../../../../../../zero-cache/src/services/view-syncer/e2e-serving-lag.ts"],"sourcesContent":["import type {ReplicaState} from '../replicator/replicator.ts';\n\nexport type PendingUpstreamCommit = {\n readonly watermark: string;\n readonly commitTimeMs: number;\n};\n\n/**\n * Pairs the upstream commit timestamps carried by `version-ready`\n * notifications with the moment the corresponding change is poked to clients,\n * yielding the end-to-end serving lag.\n *\n * This measures completion, not backlog: an observation is produced only when\n * work actually reaches clients, so the resulting histogram is a latency\n * distribution rather than a periodic snapshot of how far behind things are.\n * A ViewSyncer that is stuck contributes nothing until it recovers, instead of\n * re-reporting its age on every sample tick.\n */\nexport class E2EServingLagTracker {\n #pending: PendingUpstreamCommit | null = null;\n\n get pending(): PendingUpstreamCommit | null {\n return this.#pending;\n }\n\n /**\n * Records the upstream commit behind a `version-ready` notification.\n *\n * Notifications coalesce when the ViewSyncer is busy, so one state may stand\n * in for several commits. The *oldest* commit time is kept, since it bounds\n * the lag of everything the notification subsumed, while the watermark\n * advances to the newest — that is the one that must be served for all of\n * the subsumed commits to have been delivered.\n */\n onVersionReady({watermark, upstreamCommitTimeMs}: ReplicaState): void {\n if (watermark === undefined || upstreamCommitTimeMs === undefined) {\n return;\n }\n const pending = this.#pending;\n this.#pending = {\n watermark,\n commitTimeMs:\n pending === null\n ? upstreamCommitTimeMs\n : Math.min(pending.commitTimeMs, upstreamCommitTimeMs),\n };\n }\n\n /**\n * Called once a version has been poked to clients.\n *\n * @return the observation to record, or `null` if the served version does\n * not yet cover an outstanding upstream commit.\n */\n onVersionServed(servedVersion: string, nowMs: number): Observation | null {\n const pending = this.#pending;\n if (pending === null || servedVersion < pending.watermark) {\n return null;\n }\n this.#pending = null;\n\n const lagMs = nowMs - pending.commitTimeMs;\n if (lagMs >= 0) {\n return {lagMs, clamped: false};\n }\n // The commit time is on the upstream database's clock while `nowMs` is\n // local, so a negative duration means upstream's clock is running ahead of\n // ours by more than the entire pipeline latency. Clamp, because a negative\n // value would corrupt the histogram's sum -- but report the clamp, since\n // it is proof of gross clock skew, and skew in this direction biases the\n // metric *low*, which reads as healthy serving rather than as a broken\n // measurement. See replication.upstream_clock_skew for the magnitude.\n return {lagMs: 0, clamped: true};\n }\n}\n\nexport type Observation = {\n /** End-to-end lag in milliseconds, clamped to be non-negative. */\n readonly lagMs: number;\n /** Whether the clamp was applied, i.e. the raw measurement was negative. */\n readonly clamped: boolean;\n};\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAa,uBAAb,MAAkC;CAChC,WAAyC;CAEzC,IAAI,UAAwC;EAC1C,OAAO,KAAKA;CACd;;;;;;;;;;CAWA,eAAe,EAAC,WAAW,wBAA2C;EACpE,IAAI,cAAc,KAAA,KAAa,yBAAyB,KAAA,GACtD;EAEF,MAAM,UAAU,KAAKA;EACrB,KAAKA,WAAW;GACd;GACA,cACE,YAAY,OACR,uBACA,KAAK,IAAI,QAAQ,cAAc,oBAAoB;EAC3D;CACF;;;;;;;CAQA,gBAAgB,eAAuB,OAAmC;EACxE,MAAM,UAAU,KAAKA;EACrB,IAAI,YAAY,QAAQ,gBAAgB,QAAQ,WAC9C,OAAO;EAET,KAAKA,WAAW;EAEhB,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,IAAI,SAAS,GACX,OAAO;GAAC;GAAO,SAAS;EAAK;EAS/B,OAAO;GAAC,OAAO;GAAG,SAAS;EAAI;CACjC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,7 @@ import { ClientHandler, startPoke } from "./client-handler.js";
25
25
  import { tracer } from "./tracer.js";
26
26
  import { CVRStore, ClientNotFoundError } from "./cvr-store.js";
27
27
  import { CVRConfigDrivenUpdater, CVRQueryDrivenUpdater, nextEvictionTime } from "./cvr.js";
28
+ import { E2EServingLagTracker } from "./e2e-serving-lag.js";
28
29
  import { handleInspect } from "./inspect-handler.js";
29
30
  import { QueryCoveringIndex } from "./query-covering.js";
30
31
  import { resolver } from "@rocicorp/resolver";
@@ -90,6 +91,7 @@ var ViewSyncerService = class {
90
91
  #cvr;
91
92
  #pipelinesSynced = false;
92
93
  #servedVersion = null;
94
+ #e2eServingLagTracker = new E2EServingLagTracker();
93
95
  #expiredQueriesTimer = 0;
94
96
  #authMaintenanceTimer = 0;
95
97
  #setTimeout;
@@ -102,6 +104,14 @@ var ViewSyncerService = class {
102
104
  description: "Time from ViewSyncer query sync requiring hydration to output for a client group. Includes query transformation, query materialization, CVR flush, catchup, and pokeEnd.",
103
105
  unit: "s"
104
106
  });
107
+ #e2eServingLag = getOrCreateNativeHistogram("sync", "e2e_serving_lag", {
108
+ description: "End-to-end lag from upstream commit to ViewSyncer output. Spans the whole pipeline: the upstream transaction commit, replication to the replica, IVM advancement, CVR flush, and pokeEnd. Recorded once per served version, not sampled, so each observation is the completion latency of real replicated work.",
109
+ unit: "s"
110
+ });
111
+ #e2eServingLagClamps = getOrCreateCounter("sync", "e2e_serving_lag_clamps", {
112
+ description: "Observations of sync.e2e_serving_lag that came out negative and were clamped to zero. Non-zero means the upstream database clock is running ahead of this pod by more than the entire pipeline latency, so sync.e2e_serving_lag is biased low and reads healthier than reality. See replication.upstream_clock_skew for the magnitude.",
113
+ unit: "{observation}"
114
+ });
105
115
  #transactionAdvanceTime = getOrCreateLatencyHistogram("sync", "advance-time", "Time to advance all queries for a given client group after applying a new transaction to the replica.");
106
116
  #queryTransformations = getOrCreateCounter("sync", "query.transformations", "Number of query transformations performed");
107
117
  #queryTransformationTime = getOrCreateLatencyHistogram("sync", "query.transformation-time", "Time to transform custom queries via API server.");
@@ -186,12 +196,14 @@ var ViewSyncerService = class {
186
196
  this.#lc.debug?.(`draining view-syncer ${this.id} before running`);
187
197
  this.stop();
188
198
  }
189
- for await (const { state } of this.#stateChanges) {
199
+ for await (const replicaState of this.#stateChanges) {
200
+ const { state } = replicaState;
190
201
  if (this.#drainCoordinator.shouldDrain()) {
191
202
  this.#lc.debug?.(`draining view-syncer ${this.id} (elective)`);
192
203
  break;
193
204
  }
194
205
  assert(state === "version-ready", "state should be version-ready");
206
+ this.#e2eServingLagTracker.onVersionReady(replicaState);
195
207
  await this.#runInLockWithCVR(async (lc, cvr) => {
196
208
  const clientSchema = must(cvr.clientSchema, "cvr.clientSchema missing after initialization");
197
209
  if (!this.#pipelines.initialized()) this.#pipelines.init(clientSchema);
@@ -258,6 +270,11 @@ var ViewSyncerService = class {
258
270
  }
259
271
  #markVersionServed(version) {
260
272
  this.#servedVersion = version.stateVersion;
273
+ const observation = this.#e2eServingLagTracker.onVersionServed(version.stateVersion, Date.now());
274
+ if (observation !== null) {
275
+ this.#e2eServingLag.recordMs(observation.lagMs);
276
+ if (observation.clamped) this.#e2eServingLagClamps.add(1);
277
+ }
261
278
  }
262
279
  #keepAliveUntil = 0;
263
280
  /**