Go 서버에서 고성능 SQLite 읽기
Source: Dev.to
Workload Assumptions
이 권장 사항은 다음을 전제로 합니다:
- 읽기가 대부분이며(쓰기 작업은 드물거나 오프라인)
- 단일 서버 프로세스가 데이터베이스를 소유함
- 여러 goroutine이 동시에
SELECT를 실행함 - 데이터베이스 크기가 대부분 RAM 또는 OS 캐시에 들어감
- 전원 손실 시 내구성은 중요하지 않음
이 가정이 바뀌면 아래의 몇몇 트레이드‑오프를 재검토해야 합니다.
1. Use WAL Mode (Non‑Negotiable)
PRAGMA journal_mode = WAL;
왜 중요한가
- 읽기가 쓰기를 차단하지 않음
- 읽기가 다른 읽기를 차단하지 않음
- 읽는 동안 메인 데이터베이스 파일에 접근하지 않음
- 페이지를 WAL + DB에서 순차적으로 읽음
읽기‑중심 워크로드에서는 WAL이 SQLite를 잠금‑없는 리더 엔진으로 바꿔줍니다.
2. Set synchronous = NORMAL
PRAGMA synchronous = NORMAL;
WAL 모드에서 이것이 최적점입니다:
- 트랜잭션은 원자적이고 일관성을 유지함
- 매 커밋마다 추가
fsync가 없음 - 디스크 플러시 횟수가 크게 감소함
읽기‑중심 시스템에서는 갑작스러운 전원 손실에 대한 내구성보다 지연 시간과 처리량이 더 중요합니다.
3. Aggressively Increase Page Cache
PRAGMA cache_size = -65536; -- ~64 MiB per connection
- 음수 값은 킬로바이트 단위이며, 페이지 수가 아님
- 캐시는 연결당 별도임
- 큰 캐시는 페이지 폴트와 B‑tree 탐색 비용을 감소시킴
캐시가 클수록 디스크 읽기가 줄어들고 스캔 속도가 빨라집니다.
4. Enable Memory‑Mapped I/O (Huge Win)
PRAGMA mmap_size = 20000000000; -- 20 GiB (or larger than DB)
메모리‑맵 I/O는 OS 페이지 캐시가 무거운 작업을 수행하도록 합니다:
- 페이지당
read()시스템 콜이 없음 - 커널이 자동으로 readahead 수행
- 전체 테이블 스캔이 크게 빨라짐
데이터베이스가 RAM에 들어간다면 SQLite 읽기는 메모리 속도에 근접하게 됩니다.
경험 법칙: mmap_size를 데이터베이스 파일보다 크게 설정하세요.
5. Keep Temporary Objects in Memory
PRAGMA temp_store = MEMORY;
다음 작업에서 디스크 I/O를 회피합니다:
- 정렬
GROUP BY- 임시 인덱스
- 서브쿼리 물리화
분석용 또는 스캔‑중심 쿼리에서는 이로 인해 보이지 않지만 비용이 큰 병목이 사라집니다.
6. Use Exclusive Locking (Single‑Process Optimization)
PRAGMA locking_mode = EXCLUSIVE;
장점
- 파일 시스템 락/언락 시스템 콜이 감소
- 쿼리당 지연 시간이 약간 낮아짐
- 공유 메모리 락 조정이 필요 없음
다른 프로세스가 데이터베이스에 접근할 필요가 없을 때만 안전합니다.
7. Allow SQLite to Use Worker Threads
PRAGMA threads = 4;
활성화 효과:
- 병렬 스캔
- 큰
SELECT가 더 빨라짐 - 다코어 머신에서 CPU 활용도 향상
Go의 goroutine 동시성과 잘 맞습니다.
8. Index Like Your Throughput Depends on It (It Does)
아무리 PRAGMA를 튜닝해도 나쁜 쿼리는 구제되지 않습니다.
가이드라인
WHERE,JOIN,ORDER BY에 사용되는 모든 컬럼에 인덱스 생성- 큰 테이블에
SELECT *사용을 피함 EXPLAIN QUERY PLAN으로 실행 계획 확인
하나의 인덱스 누락이 1 ms 읽기를 200 ms 전체 스캔으로 만들 수 있습니다.
9. Keep Query Planner Stats Fresh
PRAGMA optimize;
실행 시점
- 스키마 변경 후
- 대량 데이터 삽입 후
- 오래 살아있는 연결에서 주기적으로
SQLite가 가장 빠른 접근 경로를 선택하도록 보장합니다.
10. Read‑Only Mode for Extra Safety & Speed
PRAGMA query_only = ON;
장점
- 실수로 인한 쓰기 방지
- 일부 쓰기 관련 안전 검사 생략
- 운영상 안전성 향상
데이터베이스가 실행 중에 절대 변경되지 않을 때 사용하세요.
Recommended Baseline Configuration
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -65536;
PRAGMA mmap_size = 20000000000;
PRAGMA temp_store = MEMORY;
PRAGMA locking_mode = EXCLUSIVE;
PRAGMA threads = 4;
PRAGMA query_only = ON;
이 설정은 연결당 한 번 적용하면 됩니다(journal_mode는 지속됩니다).
Go‑Specific Notes
- 연결 풀(
database/sql) 사용 - 많은 읽기 연결을 허용( WAL에서는 비용이 저렴)
- 핫 경로에서는 준비된 문을 재사용
- 읽기를 불필요하게 직렬화하지 않음
올바르게 구성하면 SQLite는 동시 읽기에서 매우 잘 확장됩니다.
Final Takeaway
SQLite는 느리지 않습니다. 잘못 구성된 SQLite가 느립니다.
WAL, 메모리‑맵 I/O, 적절한 캐시, 그리고 합리적인 내구성 트레이드‑오프만 갖추면 SQLite는 단일 파일에서 수백에서 수천 건의 읽기/초를 최소 메모리와 운영 복잡도로 충분히 처리할 수 있습니다.
읽기‑중심 워크로드와 단순한 배포 환경이라면 SQLite는 가장 효율적인 데이터베이스 중 하나입니다.

Check out: FreeDevTools
Any feedback or contributors are welcome! It’s online, open‑source, and ready for anyone to use.
⭐ Star it on GitHub: freedevtools