본문 바로가기
[ & ] AI/AI 바이브코딩

SQLite 운영 백업 기능 구현기

반응형

 

서비스가 돌아가는 중에도 안전하게 백업하고, 검증하고, 보관하는 방법

운영 중인 서비스에서 데이터베이스 백업은 단순 보조 기능이 아니라, 장애나 데이터 손상 상황에서 서비스를 지키는 중요한 안전장치라고 생각합니다. 특히 SQLite를 사용하고 있다면 파일 하나만 복사하면 끝이라고 보기 쉽지만, 실제 운영 환경에서는 WAL 모드, 파일 잠금, 무결성 검증, 보존 주기 관리까지 함께 고려해야 안정적인 백업 체계를 만들 수 있습니다.

이번에는 제가 프로젝트에 직접 구현한 SQLite 백업 기능을 정리해보려고 합니다. 단순히 DB 파일을 복사하는 수준이 아니라, 안전한 백업 생성 → 무결성 검사 → 압축 저장 → 해시 기록 → 외부 전송 → 오래된 백업 정리 까지 한 흐름으로 묶어 운영 가능한 형태로 구성했습니다.


왜 백업 기능을 따로 만들었을까

처음에는 SQLite 파일만 주기적으로 복사하면 충분하지 않을까 생각했습니다. 하지만 실제로 운영을 고려해보니 몇 가지 조건이 필요했습니다.

서비스가 실행 중인 상태에서도 백업이 가능해야 했고, 백업본이 정상인지 확인할 수 있어야 했고, 로컬 보관만이 아니라 외부 저장소로 이중화도 가능해야 했습니다. 또한 운영자가 나중에 “이 백업이 언제 생성된 것인지, 어떤 파일이 포함됐는지, 손상은 없는지”를 추적할 수 있어야 했습니다.

그래서 이번 백업 기능은 아래 요구를 만족하는 방향으로 구현했습니다.

  • 서비스가 동작 중이어도 안전하게 백업할 것
  • 백업본의 무결성을 검증할 것
  • 로컬 외에도 외부 저장소로 확장 가능할 것
  • 백업 결과를 메타데이터로 추적 가능할 것
  • 보존 기간이 지난 백업은 자동 정리할 것

현재 백업 대상

현재 자동 백업 워커 기준으로 관리하는 SQLite 데이터베이스는 아래 두 개입니다.

  • main.db
  • users.db

추가로 수동 백업 스크립트에서는 보조 데이터베이스 파일이 존재할 경우 함께 백업하도록 구성했습니다.

백업 결과물은 기본적으로 아래 경로에 저장됩니다.

backups/sqlite/<timestamp>/

예를 들면 이런 형태입니다.

backups/sqlite/20260319-041911/

각 백업 폴더 안에는 압축된 .gz 파일과 백업 메타데이터를 담은 manifest.json이 함께 생성됩니다.


전체 구조는 이렇게 구성했다

이번 백업 기능은 몇 개의 스크립트로 역할을 나눴습니다.

파일 역할
scripts/backup-worker.mjs 자동 백업 핵심 워커. 스케줄 실행, 압축, 무결성 검사, 이메일/원격 복사/S3 업로드, 보존 기간 정리 담당
scripts/sqlite-backup.mjs 수동 실행용 단발성 SQLite 백업 스크립트
scripts/start-with-worker.mjs 웹 서버 실행 시 백업 워커를 함께 띄우는 런처
package.json 백업 관련 npm 스크립트 정의
.env.example 백업 환경변수 예시
docker-compose.yml ./backups:/app/backups 볼륨 마운트로 백업 보존

운영 자동화 중심은 backup-worker.mjs, 긴급 점검이나 수동 실행용은 sqlite-backup.mjs로 분리한 것이 핵심입니다.


자동 백업 워커는 어떻게 동작할까

자동 백업은 별도 워커 스크립트에서 수행합니다. 단순히 정해진 시간에 파일만 복사하는 구조가 아니라, 다음 순서대로 동작하도록 만들었습니다.

1. 스케줄에 맞춰 백업 시작

node-cron을 사용해서 정해진 시각에 백업이 실행되도록 했습니다.

기본 설정은 아래와 같습니다.

  • 크론 표현식: 0 3 * * *
  • 실행 시각: 매일 오전 3시
  • 시간대: Asia/Seoul

운영 환경에서 시간을 명확히 맞추기 위해 스케줄 시간대도 함께 지정했습니다.

2. 실행 시점마다 고유한 백업 폴더 생성

