@prisma/streams-server 0.1.1 → 0.1.3
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/CONTRIBUTING.md +8 -0
- package/package.json +2 -1
- package/src/app.ts +290 -17
- package/src/app_core.ts +1833 -698
- package/src/app_local.ts +144 -4
- package/src/auto_tune.ts +62 -0
- package/src/bootstrap.ts +159 -1
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +116 -14
- package/src/db/db.ts +1201 -131
- package/src/db/schema.ts +308 -8
- package/src/foreground_activity.ts +55 -0
- package/src/index/indexer.ts +254 -124
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +789 -0
- package/src/index/secondary_indexer.ts +824 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +10 -12
- package/src/manifest.ts +143 -8
- package/src/memory.ts +183 -8
- package/src/metrics.ts +15 -29
- package/src/metrics_emitter.ts +26 -3
- package/src/notifier.ts +121 -5
- package/src/objectstore/accounting.ts +92 -0
- package/src/objectstore/mock_r2.ts +1 -1
- package/src/objectstore/r2.ts +17 -1
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +299 -0
- package/src/profiles/generic.ts +47 -0
- package/src/profiles/index.ts +205 -0
- package/src/profiles/metrics/block_format.ts +109 -0
- package/src/profiles/metrics/normalize.ts +366 -0
- package/src/profiles/metrics/schema.ts +319 -0
- package/src/profiles/metrics.ts +85 -0
- package/src/profiles/profile.ts +225 -0
- package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
- package/src/profiles/stateProtocol/routes.ts +389 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +100 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2151 -164
- package/src/runtime/host_runtime.ts +5 -0
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +235 -0
- package/src/schema/read_json.ts +43 -0
- package/src/schema/registry.ts +563 -59
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +389 -0
- package/src/search/binary/codec.ts +162 -0
- package/src/search/binary/docset.ts +67 -0
- package/src/search/binary/restart_strings.ts +181 -0
- package/src/search/binary/varint.ts +34 -0
- package/src/search/bitset.ts +19 -0
- package/src/search/col_format.ts +382 -0
- package/src/search/col_runtime.ts +59 -0
- package/src/search/column_encoding.ts +43 -0
- package/src/search/companion_file_cache.ts +319 -0
- package/src/search/companion_format.ts +313 -0
- package/src/search/companion_manager.ts +1086 -0
- package/src/search/companion_plan.ts +218 -0
- package/src/search/fts_format.ts +423 -0
- package/src/search/fts_runtime.ts +333 -0
- package/src/search/query.ts +875 -0
- package/src/search/schema.ts +245 -0
- package/src/segment/cache.ts +93 -2
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +108 -36
- package/src/segment/segmenter.ts +79 -5
- package/src/segment/segmenter_worker.ts +35 -6
- package/src/segment/segmenter_workers.ts +42 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +185 -14
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +3 -3
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_metrics.ts +94 -64
- package/src/touch/live_templates.ts +15 -1
- package/src/touch/manager.ts +166 -88
- package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +8 -14
- package/src/touch/worker_protocol.ts +3 -3
- package/src/uploader.ts +77 -6
- package/src/util/bloom256.ts +2 -2
- package/src/util/byte_lru.ts +73 -0
- package/src/util/lru.ts +8 -0
- package/src/util/stream_paths.ts +19 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { IngestQueue } from "../ingest";
|
|
2
2
|
import type { SqliteDurableStore } from "../db/db";
|
|
3
|
+
import type { StreamProfileStore } from "../profiles";
|
|
4
|
+
import { resolveEnabledTouchCapability } from "../profiles";
|
|
3
5
|
import { STREAM_FLAG_TOUCH } from "../db/db";
|
|
4
6
|
import { encodeOffset } from "../offset";
|
|
5
7
|
import type { TouchConfig } from "./spec";
|
|
@@ -126,7 +128,7 @@ type StreamCounters = {
|
|
|
126
128
|
latencySumMs: number;
|
|
127
129
|
latencyHist: LatencyHistogram;
|
|
128
130
|
};
|
|
129
|
-
|
|
131
|
+
processor: {
|
|
130
132
|
eventsIn: number;
|
|
131
133
|
changesOut: number;
|
|
132
134
|
errors: number;
|
|
@@ -134,7 +136,7 @@ type StreamCounters = {
|
|
|
134
136
|
scannedBatches: number;
|
|
135
137
|
scannedButEmitted0Batches: number;
|
|
136
138
|
noInterestFastForwardBatches: number;
|
|
137
|
-
|
|
139
|
+
processedThroughDelta: number;
|
|
138
140
|
touchesEmittedDelta: number;
|
|
139
141
|
commitLagSamples: number;
|
|
140
142
|
commitLagMsSum: number;
|
|
@@ -142,6 +144,10 @@ type StreamCounters = {
|
|
|
142
144
|
};
|
|
143
145
|
};
|
|
144
146
|
|
|
147
|
+
export type LiveMetricsMemoryStats = {
|
|
148
|
+
counterStreams: number;
|
|
149
|
+
};
|
|
150
|
+
|
|
145
151
|
function defaultCounters(touchCfg: TouchConfig): StreamCounters {
|
|
146
152
|
return {
|
|
147
153
|
touch: {
|
|
@@ -179,7 +185,7 @@ function defaultCounters(touchCfg: TouchConfig): StreamCounters {
|
|
|
179
185
|
},
|
|
180
186
|
templates: { activated: 0, retired: 0, evicted: 0, activationDenied: 0 },
|
|
181
187
|
wait: { calls: 0, keysWatchedTotal: 0, touched: 0, timeout: 0, stale: 0, latencySumMs: 0, latencyHist: makeLatencyHistogram() },
|
|
182
|
-
|
|
188
|
+
processor: {
|
|
183
189
|
eventsIn: 0,
|
|
184
190
|
changesOut: 0,
|
|
185
191
|
errors: 0,
|
|
@@ -187,7 +193,7 @@ function defaultCounters(touchCfg: TouchConfig): StreamCounters {
|
|
|
187
193
|
scannedBatches: 0,
|
|
188
194
|
scannedButEmitted0Batches: 0,
|
|
189
195
|
noInterestFastForwardBatches: 0,
|
|
190
|
-
|
|
196
|
+
processedThroughDelta: 0,
|
|
191
197
|
touchesEmittedDelta: 0,
|
|
192
198
|
commitLagSamples: 0,
|
|
193
199
|
commitLagMsSum: 0,
|
|
@@ -199,6 +205,7 @@ function defaultCounters(touchCfg: TouchConfig): StreamCounters {
|
|
|
199
205
|
export class LiveMetricsV2 {
|
|
200
206
|
private readonly db: SqliteDurableStore;
|
|
201
207
|
private readonly ingest: IngestQueue;
|
|
208
|
+
private readonly profiles: StreamProfileStore;
|
|
202
209
|
private readonly metricsStream: string;
|
|
203
210
|
private readonly enabled: boolean;
|
|
204
211
|
private readonly intervalMs: number;
|
|
@@ -206,6 +213,10 @@ export class LiveMetricsV2 {
|
|
|
206
213
|
private readonly snapshotChunkSize: number;
|
|
207
214
|
private readonly retentionMs: number;
|
|
208
215
|
private readonly getTouchJournal?: (stream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
216
|
+
private readonly onAppended?: (args: {
|
|
217
|
+
lastOffset: bigint;
|
|
218
|
+
stream: string;
|
|
219
|
+
}) => void;
|
|
209
220
|
private timer: any | null = null;
|
|
210
221
|
private snapshotTimer: any | null = null;
|
|
211
222
|
private retentionTimer: any | null = null;
|
|
@@ -224,6 +235,7 @@ export class LiveMetricsV2 {
|
|
|
224
235
|
constructor(
|
|
225
236
|
db: SqliteDurableStore,
|
|
226
237
|
ingest: IngestQueue,
|
|
238
|
+
profiles: StreamProfileStore,
|
|
227
239
|
opts?: {
|
|
228
240
|
enabled?: boolean;
|
|
229
241
|
stream?: string;
|
|
@@ -232,10 +244,12 @@ export class LiveMetricsV2 {
|
|
|
232
244
|
snapshotChunkSize?: number;
|
|
233
245
|
retentionMs?: number;
|
|
234
246
|
getTouchJournal?: (stream: string) => { meta: TouchJournalMeta; interval: TouchJournalIntervalStats } | null;
|
|
247
|
+
onAppended?: (args: { lastOffset: bigint; stream: string }) => void;
|
|
235
248
|
}
|
|
236
249
|
) {
|
|
237
250
|
this.db = db;
|
|
238
251
|
this.ingest = ingest;
|
|
252
|
+
this.profiles = profiles;
|
|
239
253
|
this.enabled = opts?.enabled !== false;
|
|
240
254
|
this.metricsStream = opts?.stream ?? "live.metrics";
|
|
241
255
|
this.intervalMs = opts?.intervalMs ?? 1000;
|
|
@@ -243,6 +257,7 @@ export class LiveMetricsV2 {
|
|
|
243
257
|
this.snapshotChunkSize = opts?.snapshotChunkSize ?? 200;
|
|
244
258
|
this.retentionMs = opts?.retentionMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
245
259
|
this.getTouchJournal = opts?.getTouchJournal;
|
|
260
|
+
this.onAppended = opts?.onAppended;
|
|
246
261
|
}
|
|
247
262
|
|
|
248
263
|
start(): void {
|
|
@@ -329,12 +344,16 @@ export class LiveMetricsV2 {
|
|
|
329
344
|
return c;
|
|
330
345
|
}
|
|
331
346
|
|
|
332
|
-
|
|
347
|
+
getMemoryStats(): LiveMetricsMemoryStats {
|
|
348
|
+
return { counterStreams: this.counters.size };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
recordProcessorError(stream: string, touchCfg: TouchConfig): void {
|
|
333
352
|
const c = this.get(stream, touchCfg);
|
|
334
|
-
c.
|
|
353
|
+
c.processor.errors += 1;
|
|
335
354
|
}
|
|
336
355
|
|
|
337
|
-
|
|
356
|
+
recordProcessorBatch(args: {
|
|
338
357
|
stream: string;
|
|
339
358
|
touchCfg: TouchConfig;
|
|
340
359
|
rowsRead: number;
|
|
@@ -361,7 +380,7 @@ export class LiveMetricsV2 {
|
|
|
361
380
|
fineTouchesSuppressedDueToBudget?: boolean;
|
|
362
381
|
scannedButEmitted0?: boolean;
|
|
363
382
|
noInterestFastForward?: boolean;
|
|
364
|
-
|
|
383
|
+
processedThroughDelta?: number;
|
|
365
384
|
touchesEmittedDelta?: number;
|
|
366
385
|
}): void {
|
|
367
386
|
const c = this.get(args.stream, args.touchCfg);
|
|
@@ -377,18 +396,18 @@ export class LiveMetricsV2 {
|
|
|
377
396
|
c.touch.fineWaitersActive = Math.max(c.touch.fineWaitersActive, Math.max(0, Math.floor(args.fineWaitersActive ?? 0)));
|
|
378
397
|
c.touch.coarseWaitersActive = Math.max(c.touch.coarseWaitersActive, Math.max(0, Math.floor(args.coarseWaitersActive ?? 0)));
|
|
379
398
|
c.touch.broadFineWaitersActive = Math.max(c.touch.broadFineWaitersActive, Math.max(0, Math.floor(args.broadFineWaitersActive ?? 0)));
|
|
380
|
-
c.
|
|
381
|
-
c.
|
|
382
|
-
c.
|
|
383
|
-
c.
|
|
384
|
-
if (args.scannedButEmitted0) c.
|
|
385
|
-
if (args.noInterestFastForward) c.
|
|
386
|
-
c.
|
|
387
|
-
c.
|
|
399
|
+
c.processor.eventsIn += Math.max(0, args.rowsRead);
|
|
400
|
+
c.processor.changesOut += Math.max(0, args.changes);
|
|
401
|
+
c.processor.lagSourceOffsets = Math.max(c.processor.lagSourceOffsets, Math.max(0, args.lagSourceOffsets));
|
|
402
|
+
c.processor.scannedBatches += 1;
|
|
403
|
+
if (args.scannedButEmitted0) c.processor.scannedButEmitted0Batches += 1;
|
|
404
|
+
if (args.noInterestFastForward) c.processor.noInterestFastForwardBatches += 1;
|
|
405
|
+
c.processor.processedThroughDelta += Math.max(0, Math.floor(args.processedThroughDelta ?? 0));
|
|
406
|
+
c.processor.touchesEmittedDelta += Math.max(0, Math.floor(args.touchesEmittedDelta ?? 0));
|
|
388
407
|
if (args.commitLagMs != null && Number.isFinite(args.commitLagMs) && args.commitLagMs >= 0) {
|
|
389
|
-
c.
|
|
390
|
-
c.
|
|
391
|
-
c.
|
|
408
|
+
c.processor.commitLagSamples += 1;
|
|
409
|
+
c.processor.commitLagMsSum += args.commitLagMs;
|
|
410
|
+
c.processor.commitLagHist.record(args.commitLagMs);
|
|
392
411
|
}
|
|
393
412
|
c.touch.fineTouchesDroppedDueToBudget += Math.max(0, args.fineTouchesDroppedDueToBudget ?? 0);
|
|
394
413
|
c.touch.fineTouchesSkippedColdTemplate += Math.max(0, args.fineTouchesSkippedColdTemplate ?? 0);
|
|
@@ -458,12 +477,18 @@ export class LiveMetricsV2 {
|
|
|
458
477
|
}
|
|
459
478
|
|
|
460
479
|
try {
|
|
461
|
-
await this.ingest.appendInternal({
|
|
480
|
+
const appendRes = await this.ingest.appendInternal({
|
|
462
481
|
stream: this.metricsStream,
|
|
463
482
|
baseAppendMs: BigInt(Date.now()),
|
|
464
483
|
rows,
|
|
465
484
|
contentType: "application/json",
|
|
466
485
|
});
|
|
486
|
+
if (!Result.isError(appendRes)) {
|
|
487
|
+
this.onAppended?.({
|
|
488
|
+
lastOffset: appendRes.value.lastOffset,
|
|
489
|
+
stream: this.metricsStream,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
467
492
|
} catch {
|
|
468
493
|
// best-effort
|
|
469
494
|
}
|
|
@@ -483,7 +508,7 @@ export class LiveMetricsV2 {
|
|
|
483
508
|
return v > max ? Number.MAX_SAFE_INTEGER : Number(v);
|
|
484
509
|
};
|
|
485
510
|
|
|
486
|
-
const states = this.db.
|
|
511
|
+
const states = this.db.listStreamTouchStates();
|
|
487
512
|
if (states.length === 0) return;
|
|
488
513
|
|
|
489
514
|
const rows: Array<{ routingKey: Uint8Array | null; contentType: string; payload: Uint8Array }> = [];
|
|
@@ -501,16 +526,9 @@ export class LiveMetricsV2 {
|
|
|
501
526
|
if (!regRow) continue;
|
|
502
527
|
|
|
503
528
|
const touchCfg = ((): TouchConfig | null => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const raw = JSON.parse(row.registry_json);
|
|
508
|
-
const cfg = raw?.interpreter?.touch;
|
|
509
|
-
if (!cfg || !cfg.enabled) return null;
|
|
510
|
-
return cfg as TouchConfig;
|
|
511
|
-
} catch {
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
529
|
+
const profileRes = this.profiles.getProfileResult(stream, regRow);
|
|
530
|
+
if (Result.isError(profileRes)) return null;
|
|
531
|
+
return resolveEnabledTouchCapability(profileRes.value)?.touchCfg ?? null;
|
|
514
532
|
})();
|
|
515
533
|
if (!touchCfg) continue;
|
|
516
534
|
|
|
@@ -518,9 +536,9 @@ export class LiveMetricsV2 {
|
|
|
518
536
|
const journal = this.getTouchJournal?.(stream) ?? null;
|
|
519
537
|
const waitActive = journal?.meta.activeWaiters ?? 0;
|
|
520
538
|
const tailSeq = regRow.next_offset > 0n ? regRow.next_offset - 1n : -1n;
|
|
521
|
-
const
|
|
522
|
-
const gcThrough =
|
|
523
|
-
const backlog = tailSeq >=
|
|
539
|
+
const processedThrough = st.processed_through;
|
|
540
|
+
const gcThrough = processedThrough < regRow.uploaded_through ? processedThrough : regRow.uploaded_through;
|
|
541
|
+
const backlog = tailSeq >= processedThrough ? tailSeq - processedThrough : 0n;
|
|
524
542
|
const backlogNum = backlog > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(backlog);
|
|
525
543
|
let walOldestOffset: string | null = null;
|
|
526
544
|
try {
|
|
@@ -603,27 +621,27 @@ export class LiveMetricsV2 {
|
|
|
603
621
|
notifyWakeMsMax: journal?.interval.notifyWakeMsMax ?? 0,
|
|
604
622
|
timeoutHeapSize: journal?.interval.heapSize ?? 0,
|
|
605
623
|
},
|
|
606
|
-
|
|
607
|
-
eventsIn: c.
|
|
608
|
-
changesOut: c.
|
|
609
|
-
errors: c.
|
|
610
|
-
lagSourceOffsets: c.
|
|
611
|
-
scannedBatches: c.
|
|
612
|
-
scannedButEmitted0Batches: c.
|
|
613
|
-
noInterestFastForwardBatches: c.
|
|
614
|
-
|
|
615
|
-
touchesEmittedDelta: c.
|
|
616
|
-
commitLagMsAvg: c.
|
|
617
|
-
commitLagMsP50: c.
|
|
618
|
-
commitLagMsP95: c.
|
|
619
|
-
commitLagMsP99: c.
|
|
624
|
+
processor: {
|
|
625
|
+
eventsIn: c.processor.eventsIn,
|
|
626
|
+
changesOut: c.processor.changesOut,
|
|
627
|
+
errors: c.processor.errors,
|
|
628
|
+
lagSourceOffsets: c.processor.lagSourceOffsets,
|
|
629
|
+
scannedBatches: c.processor.scannedBatches,
|
|
630
|
+
scannedButEmitted0Batches: c.processor.scannedButEmitted0Batches,
|
|
631
|
+
noInterestFastForwardBatches: c.processor.noInterestFastForwardBatches,
|
|
632
|
+
processedThroughDelta: c.processor.processedThroughDelta,
|
|
633
|
+
touchesEmittedDelta: c.processor.touchesEmittedDelta,
|
|
634
|
+
commitLagMsAvg: c.processor.commitLagSamples > 0 ? c.processor.commitLagMsSum / c.processor.commitLagSamples : 0,
|
|
635
|
+
commitLagMsP50: c.processor.commitLagHist.p50(),
|
|
636
|
+
commitLagMsP95: c.processor.commitLagHist.p95(),
|
|
637
|
+
commitLagMsP99: c.processor.commitLagHist.p99(),
|
|
620
638
|
},
|
|
621
639
|
base: {
|
|
622
640
|
tailOffset: encodeOffset(regRow.epoch, tailSeq),
|
|
623
641
|
nextOffset: encodeOffset(regRow.epoch, regRow.next_offset),
|
|
624
642
|
sealedThrough: encodeOffset(regRow.epoch, regRow.sealed_through),
|
|
625
643
|
uploadedThrough: encodeOffset(regRow.epoch, regRow.uploaded_through),
|
|
626
|
-
|
|
644
|
+
processedThrough: encodeOffset(regRow.epoch, processedThrough),
|
|
627
645
|
gcThrough: encodeOffset(regRow.epoch, gcThrough),
|
|
628
646
|
walOldestOffset,
|
|
629
647
|
walRetainedRows: clampBigInt(regRow.wal_rows),
|
|
@@ -683,18 +701,18 @@ export class LiveMetricsV2 {
|
|
|
683
701
|
c.wait.stale = 0;
|
|
684
702
|
c.wait.latencySumMs = 0;
|
|
685
703
|
c.wait.latencyHist.reset();
|
|
686
|
-
c.
|
|
687
|
-
c.
|
|
688
|
-
c.
|
|
689
|
-
c.
|
|
690
|
-
c.
|
|
691
|
-
c.
|
|
692
|
-
c.
|
|
693
|
-
c.
|
|
694
|
-
c.
|
|
695
|
-
c.
|
|
696
|
-
c.
|
|
697
|
-
c.
|
|
704
|
+
c.processor.eventsIn = 0;
|
|
705
|
+
c.processor.changesOut = 0;
|
|
706
|
+
c.processor.errors = 0;
|
|
707
|
+
c.processor.lagSourceOffsets = 0;
|
|
708
|
+
c.processor.scannedBatches = 0;
|
|
709
|
+
c.processor.scannedButEmitted0Batches = 0;
|
|
710
|
+
c.processor.noInterestFastForwardBatches = 0;
|
|
711
|
+
c.processor.processedThroughDelta = 0;
|
|
712
|
+
c.processor.touchesEmittedDelta = 0;
|
|
713
|
+
c.processor.commitLagSamples = 0;
|
|
714
|
+
c.processor.commitLagMsSum = 0;
|
|
715
|
+
c.processor.commitLagHist.reset();
|
|
698
716
|
c.gc.baseWalGcCalls = 0;
|
|
699
717
|
c.gc.baseWalGcDeletedRows = 0;
|
|
700
718
|
c.gc.baseWalGcDeletedBytes = 0;
|
|
@@ -704,12 +722,18 @@ export class LiveMetricsV2 {
|
|
|
704
722
|
|
|
705
723
|
if (rows.length === 0) return;
|
|
706
724
|
try {
|
|
707
|
-
await this.ingest.appendInternal({
|
|
725
|
+
const appendRes = await this.ingest.appendInternal({
|
|
708
726
|
stream: this.metricsStream,
|
|
709
727
|
baseAppendMs: BigInt(nowMs),
|
|
710
728
|
rows,
|
|
711
729
|
contentType: "application/json",
|
|
712
730
|
});
|
|
731
|
+
if (!Result.isError(appendRes)) {
|
|
732
|
+
this.onAppended?.({
|
|
733
|
+
lastOffset: appendRes.value.lastOffset,
|
|
734
|
+
stream: this.metricsStream,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
713
737
|
} catch {
|
|
714
738
|
// best-effort
|
|
715
739
|
}
|
|
@@ -718,7 +742,7 @@ export class LiveMetricsV2 {
|
|
|
718
742
|
private async emitSnapshots(): Promise<void> {
|
|
719
743
|
if (!this.enabled) return;
|
|
720
744
|
const nowMs = Date.now();
|
|
721
|
-
const streams = this.db.
|
|
745
|
+
const streams = this.db.listStreamTouchStates().map((r) => r.stream);
|
|
722
746
|
if (streams.length === 0) return;
|
|
723
747
|
|
|
724
748
|
const encoder = new TextEncoder();
|
|
@@ -814,12 +838,18 @@ export class LiveMetricsV2 {
|
|
|
814
838
|
|
|
815
839
|
if (rows.length === 0) return;
|
|
816
840
|
try {
|
|
817
|
-
await this.ingest.appendInternal({
|
|
841
|
+
const appendRes = await this.ingest.appendInternal({
|
|
818
842
|
stream: this.metricsStream,
|
|
819
843
|
baseAppendMs: BigInt(nowMs),
|
|
820
844
|
rows,
|
|
821
845
|
contentType: "application/json",
|
|
822
846
|
});
|
|
847
|
+
if (!Result.isError(appendRes)) {
|
|
848
|
+
this.onAppended?.({
|
|
849
|
+
lastOffset: appendRes.value.lastOffset,
|
|
850
|
+
stream: this.metricsStream,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
823
853
|
} catch {
|
|
824
854
|
// best-effort
|
|
825
855
|
}
|
|
@@ -66,6 +66,12 @@ export type TemplateLifecycleEvent =
|
|
|
66
66
|
|
|
67
67
|
type RateState = { tokens: number; lastRefillMs: number };
|
|
68
68
|
|
|
69
|
+
export type LiveTemplateRegistryMemoryStats = {
|
|
70
|
+
lastSeenEntries: number;
|
|
71
|
+
dirtyLastSeenEntries: number;
|
|
72
|
+
rateStateStreams: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
69
75
|
function nowIso(ms: number): string {
|
|
70
76
|
return new Date(ms).toISOString();
|
|
71
77
|
}
|
|
@@ -105,6 +111,14 @@ export class LiveTemplateRegistry {
|
|
|
105
111
|
return `${stream}\n${templateId}`;
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
getMemoryStats(): LiveTemplateRegistryMemoryStats {
|
|
115
|
+
return {
|
|
116
|
+
lastSeenEntries: this.lastSeenMem.size,
|
|
117
|
+
dirtyLastSeenEntries: this.dirtyLastSeen.size,
|
|
118
|
+
rateStateStreams: this.rate.size,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
108
122
|
private allowActivation(stream: string, nowMs: number, limitPerMinute: number): boolean {
|
|
109
123
|
if (limitPerMinute <= 0) return true;
|
|
110
124
|
const ratePerMs = limitPerMinute / 60_000;
|
|
@@ -163,7 +177,7 @@ export class LiveTemplateRegistry {
|
|
|
163
177
|
*
|
|
164
178
|
* `baseStreamNextOffset` is used to set `active_from_source_offset` so we do
|
|
165
179
|
* not backfill fine touches for history when a template is activated while
|
|
166
|
-
*
|
|
180
|
+
* touch processing is behind.
|
|
167
181
|
*/
|
|
168
182
|
activate(args: {
|
|
169
183
|
stream: string;
|