SQLite WAL 모드인데도 database is locked 뜨는 이유, 원인부터 좁혀보기

SQLite WAL 모드인데도

결론부터 말씀드리면, SQLite WAL 모드인데도 database is locked 에러가 나는 경우는 원인이 하나가 아닙니다. 에러 메시지 텍스트를 자세히 보면 두 갈래로 갈리는데, 단순히 “database is locked”만 뜨는지 아니면 “database table is locked”처럼 테이블 이름까지 붙어서 뜨는지가 첫 번째 단서입니다. 전자는 다른 커넥션과의 충돌(SQLITE_BUSY)이고, 후자는 같은 커넥션 안에서 커서를 안 닫아 생기는 자기 잠금(SQLITE_LOCKED)이라 해결 방법이 완전히 다릅니다.

이 구분을 못 하고 busy_timeout만 계속 늘리는 분들이 많은데, 그러면 절반의 경우에는 아무리 기다려도 절대 풀리지 않습니다. 아래에서 어떤 조건일 때 어느 쪽인지, 그리고 각각 어떻게 확인하고 고치는지 순서대로 짚어보겠습니다.

메시지 문구부터 다시 확인해 보세요

WAL 모드는 쓰기 내용을 본 데이터베이스 파일에 바로 반영하지 않고 별도의 -wal 파일에 순차 기록한 뒤, 나중에 체크포인트로 병합하는 방식입니다. 이 구조 덕분에 읽기와 쓰기가 서로 막지 않아서 락 문제가 거의 사라진 것처럼 알려져 있지만, 실제로는 쓰기 커넥션은 여전히 한 번에 하나만 허용됩니다.

로그에 찍힌 에러 문자열을 그대로 복사해서 다시 읽어보시길 권합니다. 테이블명 없이 “database is locked”만 있으면 다른 프로세스나 스레드가 쓰기 중이라는 뜻이고, “database table users is locked”처럼 테이블명이 붙어 있으면 지금 이 코드가 실행 중인 바로 그 커넥션 자신이 원인입니다. 이 둘을 같은 문제로 보고 접근하면 원인 좁히기가 계속 헛돌게 됩니다.

busy_timeout이 0으로 남아있는지부터 점검하세요

SQLite C 라이브러리는 기본적으로 busy_timeout이 0입니다. 다른 커넥션이 쓰기 중일 때 대기 없이 즉시 에러를 던진다는 뜻입니다. 값을 직접 확인하는 방법은 다음과 같습니다.

sqlite3 app.db "PRAGMA journal_mode;"
-- wal

sqlite3 app.db "PRAGMA busy_timeout;"
-- 0

바인딩마다 기본값이 다르다는 점도 실무에서 자주 놓치는 부분입니다. 파이썬 sqlite3 모듈은 connect()의 timeout 파라미터 기본값이 5.0초라서 자잘한 충돌은 이미 어느 정도 흡수하지만, Node.js의 better-sqlite3 같은 라이브러리는 명시적으로 pragma(‘busy_timeout = 5000’)을 호출하지 않으면 C 라이브러리 기본값인 0을 그대로 씁니다. 같은 코드베이스인데 파이썬 배치 스크립트는 안 터지고 Node 서버만 터진다면 이 차이를 의심해 보시면 됩니다.

같은 커넥션에서 커서를 안 닫고 쓰기를 시도하는 경우

busy_timeout을 30초, 60초로 늘려도 그대로 즉시 에러가 뜬다면 이건 대기 시간 문제가 아닙니다. SQLite는 같은 커넥션 안에서 SELECT 결과를 다 읽지 않은 채로 UPDATE나 INSERT를 실행하면 SQLITE_LOCKED를 반환합니다. 이는 다른 커넥션과의 경합이 아니라 자기 자신과의 충돌이라서 아무리 기다려도 절대 풀리지 않습니다.

파이썬 코드로 재현하면 이렇습니다.

import sqlite3

conn = sqlite3.connect("app.db", timeout=30)
conn.execute("PRAGMA journal_mode=WAL;")

cur = conn.execute("SELECT id FROM users WHERE active=1")
row = cur.fetchone()  # 커서가 아직 열린 상태

# 같은 커넥션에서 쓰기 시도 → sqlite3.OperationalError:
# database table users is locked
conn.execute("UPDATE users SET last_seen=? WHERE id=?", (now, row[0]))

고치는 방법은 두 가지입니다. cur.fetchall()로 결과를 다 소비하거나 cur.close()를 호출해 커서를 정리한 뒤에 쓰기 문을 실행하는 것, 또는 애초에 읽기용 커넥션과 쓰기용 커넥션을 분리하는 것입니다. ORM을 쓰는 경우 세션 하나에서 스트리밍 쿼리를 열어둔 채 같은 세션으로 커밋을 시도하는 패턴이 이 문제의 전형적인 발생 지점입니다.