백업이 시작되면 YYYYMMDD-HHmmss 형식의 백업 ID를 만듭니다. 이 값이 그대로 디렉터리 이름이 되기 때문에, 어떤 시점의 백업인지 폴더명만 봐도 알 수 있습니다.

backups/sqlite/20260319-041911/

참고로 이 백업 ID는 UTC 기준으로 생성되도록 했습니다. 스케줄 시간대는 Asia/Seoul이지만 폴더명은 UTC 기준으로 남기도록 구성해, 로그와 파일 기준 시점을 명확히 구분할 수 있게 했습니다.

3. 단순 복사가 아니라 VACUUM INTO 방식으로 안전하게 백업

운영 중인 SQLite 파일은 WAL 모드나 파일 잠금 상태 때문에 단순 복사만으로는 불안정할 수 있습니다. 그래서 자동 백업 워커에서는 원본 DB를 그대로 복사하지 않고, VACUUM INTO 방식을 사용했습니다.

  • 원본 DB를 직접 건드리지 않습니다.
  • 실행 중인 환경에서도 비교적 안전하게 별도 백업 파일을 생성할 수 있습니다.
  • 이후 검증과 압축을 진행하기 쉬운 독립 SQLite 파일이 만들어집니다.

4. 백업 파일 무결성 검사

백업은 “생성했다”보다 “정상적으로 복구 가능한 상태다”가 중요하다고 생각해서, 백업 파일을 만든 뒤 바로 무결성 검사를 수행하도록 넣었습니다.

  • PRAGMA integrity_check
  • 결과가 정확히 ok인지 확인

검사 결과가 다르면 해당 백업은 정상 백업으로 보지 않도록 했습니다.

5. gzip 압축과 해시 기록

검증을 통과한 백업 파일은 gzip으로 압축해서 저장합니다. 장기 보관과 외부 전송을 고려하면 압축이 꽤 효율적이었습니다.

최종적으로 핵심 결과물은 아래처럼 남습니다.

  • main.backup.db.gz
  • users.backup.db.gz
  • manifest.json

그리고 manifest.json에는 다음 정보들을 기록하도록 했습니다.

  • 백업 생성 시각
  • 백업 ID
  • 출력 경로
  • 원본 파일명
  • 압축 파일명
  • 파일 크기
  • SHA-256 해시

예시 형태는 다음과 같습니다.

{
  "createdAt": "2026-03-19T04:19:12.268Z",
  "timestamp": "20260319-041911",
  "outputRoot": "/path/to/project/backups/sqlite/20260319-041911",
  "backups": [
    {
      "file": "main.db",
      "gzFile": "main.backup.db.gz",
      "sizeBytes": 47595,
      "sha256": "..."
    },
    {
      "file": "users.db",
      "gzFile": "users.backup.db.gz",
      "sizeBytes": 5153,
      "sha256": "..."
    }
  ]
}

실제 프로젝트 경로나 실제 데이터 파일명은 블로그 공개용으로 일반화했습니다.


선택 기능도 같이 붙였다

이메일 첨부 전송

SMTP 설정이 있으면 백업 결과물을 메일로 받을 수 있도록 했습니다.

  • 백업 ID
  • 실행 시각
  • 백업 파일 목록
  • .gz 첨부파일

원격 경로 복사

BACKUP_REMOTE_PATH가 설정되면 백업 디렉터리를 별도 경로로 복사하도록 했습니다.

  • NAS
  • 네트워크 공유 폴더
  • 마운트된 별도 백업 디스크

AWS S3 업로드

BACKUP_S3_BUCKET이 설정되어 있으면 S3 업로드도 가능하도록 구성했습니다.

  • 각 .gz 백업 파일
  • manifest.json

객체 메타데이터에는 백업 시각, 원본 파일명, SHA-256 해시를 함께 담도록 했습니다.

실패해도 로컬 백업은 지키도록 구성

이메일 전송, 원격 복사, S3 업로드는 모두 선택 기능이기 때문에, 각 단계를 개별 try/catch로 처리했습니다. 외부 전송 중 하나가 실패하더라도 이미 만들어진 로컬 백업 자체는 유지되도록 구성했습니다.


오래된 백업은 자동으로 정리

백업은 쌓는 것도 중요하지만, 계속 쌓이기만 하면 저장 공간을 빠르게 사용하게 됩니다. 그래서 마지막 단계에서는 보존 기간이 지난 백업 폴더를 자동 삭제하도록 구현했습니다.

  • BACKUP_RETENTION_DAYS=7

수동 백업 스크립트는 왜 따로 만들었을까

