aifastdb 3.10.2 → 3.10.5

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 (62) hide show
  1. package/aifastdb.win32-x64-msvc.node +0 -0
  2. package/dist/architecture-store.d.ts +2 -2
  3. package/dist/architecture-store.d.ts.map +1 -1
  4. package/dist/architecture-store.js +4 -4
  5. package/dist/architecture-store.js.map +1 -1
  6. package/dist/concurrent-document-store-benchmark.d.ts +29 -0
  7. package/dist/concurrent-document-store-benchmark.d.ts.map +1 -0
  8. package/dist/concurrent-document-store-benchmark.js +726 -0
  9. package/dist/concurrent-document-store-benchmark.js.map +1 -0
  10. package/dist/concurrent-document-store-profile.d.ts +15 -0
  11. package/dist/concurrent-document-store-profile.d.ts.map +1 -0
  12. package/dist/concurrent-document-store-profile.js +197 -0
  13. package/dist/concurrent-document-store-profile.js.map +1 -0
  14. package/dist/concurrent-document-store.d.ts +281 -0
  15. package/dist/concurrent-document-store.d.ts.map +1 -0
  16. package/dist/concurrent-document-store.js +354 -0
  17. package/dist/concurrent-document-store.js.map +1 -0
  18. package/dist/document-store.d.ts +11 -12
  19. package/dist/document-store.d.ts.map +1 -1
  20. package/dist/document-store.js +8 -9
  21. package/dist/document-store.js.map +1 -1
  22. package/dist/federation/FederatedDb.d.ts +17 -2
  23. package/dist/federation/FederatedDb.d.ts.map +1 -1
  24. package/dist/federation/FederatedDb.js +23 -4
  25. package/dist/federation/FederatedDb.js.map +1 -1
  26. package/dist/federation/index.d.ts +1 -1
  27. package/dist/federation/index.d.ts.map +1 -1
  28. package/dist/federation/index.js.map +1 -1
  29. package/dist/federation/types.d.ts +11 -0
  30. package/dist/federation/types.d.ts.map +1 -1
  31. package/dist/federation/types.js.map +1 -1
  32. package/dist/find-by-metadata-fast-path-bench.d.ts +19 -0
  33. package/dist/find-by-metadata-fast-path-bench.d.ts.map +1 -0
  34. package/dist/find-by-metadata-fast-path-bench.js +146 -0
  35. package/dist/find-by-metadata-fast-path-bench.js.map +1 -0
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +11 -6
  39. package/dist/index.js.map +1 -1
  40. package/dist/mcp-server/index.js +1 -1
  41. package/dist/mcp-server/index.js.map +1 -1
  42. package/dist/native.d.ts +3 -2
  43. package/dist/native.d.ts.map +1 -1
  44. package/dist/native.js +9 -4
  45. package/dist/native.js.map +1 -1
  46. package/dist/read-profiler-verify.d.ts +15 -0
  47. package/dist/read-profiler-verify.d.ts.map +1 -0
  48. package/dist/read-profiler-verify.js +283 -0
  49. package/dist/read-profiler-verify.js.map +1 -0
  50. package/dist/social-graph-v2-benchmark.js +98 -8
  51. package/dist/social-graph-v2-benchmark.js.map +1 -1
  52. package/dist/social-graph-v2-read-benchmark.d.ts +19 -0
  53. package/dist/social-graph-v2-read-benchmark.d.ts.map +1 -0
  54. package/dist/social-graph-v2-read-benchmark.js +153 -0
  55. package/dist/social-graph-v2-read-benchmark.js.map +1 -0
  56. package/dist/social-graph-v2.d.ts +20 -0
  57. package/dist/social-graph-v2.d.ts.map +1 -1
  58. package/dist/social-graph-v2.js +28 -0
  59. package/dist/social-graph-v2.js.map +1 -1
  60. package/dist/test-delete.js +2 -2
  61. package/dist/test-delete.js.map +1 -1
  62. package/package.json +3 -3
