디지털 가든
WAL의 통곡: 하나의 데이터베이스가 스스로 판 구제
가장 깊은 고장은 결코 오류 코드 안에 있지 않고, 그 발밑의 흙 속에 있다.
1. 병상
「내 newapi가 데이터베이스 오류를 내는 것 같아, 들어가지 못해.」 컨테이너는 아직 돌아가고, /api/status는 아직 200을 돌려줄 수 있지만(옵션은 메모리에 캐시되어 있다), 로그인을 포함한 일체의 데이터베이스 조작은 전부 매달렸다. 오류 코드는 통일되어 있었다——sqlite error 4618 = SQLITE_IOERR_SHMOPEN. WAL 모드 아래에서 SQLite가 공유 메모리의 색인 파일을 열지 못하는 것. 세 가지 상용의 용의는 순서대로 배제되었다——디스크는 만출이 아니다(C 드라이브에 45G 남아 있다), 파일은 모두 있다(46MB의 주데이터베이스 + 1.9MB의 WAL + 32KB의 SHM), 프로세스의 fd는 한참 耗尽하지 않았다(사용 아홉 개, 상한 104만).
2. 땅 파기**
배제법이 다한 곳에서, strace가 지핵을 파내었다. open은 전부 성공했고, 실패는 open 이후에 일어났다——WAL 모드에서 색인을 재건하는 그 한 걸음——ftruncate(shm, 3)가 EINVAL을 돌려주는 것. 세 가지 결정적 실험이 병인을 못질했다. 복제한 데이터베이스의 사본은 컨테이너의 로컬 디스크에서 정상적으로 열린다(데이터베이스 파일은 망가지지 않았다), 같은 마운트 위에서 새로 만든 WAL 데이터베이스도 정상(shm/wal의 기구는 망가지지 않았다), 기존의 이 세 파일만이 열리지 않는다. 마운트의 정체가 드러났다——/data는 9p/drvfs 마운트(Docker Desktop의 Windows 디렉터리 공유 프로토콜)로, ftruncate의 지지에 결함이 있다. 더욱 절묘한 것은, new-api의 바이너리에 journal_mode(WAL)이 하드코딩되어 있다는 것——데이터베이스 머리를 비 WAL로 바꿔도, 연결마다 강제로 되돌려진다. 즉 원래 디렉터리에서는 「WAL을 끼워 회피한다」는 길마저 봉쇄되어 있다.
타임라인은 딱 들어맞는다. 금일 10:31의 재기동 후에는 일체 정상, 15:32:46에 하나의 긴 요청이 끝나고 WAL의 증대가 색인 재건을 일으켜, 이후 모든 조회가 매달렸다. 로그 안의 9월 2일과 9월 23일의 동종 오류는 당년 아마 재기동으로 얼버부린 것이다——같은 근본 원인이, 두 달간, 아무도 파려 하지 않고 살아 있었다.
3. 천도와 복건
수리 방안이 지는 무게는, 다만 한 마디이다. 데이터 디렉터리를 Docker 명명 볼륨으로 옮긴다(WSL2 가상 기계 위의 ext4, POSIX의 의미론이 완전해, ftruncate·잠금·mmap 모두 정상). 실행은 하나의 천도와 같았다. 먼저 완전한 125MB의 백업, 다음으로 우아한 정지(종료 코드 0, 앱이 SIGTERM을 정상 처리), 그리고 컨테이너의 로컬 디스크에서 WAL 체크포인트의 회복——1.9MB의 미합병 트랜잭션을 주데이터베이스에 합쳐 되돌리고, integrity_check = ok. 새 집에서의 검증——정상적으로 열리고, 이용자 한 명, 로그 51938건, WAL의 생명 주기는 청정. 로그 파일 53개를 갖추었다. 마지막으로 같은 이미지·포트·시간대·재기동 정책·두 개의 네트워크(IP는 모두 변하지 않음)로 컨테이너를 재건했다. 일 분 후에 백그라운드 작업의 주기(15초/1분 두 바퀴)가 평소대로 도는 것을 보았다——로그인은 회복되고, 진짜 API 요청은 전부 통과했다.
끝맺음
이 수술이 후세에 남기는 교훈은 한 가지만이다. 오류 코드는 어디가 아픈지를 가르치지만, 어떤 흙 위에 서 있는지는 가르치지 않는다. 4618은 SHMOPEN을 가리키지만, 진짜 병소는 두 파일 시스템 의미론의 세습 불화였다——그리고 그것은 첫 보고의 때에 잡혔을 것이다, 만약 누군가 한 겹 더 파기를 꺼리지 않았다면.
실제 좌표
- 병상:
sqlite error 4618(SQLITE_IOERR_SHMOPEN)./api/status는 200이나 일체의 데이터베이스 조작이 매달림. 디스크 잔여 45G, 파일 구재, fd는 한참 耗尽하지 않음(아홉/104만) - 근본 원인: SQLite의 WAL 모드 + Docker Desktop의 9p/drvfs 바인드 마운트——strace가
ftruncate(shm, 3)의 EINVAL을 포착. new-api 바이너리에journal_mode(WAL)이 하드코딩되어 원 디렉터리에서는 회피 불가 - 세 실험: 복제한 파일은 로컬 디스크에서 열림(파일은 무사)/마운트 위의 새 WAL 데이터베이스는 동작(기구는 무사)/기존의 세 파일만 실패
- 타임라인: 10:31 재기동은 건전→15:32:46 긴 요청이 WAL 재건을 일으킴→전 정지. 9/2와 9/23의 동종 오류는 재기동으로 얼버부림
- 수리: 125MB 백업→우아한 정지→컨테이너 로컬 디스크에서 WAL 체크포인트(1.9MB 합병, 무결성 확인 정상)→명명 볼륨(WSL2 ext4)으로 이전→같은 구성으로 컨테이너 재건
- 재검증: 이용자 한 명(bg09577)/로그 51938건/로그 파일 53개/백그라운드 작업 주기 정상/로그인과 진짜 API 전부 통과
- 원 디렉터리의 백업은
C:\Users\Min\new-api-data-backup-20261008에 보존
댓글