- Published on
MySQL Replication Lag 폭증 원인·튜닝 7단계
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데도 Seconds_Behind_Master(또는 Seconds_Behind_Source)가 갑자기 수백~수천 초로 치솟는 순간이 있습니다. 이때 가장 흔한 실수는 원인을 특정하기 전에 무작정 파라미터를 만지거나, 반대로 “일시적이겠지” 하고 방치하는 것입니다. 복제 지연은 대개 I/O(바이너리 로그 읽기), SQL 적용(릴레이 로그 실행), 락/트랜잭션 구조, 스토리지/네트워크, 스키마/인덱스, 운영 이벤트(배치/DDL) 중 하나로 수렴합니다.
이 글은 “지연이 폭증했을 때”를 전제로, 원인 분류 → 병목 포인트 식별 → 안전한 튜닝 순서로 진행하는 7단계 플로우를 제공합니다.
> 참고: 쿠버네티스/클라우드 환경에서 네트워크/노드 이슈가 간헐적으로 섞여 들어오는 경우가 많습니다. 노드나 CNI 문제가 의심되면 EKS Node NotReady - CNI ENI 할당 실패 해결 가이드도 함께 확인해 두면 좋습니다.
1단계: “어느 스레드가 막혔는지” 먼저 분리하기
복제 지연은 크게 두 가지로 나뉩니다.
- IO Thread 지연: 소스(마스터)에서 binlog를 못 가져옴(네트워크/권한/연결/디스크)
- SQL Thread 지연: 릴레이 로그는 쌓이는데 적용이 느림(쿼리/락/인덱스/병렬성)
MySQL 8.0 기준으로 아래를 먼저 확인합니다.
-- 레플리카 상태 요약(MySQL 8.0)
SHOW REPLICA STATUS\G
-- 성능 스키마로 스레드 상태 확인
SELECT
THREAD_ID, NAME, PROCESSLIST_STATE, PROCESSLIST_INFO
FROM performance_schema.threads
WHERE NAME LIKE 'thread/sql/%'
OR NAME LIKE 'thread/io/%';
핵심 포인트:
Replica_IO_Running/Replica_SQL_Running중 무엇이No인지Seconds_Behind_Source가 증가하는 동안 Relay_Log_Space가 증가하는지(=SQL 적용이 느림)Last_Errno,Last_Error가 있는지(=지연이 아니라 “정지”)
> Seconds_Behind_*는 “현재 실행 중인 이벤트의 타임스탬프 기반”이라 0이어도 지연이 있을 수 있고, 반대로 갑자기 튈 수 있습니다. 반드시 릴레이 로그 적체량과 함께 보세요.
2단계: 지연 폭증 시점의 “단일 대형 트랜잭션”부터 의심하기
레플리카 SQL 스레드가 느린 가장 흔한 이유는 대형 트랜잭션 1개입니다.
- 수백만 row UPDATE/DELETE
- 인덱스 없는 조건으로 대량 변경
- 대형 INSERT…SELECT
- 큰 DDL(특히 온라인 DDL이 아닌 경우)
바이너리 로그가 row 기반(binlog_format=ROW)이면 이벤트 자체가 커지고, 레플리카에서 적용 비용이 커집니다.
확인 방법(소스에서):
-- 현재 긴 트랜잭션/락 확인
SELECT
trx_id, trx_started, trx_mysql_thread_id, trx_rows_locked, trx_rows_modified
FROM information_schema.innodb_trx
ORDER BY trx_started;
-- 오래 실행 중인 쿼리
SHOW FULL PROCESSLIST;
레플리카에서 “어떤 이벤트를 실행 중인지”는 SHOW REPLICA STATUS\G의 Replica_SQL_Running_State(또는 Slave_SQL_Running_State)가 힌트를 줍니다. Waiting for table metadata lock, Waiting for dependent transaction to commit 같은 문구가 보이면 다음 단계로 바로 넘어가세요.
3단계: 락/메타데이터 락(MDL)로 막히는지 확인하기
복제 지연이 폭증하는데 CPU는 놀고 있고, 디스크도 여유가 있다면 락 대기일 확률이 높습니다.
대표 패턴:
- 레플리카에서 read 트래픽이 많고(리드 스케일), 긴 SELECT가 존재
- DDL 또는 DML이 테이블 메타데이터 락(MDL)에 걸림
- FK/트리거/갭락 등으로 잠금 범위가 커짐
MySQL 8.0에서 MDL/락 대기를 빠르게 확인:
-- 어떤 세션이 어떤 락을 기다리는지
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) waiting_seconds
FROM information_schema.innodb_lock_waits w
JOIN information_schema.innodb_trx r ON w.requesting_trx_id = r.trx_id
JOIN information_schema.innodb_trx b ON w.blocking_trx_id = b.trx_id
ORDER BY waiting_seconds DESC;
-- performance_schema로 메타데이터 락 확인
SELECT *
FROM performance_schema.metadata_locks
WHERE LOCK_STATUS = 'PENDING';
해결 방향:
- 레플리카의 긴 읽기 트랜잭션(특히
REPEATABLE READ)을 줄이기 - DDL을 트래픽 저점에 수행하거나, 온라인 DDL 전략으로 변경
- 애플리케이션에서 불필요한 테이블 스캔을 줄이고 인덱스를 보강
4단계: 병렬 복제(Parallel Replication) 설정을 점검하기
SQL 적용이 느린데 “단일 스레드”로 적용 중이면, 병렬화를 통해 지연을 크게 줄일 수 있습니다.
MySQL 8.0에서 핵심 변수:
replica_parallel_workers(예: 4~32)replica_parallel_type(LOGICAL_CLOCK권장)replica_preserve_commit_order(일관성 위해 ON이 일반적)
예시 설정:
-- 현재 값 확인
SHOW VARIABLES LIKE 'replica_parallel%';
-- 병렬 워커 활성화(동적 적용 가능)
SET GLOBAL replica_parallel_workers = 8;
SET GLOBAL replica_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL replica_preserve_commit_order = ON;
주의할 점:
- 병렬 워커를 늘려도 단일 대형 트랜잭션은 병렬화되지 않습니다.
- 워커 수를 과도하게 올리면 레플리카에서 CPU/IO가 포화되어 오히려 지연이 악화될 수 있습니다.
Waiting for dependent transaction to commit가 자주 보이면 커밋 순서 보존/의존성이 병목일 수 있습니다(스키마/트랜잭션 구조 개선 필요).
5단계: 릴레이 로그/바이너리 로그 I/O 병목을 제거하기
IO Thread가 느리거나, SQL Thread가 빠른데도 Relay_Log_Space가 비정상적으로 증가한다면 디스크/로그 I/O를 확인해야 합니다.
체크리스트:
- 레플리카 디스크가 gp2/gp3, io1/io2 등에서 IOPS 한계에 걸렸는지
relay_log가 느린 볼륨에 있지 않은지sync_binlog,innodb_flush_log_at_trx_commit조합이 과도하게 보수적인지(특히 레플리카)
레플리카는 “데이터 소스”가 아니고 재해복구/읽기 분산 목적이므로, 내구성 정책을 소스보다 완화할 수 있는 경우가 많습니다(조직의 RPO/RTO 정책에 따름).
예시(신중히 적용):
-- 레플리카에서만 내구성 완화(정책 검토 필수)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
SET GLOBAL sync_binlog = 0;
또한 네트워크가 원인일 수 있습니다. 같은 AZ/리전에 두었는지, NAT/프록시를 타는지, 패킷 드롭이 있는지 확인하세요. 쿠버네티스 환경이라면 노드/네트워크 이상이 애플리케이션 증상으로 먼저 보일 때가 많습니다(예: 이미지 풀 실패/노드 불안정). 비슷한 운영 맥락의 트러블슈팅 글로 EKS에서 kubectl port-forward 끊김·hang 해결도 참고가 됩니다.
6단계: “문제 쿼리/스키마”를 복제 친화적으로 바꾸기
복제 지연은 결국 레플리카가 실행해야 하는 SQL(또는 row 이벤트)의 비용 문제로 귀결됩니다. 다음 패턴은 지연을 구조적으로 키웁니다.
- 조건 컬럼에 인덱스가 없어 레플리카에서 테이블 풀스캔 발생
- 업데이트 대상이 넓고(대량), 트랜잭션이 길며, 커밋 빈도가 낮음
- 핫 테이블에 단일 PK 순차 업데이트로 경합 증가
실전 개선 예시:
대량 UPDATE를 배치로 쪼개기
-- 나쁜 예: 한 번에 대량 업데이트
UPDATE orders
SET status='ARCHIVED'
WHERE created_at < NOW() - INTERVAL 180 DAY;
-- 개선: PK 범위/리밋 기반으로 분할(애플리케이션/배치에서 반복)
UPDATE orders
SET status='ARCHIVED'
WHERE id BETWEEN ? AND ?;
필요한 인덱스 추가(단, DDL 자체가 지연을 유발할 수 있음)
-- created_at 조건이 자주 쓰이면 인덱스 고려
ALTER TABLE orders
ADD INDEX idx_orders_created_at (created_at);
DDL은 레플리카에서 적용될 때도 비용이 큽니다. 가능하면 온라인 DDL 도구(예: gh-ost, pt-online-schema-change)나 MySQL 8.0의 ALGORITHM=INPLACE/INSTANT 가능 여부를 검토하세요.
7단계: “회복(캐치업) 모드”로 안전하게 따라잡기
원인을 제거했는데도 이미 릴레이 로그가 많이 쌓였다면, 레플리카가 따라잡는 시간을 줄이는 운영 모드가 필요합니다.
권장 순서:
- 레플리카의 읽기 트래픽을 일시적으로 다른 노드로 우회(가능하면)
- 레플리카 자원(CPU/IOPS) 임시 증설
- 병렬 워커 조정 및 내구성 옵션(정책 범위 내) 완화
- 캐치업 후 원복
읽기 트래픽 우회는 서비스/배포 시스템과 맞물립니다. GitOps로 트래픽 라우팅/리드 엔드포인트를 바꾸는 환경이라면, 동기화/상태 꼬임이 함께 발생할 수 있으니 Argo CD Sync 실패 - OutOfSync·Degraded 해결법처럼 배포 상태까지 함께 점검하는 습관이 도움이 됩니다.
캐치업 중 모니터링할 지표:
Seconds_Behind_Source추세(절대값보다 기울기)Relay_Log_Space감소 속도- 레플리카의 CPU iowait, 디스크 latency
performance_schema.replication_applier_status_by_worker(워커별 지연)
-- 워커별 적용 상태(8.0)
SELECT
WORKER_ID,
SERVICE_STATE,
LAST_APPLIED_TRANSACTION,
LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP,
APPLYING_TRANSACTION,
APPLYING_TRANSACTION_START_APPLY_TIMESTAMP
FROM performance_schema.replication_applier_status_by_worker
ORDER BY WORKER_ID;
운영에서 자주 놓치는 “지연 폭증” 트리거 5가지
마지막으로, 원인 분석을 빠르게 하기 위해 현장에서 자주 만나는 트리거를 정리합니다.
- 배치 작업이 평소보다 큰 범위로 실행(조건 누락/파티션 누락)
- DDL이 피크 타임에 수행되어 MDL 경합 발생
- 레플리카에 리드 트래픽 과다로 인해 SQL thread가 자원 경쟁
- 스토리지 크레딧/IOPS 한계(버스트 소진)로 릴레이 로그 I/O 급락
- 네트워크 순간 장애로 IO thread 재연결/재시도 반복
결론: 튜닝은 “원인 분류 → 병목 제거 → 캐치업” 순서로
Replication Lag 폭증은 대부분 “SQL 적용 병목(락/대형 트랜잭션/스키마)” 또는 “로그 I/O 병목(디스크/네트워크)”로 수렴합니다. 가장 빠른 해결은 파라미터를 무작정 올리는 것이 아니라, 1) IO/SQL 스레드 분리, 2) 대형 트랜잭션/락 확인, 3) 병렬 복제 점검, 4) I/O 병목 제거, 5) 쿼리/스키마 개선, 6) 캐치업 모드 운영의 순서로 접근하는 것입니다.
현 시점에서 환경(MySQL 5.7/8.0, GTID 사용 여부, binlog_format, 레플리카 수, 인스턴스 스펙/스토리지 타입)을 알려주면, 위 7단계를 당신의 조건에 맞춘 “우선순위 체크 SQL 세트”로 더 구체화해 드릴 수 있습니다.