본문으로 건너뛰기
피드

SQLite를 프로덕션에서 쓰려면 WAL, 락, VFS를 제대로 알아야 한다

backend 약 10분
vote
0
댓글
북마크

이 글은 SQLite를 단순한 로컬 임베디드 DB가 아니라 저지연 서버용 데이터베이스로 쓰는 방법을 정리한다. WAL 모드, busy timeout, BEGIN IMMEDIATE, cache_size, mmap_size, VFS 기반 복제까지 실전 설정이 꽤 구체적으로 나온다. 다만 분산 쓰기나 테라바이트급 데이터셋에는 여전히 PostgreSQL 같은 서버형 DB가 맞다는 선도 분명히 긋는다.

  • 1

    WAL 모드를 켜면 읽기와 쓰기가 서로 막히는 문제를 크게 줄일 수 있다

  • 2

    SQLite는 여전히 단일 writer 모델이라 SQLITE_BUSY를 busy_timeout과 BEGIN IMMEDIATE로 다뤄야 한다

  • 3

    cache_size와 mmap_size 설정으로 읽기 성능을 끌어올릴 수 있다

  • 4

    클라우드의 휘발성 로컬 디스크에서는 Litestream, LiteFS 같은 VFS·복제 도구가 사실상 필수다

SQLite는 로컬 장난감이 아니라, 조건 맞으면 꽤 진지한 서버 DB가 됨

  • 글의 출발점은 단순함: 요즘 하드웨어에서는 DB 네트워크 왕복이 병목이 될 수 있음

    • NVMe SSD와 빠른 로컬 스토리지가 흔해지면서, 같은 서버 프로세스 안에서 SQLite를 직접 쓰면 네트워크 latency를 통째로 없앨 수 있음
    • 읽기는 메모리 매핑된 파일 접근에 가까워져서 sub-millisecond 쿼리도 가능하다는 주장임
  • 하지만 SQLite를 프로덕션에 그냥 던져 넣으면 안 됨

    • 기본 설정은 호환성과 안전성을 넓게 잡은 쪽이지, 고동시성 앱 서버에 맞춘 설정이 아님
    • WAL, 체크포인트, 락, 캐시, VFS 같은 내부 동작을 알고 조정해야 함

WAL 모드는 사실상 첫 번째 관문임

  • 기본 rollback journal 방식은 쓰기 작업 중 읽기와 쓰기가 서로 막히기 쉬움

    • 쓰기 전에 원본 페이지를 rollback journal 파일에 복사하고, 성공하면 삭제하고, 실패하면 복구하는 방식임
    • 문제는 write가 read를 막고 read가 write를 막는 동시성 구조임
  • 프로덕션 서버에서 SQLite를 쓰려면 WAL 모드를 켜는 게 핵심임

    • 설정은 PRAGMA journal_mode = WAL; 한 줄임
    • WAL 모드에서는 본 DB 파일을 바로 고치지 않고 .sqlite-wal 파일에 새 트랜잭션을 append함
    • reader는 main DB와 WAL의 필요한 페이지를 읽고, writer는 WAL 끝에 쓰기 때문에 읽기와 쓰기가 동시에 가능해짐
  • WAL은 공짜가 아니라 checkpoint 관리가 따라옴

    • WAL 파일이 계속 커지면 읽기 때 WAL index를 더 많이 봐야 해서 느려질 수 있음
    • SQLite는 WAL 내용을 main DB로 합치는 checkpoint를 주기적으로 해야 함
  • checkpoint 모드는 운영 지연 시간에 직접 영향을 줌

    • PASSIVE는 reader와 writer를 막지 않고 가능한 만큼만 병합함
    • FULL은 새 write를 막고 기존 read가 끝나길 기다린 뒤 전체 병합을 시도함
    • RESTART는 FULL과 비슷하지만 WAL을 다시 처음부터 쓰게 만듦
    • TRUNCATE는 RESTART에 더해 디스크 파일 크기까지 0바이트로 줄임

💡

> write가 많은 서버에서 자동 checkpoint만 믿으면, 항상 active reader가 있는 상황에서 WAL 파일이 계속 커질 수 있음. 백그라운드 작업으로 PRAGMA wal_checkpoint(PASSIVE);나 RESTART 계열을 명시적으로 돌리는 설계가 낫다.

