13P by GN⁺ 20시간전 | ★ favorite | 댓글 1개
  • 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는 로컬 환경에서의 고성능 트랜잭션 처리에 매우 효과적인 선택지임
Hacker News 의견
  • 나는 SQLite 기반의 hybrid protobuf ORM/CRUD 서버를 만들고 있음
    코드와 설명은 GitHub - 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라고 공식 문서에 명시되어 있음. 실제로는 파일시스템 한계가 더 작지만, 동작은 정상임
    • 단일 머신 스케일업은 안정적이지만 탄력성(elasticity) 이 떨어짐. 트래픽 급증 시 과할당하거나 장애를 감수해야 함
    • “데이터가 RAM에 들어가나?”를 묻는 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 GitHubgossip 기반 복제 메커니즘이 새로 추가됨
  • 실제로 SQLite를 프로덕션에서 한계까지 밀어붙인 사례가 있는지 궁금함

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