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.
Files changed (2) hide show
  1. package/benchmark/run.js +134 -10
  2. 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 (const arg of args) {
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, 50000, 100000)
80
- --batch=N Batch chunk size for insertMany (default: 3000)
81
- --queries=N Number of random indexed lookups to test (default: 500)
82
- --unindexed=N Number of unindexed full-scan operations to sample (default: 25)
83
- --dir=PATH Custom benchmark temp directory (default: ./benchmark/Benchmark_TempData)
84
- --keep Keep benchmark database files after run (default: auto-clean)
85
- --help, -h Show this help message
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
- const coldMs = nsToMs(process.hrtime.bigint() - coldStart);
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
- const cacheSpeedup = (Number(coldMs) / Math.max(Number(warmStats.avg), 0.001)).toFixed(1);
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.0",
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",