SNS 공유 미리보기
SQLite 백업 파일을 믿어도 될까? WAL부터 복구 시험까지 확인하기
SQLite WAL 환경에서 단순 파일 복사의 한계를 짚고, Python 백업 예제와 무결성 검사·격리 복구 시험·RPO/RTO 확인 절차를 정리합니다.
카카오톡, 페이스북 등 SNS 공유 시
위와 같은 형태로 노출됩니다.
SQLite 백업 파일을 믿어도 될까? WAL부터 복구 시험까지 확인하기
백업 파일이 매일 생긴다고 복구가 보장되는 것은 아니에요. SQLite의 WAL 구조를 이해하고, 일관된 복사본을 만들고, 실제 앱에서 열어 보는 순서로 백업의 신뢰도를 확인해 봐요.
STEP 1
파일 하나를 복사했다고 백업이 끝나지는 않아요
SQLite를 쓰는 작은 서비스는 관리가 간단해 보여요. 데이터베이스 파일 하나만 챙기면 될 것 같죠. 하지만 운영 중인 데이터베이스가 WAL 모드라면 파일 탐색기에서 보이는 본체만 복사하는 습관부터 점검해야 해요. 정상적으로 커밋된 변경이 아직 본체 파일에 반영되지 않았을 수 있기 때문입니다.
WAL은 변경 내용을 별도의 로그에 먼저 기록하는 방식이에요. 체크포인트는 그 변경을 데이터베이스 본체로 옮기는 과정입니다. 따라서 app.db 옆의 app.db-wal은 단순한 임시 쓰레기 파일로 보면 안 돼요. SQLite 공식 문서는 WAL을 영속 상태의 일부로 설명하며, 본체와 분리하면 이미 커밋된 트랜잭션을 잃거나 데이터베이스가 손상될 수 있다고 안내해요.
그렇다고 세 파일을 순서대로 복사하면 항상 안전하다는 뜻도 아니에요. 쓰기가 계속되는 동안 파일마다 다른 시점을 담을 수 있죠. “파일이 모두 존재한다”는 확인과 “일관된 데이터베이스를 확보했다”는 확인을 분리해야 해요. 운영 중 백업에는 데이터베이스가 제공하는 방법을 쓰는 편이 목적에 맞습니다.
이 글은 2026년 10월 5~11일의 신규 출시 소식 대신, 공식 문서로 현재 동작을 확인한 운영 실무 주제를 다뤄요. 예제는 로컬 SQLite 파일을 직접 관리하는 경우를 가정합니다. 관리형 데이터베이스 서비스나 브라우저 안의 저장소에는 해당 서비스가 제공하는 백업·내보내기 절차를 적용해야 해요.
STEP 2
온라인 백업 API와 VACUUM INTO를 구분해요
SQLite의 Online Backup API는 열린 데이터베이스를 다른 데이터베이스로 복사하는 기능이에요. 복사를 나눠 수행할 수 있으며, 원본을 읽는 짧은 구간에 잠금을 잡도록 설계돼 있어요. 그렇지만 운영 부하가 전혀 없다는 뜻은 아니에요. 읽기·쓰기 활동과 저장 장치 상태에 따라 시간과 잠금 경합을 관찰해야 합니다.
원본에 변경이 계속 생기면 복사가 다시 시작되는 상황도 있을 수 있어요. 따라서 “작은 파일이니 반드시 몇 초면 끝난다”는 식으로 완료 시점을 단정하기보다, 진행 시간과 실패를 기록하고 과도하게 길어질 때 알려 주는 운영 절차를 붙이는 편이 좋아요. 대상 연결을 백업 도중 다른 작업과 공유하지 않는 것도 중요합니다.
VACUUM INTO는 별도 파일에 정리된 복사본을 만드는 대안이에요. 삭제된 내용의 흔적을 제거하고 공간을 정리한 복사본을 얻는 데 유용하지만, 작업 성격과 자원 사용이 같지는 않아요. 대상 파일은 없거나 비어 있어야 하며, 도중에 중단된 산출물은 완성된 백업으로 취급하면 안 됩니다.
선택 기준은 “더 최신인 명령인가?”보다 “운영 제약에 맞는가?”예요. 애플리케이션에서 진행 상태를 관찰하고 복사 단위를 조절하려면 백업 API가 자연스러워요. 별도 정리된 복사본이 필요하면 VACUUM INTO를 검토할 수 있죠. 어느 쪽이든 실제 데이터 크기와 쓰기 패턴에서 걸리는 시간, 디스크 여유, 복구 결과를 시험해야 합니다.
파일 복사를 유지하려면 서비스를 멈추고 모든 연결을 안전하게 닫는 절차 등 일관된 상태를 확보하는 조건을 별도로 설계해야 해요. 여기서는 그 조건을 생략한 채 운영 중 cp만 실행하는 방법을 백업 절차로 권하지 않습니다. 체크포인트 명령을 한 번 실행한 직후에도 새 쓰기가 들어올 수 있다는 점을 기억해요.
STEP 3
완료 전 파일과 검증된 파일을 이름으로 구분해요
다음은 Python 표준 sqlite3 모듈로 구현한 설명용 최소 예제입니다. /srv/app/app.db라는 기존 원본과 /srv/backup이라는 준비된 비공개 디렉터리를 가정해요. 실제 경로·권한·여유 공간을 확인한 뒤 별도 시험 환경에서 검증해야 합니다. 원본은 mode=rw로 열어 경로 오타가 빈 데이터베이스를 새로 만드는 일을 막아요.
import sqlite3
from contextlib import closing
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4
root = Path("/srv/backup")
root.mkdir(parents=True, exist_ok=True)
label = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
name = f"app-{label}-{uuid4().hex[:8]}"
partial = root / f"{name}.partial"
ready = root / f"{name}.db"
try:
with closing(sqlite3.connect(
"file:/srv/app/app.db?mode=rw", uri=True
)) as src, closing(sqlite3.connect(partial)) as dst:
src.backup(dst, pages=256, sleep=0.1)
check = dst.execute("PRAGMA integrity_check").fetchall()
if check != [("ok",)]:
raise RuntimeError(f"integrity check failed: {check}")
if dst.execute("PRAGMA foreign_key_check").fetchone():
raise RuntimeError("foreign key check failed")
partial.rename(ready)
print(ready)
except Exception:
# 실패를 작업 실행기에 전달해요. partial은 조사 대상으로 남겨요.
raisepages=256과 sleep=0.1은 예제를 위한 값이고 권장 성능값은 아니에요. sleep은 백업 중 재시도 사이의 대기 설정이며 전체 작업 제한 시간으로 해석하면 안 돼요. 실서비스에서는 별도 실행기의 제한 시간과 실패 알림을 정하고, 종료된 작업이 남긴 partial 파일을 조사한 뒤 정리하는 정책도 필요합니다.
ready 이름으로 바꾸는 시점을 검사 이후로 미룬 이유는 간단해요. 디렉터리에 파일이 생겼다는 사실만 보고 다음 단계가 업로드하거나 이전 백업을 지우는 것을 막기 위해서예요. 이름을 바꾸는 것만으로 전원 장애까지 견디는 저장 내구성을 증명하는 것은 아니며, 외부 보관처에 전송했다면 그쪽의 수신·검증 결과도 따로 확인해야 해요.
Python 연결을 with 블록에 넣으면 트랜잭션 처리를 도와주지만 연결 닫기와 같은 뜻은 아니에요. 예제에서 contextlib.closing을 쓴 것은 연결을 명시적으로 닫기 위해서입니다. 이 코드는 애플리케이션의 정상 동작 검사, 원격 전송, 보존 기간 정리까지 포함한 완성형 백업 제품이 아니라 일관된 로컬 복사와 기본 검사 단계의 출발점이에요.
STEP 4
무결성 검사 다음에는 앱으로 열어 봐요
PRAGMA integrity_check가 ok를 반환하면 데이터베이스 구조의 여러 오류를 점검하는 데 도움이 돼요. 그러나 외래 키 위반은 이 검사에 포함되지 않으므로 foreign_key_check를 별도로 확인해야 합니다. 그리고 두 검사에 모두 통과해도 사용자가 기대한 마지막 문서가 있는지, 첨부파일이 맞는지까지 알려 주지는 않아요.
복구 시험은 운영 파일을 덮어쓰지 않는 별도 디렉터리에서 진행해요. 복사본의 출처와 생성 시각을 기록하고, 실제 서비스와 맞는 애플리케이션 버전으로 열어 봅니다. 읽기만으로 확인 가능한 대표 화면, 검색, 최근 기록 조회를 먼저 점검하고, 쓰기 시험은 원본과 완전히 분리된 환경에서 수행해요.
- 다운로드한 백업의 크기와 기록된 체크섬을 대조해요.
- 구조 검사와 외래 키 검사 결과를 남겨요.
- 백업 당시 기록한 기준 데이터와 복사본의 내용을 비교해요.
- 로그인 없이 가능한 화면 등 낮은 위험의 기능부터 점검해요.
- 준비부터 서비스 확인까지 걸린 시간을 실제로 기록해요.
- 1 · 일관된 복사본 생성 ↓Backup API 또는 검토한 백업 방법으로 완료본 확보
- 2 · 구조와 관계 검사 ↓integrity_check + foreign_key_check
- 3 · 격리된 앱에서 의미 확인 ↓대표 데이터·첨부파일·실제 화면 확인
- 4 · 복구 목표와 비교최근 성공본의 시각과 전체 복구 시간 기록
체크섬은 보관하거나 옮기는 동안 파일이 바뀌었는지 확인하는 수단이에요. 잘못 만든 백업도 그 자체의 체크섬은 정상일 수 있으므로 복구 가능성을 대신하지 못해요. 마찬가지로 테이블의 행 수가 맞아도 중요한 값이 잘못됐을 수 있죠. 데이터의 의미를 확인하는 작은 검사 몇 개가 필요한 이유입니다.
예를 들어 가상의 메모 서비스라면 “최근 저장한 테스트 메모를 읽을 수 있는가”, “메모에 연결된 이미지가 열리는가”, “삭제 처리한 메모가 다시 보이지 않는가”를 확인할 수 있어요. 데이터베이스 바깥의 업로드 폴더, 검색 인덱스, 설정 파일이 있다면 각각의 복구 관계를 문서에 적어 둡니다. 인덱스는 재생성할 수 있어도 원본 이미지는 그럴 수 없을지 모르니까요.
특히 첨부파일과 데이터베이스의 시점이 다르면 복구 후 참조가 깨질 수 있어요. 새 파일은 존재하지만 데이터 행이 없거나, 행은 있는데 파일이 없는 상황을 떠올려 보면 쉬워요. 두 저장소의 일관성을 어떻게 확보할지, 불일치를 어떻게 감지하고 처리할지는 애플리케이션별로 정해야 하는 설계 문제입니다.
STEP 5
허용 가능한 손실과 복구 시간을 숫자로 정해요
백업 주기를 정하기 전에 어느 정도의 데이터 손실을 감당할 수 있는지부터 질문해 봐요. 이를 RPO, 복구까지 허용하는 시간을 RTO라고 부르죠. 숫자를 정했다고 그 목표가 자동으로 달성되지는 않아요. 성공한 백업의 실제 시각과 복구 시험 시간을 계속 비교해야 합니다.
가상의 예로 백업을 매일 02시에 만들고 18시에 장애가 났다면, 당일 백업이 정상 완료됐다는 조건에서 마지막 백업 이후 16시간의 변경이 빠질 수 있어요. 전날 이후 백업이 계속 실패했다면 손실 구간은 더 길어지죠. “하루 한 번 실행하도록 예약했다”와 “하루 이내 데이터를 복구할 수 있다”는 같은 문장이 아닙니다.
복구 시간도 파일 다운로드만 재면 부족해요. 예를 들어 준비 10분, 다운로드 8분, 검사 12분, 애플리케이션 기동과 확인 15분을 가정하면 총 45분이에요. 전부 설명용 가정값입니다. 실제 목표가 30분이라면 어떤 단계를 줄이거나 미리 준비할지 복구 연습에서 확인해야 해요. 숫자는 멋진 대시보드보다 의사결정을 돕는 데 써야 합니다.
백업본을 같은 서버의 다른 폴더에만 두면 서버 자체의 손실에 대비하기 어려워요. 별도 장애 영역에 보관하되 접근 권한, 암호화와 보존 기간을 정합니다. 데이터베이스에는 개인정보가 들어 있을 수 있으므로 외부 전송 위치를 즉흥적으로 고르지 않고 승인된 저장소를 사용해야 해요. 복구 담당자가 실제로 접근할 수 있는지도 확인합니다.
마지막으로 실행 중인 SQLite 라이브러리 버전도 함께 기록해 두면 좋아요. Python에서는 sqlite3.sqlite_version으로 확인할 수 있어요. 운영체제의 sqlite3 명령 버전과 애플리케이션에 연결된 라이브러리 버전이 항상 같다고 가정하지 않는 것이 중요합니다. 배포 환경을 바꿀 때에는 호환성과 공식 수정 내역을 함께 검토해요.
좋은 백업 절차의 완료 기준은 파일 개수가 늘어나는 것이 아니에요. 최근 성공본의 시점을 알고, 그 파일로 필요한 데이터를 되살렸으며, 복구에 걸린 시간이 목표 안에 들어오는 상태입니다. 가장 작은 실천은 운영 원본을 건드리지 않고 최근 백업 한 건을 격리된 환경에서 열어 보는 것이에요.
REFERENCES · 참고자료
확인일: 2026-10-12 KST. 공식 문서의 제품 동작과 아래 운영 제안을 구분해 읽어 주세요.
- 원문 보기
SQLite · Write-Ahead LoggingWAL의 커밋·체크포인트 관계와 WAL 파일이 영속 상태의 일부라는 설명을 참고했어요. 원문 보기 ↗
WAL의 커밋·체크포인트 관계와 WAL 파일이 영속 상태의 일부라는 설명을 참고했어요.
- 원문 보기
SQLite · Online Backup API실행 중인 데이터베이스의 일관된 복사본, 점진적 복사와 잠금 특성을 확인했어요. 원문 보기 ↗
실행 중인 데이터베이스의 일관된 복사본, 점진적 복사와 잠금 특성을 확인했어요.
- 원문 보기
SQLite C API · backup functions백업 중 원본 변경에 따른 재시작 가능성과 대상 연결의 동시 사용 제약을 확인했어요. 원문 보기 ↗
백업 중 원본 변경에 따른 재시작 가능성과 대상 연결의 동시 사용 제약을 확인했어요.
- 원문 보기
SQLite · VACUUMVACUUM INTO의 정리된 복사본 생성, 대상 파일 조건과 중단 시 미완성 출력 위험을 참고했어요. 원문 보기 ↗
VACUUM INTO의 정리된 복사본 생성, 대상 파일 조건과 중단 시 미완성 출력 위험을 참고했어요.
- 원문 보기
Python · sqlite3Connection.backup의 인수, URI 연결, 연결 컨텍스트 관리와 sqlite_version을 예제에 반영했어요. 원문 보기 ↗
Connection.backup의 인수, URI 연결, 연결 컨텍스트 관리와 sqlite_version을 예제에 반영했어요.
- 원문 보기
SQLite · PRAGMA statementsintegrity_check와 foreign_key_check의 검사 범위 차이를 확인했어요. 원문 보기 ↗
integrity_check와 foreign_key_check의 검사 범위 차이를 확인했어요.
오늘 남겨볼 질문: 가장 최근 백업의 생성 시각을 알고 있나요? 그 파일만으로 필요한 데이터를 다시 열어 보고, 복구 시간까지 기록해 본 적이 있나요?


