SQLite WAL 증가와 database is locked를 구분하는 작은 실험

WAL 파일이 커지고 요청이 느려진다는 이유만으로 “자식 프로세스가 락을 상속해 데드락”이라고 결론내릴 수 없습니다. 오래 열린 읽기 트랜잭션은 checkpoint 진행을 제한할 수 있고, 이미 쓰는 연결은 다른 writer를 기다리게 할 수 있습니다. 두 현상을 분리하면 무엇을 먼저 닫거나 짧게 만들어야 하는지 정할 수 있습니다.

읽기는 오래된 값을 보고 쓰기는 계속됐다

임시 DB를 WAL 모드로 만들고 자동 checkpoint를 끈 뒤, 두 번째 연결에서 BEGIN과 SELECT로 읽기 트랜잭션을 유지했습니다. 첫 연결이 5번 추가 INSERT를 커밋한 뒤 PASSIVE checkpoint 결과는 (0, 5, 0)이었습니다. 읽기 연결의 행 수는 계속 1이었습니다. 읽기 트랜잭션을 ROLLBACK한 다음 TRUNCATE checkpoint는 (0, 0, 0)을 반환했습니다.

이 세 숫자는 busy 표시, WAL 프레임 수, checkpoint한 프레임 수입니다. 첫 값이 0이어도 모든 프레임을 옮겼다는 뜻은 아닙니다. 읽기의 snapshot과 checkpoint 한계는 WAL 동작 설명에 부합합니다. 관찰한 프레임 수 5는 이 작은 파일의 결과이며 운영 DB의 크기 예측값이 아닙니다.

두 번째 writer는 다른 이유로 실패했다

BEGIN IMMEDIATE;
-- On the second connection: BEGIN IMMEDIATE;
-- Observed with a 0.05-second timeout: SQLITE_BUSY (5)
ROLLBACK;

첫 연결의 쓰기 트랜잭션을 열어 둔 상태에서는 두 번째 연결의 BEGIN IMMEDIATE가 SQLITE_BUSY (5)로 끝났습니다. 이때는 reader를 닫는 것만으로 충분하지 않고 첫 writer의 트랜잭션 종료가 필요합니다. SQL 실행 사이에 외부 HTTP 호출이나 긴 계산을 넣어 writer를 붙잡는 코드가 없는지 봅니다.

504와 연결하려면 요청 시간표가 필요하다

운영에서는 요청 시작·DB 대기·commit/rollback·프록시 timeout 시각을 같은 시간대로 기록합니다. DB 대기 시간이 요청 지연을 설명하는지 확인한 뒤 트랜잭션 범위를 줄입니다. busy timeout 증가는 기다릴 시간을 늘릴 뿐 락 소유자를 없애지 않습니다. WAL·SHM 파일을 직접 지우거나 열린 DB 파일을 fork 이후 공유하는 방식으로 고치지 마세요.

실험은 임시 로컬 파일 두 연결에 한정했습니다. 운영에서는 배포판의 SQLite 수정 사항과 사용 중인 라이브러리 버전도 확인해야 하며, 이 결과를 특정 Dashboard나 MCP 제품의 결함 증거로 사용할 수 없습니다.