← Back to Blogs
HN Story

SQLite Performance on Budget VPS: Real-World Workload Benchmarks

May 10, 2026

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. While NORMAL is not strictly ACID durable (a power loss could lose the most recent transactions in the WAL), it prevents database corruption and is significantly faster than FULL. For applications where absolute durability is required (e.g., payment processing), FULL remains the necessary choice.
  • page_size = 8192 and cache_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 43x 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.

References

HN Stories