LLM WikiAccess-protected knowledge portal
← 스터디 홈
2편 · 약 21분

Replication 운영: GTID, semi-sync, lag 원인, replica read 안전성

복제는 읽기 확장 기능이기 전에 장애 시나리오다

MySQL 복제를 처음 배울 때는 보통 source의 binlog를 replica가 받아서 재생한다는 흐름으로 이해한다. 하지만 운영자가 복제를 다룰 때 더 중요한 질문은 조금 다르다.

  • 장애가 났을 때 어떤 replica를 승격해도 되는가?
  • 애플리케이션이 replica에서 읽은 값은 source의 최신 쓰기를 반영한다고 믿어도 되는가?
  • 지연이 생겼을 때 네트워크 문제인지, relay log 수신 문제인지, SQL 적용 문제인지 어떻게 나눌 것인가?
  • semi-sync를 켜면 정말 데이터 손실이 없어지는가, 아니면 손실 가능성이 줄어드는 것인가?

MySQL 8.4의 공식 문서는 기본 복제가 비동기이며, GTID 기반 복제는 파일명과 position을 직접 맞추지 않아도 트랜잭션 단위로 복제를 추적할 수 있게 한다고 설명한다. 또 semi-synchronous replication은 source가 적어도 하나의 replica가 이벤트를 받아 relay log에 기록했다는 응답을 받을 때까지 commit 경로를 기다리게 할 수 있다. 단, 이것은 모든 replica가 적용까지 끝냈다는 뜻이 아니다.

그래서 이번 장의 핵심은 복제를 데이터 이동 파이프라인이 아니라 커밋, 전송, 적용, 읽기 안전성 사이의 간격을 관리하는 운영 시스템으로 읽는 것이다.


한 장으로 보는 MySQL 복제 경로

Client Write transaction commit read-after-write expectation Source MySQL redo/binlog GTID Replica MySQL relay log applier Async source commits first replica may be behind Semi-sync waits for receive + log ack not necessarily applied 운영 판단 포인트 GTID gap · network lag · apply lag · read routing · failover candidate · stale read 허용 범위 COMMIT binlog stream apply semi-sync ack point
MySQL 복제 운영에서 봐야 할 네 구간

그림에서 가장 중요한 구분은 relay log에 기록됨replica 데이터에 적용됨이 다르다는 점이다. semi-sync는 source가 commit 응답을 늦추면서 적어도 하나의 replica가 이벤트를 받았다는 안전성을 더한다. 하지만 그 replica의 SQL applier가 이미 실행을 끝냈다고 보장하지는 않는다. 따라서 semi-sync를 켰더라도 replica read의 최신성은 별도로 관리해야 한다.


GTID: 파일 position 대신 트랜잭션 집합으로 말하기

예전 방식의 복제 운영은 mysql-bin.000123의 몇 번째 position까지 읽었는지, relay log의 어디까지 실행했는지를 맞추는 일이 많았다. GTID는 이 문제를 트랜잭션 식별자 단위로 바꾼다.

GTID는 일반적으로 다음 형태다.

server_uuid:transaction_id

MySQL 8.4 문서에 따르면 source에서 commit되어 binlog에 기록된 클라이언트 트랜잭션은 단조 증가하는 GTID를 받는다. replica에서 재생되는 트랜잭션은 원래 source에서 받은 GTID를 유지한다. 이미 실행된 GTID를 다시 적용하려고 하면 auto-skip 동작으로 중복 실행을 피한다.

운영자가 GTID에서 얻는 이점은 세 가지다.

운영 작업position 기반 사고GTID 기반 사고
failover 후보 판단파일명과 position 비교gtid_executed 집합 비교
복제 재구성새 source의 binlog 위치를 찾아야 함실행하지 않은 GTID를 이어서 받도록 구성
중복 적용 방지실수로 같은 event를 재생할 위험이미 실행한 GTID는 auto-skip

다만 GTID가 모든 운영 실수를 없애지는 않는다. GTID set이 맞더라도 다음 문제는 여전히 별도 판단이 필요하다.

  • replica가 source보다 뒤처져 있으면 최신 데이터 읽기는 안전하지 않다.
  • 잘못된 replication filter는 의도적으로 일부 데이터를 빼므로 GTID만 보고 데이터 동등성을 단정할 수 없다.
  • 장애 후 이전 source를 다시 붙일 때는 split-brain과 divergent transaction을 확인해야 한다.
  • RESET BINARY LOGS AND GTIDS 같은 명령은 복제 이력 자체를 바꾸므로 절차 없이 실행하면 안 된다.

기본 점검은 다음처럼 시작한다.

SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';
SHOW VARIABLES LIKE 'server_uuid';
SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
SHOW REPLICA STATUS\G