SQLITE_BUSY는 버그가 아니라 설계 제약임

  • WAL을 켜도 SQLite는 writer가 하나뿐임

    • 여러 reader와 하나의 writer는 괜찮지만, 동시에 두 writer가 쓰는 건 안 됨
    • 두 번째 writer가 들어오면 SQLITE_BUSY가 날 수 있음
  • 그래서 busy_timeout은 프로덕션 기본값처럼 봐야 함

    • 예시는 PRAGMA busy_timeout = 5000;로 5초 동안 락 획득을 재시도하게 하는 설정임
    • SQLite가 내부적으로 sleep과 retry를 하면서 피크 시간대의 애플리케이션 레벨 오류를 줄여줌
  • 쓰기가 포함된 트랜잭션은 BEGIN IMMEDIATE로 시작하는 편이 안전함

    • 기본 DEFERRED 트랜잭션은 처음엔 락을 안 잡고 읽기처럼 시작했다가, 실제 write 순간에 락을 올리려 함
    • 두 연결이 둘 다 읽고 있다가 나중에 동시에 쓰려고 하면 경합이 꼬일 수 있음
    • BEGIN IMMEDIATE는 시작할 때 reserved lock을 잡아서, 다른 write 트랜잭션과의 충돌을 초기에 정리함

캐시와 mmap은 읽기 성능에 바로 꽂히는 설정임

  • SQLite 기본 cache size는 보통 서버 워킹셋 기준으로 작음

    • 글에서는 PRAGMA cache_size = -64000; 예시를 들며 약 64MB 캐시를 할당함
    • 음수 값은 페이지 수가 아니라 KiB 기준 크기를 뜻함
  • mmap을 켜면 DB 파일을 애플리케이션 가상 주소 공간에 매핑할 수 있음

    • 예시는 PRAGMA mmap_size = 2147483648;로 최대 2GB까지 매핑하는 설정임
    • DB가 이보다 작으면 전체 DB가 메모리에 매핑될 수 있고, 읽기가 포인터 접근에 가까워짐
    • OS 커널의 페이지 캐시를 활용하므로 user-space 복사 비용도 줄어듦

클라우드에서 SQLite를 쓰려면 VFS와 복제를 같이 봐야 함

  • SQLite의 VFS는 파일 작업을 갈아끼울 수 있는 강력한 추상화임

    • SQLite는 OS 파일 시스템에 직접 쓰는 대신 open, read, write, sync 같은 작업을 VFS 모듈에 맡김
    • 이 구조 덕분에 저장 위치나 복제 방식을 바꾸는 도구들이 나올 수 있음
  • Litestream은 WAL 프레임을 object storage로 계속 흘려보내는 쪽에 가까움

    • 별도 프로세스로 돌면서 AWS S3 같은 저장소에 incremental WAL frame을 주기적으로 업로드함
    • 목적은 거의 0에 가까운 오버헤드로 point-in-time recovery를 확보하는 것임
  • LiteFS는 FUSE 기반 VFS로 SQLite DB를 여러 노드에 복제하는 방식임

    • 파일 시스템 레벨에서 write를 가로채고 read replica로 트랜잭션을 실시간 복제함
    • Fly.io 같은 환경에서 globally distributed SQLite 배포를 가능하게 하는 쪽임

⚠️주의

> AWS ECS, Kubernetes, Fly.io처럼 로컬 디스크가 사라질 수 있는 환경에서 SQLite만 덜렁 쓰면 내구성이 깨질 수 있음. 이런 환경에서는 VFS 기반 복제나 WAL 백업 전략이 선택이 아니라 운영 조건에 가까움.

글에서 제안하는 기본 설정 묶음

  • 앱 부트스트랩에서 커넥션을 열자마자 PRAGMA를 일관되게 적용하라는 게 핵심임

    • journal_mode = WAL로 읽기·쓰기 동시성 확보
    • synchronous = NORMAL로 WAL 환경에서 commit마다 디스크 sync하는 부담 완화
    • busy_timeout = 5000으로 write lock 경합 흡수
    • cache_size = -64000으로 약 64MB 캐시 할당
    • mmap_size = 1073741824로 최대 1GB memory-mapped I/O 활성화
    • foreign_keys = ON으로 외래 키 제약 적용
    • journal_size_limit = 67108864로 WAL 파일을 64MB 수준에서 제한
    • auto_vacuum = INCREMENTAL로 페이지 회수와 쿼리 계획 관리에 도움
  • 결론은 꽤 현실적임

    • read-heavy 서비스, 수백 GB 안쪽 데이터셋, 단일 서버나 단일 tenant edge 배포라면 SQLite가 운영 단순성과 저지연 면에서 매력적일 수 있음
    • 반대로 여러 지역에서 복잡한 분산 write 트랜잭션이 필요하거나, 데이터가 수 TB를 넘으면 PostgreSQL 같은 서버형 DB가 맞음

