18강 / 20강

DB가 느린 진짜 이유 — fsync 와 WAL

안전하게 저장한다는 것의 비용

DB가 느린 진짜 이유 — fsync 와 WAL
"저장했다"고 말하려면 진짜로 디스크에 닿을 때까지 기다려야 합니다.

이상한 현상이 있습니다. NVMe SSD는 초당 50만 번 쓸 수 있다는데, 쇼핑몰 DB는 초당 수백 건의 주문밖에 못 넣습니다. 1000배 차이가 어디로 갔을까요?

답은 fsync입니다.

"저장했다"는 말의 세 가지 뜻

📝
앱 버퍼
프로그램 메모리
죽으면 사라짐
🗄️
커널 페이지 캐시
write() 가 끝난 상태
전원 나가면 사라짐
💾
진짜 디스크
fsync() 가 끝난 상태
전원이 나가도 남음

write()는 커널에 넘겼다는 뜻일 뿐 디스크에 닿았다는 뜻이 아닙니다. 전원이 나가면 사라져요. 그래서 DB는 커밋할 때마다 fsync()를 부릅니다 — "진짜로 디스크에 박힐 때까지 기다려"라고요.

fsync 실습실 — 커밋마다 동기화할 때와 모아서 할 때, 그리고 안 할 때의 처리량 차이를 재 보세요.
불러오는 중…
fsync 한 번의 값이 36번의 그냥 쓰기와 맞먹습니다.
Python 브라우저에서 실행

WAL — 두 번 쓰는데 왜 더 빠른가

DB는 데이터를 고칠 때 먼저 로그에 적고 나서 진짜 데이터를 고칩니다. 이걸 WAL(Write-Ahead Log) 이라고 해요. 두 번 쓰는 셈인데 왜 더 빠를까요?

📜
WAL 쓰기
순차 쓰기
파일 끝에 이어 붙이기만
🎲
데이터 파일 쓰기
랜덤 쓰기
여기저기 흩어진 페이지

커밋 시점에는 순차 로그만 fsync 하면 됩니다. 랜덤 쓰기는 나중에 한가할 때 모아서 하고요. 12강에서 봤듯이 순차와 랜덤은 속도가 하늘과 땅 차이라, 두 번 쓰는 게 더 빠릅니다.

게다가 전원이 나가도 로그를 다시 읽어 복구할 수 있어요. 빠르고 안전한, 드문 조합입니다.

디스크 병목을 진단하는 순서

wa 가 20% 넘으면 디스크를 의심합니다.
bash 브라우저에서 실행
%util 만 보지 말고 await 를 함께 봅니다.
bash 브라우저에서 실행
순차는 빠른데 랜덤이 느리면 DB가 고생합니다.
bash 브라우저에서 실행

대응 방법

증상대응
커밋이 많아 fsync가 병목여러 건을 한 트랜잭션으로 묶는다 (그룹 커밋)
같은 데이터를 반복 조회인덱스 · 앱 캐시 · Redis
로그 쓰기가 디스크를 잡아먹음로그를 다른 디스크로, 또는 비동기 기록
백업이 낮에 돌아 서비스를 방해새벽으로, ionice로 우선순위 낮추기
HDD에서 DB를 돌리는 중SSD로 옮긴다. 가장 확실하고 가장 극적인 개선
디스크가 90% 이상 참정리한다. SSD는 꽉 차면 쓰기가 급격히 느려진다 (12강)
루프 안에서 매번 commit() 하는 코드는 이 함정에 빠져 있습니다.
Python 브라우저에서 실행
🤔 확인 문제
NVMe SSD가 초당 50만 IOPS인데 DB가 초당 1,000건의 주문밖에 처리하지 못합니다. 가장 큰 이유는?
🤔 확인 문제
DB가 데이터를 고칠 때 WAL(로그)에 먼저 쓰고 나중에 데이터 파일을 고치는데, 두 번 쓰는데도 더 빠른 이유는?