SHOW REPLICA STATUS는 익숙하지만, 자동화나 대시보드에서는 Performance Schema replication tables를 같이 보는 편이 좋다. MySQL 문서는 replication_connection_status, replication_applier_status, replication_applier_status_by_worker 같은 테이블이 복제 연결, applier 상태, worker별 오류를 더 구조화된 형태로 제공한다고 설명한다.


Async와 semi-sync의 차이는 장애 시점에서 드러난다

비동기 복제의 장점은 단순하고 빠르다는 것이다. source는 binlog에 기록하고 commit을 완료한 뒤 클라이언트에 응답한다. replica가 언제 가져가고 언제 적용하는지는 source commit의 성공 조건이 아니다. 정상 상태에서는 이 구조가 읽기 확장과 분석 부하 분리에 유리하다.

문제는 source가 commit 응답을 돌려준 직후 장애가 나는 경우다. 그 트랜잭션이 아직 어떤 replica에도 전송되지 않았다면, replica를 승격했을 때 방금 성공한 쓰기가 사라진 것처럼 보일 수 있다. 비동기 복제에서 이 위험은 구조적으로 존재한다.

semi-synchronous replication은 이 지점을 줄인다. source는 적어도 하나, 또는 설정된 수의 replica가 이벤트를 받아 relay log에 기록했다는 ACK를 받을 때까지 commit 경로에서 기다린다. 그래서 commit 성공이 반환된 트랜잭션은 적어도 한 replica에 전송되었다는 더 강한 신호를 갖는다.

하지만 운영자가 기억해야 할 제한도 뚜렷하다.

  1. ACK는 적용 완료가 아니라 수신 및 기록에 가깝다.
  2. ACK를 줄 replica가 없거나 timeout이 나면 source가 비동기 모드로 돌아갈 수 있다.
  3. semi-sync는 latency를 증가시킨다. replica나 네트워크가 흔들리면 commit 지연이 사용자 요청 지연으로 드러난다.
  4. failover 뒤에는 실패한 이전 source를 아무 검증 없이 다시 source로 쓰면 안 된다. 공식 문서도 semi-sync failover 후 실패한 source에는 ACK되지 않은 트랜잭션이 있을 수 있으므로 폐기해야 한다고 경고한다.

따라서 semi-sync 운영 정책은 단순히 ON/OFF가 아니라 다음 질문으로 정해야 한다.

질문운영 판단
ACK를 몇 개 요구할 것인가AZ 장애, replica 수, commit latency 예산과 함께 결정
timeout 후 async 전환을 허용할 것인가일시 장애 때 가용성을 택할지, commit 안전성을 택할지 결정
ACK replica를 어떻게 감시할 것인가semi-sync status, source commit latency, replica I/O 상태를 함께 봄
failover 자동화가 GTID를 검증하는가가장 최신이며 안전한 replica만 승격해야 함

Replication lag은 하나의 숫자가 아니라 세 구간의 문제다

복제 지연을 Seconds_Behind_Source 같은 하나의 숫자로만 보면 대응이 늦어진다. 지연은 보통 다음 세 구간 중 하나에서 생긴다.

  1. source가 binlog를 만들고 보내는 구간
  2. replica가 네트워크로 받아 relay log에 저장하는 구간
  3. replica applier가 relay log를 실제 테이블에 적용하는 구간

Google Cloud SQL 문서는 MySQL read replica의 lag을 primary가 변경을 빨리 보내지 못하거나, replica가 빨리 받지 못하거나, replica가 빨리 적용하지 못할 때 발생한다고 나눈다. network_lag와 전체 replica_lag를 분리해서 보면 수신 문제와 적용 문제를 가르는 데 도움이 된다. 자체 운영 환경에서도 같은 사고법을 적용할 수 있다.

지연 위치흔한 원인확인 방향
source 전송 전큰 트랜잭션, binlog flush 지연, source I/O 포화transaction size, binlog fsync, source CPU/I/O
네트워크/수신네트워크 지연, replica I/O thread 정지, relay log 디스크 포화replication_connection_status, heartbeat, relay log 증가
replica 적용단일 worker 병목, lock wait, missing PK, DDL, replica read 부하replication_applier_status_by_worker, row lock, long SELECT

특히 운영에서 자주 만나는 패턴은 다음과 같다.

큰 트랜잭션

UPDATE, DELETE, bulk load는 source에서 오래 실행된 뒤 commit 순간에 큰 binlog 묶음으로 replica에 도착한다. replica는 그 큰 변경을 재생하느라 갑자기 뒤처진다. 해결은 대개 한 번에 많은 row를 바꾸는 대신 작은 batch로 나누고, 각 batch 사이에 대기와 모니터링을 넣는 것이다.

