axiodb 22.27.1 → 22.28.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiodb",
3
- "version": "22.27.1",
3
+ "version": "22.28.1",
4
4
  "description": "The embedded database for Node.js. Replaces SQLite, LowDB, NeDB & raw JSON files with MongoDB-style queries, ACID transactions, zero native dependencies, and built-in InMemoryCache. No compilation, no platform issues. Perfect for desktop apps, CLI tools, and embedded systems.",
5
5
  "main": "./lib/config/DB.js",
6
6
  "types": "./lib/config/DB.d.ts",
package/benchmark/run.js DELETED
@@ -1,785 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const { execSync } = require('child_process');
7
- const DEFAULT_BENCHMARK_DIR = path.resolve(__dirname, './Benchmark_TempData');
8
- let activeBenchmarkDir = DEFAULT_BENCHMARK_DIR;
9
-
10
- // Auto-build if lib is missing
11
- const libEntry = path.resolve(__dirname, '../lib/config/DB.js');
12
- if (!fs.existsSync(libEntry)) {
13
- console.log('\x1b[33m[Benchmark] Compiled lib/ not found. Running build first...\x1b[0m');
14
- execSync('npm run build', {
15
- stdio: 'inherit',
16
- cwd: path.resolve(__dirname, '..')
17
- });
18
- }
19
-
20
- const { AxioDB } = require('../lib/config/DB.js');
21
-
22
- // ANSI Color formatting
23
- const c = {
24
- reset: '\x1b[0m',
25
- bright: '\x1b[1m',
26
- dim: '\x1b[2m',
27
- green: '\x1b[32m',
28
- yellow: '\x1b[33m',
29
- blue: '\x1b[34m',
30
- magenta: '\x1b[35m',
31
- cyan: '\x1b[36m',
32
- red: '\x1b[31m',
33
- gray: '\x1b[90m'
34
- };
35
-
36
- // Parse command line arguments
37
- function parseArgs() {
38
- const args = process.argv.slice(2);
39
- const options = {
40
- docs: 15000, // Default set to 15k
41
- batchSize: 3000, // Default batch size set to 3k
42
- queries: 500, // Indexed read lookups
43
- unindexedSamples: 25, // Unindexed full-scan lookups (scan over dataset)
44
- updateQueries: 100, // Indexed updates
45
- deleteQueries: 50, // Indexed deletes
46
- keepData: false, // Auto-delete temp data by default
47
- dir: DEFAULT_BENCHMARK_DIR
48
- };
49
-
50
- for (let i = 0; i < args.length; i++) {
51
- const arg = args[i];
52
- if (arg.startsWith('--docs=')) {
53
- const val = parseInt(arg.split('=')[1], 10);
54
- if (!isNaN(val) && val > 0) options.docs = val;
55
- } else if (arg === '--docs' && i + 1 < args.length) {
56
- const val = parseInt(args[++i], 10);
57
- if (!isNaN(val) && val > 0) options.docs = val;
58
- } else if (arg.startsWith('--batch=')) {
59
- const val = parseInt(arg.split('=')[1], 10);
60
- if (!isNaN(val) && val > 0) options.batchSize = val;
61
- } else if (arg === '--batch' && i + 1 < args.length) {
62
- const val = parseInt(args[++i], 10);
63
- if (!isNaN(val) && val > 0) options.batchSize = val;
64
- } else if (arg.startsWith('--queries=')) {
65
- const val = parseInt(arg.split('=')[1], 10);
66
- if (!isNaN(val) && val > 0) options.queries = val;
67
- } else if (arg === '--queries' && i + 1 < args.length) {
68
- const val = parseInt(args[++i], 10);
69
- if (!isNaN(val) && val > 0) options.queries = val;
70
- } else if (arg.startsWith('--unindexed=')) {
71
- const val = parseInt(arg.split('=')[1], 10);
72
- if (!isNaN(val) && val > 0) options.unindexedSamples = val;
73
- } else if (arg === '--unindexed' && i + 1 < args.length) {
74
- const val = parseInt(args[++i], 10);
75
- if (!isNaN(val) && val > 0) options.unindexedSamples = val;
76
- } else if (arg.startsWith('--dir=')) {
77
- const customDir = arg.split('=')[1];
78
- if (customDir && customDir.trim()) {
79
- options.dir = path.resolve(customDir.trim());
80
- }
81
- } else if (arg === '--dir' && i + 1 < args.length) {
82
- const customDir = args[++i];
83
- if (customDir && customDir.trim()) {
84
- options.dir = path.resolve(customDir.trim());
85
- }
86
- } else if (arg === '--keep') {
87
- options.keepData = true;
88
- } else if (arg === '--help' || arg === '-h') {
89
- console.log(`
90
- AxioDB Performance & Scale Benchmark Suite
91
-
92
- Usage:
93
- node benchmark/run.js [options]
94
- npm run benchmark -- [options]
95
-
96
- Options:
97
- --docs=N, --docs N Total documents to insert (default: 15000, e.g. 10000, 20000)
98
- --batch=N, --batch N Batch chunk size for insertMany (default: 3000)
99
- --queries=N, --queries N Number of random indexed lookups to test (default: 500)
100
- --unindexed=N Number of unindexed full-scan operations to sample (default: 25)
101
- --dir=PATH Custom benchmark temp directory (default: ./benchmark/Benchmark_TempData)
102
- --keep Keep benchmark database files after run (default: auto-clean)
103
- --help, -h Show this help message
104
- `);
105
- process.exit(0);
106
- }
107
- }
108
-
109
- // Adjust batch size if docs < batchSize
110
- if (options.docs < options.batchSize) {
111
- options.batchSize = options.docs;
112
- }
113
-
114
- options.batch = options.batchSize;
115
- return options;
116
- }
117
-
118
- // Convert BigInt nanoseconds to milliseconds float
119
- function nsToMs(ns) {
120
- return Number(ns) / 1e6;
121
- }
122
-
123
- // Format bytes into readable MB
124
- function bytesToMB(bytes) {
125
- return (bytes / (1024 * 1024)).toFixed(2);
126
- }
127
-
128
- // Format bytes into readable string (B, KB, MB)
129
- function bytesToReadable(bytes) {
130
- if (bytes < 1024) return `${bytes} B`;
131
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
132
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
133
- }
134
-
135
- function safeRate(count, durationMs) {
136
- if (count <= 0 || durationMs <= 0) return 0;
137
- return Math.round((count / durationMs) * 1000);
138
- }
139
-
140
- function clampWorkload(requested, available, minimum = 0) {
141
- if (available <= 0) return 0;
142
- return Math.max(minimum, Math.min(requested, available));
143
- }
144
-
145
- // Calculate directory sizes recursively (Total vs Data-only vs Indexes)
146
- function calculateDirSizes(dirPath) {
147
- let totalBytes = 0;
148
- let withoutIndexBytes = 0;
149
- let indexBytes = 0;
150
- let docCount = 0;
151
-
152
- function traverse(currentPath) {
153
- if (!fs.existsSync(currentPath)) return;
154
- const entries = fs.readdirSync(currentPath, { withFileTypes: true });
155
- for (const entry of entries) {
156
- const fullPath = path.join(currentPath, entry.name);
157
- if (entry.isDirectory()) {
158
- traverse(fullPath);
159
- } else if (entry.isFile()) {
160
- try {
161
- const stats = fs.statSync(fullPath);
162
- totalBytes += stats.size;
163
- // Index files: inside "indexes" folder or ending in .jsonl
164
- const isIndexFile = fullPath.includes(`${path.sep}indexes${path.sep}`) ||
165
- fullPath.endsWith('.jsonl') ||
166
- entry.name === 'index.meta.jsonl';
167
- if (isIndexFile) {
168
- indexBytes += stats.size;
169
- } else {
170
- withoutIndexBytes += stats.size;
171
- if (entry.name.endsWith('.axiodb')) {
172
- docCount++;
173
- }
174
- }
175
- } catch (_) {}
176
- }
177
- }
178
- }
179
-
180
- traverse(dirPath);
181
- return { totalBytes, withoutIndexBytes, indexBytes, docCount };
182
- }
183
-
184
- // Enhanced Percentile calculator (min, p25, p50, p75, p90, p95, p99, max, avg)
185
- function calculatePercentiles(latencies) {
186
- if (!latencies || latencies.length === 0) {
187
- return {
188
- min: '0.000',
189
- p25: '0.000',
190
- p50: '0.000',
191
- p75: '0.000',
192
- p90: '0.000',
193
- p95: '0.000',
194
- p99: '0.000',
195
- max: '0.000',
196
- avg: '0.000'
197
- };
198
- }
199
-
200
- const sorted = [...latencies].sort((a, b) => a - b);
201
- const getP = (p) => sorted[Math.min(Math.floor((p / 100) * sorted.length), sorted.length - 1)];
202
- const sum = sorted.reduce((acc, v) => acc + v, 0);
203
-
204
- return {
205
- min: Number(sorted[0]).toFixed(3),
206
- p25: Number(getP(25)).toFixed(3),
207
- p50: Number(getP(50)).toFixed(3),
208
- p75: Number(getP(75)).toFixed(3),
209
- p90: Number(getP(90)).toFixed(3),
210
- p95: Number(getP(95)).toFixed(3),
211
- p99: Number(getP(99)).toFixed(3),
212
- max: Number(sorted[sorted.length - 1]).toFixed(3),
213
- avg: (sum / sorted.length).toFixed(3)
214
- };
215
- }
216
-
217
- // Helper to remove directory cleanly (robust recursive deletion)
218
- function cleanDirectory(dirPath) {
219
- try {
220
- if (fs.existsSync(dirPath)) {
221
- fs.rmSync(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
222
- }
223
- } catch (err) {
224
- // Ignore cleanup race conditions
225
- }
226
- }
227
-
228
- // Format percentiles output line
229
- function printPercentiles(stats, indent = ' ') {
230
- console.log(`${indent}${c.dim}• Min:${c.reset} ${stats.min.padStart(7)} ms | ${c.dim}p25:${c.reset} ${stats.p25.padStart(7)} ms | ${c.dim}p50 (Med):${c.reset} ${stats.p50.padStart(7)} ms`);
231
- console.log(`${indent}${c.dim}• p75:${c.reset} ${stats.p75.padStart(7)} ms | ${c.dim}p90:${c.reset} ${stats.p90.padStart(7)} ms | ${c.dim}p95:${c.reset} ${stats.p95.padStart(7)} ms`);
232
- console.log(`${indent}${c.dim}• p99:${c.reset} ${stats.p99.padStart(7)} ms | ${c.dim}Max:${c.reset} ${stats.max.padStart(7)} ms | ${c.dim}Avg:${c.reset} ${stats.avg.padStart(7)} ms`);
233
- }
234
-
235
- // Fast document batch generator with both indexed and unindexed fields
236
- function generateBatch(startIdx, count) {
237
- const batch = new Array(count);
238
- for (let i = 0; i < count; i++) {
239
- const id = startIdx + i;
240
- batch[i] = {
241
- // Indexed fields: email, age, score, name
242
- name: `User_${id}`,
243
- email: `user_${id}@bench.axiodb.org`,
244
- age: 18 + (id % 62),
245
- score: id % 1000,
246
- // Unindexed fields: region, unindexedTag, metadata
247
- region: ['us-east', 'us-west', 'eu-central', 'ap-south'][id % 4],
248
- unindexedTag: `tag_${id}`,
249
- metadata: {
250
- loginCount: id % 50,
251
- tier: id % 10 === 0 ? 'enterprise' : 'standard'
252
- },
253
- active: id % 2 === 0,
254
- createdAt: new Date().toISOString()
255
- };
256
- }
257
- return batch;
258
- }
259
-
260
- async function runBenchmark() {
261
- const opts = parseArgs();
262
- activeBenchmarkDir = opts.dir;
263
- const initMem = process.memoryUsage();
264
-
265
- console.log(`\n${c.bright}${c.cyan}╔═══════════════════════════════════════════════════════════════════╗${c.reset}`);
266
- console.log(`${c.bright}${c.cyan}║ AxioDB Performance & Scale Benchmark Suite ║${c.reset}`);
267
- console.log(`${c.bright}${c.cyan}╚═══════════════════════════════════════════════════════════════════╝${c.reset}`);
268
-
269
- console.log(`\n${c.bright}System Environment:${c.reset}`);
270
- console.log(` ${c.dim}• Platform:${c.reset} ${os.type()} ${os.release()} (${os.arch()})`);
271
- console.log(` ${c.dim}• CPUs:${c.reset} ${os.cpus().length} cores (${os.cpus()[0]?.model || 'Generic'})`);
272
- console.log(` ${c.dim}• Total RAM:${c.reset} ${(os.totalmem() / 1e9).toFixed(2)} GB`);
273
- console.log(` ${c.dim}• Available RAM:${c.reset} ${(os.freemem() / 1e9).toFixed(2)} GB`);
274
- console.log(` ${c.dim}• Process RAM Initial:${c.reset} RSS: ${bytesToMB(initMem.rss)} MB | Heap: ${bytesToMB(initMem.heapUsed)} MB`);
275
- console.log(` ${c.dim}• Node.js:${c.reset} ${process.version} (V8: ${process.versions.v8})`);
276
- console.log(` ${c.dim}• Target Scale:${c.reset} ${opts.docs.toLocaleString()} documents (Batch Size: ${opts.batchSize.toLocaleString()})`);
277
-
278
- // Setup cleanup handler for interrupts
279
- const cleanup = () => {
280
- if (!opts.keepData) {
281
- cleanDirectory(opts.dir);
282
- }
283
- };
284
-
285
- process.on('SIGINT', () => {
286
- console.log(`\n${c.yellow}Benchmark interrupted. Cleaning up test data...${c.reset}`);
287
- cleanup();
288
- process.exit(1);
289
- });
290
-
291
- process.on('SIGTERM', () => {
292
- console.log(`\n${c.yellow}Benchmark terminated. Cleaning up test data...${c.reset}`);
293
- cleanup();
294
- process.exit(1);
295
- });
296
-
297
- // Ensure clean test directory before start
298
- cleanDirectory(opts.dir);
299
-
300
- let peakMemory = process.memoryUsage().heapUsed;
301
- let peakRss = process.memoryUsage().rss;
302
- let storageFootprint = { totalBytes: 0, withoutIndexBytes: 0, indexBytes: 0, docCount: 0 };
303
-
304
- const trackMemory = () => {
305
- const mem = process.memoryUsage();
306
- if (mem.heapUsed > peakMemory) peakMemory = mem.heapUsed;
307
- if (mem.rss > peakRss) peakRss = mem.rss;
308
- };
309
-
310
- console.log(`\n${c.bright}${c.blue}[1/6] Initializing Database & Indexes...${c.reset}`);
311
- const db = new AxioDB({ GUI: false, RootName: 'BenchmarkDB', CustomPath: opts.dir });
312
- const testDB = await db.createDB('BenchDB');
313
- const collection = await testDB.createCollection('Users');
314
-
315
- // Create multi-field indexes before bulk ingestion
316
- console.log(` ${c.dim}Creating indexes on (email, age, score, name)...${c.reset}`);
317
- const idxStart = process.hrtime.bigint();
318
- await collection.newIndex('email', 'age', 'score', 'name');
319
- const idxDuration = nsToMs(process.hrtime.bigint() - idxStart);
320
- console.log(` ${c.green}✓ Indexes created in ${idxDuration.toFixed(2)}ms${c.reset}`);
321
-
322
- // -------------------------------------------------------------
323
- // PHASE 1: Bulk Ingestion (insertMany)
324
- // -------------------------------------------------------------
325
- console.log(`\n${c.bright}${c.blue}[2/6] Ingesting ${opts.docs.toLocaleString()} Documents in Batches of ${opts.batchSize.toLocaleString()}...${c.reset}`);
326
- const totalBatches = Math.ceil(opts.docs / opts.batchSize);
327
- let totalInserted = 0;
328
-
329
- const ingestStartTime = process.hrtime.bigint();
330
-
331
- for (let b = 0; b < totalBatches; b++) {
332
- const currentBatchCount = Math.min(opts.batchSize, opts.docs - totalInserted);
333
- const batch = generateBatch(totalInserted, currentBatchCount);
334
-
335
- const batchStart = process.hrtime.bigint();
336
- const res = await collection.insertMany(batch);
337
- const batchDuration = nsToMs(process.hrtime.bigint() - batchStart);
338
-
339
- if (res && (res.status === true || res.statusCode === 200)) {
340
- totalInserted += currentBatchCount;
341
- } else {
342
- console.error(`${c.red}Failed inserting batch ${b + 1}: ${JSON.stringify(res)}${c.reset}`);
343
- break;
344
- }
345
-
346
- trackMemory();
347
- const currentMem = process.memoryUsage();
348
- const rate = Math.round((currentBatchCount / (batchDuration / 1000)));
349
- const percent = Math.round((totalInserted / opts.docs) * 100);
350
- process.stdout.write(` ${c.dim}Batch ${b + 1}/${totalBatches} (${percent}%): ${totalInserted.toLocaleString()} docs | ${rate.toLocaleString()} docs/sec | ${batchDuration.toFixed(0)}ms | RSS: ${bytesToMB(currentMem.rss)}MB | Heap: ${bytesToMB(currentMem.heapUsed)}MB\r${c.reset}`);
351
- }
352
-
353
- const totalIngestTimeMs = nsToMs(process.hrtime.bigint() - ingestStartTime);
354
- const overallIngestRate = safeRate(totalInserted, totalIngestTimeMs);
355
-
356
- console.log(`\n ${c.green}✓ Ingestion Complete: ${totalInserted.toLocaleString()} docs in ${(totalIngestTimeMs / 1000).toFixed(2)}s (${c.bright}${overallIngestRate.toLocaleString()} docs/sec${c.reset}${c.green})${c.reset}`);
357
-
358
- const countCheck = await collection.totalDocuments();
359
- const confirmedTotal = countCheck?.data?.total ?? totalInserted;
360
- console.log(` ${c.dim}Verified physical document count: ${confirmedTotal.toLocaleString()} on disk${c.reset}`);
361
-
362
- // Measure exact storage footprint on disk while all documents are present
363
- storageFootprint = calculateDirSizes(opts.dir);
364
- console.log(` ${c.dim}Directory Size:${c.reset} Total: ${bytesToReadable(storageFootprint.totalBytes)} | Data only (no index): ${bytesToReadable(storageFootprint.withoutIndexBytes)} | Indexes: ${bytesToReadable(storageFootprint.indexBytes)}`);
365
-
366
- const readQueryCount = clampWorkload(opts.queries, totalInserted, 1);
367
- const unindexedReadSamples = clampWorkload(opts.unindexedSamples, totalInserted, 1);
368
- const indexedUpdateCount = clampWorkload(opts.updateQueries, totalInserted, 1);
369
- const indexedDeleteCount = clampWorkload(opts.deleteQueries, totalInserted, 0);
370
- const remainingAfterIndexedDeletes = Math.max(totalInserted - indexedDeleteCount, 0);
371
- const unindexedDeleteCount = clampWorkload(opts.unindexedSamples, remainingAfterIndexedDeletes, 0);
372
-
373
- if (
374
- readQueryCount !== opts.queries ||
375
- unindexedReadSamples !== opts.unindexedSamples ||
376
- indexedUpdateCount !== opts.updateQueries ||
377
- indexedDeleteCount !== opts.deleteQueries ||
378
- unindexedDeleteCount !== opts.unindexedSamples
379
- ) {
380
- console.log(`\n ${c.yellow}⚠ Adjusted benchmark operation counts for current dataset size (${totalInserted.toLocaleString()} docs).${c.reset}`);
381
- console.log(` ${c.dim}Indexed reads:${c.reset} ${readQueryCount} | ${c.dim}Unindexed reads:${c.reset} ${unindexedReadSamples} | ${c.dim}Indexed updates:${c.reset} ${indexedUpdateCount} | ${c.dim}Indexed deletes:${c.reset} ${indexedDeleteCount} | ${c.dim}Unindexed deletes:${c.reset} ${unindexedDeleteCount}`);
382
- }
383
-
384
- // -------------------------------------------------------------
385
- // PHASE 2: Read Latency: Indexed vs Unindexed
386
- // -------------------------------------------------------------
387
- console.log(`\n${c.bright}${c.blue}[3/6] Testing Read Latency (Indexed vs Unindexed)...${c.reset}`);
388
-
389
- // 2A: Indexed Point Reads
390
- console.log(` ${c.dim}• Testing Indexed Point Reads (${readQueryCount.toLocaleString()} random exact matches)...${c.reset}`);
391
- const indexedReadLatencies = [];
392
- const readStart = process.hrtime.bigint();
393
-
394
- for (let q = 0; q < readQueryCount; q++) {
395
- const targetId = Math.floor(Math.random() * totalInserted);
396
- const targetEmail = `user_${targetId}@bench.axiodb.org`;
397
-
398
- const qStart = process.hrtime.bigint();
399
- const result = await collection.query({ email: targetEmail }).exec();
400
- const qDuration = nsToMs(process.hrtime.bigint() - qStart);
401
-
402
- if (result && (result.status === true || result.statusCode === 200) && result.data?.documents?.length > 0) {
403
- indexedReadLatencies.push(qDuration);
404
- }
405
- }
406
-
407
- const totalReadTimeMs = nsToMs(process.hrtime.bigint() - readStart);
408
- const readQPS = safeRate(readQueryCount, totalReadTimeMs);
409
- const indexedReadStats = calculatePercentiles(indexedReadLatencies);
410
-
411
- console.log(` ${c.green}✓ Indexed Reads:${c.reset} ${indexedReadLatencies.length}/${readQueryCount} successful (${c.bright}${readQPS.toLocaleString()} QPS${c.reset})`);
412
- printPercentiles(indexedReadStats);
413
-
414
- // 2B: Unindexed Reads (Full Collection Scan)
415
- console.log(`\n ${c.dim}• Testing Unindexed Point Reads (${unindexedReadSamples} full-scan lookups on 'unindexedTag')...${c.reset}`);
416
- const unindexedReadLatencies = [];
417
- const unindexedReadStart = process.hrtime.bigint();
418
-
419
- for (let q = 0; q < unindexedReadSamples; q++) {
420
- const targetId = Math.floor(Math.random() * totalInserted);
421
- const targetTag = `tag_${targetId}`;
422
-
423
- const uStart = process.hrtime.bigint();
424
- const result = await collection.query({ unindexedTag: targetTag }).exec();
425
- const uDuration = nsToMs(process.hrtime.bigint() - uStart);
426
-
427
- if (result && (result.status === true || result.statusCode === 200) && result.data?.documents?.length > 0) {
428
- unindexedReadLatencies.push(uDuration);
429
- }
430
- }
431
-
432
- const totalUnindexedReadTime = nsToMs(process.hrtime.bigint() - unindexedReadStart);
433
- const unindexedReadQPS = safeRate(unindexedReadSamples, totalUnindexedReadTime);
434
- const unindexedReadStats = calculatePercentiles(unindexedReadLatencies);
435
-
436
- console.log(` ${c.green}✓ Unindexed Reads:${c.reset} ${unindexedReadLatencies.length}/${unindexedReadSamples} successful (${unindexedReadQPS.toLocaleString()} QPS)`);
437
- printPercentiles(unindexedReadStats);
438
-
439
- const indexAdvantage = (Number(unindexedReadStats.avg) / Math.max(Number(indexedReadStats.avg), 0.001)).toFixed(1);
440
- console.log(` ${c.cyan}⚡ Index Advantage:${c.reset} Indexed reads are ${c.bright}${indexAdvantage}x faster${c.reset} than unindexed full scans`);
441
-
442
- // -------------------------------------------------------------
443
- // PHASE 3: Update Latency: Indexed vs Unindexed
444
- // -------------------------------------------------------------
445
- console.log(`\n${c.bright}${c.blue}[4/6] Testing Update Latency (UpdateOne: Indexed vs Unindexed)...${c.reset}`);
446
-
447
- // 3A: Indexed Updates
448
- console.log(` ${c.dim}• Testing Indexed UpdateOne (${indexedUpdateCount} queries on indexed 'email')...${c.reset}`);
449
- const indexedUpdateLatencies = [];
450
-
451
- for (let u = 0; u < indexedUpdateCount; u++) {
452
- const targetId = Math.floor(Math.random() * totalInserted);
453
- const targetEmail = `user_${targetId}@bench.axiodb.org`;
454
-
455
- const uStart = process.hrtime.bigint();
456
- const res = await collection.update({ email: targetEmail }).UpdateOne({ score: 9999 });
457
- const uDuration = nsToMs(process.hrtime.bigint() - uStart);
458
-
459
- if (res && (res.status === true || res.statusCode === 200)) {
460
- indexedUpdateLatencies.push(uDuration);
461
- }
462
- }
463
-
464
- const indexedUpdateStats = calculatePercentiles(indexedUpdateLatencies);
465
- console.log(` ${c.green}✓ Indexed Updates:${c.reset} ${indexedUpdateLatencies.length}/${indexedUpdateCount} successful`);
466
- printPercentiles(indexedUpdateStats);
467
-
468
- // 3B: Unindexed Updates
469
- console.log(`\n ${c.dim}• Testing Unindexed UpdateOne (${unindexedReadSamples} queries on 'unindexedTag')...${c.reset}`);
470
- const unindexedUpdateLatencies = [];
471
-
472
- for (let u = 0; u < unindexedReadSamples; u++) {
473
- const targetId = Math.floor(Math.random() * totalInserted);
474
- const targetTag = `tag_${targetId}`;
475
-
476
- const uStart = process.hrtime.bigint();
477
- const res = await collection.update({ unindexedTag: targetTag }).UpdateOne({ score: 8888 });
478
- const uDuration = nsToMs(process.hrtime.bigint() - uStart);
479
-
480
- if (res && (res.status === true || res.statusCode === 200)) {
481
- unindexedUpdateLatencies.push(uDuration);
482
- }
483
- }
484
-
485
- const unindexedUpdateStats = calculatePercentiles(unindexedUpdateLatencies);
486
- console.log(` ${c.green}✓ Unindexed Updates:${c.reset} ${unindexedUpdateLatencies.length}/${unindexedReadSamples} successful`);
487
- printPercentiles(unindexedUpdateStats);
488
-
489
- // -------------------------------------------------------------
490
- // PHASE 4: Delete Latency: Indexed vs Unindexed
491
- // -------------------------------------------------------------
492
- console.log(`\n${c.bright}${c.blue}[5/6] Testing Delete Latency (deleteOne: Indexed vs Unindexed)...${c.reset}`);
493
-
494
- // 4A: Indexed Deletes
495
- console.log(` ${c.dim}• Testing Indexed deleteOne (${indexedDeleteCount} deletions by indexed 'email')...${c.reset}`);
496
- const indexedDeleteLatencies = [];
497
-
498
- for (let d = 0; d < indexedDeleteCount; d++) {
499
- const targetId = totalInserted - 1 - d;
500
- if (targetId < 0) break;
501
- const targetEmail = `user_${targetId}@bench.axiodb.org`;
502
-
503
- const dStart = process.hrtime.bigint();
504
- const res = await collection.delete({ email: targetEmail }).deleteOne();
505
- const dDuration = nsToMs(process.hrtime.bigint() - dStart);
506
-
507
- if (res && (res.status === true || res.statusCode === 200)) {
508
- indexedDeleteLatencies.push(dDuration);
509
- }
510
- }
511
-
512
- const indexedDeleteStats = calculatePercentiles(indexedDeleteLatencies);
513
- console.log(` ${c.green}✓ Indexed Deletions:${c.reset} ${indexedDeleteLatencies.length}/${indexedDeleteCount} successful`);
514
- printPercentiles(indexedDeleteStats);
515
-
516
- // 4B: Unindexed Deletes
517
- console.log(`\n ${c.dim}• Testing Unindexed deleteOne (${unindexedDeleteCount} deletions by 'unindexedTag')...${c.reset}`);
518
- const unindexedDeleteLatencies = [];
519
-
520
- for (let d = 0; d < unindexedDeleteCount; d++) {
521
- const targetId = totalInserted - 1 - indexedDeleteCount - d;
522
- if (targetId < 0) break;
523
- const targetTag = `tag_${targetId}`;
524
-
525
- const dStart = process.hrtime.bigint();
526
- const res = await collection.delete({ unindexedTag: targetTag }).deleteOne();
527
- const dDuration = nsToMs(process.hrtime.bigint() - dStart);
528
-
529
- if (res && (res.status === true || res.statusCode === 200)) {
530
- unindexedDeleteLatencies.push(dDuration);
531
- }
532
- }
533
-
534
- const unindexedDeleteStats = calculatePercentiles(unindexedDeleteLatencies);
535
- console.log(` ${c.green}✓ Unindexed Deletions:${c.reset} ${unindexedDeleteLatencies.length}/${unindexedDeleteCount} successful`);
536
- printPercentiles(unindexedDeleteStats);
537
-
538
- // -------------------------------------------------------------
539
- // PHASE 5: In-Memory Cache & ACID Transactions
540
- // -------------------------------------------------------------
541
- console.log(`\n${c.bright}${c.blue}[6/6] Testing InMemoryCache & ACID Transaction Integrity...${c.reset}`);
542
-
543
- // Cache test
544
- let warmStats = calculatePercentiles([]);
545
- let cacheSpeedupLabel = 'N/A (insufficient remaining documents)';
546
- let coldMs = 0;
547
- let cacheSpeedup = null;
548
- const remainingForCache = totalInserted - indexedDeleteCount - unindexedDeleteCount;
549
-
550
- if (remainingForCache > 0) {
551
- const cacheSampleId = Math.floor(Math.random() * remainingForCache);
552
- const cacheSampleEmail = `user_${cacheSampleId}@bench.axiodb.org`;
553
-
554
- const coldStart = process.hrtime.bigint();
555
- await collection.query({ email: cacheSampleEmail }).exec();
556
- coldMs = nsToMs(process.hrtime.bigint() - coldStart);
557
-
558
- const warmLatencies = [];
559
- for (let i = 0; i < 100; i++) {
560
- const wStart = process.hrtime.bigint();
561
- await collection.query({ email: cacheSampleEmail }).exec();
562
- warmLatencies.push(nsToMs(process.hrtime.bigint() - wStart));
563
- }
564
- warmStats = calculatePercentiles(warmLatencies);
565
- cacheSpeedup = (Number(coldMs) / Math.max(Number(warmStats.avg), 0.001)).toFixed(1);
566
- cacheSpeedupLabel = `${cacheSpeedup}x faster`;
567
-
568
- console.log(` ${c.green}✓ Cache Acceleration:${c.reset} Cold: ${coldMs.toFixed(3)} ms → Warm: ${warmStats.avg} ms (${c.bright}${cacheSpeedupLabel}${c.reset})`);
569
- } else {
570
- console.log(` ${c.yellow}⚠ Cache Acceleration test skipped:${c.reset} no documents remained after delete phases.`);
571
- }
572
-
573
- // ACID Atomic Commit Test (1,000 documents)
574
- console.log(` ${c.dim}• Testing Atomic Commit (1,000 records in 1 transaction)...${c.reset}`);
575
- const txCommitStart = process.hrtime.bigint();
576
- const txCommit = collection.beginTransaction();
577
- for (let i = 0; i < 1000; i++) {
578
- txCommit.insert({
579
- name: `TxCommit_${i}`,
580
- email: `txcommit_${i}@bench.axiodb.org`,
581
- age: 28,
582
- score: 1000,
583
- createdAt: new Date().toISOString()
584
- });
585
- }
586
- const commitRes = await txCommit.commit();
587
- const txCommitMs = nsToMs(process.hrtime.bigint() - txCommitStart);
588
-
589
- if (commitRes && (commitRes.status === true || commitRes.statusCode === 200)) {
590
- console.log(` ${c.green}✓ Atomic Commit Verified:${c.reset} 1,000 records committed in ${txCommitMs.toFixed(2)} ms`);
591
- } else {
592
- console.log(` ${c.red}✗ Atomic Commit Failed:${c.reset} ${JSON.stringify(commitRes)}`);
593
- }
594
-
595
- // ACID Atomic Rollback Test (1,000 documents, 0 data leakage)
596
- console.log(` ${c.dim}• Testing Atomic Rollback (1,000 records staged then rolled back)...${c.reset}`);
597
- const countBeforeRollback = await collection.totalDocuments();
598
- const docsBefore = countBeforeRollback?.data?.total || 0;
599
-
600
- const txRollback = collection.beginTransaction();
601
- for (let i = 0; i < 1000; i++) {
602
- txRollback.insert({
603
- name: `TxGhost_${i}`,
604
- email: `txghost_${i}@bench.axiodb.org`,
605
- age: 99,
606
- score: 0,
607
- createdAt: new Date().toISOString()
608
- });
609
- }
610
- await txRollback.rollback();
611
- const countAfterRollback = await collection.totalDocuments();
612
- const docsAfter = countAfterRollback?.data?.total || 0;
613
-
614
- if (docsBefore === docsAfter) {
615
- console.log(` ${c.green}✓ ACID Rollback Verified:${c.reset} 100% Consistent (0 leaked records: ${docsBefore.toLocaleString()} before == ${docsAfter.toLocaleString()} after)`);
616
- } else {
617
- console.log(` ${c.red}✗ ACID Rollback Failed:${c.reset} Expected ${docsBefore} docs, found ${docsAfter}`);
618
- }
619
-
620
- // -------------------------------------------------------------
621
- // SUMMARY REPORT
622
- // -------------------------------------------------------------
623
- trackMemory();
624
- const finalMem = process.memoryUsage();
625
-
626
- console.log(`\n${c.bright}${c.cyan}╔═══════════════════════════════════════════════════════════════════╗${c.reset}`);
627
- console.log(`${c.bright}${c.cyan}║ BENCHMARK RESULTS SUMMARY ║${c.reset}`);
628
- console.log(`${c.bright}${c.cyan}╚═══════════════════════════════════════════════════════════════════╝${c.reset}`);
629
-
630
- console.log(` ${c.bright}Dataset Scale:${c.reset} ${opts.docs.toLocaleString()} Documents`);
631
- console.log(` ${c.bright}Ingestion Throughput:${c.reset} ${c.green}${overallIngestRate.toLocaleString()} docs/sec${c.reset} (${totalInserted.toLocaleString()} docs in ${(totalIngestTimeMs / 1000).toFixed(2)}s)`);
632
- console.log(` ${c.bright}Indexed Point Read (QPS):${c.reset} ${c.green}${readQPS.toLocaleString()} QPS${c.reset} (p50: ${indexedReadStats.p50}ms | p95: ${indexedReadStats.p95}ms | p99: ${indexedReadStats.p99}ms)`);
633
- console.log(` ${c.bright}Unindexed Read Scan (QPS):${c.reset} ${unindexedReadQPS.toLocaleString()} QPS (p50: ${unindexedReadStats.p50}ms | p95: ${unindexedReadStats.p95}ms)`);
634
- console.log(` ${c.bright}Index Read Speedup:${c.reset} ${c.green}${indexAdvantage}x faster${c.reset} than full scan`);
635
- console.log(` ${c.bright}Indexed Update (p50):${c.reset} ${indexedUpdateStats.p50} ms (p95: ${indexedUpdateStats.p95}ms)`);
636
- console.log(` ${c.bright}Unindexed Update (p50):${c.reset} ${unindexedUpdateStats.p50} ms (p95: ${unindexedUpdateStats.p95}ms)`);
637
- console.log(` ${c.bright}Indexed Delete (p50):${c.reset} ${indexedDeleteStats.p50} ms (p95: ${indexedDeleteStats.p95}ms)`);
638
- console.log(` ${c.bright}Unindexed Delete (p50):${c.reset} ${unindexedDeleteStats.p50} ms (p95: ${unindexedDeleteStats.p95}ms)`);
639
- console.log(` ${c.bright}Cache Acceleration:${c.reset} ${cacheSpeedupLabel.includes('x faster') ? `${c.green}${cacheSpeedupLabel}${c.reset}` : cacheSpeedupLabel} (${warmStats.avg}ms warm cache)`);
640
- console.log(` ${c.bright}ACID Commit Latency:${c.reset} ${txCommitMs.toFixed(2)} ms for 1,000 atomic documents`);
641
- console.log(` ${c.bright}ACID Rollback Integrity:${c.reset} ${c.green}100% Consistent (0 data leakage)${c.reset}`);
642
- console.log(` ${c.bright}System Available RAM:${c.reset} ${(os.freemem() / 1e9).toFixed(2)} GB / ${(os.totalmem() / 1e9).toFixed(2)} GB`);
643
- console.log(` ${c.bright}Node Process Peak RAM:${c.reset} RSS: ${bytesToMB(peakRss)} MB | Heap: ${bytesToMB(peakMemory)} MB`);
644
- console.log(` ${c.bright}Node Process Final RAM:${c.reset} RSS: ${bytesToMB(finalMem.rss)} MB | Heap: ${bytesToMB(finalMem.heapUsed)} MB`);
645
-
646
- // Dynamic comparison estimation based on sample document
647
- const sampleDoc = generateBatch(0, 1)[0];
648
- const sampleDocBytes = Buffer.byteLength(JSON.stringify(sampleDoc));
649
- const estRawJsonBytes = (sampleDocBytes + 1) * opts.docs + 2;
650
- const estLowDBBytes = estRawJsonBytes + 30;
651
- const estSQLiteBytes = Math.ceil(((sampleDocBytes * 0.85) + 48) * opts.docs / 4096) * 4096;
652
-
653
- console.log(`\n ${c.bright}${c.magenta}On-Disk Storage Footprint (${opts.docs.toLocaleString()} Documents):${c.reset}`);
654
- console.log(` ${c.dim}• Total Directory Size (With Indexes):${c.reset} ${c.green}${bytesToReadable(storageFootprint.totalBytes)}${c.reset} (${bytesToMB(storageFootprint.totalBytes)} MB)`);
655
- console.log(` ${c.dim}• Data Only Size (Excluding Indexes):${c.reset} ${c.green}${bytesToReadable(storageFootprint.withoutIndexBytes)}${c.reset} (${bytesToMB(storageFootprint.withoutIndexBytes)} MB)`);
656
- console.log(` ${c.dim}• Index Storage Overhead (JSONL / Meta):${c.reset} ${bytesToReadable(storageFootprint.indexBytes)} (${((storageFootprint.indexBytes / Math.max(storageFootprint.totalBytes, 1)) * 100).toFixed(1)}% of total)`);
657
-
658
- console.log(`\n ${c.bright}Storage & Architecture Comparison (${opts.docs.toLocaleString()} Records):${c.reset}`);
659
- console.log(` ┌─────────────────────────┬──────────────┬──────────────┬────────────────────────────────────┐`);
660
- console.log(` │ Database Engine │ Disk Size │ Storage Type │ Trade-off / Advantage │`);
661
- console.log(` ├─────────────────────────┼──────────────┼──────────────┼────────────────────────────────────┤`);
662
- console.log(` │ ${c.cyan}AxioDB (Data Only)${c.reset} │ ${bytesToReadable(storageFootprint.withoutIndexBytes).padEnd(12)} │ File-per-doc │ Zero native C++ deps; atomic WAL │`);
663
- console.log(` │ ${c.cyan}AxioDB (With Indexes)${c.reset} │ ${bytesToReadable(storageFootprint.totalBytes).padEnd(12)} │ Data + JSONL │ Sub-ms indexed point reads & cache │`);
664
- console.log(` │ SQLite (Table + Indexes)│ ${('~' + bytesToReadable(estSQLiteBytes)).padEnd(12)} │ Single file │ Requires C++ native build toolchain│`);
665
- console.log(` │ LowDB (JSON File) │ ${('~' + bytesToReadable(estLowDBBytes)).padEnd(12)} │ Monolithic │ Rewrites entire file on every write│`);
666
- console.log(` │ Raw JSON File │ ${('~' + bytesToReadable(estRawJsonBytes)).padEnd(12)} │ Single Array │ No indexing; O(N) memory to parse │`);
667
- console.log(` └─────────────────────────┴──────────────┴──────────────┴────────────────────────────────────┘`);
668
-
669
- // Dynamic Architectural Recommendations (computed directly from the run's metrics)
670
- console.log(`\n ${c.bright}${c.cyan}Dynamic Engineering Recommendations (Based on this Run):${c.reset}`);
671
-
672
- const recommendations = [];
673
-
674
- // 1. Query Indexing Strategy
675
- const numIndexAdvantage = Number(indexAdvantage);
676
- if (!isNaN(numIndexAdvantage) && numIndexAdvantage > 50) {
677
- recommendations.push({
678
- topic: "Indexing Crucial",
679
- text: `Indexed lookups delivered ${numIndexAdvantage.toFixed(1)}x speedup (${indexedReadStats.p50}ms vs ${unindexedReadStats.p50}ms unindexed). Always index fields used in .query(); unindexed scans inspect all ${opts.docs.toLocaleString()} files on disk, causing heavy I/O wait.`
680
- });
681
- } else {
682
- recommendations.push({
683
- topic: "Indexing Status",
684
- text: `Indexed lookups averaged ${indexedReadStats.p50}ms. As data volume scales beyond ${opts.docs.toLocaleString()} records, ensure query predicates have corresponding indexes.`
685
- });
686
- }
687
-
688
- // 2. Targeted Mutations (Update & Delete)
689
- const unindexedUpdateP50 = Number(unindexedUpdateStats.p50);
690
- const indexedUpdateP50 = Number(indexedUpdateStats.p50);
691
- const unindexedDeleteP50 = Number(unindexedDeleteStats.p50);
692
- const indexedDeleteP50 = Number(indexedDeleteStats.p50);
693
- const updateDiff = indexedUpdateP50 > 0 ? (unindexedUpdateP50 / indexedUpdateP50).toFixed(1) : "10+";
694
- const deleteDiff = indexedDeleteP50 > 0 ? (unindexedDeleteP50 / indexedDeleteP50).toFixed(1) : "10+";
695
-
696
- if (unindexedUpdateP50 > 150 || Number(updateDiff) >= 2) {
697
- recommendations.push({
698
- topic: "Targeted Mutations",
699
- text: `Mutating via indexed fields is ${updateDiff}x faster for updates (${indexedUpdateStats.p50}ms vs ${unindexedUpdateStats.p50}ms) and ${deleteDiff}x faster for deletes (${indexedDeleteStats.p50}ms vs ${unindexedDeleteStats.p50}ms). Always target records by documentId or indexed keys in .UpdateOne() and .deleteOne().`
700
- });
701
- }
702
-
703
- // 3. Storage Overhead & Index Pruning
704
- const indexRatio = storageFootprint.totalBytes > 0 ? (storageFootprint.indexBytes / storageFootprint.totalBytes) : 0;
705
- const indexPercent = (indexRatio * 100).toFixed(1);
706
- if (indexRatio > 0.40) {
707
- recommendations.push({
708
- topic: "Index Pruning",
709
- text: `Index files account for ${indexPercent}% (${bytesToReadable(storageFootprint.indexBytes)}) of total on-disk storage. Avoid indexing low-cardinality flags (e.g. isActive) or long text to conserve disk space and minimize multi-index update overhead.`
710
- });
711
- } else {
712
- recommendations.push({
713
- topic: "Storage Footprint",
714
- text: `Index storage overhead is lean (${indexPercent}% of total directory size), delivering sub-millisecond point lookups without excessive disk usage.`
715
- });
716
- }
717
-
718
- // 4. In-Memory Cache Optimization
719
- const numCacheSpeedup = cacheSpeedup !== null ? Number(cacheSpeedup) : NaN;
720
- if (!isNaN(numCacheSpeedup) && numCacheSpeedup > 10) {
721
- recommendations.push({
722
- topic: "InMemoryCache",
723
- text: `Hot-path cache yielded ${numCacheSpeedup.toFixed(1)}x acceleration (${warmStats.avg}ms warm vs ${Number(coldMs).toFixed(2)}ms cold). Keep { Cache: true } enabled for read-heavy records (e.g. user sessions, profiles, configs) to bypass disk and index lookups completely.`
724
- });
725
- } else if (!isNaN(numCacheSpeedup)) {
726
- recommendations.push({
727
- topic: "InMemoryCache",
728
- text: `Hot-path cache yielded ${numCacheSpeedup.toFixed(1)}x speedup (${warmStats.avg}ms warm vs ${Number(coldMs).toFixed(2)}ms cold). Leverage { Cache: true } for repeating query patterns.`
729
- });
730
- }
731
-
732
- // 5. Bulk Ingestion & Batch Sizing
733
- if (overallIngestRate > 0) {
734
- const activeBatch = opts.batchSize || opts.batch || 3000;
735
- recommendations.push({
736
- topic: "Batch Ingestion",
737
- text: `Ingestion sustained ${overallIngestRate.toLocaleString()} docs/sec in batches of ${activeBatch.toLocaleString()}. For large bulk workloads, keeping batch sizes between 2,500 and 5,000 amortizes fsync overhead while keeping memory usage stable.`
738
- });
739
- }
740
-
741
- // 6. Memory & Host Headroom
742
- const freeRamGB = os.freemem() / 1e9;
743
- if (freeRamGB < 2.0) {
744
- recommendations.push({
745
- topic: "Host RAM Tuning",
746
- text: `Host system has ${freeRamGB.toFixed(2)} GB free RAM. For heavy workloads, use moderate batch sizes (2,000–3,000) to keep Node RSS headroom safe (${bytesToMB(peakRss)} MB peak observed).`
747
- });
748
- } else {
749
- recommendations.push({
750
- topic: "RAM Headroom",
751
- text: `Node process consumed ${bytesToMB(peakMemory)} MB peak heap (${bytesToMB(peakRss)} MB RSS) with ${freeRamGB.toFixed(2)} GB host RAM remaining. Memory scaling is well within safe operating limits.`
752
- });
753
- }
754
-
755
- // 7. Architectural Workload Fit
756
- if (opts.docs <= 30000) {
757
- recommendations.push({
758
- topic: "Workload Fit",
759
- text: `At ${opts.docs.toLocaleString()} records, AxioDB is in its ideal sweet spot: pure JavaScript with zero C++ native compiler dependencies, instant cold starts, and sub-millisecond indexed queries make it ideal for CLI utilities, Electron/Tauri desktop apps, and embedded services.`
760
- });
761
- } else {
762
- recommendations.push({
763
- topic: "High Volume Scale",
764
- text: `At ${opts.docs.toLocaleString()} records, always run on SSD/NVMe storage and use WAL-backed insertMany() for bulk operations, ensuring hot query paths have dedicated indexes.`
765
- });
766
- }
767
-
768
- recommendations.forEach((rec, idx) => {
769
- console.log(` ${c.dim}[${idx + 1}]${c.reset} ${c.bright}${rec.topic}:${c.reset} ${rec.text}`);
770
- });
771
-
772
- // Final cleanup of the temporary directory
773
- if (!opts.keepData) {
774
- cleanDirectory(opts.dir);
775
- console.log(`\n${c.dim}✓ Completely removed temporary benchmark folder at: ${opts.dir}${c.reset}\n`);
776
- } else {
777
- console.log(`\n${c.yellow}Preserved benchmark data at: ${opts.dir}${c.reset}\n`);
778
- }
779
- }
780
-
781
- runBenchmark().catch((err) => {
782
- console.error(`\n${c.red}Benchmark execution encountered an error:${c.reset}`, err);
783
- cleanDirectory(activeBenchmarkDir);
784
- process.exit(1);
785
- });
package/funding.json DELETED
@@ -1,61 +0,0 @@
1
- {
2
- "version": "v1.1.0",
3
- "entity": {
4
- "type": "individual",
5
- "role": "maintainer",
6
- "name": "Ankan Saha",
7
- "email": "connect@ankan.in",
8
- "description": "Systems engineer and open-source maintainer based in West Bengal, India. Creator of AxioDB, an embedded ACID document database for Node.js and Bun with 27,000+ annual npm downloads.",
9
- "webpageUrl": {
10
- "url": "https://ankan.in"
11
- }
12
- },
13
- "projects": [
14
- {
15
- "guid": "axiodb",
16
- "name": "AxioDB",
17
- "description": "Zero-dependency, pure TypeScript embedded ACID document database for Node.js and Bun. Eliminates native C++ compilation (node-gyp) overhead while providing durability, indexing, and high-concurrency document operations for local and edge environments.",
18
- "webpageUrl": {
19
- "url": "https://axiodb.in"
20
- },
21
- "repositoryUrl": {
22
- "url": "https://github.com/nexoral/AxioDB"
23
- },
24
- "licenses": [
25
- "spdx:MIT"
26
- ],
27
- "tags": [
28
- "database",
29
- "typescript",
30
- "nodejs",
31
- "embedded",
32
- "acid",
33
- "developer-tools"
34
- ]
35
- }
36
- ],
37
- "funding": {
38
- "channels": [
39
- {
40
- "guid": "bank-transfer",
41
- "type": "bank",
42
- "address": "Indian Bank Account (Details provided upon grant approval)",
43
- "description": "Direct NEFT/IMPS bank transfer for FOSS United grant disbursement."
44
- }
45
- ],
46
- "plans": [
47
- {
48
- "guid": "axiodb-dev-grant",
49
- "status": "active",
50
- "name": "AxioDB Engine Scaling & Concurrency Benchmarks",
51
- "description": "3-month milestone funding plan to support full-time open-source development: 1) In-memory B-Tree indexer for sub-millisecond query filters; 2) Automated ACID crash-recovery test suite testing process kill durability (SIGKILL resistance); 3) Zero-copy streaming JSON reader for bounded memory consumption under multi-gigabyte collections; 4) Public benchmark dashboard comparing AxioDB with LevelDB and SQLite.",
52
- "amount": 75000,
53
- "currency": "INR",
54
- "frequency": "one-time",
55
- "channels": [
56
- "bank-transfer"
57
- ]
58
- }
59
- ]
60
- }
61
- }