Published on

MySQL 복제 지연(Seconds_Behind_Master) 0으로 만드는 실전 가이드

Authors

서버 운영 중 읽기 부하를 리플리카로 분산하려고 MySQL 복제를 붙였는데, SHOW SLAVE STATUS(또는 8.0의 SHOW REPLICA STATUS)에서 Seconds_Behind_Master가 0으로 내려오지 않는 상황은 꽤 흔합니다. 문제는 단순히 “지연이 있다”가 아니라, **왜 지연이 발생하는지(전송/적용/락/리소스/설정)**를 분리해서 봐야 해결이 빨라진다는 점입니다.

이 글은 Seconds_Behind_Master를 0에 가깝게(혹은 지속적으로 0을 유지하도록) 만들기 위한 진단 순서실제 조치를 중심으로 정리합니다. 단, 워크로드 특성상 0이 “항상” 가능한 것은 아닙니다(대량 배치, 장시간 트랜잭션, DDL 등). 목표는 지속적인 0 또는 예측 가능한 작은 지연입니다.

> 참고: 쿠버네티스/EKS 환경에서 DB 리소스가 흔들리면 복제 지연이 급증하기도 합니다. 노드 디스크 압박이나 Eviction 폭주가 있다면 먼저 인프라를 안정화하세요: EKS 노드 디스크 부족 Evicted 폭주 해결 가이드

1) Seconds_Behind_Master의 의미부터 정확히

Seconds_Behind_Master는 “마스터와의 실제 시간 차”가 아니라, 리플리카 SQL 스레드(적용)가 현재 이벤트를 적용하는 시점과 마스터가 해당 이벤트를 기록한 시점의 차이를 기반으로 계산됩니다.

즉 아래 같은 경우 값이 왜곡되거나 0이더라도 안심할 수 없습니다.

  • 리플리카가 이벤트를 더 이상 받지 못하는데(네트워크 단절) 값이 멈춰 보일 수 있음
  • SQL 스레드가 멈춤(에러)인데 0처럼 보이거나 업데이트가 안 될 수 있음
  • 긴 트랜잭션이 적용 중이면 “적용이 끝날 때까지” 값이 큰 폭으로 유지됨

그래서 항상 함께 봐야 하는 핵심 필드는 다음입니다.

  • Replica_IO_Running / Replica_SQL_Running (또는 Slave_*)
  • Last_IO_Error, Last_SQL_Error
  • Relay_Log_Space
  • Retrieved_Gtid_Set, Executed_Gtid_Set(GTID 사용 시)

2) 가장 먼저 할 30초 진단: 복제 스레드 상태

리플리카에서 먼저 확인합니다.

SHOW REPLICA STATUS\G
-- MySQL 5.7 이하라면
SHOW SLAVE STATUS\G

체크 포인트:

  • Replica_IO_Running = Yes 인가?
  • Replica_SQL_Running = Yes 인가?
  • 에러가 있다면(Last_*_Error) 지연이 아니라 정지입니다. 0으로 만들기 전에 에러를 해결해야 합니다.

GTID 환경이면 “남은 갭”을 수치로 확인

GTID를 쓰면 “몇 초” 대신 “얼마나 뒤처졌는지”를 정확히 볼 수 있습니다.

SELECT
  GTID_SUBTRACT((SELECT @@GLOBAL.gtid_executed),
                (SELECT @@GLOBAL.gtid_executed)) AS noop;

위 쿼는 예시용이고, 실제로는 마스터의 gtid_executed와 리플리카의 gtid_executed 차이를 봐야 합니다. 운영에서는 보통 애플리케이션/스크립트로 두 서버 값을 수집해 GTID_SUBTRACT(master, replica) 결과가 비어있는지(= 따라잡음)를 확인합니다.

3) 지연 원인을 3가지로 쪼개기: 전송/저장/적용

복제 지연은 크게 아래 중 하나(혹은 복합)입니다.

  1. 전송 지연(IO thread): 마스터 binlog를 리플리카가 늦게 가져옴
  2. 릴레이 로그/디스크 병목: 가져온 이벤트를 relay log에 쓰는 과정이 느림
  3. 적용 지연(SQL thread): 가져온 이벤트를 적용(리플레이)하는 게 느림

3-1) IO thread(전송) 병목 징후

  • Replica_IO_Running=Yes인데도 Seconds_Behind_Master가 증가
  • 네트워크 RTT 증가, 패킷 드랍, TLS/압축 오버헤드
  • 마스터가 바쁘거나 binlog flush가 느린 경우