replica의 읽기 부하

replica를 읽기 확장용으로 쓰면 SQL applier와 사용자 SELECT가 같은 CPU, buffer pool, 디스크를 공유한다. 긴 리포트 SELECT, table scan, 낮은 캐시 hit rate는 applier를 직접 막거나 간접적으로 느리게 만든다. replica를 분석용과 온라인 read용으로 분리하는 이유가 여기에 있다.

missing primary key

row-based replication에서 primary key가 없는 테이블은 변경 대상 row를 찾는 비용이 커질 수 있다. Cloud SQL 문서도 MySQL read replica에서 primary key가 없는 테이블이 row-based replication 성능에 나쁘다고 설명하며, MySQL 8 이상에서는 sql_require_primary_key=ON을 고려하라고 권고한다.

병렬 복제와 lock wait

replica_parallel_workers를 늘리면 적용 처리량이 좋아질 수 있다. 하지만 무조건 많이 늘리면 worker 간 lock wait, timeout, deadlock이 늘 수 있다. Cloud SQL 문서는 대략 replica vCPU 수에 맞춰 시작하되, lock wait가 복제 지연과 같이 튀면 병렬도를 줄이는 방향도 고려하라고 설명한다.

점검 쿼리 예시는 다음과 같다.

SHOW REPLICA STATUS\G

SELECT CHANNEL_NAME, SERVICE_STATE, RECEIVED_TRANSACTION_SET
FROM performance_schema.replication_connection_status;

SELECT CHANNEL_NAME, SERVICE_STATE, REMAINING_DELAY, COUNT_TRANSACTIONS_RETRIES
FROM performance_schema.replication_applier_status;

SELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE,
       LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE,
       APPLYING_TRANSACTION,
       LAST_APPLIED_TRANSACTION
FROM performance_schema.replication_applier_status_by_worker;

중요한 것은 한 번의 값을 캡처하는 것보다 변화율이다. relay log가 계속 쌓이는데 connection은 정상이라면 적용이 밀리는 것이다. worker별 오류가 반복되면 특정 트랜잭션이나 lock 문제가 있을 수 있다. source의 쓰기 피크와 replica lag 그래프가 같은 시간대에 움직이면 batch 크기와 commit 주기를 먼저 본다.


Replica read는 기본적으로 stale read를 허용한다

많은 애플리케이션이 쓰기는 source로, 읽기는 replica로 보낸다. 이 구조는 읽기 부하를 줄이지만, 사용자가 방금 쓴 데이터를 곧바로 다시 읽는 흐름에서는 위험하다. 비동기 복제에서는 source commit이 성공해도 replica가 아직 적용하지 않았을 수 있다. semi-sync를 켰더라도 앞에서 본 것처럼 ACK 지점은 적용 완료가 아니다.

따라서 replica read 안전성은 애플리케이션 정책으로 명시해야 한다.

요구사항권장 라우팅
방금 쓴 값을 반드시 읽어야 함일정 시간 source로 read-your-writes 라우팅
약간 오래된 값 허용lag 임계값 이하 replica에 라우팅
정산, 결제, 재고 같은 강한 일관성source 또는 별도 consistency guard 사용
리포트, 검색, 추천 후보stale read 허용 범위 문서화

실무에서 쓰는 방어책은 몇 가지가 있다.

  1. session stickiness: 쓰기 후 같은 사용자 세션은 짧은 TTL 동안 source에서 읽는다.
  2. lag-aware routing: replica lag이 임계값을 넘으면 해당 replica를 read pool에서 제외한다.
  3. GTID wait: 요청이 특정 GTID 이후의 데이터를 반드시 봐야 한다면 replica가 그 GTID를 실행할 때까지 기다리는 전략을 쓴다. 단, timeout과 사용자 지연을 함께 설계해야 한다.
  4. 업무별 허용치 분리: 관리자 리포트와 사용자 결제 화면의 stale 허용 범위는 다르다.

문제는 read replica가 있으니 읽기는 무조건 replica라는 단순 규칙이다. 운영자가 할 일은 replica read를 금지하는 것이 아니라, 어떤 경로가 오래된 값을 읽어도 되는지 업무별로 분류하고 라우팅 정책에 반영하는 것이다.


Failover 후보를 고를 때 확인할 것

복제 장애에서 가장 위험한 순간은 누가 새 source인가를 결정하는 시간이다. 빠른 승격만 보고 replica를 고르면 데이터 손실이나 split-brain을 만들 수 있다.

최소 점검 항목은 다음과 같다.

