# 10만 TPS와 10억 행 처리: SQLite의 놀라운 효율성

> Clean Markdown view of GeekNews topic #24792. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24792](https://news.hada.io/topic?id=24792)
- GeekNews Markdown: [https://news.hada.io/topic/24792.md](https://news.hada.io/topic/24792.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2025-12-03T10:28:34+09:00
- Updated: 2025-12-03T10:28:34+09:00
- Original source: [andersmurphy.com](https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html)
- Points: 27
- Comments: 2

## Summary

네트워크 지연이 성능의 발목을 잡는 시대에, **SQLite의 단일 작성자 구조**가 다시 주목받고 있습니다. 이번 실험은 ‘가벼운 내장형 DB’라는 기존 인식과 달리, 네트워크를 제거한 로컬 환경에서 SQLite가 **10만 TPS를 넘는 처리량**을 보여줄 수 있음을 입증했습니다. 핵심은 복잡한 분산 최적화가 아니라, **Amdahl의 법칙이 지적한 병목을 원천적으로 피하는 구조적 단순함**에 있습니다. 로컬 우선 아키텍처를 고민하는 개발자라면, 이 결과가 새로운 설계의 출발점이 될지도 모릅니다.

## Topic Body

- **SQLite의 단일 작성자 구조와 내장형 특성**이 오히려 확장성과 성능을 높이는 요인으로 작용함을 실험으로 입증  
- 동일한 조건에서 **Postgres는 네트워크 지연 시 348 TPS까지 하락**했지만, SQLite는 네트워크 제거로 **44,096 TPS**를 달성  
- 단일 작성자 모델을 활용한 **배치 처리와 SAVEPOINT 기반 세분화 트랜잭션**으로 최대 **186,157 TPS**, 안정적 구성 시 **102,545 TPS** 기록  
- **Amdahl의 법칙**이 네트워크 기반 데이터베이스의 병목을 설명하며, SQLite는 이를 회피함으로써 높은 효율을 유지  
- 이 결과는 **로컬 환경에서의 SQLite 활용 가능성**과 **네트워크 병목 제거의 중요성**을 강조함  

---

### SQLite의 구조와 실험 환경
- SQLite는 **MVCC가 없고 단일 작성자만 허용**하지만, 이러한 구조가 오히려 높은 확장성을 가능하게 함  
  - 내장형 데이터베이스로서 네트워크 오버헤드가 없음  
- 벤치마크는 **Apple M1 Pro, 16GB 메모리의 MacBook Pro(2021)** 환경에서 수행  
- 실험은 완벽한 최적화가 아닌, **일반적인 조건에서도 높은 쓰기 처리량을 달성할 수 있음을 보여주는 목적**으로 진행  

### TPS 정의와 트랜잭션 예시
- TPS는 단순한 쓰기 속도가 아니라 **상호작용형 트랜잭션(Interactive Transaction)** 을 의미  
  - 예: 계좌 간 송금 시 여러 쿼리와 애플리케이션 코드가 하나의 트랜잭션 내에서 실행  
- 트랜잭션은 오류 발생 시 상태를 롤백할 수 있어 **일관성 유지에 핵심적 역할**  

### 벤치마크 구성
- **Clojure 기반 가상 스레드(virtual threads)** 를 사용해 대규모 동시 요청을 시뮬레이션  
- Postgres는 **HikariCP 기반 커넥션 풀**로 구성, SQLite는 **단일 작성자와 코어 수만큼의 읽기 연결**을 사용  
- 두 데이터베이스 모두 **id, balance** 필드를 가진 단순한 account 테이블을 사용하며, **10억 개의 행**을 삽입  
- 사용자 활동은 **파워 법칙 분포(0.9995)** 를 따르며, 약 **10만 명의 활성 사용자**가 존재  

### 네트워크 데이터베이스(Postgres) 성능
- 동일 서버 내에서 Postgres는 **13,756 TPS** 달성  
- 네트워크 지연 5ms 추가 시 **1,214 TPS**, 10ms 시 **702 TPS**로 급감  
- 직렬화 격리 수준 적용 후 **660 TPS**, 추가 쿼리 포함 시 **348 TPS**로 하락  
- 이는 **Amdahl의 법칙**에 따라 네트워크 병목이 전체 성능을 제한함을 보여줌  
  - 네트워크 지연이 증가하면 트랜잭션 락 경쟁이 심화되어 확장 불가능  

### SQLite의 내장형 이점
- 네트워크 제거 후 SQLite는 **44,096 TPS** 달성  
  - 네트워크 병목이 사라지면서 Amdahl의 법칙의 영향을 최소화  
- 단일 작성자 구조를 활용해 **배치 처리(batch processing)** 적용 시 **186,157 TPS**까지 상승  
  - 동적 배치 크기 조정으로 **지연(latency)과 처리량(throughput)** 을 자동 최적화  

### SAVEPOINT를 통한 세분화 트랜잭션
- 배치 내 개별 트랜잭션 실패를 방지하기 위해 **SAVEPOINT를 사용한 중첩 트랜잭션** 적용  
  - 실패 시 해당 트랜잭션만 롤백, 전체 배치는 유지  
- 이 방식으로도 **121,922 TPS** 유지  

### 읽기/쓰기 혼합 부하 테스트
- 전체 요청의 75%를 읽기, 25%를 쓰기로 구성  
- 별도의 읽기 스레드 풀을 사용하여 **읽기 요청이 쓰기를 방해하지 않도록 분리**  
- 결과적으로 **102,545 TPS** 달성  

### 성능 비교 요약

| 조건 | Postgres | SQLite |
|------|-----------|---------|
| 네트워크 없음 | 13,756 | 44,096 |
| 5ms 지연 | 1,214 | n/a |
| 10ms 지연 | 702 | n/a |
| 10ms + 직렬화 | 660 | n/a |
| 배치 처리 | n/a | 186,157 |
| 배치 + SAVEPOINT | n/a | 121,922 |
| 배치 + SAVEPOINT + 읽기 | n/a | 102,545 |

### 결론
- SQLite는 **단일 작성자 모델과 내장형 구조** 덕분에 네트워크 기반 데이터베이스보다 훨씬 높은 TPS를 달성  
- **Amdahl의 법칙이 제시하는 네트워크 병목 한계**를 회피함으로써 효율성을 극대화  
- 전체 코드는 GitHub에서 공개되어 있으며, 관련 주제로 **Amdahl의 법칙, 파워 법칙, SQLite 확장 사례** 등의 자료가 함께 제시됨  
- SQLite는 **로컬 환경에서의 고성능 트랜잭션 처리**에 매우 효과적인 선택지임

## Comments



### Comment 47480

- Author: ppp123
- Created: 2025-12-10T09:10:02+09:00
- Points: 1

외부 서버를 안가고 로컬환경에서만 사용할거면, 네트워크란 세금을 낼 필요가 있냐는 거군. (VFS vs Socket)

### Comment 47108

- Author: neo
- Created: 2025-12-03T10:28:35+09:00
- Points: 2

###### [Hacker News 의견](https://news.ycombinator.com/item?id=46124205) 
- 나는 **SQLite 기반의 hybrid protobuf ORM/CRUD 서버**를 만들고 있음  
  코드와 설명은 [GitHub - accretional/collector](https://github.com/accretional/collector)에 있음  
  실시간 백업 시 **5~15ms 다운타임**, 수백 개의 읽기/쓰기 요청 큐잉, CRUD 전체 지연 1ms 수준, WAL 기반 **스트리밍 백업**까지 가능함  
  예전엔 Postgres와 Spanner만 썼는데, Collector에 파티션 기능만 추가되면 Postgres는 다시 안 쓸 것 같음
  - BTRFS 같은 **atomic snapshot** 파일시스템을 써서 SQLite + WAL로 **제로 다운타임 백업**을 하는 방법도 고려해봤는지 궁금함. 스냅샷 후 천천히 백업하고 삭제하면 됨  

- 단점은 모든 데이터와 연산이 **단일 머신**에 들어가야 한다는 점임  
  AWS의 u-24tb1.112xlarge 인스턴스(448 vcore, 24TB RAM, 64TB EBS)를 쓰면 꽤 여유가 있음
  - 하지만 **Hetzner의 베어메탈 서버**를 빌리면 코어당 성능이 2~3배, 비용은 90% 절감됨  
  - SQLite의 **이론적 최대 DB 크기**는 281TB라고 [공식 문서](https://sqlite.org/limits.html)에 명시되어 있음. 실제로는 파일시스템 한계가 더 작지만, 동작은 정상임  
  - 단일 머신 스케일업은 안정적이지만 **탄력성(elasticity)** 이 떨어짐. 트래픽 급증 시 과할당하거나 장애를 감수해야 함  
  - “데이터가 RAM에 들어가나?”를 묻는 [yourdatafitsinram.net](https://yourdatafitsinram.net/) 링크를 보면, 고성능 단일 노드라면 EC2보단 전용 서버가 낫다고 생각함  

- 글에서 SQLite의 효율성을 강조했지만, **비교 기준이 불명확**하다고 느낌  
  원래 분리된 서버 구조를 전제로 하다가, 로컬 임베디드 DB 성능을 측정했기 때문임  
  같은 조건이라면 로컬 Postgres 튜닝으로도 비슷한 성능을 낼 수 있음
  - SQLite는 같은 머신의 Postgres보다도 빠름. 실제 배포 환경에서의 구성을 기준으로 테스트하는 게 타당함  
  - SQLite를 **요청 핸들러 뒤에 감싸서** 다른 서버에서 돌릴 수도 있음. 결국 DB는 요청 처리기와 저장소의 조합일 뿐임  
  - 단일 박스에서 **원시 처리량(raw throughput)** 이 중요함. SQLite는 PG보다 10배 빠르고, PG는 트랜잭션 복잡도가 높을수록 느려짐  
  - “그럼 SQLite는 비교 대상이 아니다”라는 말은 너무 단순함. 글이 너무 짧아질 것임  
  - SQLite는 모바일이나 임베디드뿐 아니라 **저동시성 서버 앱**에도 적합함. 웹 서버만을 위한 DB는 아님  

- Postgres 연결 수를 8개로 제한한 건 **병목**일 수 있음  
  CPU와 스레드 사용량을 함께 공개하고, 더 큰 커넥션 풀로 재테스트하면 좋겠음
  - 커넥션 풀을 코어 수(8)에 맞춘 건 괜찮지만, 트랜잭션 내 **sleep**이 있으면 병목이 생김  
    64개 연결로 늘리면 처리량이 8배 늘 수도 있음. 한계에 도달할 때까지 **클라이언트 설정을 확장**해야 함  
  - 이 글의 수치는 믿기 어려움. 나는 네트워크 기반 MySQL에서도 훨씬 높은 TPS를 내고 있음  

- 핵심은 **네트워크 지연이 병목인지 인식하는 것**임  
  많은 워크로드에서 평범한 로컬 DB가 훌륭한 원격 DB보다 빠름  
  중요한 건 “어떤 DB가 최고냐”가 아니라 “**네트워크 경계를 넘을 필요가 있느냐**”임
  - (작성자) 맞음. SQLite vs Postgres 논쟁이 아니라, **네트워크 기반 DB의 한계**를 다루려 했음  
  - 물론 모든 걸 메모리에 두고 Redis나 Memcache를 쓰면 성능은 쉽게 높아짐. 하지만 그건 규칙이 달라지는 것임  

- **네트워크형 DB**는 앱 재배포가 쉬운 장점이 있음  
  새 인스턴스를 띄우고 기존 걸 종료하면 거의 **무중단 배포**가 가능함  
  SQLite를 같은 인스턴스에 두면 교체 시 DB를 다시 올려야 해서 더 복잡함. 실제 운영에서 이런 문제를 겪었는지 궁금함
  - SQLite를 프로덕션에서 쓰려면 **영구 스토리지와 NVMe**가 필요함. 보통은 베어메탈 단일 서버로 운영함  
    마이그레이션 시에는 다운타임이 생길 수 있음. **Litestream** 덕분에 이제는 복제와 백업이 쉬워졌음  
  - SQLite는 **멀티 프로세스 접근**을 지원하므로, 새 프로세스를 띄우고 기존 걸 종료하는 식의 무중단 교체도 가능함  

- 작성자가 `PRAGMA synchronous="normal"`로 설정했는데, 이는 **fsync를 매번 수행하지 않음**  
  공정한 비교를 위해 `"full"`로 설정해야 함
  - 하지만 WAL 모드에서는 `"normal"`도 괜찮음. 전원 손실 시 내구성은 잃지만 **트랜잭션의 일관성**은 유지됨  

- SQLite의 **HA(고가용성)** 구성은 어떻게 되는지 궁금함  
  최소한 자동 장애 조치가 가능한 수준은 되어야 함
  - SQLite는 C 라이브러리이므로, **rqlite**, **litestream**, **litefs** 같은 프로젝트로 확장 가능함  
    나는 현재 Postgres와 SQLite(litestream 포함) 중 고민 중임.  
    내 앱은 약간의 다운타임이 허용되므로, 단일 박스에서 수직 확장하는 게 더 단순하고 저렴함  
  - 최근엔 **Marmot**이라는 멀티마스터 프로젝트가 2년 만에 부활했음.  
    [Marmot GitHub](https://github.com/maxpert/marmot/)에 **gossip 기반 복제 메커니즘**이 새로 추가됨  

- 실제로 SQLite를 **프로덕션에서 한계까지 밀어붙인 사례**가 있는지 궁금함
  - [Expensify의 사례](https://use.expensify.com/blog/scaling-sqlite-to-4m-qps-on-a-single-server)가 가장 극단적인 예시임  

- 일반적인 웹앱이나 커머스 환경에서 SQLite vs Postgres의 **사용자 수 한계**가 어느 정도인지 궁금함  
  SQLite는 최근 업데이트로 **동시 읽기**는 가능하지만 **단일 쓰기**만 허용됨  
  어떤 경우에 이것이 문제가 되는지, 그리고 확장을 고려한다면 Postgres로 시작하는 게 나은지 의견을 묻고 싶음
