테마
복제 토폴로지와 고급 설정
복제(replication) 토폴로지(topology)는 소스(source)와 레플리카(replica)를 어떤 모양으로 잇느냐이고, 모양에 따라 읽기 확장성과 장애 영향 범위가 달라진다. 이 문서는 대표 모양 다섯 가지와 지연된 복제(delayed replication), 필터링된 복제(filtered replication)를 다룬다.
대표 토폴로지
| 모양 | 연결 | 주 용도 | 조심할 점 |
|---|---|---|---|
| 싱글 레플리카 | 소스 1 → 레플리카 1 | 대기 서버·백업 | 레플리카가 죽으면 대비책이 없다 |
| 멀티 레플리카 | 소스 1 → 레플리카 N | 읽기 분산 | 레플리카마다 소스가 전송 부담 |
| 체인 | 소스 → 중간 → 하위 | 소스 전송 부담 분산 | 중간 서버 장애가 하위 전체에 번짐 |
| 듀얼 소스 | 두 서버가 서로의 레플리카 | 빠른 전환 | 양쪽 쓰기 충돌을 감지하지 않음 |
| 멀티 소스 | 소스 N → 레플리카 1 | 모아서 백업·분석 | 소스 간 충돌을 감지하지 않음 |
멀티 레플리카는 쓰기는 소스가, 읽기는 여러 레플리카가 나눠 받는 가장 흔한 모양이다. 소스는 레플리카마다 덤프 스레드(dump thread)를 하나씩 띄워 바이너리 로그(binary log)에 적힌 변경 기록(이벤트)을 보내므로, 레플리카가 많아지면 소스의 네트워크와 CPU 부담이 커진다.
체인(계단식 복제, chained replication)은 그 부담을 줄이려고 레플리카 하나를 중간 소스로 쓰는 모양이다. 중간 서버는 소스에서 받아 적용한 변경을 자기 바이너리 로그에도 다시 적어야 하위 레플리카에 넘길 수 있다. 이를 정하는 log_replica_updates는 MySQL 8.4에서 기본으로 켜져 있다. 대신 단계가 늘수록 지연이 쌓인다.
듀얼 소스(소스-소스 복제)는 두 서버가 서로를 소스로 삼는다. 한쪽에만 쓰고 다른 쪽은 대기시키는 방식이면, 장애 때 반대편으로 쓰기만 옮기면 되므로 전환이 빠르다. 양쪽에 동시에 쓰면, MySQL 비동기 복제(asynchronous replication)는 두 서버가 같은 행을 고쳐도 충돌을 감지하지 않아 데이터가 조용히 어긋난다. AUTO_INCREMENT 값이 겹치는 문제는 auto_increment_increment(증가 폭)와 auto_increment_offset(시작값)을 서버마다 다르게 주어 피하지만, 같은 행 충돌까지 막아 주지는 않는다. 자기가 보낸 이벤트가 되돌아와도 서버는 이벤트에 적힌 고유 번호 server_id가 자기 것이면 적용하지 않고, GTID(Global Transaction Identifier, 전역 트랜잭션 식별자)를 쓰면 이미 적용한 트랜잭션도 건너뛴다. 여러 서버에 동시에 써야 하면 충돌을 감지하는 그룹 복제의 멀티 프라이머리 모드(multi-primary mode)나 샤딩(sharding)을 검토한다.
멀티 소스 복제(multi-source replication)는 레플리카 한 대가 여러 소스에서 동시에 받는 모양이다. 소스마다 복제 채널(replication channel)을 하나씩 만들고, 채널마다 변경을 받아 릴레이 로그(relay log)에 적는 리시버 스레드(receiver thread)와 그 기록을 적용하는 어플라이어 스레드(applier thread)가 따로 돈다. 여러 서버를 모아 백업하거나 샤드를 합쳐 분석할 때 쓰며, 소스 간 충돌 처리는 애플리케이션 몫이다. 반동기 복제(semisynchronous replication)는 이름 없는 기본 채널 하나만 있을 때 쓸 수 있으므로 멀티 소스 복제와 함께 쓸 수 없다.
새 레플리카는 보통 소스의 백업이나, 실행 중인 서버 데이터를 네트워크로 통째로 복사하는 클론(clone)으로 채운 뒤 복제를 연결한다. 암호화된 테이블도 복제로 받는 레플리카는 소스 키가 아니라 자기 키링(keyring)의 키로 따로 암호화한다(데이터 암호화).
지연된 복제
지연된 복제는 레플리카가 소스보다 일부러 정해진 시간만큼 늦게 적용하게 하는 설정이다. 소스에서 누군가 WHERE 없는 DELETE로 테이블 전체를 지웠다고 하자. 일반 레플리카는 몇 초 안에 똑같이 지워지지만, 1시간 늦게 적용하는 레플리카에는 아직 데이터가 남아 있다.
sql
STOP REPLICA;
CHANGE REPLICATION SOURCE TO SOURCE_DELAY = 3600; -- 초 단위, 기본 0
START REPLICA;MySQL 8.4에서 지연은 트랜잭션(transaction) 단위로 걸린다. 받은 트랜잭션은 바로 위 소스(체인이면 중간 서버)에서 커밋(commit)된 시각으로부터 최소 N초가 지나야 적용된다. 실수가 발견되면 지연 레플리카의 적용을 멈추고, START REPLICA UNTIL SQL_BEFORE_GTIDS = '...'로 문제 트랜잭션 직전까지만 적용한 뒤 그 상태에서 데이터를 꺼내 복구한다. 남은 지연 시간은 Performance 스키마(Performance Schema)의 replication_applier_status 테이블 REMAINING_DELAY 칼럼으로 본다.
지연 레플리카는 일부러 늦게 보여 주는 서버이므로 읽기 분산에 쓰면 안 된다. 또 받기는 즉시 하고 적용만 늦추므로, 릴레이 로그가 지연 시간만큼 쌓일 디스크가 필요하다.
필터링된 복제
필터링된 복제는 레플리카가 받은 이벤트 중 일부 데이터베이스나 테이블만 적용하는 설정이다. --replicate-do-db, --replicate-ignore-db, --replicate-do-table 같은 옵션으로 정하고, 실행 중에는 CHANGE REPLICATION FILTER로 바꾼다.
- 소스가 아니라 레플리카에서 거른다. 소스에도 바이너리 로그에서 특정 DB를 빼는
--binlog-do-db가 있지만, 매뉴얼은 이것으로 복제 대상을 조절하지 말라고 안내한다. 바이너리 로그에서 빠진 변경은 어느 레플리카에도 가지 않고, 그 로그로 하는 시점 복구(point-in-time recovery)에서도 빠진다. - 포맷에 따라 판단 기준이 다르다. 바이너리 로그는 실행한 SQL 문장을 적는 문장 기반(statement-based) 포맷이나 바뀐 행 값을 적는 행 기반(row-based) 포맷으로 기록된다(로그 포맷 참고). 문장 기반 로그에서
--replicate-do-db는 문장이 실행될 때의 기본 데이터베이스, 즉USE로 고른 DB를 본다. 그래서USE a; UPDATE b.t ...처럼 다른 DB를 고치는 문장은 의도와 다르게 걸러지거나 통과한다. 행 기반 로그에서는 실제로 바뀐 테이블의 DB를 본다. - 걸러 낸 레플리카는 소스와 데이터가 다르므로 장애 조치(failover) 대상으로 쓰지 않는다. 그룹 복제(Group Replication)는 모든 멤버가 같은 데이터를 가진다는 전제로 트랜잭션 순서와 충돌을 함께 판단하므로 전역 복제 필터를 쓸 수 없다.
면접 포인트
- "레플리카를 늘리면 무엇이 좋아지고 무엇이 나빠지나요?" → 읽기 분산은 좋아지지만 쓰기는 그대로이고, 소스 전송 부담과 레플리카 간 지연 차이가 생긴다.
- "듀얼 소스에 양쪽으로 쓰면?" → 충돌 감지가 없어 조용히 어긋난다.
AUTO_INCREMENT설정은 키 충돌만 피한다. - "실수로 지운 데이터를 복제로 살릴 수 있나요?" → 지연된 복제면 가능. 적용을 멈추고
UNTIL SQL_BEFORE_GTIDS로 직전까지 적용한다. - "필터는 어디서 거나요?" → 레플리카. 문장 기반에서는
USE로 고른 기본 DB 기준이라 함정이 있다.
