WAL(Write-Ahead Logging) 모드
WAL(Write-Ahead Logging) 모드
SQLite가 원자적 커밋과 롤백을 구현하는 기본 방식은 롤백 저널(rollback journal)이에요. 그런데 3.7.0(2010-07-21) 버전부터는 롤백 저널 대신 쓸 수 있는 "WAL"(Write-Ahead Log) 옵션이 생겼어요. 이름 그대로 변경 사항을 데이터베이스 파일에 바로 쓰지 않고, 별도 로그 파일에 "먼저(ahead)" 기록한 뒤 나중에 데이터베이스로 옮기는 방식이에요.
WAL의 장점과 단점
장점은 이래요.
- 대부분의 시나리오에서 WAL이 훨씬 빠르고,
- 읽는 쪽이 쓰는 쪽을 막지 않고, 쓰는 쪽도 읽는 쪽을 막지 않아 동시성이 높아요. 읽기와 쓰기가 동시에 진행될 수 있죠.
- 디스크 I/O가 더 순차적으로 이뤄지고,
fsync()호출 횟수가 훨씬 적어서fsync()가 깨진 시스템에서도 문제에 덜 노출돼요.
단점도 있어요.
- 같은 데이터베이스를 쓰는 모든 프로세스가 같은 호스트에 있어야 해요. WAL은 프로세스들이 작은 공유 메모리를 공유해야 하기 때문에 네트워크 파일시스템에서는 동작하지 않아요.
- 여러
ATTACHed 데이터베이스에 걸친 변경 트랜잭션은 데이터베이스별로는 원자적이지만, 전체를 하나로 묶어 원자적이진 않아요. - WAL 모드에 들어간 뒤에는
page_size를 바꿀 수 없어요. 페이지 크기를 바꾸려면 롤백 저널 모드여야 해요. - 읽기 전용 WAL 데이터베이스를 여는 게 기본적으로 불가능해요.
-shm파일에 대한 쓰기 권한이 필요하죠. - 읽기 위주로 거의 쓰지 않는 애플리케이션에서는 WAL이 전통적인 롤백 저널보다 아주 약간(1~2%) 느릴 수 있어요.
- 각 데이터베이스에
-wal파일과-shm공유 메모리 파일이 추가되어, 응용 프로그램 파일 형식으로 쓰기엔 덜 매력적일 수 있어요. - 체크포인트(checkpoint)라는 추가 작업이 있고, 기본적으로 자동이지만 개발자가 인지하고 있어야 해요.
WAL이 동작하는 방식
롤백 저널은 원래의 변경 전 데이터를 별도 저널 파일에 복사하고, 변경은 데이터베이스 파일에 직접 써요. 크래시나 ROLLBACK이 발생하면 저널의 원본을 데이터베이스로 되돌려 재생하는 방식이죠. COMMIT은 저널 파일이 삭제될 때 일어나요.
WAL은 이 과정을 뒤집어요. 원본 내용은 데이터베이스 파일에 그대로 두고, 변경 사항은 별도의 WAL 파일에 **추가(append)**해요. 커밋을 나타내는 특별한 레코드가 WAL에 추가될 때 COMMIT이 일어나요. 그래서 원본 데이터베이스를 전혀 건드리지 않고 커밋이 가능하고, 변경이 WAL에 커밋되는 동안에도 읽는 쪽은 원본 데이터베이스로 계속 읽을 수 있어요. 하나의 WAL 파일 끝에 여러 트랜잭션을 계속 쌓을 수 있어요.
체크포인트
WAL에 쌓인 트랜잭션을 언젠가는 원본 데이터베이스로 옮겨야 해요. WAL 파일의 트랜잭션을 데이터베이스로 이동하는 것을 체크포인트라고 불러요. 기본적으로 SQLite는 WAL 파일이 1000페이지 크기의 임계값에 도달하면 자동으로 체크포인트를 실행해요. 애플리케이션이 특별히 할 일은 없지만, 원하면 자동 체크포인트의 임계값을 조정하거나 자동 체크포인트를 끄고 한가한 시간이나 별도 스레드에서 실행할 수도 있어요.
동시성
WAL 모드에서 읽기 작업이 시작되면 먼저 WAL에서 마지막 유효한 커밋 레코드의 위치를 기억해요. 이 지점을 "end mark"라고 불러요. 각 읽기 트랜잭션은 자신의 end mark를 지니고, 트랜잭션 동안에는 그 end mark가 바뀌지 않아서 특정 시점의 데이터베이스 내용만 보게 돼요.
쓰기는 단순히 WAL 파일 끝에 새 내용을 추가할 뿐이라 읽기를 방해하지 않아요. 그래서 읽기와 쓰기를 동시에 할 수 있지만, WAL 파일이 하나뿐이라 한 번에 쓰는 쪽은 하나만 존재해요. 읽는 쪽이 WAL에서 페이지를 빠르게 찾도록 wal-index라는 구조가 공유 메모리에 유지돼요. 이 공유 메모리 때문에 모든 읽는 쪽이 같은 머신에 있어야 하고, 네트워크 파일시스템에서 동작하지 않는 이유예요.
체크포인트는 읽기와 동시에 진행될 수 있지만, 현재 읽기의 end mark를 지나는 페이지에 도달하면 멈춰야 해요. 그래서 오래 실행되는 읽기 트랜잭션이 체크포인트 진행을 막을 수 있어요. 결국 그 읽기 트랜잭션은 언젠가 끝나고 체크포인트가 이어서 진행되겠죠.
성능 고려사항
쓰기 트랜잭션은 내용을 한 번만 쓰고(롤백 저널은 두 번) 순차적으로 써서 매우 빨라요. 반면 WAL 파일이 커질수록 읽기 성능은 떨어져요. 읽는 쪽이 WAL을 확인해야 하는데 그 시간이 WAL 크기에 비례하기 때문이에요. wal-index가 훨씬 빠르게 찾게 도와주지만 성능 저하를 완전히 막을 순 없어요. 그래서 정기적으로 체크포인트를 돌려 WAL 파일 크기를 작게 유지하는 게 좋은 읽기 성능 유지에 중요해요.
기본 전략은 WAL이 약 1000페이지가 될 때까지 여러 쓰기 트랜잭션이 쌓이게 두고, 그 후에는 WAL이 1000페이지 미만으로 줄 때까지 커밋마다 체크포인트를 실행하는 거예요. 기본적으로 커밋을 수행한 그 스레드가 자동으로 체크포인트를 돌리기 때문에, 대부분의 커밋은 매우 빠르지만 체크포인트를 유발하는 가끔의 커밋은 훨씬 느릴 수 있어요. 이게 싫다면 자동 체크포인트를 끄고 주기적 체크포인트를 별도 스레드나 프로세스에서 돌리면 돼요.