@@ -0,0 +1,726 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ConcurrentDocumentStore TypeScript API benchmark.
5
+ *
6
+ * Measures Node/TypeScript API-layer throughput for the high-concurrency
7
+ * document engine. Inspired by Karpathy's autoresearch experiment loop and
8
+ * the SocialGraphV2 benchmark used to discover the addPersonFast optimisation
9
+ * (research/program.md). It establishes a fixed protocol so before/after runs
10
+ * are directly comparable, and emits grep-friendly summary lines suitable for
11
+ * `research/results.tsv` ingestion.
12
+ *
13
+ * Scenarios covered:
14
+ * - write (per-call enqueue; current default JS path)
15
+ * - writeSync (per-call wait-for-persistence)
16
+ * - writeBatch-N (TS-side batching with batchSize=N)
17
+ * - writeFast (opt-in) (auto-batched fire-and-forget, see writeFast() helper)
18
+ * - randomGet (point reads, random IDs)
19
+ * - findByTag (single-tag scan)
20
+ * - findByMetadata (operator-style filter)
21
+ *
22
+ * Usage:
23
+ * node dist/concurrent-document-store-benchmark.js
24
+ * node dist/concurrent-document-store-benchmark.js --total=20000 --batch-size=2000 --shards=16 --wal=false
25
+ *
26
+ * # Only run a subset (comma-separated):
27
+ * node dist/concurrent-document-store-benchmark.js --scenarios=write,writeBatch,writeFast
28
+ */
29
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ var desc = Object.getOwnPropertyDescriptor(m, k);
32
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
33
+ desc = { enumerable: true, get: function() { return m[k]; } };
34
+ }
35
+ Object.defineProperty(o, k2, desc);
36
+ }) : (function(o, m, k, k2) {
37
+ if (k2 === undefined) k2 = k;
38
+ o[k2] = m[k];
39
+ }));
40
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
41
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
42
+ }) : function(o, v) {
43
+ o["default"] = v;
44
+ });
45
+ var __importStar = (this && this.__importStar) || (function () {
46
+ var ownKeys = function(o) {
47
+ ownKeys = Object.getOwnPropertyNames || function (o) {
48
+ var ar = [];
49
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
50
+ return ar;
51
+ };
52
+ return ownKeys(o);
53
+ };
54
+ return function (mod) {
55
+ if (mod && mod.__esModule) return mod;
56
+ var result = {};
57
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
58
+ __setModuleDefault(result, mod);
59
+ return result;
60
+ };
61
+ })();
62
+ Object.defineProperty(exports, "__esModule", { value: true });
63
+ const fs = __importStar(require("fs"));
64
+ const os = __importStar(require("os"));
65
+ const path = __importStar(require("path"));
66
+ const concurrent_document_store_1 = require("./concurrent-document-store");
67
+ const DEFAULT_SCENARIOS = [
68
+ 'write',
69
+ 'writeSync',
70
+ 'writeSyncConcurrent',
71
+ 'writeBatch',
72
+ 'writeFast',
73
+ 'writeFastAndWait',
74
+ 'randomGet',
75
+ 'findByTag',
76
+ 'findByMetadata',
77
+ ];
78
+ function parseArgs() {
79
+ const cpuCount = os.cpus().length;
80
+ const defaults = {
81
+ total: 20000,
82
+ batchSize: 2000,
83
+ shardCount: Math.max(4, Math.min(cpuCount, 32)),
84
+ wal: false,
85
+ readOps: 50000,
86
+ scanOps: 2000,
87
+ fastBatch: 2000,
88
+ fastFlushMs: 5,
89
+ };
90
+ const args = new Map();
91
+ for (const rawArg of process.argv.slice(2)) {
92
+ if (!rawArg.startsWith('--'))
93
+ continue;
94
+ const [key, value] = rawArg.slice(2).split('=');
95
+ if (key && value !== undefined)
96
+ args.set(key, value);
97
+ }
98
+ const readPositiveInt = (key, raw) => {
99
+ if (raw === undefined)
100
+ return defaults[key];
101
+ const parsed = Number.parseInt(raw, 10);
102
+ if (!Number.isFinite(parsed) || parsed < 0) {
103
+ throw new Error(`Invalid --${key} value: ${raw}`);
104
+ }
105
+ return parsed;
106
+ };
107
+ const rawWal = args.get('wal');
108
+ const rawScenarios = args.get('scenarios');
109
+ return {
110
+ total: readPositiveInt('total', args.get('total')),
111
+ batchSize: readPositiveInt('batchSize', args.get('batchSize') ?? args.get('batch-size')),
112
+ shardCount: readPositiveInt('shardCount', args.get('shardCount') ?? args.get('shards')),
113
+ wal: rawWal === undefined ? defaults.wal : rawWal !== 'false',
114
+ readOps: readPositiveInt('readOps', args.get('readOps') ?? args.get('read-ops')),
115
+ scanOps: readPositiveInt('scanOps', args.get('scanOps') ?? args.get('scan-ops')),
116
+ fastBatch: readPositiveInt('fastBatch', args.get('fastBatch') ?? args.get('fast-batch')),
117
+ fastFlushMs: readPositiveInt('fastFlushMs', args.get('fastFlushMs') ?? args.get('fast-flush-ms')),
118
+ scenarios: rawScenarios
119
+ ? new Set(rawScenarios.split(',').map((s) => s.trim()).filter(Boolean))
120
+ : null,
121
+ };
122
+ }
123
+ function shouldRun(options, name) {
124
+ return options.scenarios === null || options.scenarios.has(name);
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Helpers
128
+ // ---------------------------------------------------------------------------
129
+ function makeTempDir(prefix) {
130
+ return fs.mkdtempSync(path.join(os.tmpdir(), `${prefix}-`));
131
+ }
132
+ function cleanupDir(dir) {
133
+ try {
134
+ fs.rmSync(dir, { recursive: true, force: true });
135
+ }
136
+ catch {
137
+ // Best-effort cleanup; Windows may briefly hold the WAL handle.
138
+ }
139
+ }
140
+ function createStore(dbPath, options) {
141
+ return new concurrent_document_store_1.ConcurrentDocumentStore(dbPath, {
142
+ shardCount: options.shardCount,
143
+ enableWal: options.wal,
144
+ mode: 'high_throughput',
145
+ enableFileLock: false,
146
+ });
147
+ }
148
+ function sleep(ms) {
149
+ return new Promise((resolve) => setTimeout(resolve, ms));
150
+ }
151
+ async function waitForWrites(store, expected, timeoutMs = 30000) {
152
+ // Tight 1ms poll — settle time is on the critical path of every benchmark.
153
+ // Note: `currentQueueSize` is not reliable as a "fully drained" signal —
154
+ // the underlying core can keep an outstanding queue counter even after all
155
+ // user writes succeed, so we only gate on `successfulWrites`.
156
+ const deadline = performance.now() + timeoutMs;
157
+ while (performance.now() < deadline) {
158
+ if (store.metrics().successfulWrites >= expected)
159
+ return;
160
+ await sleep(1);
161
+ }
162
+ }
163
+ const TAG_POOL = ['bench', 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta'];
164
+ function makeDoc(index) {
165
+ return {
166
+ id: `doc-${index}`,
167
+ content: `ConcurrentDocumentStore benchmark payload #${index}`,
168
+ tags: [TAG_POOL[index % TAG_POOL.length], TAG_POOL[(index * 3 + 1) % TAG_POOL.length]],
169
+ docType: index % 2 === 0 ? 'note' : 'report',
170
+ importance: (index % 100) / 100,
171
+ metadata: {
172
+ source: `bench-${index % 16}`,
173
+ bucket: index % 64,
174
+ priority: 1 + (index % 5),
175
+ active: index % 2 === 0,
176
+ },
177
+ };
178
+ }
179
+ function fmt(n) {
180
+ return n.toLocaleString('en-US', { maximumFractionDigits: 1 });
181
+ }
182
+ function getMemoryMB() {
183
+ const u = process.memoryUsage();
184
+ return (u.rss + (u.external || 0)) / (1024 * 1024);
185
+ }
186
+ function printHeader(options) {
187
+ console.log('============================================================');
188
+ console.log(' ConcurrentDocumentStore TypeScript API Benchmark');
189
+ console.log('============================================================');
190
+ console.log(` Total documents: ${options.total.toLocaleString()}`);
191
+ console.log(` Batch size: ${options.batchSize.toLocaleString()}`);
192
+ console.log(` writeFast batch: ${options.fastBatch.toLocaleString()} (flush=${options.fastFlushMs}ms)`);
193
+ console.log(` Shards: ${options.shardCount.toLocaleString()}`);
194
+ console.log(` WAL: ${options.wal}`);
195
+ console.log(` Read ops: ${options.readOps.toLocaleString()}, Scan ops: ${options.scanOps.toLocaleString()}`);
196
+ console.log(` CPU cores: ${os.cpus().length}, Platform: ${process.platform}-${process.arch}`);
197
+ console.log(` Node: ${process.version}`);
198
+ console.log('');
199
+ }
200
+ function printResult(result) {
201
+ console.log(`\n=== ${result.scenario} ===`);
202
+ console.log(` Items: ${result.total.toLocaleString()} ${result.unit}`);
203
+ console.log(` Elapsed: ${result.elapsedMs.toFixed(2)} ms`);
204
+ console.log(` Throughput: ${Math.round(result.throughput).toLocaleString()} ${result.unit}/sec`);
205
+ console.log(` Avg cost: ${result.avgMsPerItem.toFixed(4)} ms/${result.unit === 'docs' ? 'doc' : 'op'}`);
206
+ if (result.metrics) {
207
+ const m = result.metrics;
208
+ console.log(` Metrics: writes=${m.successfulWrites}, failed=${m.failedWrites}, rejected=${m.rejectedRequests}, avgWrite=${(m.avgWriteTimeUs / 1000).toFixed(2)}ms, avgQueue=${(m.avgQueueTimeUs / 1000).toFixed(2)}ms, avgBatch=${m.avgBatchSize.toFixed(1)}, queue=${m.currentQueueSize}/${m.peakQueueSize}`);
209
+ }
210
+ }
211
+ // ---------------------------------------------------------------------------
212
+ // Scenarios — writes
213
+ // ---------------------------------------------------------------------------
214
+ async function benchmarkWrite(options) {
215
+ const root = makeTempDir('cds-bench-write');
216
+ const store = createStore(path.join(root, 'store'), options);
217
+ try {
218
+ const start = performance.now();
219
+ for (let i = 0; i < options.total; i += 1) {
220
+ const doc = makeDoc(i);
221
+ store.write(doc.id, doc.content, {
222
+ tags: doc.tags,
223
+ docType: doc.docType,
224
+ importance: doc.importance,
225
+ metadata: doc.metadata,
226
+ });
227
+ }
228
+ store.flush();
229
+ await waitForWrites(store, options.total);
230
+ const elapsedMs = performance.now() - start;
231
+ return {
232
+ scenario: 'write (single-call enqueue)',
233
+ total: options.total,
234
+ elapsedMs,
235
+ throughput: options.total / (elapsedMs / 1000),
236
+ avgMsPerItem: elapsedMs / options.total,
237
+ unit: 'docs',
238
+ len: store.len(),
239
+ metrics: store.metrics(),
240
+ };
241
+ }
242
+ finally {
243
+ await store.closeAsync().catch(() => undefined);
244
+ cleanupDir(root);
245
+ }
246
+ }
247
+ async function benchmarkWriteSync(options) {
248
+ const root = makeTempDir('cds-bench-write-sync');
249
+ const store = createStore(path.join(root, 'store'), options);
250
+ try {
251
+ const start = performance.now();
252
+ for (let i = 0; i < options.total; i += 1) {
253
+ const doc = makeDoc(i);
254
+ store.writeSync(doc.id, doc.content, {
255
+ tags: doc.tags,
256
+ docType: doc.docType,
257
+ importance: doc.importance,
258
+ metadata: doc.metadata,
259
+ });
260
+ }
261
+ store.flush();
262
+ await waitForWrites(store, options.total);
263
+ const elapsedMs = performance.now() - start;
264
+ return {
265
+ scenario: 'writeSync (wait per call)',
266
+ total: options.total,
267
+ elapsedMs,
268
+ throughput: options.total / (elapsedMs / 1000),
269
+ avgMsPerItem: elapsedMs / options.total,
270
+ unit: 'docs',
271
+ len: store.len(),
272
+ metrics: store.metrics(),
273
+ };
274
+ }
275
+ finally {
276
+ await store.closeAsync().catch(() => undefined);
277
+ cleanupDir(root);
278
+ }
279
+ }
280
+ /**
281
+ * phase-375/T375.1: concurrent writeSync via the new `writeSyncAsync`
282
+ * NAPI method. The single-threaded `for { writeSync }` loop in
283
+ * `benchmarkWriteSync` is capped by NAPI sync round-trip (~48 K ops/s
284
+ * end of phase-373). This scenario fans out N JS Promises so the
285
+ * underlying multi-thread runtime + completion-triggered flush
286
+ * actually batch writes from multiple callers. Target: 80 K+.
287
+ */
288
+ async function benchmarkWriteSyncConcurrent(options) {
289
+ const root = makeTempDir('cds-bench-write-sync-concurrent');
290
+ const store = createStore(path.join(root, 'store'), options);
291
+ const concurrency = Math.max(8, Math.min(options.shardCount * 4, 256));
292
+ try {
293
+ const start = performance.now();
294
+ let nextIndex = 0;
295
+ await Promise.all(Array.from({ length: concurrency }, async () => {
296
+ while (true) {
297
+ const i = nextIndex;
298
+ nextIndex += 1;
299
+ if (i >= options.total)
300
+ break;
301
+ const doc = makeDoc(i);
302
+ await store.writeSyncAsync(doc.id, doc.content, {
303
+ tags: doc.tags,
304
+ docType: doc.docType,
305
+ importance: doc.importance,
306
+ metadata: doc.metadata,
307
+ });
308
+ }
309
+ }));
310
+ store.flush();
311
+ await waitForWrites(store, options.total);
312
+ const elapsedMs = performance.now() - start;
313
+ return {
314
+ scenario: `writeSyncConcurrent (concurrency=${concurrency})`,
315
+ total: options.total,
316
+ elapsedMs,
317
+ throughput: options.total / (elapsedMs / 1000),
318
+ avgMsPerItem: elapsedMs / options.total,
319
+ unit: 'docs',
320
+ len: store.len(),
321
+ metrics: store.metrics(),
322
+ };
323
+ }
324
+ finally {
325
+ await store.closeAsync().catch(() => undefined);
326
+ cleanupDir(root);
327
+ }
328
+ }
329
+ async function benchmarkWriteBatch(options) {
330
+ const root = makeTempDir('cds-bench-write-batch');
331
+ const store = createStore(path.join(root, 'store'), options);
332
+ try {
333
+ const start = performance.now();
334
+ for (let batchStart = 0; batchStart < options.total; batchStart += options.batchSize) {
335
+ const batchEnd = Math.min(batchStart + options.batchSize, options.total);
336
+ const batch = [];
337
+ for (let i = batchStart; i < batchEnd; i += 1)
338
+ batch.push(makeDoc(i));
339
+ store.writeBatch(batch);
340
+ }
341
+ store.flush();
342
+ await waitForWrites(store, options.total);
343
+ const elapsedMs = performance.now() - start;
344
+ return {
345
+ scenario: `writeBatch (batchSize=${options.batchSize})`,
346
+ total: options.total,
347
+ elapsedMs,
348
+ throughput: options.total / (elapsedMs / 1000),
349
+ avgMsPerItem: elapsedMs / options.total,
350
+ unit: 'docs',
351
+ len: store.len(),
352
+ metrics: store.metrics(),
353
+ };
354
+ }
355
+ finally {
356
+ await store.closeAsync().catch(() => undefined);
357
+ cleanupDir(root);
358
+ }
359
+ }
360
+ async function benchmarkWriteFast(options) {
361
+ const mod = await Promise.resolve().then(() => __importStar(require('./concurrent-document-store')));
362
+ const factory = mod.createBatchedWriter;
363
+ if (typeof factory !== 'function') {
364
+ console.log('\n=== writeFast — SKIPPED ===');
365
+ console.log(' createBatchedWriter() not available yet. Run T365.3 to enable.');
366
+ return null;
367
+ }
368
+ const root = makeTempDir('cds-bench-write-fast');
369
+ const store = createStore(path.join(root, 'store'), options);
370
+ try {
371
+ const writer = factory(store, {
372
+ batchSize: options.fastBatch,
373
+ flushIntervalMs: options.fastFlushMs,
374
+ });
375
+ const start = performance.now();
376
+ for (let i = 0; i < options.total; i += 1)
377
+ writer.writeFast(makeDoc(i));
378
+ await writer.flush();
379
+ store.flush();
380
+ await waitForWrites(store, options.total);
381
+ const elapsedMs = performance.now() - start;
382
+ await writer.close();
383
+ return {
384
+ scenario: `writeFast (autoBatch=${options.fastBatch})`,
385
+ total: options.total,
386
+ elapsedMs,
387
+ throughput: options.total / (elapsedMs / 1000),
388
+ avgMsPerItem: elapsedMs / options.total,
389
+ unit: 'docs',
390
+ len: store.len(),
391
+ metrics: store.metrics(),
392
+ };
393
+ }
394
+ finally {
395
+ await store.closeAsync().catch(() => undefined);
396
+ cleanupDir(root);
397
+ }
398
+ }
399
+ async function benchmarkWriteFastAndWait(options) {
400
+ const mod = await Promise.resolve().then(() => __importStar(require('./concurrent-document-store')));
401
+ const factory = mod.createBatchedWriter;
402
+ if (typeof factory !== 'function') {
403
+ console.log('\n=== writeFastAndWait — SKIPPED ===');
404
+ console.log(' createBatchedWriter() not available yet.');
405
+ return null;
406
+ }
407
+ const root = makeTempDir('cds-bench-write-fast-wait');
408
+ const store = createStore(path.join(root, 'store'), options);
409
+ try {
410
+ const writer = factory(store, {
411
+ batchSize: options.fastBatch,
412
+ flushIntervalMs: options.fastFlushMs,
413
+ });
414
+ const start = performance.now();
415
+ const promises = new Array(options.total);
416
+ for (let i = 0; i < options.total; i += 1) {
417
+ promises[i] = writer.writeFastAndWait(makeDoc(i));
418
+ }
419
+ await Promise.all(promises);
420
+ store.flush();
421
+ await waitForWrites(store, options.total);
422
+ const elapsedMs = performance.now() - start;
423
+ await writer.close();
424
+ return {
425
+ scenario: `writeFastAndWait (autoBatch=${options.fastBatch})`,
426
+ total: options.total,
427
+ elapsedMs,
428
+ throughput: options.total / (elapsedMs / 1000),
429
+ avgMsPerItem: elapsedMs / options.total,
430
+ unit: 'docs',
431
+ len: store.len(),
432
+ metrics: store.metrics(),
433
+ };
434
+ }
435
+ finally {
436
+ await store.closeAsync().catch(() => undefined);
437
+ cleanupDir(root);
438
+ }
439
+ }
440
+ // ---------------------------------------------------------------------------
441
+ // Scenarios — reads / queries
442
+ // ---------------------------------------------------------------------------
443
+ async function seedReadStore(options) {
444
+ const root = makeTempDir('cds-bench-read');
445
+ const store = createStore(path.join(root, 'store'), options);
446
+ try {
447
+ const seedBatch = Math.max(500, Math.min(options.batchSize, 2000));
448
+ for (let i = 0; i < options.total; i += seedBatch) {
449
+ const end = Math.min(i + seedBatch, options.total);
450
+ const batch = [];
451
+ for (let j = i; j < end; j += 1)
452
+ batch.push(makeDoc(j));
453
+ store.writeBatch(batch);
454
+ }
455
+ store.flush();
456
+ await waitForWrites(store, options.total);
457
+ return {
458
+ store,
459
+ ids: store.ids(),
460
+ cleanup: () => {
461
+ try {
462
+ store.close();
463
+ }
464
+ catch {
465
+ // ignore double-close
466
+ }
467
+ cleanupDir(root);
468
+ },
469
+ };
470
+ }
471
+ catch (e) {
472
+ cleanupDir(root);
473
+ throw e;
474
+ }
475
+ }
476
+ function benchmarkRandomGet(store, ids, readOps) {
477
+ const len = ids.length;
478
+ store.readProfileReset();
479
+ const start = performance.now();
480
+ let hits = 0;
481
+ for (let i = 0; i < readOps; i += 1) {
482
+ const node = store.get(ids[(i * 2654435761) % len]);
483
+ if (node)
484
+ hits += 1;
485
+ }
486
+ const elapsedMs = performance.now() - start;
487
+ if (hits === 0)
488
+ throw new Error('randomGet: zero hits — seed data missing');
489
+ console.log('\n--- read profile (randomGet) ---');
490
+ console.log(store.readProfileSnapshot());
491
+ return {
492
+ scenario: 'randomGet (point read)',
493
+ total: readOps,
494
+ elapsedMs,
495
+ throughput: readOps / (elapsedMs / 1000),
496
+ avgMsPerItem: elapsedMs / readOps,
497
+ unit: 'ops',
498
+ len: store.len(),
499
+ };
500
+ }
501
+ function benchmarkFindByTag(store, scanOps) {
502
+ const tag = TAG_POOL[0];
503
+ store.readProfileReset();
504
+ const start = performance.now();
505
+ let total = 0;
506
+ for (let i = 0; i < scanOps; i += 1) {
507
+ total += store.findByTag(tag, { limit: 100 }).length;
508
+ }
509
+ const elapsedMs = performance.now() - start;
510
+ if (total === 0)
511
+ throw new Error('findByTag: zero results — seed data missing');
512
+ console.log('\n--- read profile (findByTag) ---');
513
+ console.log(store.readProfileSnapshot());
514
+ return {
515
+ scenario: 'findByTag (tag=bench, limit=100)',
516
+ total: scanOps,
517
+ elapsedMs,
518
+ throughput: scanOps / (elapsedMs / 1000),
519
+ avgMsPerItem: elapsedMs / scanOps,
520
+ unit: 'ops',
521
+ len: store.len(),
522
+ };
523
+ }
524
+ function benchmarkFindByMetadata(store, scanOps) {
525
+ store.readProfileReset();
526
+ const start = performance.now();
527
+ let total = 0;
528
+ for (let i = 0; i < scanOps; i += 1) {
529
+ total += store.findByMetadata({ priority: { $gte: 3 }, active: true }, { limit: 50 }).length;
530
+ }
531
+ const elapsedMs = performance.now() - start;
532
+ if (total === 0)
533
+ throw new Error('findByMetadata: zero results — seed data missing');
534
+ console.log('\n--- read profile (findByMetadata) ---');
535
+ console.log(store.readProfileSnapshot());
536
+ return {
537
+ scenario: 'findByMetadata (operator + limit=50)',
538
+ total: scanOps,
539
+ elapsedMs,
540
+ throughput: scanOps / (elapsedMs / 1000),
541
+ avgMsPerItem: elapsedMs / scanOps,
542
+ unit: 'ops',
543
+ len: store.len(),
544
+ };
545
+ }
546
+ // ---------------------------------------------------------------------------
547
+ // Summary
548
+ // ---------------------------------------------------------------------------
549
+ function geometricMean(values) {
550
+ const valid = values.filter((v) => Number.isFinite(v) && v > 0);
551
+ if (valid.length === 0)
552
+ return 0;
553
+ const log = valid.reduce((acc, v) => acc + Math.log(v), 0) / valid.length;
554
+ return Math.exp(log);
555
+ }
556
+ function printSummary(results, options, peakMemMb, totalSeconds) {
557
+ console.log('\n============================================================');
558
+ console.log(' Summary (human-readable)');
559
+ console.log('============================================================');
560
+ for (const r of results) {
561
+ console.log(` ${r.scenario.padEnd(40)} ${fmt(Math.round(r.throughput))} ${r.unit}/s (${r.avgMsPerItem.toFixed(4)} ms/${r.unit === 'docs' ? 'doc' : 'op'})`);
562
+ }
563
+ const get = (name) => {
564
+ const r = results.find((x) => x.scenario.startsWith(name));
565
+ return r ? r.throughput : 0;
566
+ };
567
+ const single = get('write (single-call');
568
+ const sync = get('writeSync');
569
+ const batch = get('writeBatch');
570
+ const fast = get('writeFast (');
571
+ const fastWait = get('writeFastAndWait');
572
+ const reads = get('randomGet');
573
+ const tagScan = get('findByTag');
574
+ const metaScan = get('findByMetadata');
575
+ console.log('');
576
+ if (sync && single) {
577
+ console.log(` write vs writeSync: ${(single / Math.max(sync, 1)).toFixed(2)}x faster`);
578
+ }
579
+ if (batch && single) {
580
+ console.log(` writeBatch vs write: ${(batch / Math.max(single, 1)).toFixed(2)}x faster`);
581
+ }
582
+ if (fast && single) {
583
+ console.log(` writeFast vs write: ${(fast / Math.max(single, 1)).toFixed(2)}x faster`);
584
+ }
585
+ if (fast && batch) {
586
+ console.log(` writeFast vs writeBatch: ${(fast / Math.max(batch, 1)).toFixed(2)}x`);
587
+ }
588
+ if (fastWait && sync) {
589
+ console.log(` writeFastAndWait vs writeSync: ${(fastWait / Math.max(sync, 1)).toFixed(0)}x faster (autoresearch fire-and-forget pattern)`);
590
+ }
591
+ if (fastWait && fast) {
592
+ console.log(` writeFastAndWait vs writeFast: ${(fastWait / Math.max(fast, 1)).toFixed(2)}x`);
593
+ }
594
+ // ---------------------------------------------------------------------
595
+ // Machine-parseable block (autoresearch-style)
596
+ // ---------------------------------------------------------------------
597
+ const composite = geometricMean([single, batch, reads, Math.max(tagScan, 0.01)]);
598
+ console.log('');
599
+ console.log('---');
600
+ console.log(`single_write_ops: ${single.toFixed(1)}`);
601
+ console.log(`sync_write_ops: ${sync.toFixed(1)}`);
602
+ console.log(`batch_write_ops: ${batch.toFixed(1)}`);
603
+ console.log(`fast_write_ops: ${fast.toFixed(1)}`);
604
+ console.log(`fast_wait_write_ops: ${fastWait.toFixed(1)}`);
605
+ console.log(`random_read_ops: ${reads.toFixed(1)}`);
606
+ console.log(`tag_scan_ops: ${tagScan.toFixed(1)}`);
607
+ console.log(`metadata_scan_ops: ${metaScan.toFixed(1)}`);
608
+ console.log(`composite_score: ${composite.toFixed(1)}`);
609
+ console.log(`peak_memory_mb: ${peakMemMb.toFixed(1)}`);
610
+ console.log(`total_seconds: ${totalSeconds.toFixed(1)}`);
611
+ console.log(`total_docs: ${options.total}`);
612
+ console.log(`batch_size: ${options.batchSize}`);
613
+ console.log(`fast_batch: ${options.fastBatch}`);
614
+ console.log(`shards: ${options.shardCount}`);
615
+ console.log(`wal: ${options.wal}`);
616
+ console.log(`platform: ${process.platform}-${process.arch}`);
617
+ console.log(`cpus: ${os.cpus().length}`);
618
+ console.log(`node_version: ${process.version}`);
619
+ console.log('---');
620
+ }
621
+ // ---------------------------------------------------------------------------
622
+ // Main
623
+ // ---------------------------------------------------------------------------
624
+ async function cooldown() {
625
+ // Brief pause between scenarios so the prior store's tempdir cleanup,
626
+ // shard worker shutdown, and Node GC don't pollute the next scenario's
627
+ // settle-phase timings. Tuned empirically: under 100ms left enough cross-
628
+ // scenario noise to skew writeFastAndWait by 5x; 250ms is solid.
629
+ await sleep(250);
630
+ if (typeof global.gc === 'function')
631
+ global.gc();
632
+ }
633
+ async function main() {
634
+ const options = parseArgs();
635
+ printHeader(options);
636
+ const t0 = performance.now();
637
+ let peakMem = getMemoryMB();
638
+ const results = [];
639
+ if (shouldRun(options, 'write')) {
640
+ const r = await benchmarkWrite(options);
641
+ peakMem = Math.max(peakMem, getMemoryMB());
642
+ printResult(r);
643
+ results.push(r);
644
+ await cooldown();
645
+ }
646
+ if (shouldRun(options, 'writeSync')) {
647
+ const r = await benchmarkWriteSync(options);
648
+ peakMem = Math.max(peakMem, getMemoryMB());
649
+ printResult(r);
650
+ results.push(r);
651
+ await cooldown();
652
+ }
653
+ if (shouldRun(options, 'writeSyncConcurrent')) {
654
+ const r = await benchmarkWriteSyncConcurrent(options);
655
+ peakMem = Math.max(peakMem, getMemoryMB());
656
+ printResult(r);
657
+ results.push(r);
658
+ await cooldown();
659
+ }
660
+ if (shouldRun(options, 'writeBatch')) {
661
+ const r = await benchmarkWriteBatch(options);
662
+ peakMem = Math.max(peakMem, getMemoryMB());
663
+ printResult(r);
664
+ results.push(r);
665
+ await cooldown();
666
+ }
667
+ if (shouldRun(options, 'writeFast')) {
668
+ const r = await benchmarkWriteFast(options);
669
+ peakMem = Math.max(peakMem, getMemoryMB());
670
+ if (r) {
671
+ printResult(r);
672
+ results.push(r);
673
+ }
674
+ await cooldown();
675
+ }
676
+ if (shouldRun(options, 'writeFastAndWait')) {
677
+ const r = await benchmarkWriteFastAndWait(options);
678
+ peakMem = Math.max(peakMem, getMemoryMB());
679
+ if (r) {
680
+ printResult(r);
681
+ results.push(r);
682
+ }
683
+ await cooldown();
684
+ }
685
+ // Read scenarios share a single seeded store
686
+ const needsRead = shouldRun(options, 'randomGet') || shouldRun(options, 'findByTag') || shouldRun(options, 'findByMetadata');
687
+ if (needsRead) {
688
+ console.log(`\n[bench] Seeding read store with ${options.total.toLocaleString()} docs ...`);
689
+ const seedT0 = performance.now();
690
+ const seeded = await seedReadStore(options);
691
+ console.log(`[bench] Seeded in ${((performance.now() - seedT0) / 1000).toFixed(1)}s (${seeded.ids.length} ids)`);
692
+ try {
693
+ if (shouldRun(options, 'randomGet')) {
694
+ const r = benchmarkRandomGet(seeded.store, seeded.ids, options.readOps);
695
+ peakMem = Math.max(peakMem, getMemoryMB());
696
+ printResult(r);
697
+ results.push(r);
698
+ }
699
+ if (shouldRun(options, 'findByTag')) {
700
+ const r = benchmarkFindByTag(seeded.store, options.scanOps);
701
+ peakMem = Math.max(peakMem, getMemoryMB());
702
+ printResult(r);
703
+ results.push(r);
704
+ }
705
+ if (shouldRun(options, 'findByMetadata')) {
706
+ const r = benchmarkFindByMetadata(seeded.store, options.scanOps);
707
+ peakMem = Math.max(peakMem, getMemoryMB());
708
+ printResult(r);
709
+ results.push(r);
710
+ }
711
+ }
712
+ finally {
713
+ seeded.cleanup();
714
+ }
715
+ }
716
+ const totalSeconds = (performance.now() - t0) / 1000;
717
+ printSummary(results, options, peakMem, totalSeconds);
718
+ }
719
+ main().catch((err) => {
720
+ console.error('Benchmark failed:', err);
721
+ console.error(err.stack);
722
+ process.exit(1);
723
+ });
724
+ // Allow `--scenarios` to be referenced inside printResult etc. without warnings.
725
+ void DEFAULT_SCENARIOS;
726
+ //# sourceMappingURL=concurrent-document-store-benchmark.js.map