@powersync/service-core 1.20.2 → 1.20.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.
- package/CHANGELOG.md +14 -0
- package/dist/metrics/RollingBucketMax.d.ts +28 -0
- package/dist/metrics/RollingBucketMax.js +80 -0
- package/dist/metrics/RollingBucketMax.js.map +1 -0
- package/dist/metrics/metrics-index.d.ts +1 -0
- package/dist/metrics/metrics-index.js +1 -0
- package/dist/metrics/metrics-index.js.map +1 -1
- package/dist/replication/AbstractReplicationJob.d.ts +1 -1
- package/dist/replication/AbstractReplicator.d.ts +1 -1
- package/dist/replication/AbstractReplicator.js +10 -7
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationLagTracker.d.ts +50 -0
- package/dist/replication/ReplicationLagTracker.js +78 -0
- package/dist/replication/ReplicationLagTracker.js.map +1 -0
- package/dist/replication/replication-index.d.ts +1 -0
- package/dist/replication/replication-index.js +1 -0
- package/dist/replication/replication-index.js.map +1 -1
- package/package.json +5 -5
- package/src/metrics/RollingBucketMax.ts +109 -0
- package/src/metrics/metrics-index.ts +1 -0
- package/src/replication/AbstractReplicationJob.ts +1 -1
- package/src/replication/AbstractReplicator.ts +9 -7
- package/src/replication/ReplicationLagTracker.ts +86 -0
- package/src/replication/replication-index.ts +1 -0
- package/test/src/ReplicationLagTracker.test.ts +53 -0
- package/test/src/RollingBucketMax.test.ts +106 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @powersync/service-core
|
|
2
2
|
|
|
3
|
+
## 1.20.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- df451c6: Node 24.14.0 and other dependency upgrades.
|
|
8
|
+
- 11b4deb: Restructure `powersync_replication_lag_seconds` metric.
|
|
9
|
+
- Updated dependencies [dea1e00]
|
|
10
|
+
- Updated dependencies [ada86f2]
|
|
11
|
+
- @powersync/service-sync-rules@0.34.1
|
|
12
|
+
- @powersync/lib-services-framework@0.9.2
|
|
13
|
+
- @powersync/service-rsocket-router@0.2.18
|
|
14
|
+
|
|
15
|
+
## 1.20.3
|
|
16
|
+
|
|
3
17
|
## 1.20.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface RollingBucketMaxOptions {
|
|
2
|
+
bucketSizeMs?: number;
|
|
3
|
+
windowSizeMs?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Tracks a rolling max over a fixed number of time buckets.
|
|
7
|
+
*
|
|
8
|
+
* The window is bucket-aligned: with the default 30s window and 5s buckets,
|
|
9
|
+
* the rolling max covers the current 5s bucket plus the previous 5 buckets.
|
|
10
|
+
*/
|
|
11
|
+
export declare class RollingBucketMax {
|
|
12
|
+
private readonly bucketSizeMs;
|
|
13
|
+
private readonly bucketCount;
|
|
14
|
+
private readonly buckets;
|
|
15
|
+
constructor(options?: RollingBucketMaxOptions);
|
|
16
|
+
/**
|
|
17
|
+
* Reports a new observed value into the bucket for the provided timestamp.
|
|
18
|
+
*/
|
|
19
|
+
report(value: number | undefined, timestampMs?: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the maximum value across the current bucket and prior buckets still
|
|
22
|
+
* inside the rolling window, or undefined when the window has no samples.
|
|
23
|
+
*/
|
|
24
|
+
getRollingMax(timestampMs?: number): number | undefined;
|
|
25
|
+
private getBucketId;
|
|
26
|
+
private getBucket;
|
|
27
|
+
private assertFiniteNumber;
|
|
28
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks a rolling max over a fixed number of time buckets.
|
|
3
|
+
*
|
|
4
|
+
* The window is bucket-aligned: with the default 30s window and 5s buckets,
|
|
5
|
+
* the rolling max covers the current 5s bucket plus the previous 5 buckets.
|
|
6
|
+
*/
|
|
7
|
+
export class RollingBucketMax {
|
|
8
|
+
bucketSizeMs;
|
|
9
|
+
bucketCount;
|
|
10
|
+
// Fixed-size ring buffer keyed by bucket id modulo bucketCount.
|
|
11
|
+
buckets;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.bucketSizeMs = options.bucketSizeMs ?? 5_000;
|
|
14
|
+
const windowSizeMs = options.windowSizeMs ?? 30_000;
|
|
15
|
+
if (!Number.isInteger(this.bucketSizeMs) || this.bucketSizeMs <= 0) {
|
|
16
|
+
throw new Error('bucketSizeMs must be a positive integer.');
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isInteger(windowSizeMs) || windowSizeMs <= 0) {
|
|
19
|
+
throw new Error('windowSizeMs must be a positive integer.');
|
|
20
|
+
}
|
|
21
|
+
if (windowSizeMs % this.bucketSizeMs !== 0) {
|
|
22
|
+
throw new Error('windowSizeMs must be an exact multiple of bucketSizeMs.');
|
|
23
|
+
}
|
|
24
|
+
this.bucketCount = windowSizeMs / this.bucketSizeMs;
|
|
25
|
+
this.buckets = Array.from({ length: this.bucketCount }, () => ({
|
|
26
|
+
id: Number.NaN,
|
|
27
|
+
max: undefined
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reports a new observed value into the bucket for the provided timestamp.
|
|
32
|
+
*/
|
|
33
|
+
report(value, timestampMs = Date.now()) {
|
|
34
|
+
if (value == null) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.assertFiniteNumber(value, 'value');
|
|
38
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
39
|
+
const bucket = this.getBucket(this.getBucketId(timestampMs));
|
|
40
|
+
bucket.max = bucket.max === undefined ? value : Math.max(bucket.max, value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the maximum value across the current bucket and prior buckets still
|
|
44
|
+
* inside the rolling window, or undefined when the window has no samples.
|
|
45
|
+
*/
|
|
46
|
+
getRollingMax(timestampMs = Date.now()) {
|
|
47
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
48
|
+
const currentBucketId = this.getBucketId(timestampMs);
|
|
49
|
+
const minimumBucketId = currentBucketId - this.bucketCount + 1;
|
|
50
|
+
let rollingMax;
|
|
51
|
+
for (const bucket of this.buckets) {
|
|
52
|
+
if (bucket.max === undefined) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (bucket.id < minimumBucketId || bucket.id > currentBucketId) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
rollingMax = rollingMax === undefined ? bucket.max : Math.max(rollingMax, bucket.max);
|
|
59
|
+
}
|
|
60
|
+
return rollingMax;
|
|
61
|
+
}
|
|
62
|
+
getBucketId(timestampMs) {
|
|
63
|
+
return Math.floor(timestampMs / this.bucketSizeMs);
|
|
64
|
+
}
|
|
65
|
+
getBucket(bucketId) {
|
|
66
|
+
const index = ((bucketId % this.bucketCount) + this.bucketCount) % this.bucketCount;
|
|
67
|
+
const bucket = this.buckets[index];
|
|
68
|
+
if (bucket.id !== bucketId) {
|
|
69
|
+
bucket.id = bucketId;
|
|
70
|
+
bucket.max = undefined;
|
|
71
|
+
}
|
|
72
|
+
return bucket;
|
|
73
|
+
}
|
|
74
|
+
assertFiniteNumber(value, name) {
|
|
75
|
+
if (!Number.isFinite(value)) {
|
|
76
|
+
throw new Error(`${name} must be a finite number.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=RollingBucketMax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RollingBucketMax.js","sourceRoot":"","sources":["../../src/metrics/RollingBucketMax.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './MetricsEngine.js';
|
|
2
2
|
export * from './metrics-interfaces.js';
|
|
3
|
+
export * from './RollingBucketMax.js';
|
|
3
4
|
export * from './register-metrics.js';
|
|
4
5
|
export * from './open-telemetry/OpenTelemetryMetricsFactory.js';
|
|
5
6
|
export * from './open-telemetry/util.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './MetricsEngine.js';
|
|
2
2
|
export * from './metrics-interfaces.js';
|
|
3
|
+
export * from './RollingBucketMax.js';
|
|
3
4
|
export * from './register-metrics.js';
|
|
4
5
|
export * from './open-telemetry/OpenTelemetryMetricsFactory.js';
|
|
5
6
|
export * from './open-telemetry/util.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics-index.js","sourceRoot":"","sources":["../../src/metrics/metrics-index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iDAAiD,CAAC;AAChE,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"metrics-index.js","sourceRoot":"","sources":["../../src/metrics/metrics-index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iDAAiD,CAAC;AAChE,cAAc,0BAA0B,CAAC"}
|
|
@@ -85,5 +85,5 @@ export declare abstract class AbstractReplicator<T extends AbstractReplicationJo
|
|
|
85
85
|
*
|
|
86
86
|
* "processing" replication streams are not taken into account for this metric.
|
|
87
87
|
*/
|
|
88
|
-
getReplicationLagMillis():
|
|
88
|
+
getReplicationLagMillis(): number | undefined;
|
|
89
89
|
}
|
|
@@ -64,15 +64,18 @@ export class AbstractReplicator {
|
|
|
64
64
|
}, 1000);
|
|
65
65
|
});
|
|
66
66
|
this.metrics.getObservableGauge(ReplicationMetric.REPLICATION_LAG_SECONDS).setValueProvider(async () => {
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
const lag = this.getReplicationLagMillis();
|
|
69
|
+
if (lag == null) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
// ms to seconds
|
|
73
|
+
return Math.round(lag / 1000);
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
68
76
|
this.logger.error('Failed to get replication lag', e);
|
|
69
77
|
return undefined;
|
|
70
|
-
});
|
|
71
|
-
if (lag == null) {
|
|
72
|
-
return undefined;
|
|
73
78
|
}
|
|
74
|
-
// ms to seconds
|
|
75
|
-
return Math.round(lag / 1000);
|
|
76
79
|
});
|
|
77
80
|
}
|
|
78
81
|
async stop() {
|
|
@@ -257,7 +260,7 @@ export class AbstractReplicator {
|
|
|
257
260
|
*
|
|
258
261
|
* "processing" replication streams are not taken into account for this metric.
|
|
259
262
|
*/
|
|
260
|
-
|
|
263
|
+
getReplicationLagMillis() {
|
|
261
264
|
return this.activeReplicationJob?.getReplicationLagMillis();
|
|
262
265
|
}
|
|
263
266
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractReplicator.js","sourceRoot":"","sources":["../../src/replication/AbstractReplicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,OAAO,MAAM,6BAA6B,CAAC;AAOvD,WAAW;AACX,MAAM,aAAa,GAAG,WAAc,GAAG,GAAG,CAAC;AAkB3C;;;;GAIG;AACH,MAAM,OAAgB,kBAAkB;IA2BR;IA1BpB,MAAM,CAAiB;IACzB,WAAW,GAAY,KAAK,CAAC;IACrC;;;OAGG;IACK,eAAe,GAAG,IAAI,GAAG,EAAa,CAAC;IAE/C;;;;;OAKG;IACK,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;IAExD;;OAEG;IACK,oBAAoB,GAAkB,SAAS,CAAC;IAExD,wDAAwD;IAChD,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;IAE3B,eAAe,CAA8B;IAErD,YAA8B,OAAkC;QAAlC,YAAO,GAAP,OAAO,CAA2B;QAC9D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC;IAUD,IAAW,EAAE;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACzB,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,mBAAmB,CAAC;IACxD,CAAC;IAED,IAAc,gBAAgB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC;IACvC,CAAC;IAED,IAAc,WAAW;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;IAClC,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IACpC,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC;IAC9C,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YACzB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,EAAE,CAAC,CAAC,CAAC;YAC5D,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACvC,UAAU,CAAC,GAAG,EAAE;gBACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACrG,MAAM,GAAG,GAAG,
|
|
1
|
+
{"version":3,"file":"AbstractReplicator.js","sourceRoot":"","sources":["../../src/replication/AbstractReplicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,OAAO,MAAM,6BAA6B,CAAC;AAOvD,WAAW;AACX,MAAM,aAAa,GAAG,WAAc,GAAG,GAAG,CAAC;AAkB3C;;;;GAIG;AACH,MAAM,OAAgB,kBAAkB;IA2BR;IA1BpB,MAAM,CAAiB;IACzB,WAAW,GAAY,KAAK,CAAC;IACrC;;;OAGG;IACK,eAAe,GAAG,IAAI,GAAG,EAAa,CAAC;IAE/C;;;;;OAKG;IACK,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;IAExD;;OAEG;IACK,oBAAoB,GAAkB,SAAS,CAAC;IAExD,wDAAwD;IAChD,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;IAE3B,eAAe,CAA8B;IAErD,YAA8B,OAAkC;QAAlC,YAAO,GAAP,OAAO,CAA2B;QAC9D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC;IAUD,IAAW,EAAE;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACzB,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,mBAAmB,CAAC;IACxD,CAAC;IAED,IAAc,gBAAgB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC;IACvC,CAAC;IAED,IAAc,WAAW;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;IAClC,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IACpC,CAAC;IAED,IAAc,OAAO;QACnB,OAAO,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC;IAC9C,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YACzB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,EAAE,CAAC,CAAC,CAAC;YAC5D,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;YACvC,UAAU,CAAC,GAAG,EAAE;gBACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,EAAE,IAAI,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YACrG,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAC3C,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;oBAChB,OAAO,SAAS,CAAC;gBACnB,CAAC;gBACD,gBAAgB;gBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;YAChC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;gBACtD,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,CAAC;QAC9B,IAAI,QAAQ,GAAoB,EAAE,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;YAChD,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC;QAEpD,IAAI,cAAc,GAAwC,SAAS,CAAC;QACpE,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACtC,IAAI,CAAC;gBACH,kDAAkD;gBAClD,2GAA2G;gBAE3G,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,kBAAkB,CACpD,OAAO,CAAC,uBAAuB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CACxG,CAAC;gBACF,IAAI,IAAI,EAAE,CAAC;oBACT,cAAc,GAAG,IAAI,CAAC;gBACxB,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,4BAA4B;gBAC5B,4EAA4E;gBAC5E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,EAAE,CAAC,CAAC,CAAC;gBACvE,MAAM,CAAC,CAAC;YACV,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC;gBACxD,+CAA+C;gBAC/C,cAAc,GAAG,SAAS,CAAC;gBAE3B,gEAAgE;gBAChE,6EAA6E;gBAC7E,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;oBAC5B,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,aAAa,EAAE,CAAC;wBACzC,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;4BACtD,MAAM,SAAS,CAAC,SAAS,EAAE,CAAC;wBAC9B,CAAC;wBAED,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;oBACtB,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,EAAE,CAAC,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,OAAuD;QAC3E,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,cAAc,GAAG,OAAO,EAAE,eAAe,CAAC;QAE9C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAY,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACxE,MAAM,oBAAoB,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,CAAC;QAC1E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;QACrC,IAAI,SAAS,GAAkB,SAAS,CAAC;QACzC,KAAK,IAAI,SAAS,IAAI,oBAAoB,EAAE,CAAC;YAC3C,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnD,IAAI,SAAS,CAAC,MAAM,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;gBAC1C,SAAS,GAAG,WAAW,CAAC;YAC1B,CAAC;YACD,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC1C,YAAY;gBACZ,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBAClC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;YACzC,CAAC;iBAAM,IAAI,WAAW,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;gBAChD,8BAA8B;gBAC9B,gEAAgE;gBAChE,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,sDAAsD;gBACtD,IAAI,CAAC;oBACH,IAAI,IAA6B,CAAC;oBAClC,IAAI,cAAc,EAAE,aAAa,IAAI,SAAS,CAAC,EAAE,EAAE,CAAC;wBAClD,IAAI,GAAG,cAAc,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACN,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;oBAChC,CAAC;oBACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;oBACpD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC;wBAC5B,IAAI,EAAE,IAAI;wBACV,OAAO,EAAE,OAAO;qBACjB,CAAC,CAAC;oBAEH,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;oBAClC,MAAM,CAAC,KAAK,EAAE,CAAC;oBACf,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;wBACrB,SAAS,GAAG,MAAM,CAAC;oBACrB,CAAC;oBACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBAC3B,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,KAAK,SAAS,CAAC,WAAW,EAAE,CAAC;wBACjD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;4BACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;4BACrE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;wBAC1B,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,qCAAqC;wBACrC,uDAAuD;wBACvD,gDAAgD;wBAChD,gFAAgF;wBAChF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,EAAE,CAAC,CAAC,CAAC;oBACzE,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;QAC/B,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC;QAEtC,yDAAyD;QACzD,4BAA4B;QAC5B,KAAK,IAAI,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC;YACtC,0BAA0B;YAC1B,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YACnB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,uBAAuB;gBACvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,mEAAmE;QACnE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;QACzD,KAAK,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxC,sBAAsB;gBACtB,SAAS;YACX,CAAC;YAED,mCAAmC;YACnC,uFAAuF;YACvF,wCAAwC;YACxC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC;iBACrD,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;gBACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qDAAqD,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3F,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;YACL,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAES,WAAW,CAAC,UAAkB;QACtC,OAAO,GAAG,IAAI,CAAC,EAAE,IAAI,UAAU,EAAE,CAAC;IACpC,CAAC;IAES,KAAK,CAAC,kBAAkB,CAAC,eAA+C;QAChF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,eAAe,CAAC,QAAQ,KAAK,CAAC,CAAC;QAC3E,qEAAqE;QACrE,qEAAqE;QACrE,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACpC,MAAM,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,eAAe,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtF,CAAC;IAID;;;;;;;;;;;;;;;;;OAiBG;IACH,uBAAuB;QACrB,OAAO,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,EAAE,CAAC;IAC9D,CAAC;CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks replication lag across the current in-flight transaction and a rolling
|
|
3
|
+
* max of recently observed lag values.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ReplicationLagTracker {
|
|
6
|
+
private readonly rollingReplicationLag;
|
|
7
|
+
private _oldestUncommittedChange;
|
|
8
|
+
private _isStartingReplication;
|
|
9
|
+
/**
|
|
10
|
+
* The oldest source timestamp still part of the current in-flight work.
|
|
11
|
+
*/
|
|
12
|
+
get oldestUncommittedChange(): Date | null;
|
|
13
|
+
/**
|
|
14
|
+
* True until replication has seen its first completed commit or equivalent keepalive.
|
|
15
|
+
*/
|
|
16
|
+
get isStartingReplication(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Registers the first source timestamp for the current in-flight work,
|
|
19
|
+
* for example the start of a transaction
|
|
20
|
+
*/
|
|
21
|
+
trackUncommittedChange(timestamp: Date | null | undefined): void;
|
|
22
|
+
/**
|
|
23
|
+
* Clears the current in-flight timestamp without changing startup state.
|
|
24
|
+
*/
|
|
25
|
+
clearUncommittedChange(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Marks replication as started even if no committed transaction lag was recorded.
|
|
28
|
+
*/
|
|
29
|
+
markStarted(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Mark the current pending changes as "committed".
|
|
32
|
+
*
|
|
33
|
+
* Records the current in-flight lag into the rolling window and clears it.
|
|
34
|
+
* The current lag is calculated as the differnence between current time and the oldest change,
|
|
35
|
+
* as marked by trackUncommittedChange.
|
|
36
|
+
*/
|
|
37
|
+
markCommitted(timestampMs?: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the lag for the current in-flight work.
|
|
40
|
+
*
|
|
41
|
+
* 0 if idle (no pending changes to replicate).
|
|
42
|
+
*
|
|
43
|
+
* undefined when replication is still starting up.
|
|
44
|
+
*/
|
|
45
|
+
getCurrentLagMillis(timestampMs?: number): number | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Returns the rolling lag metric value, including the current in-flight lag when present.
|
|
48
|
+
*/
|
|
49
|
+
getLagMillis(timestampMs?: number): number | undefined;
|
|
50
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { RollingBucketMax } from '../metrics/RollingBucketMax.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks replication lag across the current in-flight transaction and a rolling
|
|
4
|
+
* max of recently observed lag values.
|
|
5
|
+
*/
|
|
6
|
+
export class ReplicationLagTracker {
|
|
7
|
+
rollingReplicationLag = new RollingBucketMax();
|
|
8
|
+
_oldestUncommittedChange = null;
|
|
9
|
+
_isStartingReplication = true;
|
|
10
|
+
/**
|
|
11
|
+
* The oldest source timestamp still part of the current in-flight work.
|
|
12
|
+
*/
|
|
13
|
+
get oldestUncommittedChange() {
|
|
14
|
+
return this._oldestUncommittedChange;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* True until replication has seen its first completed commit or equivalent keepalive.
|
|
18
|
+
*/
|
|
19
|
+
get isStartingReplication() {
|
|
20
|
+
return this._isStartingReplication;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Registers the first source timestamp for the current in-flight work,
|
|
24
|
+
* for example the start of a transaction
|
|
25
|
+
*/
|
|
26
|
+
trackUncommittedChange(timestamp) {
|
|
27
|
+
if (this._oldestUncommittedChange == null && timestamp != null) {
|
|
28
|
+
this._oldestUncommittedChange = timestamp;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Clears the current in-flight timestamp without changing startup state.
|
|
33
|
+
*/
|
|
34
|
+
clearUncommittedChange() {
|
|
35
|
+
this._oldestUncommittedChange = null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Marks replication as started even if no committed transaction lag was recorded.
|
|
39
|
+
*/
|
|
40
|
+
markStarted() {
|
|
41
|
+
this._isStartingReplication = false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Mark the current pending changes as "committed".
|
|
45
|
+
*
|
|
46
|
+
* Records the current in-flight lag into the rolling window and clears it.
|
|
47
|
+
* The current lag is calculated as the differnence between current time and the oldest change,
|
|
48
|
+
* as marked by trackUncommittedChange.
|
|
49
|
+
*/
|
|
50
|
+
markCommitted(timestampMs = Date.now()) {
|
|
51
|
+
if (this._oldestUncommittedChange != null) {
|
|
52
|
+
this.rollingReplicationLag.report(timestampMs - this._oldestUncommittedChange.getTime(), timestampMs);
|
|
53
|
+
}
|
|
54
|
+
this.clearUncommittedChange();
|
|
55
|
+
this.markStarted();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns the lag for the current in-flight work.
|
|
59
|
+
*
|
|
60
|
+
* 0 if idle (no pending changes to replicate).
|
|
61
|
+
*
|
|
62
|
+
* undefined when replication is still starting up.
|
|
63
|
+
*/
|
|
64
|
+
getCurrentLagMillis(timestampMs = Date.now()) {
|
|
65
|
+
if (this._oldestUncommittedChange == null) {
|
|
66
|
+
return this._isStartingReplication ? undefined : 0;
|
|
67
|
+
}
|
|
68
|
+
return timestampMs - this._oldestUncommittedChange.getTime();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns the rolling lag metric value, including the current in-flight lag when present.
|
|
72
|
+
*/
|
|
73
|
+
getLagMillis(timestampMs = Date.now()) {
|
|
74
|
+
this.rollingReplicationLag.report(this.getCurrentLagMillis(timestampMs), timestampMs);
|
|
75
|
+
return this.rollingReplicationLag.getRollingMax(timestampMs);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=ReplicationLagTracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplicationLagTracker.js","sourceRoot":"","sources":["../../src/replication/ReplicationLagTracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IACf,qBAAqB,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACxD,wBAAwB,GAAgB,IAAI,CAAC;IAC7C,sBAAsB,GAAG,IAAI,CAAC;IAEtC;;OAEG;IACH,IAAI,uBAAuB;QACzB,OAAO,IAAI,CAAC,wBAAwB,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,IAAI,qBAAqB;QACvB,OAAO,IAAI,CAAC,sBAAsB,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,sBAAsB,CAAC,SAAkC;QACvD,IAAI,IAAI,CAAC,wBAAwB,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;YAC/D,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC;QAC5C,CAAC;IACH,CAAC;IAED;;OAEG;IACH,sBAAsB;QACpB,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,WAAW;QACT,IAAI,CAAC,sBAAsB,GAAG,KAAK,CAAC;IACtC,CAAC;IAED;;;;;;OAMG;IACH,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE;QACpC,IAAI,IAAI,CAAC,wBAAwB,IAAI,IAAI,EAAE,CAAC;YAC1C,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,wBAAwB,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;QACxG,CAAC;QACD,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAED;;;;;;OAMG;IACH,mBAAmB,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE;QAC1C,IAAI,IAAI,CAAC,wBAAwB,IAAI,IAAI,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,WAAW,GAAG,IAAI,CAAC,wBAAwB,CAAC,OAAO,EAAE,CAAC;IAC/D,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE;QACnC,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC,qBAAqB,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC;CACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './AbstractReplicationJob.js';
|
|
2
2
|
export * from './AbstractReplicator.js';
|
|
3
3
|
export * from './ErrorRateLimiter.js';
|
|
4
|
+
export * from './ReplicationLagTracker.js';
|
|
4
5
|
export * from './ReplicationEngine.js';
|
|
5
6
|
export * from './ReplicationModule.js';
|
|
6
7
|
export * from './replication-metrics.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './AbstractReplicationJob.js';
|
|
2
2
|
export * from './AbstractReplicator.js';
|
|
3
3
|
export * from './ErrorRateLimiter.js';
|
|
4
|
+
export * from './ReplicationLagTracker.js';
|
|
4
5
|
export * from './ReplicationEngine.js';
|
|
5
6
|
export * from './ReplicationModule.js';
|
|
6
7
|
export * from './replication-metrics.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replication-index.js","sourceRoot":"","sources":["../../src/replication/replication-index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"replication-index.js","sourceRoot":"","sources":["../../src/replication/replication-index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.20.
|
|
8
|
+
"version": "1.20.4",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"license": "FSL-1.1-ALv2",
|
|
11
11
|
"type": "module",
|
|
@@ -33,17 +33,17 @@
|
|
|
33
33
|
"uuid": "^11.1.0",
|
|
34
34
|
"winston": "^3.13.0",
|
|
35
35
|
"yaml": "^2.3.2",
|
|
36
|
-
"@powersync/lib-services-framework": "0.9.
|
|
36
|
+
"@powersync/lib-services-framework": "0.9.2",
|
|
37
37
|
"@powersync/service-jsonbig": "0.17.12",
|
|
38
|
-
"@powersync/service-rsocket-router": "0.2.
|
|
39
|
-
"@powersync/service-sync-rules": "0.34.
|
|
38
|
+
"@powersync/service-rsocket-router": "0.2.18",
|
|
39
|
+
"@powersync/service-sync-rules": "0.34.1",
|
|
40
40
|
"@powersync/service-types": "0.15.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/async": "^3.2.24",
|
|
44
44
|
"@types/negotiator": "^0.6.4",
|
|
45
45
|
"@types/lodash": "^4.17.5",
|
|
46
|
-
"fastify": "^5.
|
|
46
|
+
"fastify": "^5.8.2",
|
|
47
47
|
"fastify-plugin": "^5.0.1"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export interface RollingBucketMaxOptions {
|
|
2
|
+
bucketSizeMs?: number;
|
|
3
|
+
windowSizeMs?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Bucket {
|
|
7
|
+
// Absolute bucket id derived from floor(timestamp / bucketSizeMs).
|
|
8
|
+
id: number;
|
|
9
|
+
// Maximum reported value seen within this bucket.
|
|
10
|
+
max: number | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a rolling max over a fixed number of time buckets.
|
|
15
|
+
*
|
|
16
|
+
* The window is bucket-aligned: with the default 30s window and 5s buckets,
|
|
17
|
+
* the rolling max covers the current 5s bucket plus the previous 5 buckets.
|
|
18
|
+
*/
|
|
19
|
+
export class RollingBucketMax {
|
|
20
|
+
private readonly bucketSizeMs: number;
|
|
21
|
+
private readonly bucketCount: number;
|
|
22
|
+
// Fixed-size ring buffer keyed by bucket id modulo bucketCount.
|
|
23
|
+
private readonly buckets: Bucket[];
|
|
24
|
+
|
|
25
|
+
constructor(options: RollingBucketMaxOptions = {}) {
|
|
26
|
+
this.bucketSizeMs = options.bucketSizeMs ?? 5_000;
|
|
27
|
+
const windowSizeMs = options.windowSizeMs ?? 30_000;
|
|
28
|
+
|
|
29
|
+
if (!Number.isInteger(this.bucketSizeMs) || this.bucketSizeMs <= 0) {
|
|
30
|
+
throw new Error('bucketSizeMs must be a positive integer.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!Number.isInteger(windowSizeMs) || windowSizeMs <= 0) {
|
|
34
|
+
throw new Error('windowSizeMs must be a positive integer.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (windowSizeMs % this.bucketSizeMs !== 0) {
|
|
38
|
+
throw new Error('windowSizeMs must be an exact multiple of bucketSizeMs.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.bucketCount = windowSizeMs / this.bucketSizeMs;
|
|
42
|
+
this.buckets = Array.from({ length: this.bucketCount }, () => ({
|
|
43
|
+
id: Number.NaN,
|
|
44
|
+
max: undefined
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reports a new observed value into the bucket for the provided timestamp.
|
|
50
|
+
*/
|
|
51
|
+
report(value: number | undefined, timestampMs = Date.now()): void {
|
|
52
|
+
if (value == null) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.assertFiniteNumber(value, 'value');
|
|
56
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
57
|
+
|
|
58
|
+
const bucket = this.getBucket(this.getBucketId(timestampMs));
|
|
59
|
+
bucket.max = bucket.max === undefined ? value : Math.max(bucket.max, value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the maximum value across the current bucket and prior buckets still
|
|
64
|
+
* inside the rolling window, or undefined when the window has no samples.
|
|
65
|
+
*/
|
|
66
|
+
getRollingMax(timestampMs = Date.now()): number | undefined {
|
|
67
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
68
|
+
|
|
69
|
+
const currentBucketId = this.getBucketId(timestampMs);
|
|
70
|
+
const minimumBucketId = currentBucketId - this.bucketCount + 1;
|
|
71
|
+
|
|
72
|
+
let rollingMax: number | undefined;
|
|
73
|
+
for (const bucket of this.buckets) {
|
|
74
|
+
if (bucket.max === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (bucket.id < minimumBucketId || bucket.id > currentBucketId) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
rollingMax = rollingMax === undefined ? bucket.max : Math.max(rollingMax, bucket.max);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return rollingMax;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getBucketId(timestampMs: number): number {
|
|
89
|
+
return Math.floor(timestampMs / this.bucketSizeMs);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private getBucket(bucketId: number): Bucket {
|
|
93
|
+
const index = ((bucketId % this.bucketCount) + this.bucketCount) % this.bucketCount;
|
|
94
|
+
const bucket = this.buckets[index];
|
|
95
|
+
|
|
96
|
+
if (bucket.id !== bucketId) {
|
|
97
|
+
bucket.id = bucketId;
|
|
98
|
+
bucket.max = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return bucket;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private assertFiniteNumber(value: number, name: string): void {
|
|
105
|
+
if (!Number.isFinite(value)) {
|
|
106
|
+
throw new Error(`${name} must be a finite number.`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './MetricsEngine.js';
|
|
2
2
|
export * from './metrics-interfaces.js';
|
|
3
|
+
export * from './RollingBucketMax.js';
|
|
3
4
|
export * from './register-metrics.js';
|
|
4
5
|
export * from './open-telemetry/OpenTelemetryMetricsFactory.js';
|
|
5
6
|
export * from './open-telemetry/util.js';
|
|
@@ -107,15 +107,17 @@ export abstract class AbstractReplicator<T extends AbstractReplicationJob = Abst
|
|
|
107
107
|
}, 1000);
|
|
108
108
|
});
|
|
109
109
|
this.metrics.getObservableGauge(ReplicationMetric.REPLICATION_LAG_SECONDS).setValueProvider(async () => {
|
|
110
|
-
|
|
110
|
+
try {
|
|
111
|
+
const lag = this.getReplicationLagMillis();
|
|
112
|
+
if (lag == null) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
// ms to seconds
|
|
116
|
+
return Math.round(lag / 1000);
|
|
117
|
+
} catch (e) {
|
|
111
118
|
this.logger.error('Failed to get replication lag', e);
|
|
112
119
|
return undefined;
|
|
113
|
-
});
|
|
114
|
-
if (lag == null) {
|
|
115
|
-
return undefined;
|
|
116
120
|
}
|
|
117
|
-
// ms to seconds
|
|
118
|
-
return Math.round(lag / 1000);
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -312,7 +314,7 @@ export abstract class AbstractReplicator<T extends AbstractReplicationJob = Abst
|
|
|
312
314
|
*
|
|
313
315
|
* "processing" replication streams are not taken into account for this metric.
|
|
314
316
|
*/
|
|
315
|
-
|
|
317
|
+
getReplicationLagMillis(): number | undefined {
|
|
316
318
|
return this.activeReplicationJob?.getReplicationLagMillis();
|
|
317
319
|
}
|
|
318
320
|
}
|