이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- DB는 데이터를 매번 디스크에서 직접 읽는가?
- Buffer cache와 WAL은 성능과 복구에 어떤 역할을 하는가?
- 백엔드 대량 작업이 DB 내부 저장 구조에 어떤 영향을 주는가?
개요
DB는 단순히 파일에 데이터를 읽고 쓰지 않는다. 메모리 버퍼, 디스크 페이지, 로그, 체크포인트, 캐시가 함께 동작한다. 이 구조를 이해하면 왜 대량 update가 느리고, 왜 커밋된 데이터가 장애 후 복구될 수 있는지 감이 잡힌다.
백엔드 개발자는 내부 구현을 모두 알 필요는 없지만, 자신의 쿼리가 메모리와 디스크, 로그를 어떻게 압박하는지는 알아야 한다.
원리
DB는 데이터를 논리적으로는 table과 row로 보여주지만, 물리적으로는 page/block 단위로 읽고 쓴다. 자주 접근되는 page는 buffer cache에 머물고, 변경은 먼저 WAL이나 redo log 같은 복구 로그에 기록된 뒤 데이터 파일에 반영된다.
그래서 백엔드의 작은 코드 변경도 내부적으로는 여러 비용을 만든다. 목록 API 하나가 table scan을 만들면 buffer cache를 밀어낼 수 있고, 대량 update 하나가 WAL/binlog와 checkpoint 부하, replica lag를 만들 수 있다. “쿼리 한 번”이 아니라 “읽은 page, 더러워진 page, 기록된 log, 밀려난 cache”로 보는 감각이 필요하다.
Buffer Cache
DB는 자주 읽는 데이터 페이지를 메모리에 올려둔다. 같은 데이터를 반복 조회하면 디스크보다 메모리에서 빠르게 읽을 수 있다.
하지만 테이블 전체를 훑는 대형 쿼리가 자주 실행되면 캐시에 있던 중요한 페이지를 밀어낼 수 있다. 관리자 대량 검색이나 리포트 쿼리가 사용자 API를 느리게 만들 수 있는 이유다.
SELECT *
FROM access_logs
WHERE created_at >= now() - interval '1 year';이런 쿼리는 운영 DB에서 buffer cache와 I/O를 크게 흔들 수 있으므로 실행 전에 기간, limit, 실행 계획, replica 사용 가능 여부를 확인해야 한다.
실무에서는 “이 쿼리가 느린가”뿐 아니라 “이 쿼리가 캐시를 오염시켜 다른 API를 느리게 만들 수 있는가”도 본다. 고객 API와 관리자 리포트를 같은 primary에서 동시에 처리하면, 사용자 요청은 직접 해당 쿼리를 호출하지 않아도 영향을 받을 수 있다.
WAL과 Redo Log
PostgreSQL의 WAL, MySQL InnoDB의 redo log는 장애 복구를 위한 핵심 로그다. DB는 변경 내용을 로그에 기록해 커밋 후 장애가 나도 복구할 수 있게 한다.
UPDATE orders
SET status = 'CANCELED'
WHERE id = 100;이 작은 update도 데이터 페이지 변경, 인덱스 변경, 로그 기록을 만들 수 있다. 대량 update는 이 비용이 크게 누적된다.
Checkpoint와 Flush
DB는 변경된 메모리 페이지를 적절한 시점에 디스크로 반영한다. 이 과정은 checkpoint와 연결된다. 쓰기량이 급증하면 checkpoint와 I/O 부하가 커질 수 있다.
백엔드에서 backfill이나 대량 배치를 실행할 때 DB I/O와 WAL/binlog 증가를 보는 이유가 여기에 있다.
대량 작업 중에는 오래 실행되는 쿼리와 대기 이벤트를 확인한다. PostgreSQL이라면 다음처럼 현재 세션 상태를 먼저 볼 수 있다.
SELECT pid,
state,
now() - query_start AS query_age,
wait_event_type,
wait_event,
left(query, 200) AS query_sample
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY query_age DESC;MySQL 계열에서는 Performance Schema, slow query log, SHOW PROCESSLIST 같은 도구로 비슷한 질문을 던진다. 중요한 것은 DBMS별 명령을 외우는 것보다 “지금 CPU를 쓰는지, I/O를 기다리는지, lock을 기다리는지, replica가 밀리는지”를 분리하는 것이다.
Cache Hit
운영에서 buffer cache hit ratio 같은 지표를 볼 수 있다. 캐시 적중률이 낮아지고 디스크 읽기가 증가하면 쿼리가 느려질 수 있다.
그러나 캐시 적중률 하나만으로 결론 내리면 안 된다. 쿼리 패턴, 인덱스, 데이터 크기, 메모리, 동시성, checkpoint를 함께 본다.
실전 팁
- 대량 조회는 운영 primary에서 직접 실행하지 않는다.
- backfill은 작은 batch로 나눠 WAL과 I/O를 조절한다.
- 인덱스는 읽기뿐 아니라 쓰기와 로그 비용을 만든다.
- 캐시가 따뜻한 개발 환경과 차가운 운영 상황을 구분한다.
- 리포트성 조회는 replica나 분석 DB를 검토한다.
- 대량 update/delete는 대상 건수, batch 크기, sleep 간격, lock 범위, replica lag 기준을 정하고 실행한다.
- 장애 중에는 “쿼리를 죽일지”, “트래픽을 줄일지”, “batch를 멈출지”, “replica 읽기를 차단할지”를 인프라/DBA와 같은 지표로 결정한다.
위험 신호!
- 운영 DB에서 전체 기간 리포트를 동기 API로 실행한다.
- 대량 update 후 replica lag와 WAL 증가를 보지 않는다.
- 인덱스 추가가 쓰기 로그 비용을 만든다는 점을 모른다.
- 캐시가 우연히 따뜻한 상태에서 성능을 판단한다.
- 분석 쿼리와 사용자 API가 같은 DB 리소스를 경쟁한다.
- 운영 batch가 실패했는데 이미 변경한 row 수와 재시작 기준을 모른다.
확인 질문
- DB가 buffer cache를 사용하는 이유는 무엇인가?
- 자주 접근하는 데이터 페이지를 메모리에서 읽어 디스크 I/O를 줄이기 위해서다.
- WAL이나 redo log가 필요한 이유는 무엇인가?
- 커밋된 변경을 장애 후에도 복구할 수 있게 변경 내용을 먼저 로그로 남기기 위해서다.
- 백엔드 대량 작업이 DB 내부에 주는 부담은 무엇인가?
- 많은 페이지 변경, 로그 증가, I/O 부하, cache 오염, replica lag를 만들 수 있다.
- 대량 작업을 실행하기 전에 인프라/DBA와 합의해야 할 기준은 무엇인가?
- batch 크기, 실행 시간대, 중단 기준, lock 대기 허용치, replica lag 허용치, 재시작 방법, 백업과 rollback 가능성이다.