@prisma/streams-server 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +5 -5
- package/README.md +3 -3
- package/SECURITY.md +2 -2
- package/package.json +2 -2
- package/src/app_core.ts +114 -391
- package/src/db/db.ts +0 -54
- package/src/db/schema.ts +12 -6
- package/src/touch/interpreter_worker.ts +16 -33
- package/src/touch/live_metrics.ts +18 -49
- package/src/touch/manager.ts +26 -168
- package/src/touch/spec.ts +8 -78
- package/src/touch/worker_protocol.ts +0 -2
- package/src/touch/naming.ts +0 -13
- package/src/touch/routing_key_notifier.ts +0 -275
package/src/db/db.ts
CHANGED
|
@@ -964,60 +964,6 @@ export class SqliteDurableStore {
|
|
|
964
964
|
}
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
-
findFirstWalOffsetForRoutingKeys(
|
|
968
|
-
stream: string,
|
|
969
|
-
startOffsetExclusive: bigint,
|
|
970
|
-
endOffsetInclusive: bigint,
|
|
971
|
-
routingKeys: Uint8Array[]
|
|
972
|
-
): bigint | null {
|
|
973
|
-
if (routingKeys.length === 0) return null;
|
|
974
|
-
const start = this.bindInt(startOffsetExclusive);
|
|
975
|
-
|
|
976
|
-
// Avoid tripping SQLite variable limits (commonly 999) when callers watch a
|
|
977
|
-
// large number of keys. Chunking is cheap because the touch WAL ranges we
|
|
978
|
-
// scan are typically small and indexed by (stream, routing_key, offset).
|
|
979
|
-
const MAX_KEYS_PER_QUERY = 400;
|
|
980
|
-
let best: bigint | null = null;
|
|
981
|
-
let endLimit = endOffsetInclusive;
|
|
982
|
-
const touchNameLooksDefault = stream.endsWith(".__touch");
|
|
983
|
-
const touchLikeClause = touchNameLooksDefault ? " AND stream LIKE '%.__touch'" : "";
|
|
984
|
-
|
|
985
|
-
for (let i = 0; i < routingKeys.length; i += MAX_KEYS_PER_QUERY) {
|
|
986
|
-
const chunk = routingKeys.slice(i, i + MAX_KEYS_PER_QUERY);
|
|
987
|
-
if (chunk.length === 0) continue;
|
|
988
|
-
const end = this.bindInt(endLimit);
|
|
989
|
-
const placeholders = chunk.map(() => "?").join(", ");
|
|
990
|
-
const stmt = this.db.query(
|
|
991
|
-
`SELECT MIN(offset) as offset
|
|
992
|
-
FROM wal
|
|
993
|
-
WHERE stream = ?${touchLikeClause}
|
|
994
|
-
AND offset > ?
|
|
995
|
-
AND offset <= ?
|
|
996
|
-
AND routing_key IN (${placeholders});`
|
|
997
|
-
);
|
|
998
|
-
try {
|
|
999
|
-
const row = stmt.get(stream, start, end, ...chunk) as any;
|
|
1000
|
-
if (!row || row.offset == null) continue;
|
|
1001
|
-
const off = this.toBigInt(row.offset);
|
|
1002
|
-
if (best == null || off < best) {
|
|
1003
|
-
best = off;
|
|
1004
|
-
// No need to search beyond the best match so far.
|
|
1005
|
-
endLimit = off - 1n;
|
|
1006
|
-
if (off <= startOffsetExclusive + 1n) break;
|
|
1007
|
-
}
|
|
1008
|
-
} finally {
|
|
1009
|
-
try {
|
|
1010
|
-
stmt.finalize?.();
|
|
1011
|
-
} catch {
|
|
1012
|
-
// ignore
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
if (endLimit < startOffsetExclusive + 1n) break;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
return best;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
967
|
nextSegmentIndexForStream(stream: string): number {
|
|
1022
968
|
const row = this.stmts.nextSegmentIndex.get(stream) as any;
|
|
1023
969
|
return Number(row?.next_idx ?? 0);
|
package/src/db/schema.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { dsError } from "../util/ds_error.ts";
|
|
|
9
9
|
* - local metadata store (streams/segments/manifests/schemas)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export const SCHEMA_VERSION =
|
|
12
|
+
export const SCHEMA_VERSION = 11;
|
|
13
13
|
|
|
14
14
|
export const DEFAULT_PRAGMAS_SQL = `
|
|
15
15
|
PRAGMA journal_mode = WAL;
|
|
@@ -75,8 +75,6 @@ CREATE TABLE IF NOT EXISTS wal (
|
|
|
75
75
|
CREATE UNIQUE INDEX IF NOT EXISTS wal_stream_offset_uniq ON wal(stream, offset);
|
|
76
76
|
CREATE INDEX IF NOT EXISTS wal_stream_offset_idx ON wal(stream, offset);
|
|
77
77
|
CREATE INDEX IF NOT EXISTS wal_ts_idx ON wal(ts_ms);
|
|
78
|
-
-- Partial index for internal companion touch streams (WAL-only, routing-key heavy).
|
|
79
|
-
CREATE INDEX IF NOT EXISTS wal_touch_stream_rk_offset_idx ON wal(stream, routing_key, offset) WHERE stream LIKE '%.__touch';
|
|
80
78
|
|
|
81
79
|
CREATE TABLE IF NOT EXISTS segments (
|
|
82
80
|
segment_id TEXT PRIMARY KEY,
|
|
@@ -134,7 +132,7 @@ CREATE TABLE IF NOT EXISTS stream_interpreters (
|
|
|
134
132
|
updated_at_ms INTEGER NOT NULL
|
|
135
133
|
);
|
|
136
134
|
|
|
137
|
-
-- Live
|
|
135
|
+
-- Live dynamic template registry (per base stream).
|
|
138
136
|
CREATE TABLE IF NOT EXISTS live_templates (
|
|
139
137
|
stream TEXT NOT NULL,
|
|
140
138
|
template_id TEXT NOT NULL,
|
|
@@ -267,7 +265,6 @@ const CREATE_INDEXES_V4_SQL = `
|
|
|
267
265
|
CREATE UNIQUE INDEX IF NOT EXISTS wal_stream_offset_uniq ON wal(stream, offset);
|
|
268
266
|
CREATE INDEX IF NOT EXISTS wal_stream_offset_idx ON wal(stream, offset);
|
|
269
267
|
CREATE INDEX IF NOT EXISTS wal_ts_idx ON wal(ts_ms);
|
|
270
|
-
CREATE INDEX IF NOT EXISTS wal_touch_stream_rk_offset_idx ON wal(stream, routing_key, offset) WHERE stream LIKE '%.__touch';
|
|
271
268
|
|
|
272
269
|
CREATE INDEX IF NOT EXISTS streams_pending_bytes_idx ON streams(pending_bytes);
|
|
273
270
|
CREATE INDEX IF NOT EXISTS streams_last_cut_idx ON streams(last_segment_cut_ms);
|
|
@@ -326,6 +323,8 @@ export function initSchema(db: SqliteDatabase, opts: { skipMigrations?: boolean
|
|
|
326
323
|
migrateV8ToV9(db);
|
|
327
324
|
} else if (version === 9) {
|
|
328
325
|
migrateV9ToV10(db);
|
|
326
|
+
} else if (version === 10) {
|
|
327
|
+
migrateV10ToV11(db);
|
|
329
328
|
} else {
|
|
330
329
|
throw dsError(`unexpected schema version: ${version} (expected ${SCHEMA_VERSION})`);
|
|
331
330
|
}
|
|
@@ -555,7 +554,6 @@ function migrateV6ToV7(db: SqliteDatabase): void {
|
|
|
555
554
|
|
|
556
555
|
function migrateV7ToV8(db: SqliteDatabase): void {
|
|
557
556
|
const tx = db.transaction(() => {
|
|
558
|
-
db.exec(`CREATE INDEX IF NOT EXISTS wal_touch_stream_rk_offset_idx ON wal(stream, routing_key, offset) WHERE stream LIKE '%.__touch';`);
|
|
559
557
|
db.exec(`UPDATE schema_version SET version = 8;`);
|
|
560
558
|
});
|
|
561
559
|
tx();
|
|
@@ -613,6 +611,14 @@ function migrateV9ToV10(db: SqliteDatabase): void {
|
|
|
613
611
|
`);
|
|
614
612
|
db.exec(`DROP TABLE wal_stats;`);
|
|
615
613
|
|
|
614
|
+
db.exec(`UPDATE schema_version SET version = 10;`);
|
|
615
|
+
});
|
|
616
|
+
tx();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function migrateV10ToV11(db: SqliteDatabase): void {
|
|
620
|
+
const tx = db.transaction(() => {
|
|
621
|
+
db.exec(`DROP INDEX IF EXISTS wal_touch_stream_rk_offset_idx;`);
|
|
616
622
|
db.exec(`UPDATE schema_version SET version = ${SCHEMA_VERSION};`);
|
|
617
623
|
});
|
|
618
624
|
tx();
|
|
@@ -5,9 +5,8 @@ import { SqliteDurableStore } from "../db/db.ts";
|
|
|
5
5
|
import { initConsoleLogging } from "../util/log.ts";
|
|
6
6
|
import type { ProcessRequest } from "./worker_protocol.ts";
|
|
7
7
|
import { interpretRecordToChanges } from "./engine.ts";
|
|
8
|
-
import { encodeTemplateArg,
|
|
8
|
+
import { encodeTemplateArg, tableKeyIdFor, templateKeyIdFor, watchKeyIdFor, type TemplateEncoding } from "./live_keys.ts";
|
|
9
9
|
import { isTouchEnabled } from "./spec.ts";
|
|
10
|
-
import { resolveTouchStreamName } from "./naming.ts";
|
|
11
10
|
|
|
12
11
|
initConsoleLogging();
|
|
13
12
|
|
|
@@ -50,9 +49,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
50
49
|
return;
|
|
51
50
|
}
|
|
52
51
|
const touch = interpreter.touch;
|
|
53
|
-
const derivedStream = resolveTouchStreamName(stream, touch);
|
|
54
|
-
const touchStorage = touch.storage ?? "memory";
|
|
55
|
-
const emitRoutingKey = touchStorage === "sqlite";
|
|
56
52
|
|
|
57
53
|
const fineBudgetRaw = msg.fineTouchBudget ?? touch.fineTouchBudgetPerBatch;
|
|
58
54
|
const fineBudget = fineBudgetRaw == null ? null : Math.max(0, Math.floor(fineBudgetRaw));
|
|
@@ -125,7 +121,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
125
121
|
|
|
126
122
|
type PendingTouch = {
|
|
127
123
|
keyId: number;
|
|
128
|
-
key?: string;
|
|
129
124
|
windowStartMs: number;
|
|
130
125
|
watermark: string;
|
|
131
126
|
entity: string;
|
|
@@ -136,17 +131,16 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
136
131
|
|
|
137
132
|
const pending = new Map<string, PendingTouch>();
|
|
138
133
|
const templateOnlyEntityTouch = new Map<string, EntityTemplateOnlyTouch>();
|
|
139
|
-
const touches: Array<{ keyId: number;
|
|
134
|
+
const touches: Array<{ keyId: number; watermark: string; entity: string; kind: "table" | "template"; templateId?: string }> = [];
|
|
140
135
|
let fineTouchesDroppedDueToBudget = 0;
|
|
141
136
|
let fineTouchesSkippedColdTemplate = 0;
|
|
142
137
|
|
|
143
138
|
const flush = (_mapKey: string, p: PendingTouch) => {
|
|
144
|
-
touches.push({ keyId: p.keyId >>> 0,
|
|
139
|
+
touches.push({ keyId: p.keyId >>> 0, watermark: p.watermark, entity: p.entity, kind: p.kind, templateId: p.templateId });
|
|
145
140
|
};
|
|
146
141
|
|
|
147
142
|
const queueTouch = (args: {
|
|
148
143
|
keyId: number;
|
|
149
|
-
key?: string;
|
|
150
144
|
tsMs: number;
|
|
151
145
|
watermark: string;
|
|
152
146
|
entity: string;
|
|
@@ -154,7 +148,7 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
154
148
|
templateId?: string;
|
|
155
149
|
windowMs: number;
|
|
156
150
|
}) => {
|
|
157
|
-
const mapKey =
|
|
151
|
+
const mapKey = `i:${args.keyId >>> 0}`;
|
|
158
152
|
const prev = pending.get(mapKey);
|
|
159
153
|
|
|
160
154
|
// Guardrail: cap fine/template touches (key cardinality) per batch.
|
|
@@ -177,7 +171,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
177
171
|
if (!prev) {
|
|
178
172
|
pending.set(mapKey, {
|
|
179
173
|
keyId: args.keyId >>> 0,
|
|
180
|
-
key: args.key,
|
|
181
174
|
windowStartMs: args.tsMs,
|
|
182
175
|
watermark: args.watermark,
|
|
183
176
|
entity: args.entity,
|
|
@@ -194,7 +187,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
194
187
|
flush(mapKey, prev);
|
|
195
188
|
pending.set(mapKey, {
|
|
196
189
|
keyId: args.keyId >>> 0,
|
|
197
|
-
key: args.key,
|
|
198
190
|
windowStartMs: args.tsMs,
|
|
199
191
|
watermark: args.watermark,
|
|
200
192
|
entity: args.entity,
|
|
@@ -237,7 +229,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
237
229
|
const coarseKeyId = tableKeyIdFor(entity);
|
|
238
230
|
queueTouch({
|
|
239
231
|
keyId: coarseKeyId,
|
|
240
|
-
key: emitRoutingKey ? tableKeyFor(entity) : undefined,
|
|
241
232
|
tsMs,
|
|
242
233
|
watermark,
|
|
243
234
|
entity,
|
|
@@ -267,7 +258,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
267
258
|
if (fineGranularity === "template") {
|
|
268
259
|
queueTouch({
|
|
269
260
|
keyId: templateKeyIdFor(tpl.templateId) >>> 0,
|
|
270
|
-
key: emitRoutingKey ? templateKeyFor(tpl.templateId) : undefined,
|
|
271
261
|
tsMs,
|
|
272
262
|
watermark,
|
|
273
263
|
entity,
|
|
@@ -282,9 +272,9 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
282
272
|
const afterObj = ch.after;
|
|
283
273
|
const beforeObj = ch.before;
|
|
284
274
|
|
|
285
|
-
const
|
|
275
|
+
const watchKeyIds = new Set<number>();
|
|
286
276
|
|
|
287
|
-
const compute = (obj: unknown):
|
|
277
|
+
const compute = (obj: unknown): number | null => {
|
|
288
278
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return null;
|
|
289
279
|
const args: string[] = [];
|
|
290
280
|
for (let i = 0; i < tpl.fields.length; i++) {
|
|
@@ -295,19 +285,15 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
295
285
|
if (encoded == null) return null;
|
|
296
286
|
args.push(encoded);
|
|
297
287
|
}
|
|
298
|
-
|
|
299
|
-
const key = watchKeyFor(tpl.templateId, args);
|
|
300
|
-
return { keyId: Number.parseInt(key.slice(8), 16) >>> 0, key };
|
|
301
|
-
}
|
|
302
|
-
return { keyId: watchKeyIdFor(tpl.templateId, args) >>> 0 };
|
|
288
|
+
return watchKeyIdFor(tpl.templateId, args) >>> 0;
|
|
303
289
|
};
|
|
304
290
|
|
|
305
291
|
if (ch.op === "insert") {
|
|
306
292
|
const k = compute(afterObj);
|
|
307
|
-
if (k)
|
|
293
|
+
if (k != null) watchKeyIds.add(k >>> 0);
|
|
308
294
|
} else if (ch.op === "delete") {
|
|
309
295
|
const k = compute(beforeObj);
|
|
310
|
-
if (k)
|
|
296
|
+
if (k != null) watchKeyIds.add(k >>> 0);
|
|
311
297
|
} else {
|
|
312
298
|
// update: compute touches from both before and after (when possible).
|
|
313
299
|
// Policy for missing/insufficient before image:
|
|
@@ -317,9 +303,9 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
317
303
|
const kAfter = compute(afterObj);
|
|
318
304
|
const kBefore = compute(beforeObj);
|
|
319
305
|
|
|
320
|
-
if (kBefore) {
|
|
321
|
-
|
|
322
|
-
if (kAfter)
|
|
306
|
+
if (kBefore != null) {
|
|
307
|
+
watchKeyIds.add(kBefore >>> 0);
|
|
308
|
+
if (kAfter != null) watchKeyIds.add(kAfter >>> 0);
|
|
323
309
|
} else {
|
|
324
310
|
if (beforeObj === undefined) {
|
|
325
311
|
if (onMissingBefore === "error") {
|
|
@@ -335,17 +321,16 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
335
321
|
}
|
|
336
322
|
|
|
337
323
|
if (onMissingBefore === "skipBefore") {
|
|
338
|
-
if (kAfter)
|
|
324
|
+
if (kAfter != null) watchKeyIds.add(kAfter >>> 0);
|
|
339
325
|
} else {
|
|
340
326
|
// coarse: no fine touches
|
|
341
327
|
}
|
|
342
328
|
}
|
|
343
329
|
}
|
|
344
330
|
|
|
345
|
-
for (const
|
|
331
|
+
for (const watchKeyId of watchKeyIds) {
|
|
346
332
|
queueTouch({
|
|
347
333
|
keyId: watchKeyId >>> 0,
|
|
348
|
-
key: watchKey,
|
|
349
334
|
tsMs,
|
|
350
335
|
watermark,
|
|
351
336
|
entity,
|
|
@@ -369,7 +354,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
369
354
|
if (agg.offset < tpl.activeFromSourceOffset) continue;
|
|
370
355
|
queueTouch({
|
|
371
356
|
keyId: templateKeyIdFor(tpl.templateId) >>> 0,
|
|
372
|
-
key: emitRoutingKey ? templateKeyFor(tpl.templateId) : undefined,
|
|
373
357
|
tsMs: agg.tsMs,
|
|
374
358
|
watermark: agg.watermark,
|
|
375
359
|
entity,
|
|
@@ -386,8 +370,8 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
386
370
|
}
|
|
387
371
|
|
|
388
372
|
touches.sort((a, b) => {
|
|
389
|
-
const ak = a.
|
|
390
|
-
const bk = b.
|
|
373
|
+
const ak = a.keyId >>> 0;
|
|
374
|
+
const bk = b.keyId >>> 0;
|
|
391
375
|
if (ak < bk) return -1;
|
|
392
376
|
if (ak > bk) return 1;
|
|
393
377
|
const aw = BigInt(a.watermark);
|
|
@@ -408,7 +392,6 @@ async function handleProcess(msg: ProcessRequest): Promise<void> {
|
|
|
408
392
|
type: "result",
|
|
409
393
|
id: msg.id,
|
|
410
394
|
stream,
|
|
411
|
-
derivedStream,
|
|
412
395
|
processedThrough,
|
|
413
396
|
touches,
|
|
414
397
|
stats: {
|
|
@@ -4,8 +4,6 @@ import { STREAM_FLAG_TOUCH } from "../db/db";
|
|
|
4
4
|
import { encodeOffset } from "../offset";
|
|
5
5
|
import type { TouchConfig } from "./spec";
|
|
6
6
|
import type { TemplateLifecycleEvent } from "./live_templates";
|
|
7
|
-
import { resolveTouchStreamName } from "./naming";
|
|
8
|
-
import type { RoutingKeyNotifier } from "./routing_key_notifier";
|
|
9
7
|
import type { TouchJournalIntervalStats, TouchJournalMeta } from "./touch_journal";
|
|
10
8
|
import { Result } from "better-result";
|
|
11
9
|
|
|
@@ -207,8 +205,7 @@ export class LiveMetricsV2 {
|
|
|
207
205
|
private readonly snapshotIntervalMs: number;
|
|
208
206
|
private readonly snapshotChunkSize: number;
|
|
209
207
|
private readonly retentionMs: number;
|
|
210
|
-
private readonly
|
|
211
|
-
private readonly getTouchJournal?: (derivedStream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
208
|
+
private readonly getTouchJournal?: (stream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
212
209
|
private timer: any | null = null;
|
|
213
210
|
private snapshotTimer: any | null = null;
|
|
214
211
|
private retentionTimer: any | null = null;
|
|
@@ -234,8 +231,7 @@ export class LiveMetricsV2 {
|
|
|
234
231
|
snapshotIntervalMs?: number;
|
|
235
232
|
snapshotChunkSize?: number;
|
|
236
233
|
retentionMs?: number;
|
|
237
|
-
|
|
238
|
-
getTouchJournal?: (derivedStream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
234
|
+
getTouchJournal?: (stream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
239
235
|
}
|
|
240
236
|
) {
|
|
241
237
|
this.db = db;
|
|
@@ -246,7 +242,6 @@ export class LiveMetricsV2 {
|
|
|
246
242
|
this.snapshotIntervalMs = opts?.snapshotIntervalMs ?? 60_000;
|
|
247
243
|
this.snapshotChunkSize = opts?.snapshotChunkSize ?? 200;
|
|
248
244
|
this.retentionMs = opts?.retentionMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
249
|
-
this.routingKeyNotifier = opts?.routingKeyNotifier;
|
|
250
245
|
this.getTouchJournal = opts?.getTouchJournal;
|
|
251
246
|
}
|
|
252
247
|
|
|
@@ -500,8 +495,6 @@ export class LiveMetricsV2 {
|
|
|
500
495
|
this.lagSumMs = 0;
|
|
501
496
|
this.lagSamples = 0;
|
|
502
497
|
|
|
503
|
-
const rkInterval = this.routingKeyNotifier?.snapshotAndResetIntervalStats() ?? null;
|
|
504
|
-
|
|
505
498
|
for (const st of states) {
|
|
506
499
|
const stream = st.stream;
|
|
507
500
|
const regRow = this.db.getStream(stream);
|
|
@@ -522,26 +515,8 @@ export class LiveMetricsV2 {
|
|
|
522
515
|
if (!touchCfg) continue;
|
|
523
516
|
|
|
524
517
|
const c = this.get(stream, touchCfg);
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
const journal = storage === "memory" ? this.getTouchJournal?.(derived) ?? null : null;
|
|
528
|
-
const trow = (() => {
|
|
529
|
-
try {
|
|
530
|
-
return this.db.getStream(derived);
|
|
531
|
-
} catch {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
})();
|
|
535
|
-
const touchTailSeq = trow ? (trow.next_offset > 0n ? trow.next_offset - 1n : -1n) : -1n;
|
|
536
|
-
let touchWalOldestOffset: string | null = null;
|
|
537
|
-
try {
|
|
538
|
-
const oldest = this.db.getWalOldestOffset(derived);
|
|
539
|
-
touchWalOldestOffset = oldest == null || !trow ? null : encodeOffset(trow.epoch, oldest);
|
|
540
|
-
} catch {
|
|
541
|
-
touchWalOldestOffset = null;
|
|
542
|
-
}
|
|
543
|
-
const waitActive =
|
|
544
|
-
storage === "memory" ? (journal?.meta.activeWaiters ?? 0) : this.routingKeyNotifier ? this.routingKeyNotifier.getActiveWaiters(derived) : 0;
|
|
518
|
+
const journal = this.getTouchJournal?.(stream) ?? null;
|
|
519
|
+
const waitActive = journal?.meta.activeWaiters ?? 0;
|
|
545
520
|
const tailSeq = regRow.next_offset > 0n ? regRow.next_offset - 1n : -1n;
|
|
546
521
|
const interpretedThrough = st.interpreted_through;
|
|
547
522
|
const gcThrough = interpretedThrough < regRow.uploaded_through ? interpretedThrough : regRow.uploaded_through;
|
|
@@ -571,7 +546,6 @@ export class LiveMetricsV2 {
|
|
|
571
546
|
instanceId: this.instanceId,
|
|
572
547
|
region: this.region,
|
|
573
548
|
touch: {
|
|
574
|
-
storage,
|
|
575
549
|
coarseIntervalMs: c.touch.coarseIntervalMs,
|
|
576
550
|
coalesceWindowMs: c.touch.coalesceWindowMs,
|
|
577
551
|
mode: c.touch.mode,
|
|
@@ -596,16 +570,11 @@ export class LiveMetricsV2 {
|
|
|
596
570
|
fineTouchesSuppressedBatchesDueToLag: c.touch.fineTouchesSuppressedBatchesDueToLag,
|
|
597
571
|
fineTouchesSuppressedSecondsDueToLag: c.touch.fineTouchesSuppressedMsDueToLag / 1000,
|
|
598
572
|
fineTouchesSuppressedBatchesDueToBudget: c.touch.fineTouchesSuppressedBatchesDueToBudget,
|
|
599
|
-
cursor:
|
|
600
|
-
epoch:
|
|
601
|
-
generation:
|
|
602
|
-
pendingKeys:
|
|
603
|
-
overflowBuckets:
|
|
604
|
-
walTailOffset: trow ? encodeOffset(trow.epoch, touchTailSeq) : null,
|
|
605
|
-
walNextOffset: trow ? encodeOffset(trow.epoch, trow.next_offset) : null,
|
|
606
|
-
walOldestOffset: touchWalOldestOffset,
|
|
607
|
-
walRetainedRows: trow ? clampBigInt(trow.wal_rows) : 0,
|
|
608
|
-
walRetainedBytes: trow ? clampBigInt(trow.wal_bytes) : 0,
|
|
573
|
+
cursor: journal?.meta.cursor ?? null,
|
|
574
|
+
epoch: journal?.meta.epoch ?? null,
|
|
575
|
+
generation: journal?.meta.generation ?? null,
|
|
576
|
+
pendingKeys: journal?.meta.pendingKeys ?? 0,
|
|
577
|
+
overflowBuckets: journal?.meta.overflowBuckets ?? 0,
|
|
609
578
|
},
|
|
610
579
|
templates: {
|
|
611
580
|
active: activeTemplates,
|
|
@@ -624,15 +593,15 @@ export class LiveMetricsV2 {
|
|
|
624
593
|
avgLatencyMs: c.wait.calls > 0 ? c.wait.latencySumMs / c.wait.calls : 0,
|
|
625
594
|
p95LatencyMs: c.wait.latencyHist.p95(),
|
|
626
595
|
activeWaiters: waitActive,
|
|
627
|
-
timeoutsFired:
|
|
628
|
-
timeoutSweeps:
|
|
629
|
-
timeoutSweepMsSum:
|
|
630
|
-
timeoutSweepMsMax:
|
|
631
|
-
notifyWakeups:
|
|
632
|
-
notifyFlushes:
|
|
633
|
-
notifyWakeMsSum:
|
|
634
|
-
notifyWakeMsMax:
|
|
635
|
-
timeoutHeapSize:
|
|
596
|
+
timeoutsFired: journal?.interval.timeoutsFired ?? 0,
|
|
597
|
+
timeoutSweeps: journal?.interval.timeoutSweeps ?? 0,
|
|
598
|
+
timeoutSweepMsSum: journal?.interval.timeoutSweepMsSum ?? 0,
|
|
599
|
+
timeoutSweepMsMax: journal?.interval.timeoutSweepMsMax ?? 0,
|
|
600
|
+
notifyWakeups: journal?.interval.notifyWakeups ?? 0,
|
|
601
|
+
notifyFlushes: journal?.interval.notifyFlushes ?? 0,
|
|
602
|
+
notifyWakeMsSum: journal?.interval.notifyWakeMsSum ?? 0,
|
|
603
|
+
notifyWakeMsMax: journal?.interval.notifyWakeMsMax ?? 0,
|
|
604
|
+
timeoutHeapSize: journal?.interval.heapSize ?? 0,
|
|
636
605
|
},
|
|
637
606
|
interpreter: {
|
|
638
607
|
eventsIn: c.interpreter.eventsIn,
|