@prisma/streams-server 0.1.2 → 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_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 +31 -5
- package/src/segment/segmenter_workers.ts +40 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +155 -8
- 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} +13 -13
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +6 -13
- 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
package/src/sqlite/adapter.ts
CHANGED
|
@@ -13,15 +13,49 @@ export interface SqliteStatement {
|
|
|
13
13
|
export interface SqliteDatabase {
|
|
14
14
|
exec(sql: string): void;
|
|
15
15
|
query(sql: string): SqliteStatement;
|
|
16
|
+
prepare(sql: string): SqliteStatement;
|
|
16
17
|
transaction<T>(fn: () => T): () => T;
|
|
17
18
|
close(): void;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
type SqliteAdapterRuntimeCounts = {
|
|
22
|
+
open_connections: number;
|
|
23
|
+
prepared_statements: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const sqliteAdapterRuntimeCounts: SqliteAdapterRuntimeCounts = {
|
|
27
|
+
open_connections: 0,
|
|
28
|
+
prepared_statements: 0,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function incrementSqliteConnection(): void {
|
|
32
|
+
sqliteAdapterRuntimeCounts.open_connections += 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function decrementSqliteConnection(): void {
|
|
36
|
+
sqliteAdapterRuntimeCounts.open_connections = Math.max(0, sqliteAdapterRuntimeCounts.open_connections - 1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function incrementPreparedStatement(): void {
|
|
40
|
+
sqliteAdapterRuntimeCounts.prepared_statements += 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function decrementPreparedStatement(): void {
|
|
44
|
+
sqliteAdapterRuntimeCounts.prepared_statements = Math.max(0, sqliteAdapterRuntimeCounts.prepared_statements - 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getSqliteAdapterRuntimeCounts(): SqliteAdapterRuntimeCounts {
|
|
48
|
+
return { ...sqliteAdapterRuntimeCounts };
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
class BunStatementAdapter implements SqliteStatement {
|
|
21
52
|
private readonly stmt: any;
|
|
53
|
+
private readonly onFinalize?: () => void;
|
|
54
|
+
private finalized = false;
|
|
22
55
|
|
|
23
|
-
constructor(stmt: any) {
|
|
56
|
+
constructor(stmt: any, onFinalize?: () => void) {
|
|
24
57
|
this.stmt = stmt;
|
|
58
|
+
this.onFinalize = onFinalize;
|
|
25
59
|
}
|
|
26
60
|
|
|
27
61
|
get(...params: any[]): any {
|
|
@@ -41,23 +75,58 @@ class BunStatementAdapter implements SqliteStatement {
|
|
|
41
75
|
}
|
|
42
76
|
|
|
43
77
|
finalize(): void {
|
|
44
|
-
if (
|
|
78
|
+
if (this.finalized) return;
|
|
79
|
+
this.finalized = true;
|
|
80
|
+
try {
|
|
81
|
+
if (typeof this.stmt.finalize === "function") this.stmt.finalize();
|
|
82
|
+
} finally {
|
|
83
|
+
this.onFinalize?.();
|
|
84
|
+
}
|
|
45
85
|
}
|
|
46
86
|
}
|
|
47
87
|
|
|
48
88
|
class BunDatabaseAdapter implements SqliteDatabase {
|
|
49
89
|
private readonly db: any;
|
|
90
|
+
private preparedStatementCount = 0;
|
|
91
|
+
private closed = false;
|
|
92
|
+
private readonly statementCache = new Map<string, BunStatementAdapter>();
|
|
50
93
|
|
|
51
94
|
constructor(db: any) {
|
|
52
95
|
this.db = db;
|
|
96
|
+
incrementSqliteConnection();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private trackStatement(): () => void {
|
|
100
|
+
let released = false;
|
|
101
|
+
this.preparedStatementCount += 1;
|
|
102
|
+
incrementPreparedStatement();
|
|
103
|
+
return () => {
|
|
104
|
+
if (released) return;
|
|
105
|
+
released = true;
|
|
106
|
+
this.preparedStatementCount = Math.max(0, this.preparedStatementCount - 1);
|
|
107
|
+
decrementPreparedStatement();
|
|
108
|
+
};
|
|
53
109
|
}
|
|
54
110
|
|
|
55
111
|
exec(sql: string): void {
|
|
56
112
|
this.db.exec(sql);
|
|
57
113
|
}
|
|
58
114
|
|
|
115
|
+
prepare(sql: string): SqliteStatement {
|
|
116
|
+
return new BunStatementAdapter(this.db.query(sql), this.trackStatement());
|
|
117
|
+
}
|
|
118
|
+
|
|
59
119
|
query(sql: string): SqliteStatement {
|
|
60
|
-
|
|
120
|
+
const cached = this.statementCache.get(sql);
|
|
121
|
+
if (cached) return cached;
|
|
122
|
+
let adapter: BunStatementAdapter;
|
|
123
|
+
const release = this.trackStatement();
|
|
124
|
+
adapter = new BunStatementAdapter(this.db.query(sql), () => {
|
|
125
|
+
this.statementCache.delete(sql);
|
|
126
|
+
release();
|
|
127
|
+
});
|
|
128
|
+
this.statementCache.set(sql, adapter);
|
|
129
|
+
return adapter;
|
|
61
130
|
}
|
|
62
131
|
|
|
63
132
|
transaction<T>(fn: () => T): () => T {
|
|
@@ -65,15 +134,37 @@ class BunDatabaseAdapter implements SqliteDatabase {
|
|
|
65
134
|
}
|
|
66
135
|
|
|
67
136
|
close(): void {
|
|
68
|
-
this.
|
|
137
|
+
if (this.closed) return;
|
|
138
|
+
this.closed = true;
|
|
139
|
+
const cachedStatements = Array.from(this.statementCache.values());
|
|
140
|
+
this.statementCache.clear();
|
|
141
|
+
for (const stmt of cachedStatements) {
|
|
142
|
+
try {
|
|
143
|
+
stmt.finalize?.();
|
|
144
|
+
} catch {
|
|
145
|
+
// Ignore finalizer failures during shutdown.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
while (this.preparedStatementCount > 0) {
|
|
149
|
+
this.preparedStatementCount -= 1;
|
|
150
|
+
decrementPreparedStatement();
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
this.db.close();
|
|
154
|
+
} finally {
|
|
155
|
+
decrementSqliteConnection();
|
|
156
|
+
}
|
|
69
157
|
}
|
|
70
158
|
}
|
|
71
159
|
|
|
72
160
|
class NodeStatementAdapter implements SqliteStatement {
|
|
73
161
|
private readonly stmt: any;
|
|
162
|
+
private readonly onFinalize?: () => void;
|
|
163
|
+
private finalized = false;
|
|
74
164
|
|
|
75
|
-
constructor(stmt: any) {
|
|
165
|
+
constructor(stmt: any, onFinalize?: () => void) {
|
|
76
166
|
this.stmt = stmt;
|
|
167
|
+
this.onFinalize = onFinalize;
|
|
77
168
|
}
|
|
78
169
|
|
|
79
170
|
get(...params: any[]): any {
|
|
@@ -93,7 +184,13 @@ class NodeStatementAdapter implements SqliteStatement {
|
|
|
93
184
|
}
|
|
94
185
|
|
|
95
186
|
finalize(): void {
|
|
96
|
-
if (
|
|
187
|
+
if (this.finalized) return;
|
|
188
|
+
this.finalized = true;
|
|
189
|
+
try {
|
|
190
|
+
if (typeof this.stmt.finalize === "function") this.stmt.finalize();
|
|
191
|
+
} finally {
|
|
192
|
+
this.onFinalize?.();
|
|
193
|
+
}
|
|
97
194
|
}
|
|
98
195
|
}
|
|
99
196
|
|
|
@@ -101,19 +198,50 @@ class NodeDatabaseAdapter implements SqliteDatabase {
|
|
|
101
198
|
private txDepth = 0;
|
|
102
199
|
private txCounter = 0;
|
|
103
200
|
private readonly db: any;
|
|
201
|
+
private preparedStatementCount = 0;
|
|
202
|
+
private closed = false;
|
|
203
|
+
private readonly statementCache = new Map<string, NodeStatementAdapter>();
|
|
104
204
|
|
|
105
205
|
constructor(db: any) {
|
|
106
206
|
this.db = db;
|
|
207
|
+
incrementSqliteConnection();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private trackStatement(): () => void {
|
|
211
|
+
let released = false;
|
|
212
|
+
this.preparedStatementCount += 1;
|
|
213
|
+
incrementPreparedStatement();
|
|
214
|
+
return () => {
|
|
215
|
+
if (released) return;
|
|
216
|
+
released = true;
|
|
217
|
+
this.preparedStatementCount = Math.max(0, this.preparedStatementCount - 1);
|
|
218
|
+
decrementPreparedStatement();
|
|
219
|
+
};
|
|
107
220
|
}
|
|
108
221
|
|
|
109
222
|
exec(sql: string): void {
|
|
110
223
|
this.db.exec(sql);
|
|
111
224
|
}
|
|
112
225
|
|
|
226
|
+
prepare(sql: string): SqliteStatement {
|
|
227
|
+
const stmt = this.db.prepare(sql);
|
|
228
|
+
if (typeof stmt?.setReadBigInts === "function") stmt.setReadBigInts(true);
|
|
229
|
+
return new NodeStatementAdapter(stmt, this.trackStatement());
|
|
230
|
+
}
|
|
231
|
+
|
|
113
232
|
query(sql: string): SqliteStatement {
|
|
233
|
+
const cached = this.statementCache.get(sql);
|
|
234
|
+
if (cached) return cached;
|
|
114
235
|
const stmt = this.db.prepare(sql);
|
|
115
236
|
if (typeof stmt?.setReadBigInts === "function") stmt.setReadBigInts(true);
|
|
116
|
-
|
|
237
|
+
let adapter: NodeStatementAdapter;
|
|
238
|
+
const release = this.trackStatement();
|
|
239
|
+
adapter = new NodeStatementAdapter(stmt, () => {
|
|
240
|
+
this.statementCache.delete(sql);
|
|
241
|
+
release();
|
|
242
|
+
});
|
|
243
|
+
this.statementCache.set(sql, adapter);
|
|
244
|
+
return adapter;
|
|
117
245
|
}
|
|
118
246
|
|
|
119
247
|
transaction<T>(fn: () => T): () => T {
|
|
@@ -147,7 +275,26 @@ class NodeDatabaseAdapter implements SqliteDatabase {
|
|
|
147
275
|
}
|
|
148
276
|
|
|
149
277
|
close(): void {
|
|
150
|
-
this.
|
|
278
|
+
if (this.closed) return;
|
|
279
|
+
this.closed = true;
|
|
280
|
+
const cachedStatements = Array.from(this.statementCache.values());
|
|
281
|
+
this.statementCache.clear();
|
|
282
|
+
for (const stmt of cachedStatements) {
|
|
283
|
+
try {
|
|
284
|
+
stmt.finalize?.();
|
|
285
|
+
} catch {
|
|
286
|
+
// Ignore finalizer failures during shutdown.
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
while (this.preparedStatementCount > 0) {
|
|
290
|
+
this.preparedStatementCount -= 1;
|
|
291
|
+
decrementPreparedStatement();
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
this.db.close();
|
|
295
|
+
} finally {
|
|
296
|
+
decrementSqliteConnection();
|
|
297
|
+
}
|
|
151
298
|
}
|
|
152
299
|
}
|
|
153
300
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { detectHostRuntime } from "../runtime/host_runtime.ts";
|
|
3
|
+
import { getSqliteAdapterRuntimeCounts } from "./adapter.ts";
|
|
4
|
+
import type { SqliteRuntimeMemoryStats } from "../runtime_memory.ts";
|
|
5
|
+
|
|
6
|
+
const SQLITE_STATUS_MEMORY_USED = 0;
|
|
7
|
+
const SQLITE_STATUS_PAGECACHE_USED = 1;
|
|
8
|
+
const SQLITE_STATUS_PAGECACHE_OVERFLOW = 2;
|
|
9
|
+
const SQLITE_STATUS_MALLOC_COUNT = 9;
|
|
10
|
+
|
|
11
|
+
type SqliteStatus64Fn = (op: number, currentPtr: number, highwaterPtr: number, resetFlag: number) => number;
|
|
12
|
+
type BunFfiModule = {
|
|
13
|
+
dlopen: (
|
|
14
|
+
path: string,
|
|
15
|
+
symbols: {
|
|
16
|
+
sqlite3_status64: {
|
|
17
|
+
args: ["i32", "ptr", "ptr", "i32"];
|
|
18
|
+
returns: "i32";
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
) => { symbols: { sqlite3_status64: SqliteStatus64Fn }; close: () => void };
|
|
22
|
+
ptr: (value: TypedArray) => number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type TypedArray = BigInt64Array | BigUint64Array | Int32Array | Uint32Array;
|
|
26
|
+
|
|
27
|
+
type SqliteLibraryHandle = {
|
|
28
|
+
sqlite3_status64: SqliteStatus64Fn;
|
|
29
|
+
close: () => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type CachedSqliteRuntimeStats = {
|
|
33
|
+
at_ms: number;
|
|
34
|
+
value: SqliteRuntimeMemoryStats;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let ffiModule: BunFfiModule | null | undefined;
|
|
38
|
+
let sqliteLibrary: SqliteLibraryHandle | null | undefined;
|
|
39
|
+
let cachedRuntimeStats: CachedSqliteRuntimeStats | null = null;
|
|
40
|
+
const require = createRequire(import.meta.url);
|
|
41
|
+
|
|
42
|
+
function sqliteLibraryNames(): string[] {
|
|
43
|
+
if (process.platform === "darwin") {
|
|
44
|
+
return ["libsqlite3.dylib", "/usr/lib/libsqlite3.dylib"];
|
|
45
|
+
}
|
|
46
|
+
if (process.platform === "linux") {
|
|
47
|
+
return ["libsqlite3.so.0", "libsqlite3.so", "/usr/lib/x86_64-linux-gnu/libsqlite3.so.0", "/lib/x86_64-linux-gnu/libsqlite3.so.0"];
|
|
48
|
+
}
|
|
49
|
+
return ["libsqlite3.so", "libsqlite3.dylib"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadBunFfi(): BunFfiModule | null {
|
|
53
|
+
if (ffiModule !== undefined) return ffiModule;
|
|
54
|
+
if (detectHostRuntime() !== "bun") {
|
|
55
|
+
ffiModule = null;
|
|
56
|
+
return ffiModule;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
ffiModule = require("bun:ffi") as BunFfiModule;
|
|
60
|
+
} catch {
|
|
61
|
+
ffiModule = null;
|
|
62
|
+
}
|
|
63
|
+
return ffiModule;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function loadSqliteLibrary(): SqliteLibraryHandle | null {
|
|
67
|
+
if (sqliteLibrary !== undefined) return sqliteLibrary;
|
|
68
|
+
const ffi = loadBunFfi();
|
|
69
|
+
if (!ffi) {
|
|
70
|
+
sqliteLibrary = null;
|
|
71
|
+
return sqliteLibrary;
|
|
72
|
+
}
|
|
73
|
+
for (const name of sqliteLibraryNames()) {
|
|
74
|
+
try {
|
|
75
|
+
const lib = ffi.dlopen(name, {
|
|
76
|
+
sqlite3_status64: {
|
|
77
|
+
args: ["i32", "ptr", "ptr", "i32"],
|
|
78
|
+
returns: "i32",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
sqliteLibrary = {
|
|
82
|
+
sqlite3_status64: lib.symbols.sqlite3_status64,
|
|
83
|
+
close: () => lib.close(),
|
|
84
|
+
};
|
|
85
|
+
return sqliteLibrary;
|
|
86
|
+
} catch {
|
|
87
|
+
// Try the next library name.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
sqliteLibrary = null;
|
|
91
|
+
return sqliteLibrary;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readStatus64(
|
|
95
|
+
lib: SqliteLibraryHandle,
|
|
96
|
+
ffi: BunFfiModule,
|
|
97
|
+
op: number
|
|
98
|
+
): { current: number; highwater: number } | null {
|
|
99
|
+
const current = new BigInt64Array(1);
|
|
100
|
+
const highwater = new BigInt64Array(1);
|
|
101
|
+
try {
|
|
102
|
+
const rc = lib.sqlite3_status64(op, ffi.ptr(current), ffi.ptr(highwater), 0);
|
|
103
|
+
if (rc !== 0) return null;
|
|
104
|
+
return {
|
|
105
|
+
current: Number(current[0]),
|
|
106
|
+
highwater: Number(highwater[0]),
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function readSqliteRuntimeMemoryStats(ttlMs = 1_000): SqliteRuntimeMemoryStats {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
if (cachedRuntimeStats && now - cachedRuntimeStats.at_ms < Math.max(0, ttlMs)) {
|
|
116
|
+
return cachedRuntimeStats.value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const adapterCounts = getSqliteAdapterRuntimeCounts();
|
|
120
|
+
const unavailable: SqliteRuntimeMemoryStats = {
|
|
121
|
+
available: false,
|
|
122
|
+
source: "unavailable",
|
|
123
|
+
memory_used_bytes: 0,
|
|
124
|
+
memory_highwater_bytes: 0,
|
|
125
|
+
pagecache_used_slots: 0,
|
|
126
|
+
pagecache_used_slots_highwater: 0,
|
|
127
|
+
pagecache_overflow_bytes: 0,
|
|
128
|
+
pagecache_overflow_highwater_bytes: 0,
|
|
129
|
+
malloc_count: 0,
|
|
130
|
+
malloc_count_highwater: 0,
|
|
131
|
+
open_connections: adapterCounts.open_connections,
|
|
132
|
+
prepared_statements: adapterCounts.prepared_statements,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const ffi = loadBunFfi();
|
|
136
|
+
const lib = loadSqliteLibrary();
|
|
137
|
+
if (!ffi || !lib) {
|
|
138
|
+
cachedRuntimeStats = { at_ms: now, value: unavailable };
|
|
139
|
+
return unavailable;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const memoryUsed = readStatus64(lib, ffi, SQLITE_STATUS_MEMORY_USED);
|
|
143
|
+
const pagecacheUsed = readStatus64(lib, ffi, SQLITE_STATUS_PAGECACHE_USED);
|
|
144
|
+
const pagecacheOverflow = readStatus64(lib, ffi, SQLITE_STATUS_PAGECACHE_OVERFLOW);
|
|
145
|
+
const mallocCount = readStatus64(lib, ffi, SQLITE_STATUS_MALLOC_COUNT);
|
|
146
|
+
|
|
147
|
+
const stats: SqliteRuntimeMemoryStats = {
|
|
148
|
+
available: memoryUsed != null,
|
|
149
|
+
source: memoryUsed != null ? "sqlite3_status64" : "unavailable",
|
|
150
|
+
memory_used_bytes: Math.max(0, Math.floor(memoryUsed?.current ?? 0)),
|
|
151
|
+
memory_highwater_bytes: Math.max(0, Math.floor(memoryUsed?.highwater ?? 0)),
|
|
152
|
+
pagecache_used_slots: Math.max(0, Math.floor(pagecacheUsed?.current ?? 0)),
|
|
153
|
+
pagecache_used_slots_highwater: Math.max(0, Math.floor(pagecacheUsed?.highwater ?? 0)),
|
|
154
|
+
pagecache_overflow_bytes: Math.max(0, Math.floor(pagecacheOverflow?.current ?? 0)),
|
|
155
|
+
pagecache_overflow_highwater_bytes: Math.max(0, Math.floor(pagecacheOverflow?.highwater ?? 0)),
|
|
156
|
+
malloc_count: Math.max(0, Math.floor(mallocCount?.current ?? 0)),
|
|
157
|
+
malloc_count_highwater: Math.max(0, Math.floor(mallocCount?.highwater ?? 0)),
|
|
158
|
+
open_connections: adapterCounts.open_connections,
|
|
159
|
+
prepared_statements: adapterCounts.prepared_statements,
|
|
160
|
+
};
|
|
161
|
+
cachedRuntimeStats = { at_ms: now, value: stats };
|
|
162
|
+
return stats;
|
|
163
|
+
}
|
package/src/stats.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SqliteDurableStore } from "./db/db";
|
|
2
2
|
import type { UploaderController } from "./uploader";
|
|
3
|
-
import type {
|
|
3
|
+
import type { MemoryPressureMonitor } from "./memory";
|
|
4
4
|
import type { BackpressureGate } from "./backpressure";
|
|
5
5
|
import type { IngestQueue } from "./ingest";
|
|
6
6
|
|
|
@@ -110,7 +110,7 @@ export class StatsReporter {
|
|
|
110
110
|
private readonly uploader: UploaderController;
|
|
111
111
|
private readonly ingest?: IngestQueue;
|
|
112
112
|
private readonly backpressure?: BackpressureGate;
|
|
113
|
-
private readonly memory?:
|
|
113
|
+
private readonly memory?: MemoryPressureMonitor;
|
|
114
114
|
|
|
115
115
|
constructor(
|
|
116
116
|
stats: StatsCollector,
|
|
@@ -118,7 +118,7 @@ export class StatsReporter {
|
|
|
118
118
|
uploader: UploaderController,
|
|
119
119
|
ingest?: IngestQueue,
|
|
120
120
|
backpressure?: BackpressureGate,
|
|
121
|
-
memory?:
|
|
121
|
+
memory?: MemoryPressureMonitor,
|
|
122
122
|
intervalMs = 60_000
|
|
123
123
|
) {
|
|
124
124
|
this.stats = stats;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
import type { SqliteDurableStore, SegmentRow } from "./db/db";
|
|
5
|
+
import type { ObjectStore } from "./objectstore/interface";
|
|
6
|
+
import type { SegmentDiskCache } from "./segment/cache";
|
|
7
|
+
import { loadSegmentBytesCached } from "./segment/cached_segment";
|
|
8
|
+
import { iterateBlocksResult } from "./segment/format";
|
|
9
|
+
import { dsError } from "./util/ds_error";
|
|
10
|
+
import { yieldToEventLoop } from "./util/yield";
|
|
11
|
+
|
|
12
|
+
export class StreamSizeReconciler {
|
|
13
|
+
private stopped = false;
|
|
14
|
+
private running: Promise<void> | null = null;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly db: SqliteDurableStore,
|
|
18
|
+
private readonly os: ObjectStore,
|
|
19
|
+
private readonly segmentCache?: SegmentDiskCache,
|
|
20
|
+
private readonly onMetadataChanged?: (stream: string) => void
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
start(): void {
|
|
24
|
+
if (this.running) return;
|
|
25
|
+
this.running = this.run().finally(() => {
|
|
26
|
+
this.running = null;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
stop(): void {
|
|
31
|
+
this.stopped = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async run(): Promise<void> {
|
|
35
|
+
while (!this.stopped) {
|
|
36
|
+
const streams = this.db.listStreamsMissingLogicalSize(8);
|
|
37
|
+
if (streams.length === 0) return;
|
|
38
|
+
for (const stream of streams) {
|
|
39
|
+
if (this.stopped) return;
|
|
40
|
+
try {
|
|
41
|
+
await this.reconcileStream(stream);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
const msg = String((e as any)?.message ?? e);
|
|
44
|
+
if (!this.stopped && !msg.includes("Statement has finalized")) {
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error("stream size reconcile failed", stream, e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private async reconcileStream(stream: string): Promise<void> {
|
|
54
|
+
for (let attempt = 0; attempt < 3 && !this.stopped; attempt++) {
|
|
55
|
+
const before = this.db.getStream(stream);
|
|
56
|
+
if (!before || this.db.isDeleted(before) || before.next_offset <= 0n) return;
|
|
57
|
+
if (before.logical_size_bytes > 0n) return;
|
|
58
|
+
|
|
59
|
+
const segments = this.db.listSegmentsForStream(stream);
|
|
60
|
+
let total = 0n;
|
|
61
|
+
for (const segment of segments) {
|
|
62
|
+
if (this.stopped) return;
|
|
63
|
+
total += await this.sumSegmentPayloadBytes(segment);
|
|
64
|
+
await yieldToEventLoop();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const after = this.db.getStream(stream);
|
|
68
|
+
if (!after || this.db.isDeleted(after)) return;
|
|
69
|
+
if (after.logical_size_bytes > 0n) return;
|
|
70
|
+
|
|
71
|
+
if (segments.length !== this.db.countSegmentsForStream(stream)) continue;
|
|
72
|
+
|
|
73
|
+
const finalTotal = total + after.wal_bytes;
|
|
74
|
+
if (finalTotal > after.logical_size_bytes) {
|
|
75
|
+
this.db.setStreamLogicalSizeBytes(stream, finalTotal);
|
|
76
|
+
this.onMetadataChanged?.(stream);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async sumSegmentPayloadBytes(segment: SegmentRow): Promise<bigint> {
|
|
83
|
+
const bytes = await this.loadSegmentBytes(segment);
|
|
84
|
+
let total = 0n;
|
|
85
|
+
for (const blockRes of iterateBlocksResult(bytes)) {
|
|
86
|
+
if (Result.isError(blockRes)) throw dsError(blockRes.error.message);
|
|
87
|
+
for (const record of blockRes.value.decoded.records) {
|
|
88
|
+
total += BigInt(record.payload.byteLength);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return total;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async loadSegmentBytes(segment: SegmentRow): Promise<Uint8Array> {
|
|
95
|
+
if (existsSync(segment.local_path)) {
|
|
96
|
+
return new Uint8Array(await readFile(segment.local_path));
|
|
97
|
+
}
|
|
98
|
+
return loadSegmentBytesCached(this.os, segment, this.segmentCache);
|
|
99
|
+
}
|
|
100
|
+
}
|