[개발 깨알 상식_Tips] / SQLite 백업에서 빠진 WAL 로그.md

SQLite 백업에서 빠진 WAL 로그

조회

<!doctype html>

2026년 6월 24일 | 개발 깨알 상식_Tips


SQLite 데이터베이스를 파일 하나로 생각하면 백업이 너무 쉬워 보인다. 실행 중인 서비스 옆에서 app.db만 복사하고, 새 장비에서 열어 본 뒤 테이블 목록이 보이면 끝났다고 느끼기 쉽다. 나도 작은 내부 도구를 옮길 때 이런 식으로 확인한 적이 있다. 문제는 WAL 모드가 켜진 데이터베이스에서는 최신 커밋이 본 파일이 아니라 -wal 파일 쪽에 남아 있을 수 있다는 점이다.

SQLite WAL 모드에서 본 파일과 WAL, SHM 파일이 함께 상태를 이루고 백업 API가 일관된 스냅샷을 만드는 흐름
WAL 모드에서는 app.db 하나만 보지 말고, 로그 파일과 스냅샷 백업 경로를 함께 봐야 한다.

복사가 성공처럼 보이는 이유

SQLite의 Write-Ahead Logging, 즉 WAL은 원본 DB 파일을 바로 덮어쓰기보다 변경 내용을 로그 파일에 먼저 붙이는 방식이다. 덕분에 읽기와 쓰기가 덜 막히고 작은 서비스에서는 체감 성능도 괜찮다. 그런데 이 구조를 모른 채 파일을 옮기면 이상한 상황이 생긴다. 복사한 app.db는 열리는데, 방금 넣은 행이나 설정값은 빠져 있다. 실패가 조용히 숨어서 더 불편하다.

특히 테스트 데이터베이스, 로컬 캐시, 임베딩 인덱스 메타데이터처럼 “일단 파일만 들고 가면 되겠지”라고 생각하기 쉬운 물건에서 자주 헷갈린다. 파일 크기가 작고 스키마가 단순할수록 눈치채기도 어렵다. 그래서 나는 SQLite 파일을 복사하기 전에 먼저 현재 journal mode가 WAL인지 확인하는 쪽으로 습관을 바꿨다.

한 번은 작은 대시보드의 설정 DB를 옮기면서 이 차이를 뒤늦게 봤다. 화면은 정상으로 떠서 배포가 끝난 줄 알았는데, 마지막에 바꾼 필터 기본값만 이전 상태였다. 장애라고 부르기엔 작지만, 이런 종류의 누락은 원인을 찾는 데 더 오래 걸린다. 파일은 있었고, DB도 열렸고, 에러도 없었기 때문이다.

sqlite3 app.db "PRAGMA journal_mode;"
sqlite3 app.db "PRAGMA wal_checkpoint(PASSIVE);"

백업 API가 안전한 자리

실행 중인 DB를 보존하려면 파일 복사보다 SQLite가 제공하는 백업 경로를 먼저 보는 편이 낫다. CLI에서는 .backup을 쓸 수 있고, 새 파일을 정리된 형태로 만들고 싶을 때는 VACUUM INTO도 선택지가 된다. 핵심은 “지금 디렉터리에 보이는 파일 세 개를 사람이 맞춰 복사한다”가 아니라, SQLite가 일관된 스냅샷을 만들도록 맡기는 것이다.

sqlite3 app.db ".backup 'backup.db'"
sqlite3 app.db "VACUUM INTO 'backup.compact.db';"

.backup은 실행 중인 DB에서 짧은 읽기 구간을 여러 번 잡아 목적지 파일로 복사하는 방식이라, 오래 락을 잡는 단순 복사보다 부담이 덜하다. VACUUM INTO는 새 파일을 다시 쓰면서 빈 공간을 정리하는 성격이 있어 배포용 사본이나 테스트 fixture를 만들 때 깔끔하다. 둘 다 “파일 하나를 눈으로 보고 복사”하는 방법보다 의도를 더 분명하게 남긴다.

