SQLite Performance on Budget VPS: Real-World Workload Benchmarks
The debate over when to migrate from SQLite to a client-server database like PostgreSQL often centers on "scale." However, scale is frequently misunderstood as a binary threshold rather than a spectrum of performance trade-offs. To understand where SQLite actually hits its limits, we need to look beyond synthetic microbenchmarks and examine how it performs when the working set exceeds the available system memory.
Recent benchmarks conducted by s13k on a budget Hetzner CX23 VPS ($4.99/mo) provide a practical look at SQLite's capabilities. By testing a 6 GB database on a machine with only 3.7 GB of RAM, the tests force the engine to touch the disk, simulating a real-world scenario where data growth eventually outpaces hardware resources.
The Testing Environment
To ensure the results are applicable to production environments, the benchmarks were run on a low-end, shared-resource machine with the following specifications:
- Hardware: 2 vCPU Intel Xeon Skylake @ 2.1 GHz, 3.7 GiB RAM (no swap).
- OS: Debian 13 with an ext4 filesystem (
noatime,scheduler=none). - Software: SQLite 3.53.1, statically linked into a C benchmark tool.
Production-Realistic Configuration
Rather than tuning for maximum possible speed, the benchmarks used a set of "production-realistic" pragmas. This configuration balances performance with data safety:
journal_mode = WAL: Write-Ahead Logging allows readers to operate without blocking the writer and vice versa, enabling better concurrency.synchronous = NORMAL: This is a critical trade-off. WhileNORMALis not strictly ACID durable (a power loss could lose the most recent transactions in the WAL), it prevents database corruption and is significantly faster thanFULL. For applications where absolute durability is required (e.g., payment processing),FULLremains the necessary choice.page_size = 8192andcache_size = 256 MiB.mmap_size = 256 MiB.
Performance Analysis: Disk-Bound vs. Cached
The core of this study is the comparison between a "cached" state (where the database fits entirely in RAM) and a "disk-bound" state (where the database is larger than RAM).
When the Database Exceeds RAM
With a 6 GB database, every random read must pay the I/O penalty. The results for a mixed OLTP workload (70% Reads, 25% Updates, 5% Inserts) showed a throughput of 3,915 ops/s with a p99 latency of 710 µs and a p999 of 2.2 ms.
| Phase | Throughput | p50 | p95 | p99 | p999 |
|---|---|---|---|---|---|
| BULK INSERT (10M rows) | 61,300/s | 8 µs | 58 µs | 80 µs | 143 µs |
| SELECT random PK | 3,609/s | 265 µs | 476 µs | 715 µs | 2.3 ms |
| SELECT indexed range scan | 3,477/s | 290 µs | 599 µs | 895 µs | 2.9 ms |
| UPDATE per-row | 2,986/s | 257 µs | 473 µs | 706 µs | 2.2 ms |
| MIXED OLTP | 3,915/s | 257 µs | 455 µs | 710 µs | 2.2 ms |
The "Engine Ceiling"
When the working set was reduced to ~246 MB (1M rows), the database fit comfortably in the system cache. This revealed the maximum potential of the SQLite engine on this specific hardware, removing disk I/O from the hot path.
- Random PK SELECTs: Jumped from 3.6k/s to 155.8k/s.
- Mixed OLTP: Increased from 3.9k/s to 53.2k/s.
- Concurrent Reads: Increased from 10.3k/s to 104k/s.
Key Takeaways and the "I/O Collapse"
Comparing the two states reveals a stark reality: random reads collapse by 43 x the moment the working set outgrows the cache. This is the primary bottleneck for SQLite applications as they scale: not the engine itself, but the underlying storage medium.
However, even in the worst-case disk-bound scenario, the performance remains impressive. A throughput of ~3.9k ops/s translates to roughly 14 million operations per hour on a $5 VPS. With tail latencies staying under 3 ms, SQLite remains a highly viable option for the vast majority of web applications.
Conclusion
For most developers, the "scale" limit of SQLite is much further than commonly assumed. While the performance drop-off when moving from RAM to disk is significant, the absolute performance floor is still sufficient to handle production loads on the cheapest available hardware. Unless your application requires strict ACID durability for every single write or massive write concurrency, SQLite is often more than enough.