Skip to content

Commit eef348f

Browse files
committed
1 parent ff4d1b4 commit eef348f

8 files changed

Lines changed: 327 additions & 653 deletions

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
package-lock=true
1+
package-lock=false

README.md

Lines changed: 17 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ This project benchmarks SQLite performance across different Node.js versions (v1
1212
- **PRAGMA setting optimizations** - Testing Forward Email's actual production settings
1313
- **WAL mode performance** - Analyzing Write-Ahead Logging impact on throughput
1414
- **ChaCha20 encryption** - Quantum-resistant encryption performance testing
15+
- **Encryption overhead analysis** - Comparing better-sqlite3-multiple-ciphers vs better-sqlite3
1516
- **Cross-version compatibility** - Node.js version performance comparison
1617
- **Production workload simulation** - Insert/Select/Update operations mimicking email storage
1718

18-
## Quick Start
19-
2019
> [!TIP]
2120
> Use Node.js v20 LTS for best performance. Other versions show significant performance regressions.
2221
@@ -55,14 +54,17 @@ sqlite-benchmarks/
5554
> The `better-sqlite3-multiple-ciphers` package requires native compilation. You may need to rebuild when switching Node.js versions.
5655
5756
- **better-sqlite3-multiple-ciphers** - SQLite with ChaCha20 encryption support
57+
- **better-sqlite3** - Standard SQLite without encryption (for comparison)
5858
- **benchmark** - Performance testing framework
59+
- **cli-table3** - Terminal table formatting
60+
- **fs-extra** - Enhanced filesystem operations
5961

6062
## Benchmark Results Summary
6163

6264
### Node.js v20.19.5 (✅ RECOMMENDED)
6365
**Forward Email Production Config:**
6466
- Inserts: 10,548 ops/sec
65-
- Selects: 17,494 ops/sec
67+
- Selects: 17,494 ops/sec
6668
- Updates: 16,654 ops/sec
6769

6870
**Best Optimization (WAL Autocheckpoint 1000):**
@@ -94,124 +96,21 @@ sqlite-benchmarks/
9496
> [!WARNING]
9597
> Node.js v24 shows severe performance regressions for SQLite operations. Not recommended for production use.
9698
99+
## Encryption Overhead Analysis
97100

98-
<!-- BENCHMARK_RESULTS_START -->
99-
100-
### Latest Automated Benchmark Results
101-
102-
**Last Updated:** 2025-11-15
103-
104-
#### Forward Email Production
105-
106-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
107-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
108-
| v18.20.8 | 112.0 | 10,658 | 14,466 | 18,641 | 75,614 | 3.98 | 4.04 |
109-
| v20.19.5 | 128.2 | 9,588 | 11,908 | 16,123 | 37,552 | 3.98 | 4.04 |
110-
| v22.21.1 | 125.8 | 9,829 | 15,833 | 18,416 | 8,120 | 3.98 | 4.04 |
111-
| v24.11.1 | 123.6 | 9,938 | 7,497 | 10,446 | 66,203 | 3.98 | 4.04 |
112-
| v25.2.0 | 113.1 | 9,032 | 15,189 | 17,763 | 53,723 | 3.98 | 4.04 |
113-
114-
#### Memory Temp Storage
115-
116-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
117-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
118-
| v18.20.8 | 96.4 | 10,854 | 14,868 | 19,547 | 111,882 | 3.98 | 4.04 |
119-
| v20.19.5 | 98.0 | 9,987 | 14,293 | 18,459 | 91,075 | 3.98 | 4.04 |
120-
| v22.21.1 | 102.8 | 10,447 | 15,044 | 20,192 | 79,834 | 3.98 | 4.04 |
121-
| v24.11.1 | 118.3 | 8,792 | 12,608 | 16,794 | 81,281 | 3.98 | 4.04 |
122-
| v25.2.0 | 99.7 | 8,322 | 15,507 | 18,095 | 81,880 | 3.98 | 4.04 |
123-
124-
#### Synchronous OFF (Unsafe)
125-
126-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
127-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
128-
| v18.20.8 | 87.5 | 11,663 | 14,835 | 19,697 | 103,950 | 3.98 | 4.04 |
129-
| v20.19.5 | 95.3 | 10,379 | 14,399 | 19,055 | 87,858 | 3.98 | 4.04 |
130-
| v22.21.1 | 87.5 | 11,260 | 17,239 | 20,120 | 105,966 | 3.98 | 4.04 |
131-
| v24.11.1 | 126.4 | 8,617 | 9,316 | 15,436 | 78,382 | 3.98 | 4.04 |
132-
| v25.2.0 | 98.3 | 10,441 | 15,529 | 18,209 | 42,366 | 3.98 | 4.04 |
133-
134-
#### Synchronous EXTRA (Safe)
135-
136-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
137-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
138-
| v18.20.8 | 100.0 | 3,810 | 14,348 | 4,781 | 100,281 | 3.98 | 4.04 |
139-
| v20.19.5 | 102.4 | 1,659 | 13,736 | 1,803 | 73,335 | 3.98 | 4.04 |
140-
| v22.21.1 | 96.7 | 4,638 | 17,081 | 5,734 | 101,523 | 3.98 | 4.04 |
141-
| v24.11.1 | 144.2 | 2,973 | 9,294 | 4,405 | 96,852 | 3.98 | 4.04 |
142-
| v25.2.0 | 101.7 | 2,725 | 15,114 | 3,346 | 74,766 | 3.98 | 4.04 |
143-
144-
#### No Auto Vacuum
145-
146-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
147-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
148-
| v18.20.8 | 97.3 | 11,197 | 14,635 | 19,182 | 116,768 | 4.12 | 7.41 |
149-
| v20.19.5 | 109.6 | 10,296 | 14,288 | 18,437 | 89,366 | 4.12 | 7.41 |
150-
| v22.21.1 | 94.7 | 11,001 | 17,000 | 19,486 | 112,613 | 4.12 | 7.41 |
151-
| v24.11.1 | 100.2 | 9,981 | 16,660 | 19,736 | 113,340 | 4.12 | 7.41 |
152-
| v25.2.0 | 99.3 | 9,757 | 14,620 | 17,738 | 78,162 | 4.12 | 7.41 |
153-
154-
#### Incremental Vacuum
155-
156-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
157-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
158-
| v18.20.8 | 98.0 | 10,516 | 11,737 | 19,863 | 115,660 | 4.13 | 7.41 |
159-
| v20.19.5 | 97.3 | 9,692 | 13,681 | 18,502 | 89,358 | 4.13 | 7.41 |
160-
| v22.21.1 | 97.5 | 10,690 | 13,274 | 19,033 | 91,988 | 4.13 | 7.41 |
161-
| v24.11.1 | 96.9 | 10,628 | 16,821 | 19,934 | 117,509 | 4.13 | 7.41 |
162-
| v25.2.0 | 100.6 | 9,695 | 13,826 | 17,858 | 86,573 | 4.13 | 7.41 |
101+
The benchmark suite includes a comparison between `better-sqlite3-multiple-ciphers` (with ChaCha20 encryption) and standard `better-sqlite3` (no encryption) to quantify the performance impact of encryption.
163102

164-
#### WAL Autocheckpoint 1000
165-
166-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
167-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
168-
| v18.20.8 | 97.9 | 10,878 | 14,753 | 19,721 | 102,375 | 3.98 | 4.04 |
169-
| v20.19.5 | 99.1 | 9,543 | 13,938 | 18,233 | 87,943 | 3.98 | 4.04 |
170-
| v22.21.1 | 96.6 | 11,008 | 15,630 | 19,202 | 99,039 | 3.98 | 4.04 |
171-
| v24.11.1 | 118.2 | 10,511 | 14,410 | 19,432 | 107,550 | 3.98 | 4.04 |
172-
| v25.2.0 | 99.7 | 9,608 | 14,918 | 18,115 | 83,598 | 3.98 | 4.04 |
173-
174-
#### Cache Size 64MB
175-
176-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
177-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
178-
| v18.20.8 | 95.4 | 10,887 | 14,862 | 19,226 | 102,575 | 3.98 | 4.04 |
179-
| v20.19.5 | 99.1 | 7,168 | 14,286 | 18,386 | 87,704 | 3.98 | 4.04 |
180-
| v22.21.1 | 103.1 | 10,389 | 16,285 | 20,021 | 100,644 | 3.98 | 4.04 |
181-
| v24.11.1 | 106.8 | 9,385 | 10,502 | 15,585 | 79,170 | 3.98 | 4.04 |
182-
| v25.2.0 | 97.8 | 9,848 | 15,344 | 18,032 | 87,176 | 3.98 | 4.04 |
183-
184-
#### MMAP 256MB
185-
186-
| Node Version | Setup (ms) | Insert/sec | Select/sec | Update/sec | Delete/sec | DB Size (MB) | WAL Size (MB) |
187-
|--------------|------------|------------|------------|------------|------------|--------------|---------------|
188-
| v18.20.8 | 95.0 | 11,214 | 13,718 | 20,095 | 116,144 | 3.98 | 4.04 |
189-
| v20.19.5 | 97.8 | 9,781 | 14,213 | 18,183 | 87,176 | 3.98 | 4.04 |
190-
| v22.21.1 | 95.9 | 10,920 | 17,413 | 20,731 | 119,531 | 3.98 | 4.04 |
191-
| v24.11.1 | 107.2 | 9,419 | 13,363 | 19,434 | 94,153 | 3.98 | 4.04 |
192-
| v25.2.0 | 98.6 | 9,620 | 15,633 | 18,122 | 82,420 | 3.98 | 4.04 |
193-
194-
### Performance Comparison Summary
195-
196-
| Node Version | Platform | Arch | Timestamp |
197-
|--------------|----------|------|----------|
198-
| v18.20.8 | linux | x64 | 11/14/2025, 1:45:57 PM |
199-
| v20.19.5 | linux | x64 | 11/15/2025, 2:37:32 AM |
200-
| v22.21.1 | linux | x64 | 11/14/2025, 1:32:10 PM |
201-
| v24.11.1 | linux | x64 | 11/14/2025, 1:33:38 PM |
202-
| v25.2.0 | linux | x64 | 11/14/2025, 5:56:49 PM |
203-
204-
### Best Performers by Operation Type
205-
206-
**Insert Operations:** Synchronous OFF (Unsafe) on Node.js v18.20.8 (11,663 ops/sec)
207-
208-
**Select Operations:** MMAP 256MB on Node.js v22.21.1 (17,413 ops/sec)
209-
210-
**Update Operations:** MMAP 256MB on Node.js v22.21.1 (20,731 ops/sec)
103+
> [!NOTE]
104+
> The "better-sqlite3 (no encryption)" configuration uses the standard `better-sqlite3` package without encryption, while all other configurations use `better-sqlite3-multiple-ciphers` with ChaCha20 encryption.
211105
212-
**Delete Operations:** MMAP 256MB on Node.js v22.21.1 (119,531 ops/sec)
106+
**Typical Encryption Overhead (Node.js v20):**
107+
- **Insert Operations:** ~70-75% slower with ChaCha20 encryption
108+
- **Select Operations:** ~45-50% slower with ChaCha20 encryption
109+
- **Update Operations:** ~70-75% slower with ChaCha20 encryption
213110

111+
While encryption adds overhead, it provides quantum-resistant security for sensitive email data. The performance trade-off is acceptable for Forward Email's security requirements.
214112

113+
<!-- BENCHMARK_RESULTS_START -->
215114

216115
<!-- BENCHMARK_RESULTS_END -->
217116

@@ -396,6 +295,6 @@ This benchmark suite is part of Forward Email's open source contributions to the
396295

397296
---
398297

399-
**Created by:** Forward Email Team
400-
**License:** MIT
298+
**Created by:** Forward Email Team
299+
**License:** MIT
401300
**Node.js Support:** v18+ (v20 LTS recommended)

benchmark.js

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* Based on Forward Email's actual implementation
66
*/
77

8-
const Database = require('better-sqlite3-multiple-ciphers');
8+
const DatabaseMultipleCiphers = require('better-sqlite3-multiple-ciphers');
9+
const DatabaseStandard = require('better-sqlite3');
910
const Benchmark = require('benchmark');
1011
const Table = require('cli-table3');
1112
const fs = require('fs-extra');
@@ -35,7 +36,8 @@ const TEST_CONFIGS = {
3536
'Incremental Vacuum': { ...FORWARD_EMAIL_CONFIG, auto_vacuum: 'INCREMENTAL' },
3637
'WAL Autocheckpoint 1000': { ...FORWARD_EMAIL_CONFIG, wal_autocheckpoint: 1000 },
3738
'Cache Size 64MB': { ...FORWARD_EMAIL_CONFIG, cache_size: -64000 },
38-
'MMAP 256MB': { ...FORWARD_EMAIL_CONFIG, mmap_size: 268435456 }
39+
'MMAP 256MB': { ...FORWARD_EMAIL_CONFIG, mmap_size: 268435456 },
40+
'better-sqlite3 (no encryption)': { ...FORWARD_EMAIL_CONFIG, cipher: null, useStandard: true }
3941
};
4042

4143
class SQLiteBenchmark {
@@ -62,12 +64,14 @@ class SQLiteBenchmark {
6264
// Mimic Forward Email's setup-pragma.js implementation
6365
if (!db.open) throw new TypeError('Database is not open');
6466

65-
// Encryption setup
66-
db.pragma(`cipher='${config.cipher || 'chacha20'}'`);
67-
if (typeof db.key === 'function') {
68-
db.key(Buffer.from(testPassword));
69-
} else {
70-
db.pragma(`key="${testPassword}"`);
67+
// Encryption setup (skip for standard better-sqlite3)
68+
if (config.cipher && !config.useStandard) {
69+
db.pragma(`cipher='${config.cipher}'`);
70+
if (typeof db.key === 'function') {
71+
db.key(Buffer.from(testPassword));
72+
} else {
73+
db.pragma(`key="${testPassword}"`);
74+
}
7175
}
7276

7377
// Core PRAGMA settings
@@ -213,16 +217,19 @@ class SQLiteBenchmark {
213217
delete_ops_per_sec: 0,
214218
vacuum_time: 0,
215219
db_size_mb: 0,
216-
wal_size_mb: 0
220+
wal_size_mb: 0,
221+
library: config.useStandard ? 'better-sqlite3' : 'better-sqlite3-multiple-ciphers'
217222
};
218223

219224
try {
220225
// Setup timing
221226
const setupStart = process.hrtime.bigint();
222227

223-
const db = new Database(dbPath, {
224-
cipher: config.cipher || 'chacha20'
225-
});
228+
// Choose the appropriate database constructor
229+
const Database = config.useStandard ? DatabaseStandard : DatabaseMultipleCiphers;
230+
231+
const dbOptions = config.useStandard ? {} : { cipher: config.cipher || 'chacha20' };
232+
const db = new Database(dbPath, dbOptions);
226233

227234
this.setupPragma(db, config);
228235
this.createTestData(db);
@@ -291,10 +298,10 @@ class SQLiteBenchmark {
291298

292299
async benchmarkSelects(db, results) {
293300
const selectStmt = db.prepare(`
294-
SELECT id, subject, from_addr, date
295-
FROM messages
296-
WHERE mailbox_id = ? AND date > ?
297-
ORDER BY date DESC
301+
SELECT id, subject, from_addr, date
302+
FROM messages
303+
WHERE mailbox_id = ? AND date > ?
304+
ORDER BY date DESC
298305
LIMIT 50
299306
`);
300307

@@ -312,8 +319,8 @@ class SQLiteBenchmark {
312319

313320
async benchmarkUpdates(db, results) {
314321
const updateStmt = db.prepare(`
315-
UPDATE messages
316-
SET flags = ?
322+
UPDATE messages
323+
SET flags = ?
317324
WHERE id = ?
318325
`);
319326

@@ -367,35 +374,36 @@ class SQLiteBenchmark {
367374
const table = new Table({
368375
head: [
369376
'Configuration',
377+
'Library',
370378
'Setup (ms)',
371379
'Insert/sec',
372-
'Select/sec',
380+
'Select/sec',
373381
'Update/sec',
374382
'Delete/sec',
375-
'DB Size (MB)',
376-
'WAL Size (MB)'
383+
'DB Size (MB)'
377384
],
378-
colWidths: [25, 12, 12, 12, 12, 12, 12, 12]
385+
colWidths: [30, 20, 12, 12, 12, 12, 12, 12]
379386
});
380387

381388
Object.values(this.results).forEach(result => {
382389
if (result.error) {
383390
table.push([
384391
result.configName,
392+
result.library || 'N/A',
385393
'ERROR',
386394
result.error.substring(0, 40) + '...',
387-
'', '', '', '', ''
395+
'', '', '', ''
388396
]);
389397
} else {
390398
table.push([
391399
result.configName,
400+
result.library || 'multiple-ciphers',
392401
result.setup_time.toFixed(1),
393402
result.insert_ops_per_sec.toLocaleString(),
394403
result.select_ops_per_sec.toLocaleString(),
395404
result.update_ops_per_sec.toLocaleString(),
396405
result.delete_ops_per_sec.toLocaleString(),
397-
result.db_size_mb,
398-
result.wal_size_mb || '0'
406+
result.db_size_mb
399407
]);
400408
}
401409
});
@@ -430,6 +438,19 @@ class SQLiteBenchmark {
430438
console.log(` Update: ${forwardEmailResult.update_ops_per_sec.toLocaleString()} ops/sec`);
431439
console.log(` Database Size: ${forwardEmailResult.db_size_mb} MB`);
432440
}
441+
442+
// better-sqlite3 comparison
443+
const standardResult = this.results['better-sqlite3 (no encryption)'];
444+
if (standardResult && !standardResult.error && forwardEmailResult && !forwardEmailResult.error) {
445+
console.log(`\n🔐 Encryption Overhead Analysis:`);
446+
const insertOverhead = ((forwardEmailResult.insert_ops_per_sec / standardResult.insert_ops_per_sec - 1) * 100).toFixed(1);
447+
const selectOverhead = ((forwardEmailResult.select_ops_per_sec / standardResult.select_ops_per_sec - 1) * 100).toFixed(1);
448+
const updateOverhead = ((forwardEmailResult.update_ops_per_sec / standardResult.update_ops_per_sec - 1) * 100).toFixed(1);
449+
450+
console.log(` Insert: ${insertOverhead}% (ChaCha20 vs no encryption)`);
451+
console.log(` Select: ${selectOverhead}% (ChaCha20 vs no encryption)`);
452+
console.log(` Update: ${updateOverhead}% (ChaCha20 vs no encryption)`);
453+
}
433454
}
434455

435456
async saveResults() {

0 commit comments

Comments
 (0)