둘의 선택 기준도 어렵게 잡을 필요는 없다. 실행 중인 서비스를 건드리는 백업이면 먼저 .backup을 떠올리고, 사람이 내려받아 비교하거나 테스트에 넣을 깨끗한 복사본이면 VACUUM INTO를 본다. 중요한 것은 명령 이름보다 복사 시점과 일관성 기준이 명령 안에 들어가 있는가다. 그 기준이 없으면 파일 복사는 그냥 운 좋게 맞은 스냅샷일 뿐이다.

파일로 옮겨야 할 때의 최소 기준

그래도 운영 환경이 아니라 멈춰 있는 로컬 도구라면 파일 복사가 편한 순간이 있다. 이때는 최소한 프로세스를 멈추고, 체크포인트를 한 번 밀고, 관련 파일 존재 여부를 확인한다. WAL 모드의 DB 상태는 보통 app.db, app.db-wal, app.db-shm 세 이름으로 흩어져 보일 수 있다. 이 중 일부만 남아 있거나 일부만 복사되는 상황이 제일 애매하다.

반대로 “세 파일을 전부 복사하면 무조건 안전하다”도 조금 거칠다. 쓰기가 진행 중이면 그 순간의 조합 자체가 흔들릴 수 있고, 다른 프로세스가 열어 둔 reader 때문에 checkpoint가 끝까지 가지 못할 수도 있다. 그래서 나는 파일 세트 복사를 마지막 선택지로 두고, 쓴다면 반드시 서비스 정지나 writer 중단 조건을 같이 적는다.

상황 위험 내가 고르는 방법
서비스가 쓰는 중 스냅샷이 중간 상태가 될 수 있음 .backup 우선
배포용 깨끗한 사본 불필요한 페이지와 로그가 남음 VACUUM INTO
프로세스 완전 종료 후 이동 WAL 파일 누락 가능 checkpoint 후 파일 세트 확인

검증은 열리는지만 보면 부족하다

복사본을 열 수 있다는 사실은 첫 번째 확인일 뿐이다. 내가 더 자주 보는 것은 최근에 들어간 레코드가 있는지, row count가 원본과 맞는지, 그리고 읽기 전용으로 열었을 때 예상 쿼리가 통과하는지다. 작은 DB라면 PRAGMA integrity_check;까지 붙이면 마음이 편하다. 이 검사는 “파일 포맷이 맞다”와 “내가 기대한 최신 상태다”를 분리해 준다.

sqlite3 backup.db "PRAGMA integrity_check;"
sqlite3 backup.db "select count(*) from important_table;"

여기서 중요한 건 거창한 백업 시스템을 만들자는 이야기가 아니다. SQLite는 작아서 편하지만, WAL 모드가 켜진 순간 파일 하나짜리 감각에서 살짝 벗어난다. 그래서 복사 전에 모드를 확인하고, 가능하면 백업 API를 쓰고, 마지막에는 최신 데이터 기준으로 한 번 더 묻는다. 이 세 줄만 있어도 조용한 누락을 꽤 줄일 수 있다.

나는 row count를 볼 때 전체 건수 하나만 보지 않고, 최근에 수정한 테이블이나 가장 중요한 상태 테이블을 따로 찍어 본다. 백업이 오래된 시점으로 떠졌다면 전체 건수는 비슷해도 마지막 설정 한 줄이 다를 수 있다. 이런 확인 쿼리를 백업 명령과 같은 메모에 붙여 두면, 다음 사람이 복구할 때도 “어떤 값까지 들어 있어야 최신인가”를 바로 볼 수 있다.

작은 DB일수록 기록을 남긴다

큰 운영 DB는 다들 조심한다. 오히려 작은 SQLite 파일은 “대충 옮겨도 되겠지”라는 마음 때문에 사고가 난다. 나는 백업 파일 옆에 어떤 방식으로 만들었는지 한 줄을 같이 남겨 둔다. .backup인지, VACUUM INTO인지, 아니면 프로세스 종료 뒤 파일 세트를 복사했는지 정도만 적어도 나중에 복구할 때 추측이 줄어든다. 백업은 파일의 존재보다, 그 파일이 어떤 시점의 어떤 스냅샷인지 설명할 수 있는 상태가 더 중요하다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.