항목확인 이유
I/O thread와 applier 상태복제가 실제로 진행 중이었는지 확인
gtid_executed후보 replica가 어느 트랜잭션까지 적용했는지 비교
replication error특정 트랜잭션에서 멈춘 replica를 승격하지 않기 위해
semi-sync ACK 여부commit 성공분이 어디까지 전달되었는지 추정
application write fencing이전 source로 쓰기가 계속 들어가지 않게 차단
read-only/super_read_only승격 전후 쓰기 경로를 통제

failover 절차에서 특히 중요한 것은 fencing이다. 네트워크 분리로 운영자가 source 장애라고 판단했지만 실제 source가 살아 있고 일부 애플리케이션이 계속 쓰고 있다면, 새 source를 승격하는 순간 두 갈래의 쓰기 이력이 생긴다. GTID는 이 차이를 드러내는 데 도움을 주지만, split-brain 자체를 자동으로 막아 주지는 않는다.


운영 런북: lag이 튀었을 때의 순서

복제 지연 알림이 울리면 바로 START REPLICA, STOP REPLICA, worker 수 변경부터 하면 안 된다. 먼저 병목 위치를 좁힌다.

  1. 복제가 멈춘 것인지 느린 것인지 확인한다.

- SHOW REPLICA STATUS\G - Replica_IO_Running, Replica_SQL_Running, 최근 오류 확인

  1. 수신 지연인지 적용 지연인지 나눈다.

- connection status, heartbeat, relay log 증가량, applier worker 상태 확인

  1. source의 쓰기 피크를 확인한다.

- 대량 DML, DDL, 배치, binlog 증가량, commit latency 확인

  1. replica의 local 병목을 본다.

- CPU, I/O, buffer pool hit, long SELECT, lock wait, DDL 대기 확인

  1. 읽기 라우팅을 완화한다.

- lag이 큰 replica를 read pool에서 빼고, 필요한 경로는 source로 우회한다.

  1. 근본 원인을 줄인다.

- batch chunking, primary key 보강, 병렬 복제 조정, replica sizing, 분석 replica 분리 등을 적용한다.

장애 중에는 지연을 빨리 없애는 것보다 잘못된 읽기와 잘못된 failover를 막는 것이 먼저다. replica가 30분 뒤처졌는데도 온라인 read traffic을 계속 받으면, 복제 자체는 eventually catch up하더라도 사용자에게 오래된 상태를 계속 보여줄 수 있다.


마무리: 복제 운영의 핵심 질문

MySQL 복제 운영은 replica가 몇 초 뒤처졌는가만 보는 일이 아니다. 커밋이 어디까지 보장되었는지, 어떤 replica가 어떤 GTID까지 적용했는지, stale read를 어떤 업무에서 허용하는지, failover 시 이전 source를 어떻게 차단하는지를 함께 보는 일이다.

이번 장을 운영 체크리스트로 줄이면 다음과 같다.

  • GTID를 켰다면 gtid_executed를 failover와 복제 재구성의 공통 언어로 삼는다.
  • semi-sync는 데이터 손실 가능성을 줄이지만, replica 적용 완료와 read freshness를 보장하지 않는다.
  • lag은 source 전송, network/relay 수신, replica 적용으로 나누어 본다.
  • replica read는 stale read를 기본값으로 보고, read-your-writes가 필요한 경로는 별도 라우팅한다.
  • failover 전에는 최신성뿐 아니라 fencing과 split-brain 방지를 먼저 확인한다.

복제는 평소에는 투명하게 보인다. 그러나 장애가 났을 때는 가장 먼저 사용자의 데이터 신뢰도를 결정한다. 운영자의 목표는 복제를 빠르게 만드는 것뿐 아니라, 지연과 장애가 생겼을 때 어디까지 안전한지 정확히 말할 수 있게 만드는 것이다.

References

  • MySQL 8.4 Reference Manual, Chapter 19 Replication: https://dev.mysql.com/doc/refman/8.4/en/replication.html
  • MySQL 8.4 Reference Manual, GTID Format and Storage: https://dev.mysql.com/doc/refman/8.4/en/replication-gtids-concepts.html
  • MySQL 8.4 Reference Manual, Semisynchronous Replication: https://dev.mysql.com/doc/refman/8.4/en/replication-semisync.html
  • MySQL 8.4 Reference Manual, Delayed Replication: https://dev.mysql.com/doc/refman/8.4/en/replication-delayed.html
  • MySQL 8.4 Reference Manual, Performance Schema Replication Tables: https://dev.mysql.com/doc/refman/8.4/en/performance-schema-replication-tables.html
  • MySQL 8.4 Reference Manual, Checking Replication Status: https://dev.mysql.com/doc/refman/8.4/en/replication-administration-status.html
  • Google Cloud SQL for MySQL, Replication lag: https://docs.cloud.google.com/sql/docs/mysql/replication/replication-lag