GoatDB Benchmarks
GoatDB’s benchmarks provide a performance comparison between GoatDB’s different operational modes and SQLite. These benchmarks help understand the performance characteristics and tradeoffs of each approach.
We’re comparing GoatDB to SQLite because SQLite is widely considered the gold standard for embedded databases, offering an excellent balance of features, performance, and reliability.
SQLite is built using classic RDBMS techniques and especially shines when complex queries on larger-than-memory datasets are needed. However, when implementing synchronization mechanisms and security controls for offline writes, GoatDB is able to offer competitive performance.
The most basic trade-off between the two systems is that GoatDB first loads the entire dataset into memory before any operations can be performed. This comes with its own unique set of trade-offs. It’s important to note that GoatDB isn’t trying to replace SQLite, but rather carve its own niche for specific use cases where in-memory operations, offline capabilities, and cryptographic security are prioritized over handling larger-than-memory datasets.
Replicating these benchmarks on your machine is easy. Just clone the GoatDB repository and run:
deno task bench
All benchmarks were performed on the following configuration:
- CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
- Runtime: Deno 2.2.3 (x86_64-apple-darwin)
Summary
First, let’s compare different operational modes that guarantee durability. Note that GoatDB’s default mode cryptographically signs each commit (write) for security, while trusted mode skips these security controls for better performance:
Benchmark | GoatDB (Default) | GoatDB (Trusted) | SQLite |
---|---|---|---|
Create instance | 5.1 ms | 4.6 ms | 203.2 µs |
Open repository (empty) | 1.4 ms | 1.5 ms | 1.1 ms |
Open repository (100k items) | 738.4 ms | 721.5 ms | 186.3 µs |
Create single item | 3.0 ms | 2.0 ms | 815.6 µs |
Read item by path | 2.0 µs | 2.5 µs | 67.0 µs |
Update item | 1.8 ms | 327.5 µs | 780.6 µs |
Bulk create 100 items | 98.1 ms | 67.1 ms | 1.5 ms |
Bulk read 100 items | 421.8 µs | 468.5 µs | 1.7 ms |
Simple query | 264.1 µs | 324.2 µs | 149.0 µs |
Complex query with sort | 144.1 µs | 207.2 µs | 97.1 µs |
Repository operations: count | 4.7 µs | 4.6 µs | 64.7 µs |
Repository operations: keys | 7.9 µs | 7.9 µs | 81.6 µs |
When relaxed durability is acceptable, both GoatDB and SQLite can skip the fsync
call to achieve much faster performance. Note that with SQLite, you risk database corruption if the system crashes during a write, while GoatDB will simply discard any incomplete writes.
Benchmark | GoatDB (Fast) | SQLite (synchronous = OFF) |
---|---|---|
Create instance | 5.5 ms | 160.3 µs |
Open repository (empty) | 1.4 ms | 507.4 µs |
Open repository (100k items) | 717.1 ms | 196.3 µs |
Create single item | 91.3 µs | 701.4 µs |
Read item by path | 2.6 µs | 63.9 µs |
Update item | 24.9 µs | 521.8 µs |
Bulk create 100 items | 11.5 ms | 900.0 µs |
Bulk read 100 items | 43.9 µs | 1.8 ms |
Simple query | 293.7 µs | 116.1 µs |
Complex query with sort | 159.1 µs | 92.1 µs |
Repository operations: count | 3.9 µs | 58.5 µs |
Repository operations: keys | 7.1 µs | 67.4 µs |
GoatDB is a memory-first database built on an append-only distributed commit graph stored as a log of commits on disk. This design currently requires the entire commit graph to be loaded into memory before any operations can be performed.
Opening a repository takes time proportional to the number of commits it contains. While the benchmark shows GoatDB is significantly slower than SQLite when opening a repository with 100k items (721ms vs 186µs), it’s important to note that if we actually read the entire table’s contents into memory, GoatDB is only about 5x slower than SQLite’s SELECT *
operation (721ms vs 136ms).
The performance breakdown of repository opening reveals that bringing the raw log data into memory is the least time-consuming part (approximately 10% of the total time). The majority is spent on deserializing and constructing the in-memory representation of the commit graph—a particularly challenging workload for modern JavaScript garbage collectors.
To address this performance bottleneck, we are developing a zero-copy format that will significantly reduce this overhead and bring opening times much closer to SQLite’s performance.
GoatDB uses a different scaling approach than traditional databases. Rather than growing a single large database, it employs application-level sharding with multiple medium-sized repositories that sync independently. Each user or data group has its own repository, enabling horizontal scaling and efficient client-server synchronization. This architecture provides natural scalability for multi-user applications without complex manual sharding.
SQLite shines in query performance with its decades of battle-tested optimizations, though GoatDB’s incremental queries perform competitively in real-world scenarios despite their simpler implementation. While GoatDB is written in TypeScript and SQLite in C, the benchmark differences primarily stem from fundamentally different architectures rather than language choice, as modern JavaScript runtimes are quite competitive on raw performance. GoatDB prioritizes distributed operation and offline-first capabilities, while SQLite’s B-tree implementation has been refined since 2000 for traditional database performance, reflecting their different design goals.
Default Mode
The benchmarks below show performance in Default mode, which includes all security and cryptographic controls. While these controls add some performance overhead, they enable critical features such as:
- Cryptographically verified data integrity
- Secure multi-user collaboration
- Clients act as active replicas automatically and securely restoring a crashed server
- Protection against unauthorized offline data modifications
- Replicated tamper-proof audit trail of all changes
Benchmark | Average | p75 | p99 | p995 |
---|---|---|---|---|
Create instance | 5.1 ms | 5.4 ms | 9.1 ms | 9.1 ms |
Open repository (empty) | 1.4 ms | 1.5 ms | 1.8 ms | 1.8 ms |
Open repository (100k items) | 738.4 ms | 773.3 ms | 809.0 ms | 809.0 ms |
Create single item | 3.0 ms | 3.2 ms | 3.2 ms | 3.2 ms |
Read item by path | 2.0 µs | 2.0 µs | 2.5 µs | 2.5 µs |
Update item | 1.8 ms | 1.9 ms | 2.2 ms | 2.2 ms |
Bulk create 100 items | 98.1 ms | 77.5 ms | 402.4 ms | 402.4 ms |
Bulk read 100 items | 421.8 µs | 443.9 µs | 487.5 µs | 487.5 µs |
Simple query | 264.1 µs | 264.1 µs | 1.3 ms | 1.3 ms |
Complex query with sort | 144.1 µs | 157.3 µs | 215.2 µs | 215.2 µs |
Repository operations: count | 4.7 µs | 4.8 µs | 7.6 µs | 7.6 µs |
Repository operations: keys | 7.9 µs | 8.2 µs | 9.2 µs | 9.2 µs |
Trusted Mode
Trusted mode bypasses cryptographic verification and security controls for improved performance. This mode is suitable for applications where security is handled at a different layer or in trusted environments, such as microservices running in the cloud without direct client interaction.
Benchmark | Average | p75 | p99 | p995 |
---|---|---|---|---|
Trusted: Create instance | 4.6 ms | 4.9 ms | 7.7 ms | 7.7 ms |
Trusted: Open repository (empty) | 1.5 ms | 1.5 ms | 4.2 ms | 4.2 ms |
Trusted: Open repository (100k items) | 721.5 ms | 712.1 ms | 837.5 ms | 837.5 ms |
Trusted: Create single item | 2.0 ms | 2.1 ms | 2.5 ms | 2.5 ms |
Trusted: Read item by path | 2.5 µs | 2.7 µs | 3.3 µs | 3.3 µs |
Trusted: Update item | 327.5 µs | 373.5 µs | 441.0 µs | 441.0 µs |
Trusted: Bulk create 100 items | 67.1 ms | 78.8 ms | 80.6 ms | 80.6 ms |
Trusted: Bulk read 100 items | 468.5 µs | 462.9 µs | 952.9 µs | 952.9 µs |
Trusted: Simple query | 324.2 µs | 333.3 µs | 382.0 µs | 382.0 µs |
Trusted: Complex query with sort | 207.2 µs | 222.7 µs | 242.8 µs | 242.8 µs |
Trusted: Repository operations: count | 4.6 µs | 4.5 µs | 7.1 µs | 7.1 µs |
Trusted: Repository operations: keys | 7.9 µs | 8.6 µs | 9.1 µs | 9.1 µs |
Fast Mode
Fast mode is similar to trusted mode, but with one key difference: the code doesn’t wait for updates to be persisted to local disk before acknowledging completion. Instead, GoatDB persists updates in the background, writing to both the local disk and remote server concurrently. This mode is particularly useful for caching applications in backend environments or for trusted systems where performance is the highest priority while still maintaining eventual durability. Fast mode is ideal when you need maximum throughput for high-volume operations while accepting a small risk of data loss in case of sudden system failure.
Even in Fast mode, GoatDB maintains data integrity through its append-only commit graph architecture. This design provides inherent resistance to corruption - in the event of a system crash during write operations, the database simply trims the log to the last valid commit point. Unlike traditional databases where crashes can lead to complex recovery scenarios or data corruption, GoatDB’s approach ensures that the database always remains in a consistent state. This structural safeguard works alongside the performance optimizations of Fast mode, providing both speed and reliability without compromising data integrity, at the expense of decreased durability.
Benchmark | Average | p75 | p99 | p995 |
---|---|---|---|---|
Fast: Create instance | 5.5 ms | 5.5 ms | 14.7 ms | 14.7 ms |
Fast: Open repository (empty) | 1.4 ms | 1.5 ms | 1.6 ms | 1.6 ms |
Fast: Open repository (100k items) | 717.1 ms | 716.1 ms | 816.3 ms | 816.3 ms |
Fast: Create single item | 91.3 µs | 95.7 µs | 132.3 µs | 132.3 µs |
Fast: Read item by path | 2.6 µs | 2.8 µs | 3.2 µs | 3.2 µs |
Fast: Update item | 24.9 µs | 24.7 µs | 57.9 µs | 57.9 µs |
Fast: Bulk create 100 items | 11.5 ms | 7.3 ms | 86.4 ms | 86.4 ms |
Fast: Bulk read 100 items | 43.9 µs | 44.3 µs | 78.2 µs | 78.2 µs |
Fast: Simple query | 293.7 µs | 314.5 µs | 414.0 µs | 414.0 µs |
Fast: Complex query with sort | 159.1 µs | 176.0 µs | 263.1 µs | 263.1 µs |
Fast: Repository operations: count | 3.9 µs | 4.1 µs | 4.7 µs | 4.7 µs |
Fast: Repository operations: keys | 7.1 µs | 7.3 µs | 8.1 µs | 8.1 µs |
SQLite Comparison
SQLite is currently the leading choice in embedded databases, known for its reliability and performance. While SQLite doesn’t provide any security controls nor synchronizes across devices, its benchmark is provided here for reference purposes.
Benchmark | Average | p75 | p99 | p995 |
---|---|---|---|---|
SQLite: Create instance | 203.2 µs | 195.5 µs | 396.0 µs | 506.9 µs |
SQLite: Create table | 1.1 ms | 1.1 ms | 2.0 ms | 2.2 ms |
SQLite: Open database (100k items) | 186.3 µs | 199.9 µs | 332.2 µs | 342.4 µs |
SQLite: Read 100k items | 138.7 ms | 139.7 ms | 153.3 ms | 153.3 ms |
SQLite: Create single item | 815.6 µs | 889.9 µs | 1.2 ms | 1.2 ms |
SQLite: Read item by ID | 67.0 µs | 71.6 µs | 121.7 µs | 126.0 µs |
SQLite: Update item | 780.6 µs | 891.5 µs | 1.2 ms | 1.3 ms |
SQLite: Bulk create 100 items | 1.5 ms | 1.5 ms | 2.3 ms | 3.0 ms |
SQLite: Bulk read 100 items | 1.7 ms | 1.7 ms | 2.3 ms | 2.4 ms |
SQLite: Simple query | 149.0 µs | 155.8 µs | 291.5 µs | 491.8 µs |
SQLite: Complex query with sort | 97.1 µs | 101.8 µs | 169.7 µs | 233.7 µs |
SQLite: Count operation | 64.7 µs | 65.1 µs | 131.7 µs | 366.9 µs |
SQLite: Keys operation | 81.6 µs | 84.9 µs | 197.0 µs | 211.6 µs |
synchronous = OFF
A somewhat similar comparison to GoatDB’s Fast mode would be with SQLite’s synchronous = OFF
setting. This setting disables the durability guarantees of SQLite, allowing the database to operate in a more performant mode, while risking a corruption of the database in case of sudden system failure.
Benchmark | Average | p75 | p99 | p995 |
---|---|---|---|---|
SQLite-fast-unsafe: Create instance | 160.3 µs | 171.9 µs | 277.5 µs | 313.4 µs |
SQLite-fast-unsafe: Create table | 507.4 µs | 526.2 µs | 871.4 µs | 982.0 µs |
SQLite-fast-unsafe: Open database (100k) | 196.3 µs | 214.5 µs | 355.5 µs | 390.1 µs |
SQLite-fast-unsafe: Read 100k items | 136.5 ms | 141.0 ms | 149.7 ms | 149.7 ms |
SQLite-fast-unsafe: Create single item | 701.4 µs | 877.2 µs | 1.4 ms | 1.4 ms |
SQLite-fast-unsafe: Read item by ID | 63.9 µs | 67.2 µs | 142.1 µs | 179.0 µs |
SQLite-fast-unsafe: Update item | 521.8 µs | 534.5 µs | 1.3 ms | 1.3 ms |
SQLite-fast-unsafe: Bulk create 100 items | 900.0 µs | 919.0 µs | 1.4 ms | 1.5 ms |
SQLite-fast-unsafe: Bulk read 100 items | 1.8 ms | 1.8 ms | 2.4 ms | 2.6 ms |
SQLite-fast-unsafe: Simple query | 116.1 µs | 124.1 µs | 176.4 µs | 184.3 µs |
SQLite-fast-unsafe: Complex query w/sort | 92.1 µs | 96.1 µs | 160.0 µs | 201.3 µs |
SQLite-fast-unsafe: Count operation | 58.5 µs | 63.8 µs | 117.6 µs | 122.7 µs |
SQLite-fast-unsafe: Keys operation | 67.4 µs | 71.5 µs | 127.3 µs | 138.7 µs |