확인 방법(운영에서 흔히 쓰는 최소 체크):

  • 마스터/리플리카 간 네트워크 지표(RTT, 재전송)
  • 리플리카의 SHOW PROCESSLIST에서 Binlog Dump 상태

3-2) 디스크/릴레이 로그 병목 징후

  • Relay_Log_Space가 계속 커짐
  • 리플리카 디스크 I/O 사용률이 높고 fsync 대기 증가

리플리카에서 I/O 병목이 있으면 복제뿐 아니라 쿼도 같이 느려집니다. 특히 컨테이너 환경에서 PV 성능/노드 디스크 압박이 있으면 지연이 폭발합니다. (EKS라면 위에서 링크한 디스크 압박 글처럼 노드 레벨부터 점검하세요.)

3-3) SQL thread(적용) 병목 징후

가장 흔한 케이스입니다.

  • Replica_IO_Running=Yes이고 relay log는 쌓이는데, Replica_SQL_Running=Yes 상태에서 적용이 따라가지 못함
  • 리플리카에서 CPU가 높거나, InnoDB row lock 대기, 혹은 단일 스레드 적용의 한계
  • 대량 UPDATE/DELETE, 인덱스 없는 조건, 장시간 트랜잭션, DDL

4) 즉시 효과가 큰 설정/구성 개선

여기부터는 “Seconds_Behind_Master를 0으로” 만드는 데 실무에서 체감 효과가 큰 순서로 정리합니다.

4-1) 병렬 복제(멀티스레드) 적용: 가장 강력한 카드

단일 SQL 스레드로는 쓰기량이 조금만 커져도 리플리카가 밀립니다. MySQL 5.7+ / 8.0에서는 병렬 적용을 적극 고려하세요.

대표 설정(예시):

# my.cnf (replica)
slave_parallel_workers=8
slave_parallel_type=LOGICAL_CLOCK
slave_preserve_commit_order=1

MySQL 8.0 용어로는 replica_parallel_workers 등이 사용되기도 하지만(버전별 차이), 핵심은:

  • workers 수를 CPU/워크로드에 맞게 늘리고
  • LOGICAL_CLOCK 기반으로 트랜잭션 간 병렬성을 확보하며
  • 커밋 순서 보장을 위해 preserve_commit_order를 켜는 패턴이 일반적입니다.

주의점:

  • 병렬화가 잘 먹히려면 마스터에서 트랜잭션이 서로 다른 키/파티션/테이블로 분산되어 있어야 합니다.
  • 모든 트랜잭션이 같은 테이블/같은 핫 키를 때리면 병렬화 이점이 제한됩니다.

4-2) 리플리카 전용 튜닝: 읽기 쿼와 복제가 싸우지 않게

리플리카는 보통 읽기 트래픽도 받습니다. 이때 읽기 쿼가 버퍼풀을 잠식하거나 I/O를 독점하면 SQL 스레드 적용이 느려져 지연이 늘어납니다.

현장에서 자주 쓰는 전략:

  • 리플리카에만 innodb_buffer_pool_size를 충분히 확보(읽기 캐시 안정화)
  • 읽기 트래픽이 큰 리플리카라면 복제 전용 리플리카읽기 전용 리플리카를 분리(역할 분리)
  • 리플리카에서 무거운 리포트 쿼는 별도 분석 DB/ETL로 분리

4-3) binlog/relaylog 내구성 옵션의 트레이드오프 이해

지연을 줄이려고 flush 정책을 완화하면 성능은 좋아지지만 장애 시 데이터 손실 가능성이 커집니다. 운영 정책에 맞춰 선택해야 합니다.

예(개념 설명용):

  • sync_binlog=1(안전) vs sync_binlog=0/100(성능)
  • innodb_flush_log_at_trx_commit=1(안전) vs 2(성능)

이 옵션은 마스터/리플리카 모두에 영향을 줄 수 있습니다. “Seconds_Behind_Master 0”만 보고 무작정 낮추면 안 됩니다.

5) 쿼/트랜잭션 패턴이 만드는 지연: 원인별 처방

설정만으로 해결이 안 되는 경우, 결국 쓰기 패턴을 손봐야 합니다.

5-1) 장시간 트랜잭션(롱 트랜잭션)

마스터에서 10분짜리 트랜잭션이 커밋되면, 리플리카는 해당 트랜잭션을 적용하는 동안 뒤처질 수 있습니다.