자동 백업 워커와 별개로, 즉시 실행 가능한 수동 백업 스크립트도 따로 만들었습니다. 자동화 중심의 워커와 달리, 긴급 점검이나 일회성 실행에 더 적합한 용도입니다.

  • better-sqlite3의 backup() API 사용
  • 로컬 백업 디렉터리에 .backup.db 파일 생성
  • 무결성 검사와 SHA-256 계산 후 manifest.json 기록
  • 보조 데이터베이스 파일이 존재하면 함께 백업

실제 실행은 이렇게 할 수 있다

명령어 설명
npm run backup:sqlite 수동 로컬 SQLite 백업 실행
npm run backup:now 자동 백업 워커를 1회 실행 후 종료
npm run worker:backup 자동 백업 워커 상시 실행
npm run start:with-worker 웹 서버 실행, 설정 시 백업 워커도 함께 시작
npm run backup:now
node scripts/backup-worker.mjs --once
$env:START_BACKUP_WORKER="true"
npm run start:with-worker

환경변수는 이렇게 분리했다

핵심 설정

변수명 설명 기본값
START_BACKUP_WORKER 웹 시작 시 백업 워커 동시 실행 여부 false
BACKUP_CRON 자동 백업 스케줄 0 3 * * *
BACKUP_RETENTION_DAYS 로컬 백업 보존 일수 7
BACKUP_RUN_NOW 워커 시작 직후 1회 즉시 실행 후 대기 false
BACKUP_RUN_ONCE 1회 실행 후 종료 false

이메일 설정

변수명 설명
BACKUP_EMAIL_HOST SMTP 서버 주소
BACKUP_EMAIL_PORT SMTP 포트
BACKUP_EMAIL_SECURE TLS/SSL 사용 여부
BACKUP_EMAIL_USER SMTP 로그인 계정
BACKUP_EMAIL_PASS SMTP 비밀번호 또는 앱 비밀번호
BACKUP_EMAIL_TO 수신 이메일 주소
BACKUP_EMAIL_FROM 발신 이메일 주소

원격 경로 설정

변수명 설명
BACKUP_REMOTE_PATH 백업 파일 복사 대상 디렉터리

AWS S3 설정

변수명 설명 기본값
BACKUP_S3_BUCKET 업로드 대상 버킷 없음
BACKUP_S3_PREFIX S3 경로 접두사 db-backups/
AWS_REGION AWS 리전 ap-northeast-2
AWS_ACCESS_KEY_ID AWS 접근 키 없음
AWS_SECRET_ACCESS_KEY AWS 비밀 키 없음

백업 결과물은 이런 모습이다

backups/
└─ sqlite/
   └─ 20260319-041911/
      ├─ main.backup.db.gz
      ├─ users.backup.db.gz
      ├─ manifest.json
      ├─ main.backup.db-shm
      ├─ main.backup.db-wal
      ├─ users.backup.db-shm
      └─ users.backup.db-wal

여기서 실제 복구 시 핵심이 되는 파일은 .gz 백업본과 manifest.json입니다.


복구는 어떻게 할까

  1. 복구할 백업 시점을 선택합니다.
  2. 필요한 .backup.db.gz 파일을 압축 해제합니다.
  3. 애플리케이션을 중지합니다.
  4. 기존 DB를 별도로 한 번 더 복사해둡니다.
  5. 압축 해제한 파일을 서비스 DB 파일명으로 바꿔 교체합니다.
  6. 애플리케이션을 다시 시작합니다.

운영하면서 특히 주의한 점

  • 백업 폴더명은 UTC 기준이라 한국 시간과 차이가 날 수 있습니다.
  • 자동 워커는 현재 핵심 DB 파일만 기본 대상으로 삼고 있습니다.
  • 보조 DB는 수동 백업 시에만 포함되도록 분리했습니다.
  • 이메일, 원격 복사, S3 업로드는 선택 기능입니다.
  • 외부 전송이 실패해도 로컬 백업은 유지됩니다.
  • Docker 환경에서는 볼륨 마운트로 백업 파일이 유지되도록 구성했습니다.

마무리

이번 백업 기능은 단순히 SQLite 파일을 복사하는 수준에서 끝내지 않고, 운영 환경에서 실제로 사용할 수 있도록 아래 흐름을 한 번에 묶어 구현한 것이 핵심이었습니다.

  • 안전한 SQLite 백업 생성
  • 무결성 검사
  • 압축 저장
  • SHA-256 해시 기록
  • 이메일 / 원격 경로 / S3 전송
  • 보존 주기 자동 정리

결국 백업은 “파일을 남겼다”에서 끝나는 기능이 아니라, 문제가 생겼을 때 정말 복구 가능한 상태로 보관하고 있었는가가 더 중요하다고 생각합니다.

반응형

댓글