저가형 VPS에서의 SQLite 성능: 실제 워크로드 벤치마크
SQLite에서 PostgreSQL와 같은 클라이언트-서버 데이터베이스로 언제 마이그레이션해야 하는지에 대한 논쟁은 종종 "scale"에 집중됩니다. 하지만 scale은 성능 트레이드오프의 스펙트럼이라기보다는 이진 임계값으로 오해받는 경우가 많습니다. SQLite가 실제로 어디에서 한계에 부딪히는지 이해하려면, 합성 마이크로벤치마크를 넘어 작업 세트(working set)가 가용 시스템 메모리를 초과할 때 어떻게 작동하는지 살펴봐야 합니다.
최근 s13k가 저가형 Hetzner CX23 VPS($4.99/mo)에서 실시한 벤치마크는 SQLite의 역량을 실질적으로 보여줍니다. 3.7 GB RAM만 있는 머신에서 6 GB 데이터베이스를 테스트함으로써, 데이터 성장이 결국 하드웨어 리소스를 앞지르는 실제 상황을 시뮬레이션하여 엔진이 디스크에 접근하도록 강제했습니다.
테스트 환경
결과가 프로덕션 환경에 적용 가능하도록, 벤치마크는 다음과 같은 사양의 저사양 공유 리소스 머신에서 실행되었습니다:
- 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.
프로덕션 실무 설정
최대 속도를 위해 튜닝하는 대신, 벤치마크는 "프로덕션 실무" pragmas 세트를 사용했습니다. 이 설정은 성능과 데이터 안전성 사이의 균형을 맞춥니다:
journal_mode = WAL: Write-Ahead Logging은 읽기 작업이 쓰기 작업을 차단하지 않고 그 반대도 마찬가지로 작동하게 하여 더 나은 동시성을 가능하게 합니다.synchronous = NORMAL: 이는 중요한 트레이드오프입니다.NORMAL은 엄격한 ACID 내구성을 보장하지는 않지만(전력 손실 시 WAL의 가장 최근 트랜잭션이 손실될 수 있음), 데이터베이스 손상을 방지하며FULL보다 훨씬 빠릅니다. 절대적인 내구성이 필요한 애플리케이션(예: 결제 처리)의 경우FULL이 필수적인 선택입니다.page_size = 8192및cache_size = 256 MiB.mmap_size = 256 MiB.
성능 분석: 디스크 바운드 vs. 캐시됨
이 연구의 핵심은 "캐시된" 상태(데이터베이스가 RAM에 완전히 들어가는 경우)와 "디스크 바운드" 상태(데이터베이스가 RAM보다 큰 경우) 사이의 비교입니다.
데이터베이스가 RAM을 초과할 때
6 GB 데이터베이스의 경우, 모든 랜덤 읽기는 I/O 페널티를 감수해야 합니다. 혼합 OLTP 워크로드(70% Reads, 25% Updates, 5% Inserts)에 대한 결과는 3,915 ops/s의 처리량과 710 µs의 p99 지연 시간 및 2.2 ms의 p999 지연 시간을 보여주었습니다.
| 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 |
"엔진 천장"
작업 세트가 ~246 MB(1M rows)로 줄어들었을 때, 데이터베이스는 시스템 캐시 내에 안정적으로 들어갔습니다. 이는 디스크 I/O를 핫 패스(hot path)에서 제거함으로써 이 특정 하드웨어에서 SQLite 엔진의 최대 잠재력을 보여주었습니다.
- Random PK SELECTs: 3.6k/s에서 155.8k/s로 급증했습니다.
- Mixed OLTP: 3.9k/s에서 53.2k/s로 증가했습니다.
- Concurrent Reads: 10.3k/s에서 104k/s로 증가했습니다.
핵심 요약 및 "I/O 붕괴"
두 상태를 비교하면 냉혹한 현실이 드러납니다: 작업 세트가 캐시를 초과하는 순간 랜덤 읽기는 43배 급감합니다. 이는 SQLite 애플리케이션이 확장될 때 발생하는 주요 병목 현상입니다. 엔진 자체가 아니라, 기반이 되는 저장 매체입니다.
하지만 최악의 디스크 바운드 시나리오에서도 성능은 인상적입니다. ~3.9k ops/s의 처리량은 $5 VPS에서 대략 시간당 1,400만 건의 작업으로 변s합니다. Tail 지연 시간은 3 ms 미만으로 유지되며, SQLite는 대다수의 웹 애플리케이션에 매우 유력한 옵션으로 남습니다.
결론
대부분의 개발자에게 SQLite의 "scale" 한계는 일반적으로 생각하는 것보다 훨씬 더 멉니다. RAM에서 디스크로 이동할 때의 성능 저하가 상당하지만, 절대적인 성능 하한선은 가장 저렴한 하드웨어에서 프로덕션 로드를 처리하기에 충분합니다. 모든 쓰기 작업에 대해 엄격한 ACID 내구성을 요구하거나 대규모 쓰기 동시성을 요구하지 않는 한, SQLite는 종종 충분히 강력합니다.