기술 맥락

  • SQLite를 고르는 이유는 단순히 가볍기 때문이 아니라 네트워크 왕복을 없앨 수 있기 때문이에요. 앱 프로세스와 DB가 같은 머신에 있으면 쿼리 하나마다 DB 서버까지 갔다 오는 비용이 사라지거든요.

  • WAL을 켜는 이유는 읽기와 쓰기의 충돌을 줄이기 위해서예요. rollback journal은 안전하지만 동시성에서 손해를 보고, WAL은 writer가 로그에 append하는 동안 reader가 기존 DB 스냅샷을 계속 볼 수 있게 해줘요.

  • 그래도 SQLite는 단일 writer라는 제약이 남아요. 그래서 busy_timeout과 BEGIN IMMEDIATE가 중요한데, 이 둘은 락 경합을 없애는 마법이 아니라 경합을 예측 가능한 실패나 대기로 바꿔주는 장치예요.

  • mmap과 cache_size는 읽기 경로를 줄이려는 설정이에요. 워킹셋이 메모리에 들어오는 서비스라면 디스크 I/O를 줄이고 OS 페이지 캐시를 잘 활용하는 쪽이 지연 시간에 바로 영향을 줘요.

  • 클라우드 배포에서는 내구성이 별도 문제예요. 로컬 디스크가 휘발적이면 SQLite 파일 하나가 빠른 건 장점이지만, 장애 때 복구할 복제본이나 WAL 백업이 없으면 운영 DB로는 위험하거든요.

SQLite 프로덕션 논쟁은 '쓸 수 있냐 없냐'보다 '쓰기 패턴과 운영 모델이 맞냐'가 핵심이다. 읽기 많고 단일 서버·엣지 배포에 가까운 서비스라면 네트워크 왕복을 없애는 이득이 꽤 크지만, 락과 내구성 설정을 대충 두면 바로 발목 잡힌다.

댓글

댓글

댓글을 불러오는 중...

backend

데카트론 독일, 웹 결제에 유럽식 계좌이체 ‘웨로’ 붙였다

데카트론 독일이 자사 웹사이트 결제 수단에 유럽 결제 이니셔티브의 웨로를 도입했다. 카드 네트워크를 거치지 않는 실시간 계좌 간 결제로 수수료를 낮추고, 멤버십과 결제를 묶어 고객 유지까지 노리는 움직임이다.

backend

SMTP를 HTTP 위에서 다시 만든다면? 기존 부품만 조립한 현대식 이메일 실험

글쓴이는 SMTP를 대체하려는 게 아니라, HTTP·WebFinger·Ed25519·HPKE·JMAP 같은 이미 검증된 기술로 이메일의 문제를 다시 설계해보는 HMTP 실험을 소개해. 핵심은 주소 형태만 user@domain으로 유지하고, 전송·발견·서명·암호화·스팸 방어·읽기 모델을 전부 현대 웹 인프라 위에 얹는 거야.

backend

버셀 실험실의 Scriptc, TypeScript를 Node 없이 네이티브 바이너리로 컴파일

Vercel Labs가 공개한 Scriptc는 일반 TypeScript 코드를 Node, V8, JavaScript 엔진 없이 작은 네이티브 실행 파일로 컴파일하는 실험적 컴파일러다. 정적으로 컴파일 가능한 코드와 동적 실행이 필요한 코드를 명확히 나누고, Node와 결과가 바이트 단위로 맞는지 800개 이상 테스트로 검증한다.

backend

고의 새 가비지 컬렉터 ‘그린 티’, 힙을 훑는 방식이 어떻게 달라졌나

고 1.25에서 도입되고 1.26에서 기본값이 된 새 가비지 컬렉터 ‘그린 티’를 실제 힙 배치와 성능 측정으로 뜯어본 글이다. 핵심은 포인터를 무작정 따라가는 대신 메모리 span 단위로 훑어 캐시 친화성을 높였다는 점이고, 동시에 고의 비이동식 컬렉터가 여전히 sparse page 회수에 약하다는 한계도 같이 보여준다.

backend

SIMD는 고수 전용 흑마법이 아니라, 모든 개발자가 알아야 할 반복문 최적화임

미첼 하시모토가 SIMD를 ‘복잡한 고성능 영역’으로만 볼 필요가 없다고 설명한 글이다. Ghostty의 실제 Zig 코드를 예로 들며, 큰 배열이나 문자열을 훑는 뜨거운 반복문은 벡터 너비만큼 한 번에 처리하는 5단계 패턴으로 생각할 수 있다고 풀어낸다.