- Published on
RDS PostgreSQL replication lag 폭증 원인·해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데도 어느 순간 Read Replica 지연이 수십 초~수 분으로 치솟으면, 대개 “복제 자체”보다 WAL 생성량과 적용(redo) 속도의 불균형이 원인입니다. RDS PostgreSQL의 replication lag 폭증은 애플리케이션 레벨(트랜잭션/쿼리 패턴)과 스토리지/인스턴스 레벨(I/O, CPU) 요인이 얽혀 나타나므로, 증상 → 지표 → SQL → 조치 순서로 좁혀가야 합니다.
아래는 운영에서 자주 만나는 패턴을 중심으로, 빠르게 원인을 가르는 진단법과 해결책을 실전 관점으로 정리한 글입니다.
replication lag의 의미부터 정확히 잡기
PostgreSQL 스트리밍 복제에서 lag은 보통 다음 중 하나로 관측됩니다.
- WAL 위치 차이(Lsn gap): primary가 생성한 WAL의 위치와 replica가 받은/재생한 위치 차이
- 시간 기반 지연:
pg_last_xact_replay_timestamp()기준으로 “마지막으로 재생된 트랜잭션 시각”이 현재와 얼마나 차이 나는지
RDS 콘솔/CloudWatch의 ReplicaLag는 내부적으로 시간 기반 지연에 가깝게 표현되지만, 상황에 따라 LSN은 따라가는데 시간만 벌어지거나(긴 트랜잭션), 반대로 시간은 짧아도 LSN 갭이 커지는(WAL 폭주) 케이스가 있습니다. 그래서 SQL로 함께 확인하는 습관이 중요합니다.
1차 분류: “전송이 느린가” vs “적용이 느린가”
먼저 replica에서 아래를 확인해 **receive(수신)**와 replay(재생) 중 어디가 막히는지 나눕니다.
Replica에서 LSN 기반 지연 확인
-- replica에서 실행
SELECT
pg_is_in_recovery() AS in_recovery,
pg_last_wal_receive_lsn() AS receive_lsn,
pg_last_wal_replay_lsn() AS replay_lsn,
pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) AS bytes_between_receive_and_replay;
receive_lsn이 primary를 잘 따라가는데replay_lsn만 뒤처지면 → replica 적용(redo) 병목receive_lsn자체가 primary 대비 뒤처지면 → 네트워크/전송/primary의 WAL sender/replica의 WAL receiver 병목 가능성
Replica에서 시간 기반 지연 확인
-- replica에서 실행
SELECT now() - pg_last_xact_replay_timestamp() AS replay_delay;
- 값이
NULL이면 아직 재생된 트랜잭션이 없거나(초기), 측정이 어려운 상태일 수 있습니다.
Primary에서 replication sender 상태 확인
-- primary에서 실행
SELECT
pid, client_addr, state,
sent_lsn, write_lsn, flush_lsn, replay_lsn,
write_lag, flush_lag, replay_lag,
sync_state
FROM pg_stat_replication
ORDER BY replay_lag DESC NULLS LAST;
write_lag/flush_lag가 크면 전송/디스크 flush 문제replay_lag만 크면 replica 적용 지연
이 1차 분류만 해도 원인의 절반은 정리됩니다.
원인 A: 쓰기 폭주로 WAL 생성량이 폭증
가장 흔한 케이스입니다. 배치/마이그레이션/인덱스 생성/대량 UPDATE가 발생하면 primary는 WAL을 빠르게 만들지만, replica는 이를 단일 스레드에 가까운 redo로 따라가야 해서 쉽게 밀립니다.
징후
- CloudWatch에서 WriteIOPS, WriteThroughput, CPU가 primary에서 급상승
pg_stat_replication에서 sender는 정상인데 replicareplay_lag가 증가pg_wal_lsn_diff가 계속 커짐
해결
- 대량 변경을 쪼개기(배치 chunking)
UPDATE ... WHERE id BETWEEN ...형태로 범위를 나눠 커밋을 자주DELETE도 대량이면 파티션 드롭/교체 전략 고려
- 인덱스/스키마 변경 시점 통제
CREATE INDEX는 WAL을 많이 발생시킵니다. 가능하면 트래픽 저점에 수행CREATE INDEX CONCURRENTLY는 락을 줄이지만 시간이 길어져 WAL이 오래 발생할 수 있어 상황에 따라 선택
- 애플리케이션 커넥션 폭주가 쓰기 폭주로 이어지는지 점검 커넥션이 갑자기 늘면 동시 쓰기/짧은 트랜잭션이 폭증해 WAL이 튈 수 있습니다. 커넥션 상한과 풀링을 함께 점검하세요.
원인 B: replica의 I/O 병목(스토리지/버퍼 캐시/체크포인트)
replica가 WAL을 적용하려면 결국 데이터 파일에 쓰기를 해야 합니다. replica 인스턴스의 디스크 처리량이 부족하거나, 체크포인트/백그라운드 라이터가 밀리면 replay가 느려집니다.
징후
- replica에서 Read/WriteLatency, DiskQueueDepth 상승
bytes_between_receive_and_replay가 커짐(받았는데 못 씀)
해결
- replica 인스턴스 클래스 상향(특히 메모리/IO 대역)
- 스토리지 타입/용량 조정(일부 환경에서 gp3의 IOPS/throughput 명시적 상향이 효과적)
- 파라미터 튜닝(변경 전/후 부하 테스트 권장)
shared_buffers(RDS에서는 제한적),effective_cache_sizecheckpoint_timeout,max_wal_size(체크포인트 과도 발생 완화)checkpoint_completion_target(체크포인트 I/O 스파이크 완화)
원인 C: long transaction / idle in transaction이 복제를 “시간으로” 늦춤
복제 지연이 커 보이는데 실제로는 WAL 적용이 막힌 게 아니라, 재생 시각 기준이 오래된 트랜잭션에 묶여 측정되는 경우가 있습니다. 특히 긴 트랜잭션이나 idle in transaction은 vacuum도 막고, 결과적으로 bloat와 I/O까지 악화시켜 복제 지연을 연쇄적으로 키웁니다.
Primary에서 긴 트랜잭션 찾기
SELECT
pid, usename, state,
now() - xact_start AS xact_age,
now() - query_start AS query_age,
wait_event_type, wait_event,
left(query, 200) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 20;
해결
- 애플리케이션에서 트랜잭션 범위를 줄이기(특히 “읽기만 하는데 BEGIN을 오래 잡는” 패턴 제거)
- 커넥션 풀에서
idle_in_transaction_session_timeout(RDS 파라미터) 적용 검토 - 배치 작업은 주기적으로 커밋하고 재시도 가능하게 설계
원인 D: VACUUM/Autovacuum 지연 → 테이블/인덱스 bloat → I/O 증가
복제는 WAL을 적용하면서 결국 페이지를 만지는데, bloat가 심하면 같은 논리 작업이 더 많은 물리 I/O로 바뀝니다. 이때 lag은 “증상”일 뿐, 근본은 vacuum 지연일 수 있습니다.
vacuum 상태 확인
SELECT
relname,
n_dead_tup,
last_autovacuum,
last_vacuum,
vacuum_count,
autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
해결
- autovacuum 튜닝(테이블별로
autovacuum_vacuum_scale_factor,autovacuum_analyze_scale_factor낮추기) - 대형 테이블은 작업 시간대 분산,
VACUUM (VERBOSE, ANALYZE)를 계획적으로 수행 - bloat가 심각하면
pg_repack같은 온라인 정리 도구를 검토(운영 영향 평가 필수)
원인 E: Replica에서 read 쿼리 과부하(리포팅/OLAP)가 redo를 방해
Read Replica를 “읽기 전용 분석 DB”처럼 쓰다 보면, 무거운 쿼리가 버퍼 캐시를 날리고 I/O를 점유해 WAL replay가 밀릴 수 있습니다.
징후
- replica CPU가 90% 이상,
top에서 postgres 백엔드가 과다 - CloudWatch에서 replica ReadIOPS 급증 + ReplicaLag 상승
해결
- 분석/리포팅 쿼리를 별도 분석용 클러스터로 분리(데이터 파이프라인, DWH)
- replica에
statement_timeout/work_mem가드레일 설정 - 인덱스/쿼리 플랜 최적화(특히 정렬/해시 조인으로 temp 파일 쓰는지 확인)
원인 F: 네트워크/전송 문제 또는 WAL sender 병목
멀티 AZ/리전 간 복제, 또는 네트워크 품질 저하가 있으면 receive가 밀립니다.
징후
- primary의
pg_stat_replication에서write_lag/flush_lag증가 - replica의
receive_lsn자체가 primary와 큰 차이
해결
- 같은 리전 내라면 보통 네트워크보다 인스턴스/스토리지 병목이 더 흔합니다. 그래도 다음을 점검:
- Enhanced Monitoring에서 네트워크 대역/패킷 드롭 징후
- 복제 슬롯/설정(사용 중인 경우)로 인해 sender가 대기하는지
“폭증” 상황에서의 응급 처치(서비스 우선)
lag이 커지면 읽기 분산이 깨지고, 읽기 일관성이 요구되는 기능이 오동작할 수 있습니다. 아래는 흔한 응급 시나리오입니다.
1) Read Replica를 트래픽에서 잠시 제외
- 라우팅 레이어(애플리케이션/프록시)에서 replica read 비중을 낮춰 replay가 따라잡게 합니다.
2) 무거운 쿼리/배치를 즉시 중단
- primary의 대량 쓰기 배치, replica의 리포팅 쿼리를 중지
- long transaction을 종료(필요 시
pg_terminate_backend(pid); 영향 범위 확인)
-- 위험: 실제 운영에서는 영향 검토 후 실행
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid = 12345;
3) 스케일 업/스토리지 성능 상향으로 “적용 속도”를 올리기
- 가장 빠른 완화책은 replica 인스턴스 클래스를 올려 I/O/CPU를 늘리는 것입니다.
재발 방지 체크리스트(운영 표준으로 만들기)
아래 항목을 “장애 후 회고 액션”이 아니라, 상시 룰로 두면 lag 폭증 빈도가 크게 줄어듭니다.
관측(Observability)
- CloudWatch 대시보드에 최소 포함
- Primary: CPU, WriteIOPS/Throughput, FreeStorageSpace, Read/WriteLatency
- Replica: CPU, Read/WriteIOPS/Throughput, Read/WriteLatency, ReplicaLag
- 알람은
ReplicaLag단일 지표만 두지 말고, I/O 지표와 함께 조건을 조합(예: ReplicaLag>30s AND Replica WriteLatency 상승)
쿼리/트랜잭션 가드레일
idle_in_transaction_session_timeout,statement_timeout정책화- 배치 작업은 chunk + 재시도 + 관측 지표(처리량/WAL) 노출
커넥션/풀링
- 커넥션 폭주는 쓰기/락/컨텍스트 스위칭을 늘려 lag의 도화선이 됩니다. RDS Proxy/pgBouncer로 안정화하고
max_connections를 보수적으로 운영하세요. - 참고: Aurora PostgreSQL 커넥션 폭주 차단 체크리스트
Kubernetes/EKS 환경이라면(간접 원인)
DB가 직접 문제처럼 보여도, 실제로는 앱 파드 OOM/재시작 → 재시도 폭주 → 쓰기 폭주 → WAL 폭증으로 이어지는 경우가 많습니다. 애플리케이션 레이어의 장애도 함께 점검하세요.
빠른 결론: 가장 흔한 3가지와 우선순위
운영에서 replication lag “폭증”을 가장 자주 만든 원인은 보통 아래 3개입니다.
- 대량 쓰기(배치/마이그레이션)로 WAL 폭증 → 작업 쪼개기, 시간대 통제, 쓰기량 관측
- replica I/O 병목 → 인스턴스/스토리지 성능 상향, 체크포인트/WAL 관련 튜닝
- long transaction/idle in transaction → 타임아웃 정책, 트랜잭션 범위 축소
마지막으로, lag 자체를 “없애는” 것보다 중요한 건 lag이 커져도 서비스가 안전하게 동작하도록 읽기 일관성 요구 기능을 분리하고, replica를 단순 캐시처럼 쓰지 않도록 아키텍처를 정리하는 것입니다. 이를 전제로 위의 진단 SQL과 지표 조합을 표준 런북으로 만들어 두면, 다음 번 폭증은 ‘장애’가 아니라 ‘이벤트’로 끝낼 수 있습니다.