네트워크 드라이브·컨테이너 볼륨에서는 WAL이 다르게 동작합니다

WAL 모드는 -wal 파일 외에 -shm이라는 공유 메모리 인덱스 파일을 추가로 사용해서 여러 프로세스가 WAL 파일의 어디까지 읽었는지 조율합니다. 문제는 NFS나 SMB 같은 네트워크 파일시스템에서는 이 공유 메모리 매핑이 제대로 동작하지 않는 경우가 있다는 점입니다. 로컬 디스크에서는 잘 되던 코드를 그대로 NAS나 마운트된 네트워크 드라이브로 옮기면 락 관련 에러가 새로 발생하는 이유가 대부분 여기 있습니다.

여러 데이터베이스 커넥션이 동시에 연결된 개념 다이어그램

Docker에서도 비슷한 상황이 생깁니다. 바인드 마운트나 일부 볼륨 드라이버가 내부적으로 네트워크 파일시스템 계층을 거치는 구성이면 컨테이너 안에서는 WAL 모드가 불안정하게 동작할 수 있습니다. 이럴 때는 두 가지 선택지가 있는데, 데이터베이스 파일을 컨테이너 로컬 볼륨으로 옮기거나, 그게 어렵다면 journal_mode를 DELETE로 되돌려서 안정성을 우선하는 방법입니다.

조건 실제 원인 확인 방법 대응
다른 프로세스가 쓰기 중 SQLITE_BUSY (커넥션 간 경합) 시간이 지나면 자연히 풀림 busy_timeout을 5000ms 이상으로 명시
메시지에 테이블명 포함 SQLITE_LOCKED (같은 커넥션 자기 잠금) 아무리 기다려도 즉시 에러 커서 close/fetchall 후 쓰기, 읽기·쓰기 커넥션 분리
NFS·SMB·일부 컨테이너 볼륨 -shm 공유 메모리 매핑 실패 로컬 디스크에서는 재현 안 됨 로컬 볼륨 이동 또는 journal_mode=DELETE
오래 열린 읽기 트랜잭션 체크포인트 지연, WAL 파일 비대화 -wal 파일 크기가 계속 증가 트랜잭션 짧게 유지, PRAGMA wal_checkpoint(TRUNCATE) 수동 실행

busy_timeout을 늘렸는데도 여전히 잠기는 이유가 뭘까요?

이 질문을 하시는 분들 코드를 보면 십중팔구 커넥션 풀 설정 쪽에 원인이 있습니다. SQLAlchemy 같은 ORM에서 커넥션 풀을 쓰면서 각 커넥션마다 개별적으로 PRAGMA busy_timeout을 실행하지 않고 최초 커넥션 하나에만 설정해둔 경우, 풀에서 새로 열리는 커넥션은 여전히 기본값 0을 씁니다. connect 이벤트 리스너에 PRAGMA 설정을 걸어서 모든 커넥션에 동일하게 적용되는지부터 확인해 보시면 됩니다.

그다음으로 흔한 경우는 백그라운드 스레드가 트랜잭션을 커밋도 롤백도 하지 않은 채로 방치하는 패턴입니다. try 블록에서 예외가 나서 함수가 중간에 빠져나갔는데 finally에서 rollback을 호출하지 않으면 그 커넥션은 트랜잭션을 계속 물고 있는 상태로 남고, 이후 다른 쓰기 요청이 전부 대기하다가 timeout을 넘겨서 에러가 납니다.

지금 코드에서 확인할 순서: 메시지 → busy_timeout → 커서 → 파일 위치

정리하면, database is locked 에러 로그에서 테이블명 유무부터 보시고, 없다면 모든 커넥션에 busy_timeout이 실제로 적용되는지 PRAGMA busy_timeout으로 조회해서 확인하시면 됩니다. 테이블명이 붙어 있다면 busy_timeout을 아무리 늘려도 소용없으니 같은 커넥션 안에서 열린 커서나 끝나지 않은 트랜잭션을 찾아서 정리하시는 쪽이 맞습니다. 마지막으로 데이터베이스 파일이 네트워크 드라이브나 특이한 볼륨 위에 있다면 로컬 디스크로 옮겨서 재현되는지 한 번 테스트해 보시길 권합니다.

이 네 단계만 순서대로 밟아도 대부분의 WAL 모드 락 문제는 원인이 어느 쪽인지 명확해집니다. 원인을 못 찾은 채로 timeout 숫자만 올리는 시도는 시간 낭비인 경우가 많으니, 다음에 같은 에러를 만나면 메시지 문구부터 다시 읽어보시기 바랍니다.

LLM 답변에 최신 정보가 없을 때, 웹 검색 도구는 언제 필요할까요

Leave a Comment