Performance benchmarks
The interactive reports require JavaScript. All original reports are also available in the benchmark reference directory.
Understand the numbers
Methodology and reproduction
One indexed profile-store workload, measured across four database engines. Read each result alongside its hardware, runtime, durability settings, and engine version. Linux and Windows runs can use different query limits and versions.
Workload
Each engine stores the same profile records and maintains the same logical indexes:
- Individual profile inserts and updates.
- Primary-key reads and email lookups.
- Ordered index scans over country/status, creation time, and reputation.
- Indexed queries that also fetch matching profile records.
- Reads, lookups, scans, and queries after updates.
- Final settle or checkpoint, reopen, and persistence verification.
Every measured phase contributes a deterministic checksum. Skipped work or incorrect results cannot count as a successful comparison.
Profile data model
UserProfile(
UserId,
Email,
Country,
Status,
CreatedAtUnixMs,
LastLoginUnixMs,
Reputation,
DisplayName,
Bio) Engine layout
- ZoneTree
- Separate ZoneTree instances hold the primary records and application-managed secondary indexes.
- RocksDB
- Separate databases hold records and indexes. Separate writes, rather than a
WriteBatch, mirror the ZoneTree layout. - SQLite and MySQL
- A profile table uses native SQL indexes, maintained inside the database engine.
MySQL is a client/server baseline measured over TCP. Its server or container memory is excluded from the reported client-process memory.
Durability profile
These practical throughput-oriented configurations use different internal durability mechanisms. Check the selected report for its effective settings.
| Engine | Configuration |
|---|---|
| ZoneTree | Async compressed WAL, compressed disk segments, and application-managed secondary indexes. |
| RocksDB | WAL enabled, Zstd compression, and five separate databases. |
| SQLite | WAL journal mode, synchronous=NORMAL, and native SQL indexes. |
| MySQL | InnoDB. The benchmark Docker configuration disables binlog and uses innodb_flush_log_at_trx_commit=2 and sync_binlog=0. |
Reading the reports
- Completed phase time
- The sum of measured workload phases. Excludes initialization, stabilization, settle or checkpoint, reopen, verification, and report writing.
- Full run time
- The complete engine run, including work outside the measured phases.
- Stabilization
- ZoneTree and RocksDB settle or compact after inserts and updates, before read/query measurements. This reduces background-maintenance noise.
- Storage
- Disk usage after the engine settles or checkpoints its data.
- Process peak memory
- A high-water mark for the benchmark process. Includes embedded engines, but excludes the MySQL server. It is not steady-state memory usage.
- Scan vs. query throughput
- Index scans read index records only. Queries also fetch the matching profile records.
Reproduce the benchmark
Run from benchmarks/profile-store with the .NET SDK and sufficient disk space. Reference runs disable tiered compilation; first-use JIT work inside a measured phase still counts.
export DOTNET_TieredCompilation=0
dotnet run --project src/ProfileStore.Benchmark.csproj -c Release -- \
--engine zonetree,rocksdb,sqlite \
--profiles 100K,500K,1M,2M,5M,10M \
--parallelism 1,16 \
--output results --data data --update-latest \
--timeout-seconds 1200 In PowerShell, set $env:DOTNET_TieredCompilation = "0" and put the dotnet run command on one line, without the Bash continuation characters.
For MySQL, use the benchmark Docker setup or a separately configured server. The benchmark README covers Docker commands, prerequisites, runtime policy, and cleanup.
Tradeoffs and scope
This workload is useful for applications with live individual writes, ordered keys, range scans, secondary indexes, and profile-style values. Compare completed runs on the same documented machine.
Read throughput and storage together with memory. ZoneTree can cache decompressed disk blocks during reads and scans; cache lifetime controls when unused blocks are released. The .NET runtime can retain that memory for reuse after release.
Results depend on CPU, storage, operating system, runtime, compression, background activity, and query distribution. For different record sizes, write batching, transaction boundaries, durability requirements, or deployment models, rerun with your target workload and hardware.