@tungthedev/streams-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CODE_OF_CONDUCT.md +45 -0
  2. package/CONTRIBUTING.md +76 -0
  3. package/LICENSE +201 -0
  4. package/README.md +58 -0
  5. package/SECURITY.md +42 -0
  6. package/bin/prisma-streams-server +2 -0
  7. package/package.json +46 -0
  8. package/src/app.ts +583 -0
  9. package/src/app_core.ts +3144 -0
  10. package/src/app_local.ts +206 -0
  11. package/src/auth.ts +124 -0
  12. package/src/auto_tune.ts +69 -0
  13. package/src/backpressure.ts +66 -0
  14. package/src/bootstrap.ts +613 -0
  15. package/src/compute/demo_entry.ts +415 -0
  16. package/src/compute/demo_site.ts +1242 -0
  17. package/src/compute/entry.ts +19 -0
  18. package/src/compute/package_entry.ts +4 -0
  19. package/src/compute/virtual-modules.d.ts +15 -0
  20. package/src/compute/worker_module_url.ts +9 -0
  21. package/src/concurrency_gate.ts +108 -0
  22. package/src/config.ts +402 -0
  23. package/src/db/bootstrap_store.ts +9 -0
  24. package/src/db/db.ts +2424 -0
  25. package/src/db/schema.ts +925 -0
  26. package/src/db/sqlite_manifest_snapshot.ts +81 -0
  27. package/src/db/sqlite_touch_store.ts +491 -0
  28. package/src/db/sqlite_wal_store.ts +472 -0
  29. package/src/details/full_mode_details.ts +568 -0
  30. package/src/expiry_sweeper.ts +47 -0
  31. package/src/foreground_activity.ts +55 -0
  32. package/src/hist.ts +169 -0
  33. package/src/index/binary_fuse.ts +379 -0
  34. package/src/index/indexer.ts +947 -0
  35. package/src/index/lexicon_file_cache.ts +261 -0
  36. package/src/index/lexicon_format.ts +93 -0
  37. package/src/index/lexicon_indexer.ts +863 -0
  38. package/src/index/run_cache.ts +84 -0
  39. package/src/index/run_format.ts +213 -0
  40. package/src/index/schedule.ts +28 -0
  41. package/src/index/secondary_indexer.ts +901 -0
  42. package/src/index/secondary_schema.ts +105 -0
  43. package/src/ingest.ts +309 -0
  44. package/src/lens/lens.ts +501 -0
  45. package/src/manifest.ts +249 -0
  46. package/src/memory.ts +334 -0
  47. package/src/metrics.ts +147 -0
  48. package/src/metrics_emitter.ts +83 -0
  49. package/src/notifier.ts +180 -0
  50. package/src/objectstore/accounting.ts +151 -0
  51. package/src/objectstore/interface.ts +13 -0
  52. package/src/objectstore/mock_r2.ts +269 -0
  53. package/src/objectstore/null.ts +32 -0
  54. package/src/objectstore/r2.ts +318 -0
  55. package/src/observe/pairing.ts +61 -0
  56. package/src/observe/request.ts +772 -0
  57. package/src/offset.ts +70 -0
  58. package/src/postgres/bootstrap.ts +269 -0
  59. package/src/postgres/companions.ts +197 -0
  60. package/src/postgres/control_restore.ts +109 -0
  61. package/src/postgres/details.ts +189 -0
  62. package/src/postgres/lexicon_index.ts +260 -0
  63. package/src/postgres/routing_index.ts +189 -0
  64. package/src/postgres/rows.ts +132 -0
  65. package/src/postgres/schema.ts +355 -0
  66. package/src/postgres/secondary_index.ts +238 -0
  67. package/src/postgres/segments.ts +900 -0
  68. package/src/postgres/stats.ts +103 -0
  69. package/src/postgres/store.ts +947 -0
  70. package/src/postgres/touch.ts +591 -0
  71. package/src/postgres/types.ts +32 -0
  72. package/src/profiles/evlog/schema.ts +234 -0
  73. package/src/profiles/evlog.ts +473 -0
  74. package/src/profiles/generic.ts +51 -0
  75. package/src/profiles/index.ts +237 -0
  76. package/src/profiles/metrics/block_format.ts +109 -0
  77. package/src/profiles/metrics/normalize.ts +366 -0
  78. package/src/profiles/metrics/schema.ts +319 -0
  79. package/src/profiles/metrics.ts +83 -0
  80. package/src/profiles/otelTraces/normalize.ts +955 -0
  81. package/src/profiles/otelTraces/otlp.ts +1002 -0
  82. package/src/profiles/otelTraces/schema.ts +408 -0
  83. package/src/profiles/otelTraces.ts +390 -0
  84. package/src/profiles/profile.ts +284 -0
  85. package/src/profiles/stateProtocol/change_event_conformance.typecheck.ts +35 -0
  86. package/src/profiles/stateProtocol/changes.ts +24 -0
  87. package/src/profiles/stateProtocol/ingest.ts +115 -0
  88. package/src/profiles/stateProtocol/routes.ts +511 -0
  89. package/src/profiles/stateProtocol/types.ts +6 -0
  90. package/src/profiles/stateProtocol/validation.ts +51 -0
  91. package/src/profiles/stateProtocol.ts +107 -0
  92. package/src/read_filter.ts +468 -0
  93. package/src/reader.ts +2986 -0
  94. package/src/runtime/hash.ts +156 -0
  95. package/src/runtime/hash_vendor/LICENSE.hash-wasm +38 -0
  96. package/src/runtime/hash_vendor/NOTICE.md +8 -0
  97. package/src/runtime/hash_vendor/xxhash3.umd.min.cjs +7 -0
  98. package/src/runtime/hash_vendor/xxhash32.umd.min.cjs +7 -0
  99. package/src/runtime/hash_vendor/xxhash64.umd.min.cjs +7 -0
  100. package/src/runtime/host_runtime.ts +5 -0
  101. package/src/runtime_memory.ts +200 -0
  102. package/src/runtime_memory_sampler.ts +237 -0
  103. package/src/schema/lens_schema.ts +290 -0
  104. package/src/schema/proof.ts +547 -0
  105. package/src/schema/read_json.ts +51 -0
  106. package/src/schema/registry.ts +966 -0
  107. package/src/search/agg_format.ts +638 -0
  108. package/src/search/aggregate.ts +409 -0
  109. package/src/search/binary/codec.ts +162 -0
  110. package/src/search/binary/docset.ts +67 -0
  111. package/src/search/binary/restart_strings.ts +181 -0
  112. package/src/search/binary/varint.ts +34 -0
  113. package/src/search/bitset.ts +19 -0
  114. package/src/search/col_format.ts +382 -0
  115. package/src/search/col_runtime.ts +59 -0
  116. package/src/search/column_encoding.ts +43 -0
  117. package/src/search/companion_file_cache.ts +319 -0
  118. package/src/search/companion_format.ts +327 -0
  119. package/src/search/companion_manager.ts +1305 -0
  120. package/src/search/companion_plan.ts +229 -0
  121. package/src/search/exact_format.ts +281 -0
  122. package/src/search/exact_runtime.ts +55 -0
  123. package/src/search/fts_format.ts +423 -0
  124. package/src/search/fts_runtime.ts +333 -0
  125. package/src/search/query.ts +875 -0
  126. package/src/search/schema.ts +245 -0
  127. package/src/segment/cache.ts +270 -0
  128. package/src/segment/cached_segment.ts +89 -0
  129. package/src/segment/format.ts +403 -0
  130. package/src/segment/segmenter.ts +412 -0
  131. package/src/segment/segmenter_worker.ts +72 -0
  132. package/src/segment/segmenter_workers.ts +130 -0
  133. package/src/server.ts +264 -0
  134. package/src/server_auto_tune.ts +158 -0
  135. package/src/sqlite/adapter.ts +335 -0
  136. package/src/sqlite/runtime_stats.ts +163 -0
  137. package/src/stats.ts +205 -0
  138. package/src/store/append.ts +50 -0
  139. package/src/store/bootstrap_restore_store.ts +71 -0
  140. package/src/store/capabilities.ts +86 -0
  141. package/src/store/full_mode_details_store.ts +71 -0
  142. package/src/store/index_store.ts +104 -0
  143. package/src/store/profile_touch_store.ts +1 -0
  144. package/src/store/rows.ts +144 -0
  145. package/src/store/schema_profile_store.ts +73 -0
  146. package/src/store/schema_publication.ts +6 -0
  147. package/src/store/segment_manifest_store.ts +129 -0
  148. package/src/store/segment_read_store.ts +22 -0
  149. package/src/store/stats_accounting_store.ts +83 -0
  150. package/src/store/touch_store.ts +98 -0
  151. package/src/store/wal_store.ts +21 -0
  152. package/src/stream_size_reconciler.ts +100 -0
  153. package/src/touch/canonical_change.ts +7 -0
  154. package/src/touch/live_keys.ts +158 -0
  155. package/src/touch/live_metrics.ts +841 -0
  156. package/src/touch/live_templates.ts +449 -0
  157. package/src/touch/manager.ts +1292 -0
  158. package/src/touch/process_batch.ts +576 -0
  159. package/src/touch/processor_worker.ts +85 -0
  160. package/src/touch/spec.ts +459 -0
  161. package/src/touch/touch_journal.ts +771 -0
  162. package/src/touch/touch_key_id.ts +20 -0
  163. package/src/touch/worker_pool.ts +191 -0
  164. package/src/touch/worker_protocol.ts +57 -0
  165. package/src/types/proper-lockfile.d.ts +1 -0
  166. package/src/uploader.ts +358 -0
  167. package/src/util/base32_crockford.ts +81 -0
  168. package/src/util/bloom256.ts +67 -0
  169. package/src/util/byte_lru.ts +73 -0
  170. package/src/util/cleanup.ts +22 -0
  171. package/src/util/crc32c.ts +29 -0
  172. package/src/util/ds_error.ts +15 -0
  173. package/src/util/duration.ts +17 -0
  174. package/src/util/endian.ts +53 -0
  175. package/src/util/json_pointer.ts +148 -0
  176. package/src/util/log.ts +25 -0
  177. package/src/util/lru.ts +53 -0
  178. package/src/util/retry.ts +35 -0
  179. package/src/util/siphash.ts +71 -0
  180. package/src/util/stream_paths.ts +50 -0
  181. package/src/util/time.ts +14 -0
  182. package/src/util/yield.ts +3 -0
  183. package/src/util/zstd.ts +24 -0
