@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.
- package/CODE_OF_CONDUCT.md +45 -0
- package/CONTRIBUTING.md +76 -0
- package/LICENSE +201 -0
- package/README.md +58 -0
- package/SECURITY.md +42 -0
- package/bin/prisma-streams-server +2 -0
- package/package.json +46 -0
- package/src/app.ts +583 -0
- package/src/app_core.ts +3144 -0
- package/src/app_local.ts +206 -0
- package/src/auth.ts +124 -0
- package/src/auto_tune.ts +69 -0
- package/src/backpressure.ts +66 -0
- package/src/bootstrap.ts +613 -0
- package/src/compute/demo_entry.ts +415 -0
- package/src/compute/demo_site.ts +1242 -0
- package/src/compute/entry.ts +19 -0
- package/src/compute/package_entry.ts +4 -0
- package/src/compute/virtual-modules.d.ts +15 -0
- package/src/compute/worker_module_url.ts +9 -0
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +402 -0
- package/src/db/bootstrap_store.ts +9 -0
- package/src/db/db.ts +2424 -0
- package/src/db/schema.ts +925 -0
- package/src/db/sqlite_manifest_snapshot.ts +81 -0
- package/src/db/sqlite_touch_store.ts +491 -0
- package/src/db/sqlite_wal_store.ts +472 -0
- package/src/details/full_mode_details.ts +568 -0
- package/src/expiry_sweeper.ts +47 -0
- package/src/foreground_activity.ts +55 -0
- package/src/hist.ts +169 -0
- package/src/index/binary_fuse.ts +379 -0
- package/src/index/indexer.ts +947 -0
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +863 -0
- package/src/index/run_cache.ts +84 -0
- package/src/index/run_format.ts +213 -0
- package/src/index/schedule.ts +28 -0
- package/src/index/secondary_indexer.ts +901 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +309 -0
- package/src/lens/lens.ts +501 -0
- package/src/manifest.ts +249 -0
- package/src/memory.ts +334 -0
- package/src/metrics.ts +147 -0
- package/src/metrics_emitter.ts +83 -0
- package/src/notifier.ts +180 -0
- package/src/objectstore/accounting.ts +151 -0
- package/src/objectstore/interface.ts +13 -0
- package/src/objectstore/mock_r2.ts +269 -0
- package/src/objectstore/null.ts +32 -0
- package/src/objectstore/r2.ts +318 -0
- package/src/observe/pairing.ts +61 -0
- package/src/observe/request.ts +772 -0
- package/src/offset.ts +70 -0
- package/src/postgres/bootstrap.ts +269 -0
- package/src/postgres/companions.ts +197 -0
- package/src/postgres/control_restore.ts +109 -0
- package/src/postgres/details.ts +189 -0
- package/src/postgres/lexicon_index.ts +260 -0
- package/src/postgres/routing_index.ts +189 -0
- package/src/postgres/rows.ts +132 -0
- package/src/postgres/schema.ts +355 -0
- package/src/postgres/secondary_index.ts +238 -0
- package/src/postgres/segments.ts +900 -0
- package/src/postgres/stats.ts +103 -0
- package/src/postgres/store.ts +947 -0
- package/src/postgres/touch.ts +591 -0
- package/src/postgres/types.ts +32 -0
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +473 -0
- package/src/profiles/generic.ts +51 -0
- package/src/profiles/index.ts +237 -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 +83 -0
- package/src/profiles/otelTraces/normalize.ts +955 -0
- package/src/profiles/otelTraces/otlp.ts +1002 -0
- package/src/profiles/otelTraces/schema.ts +408 -0
- package/src/profiles/otelTraces.ts +390 -0
- package/src/profiles/profile.ts +284 -0
- package/src/profiles/stateProtocol/change_event_conformance.typecheck.ts +35 -0
- package/src/profiles/stateProtocol/changes.ts +24 -0
- package/src/profiles/stateProtocol/ingest.ts +115 -0
- package/src/profiles/stateProtocol/routes.ts +511 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +107 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2986 -0
- package/src/runtime/hash.ts +156 -0
- package/src/runtime/hash_vendor/LICENSE.hash-wasm +38 -0
- package/src/runtime/hash_vendor/NOTICE.md +8 -0
- package/src/runtime/hash_vendor/xxhash3.umd.min.cjs +7 -0
- package/src/runtime/hash_vendor/xxhash32.umd.min.cjs +7 -0
- package/src/runtime/hash_vendor/xxhash64.umd.min.cjs +7 -0
- package/src/runtime/host_runtime.ts +5 -0
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +237 -0
- package/src/schema/lens_schema.ts +290 -0
- package/src/schema/proof.ts +547 -0
- package/src/schema/read_json.ts +51 -0
- package/src/schema/registry.ts +966 -0
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +409 -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 +327 -0
- package/src/search/companion_manager.ts +1305 -0
- package/src/search/companion_plan.ts +229 -0
- package/src/search/exact_format.ts +281 -0
- package/src/search/exact_runtime.ts +55 -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 +270 -0
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +403 -0
- package/src/segment/segmenter.ts +412 -0
- package/src/segment/segmenter_worker.ts +72 -0
- package/src/segment/segmenter_workers.ts +130 -0
- package/src/server.ts +264 -0
- package/src/server_auto_tune.ts +158 -0
- package/src/sqlite/adapter.ts +335 -0
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +205 -0
- package/src/store/append.ts +50 -0
- package/src/store/bootstrap_restore_store.ts +71 -0
- package/src/store/capabilities.ts +86 -0
- package/src/store/full_mode_details_store.ts +71 -0
- package/src/store/index_store.ts +104 -0
- package/src/store/profile_touch_store.ts +1 -0
- package/src/store/rows.ts +144 -0
- package/src/store/schema_profile_store.ts +73 -0
- package/src/store/schema_publication.ts +6 -0
- package/src/store/segment_manifest_store.ts +129 -0
- package/src/store/segment_read_store.ts +22 -0
- package/src/store/stats_accounting_store.ts +83 -0
- package/src/store/touch_store.ts +98 -0
- package/src/store/wal_store.ts +21 -0
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_keys.ts +158 -0
- package/src/touch/live_metrics.ts +841 -0
- package/src/touch/live_templates.ts +449 -0
- package/src/touch/manager.ts +1292 -0
- package/src/touch/process_batch.ts +576 -0
- package/src/touch/processor_worker.ts +85 -0
- package/src/touch/spec.ts +459 -0
- package/src/touch/touch_journal.ts +771 -0
- package/src/touch/touch_key_id.ts +20 -0
- package/src/touch/worker_pool.ts +191 -0
- package/src/touch/worker_protocol.ts +57 -0
- package/src/types/proper-lockfile.d.ts +1 -0
- package/src/uploader.ts +358 -0
- package/src/util/base32_crockford.ts +81 -0
- package/src/util/bloom256.ts +67 -0
- package/src/util/byte_lru.ts +73 -0
- package/src/util/cleanup.ts +22 -0
- package/src/util/crc32c.ts +29 -0
- package/src/util/ds_error.ts +15 -0
- package/src/util/duration.ts +17 -0
- package/src/util/endian.ts +53 -0
- package/src/util/json_pointer.ts +148 -0
- package/src/util/log.ts +25 -0
- package/src/util/lru.ts +53 -0
- package/src/util/retry.ts +35 -0
- package/src/util/siphash.ts +71 -0
- package/src/util/stream_paths.ts +50 -0
- package/src/util/time.ts +14 -0
- package/src/util/yield.ts +3 -0
- package/src/util/zstd.ts +24 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { loadConfig } from "./config";
|
|
2
|
+
import { createApp, createPostgresApp, createPostgresFullApp, type App } from "./app";
|
|
3
|
+
import { StatsCollector, StatsReporter } from "./stats";
|
|
4
|
+
import { LatencyHistogramCollector, HistogramReporter } from "./hist";
|
|
5
|
+
import { MockR2Store } from "./objectstore/mock_r2";
|
|
6
|
+
import type { ObjectStore } from "./objectstore/interface";
|
|
7
|
+
import { R2ObjectStore } from "./objectstore/r2";
|
|
8
|
+
import { bootstrapFromR2 } from "./bootstrap";
|
|
9
|
+
import { bootstrapPostgresFromR2 } from "./postgres/bootstrap";
|
|
10
|
+
import { PostgresDurableStore } from "./postgres/store";
|
|
11
|
+
import { initConsoleLogging } from "./util/log";
|
|
12
|
+
import { applyAutoTune, AutoTuneApplyError, parseAutoTuneArg } from "./server_auto_tune";
|
|
13
|
+
import { parseAuthConfigResult, withAuth } from "./auth";
|
|
14
|
+
import { Result } from "better-result";
|
|
15
|
+
|
|
16
|
+
initConsoleLogging();
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const authConfigResult = parseAuthConfigResult(args);
|
|
20
|
+
if (Result.isError(authConfigResult)) {
|
|
21
|
+
console.error(authConfigResult.error.message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const authConfig = authConfigResult.value;
|
|
25
|
+
|
|
26
|
+
const autoTune = parseAutoTuneArg(args);
|
|
27
|
+
if (autoTune.enabled) {
|
|
28
|
+
try {
|
|
29
|
+
applyAutoTune(autoTune.valueMb);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error instanceof AutoTuneApplyError) {
|
|
32
|
+
console.error(error.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const cfg = loadConfig();
|
|
40
|
+
|
|
41
|
+
const statsEnabled = args.includes("--stats");
|
|
42
|
+
const histEnabled = args.includes("--hist");
|
|
43
|
+
const bootstrapEnabled = args.includes("--bootstrap-from-r2");
|
|
44
|
+
const bpBudgetRaw = process.env.DS_BACKPRESSURE_BUDGET_MS;
|
|
45
|
+
const bpBudgetMs = bpBudgetRaw ? Number(bpBudgetRaw) : cfg.ingestFlushIntervalMs + 1;
|
|
46
|
+
if (bpBudgetRaw && !Number.isFinite(bpBudgetMs)) {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.error(`invalid DS_BACKPRESSURE_BUDGET_MS: ${bpBudgetRaw}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const stats = statsEnabled ? new StatsCollector({ backpressureBudgetMs: bpBudgetMs }) : undefined;
|
|
52
|
+
const hist = histEnabled ? new LatencyHistogramCollector() : undefined;
|
|
53
|
+
|
|
54
|
+
const storeIdx = args.indexOf("--object-store");
|
|
55
|
+
const storeChoice = storeIdx >= 0 ? args[storeIdx + 1] : null;
|
|
56
|
+
|
|
57
|
+
function requireObjectStoreChoice(): "r2" | "local" {
|
|
58
|
+
if (!storeChoice || (storeChoice !== "r2" && storeChoice !== "local")) {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error("missing or invalid --object-store (expected: r2 | local)");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
return storeChoice;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createConfiguredObjectStore(choice: "r2" | "local"): ObjectStore {
|
|
67
|
+
if (choice === "local") {
|
|
68
|
+
const memBytesRaw = process.env.DS_MOCK_R2_MAX_INMEM_BYTES;
|
|
69
|
+
const memMbRaw = process.env.DS_MOCK_R2_MAX_INMEM_MB;
|
|
70
|
+
const putDelayRaw = process.env.DS_MOCK_R2_PUT_DELAY_MS;
|
|
71
|
+
const getDelayRaw = process.env.DS_MOCK_R2_GET_DELAY_MS;
|
|
72
|
+
const headDelayRaw = process.env.DS_MOCK_R2_HEAD_DELAY_MS;
|
|
73
|
+
const listDelayRaw = process.env.DS_MOCK_R2_LIST_DELAY_MS;
|
|
74
|
+
const memBytes = memBytesRaw ? Number(memBytesRaw) : memMbRaw ? Number(memMbRaw) * 1024 * 1024 : null;
|
|
75
|
+
const putDelayMs = putDelayRaw ? Number(putDelayRaw) : 0;
|
|
76
|
+
const getDelayMs = getDelayRaw ? Number(getDelayRaw) : 0;
|
|
77
|
+
const headDelayMs = headDelayRaw ? Number(headDelayRaw) : 0;
|
|
78
|
+
const listDelayMs = listDelayRaw ? Number(listDelayRaw) : 0;
|
|
79
|
+
if (memBytesRaw && !Number.isFinite(memBytes)) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error(`invalid DS_MOCK_R2_MAX_INMEM_BYTES: ${memBytesRaw}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (memMbRaw && !Number.isFinite(Number(memMbRaw))) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.error(`invalid DS_MOCK_R2_MAX_INMEM_MB: ${memMbRaw}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
for (const [name, value] of [
|
|
90
|
+
["DS_MOCK_R2_PUT_DELAY_MS", putDelayMs],
|
|
91
|
+
["DS_MOCK_R2_GET_DELAY_MS", getDelayMs],
|
|
92
|
+
["DS_MOCK_R2_HEAD_DELAY_MS", headDelayMs],
|
|
93
|
+
["DS_MOCK_R2_LIST_DELAY_MS", listDelayMs],
|
|
94
|
+
] as const) {
|
|
95
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.error(`invalid ${name}: ${process.env[name]}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return new MockR2Store({
|
|
102
|
+
maxInMemoryBytes: memBytes ?? undefined,
|
|
103
|
+
spillDir: process.env.DS_MOCK_R2_SPILL_DIR,
|
|
104
|
+
faults: {
|
|
105
|
+
putDelayMs,
|
|
106
|
+
getDelayMs,
|
|
107
|
+
headDelayMs,
|
|
108
|
+
listDelayMs,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const bucket = process.env.DURABLE_STREAMS_R2_BUCKET;
|
|
114
|
+
const accountId = process.env.DURABLE_STREAMS_R2_ACCOUNT_ID;
|
|
115
|
+
const accessKeyId = process.env.DURABLE_STREAMS_R2_ACCESS_KEY_ID;
|
|
116
|
+
const secretAccessKey = process.env.DURABLE_STREAMS_R2_SECRET_ACCESS_KEY;
|
|
117
|
+
const endpoint = process.env.DURABLE_STREAMS_R2_ENDPOINT;
|
|
118
|
+
const region = process.env.DURABLE_STREAMS_R2_REGION;
|
|
119
|
+
if (!bucket || !accountId || !accessKeyId || !secretAccessKey) {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.error("missing R2 env vars: DURABLE_STREAMS_R2_BUCKET, DURABLE_STREAMS_R2_ACCOUNT_ID, DURABLE_STREAMS_R2_ACCESS_KEY_ID, DURABLE_STREAMS_R2_SECRET_ACCESS_KEY");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
return new R2ObjectStore({
|
|
125
|
+
bucket,
|
|
126
|
+
accountId,
|
|
127
|
+
accessKeyId,
|
|
128
|
+
secretAccessKey,
|
|
129
|
+
endpoint,
|
|
130
|
+
region,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let app: App;
|
|
135
|
+
if (cfg.storage === "postgres") {
|
|
136
|
+
if (cfg.postgresUrl == null) {
|
|
137
|
+
// loadConfig validates this; keep the local guard for future call-site changes.
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.error("DS_POSTGRES_URL is required when DS_STORAGE=postgres");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
if (cfg.postgresMode === "wal") {
|
|
143
|
+
if (storeIdx >= 0) {
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.error("postgres WAL mode does not support --object-store");
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
if (bootstrapEnabled) {
|
|
149
|
+
// eslint-disable-next-line no-console
|
|
150
|
+
console.error("postgres WAL mode does not support --bootstrap-from-r2");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const postgresStore = await PostgresDurableStore.connect(cfg.postgresUrl);
|
|
154
|
+
app = createPostgresApp(cfg, postgresStore, { stats });
|
|
155
|
+
} else {
|
|
156
|
+
const objectStore = createConfiguredObjectStore(requireObjectStoreChoice());
|
|
157
|
+
if (bootstrapEnabled) {
|
|
158
|
+
await bootstrapPostgresFromR2(cfg, objectStore, cfg.postgresUrl, { clearLocal: true });
|
|
159
|
+
}
|
|
160
|
+
const postgresStore = await PostgresDurableStore.connectFull(cfg.postgresUrl);
|
|
161
|
+
app = createPostgresFullApp(cfg, postgresStore, objectStore, { stats });
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
const store = createConfiguredObjectStore(requireObjectStoreChoice());
|
|
165
|
+
|
|
166
|
+
if (bootstrapEnabled) {
|
|
167
|
+
await bootstrapFromR2(cfg, store, { clearLocal: true });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
app = createApp(cfg, store, { stats });
|
|
171
|
+
}
|
|
172
|
+
const statsIntervalMs = process.env.DS_STATS_INTERVAL_MS ? Number(process.env.DS_STATS_INTERVAL_MS) : 60_000;
|
|
173
|
+
if (process.env.DS_STATS_INTERVAL_MS && !Number.isFinite(statsIntervalMs)) {
|
|
174
|
+
// eslint-disable-next-line no-console
|
|
175
|
+
console.error(`invalid DS_STATS_INTERVAL_MS: ${process.env.DS_STATS_INTERVAL_MS}`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const statsReporter =
|
|
179
|
+
statsEnabled && stats && app.deps.storageStats && app.deps.uploader
|
|
180
|
+
? new StatsReporter(stats, app.deps.storageStats, app.deps.uploader, app.deps.ingest, app.deps.backpressure, app.deps.memory, statsIntervalMs)
|
|
181
|
+
: null;
|
|
182
|
+
const histReporter = histEnabled && hist ? new HistogramReporter(hist, statsIntervalMs) : null;
|
|
183
|
+
|
|
184
|
+
const fetchWithHist = hist
|
|
185
|
+
? async (req: Request): Promise<Response> => {
|
|
186
|
+
const start = Date.now();
|
|
187
|
+
const resp = await app.fetch(req);
|
|
188
|
+
const url = req.url;
|
|
189
|
+
let path: string | null = null;
|
|
190
|
+
if (url.startsWith("/")) {
|
|
191
|
+
path = url;
|
|
192
|
+
} else {
|
|
193
|
+
const schemeIdx = url.indexOf("://");
|
|
194
|
+
if (schemeIdx !== -1) {
|
|
195
|
+
const pathIdx = url.indexOf("/", schemeIdx + 3);
|
|
196
|
+
path = pathIdx === -1 ? "/" : url.slice(pathIdx);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (path) {
|
|
200
|
+
const isStream = path.startsWith("/v1/stream/") || path.startsWith("/v1/streams");
|
|
201
|
+
if (isStream) {
|
|
202
|
+
const ms = Date.now() - start;
|
|
203
|
+
const method = req.method.toUpperCase();
|
|
204
|
+
if (method === "GET" || method === "HEAD") hist.recordRead(ms);
|
|
205
|
+
else if (method === "POST" || method === "PUT" || method === "DELETE") hist.recordWrite(ms);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return resp;
|
|
209
|
+
}
|
|
210
|
+
: app.fetch;
|
|
211
|
+
const fetchWithAuth = withAuth(authConfig, fetchWithHist);
|
|
212
|
+
|
|
213
|
+
const server = Bun.serve({
|
|
214
|
+
hostname: cfg.host,
|
|
215
|
+
port: cfg.port,
|
|
216
|
+
// Default Bun idleTimeout is 10s, which is too low for long-poll endpoints like /touch/wait.
|
|
217
|
+
// Bun expects seconds here.
|
|
218
|
+
idleTimeout: (() => {
|
|
219
|
+
const raw = process.env.DS_HTTP_IDLE_TIMEOUT_SECONDS;
|
|
220
|
+
if (raw == null || raw.trim() === "") return 180;
|
|
221
|
+
const n = Number(raw);
|
|
222
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.error(`invalid DS_HTTP_IDLE_TIMEOUT_SECONDS: ${raw}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
return n;
|
|
228
|
+
})(),
|
|
229
|
+
fetch: fetchWithAuth,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
statsReporter?.start();
|
|
233
|
+
histReporter?.start();
|
|
234
|
+
|
|
235
|
+
let shuttingDown = false;
|
|
236
|
+
const shutdown = async (signal: NodeJS.Signals) => {
|
|
237
|
+
if (shuttingDown) return;
|
|
238
|
+
shuttingDown = true;
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.log(`received ${signal}, shutting down prisma-streams server`);
|
|
241
|
+
statsReporter?.stop();
|
|
242
|
+
histReporter?.stop();
|
|
243
|
+
try {
|
|
244
|
+
server.stop(true);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
// eslint-disable-next-line no-console
|
|
247
|
+
console.error("failed to stop HTTP server cleanly", err);
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
await app.close();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
// eslint-disable-next-line no-console
|
|
253
|
+
console.error("failed to close application cleanly", err);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const listenTarget = cfg.host.includes(":") ? `[${cfg.host}]:${server.port}` : `${cfg.host}:${server.port}`;
|
|
259
|
+
|
|
260
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
261
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
262
|
+
|
|
263
|
+
// eslint-disable-next-line no-console
|
|
264
|
+
console.log(`prisma-streams server listening on ${listenTarget}`);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { AUTO_TUNE_PRESETS, memoryLimitForPreset, tuneForPreset, type AutoTuneConfig } from "./auto_tune";
|
|
2
|
+
|
|
3
|
+
export class AutoTuneApplyError extends Error {
|
|
4
|
+
constructor(message: string) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "AutoTuneApplyError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseAutoTuneArg(args: string[]): { enabled: boolean; valueMb: number | null } {
|
|
11
|
+
let enabled = false;
|
|
12
|
+
let valueMb: number | null = null;
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
const arg = args[i];
|
|
15
|
+
if (arg === "--auto-tune") {
|
|
16
|
+
enabled = true;
|
|
17
|
+
const next = args[i + 1];
|
|
18
|
+
if (next && !next.startsWith("--") && /^[0-9]+$/.test(next)) {
|
|
19
|
+
valueMb = Number(next);
|
|
20
|
+
}
|
|
21
|
+
} else if (arg.startsWith("--auto-tune=")) {
|
|
22
|
+
enabled = true;
|
|
23
|
+
const raw = arg.split("=", 2)[1] ?? "";
|
|
24
|
+
if (raw.trim() !== "") valueMb = Number(raw);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { enabled, valueMb };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatPresetList<T>(presets: number[], selected: number, map: (preset: number) => T, fmt: (val: T) => string): string {
|
|
31
|
+
return presets
|
|
32
|
+
.map((preset) => {
|
|
33
|
+
const value = fmt(map(preset));
|
|
34
|
+
return preset === selected ? `[${value}]` : value;
|
|
35
|
+
})
|
|
36
|
+
.join(", ");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function applyAutoTune(
|
|
40
|
+
overrideMb: number | null,
|
|
41
|
+
opts: {
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
log?: (message: string) => void;
|
|
44
|
+
} = {}
|
|
45
|
+
): void {
|
|
46
|
+
const env = opts.env ?? process.env;
|
|
47
|
+
const log = opts.log ?? console.log;
|
|
48
|
+
const envMemRaw = env.DS_MEMORY_LIMIT_MB;
|
|
49
|
+
if (overrideMb != null) {
|
|
50
|
+
if (envMemRaw) {
|
|
51
|
+
throw new AutoTuneApplyError("--auto-tune with a value cannot be used with DS_MEMORY_LIMIT_MB");
|
|
52
|
+
}
|
|
53
|
+
} else if (!envMemRaw) {
|
|
54
|
+
throw new AutoTuneApplyError("--auto-tune requires DS_MEMORY_LIMIT_MB to be set (or pass a value)");
|
|
55
|
+
}
|
|
56
|
+
const memMb = overrideMb != null ? overrideMb : Number(envMemRaw);
|
|
57
|
+
if (!Number.isFinite(memMb) || memMb <= 0) {
|
|
58
|
+
const bad = overrideMb != null ? String(overrideMb) : String(envMemRaw);
|
|
59
|
+
throw new AutoTuneApplyError(`invalid DS_MEMORY_LIMIT_MB: ${bad}`);
|
|
60
|
+
}
|
|
61
|
+
if (env.DS_MEMORY_LIMIT_BYTES) {
|
|
62
|
+
throw new AutoTuneApplyError("--auto-tune does not allow DS_MEMORY_LIMIT_BYTES; use DS_MEMORY_LIMIT_MB");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const conflictVars = [
|
|
66
|
+
"DS_SEGMENT_MAX_BYTES",
|
|
67
|
+
"DS_SEGMENT_TARGET_ROWS",
|
|
68
|
+
"DS_SEGMENT_CACHE_MAX_BYTES",
|
|
69
|
+
"DS_INDEX_CHECK_MS",
|
|
70
|
+
"DS_SQLITE_CACHE_MB",
|
|
71
|
+
"DS_SQLITE_CACHE_BYTES",
|
|
72
|
+
"DS_WORKER_SQLITE_CACHE_MB",
|
|
73
|
+
"DS_WORKER_SQLITE_CACHE_BYTES",
|
|
74
|
+
"DS_INDEX_RUN_MEM_CACHE_BYTES",
|
|
75
|
+
"DS_LEXICON_INDEX_CACHE_MAX_BYTES",
|
|
76
|
+
"DS_INGEST_MAX_BATCH_BYTES",
|
|
77
|
+
"DS_INGEST_MAX_QUEUE_BYTES",
|
|
78
|
+
"DS_INGEST_CONCURRENCY",
|
|
79
|
+
"DS_READ_CONCURRENCY",
|
|
80
|
+
"DS_SEARCH_CONCURRENCY",
|
|
81
|
+
"DS_ASYNC_INDEX_CONCURRENCY",
|
|
82
|
+
"DS_INDEX_BUILD_CONCURRENCY",
|
|
83
|
+
"DS_INDEX_COMPACT_CONCURRENCY",
|
|
84
|
+
"DS_SEGMENTER_WORKERS",
|
|
85
|
+
"DS_UPLOAD_CONCURRENCY",
|
|
86
|
+
"DS_SEARCH_COMPANION_TOC_CACHE_BYTES",
|
|
87
|
+
"DS_SEARCH_COMPANION_SECTION_CACHE_BYTES",
|
|
88
|
+
"DS_SEARCH_COMPANION_BATCH_SEGMENTS",
|
|
89
|
+
"DS_SEARCH_COMPANION_YIELD_BLOCKS",
|
|
90
|
+
];
|
|
91
|
+
const conflicts = conflictVars.filter((v) => env[v] != null);
|
|
92
|
+
if (conflicts.length > 0) {
|
|
93
|
+
throw new AutoTuneApplyError(`--auto-tune cannot be used with manual memory settings: ${conflicts.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const presets = [...AUTO_TUNE_PRESETS];
|
|
97
|
+
const preset = [...presets].reverse().find((v) => v <= memMb);
|
|
98
|
+
if (!preset) {
|
|
99
|
+
throw new AutoTuneApplyError(`DS_MEMORY_LIMIT_MB=${memMb} is below the minimum preset (256)`);
|
|
100
|
+
}
|
|
101
|
+
const tune: AutoTuneConfig = tuneForPreset(preset);
|
|
102
|
+
|
|
103
|
+
const memoryLimitMb = memoryLimitForPreset(preset);
|
|
104
|
+
env.DS_AUTO_TUNE_REQUESTED_MB = String(memMb);
|
|
105
|
+
env.DS_AUTO_TUNE_PRESET_MB = String(preset);
|
|
106
|
+
env.DS_AUTO_TUNE_EFFECTIVE_MEMORY_LIMIT_MB = String(memoryLimitMb);
|
|
107
|
+
env.DS_MEMORY_LIMIT_MB = String(memoryLimitMb);
|
|
108
|
+
env.DS_SEGMENT_MAX_BYTES = String(tune.segmentMaxMiB * 1024 * 1024);
|
|
109
|
+
env.DS_SEGMENT_TARGET_ROWS = String(tune.segmentTargetRows);
|
|
110
|
+
env.DS_SEGMENT_CACHE_MAX_BYTES = String(tune.segmentCacheMb * 1024 * 1024);
|
|
111
|
+
env.DS_INDEX_CHECK_MS = String(tune.indexCheckMs);
|
|
112
|
+
env.DS_SQLITE_CACHE_MB = String(tune.sqliteCacheMb);
|
|
113
|
+
env.DS_WORKER_SQLITE_CACHE_MB = String(tune.workerSqliteCacheMb);
|
|
114
|
+
env.DS_INDEX_RUN_MEM_CACHE_BYTES = String(tune.indexMemMb * 1024 * 1024);
|
|
115
|
+
env.DS_LEXICON_INDEX_CACHE_MAX_BYTES = String(tune.lexiconIndexCacheMb * 1024 * 1024);
|
|
116
|
+
env.DS_SEARCH_COMPANION_TOC_CACHE_BYTES = String(tune.searchCompanionTocCacheMb * 1024 * 1024);
|
|
117
|
+
env.DS_SEARCH_COMPANION_SECTION_CACHE_BYTES = String(tune.searchCompanionSectionCacheMb * 1024 * 1024);
|
|
118
|
+
env.DS_INGEST_MAX_BATCH_BYTES = String(tune.ingestBatchMb * 1024 * 1024);
|
|
119
|
+
env.DS_INGEST_MAX_QUEUE_BYTES = String(tune.ingestQueueMb * 1024 * 1024);
|
|
120
|
+
env.DS_INGEST_CONCURRENCY = String(tune.ingestConcurrency);
|
|
121
|
+
env.DS_READ_CONCURRENCY = String(tune.readConcurrency);
|
|
122
|
+
env.DS_SEARCH_CONCURRENCY = String(tune.searchConcurrency);
|
|
123
|
+
env.DS_ASYNC_INDEX_CONCURRENCY = String(tune.asyncIndexConcurrency);
|
|
124
|
+
env.DS_INDEX_BUILD_CONCURRENCY = String(tune.indexBuildConcurrency);
|
|
125
|
+
env.DS_INDEX_COMPACT_CONCURRENCY = String(tune.indexCompactConcurrency);
|
|
126
|
+
env.DS_SEGMENTER_WORKERS = String(tune.segmenterWorkers);
|
|
127
|
+
env.DS_UPLOAD_CONCURRENCY = String(tune.uploadConcurrency);
|
|
128
|
+
env.DS_SEARCH_COMPANION_BATCH_SEGMENTS = String(tune.searchCompanionBatchSegments);
|
|
129
|
+
env.DS_SEARCH_COMPANION_YIELD_BLOCKS = String(tune.searchCompanionYieldBlocks);
|
|
130
|
+
|
|
131
|
+
const presetLine = formatPresetList(presets, preset, (v) => v, (v) => String(v));
|
|
132
|
+
log(`Auto-tuning for memory preset ${presetLine}`);
|
|
133
|
+
log(
|
|
134
|
+
`DS_MEMORY_LIMIT_MB presets: ${formatPresetList(presets, preset, (p) => memoryLimitForPreset(p), (v) => String(v))}`
|
|
135
|
+
);
|
|
136
|
+
log(`DS_SEGMENT_MAX_MIB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).segmentMaxMiB, (v) => String(v))}`);
|
|
137
|
+
log(`DS_SEGMENT_TARGET_ROWS presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).segmentTargetRows, (v) => String(v))}`);
|
|
138
|
+
log(`DS_SEGMENT_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).segmentCacheMb, (v) => String(v))}`);
|
|
139
|
+
log(`DS_INDEX_CHECK_MS presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).indexCheckMs, (v) => String(v))}`);
|
|
140
|
+
log(`DS_SQLITE_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).sqliteCacheMb, (v) => String(v))}`);
|
|
141
|
+
log(`DS_WORKER_SQLITE_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).workerSqliteCacheMb, (v) => String(v))}`);
|
|
142
|
+
log(`DS_INDEX_RUN_MEM_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).indexMemMb, (v) => String(v))}`);
|
|
143
|
+
log(`DS_LEXICON_INDEX_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).lexiconIndexCacheMb, (v) => String(v))}`);
|
|
144
|
+
log(`DS_SEARCH_COMPANION_TOC_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).searchCompanionTocCacheMb, (v) => String(v))}`);
|
|
145
|
+
log(`DS_SEARCH_COMPANION_SECTION_CACHE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).searchCompanionSectionCacheMb, (v) => String(v))}`);
|
|
146
|
+
log(`DS_INGEST_MAX_BATCH_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).ingestBatchMb, (v) => String(v))}`);
|
|
147
|
+
log(`DS_INGEST_MAX_QUEUE_MB presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).ingestQueueMb, (v) => String(v))}`);
|
|
148
|
+
log(`DS_INGEST_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).ingestConcurrency, (v) => String(v))}`);
|
|
149
|
+
log(`DS_READ_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).readConcurrency, (v) => String(v))}`);
|
|
150
|
+
log(`DS_SEARCH_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).searchConcurrency, (v) => String(v))}`);
|
|
151
|
+
log(`DS_ASYNC_INDEX_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).asyncIndexConcurrency, (v) => String(v))}`);
|
|
152
|
+
log(`DS_INDEX_BUILD_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).indexBuildConcurrency, (v) => String(v))}`);
|
|
153
|
+
log(`DS_INDEX_COMPACT_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).indexCompactConcurrency, (v) => String(v))}`);
|
|
154
|
+
log(`DS_SEGMENTER_WORKERS presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).segmenterWorkers, (v) => String(v))}`);
|
|
155
|
+
log(`DS_UPLOAD_CONCURRENCY presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).uploadConcurrency, (v) => String(v))}`);
|
|
156
|
+
log(`DS_SEARCH_COMPANION_BATCH_SEGMENTS presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).searchCompanionBatchSegments, (v) => String(v))}`);
|
|
157
|
+
log(`DS_SEARCH_COMPANION_YIELD_BLOCKS presets: ${formatPresetList(presets, preset, (p) => tuneForPreset(p).searchCompanionYieldBlocks, (v) => String(v))}`);
|
|
158
|
+
}
|