axiodb 22.27.0 → 22.27.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/benchmark/run.js +134 -10
- package/package.json +1 -1
package/benchmark/run.js
CHANGED
|
@@ -47,24 +47,42 @@ function parseArgs() {
|
|
|
47
47
|
dir: DEFAULT_BENCHMARK_DIR
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
for (
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
const arg = args[i];
|
|
51
52
|
if (arg.startsWith('--docs=')) {
|
|
52
53
|
const val = parseInt(arg.split('=')[1], 10);
|
|
53
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;
|
|
54
58
|
} else if (arg.startsWith('--batch=')) {
|
|
55
59
|
const val = parseInt(arg.split('=')[1], 10);
|
|
56
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;
|
|
57
64
|
} else if (arg.startsWith('--queries=')) {
|
|
58
65
|
const val = parseInt(arg.split('=')[1], 10);
|
|
59
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;
|
|
60
70
|
} else if (arg.startsWith('--unindexed=')) {
|
|
61
71
|
const val = parseInt(arg.split('=')[1], 10);
|
|
62
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;
|
|
63
76
|
} else if (arg.startsWith('--dir=')) {
|
|
64
77
|
const customDir = arg.split('=')[1];
|
|
65
78
|
if (customDir && customDir.trim()) {
|
|
66
79
|
options.dir = path.resolve(customDir.trim());
|
|
67
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
|
+
}
|
|
68
86
|
} else if (arg === '--keep') {
|
|
69
87
|
options.keepData = true;
|
|
70
88
|
} else if (arg === '--help' || arg === '-h') {
|
|
@@ -76,13 +94,13 @@ Usage:
|
|
|
76
94
|
npm run benchmark -- [options]
|
|
77
95
|
|
|
78
96
|
Options:
|
|
79
|
-
--docs=N Total documents to insert (default: 15000, e.g. 10000,
|
|
80
|
-
--batch=N
|
|
81
|
-
--queries=N
|
|
82
|
-
--unindexed=N
|
|
83
|
-
--dir=PATH
|
|
84
|
-
--keep
|
|
85
|
-
--help, -h
|
|
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
|
|
86
104
|
`);
|
|
87
105
|
process.exit(0);
|
|
88
106
|
}
|
|
@@ -93,6 +111,7 @@ Options:
|
|
|
93
111
|
options.batchSize = options.docs;
|
|
94
112
|
}
|
|
95
113
|
|
|
114
|
+
options.batch = options.batchSize;
|
|
96
115
|
return options;
|
|
97
116
|
}
|
|
98
117
|
|
|
@@ -524,6 +543,8 @@ async function runBenchmark() {
|
|
|
524
543
|
// Cache test
|
|
525
544
|
let warmStats = calculatePercentiles([]);
|
|
526
545
|
let cacheSpeedupLabel = 'N/A (insufficient remaining documents)';
|
|
546
|
+
let coldMs = 0;
|
|
547
|
+
let cacheSpeedup = null;
|
|
527
548
|
const remainingForCache = totalInserted - indexedDeleteCount - unindexedDeleteCount;
|
|
528
549
|
|
|
529
550
|
if (remainingForCache > 0) {
|
|
@@ -532,7 +553,7 @@ async function runBenchmark() {
|
|
|
532
553
|
|
|
533
554
|
const coldStart = process.hrtime.bigint();
|
|
534
555
|
await collection.query({ email: cacheSampleEmail }).exec();
|
|
535
|
-
|
|
556
|
+
coldMs = nsToMs(process.hrtime.bigint() - coldStart);
|
|
536
557
|
|
|
537
558
|
const warmLatencies = [];
|
|
538
559
|
for (let i = 0; i < 100; i++) {
|
|
@@ -541,7 +562,7 @@ async function runBenchmark() {
|
|
|
541
562
|
warmLatencies.push(nsToMs(process.hrtime.bigint() - wStart));
|
|
542
563
|
}
|
|
543
564
|
warmStats = calculatePercentiles(warmLatencies);
|
|
544
|
-
|
|
565
|
+
cacheSpeedup = (Number(coldMs) / Math.max(Number(warmStats.avg), 0.001)).toFixed(1);
|
|
545
566
|
cacheSpeedupLabel = `${cacheSpeedup}x faster`;
|
|
546
567
|
|
|
547
568
|
console.log(` ${c.green}✓ Cache Acceleration:${c.reset} Cold: ${coldMs.toFixed(3)} ms → Warm: ${warmStats.avg} ms (${c.bright}${cacheSpeedupLabel}${c.reset})`);
|
|
@@ -645,6 +666,109 @@ async function runBenchmark() {
|
|
|
645
666
|
console.log(` │ Raw JSON File │ ${('~' + bytesToReadable(estRawJsonBytes)).padEnd(12)} │ Single Array │ No indexing; O(N) memory to parse │`);
|
|
646
667
|
console.log(` └─────────────────────────┴──────────────┴──────────────┴────────────────────────────────────┘`);
|
|
647
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
|
+
|
|
648
772
|
// Final cleanup of the temporary directory
|
|
649
773
|
if (!opts.keepData) {
|
|
650
774
|
cleanDirectory(opts.dir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axiodb",
|
|
3
|
-
"version": "22.27.
|
|
3
|
+
"version": "22.27.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",
|