package/src/app.ts ADDED
@@ -0,0 +1,583 @@
1
+ import type { Config } from "./config";
2
+ import { createAppCore, type App } from "./app_core";
3
+ import type { ObjectStore } from "./objectstore/interface";
4
+ import { AccountingObjectStore } from "./objectstore/accounting";
5
+ import { MockR2Store } from "./objectstore/mock_r2";
6
+ import { StreamReader } from "./reader";
7
+ import { SegmentDiskCache } from "./segment/cache";
8
+ import { Segmenter, type SegmenterHooks } from "./segment/segmenter";
9
+ import { SegmenterWorkerPool } from "./segment/segmenter_workers";
10
+ import { Uploader } from "./uploader";
11
+ import { retry } from "./util/retry";
12
+ import { schemaObjectKey, streamHash16Hex } from "./util/stream_paths";
13
+ import type { StatsCollector } from "./stats";
14
+ import { IndexManager, type StreamIndexLookup } from "./index/indexer";
15
+ import { SecondaryIndexManager } from "./index/secondary_indexer";
16
+ import type { SchemaRegistry } from "./schema/registry";
17
+ import { SearchCompanionManager } from "./search/companion_manager";
18
+ import { LexiconIndexManager } from "./index/lexicon_indexer";
19
+ import { readSqliteRuntimeMemoryStats } from "./sqlite/runtime_stats";
20
+ import { sumRuntimeMemoryValues } from "./runtime_memory";
21
+ import { SqliteDurableStore } from "./db/db";
22
+ import { PostgresDurableStore } from "./postgres/store";
23
+ import { StreamSizeReconciler } from "./stream_size_reconciler";
24
+
25
+ export type { App } from "./app_core";
26
+
27
+ export type CreateAppOptions = {
28
+ stats?: StatsCollector;
29
+ };
30
+
31
+ const INTERNAL_METRICS_STREAM = "__stream_metrics__";
32
+
33
+ function clearInternalMetricsAccelerationState(db: SqliteDurableStore): void {
34
+ db.deleteAccelerationState(INTERNAL_METRICS_STREAM);
35
+ }
36
+
37
+ function reconcileDeletedStreamAccelerationState(db: SqliteDurableStore): void {
38
+ let offset = 0;
39
+ const pageSize = 1000;
40
+ for (;;) {
41
+ const streams = db.listDeletedStreams(pageSize, offset);
42
+ for (const stream of streams) {
43
+ db.deleteAccelerationState(stream);
44
+ }
45
+ if (streams.length < pageSize) break;
46
+ offset += streams.length;
47
+ }
48
+ }
49
+
50
+ class CombinedIndexController implements StreamIndexLookup {
51
+ constructor(
52
+ private readonly routingIndex: IndexManager,
53
+ private readonly secondaryIndex: SecondaryIndexManager,
54
+ private readonly companionIndex: SearchCompanionManager,
55
+ private readonly lexiconIndex: LexiconIndexManager
56
+ ) {}
57
+
58
+ start(): void {
59
+ this.routingIndex.start();
60
+ this.secondaryIndex.start();
61
+ this.companionIndex.start();
62
+ this.lexiconIndex.start();
63
+ }
64
+
65
+ async stop(): Promise<void> {
66
+ await Promise.all([
67
+ this.routingIndex.stop(),
68
+ this.secondaryIndex.stop(),
69
+ this.companionIndex.stop(),
70
+ this.lexiconIndex.stop(),
71
+ ]);
72
+ }
73
+
74
+ enqueue(stream: string): void {
75
+ this.routingIndex.enqueue(stream);
76
+ this.secondaryIndex.enqueue(stream);
77
+ this.companionIndex.enqueue(stream);
78
+ this.lexiconIndex.enqueue(stream);
79
+ }
80
+
81
+ candidateSegmentsForRoutingKey(stream: string, keyBytes: Uint8Array) {
82
+ return this.routingIndex.candidateSegmentsForRoutingKey(stream, keyBytes);
83
+ }
84
+
85
+ candidateSegmentsForSecondaryIndex(stream: string, indexName: string, keyBytes: Uint8Array) {
86
+ return this.secondaryIndex.candidateSegmentsForSecondaryIndex(stream, indexName, keyBytes);
87
+ }
88
+
89
+ getAggSegmentCompanion(stream: string, segmentIndex: number) {
90
+ return this.companionIndex.getAggSegmentCompanion(stream, segmentIndex);
91
+ }
92
+
93
+ getColSegmentCompanion(stream: string, segmentIndex: number) {
94
+ return this.companionIndex.getColSegmentCompanion(stream, segmentIndex);
95
+ }
96
+
97
+ getExactSegmentCompanion(stream: string, segmentIndex: number) {
98
+ return this.companionIndex.getExactSegmentCompanion(stream, segmentIndex);
99
+ }
100
+
101
+ getFtsSegmentCompanion(stream: string, segmentIndex: number) {
102
+ return this.companionIndex.getFtsSegmentCompanion(stream, segmentIndex);
103
+ }
104
+
105
+ getFtsSegmentCompanionWithStats(stream: string, segmentIndex: number) {
106
+ return this.companionIndex.getFtsSegmentCompanionWithStats(stream, segmentIndex);
107
+ }
108
+
109
+ getMetricsBlockSegmentCompanion(stream: string, segmentIndex: number) {
110
+ return this.companionIndex.getMetricsBlockSegmentCompanion(stream, segmentIndex);
111
+ }
112
+
113
+ listRoutingKeysResult(stream: string, after: string | null, limit: number) {
114
+ return this.lexiconIndex.listRoutingKeysResult(stream, after, limit);
115
+ }
116
+
117
+ getLocalStorageUsage(stream: string) {
118
+ return {
119
+ routing_index_cache_bytes: this.routingIndex.getLocalCacheBytes(stream),
120
+ exact_index_cache_bytes: this.secondaryIndex.getLocalCacheBytes(stream),
121
+ companion_cache_bytes: this.companionIndex.getLocalCacheBytes(stream),
122
+ lexicon_index_cache_bytes: this.lexiconIndex.getLocalCacheBytes(stream),
123
+ };
124
+ }
125
+ }
126
+
127
+ export function createApp(cfg: Config, os?: ObjectStore, opts: CreateAppOptions = {}): App<SqliteDurableStore> {
128
+ const db = new SqliteDurableStore(cfg.dbPath, { cacheBytes: cfg.sqliteCacheBytes });
129
+ return createAppCore(cfg, {
130
+ debugStore: db,
131
+ touchStore: db.touch,
132
+ storageStatsStore: db,
133
+ objectStoreAccountingStore: db,
134
+ detailsStore: db,
135
+ detailsStorageBackend: "sqlite",
136
+ lifecycleHooks: {
137
+ beforeRuntimeCreate: () => {
138
+ db.resetSegmentInProgress();
139
+ reconcileDeletedStreamAccelerationState(db);
140
+ },
141
+ afterInternalMetricsProfileEnsured: () => clearInternalMetricsAccelerationState(db),
142
+ getInitialBackpressureBytes: () => db.sumPendingBytes() + db.sumPendingSegmentBytes(),
143
+ },
144
+ store: db,
145
+ stats: opts.stats,
146
+ createRuntime: ({ config, ingest, registry, notifier, stats, backpressure, metrics, memorySampler, memory, asyncIndexGate, foregroundActivity }) => {
147
+ const rawStore = os ?? new MockR2Store();
148
+ const store = new AccountingObjectStore(rawStore, db, metrics);
149
+ const segmenterHooks: SegmenterHooks = {
150
+ onSegmentSealed: (stream, payloadBytes, segmentBytes) => {
151
+ if (stats) stats.recordSegmentSealed(payloadBytes, segmentBytes);
152
+ if (backpressure) backpressure.adjustOnSeal(payloadBytes, segmentBytes);
153
+ notifier.notifyDetailsChanged(stream);
154
+ },
155
+ };
156
+ const diskCache = new SegmentDiskCache(`${config.rootDir}/cache`, config.segmentCacheMaxBytes);
157
+ const uploader = new Uploader(config, db, store, diskCache, stats, backpressure, undefined, memorySampler);
158
+ const routingIndexer = new IndexManager(
159
+ config,
160
+ db,
161
+ store,
162
+ diskCache,
163
+ (stream) => uploader.publishManifest(stream),
164
+ metrics,
165
+ (stream) => notifier.notifyDetailsChanged(stream),
166
+ memorySampler,
167
+ registry,
168
+ asyncIndexGate,
169
+ foregroundActivity
170
+ );
171
+ const secondaryIndexer = new SecondaryIndexManager(
172
+ config,
173
+ db,
174
+ db,
175
+ store,
176
+ registry,
177
+ diskCache,
178
+ (stream) => uploader.publishManifest(stream),
179
+ (stream) => notifier.notifyDetailsChanged(stream),
180
+ memorySampler,
181
+ asyncIndexGate,
182
+ foregroundActivity
183
+ );
184
+ const companionIndexer = new SearchCompanionManager(
185
+ config,
186
+ db,
187
+ store,
188
+ registry,
189
+ diskCache,
190
+ (stream) => uploader.publishManifest(stream),
191
+ (stream) => notifier.notifyDetailsChanged(stream),
192
+ metrics,
193
+ memorySampler,
194
+ asyncIndexGate,
195
+ foregroundActivity
196
+ );
197
+ const lexiconIndexer = new LexiconIndexManager(
198
+ config,
199
+ db,
200
+ store,
201
+ diskCache,
202
+ (stream) => uploader.publishManifest(stream),
203
+ (stream) => notifier.notifyDetailsChanged(stream),
204
+ metrics,
205
+ registry,
206
+ asyncIndexGate,
207
+ foregroundActivity
208
+ );
209
+ const indexer = new CombinedIndexController(
210
+ routingIndexer,
211
+ secondaryIndexer,
212
+ companionIndexer,
213
+ lexiconIndexer
214
+ );
215
+ uploader.setHooks({
216
+ onSegmentsUploaded: (stream) => indexer.enqueue(stream),
217
+ onMetadataChanged: (stream) => notifier.notifyDetailsChanged(stream),
218
+ });
219
+ const reader = new StreamReader(
220
+ config,
221
+ db,
222
+ registry,
223
+ { segmentReads: db, objectStore: store, diskCache, index: indexer },
224
+ memorySampler,
225
+ memory
226
+ );
227
+ const segmenter =
228
+ config.segmenterWorkers > 0
229
+ ? new SegmenterWorkerPool(config, config.segmenterWorkers, {}, segmenterHooks)
230
+ : new Segmenter(config, db, {}, segmenterHooks, memorySampler);
231
+ const sizeReconciler = new StreamSizeReconciler(
232
+ db,
233
+ store,
234
+ diskCache,
235
+ (stream) => notifier.notifyDetailsChanged(stream)
236
+ );
237
+
238
+ const schemaPublication = {
239
+ uploadSchemaRegistry: async (stream: string, reg: SchemaRegistry): Promise<void> => {
240
+ const shash = streamHash16Hex(stream);
241
+ const key = schemaObjectKey(shash);
242
+ const body = new TextEncoder().encode(JSON.stringify(reg));
243
+ await retry(
244
+ () => store.put(key, body, { contentType: "application/json", contentLength: body.byteLength }),
245
+ {
246
+ retries: config.objectStoreRetries,
247
+ baseDelayMs: config.objectStoreBaseDelayMs,
248
+ maxDelayMs: config.objectStoreMaxDelayMs,
249
+ timeoutMs: config.objectStoreTimeoutMs,
250
+ }
251
+ );
252
+ db.setSchemaUploadedSizeBytes(stream, body.byteLength);
253
+ },
254
+ publishProfileSchemaRegistry: async (stream: string, reg: SchemaRegistry): Promise<void> => {
255
+ await schemaPublication.uploadSchemaRegistry(stream, reg);
256
+ await uploader.publishManifest(stream);
257
+ },
258
+ };
259
+
260
+ return {
261
+ reader,
262
+ indexer,
263
+ schemaPublication,
264
+ fullMode: {
265
+ store,
266
+ segmenter,
267
+ uploader,
268
+ segmentDiskCache: diskCache,
269
+ sizeReconciler,
270
+ manifestPublication: {
271
+ publishDeletedStreamManifest: (stream: string) => uploader.publishManifest(stream, { wait: true }),
272
+ },
273
+ getLocalStorageUsage: (stream: string) => ({
274
+ segment_cache_bytes: diskCache.bytesForObjectKeyPrefix(`streams/${streamHash16Hex(stream)}/segments/`),
275
+ ...indexer.getLocalStorageUsage?.(stream),
276
+ }),
277
+ },
278
+ getRuntimeMemorySnapshot: () => {
279
+ const ingestMemory = ingest.getMemoryStats();
280
+ const segmenterMemory = segmenter.getMemoryStats?.() ?? {
281
+ active_builds: 0,
282
+ active_streams: 0,
283
+ active_payload_bytes: 0,
284
+ active_segment_bytes_estimate: 0,
285
+ active_rows: 0,
286
+ };
287
+ const uploaderMemory = uploader.getMemoryStats?.() ?? {
288
+ inflight_segments: 0,
289
+ inflight_segment_bytes: 0,
290
+ manifest_inflight_streams: 0,
291
+ };
292
+ const routingIndexMemory = routingIndexer.getMemoryStats();
293
+ const secondaryIndexMemory = secondaryIndexer.getMemoryStats();
294
+ const companionMemory = companionIndexer.getMemoryStats();
295
+ const lexiconMemory = lexiconIndexer.getMemoryStats();
296
+ const segmentDiskStats = diskCache.stats();
297
+ const mockR2InMemoryBytes = rawStore instanceof MockR2Store ? rawStore.memoryBytes() : 0;
298
+ const mockR2ObjectCount = rawStore instanceof MockR2Store ? rawStore.size() : 0;
299
+ const sqliteRuntime = readSqliteRuntimeMemoryStats();
300
+ const heapEstimates = {
301
+ ingest_queue_payload_bytes: ingestMemory.queuedPayloadBytes,
302
+ routing_run_cache_bytes: routingIndexMemory.runCacheBytes,
303
+ exact_run_cache_bytes: secondaryIndexMemory.runCacheBytes,
304
+ mock_r2_in_memory_bytes: mockR2InMemoryBytes,
305
+ };
306
+ const mappedFiles = {
307
+ segment_cache_mapped_bytes: segmentDiskStats.mappedBytes,
308
+ routing_run_disk_cache_mapped_bytes: routingIndexMemory.runDiskMappedBytes,
309
+ exact_run_disk_cache_mapped_bytes: secondaryIndexMemory.runDiskMappedBytes,
310
+ lexicon_index_mapped_bytes: lexiconMemory.mappedFileBytes,
311
+ companion_bundle_mapped_bytes: companionMemory.mappedFileBytes,
312
+ };
313
+ const diskCaches = {
314
+ segment_disk_cache_bytes: segmentDiskStats.usedBytes,
315
+ routing_run_disk_cache_bytes: routingIndexMemory.runDiskCacheBytes,
316
+ exact_run_disk_cache_bytes: secondaryIndexMemory.runDiskCacheBytes,
317
+ lexicon_disk_cache_bytes: lexiconMemory.fileCacheBytes,
318
+ companion_disk_cache_bytes: companionMemory.fileCacheBytes,
319
+ };
320
+ const pipelineBuffers = {
321
+ segmenter_active_payload_bytes: segmenterMemory.active_payload_bytes,
322
+ segmenter_active_segment_bytes_estimate: segmenterMemory.active_segment_bytes_estimate,
323
+ uploader_inflight_segment_bytes: uploaderMemory.inflight_segment_bytes,
324
+ };
325
+ const sqliteRuntimeBytes = {
326
+ sqlite_memory_used_bytes: sqliteRuntime.memory_used_bytes,
327
+ sqlite_memory_highwater_bytes: sqliteRuntime.memory_highwater_bytes,
328
+ sqlite_pagecache_overflow_bytes: sqliteRuntime.pagecache_overflow_bytes,
329
+ sqlite_pagecache_overflow_highwater_bytes: sqliteRuntime.pagecache_overflow_highwater_bytes,
330
+ };
331
+ const configuredBudgets = {
332
+ sqlite_cache_budget_bytes: config.sqliteCacheBytes,
333
+ worker_sqlite_cache_budget_bytes: config.workerSqliteCacheBytes,
334
+ segment_cache_budget_bytes: config.segmentCacheMaxBytes,
335
+ routing_run_cache_budget_bytes: config.indexRunMemoryCacheBytes,
336
+ routing_run_disk_cache_budget_bytes: config.indexRunCacheMaxBytes,
337
+ exact_run_cache_budget_bytes: config.indexRunMemoryCacheBytes,
338
+ exact_run_disk_cache_budget_bytes: config.indexRunCacheMaxBytes,
339
+ lexicon_disk_cache_budget_bytes: config.lexiconIndexCacheMaxBytes,
340
+ companion_disk_cache_budget_bytes: config.searchCompanionFileCacheMaxBytes,
341
+ };
342
+ const counts = {
343
+ ingest_queue_requests: ingestMemory.queuedRequests,
344
+ segment_disk_cache_entries: segmentDiskStats.entryCount,
345
+ segment_mapped_files: segmentDiskStats.mappedEntryCount,
346
+ segment_pinned_files: segmentDiskStats.pinnedEntryCount,
347
+ routing_run_cache_entries: routingIndexMemory.runCacheEntries,
348
+ routing_run_disk_cache_entries: routingIndexMemory.runDiskCacheEntries,
349
+ routing_run_disk_cache_mapped_entries: routingIndexMemory.runDiskMappedEntries,
350
+ routing_run_disk_cache_pinned_entries: routingIndexMemory.runDiskPinnedEntries,
351
+ exact_run_cache_entries: secondaryIndexMemory.runCacheEntries,
352
+ exact_run_disk_cache_entries: secondaryIndexMemory.runDiskCacheEntries,
353
+ exact_run_disk_cache_mapped_entries: secondaryIndexMemory.runDiskMappedEntries,
354
+ exact_run_disk_cache_pinned_entries: secondaryIndexMemory.runDiskPinnedEntries,
355
+ secondary_index_stream_idle_ticks: secondaryIndexMemory.streamIdleTickEntries,
356
+ lexicon_cached_files: lexiconMemory.fileCacheEntries,
357
+ lexicon_mapped_files: lexiconMemory.mappedFileEntries,
358
+ lexicon_pinned_files: lexiconMemory.pinnedFileEntries,
359
+ companion_cached_files: companionMemory.fileCacheEntries,
360
+ companion_mapped_files: companionMemory.mappedFileEntries,
361
+ companion_pinned_files: companionMemory.pinnedFileEntries,
362
+ mock_r2_object_count: mockR2ObjectCount,
363
+ mock_r2_in_memory_bytes: mockR2InMemoryBytes,
364
+ pending_upload_segments: uploader.countSegmentsWaiting(),
365
+ uploader_inflight_segments: uploaderMemory.inflight_segments,
366
+ uploader_manifest_inflight_streams: uploaderMemory.manifest_inflight_streams,
367
+ segmenter_active_builds: segmenterMemory.active_builds,
368
+ segmenter_active_streams: segmenterMemory.active_streams,
369
+ segmenter_active_rows: segmenterMemory.active_rows,
370
+ sqlite_pagecache_used_slots: sqliteRuntime.pagecache_used_slots,
371
+ sqlite_pagecache_used_slots_highwater: sqliteRuntime.pagecache_used_slots_highwater,
372
+ sqlite_malloc_count: sqliteRuntime.malloc_count,
373
+ sqlite_malloc_count_highwater: sqliteRuntime.malloc_count_highwater,
374
+ sqlite_open_connections: sqliteRuntime.open_connections,
375
+ sqlite_prepared_statements: sqliteRuntime.prepared_statements,
376
+ };
377
+ return {
378
+ subsystems: {
379
+ heap_estimates: heapEstimates,
380
+ mapped_files: mappedFiles,
381
+ disk_caches: diskCaches,
382
+ configured_budgets: configuredBudgets,
383
+ pipeline_buffers: pipelineBuffers,
384
+ sqlite_runtime: sqliteRuntimeBytes,
385
+ counts,
386
+ },
387
+ totals: {
388
+ heap_estimate_bytes: sumRuntimeMemoryValues(heapEstimates),
389
+ mapped_file_bytes: sumRuntimeMemoryValues(mappedFiles),
390
+ disk_cache_bytes: sumRuntimeMemoryValues(diskCaches),
391
+ configured_budget_bytes: sumRuntimeMemoryValues(configuredBudgets),
392
+ pipeline_buffer_bytes: sumRuntimeMemoryValues(pipelineBuffers),
393
+ sqlite_runtime_bytes: sumRuntimeMemoryValues(sqliteRuntimeBytes),
394
+ },
395
+ };
396
+ },
397
+ start: () => {
398
+ segmenter.start();
399
+ uploader.start();
400
+ indexer.start();
401
+ setTimeout(() => {
402
+ try {
403
+ let offset = 0;
404
+ const pageSize = 1000;
405
+ for (;;) {
406
+ const streams = db.listStreams(pageSize, offset);
407
+ for (const row of streams) indexer.enqueue(row.stream);
408
+ if (streams.length < pageSize) break;
409
+ offset += streams.length;
410
+ }
411
+ } catch {
412
+ // App may have been closed before the startup catch-up kickoff ran.
413
+ }
414
+ }, 0);
415
+ },
416
+ };
417
+ },
418
+ });
419
+ }
420
+
421
+ export function createPostgresApp(cfg: Config, store: PostgresDurableStore, opts: CreateAppOptions = {}): App {
422
+ return createAppCore(cfg, {
423
+ store,
424
+ stats: opts.stats,
425
+ createRuntime: ({ config, registry, memorySampler, memory }) => ({
426
+ reader: new StreamReader(config, store, registry, undefined, memorySampler, memory),
427
+ start: (): void => {},
428
+ }),
429
+ });
430
+ }
431
+
432
+ export function createPostgresFullApp(cfg: Config, store: PostgresDurableStore, os: ObjectStore, opts: CreateAppOptions = {}): App {
433
+ const statsAccounting = store.fullModeStatsAccounting();
434
+ return createAppCore(cfg, {
435
+ store,
436
+ touchStore: store.fullModeTouch(),
437
+ touchUseWorkers: false,
438
+ storageStatsStore: statsAccounting,
439
+ objectStoreAccountingStore: statsAccounting,
440
+ detailsStore: store.fullModeDetails(),
441
+ detailsStorageBackend: "postgres",
442
+ stats: opts.stats,
443
+ createRuntime: ({ config, registry, notifier, stats, backpressure, metrics, memorySampler, memory, asyncIndexGate, foregroundActivity }) => {
444
+ const segmentStore = store.fullModeSegments();
445
+ const indexStores = store.fullModeIndexStores();
446
+ const objectStore = new AccountingObjectStore(os, statsAccounting, metrics);
447
+ const diskCache = new SegmentDiskCache(`${config.rootDir}/cache`, config.segmentCacheMaxBytes);
448
+ const segmenterHooks: SegmenterHooks = {
449
+ onSegmentSealed: (stream, payloadBytes, segmentBytes) => {
450
+ if (stats) stats.recordSegmentSealed(payloadBytes, segmentBytes);
451
+ if (backpressure) backpressure.adjustOnSeal(payloadBytes, segmentBytes);
452
+ notifier.notifyDetailsChanged(stream);
453
+ },
454
+ };
455
+ const uploader = new Uploader(config, segmentStore, objectStore, diskCache, stats, backpressure, undefined, memorySampler);
456
+ const routingIndexer = new IndexManager(
457
+ config,
458
+ indexStores.routing,
459
+ objectStore,
460
+ diskCache,
461
+ (stream) => uploader.publishManifest(stream),
462
+ metrics,
463
+ (stream) => notifier.notifyDetailsChanged(stream),
464
+ memorySampler,
465
+ registry,
466
+ asyncIndexGate,
467
+ foregroundActivity
468
+ );
469
+ const secondaryIndexer = new SecondaryIndexManager(
470
+ config,
471
+ indexStores.secondary,
472
+ indexStores.companions,
473
+ objectStore,
474
+ registry,
475
+ diskCache,
476
+ (stream) => uploader.publishManifest(stream),
477
+ (stream) => notifier.notifyDetailsChanged(stream),
478
+ memorySampler,
479
+ asyncIndexGate,
480
+ foregroundActivity
481
+ );
482
+ const companionIndexer = new SearchCompanionManager(
483
+ config,
484
+ indexStores.companions,
485
+ objectStore,
486
+ registry,
487
+ diskCache,
488
+ (stream) => uploader.publishManifest(stream),
489
+ (stream) => notifier.notifyDetailsChanged(stream),
490
+ metrics,
491
+ memorySampler,
492
+ asyncIndexGate,
493
+ foregroundActivity
494
+ );
495
+ const lexiconIndexer = new LexiconIndexManager(
496
+ config,
497
+ indexStores.lexicon,
498
+ objectStore,
499
+ diskCache,
500
+ (stream) => uploader.publishManifest(stream),
501
+ (stream) => notifier.notifyDetailsChanged(stream),
502
+ metrics,
503
+ registry,
504
+ asyncIndexGate,
505
+ foregroundActivity
506
+ );
507
+ const indexer = new CombinedIndexController(routingIndexer, secondaryIndexer, companionIndexer, lexiconIndexer);
508
+ uploader.setHooks({
509
+ onSegmentsUploaded: (stream) => indexer.enqueue(stream),
510
+ onMetadataChanged: (stream) => notifier.notifyDetailsChanged(stream),
511
+ });
512
+ const segmenter = new Segmenter(config, segmentStore, {}, segmenterHooks, memorySampler);
513
+ const reader = new StreamReader(
514
+ config,
515
+ store,
516
+ registry,
517
+ { segmentReads: segmentStore, objectStore, diskCache, index: indexer },
518
+ memorySampler,
519
+ memory
520
+ );
521
+ const schemaPublication = {
522
+ uploadSchemaRegistry: async (stream: string, reg: SchemaRegistry): Promise<void> => {
523
+ const shash = streamHash16Hex(stream);
524
+ const key = schemaObjectKey(shash);
525
+ const body = new TextEncoder().encode(JSON.stringify(reg));
526
+ await retry(
527
+ () => objectStore.put(key, body, { contentType: "application/json", contentLength: body.byteLength }),
528
+ {
529
+ retries: config.objectStoreRetries,
530
+ baseDelayMs: config.objectStoreBaseDelayMs,
531
+ maxDelayMs: config.objectStoreMaxDelayMs,
532
+ timeoutMs: config.objectStoreTimeoutMs,
533
+ }
534
+ );
535
+ await segmentStore.setSchemaUploadedSizeBytes(stream, body.byteLength);
536
+ },
537
+ publishProfileSchemaRegistry: async (stream: string, reg: SchemaRegistry): Promise<void> => {
538
+ await schemaPublication.uploadSchemaRegistry(stream, reg);
539
+ await uploader.publishManifest(stream);
540
+ },
541
+ };
542
+ return {
543
+ reader,
544
+ indexer,
545
+ schemaPublication,
546
+ fullMode: {
547
+ store: objectStore,
548
+ segmenter,
549
+ uploader,
550
+ segmentDiskCache: diskCache,
551
+ manifestPublication: {
552
+ publishDeletedStreamManifest: (stream: string) => uploader.publishManifest(stream, { wait: true }),
553
+ },
554
+ getLocalStorageUsage: (stream: string) => ({
555
+ segment_cache_bytes: diskCache.bytesForObjectKeyPrefix(`streams/${streamHash16Hex(stream)}/segments/`),
556
+ ...indexer.getLocalStorageUsage?.(stream),
557
+ }),
558
+ },
559
+ start: () => {
560
+ segmenter.start();
561
+ uploader.start();
562
+ indexer.start();
563
+ setTimeout(() => {
564
+ void (async () => {
565
+ try {
566
+ let offset = 0;
567
+ const pageSize = 1000;
568
+ for (;;) {
569
+ const streams = await store.listStreams(pageSize, offset);
570
+ for (const row of streams) indexer.enqueue(row.stream);
571
+ if (streams.length < pageSize) break;
572
+ offset += streams.length;
573
+ }
574
+ } catch {
575
+ // App may have been closed before the startup catch-up kickoff ran.
576
+ }
577
+ })();
578
+ }, 0);
579
+ },
580
+ };
581
+ },
582
+ });
583
+ }