대응:

  • 애플리케이션에서 배치를 작은 배치 단위로 쪼개기 (예: 10만 건 UPDATE를 5천~1만 건씩)
  • 트랜잭션 범위를 최소화
  • 가능한 경우 온라인 스키마 변경 도구(gh-ost/pt-osc) 사용

5-2) DDL이 복제를 막는 경우

특정 DDL은 리플리카에서 메타데이터 락(MDL)을 유발하고, 그 동안 SQL 스레드가 멈춰 지연이 급증합니다.

대응:

  • DDL은 트래픽 낮은 시간에
  • 온라인 DDL 지원 옵션 활용
  • 큰 테이블은 온라인 스키마 변경 도구 사용

5-3) 인덱스 없는 대량 UPDATE/DELETE

마스터에서 풀스캔 UPDATE가 일어나면, 리플리카도 똑같이 고통을 받습니다.

대응:

  • 조건절 컬럼에 인덱스 추가
  • 범위 조건이면 커버링 인덱스/복합 인덱스 재검토
  • 변경 대상을 PK 범위로 쪼개서 처리

6) 운영에서 바로 써먹는 “지연 0” 체크리스트

아래 순서대로 보면 대부분 빠르게 원인에 도달합니다.

  1. 스레드 상태: Replica_IO_Running/Replica_SQL_Running이 둘 다 Yes인가?
  2. 에러 확인: Last_IO_Error, Last_SQL_Error가 비어있는가?
  3. 릴레이 로그 적체: Relay_Log_Space가 증가 추세인가?
  4. 리플리카 리소스: CPU, 디스크 I/O, InnoDB row lock/MDL 대기 증가가 있는가?
  5. 병렬 복제 적용 여부: workers가 0/1이라면 우선 적용 검토
  6. 읽기 부하 간섭: 리플리카 읽기 트래픽이 복제 적용을 방해하는가?
  7. 장시간 트랜잭션/DDL 이벤트: 특정 시점에만 튀는가?

7) 실전: 지연 원인 파악을 위한 쿼리 모음

7-1) 현재 복제 상태 요약

SHOW REPLICA STATUS\G

여기서 아래 항목을 특히 기록해두면, “언제부터/무엇 때문에”가 보입니다.

  • Seconds_Behind_Master
  • Relay_Log_Space
  • Master_Log_File, Read_Master_Log_Pos
  • Relay_Master_Log_File, Exec_Master_Log_Pos

7-2) 리플리카에서 복제 스레드/락 징후 확인

SHOW PROCESSLIST;
  • system user로 표시되는 복제 스레드가 무엇을 기다리는지 확인
  • Waiting for table metadata lock 등이 보이면 DDL/장시간 쿼가 원인일 가능성

7-3) InnoDB 엔진 상태로 병목 힌트 찾기

SHOW ENGINE INNODB STATUS\G
  • LATEST DETECTED DEADLOCK
  • TRANSACTIONS 섹션의 lock wait
  • fsync/flush 관련 대기

8) “0이 안 되는” 정상 케이스도 있다

마지막으로, Seconds_Behind_Master=0은 좋은 신호지만 절대적인 SLA 지표로 쓰기에는 한계가 있습니다.

  • 마스터에 쓰기가 거의 없으면 0이 쉽게 나옵니다(하지만 네트워크 단절도 티가 덜 날 수 있음)
  • 쓰기 폭주/배치/DDL이 있는 시스템은 순간적으로 지연이 생기고 다시 0으로 회복하는 패턴이 자연스럽습니다.

따라서 운영 지표는 보통:

  • Seconds_Behind_MasterP95/P99
  • GTID 갭(미적용 트랜잭션 수)
  • 리플리카 지연이 일정 임계치 초과 시 읽기 라우팅에서 제외

같이 설계합니다.

9) 정리: 0으로 만드는 가장 현실적인 접근

  • 먼저 정지/에러인지 진짜 지연인지 분리
  • 지연이면 전송(IO) vs 저장(I/O) vs 적용(SQL) 중 어디가 병목인지 나누기
  • 대부분은 SQL 적용 병목이고, 해결의 핵심은
    • 병렬 복제(멀티 워커)
    • 롱 트랜잭션/대량 작업 쪼개기
    • DDL/락/인덱스 문제 제거
    • 리플리카의 리소스와 읽기 부하 간섭 제거

복제 지연은 “설정 한두 개”로 끝나는 문제가 아니라, 쓰기 패턴과 인프라/리소스까지 함께 보는 종합 문제입니다. 위 순서대로 진단하면 Seconds_Behind_Master를 0에 가깝게 유지하는 데 필요한 조치가 비교적 빠르게 드러납니다.