프로덕션 환경에서 SQLite 실행하기: 성능 튜닝 및 백업 전략

프로덕션 환경에서 SQLite 실행하기: 성능 튜닝 및 백업 전략

중소규모 웹사이트를 위해 프로덕션 환경에서 SQLite를 사용하는 것은 실행 가능한 아키텍처 선택이지만, 성능 저하와 가용성 문제를 피하기 위해서는 데이터베이스 내부 구조에 대한 이해가 필요합니다. 주요 운영 요구 사항에는 ANALYZE를 통한 정기적인 통계 업데이트, 대량 삭제 시 쓰기 잠금 관리, 그리고 비차단(non-blocking) 백업 전략 구현이 포함됩니다.

ANALYZE를 통한 쿼리 성능 최적화

ANALYZE 명령어를 실행하는 것은 SQLite 쿼리 플래너가 효율적인 실행 선택을 하도록 하는 데 매우 중요합니다. 최신 통계 정보가 없으면 쿼리 플래너가 최적화되지 않은 계획을 선택할 수 있으며, 이는 심각한 성능 퇴보로 이어질 수 있습니다.

기록된 한 사례에서는, 단 4,000개의 행만 있는 테이블에서 전체 텍스트 검색(FTS5 사용) 쿼리를 실행하는 데 5초가 소요되었습니다. ANALYZE를 실행한 후, 실행 시간은 약 0.05초로 단축되었습니다.

ANALYZE의 작동 방식

ANALYZE는 인덱스와 테이블 내 값의 분포에 대한 통계를 생성합니다. 이러한 통계는 내부 테이블(예: sqlite_stat1sqlite_stat4)에 저장됩니다.

인덱스의 값 분포에 대한 다양한 통계적 뷰를 제공하여 플래너가 인덱스가 얼마나 유용한지(선택도)를 추정할 수 있게 합니다. sqlite_stat1은 평균값만 제공하며... sqlite_stat4가 활성화된 경우 히스토그램 데이터를 저장합니다.

쓰기 경합 및 대량 삭제 관리

SQLite의 단일 쓰기 모델은 긴 실행 시간이 소요되는 쓰기 작업이 다른 작업자를 차단할 경우 애플리케이션 충돌이나 타임아웃을 유발할 수 있습니다. 이는 특히 대량의 DELETE 문을 포함하는 데이터베이스 정리 작업 중에 두드러지게 나타납니다.

타임아웃 문제

대량 삭제 작업이 설정된 데이터베이스 타임아웃(예: 5초)을 초과하면, 데이터베이스에 쓰기를 시도하는 다른 작업자들이 타임아웃이 발생하며 애플리케이션이나 VM이 충돌할 수 있습니다.

완화 전략

정리 작업 중 가용성을 유지하기 위해 다음과 같은 전략을 권장합니다:

  • 배치 처리(Batching): 단일 대규모 트랜잭션 대신 작은 배치 단위로 삭제를 수행하여 단일 쿼리가 타임아웃 임계값을 초과하지 않도록 합니다.
  • RowID 사전 로드: SELECT 문을 사용하여 삭제할 행을 먼저 식별합니다. WAL (Write-Ahead Logging) 모드에서는 읽기 작업이 쓰기 작업을 차단하지 않기 때문입니다.
  • sequential Deletion (순차적 삭제): 데이터가 추가된 순서(또는 역순)로 행을 삭제하면 저장 매체에 따라 성능을 향상시킬 수 있습니다.
  • 직접 CLI 실행: 애플리케이션 수준의 ORM이나 트랜잭션 내에서 실행되는 Python 코드의 잠재적인 오버헤드를 피하기 위해 정리 작업을 위해 SQLite Command Line Interface (CLI)를 사용하십시오.

백업 및 복구 워크플로우

신뢰할 수 있는 SQLite 백업을 위해서는 데이터베이스를 장기간 잠그지 않으면서 원격 오브젝트 스토리지에 효율적으로 저장할 수 있는 방법이 필요합니다.

방법 1: Vacuum Into와 Restic

한 가지 접근 방식은 VACUUM INTO 명령어를 사용하여 데이터베이스의 일관된 스 snapshot을 임시 파일로 생성한 다음, Restic과 같은 도구를 사용하여 S3 호환 스토리지에 압축하여 업로드하는 것입니다.

방법 2: Litestream

Litestream은 WAL (Write-Ahead Log)을 S3로 복제하여 증분 백업을 제공합니다. 이는 일반적으로 전체 스냅샷보다 리소스 효율적이며, 대규모 데이터베이스 파일을 압축할 때 때때로 발생하는 Out-of-Memory (OOM) 문제를 피할 수 있습니다.

방법 3: Compressed Dumps

.dumpzstd와 같은 압축 도구를 결합하여 사용하면, WAL 모드가 활성화된 경우 쓰기 작업자를 차단하지 않으면서 압축률이 높고 rsync-friendly한 백업을를 만들 수 있습니다.

아키텍처 고려 사항

SQLite는 많은 유스케이스에 대해 매우 효율적이지만, 특정 아키텍처 패턴을 통해 사용을 더욱 최적화할 수 있습니다:

  • 데이터베이스 분할(Database Splitting): 독립적인 데이터 도메인을 가진 프로젝트의 경우, 테이블을 여러 개의 별도 데이터베이스 파일로 분할하면 경합을 줄이고 관리를 단순화할 수 있습니다.
  • WAL 모드: Write-Ahead Logging (WAL)을 활성화하는 것은 대부분의 프로덕션 웹 환경에서 필수적입니다. 이는 여러 읽기 작업자와 한 명의 쓰기 작업자가 동시에 작동할 수 있게 하기 때문입니다.
  • 클라이언트-서버 DB로의 마이그레이션: 요구 사항이 네트워크 기반 액세스나 높은 동시성 쓰기 작업이 필요해지는 방향으로 발전할 경우, PostgreSQL과 같은 시스템으로 마이그레이션하는 것이 표준적인 경로입니다.

Sources