MySQL Shell 없이 구성하는 커뮤니티 복제 운영 기준
MySQL Shell 없이 GTID 기반 커뮤니티 복제를 구성할 때 필요한 초기 적재, 보안, 상태 판정, 재시작과 장애 전환의 운영 기준을 정리한다.
복제 연결을 시작하는 명령은 짧지만, 복제를 운영 가능한 상태로 만드는 조건은 훨씬 많다. 데이터의 출발점과 GTID 이력이 일치해야 하고, 재시작 후에도 역할이 유지되어야 하며, 쓰기 서버가 사라졌을 때 무엇을 근거로 후보를 승격할지 정해져 있어야 한다. 두 스레드가 실행 중이라는 사실만으로 이 조건들이 충족되지는 않는다.
이 글은 MySQL Community Server의 단일 source·단일 writer 비동기 GTID 복제를 대상으로 한다. MySQL Shell의 AdminAPI를 사용하지 않고 mysql 클라이언트, 서버 설정 파일, 백업 도구와 관측 쿼리로 운영하는 기준이다. MySQL Shell 자체도 무료로 사용할 수 있으므로, 이를 쓰지 않는 것이 Community의 필수 제약은 아니다. 수동 구성은 도구 비용을 줄이는 선택이라기보다 토폴로지 관리 책임을 운영팀이 직접 맡는 선택이다.
기준 문법은 MySQL 8.0.26 이후와 8.4 LTS다. 실행 예제는 폐기용 MySQL 8.0 인스턴스에서 검증하며, 8.4 전체 동작을 시험한 것으로 일반화하지 않는다. Group Replication, InnoDB Cluster, InnoDB ReplicaSet의 관리 기능을 이 구성에 자동으로 부여하지 않는다.
1. 복제의 데이터 경로와 운영 제어 경로를 분리한다
source는 커밋한 변경을 binary log에 기록한다. replica의 receiver는 source에서 이벤트를 받아 relay log에 저장하고, applier는 이를 읽어 InnoDB에 적용한다. 병렬 복제에서는 coordinator가 작업을 배분하고 worker가 트랜잭션을 실행한다. 복제를 담당하는 것은 서버 내부 기능이지 MySQL Shell 프로세스가 아니다.
flowchart LR
A[애플리케이션] --> W[단일 쓰기 진입점]
W --> S[source의 커밋과 binary log]
S --> R[replica receiver]
R --> L[relay log]
L --> P[coordinator와 worker]
P --> D[replica InnoDB]
O[운영 제어: 백업·감시·차단·승격] -.-> W
O -.-> S
O -.-> P
반면 노드 목록, 초기 적재, 인증서 교체, 장애 감지, 이전 writer 차단, 새 writer로 연결 전환은 별도의 제어 경로다. 수동 복제에 START REPLICA를 실행했다고 자동 장애 전환이나 quorum이 생기지 않는다. MySQL Router를 설치하는 것만으로 일반 비동기 토폴로지에 안전한 자동 승격이 추가되는 것도 아니다.
비동기 복제에서는 source가 replica의 영속 적용을 기다리지 않고 클라이언트에 커밋 성공을 알릴 수 있다. 따라서 source 장애 시 replica에 도달하지 않은 성공 트랜잭션이 손실될 수 있다. RPO를 0으로 선언하려면 단순 복제 설정 외의 보장 조건이 필요하다. 반동기 복제도 수신 확인과 실제 적용을 구분하고 timeout 이후의 동작을 따로 검토해야 한다.
2. 구성 파일은 역할 선언이며, 기본값에 맡기지 않는다
다음은 새로 구축하는 서버를 위한 설정 예시 구조다. 기존 운영 서버에서 GTID를 전환하는 절차나 설정 파일을 그대로 덮어쓰는 지시가 아니다. server-id와 로그 경로는 노드별로 관리하고, 대상 버전의 옵션 지원 여부와 재시작 절차를 먼저 확인한다.
[mysqld]
server-id=101
log-bin=mysql-bin
binlog-format=ROW
gtid-mode=ON
enforce-gtid-consistency=ON
log-replica-updates=ON
sync-binlog=1
innodb-flush-log-at-trx-commit=1
replica에서는 server-id를 고유한 값으로 바꾸고 다음 역할 설정을 추가한다. 아래 읽기 전용 설정은 계정 초기화와 데이터 복원을 마친 뒤 적용하는 최종 역할 정책이다. 초기화 도구가 관리자 쓰기를 수행하는 단계부터 super_read_only를 강제하면 초기화가 막힐 수 있다. source와 replica를 각각 초기화할 때는 server_uuid도 달라야 한다. 데이터 디렉터리를 복사하면서 auto.cnf까지 무분별하게 재사용하는 방식을 피한다.
[mysqld]
server-id=102
read-only=ON
super-read-only=ON
relay-log=relay-bin
relay-log-recovery=ON
relay-log-purge=ON
skip-replica-start=ON
replica-parallel-workers=4
replica-preserve-commit-order=ON
| 설정 | 채택 이유와 한계 |
|---|---|
log_bin, log_replica_updates |
승격 후보가 변경 이력을 보유하고 하위 replica에 전달할 수 있게 한다. 이 설정만으로 과거 로그가 복원되지는 않는다. |
gtid_mode, enforce_gtid_consistency |
파일 위치 대신 트랜잭션 집합으로 복제 진행 경계를 관리한다. 데이터 일치 검증을 대체하지 않는다. |
sync_binlog=1, innodb_flush_log_at_trx_commit=1 |
커밋 내구성을 우선하는 기준이다. 저장장치의 동기화 보장과 성능을 함께 검토한다. |
super_read_only |
관리 권한으로 발생하는 우발적인 로컬 쓰기까지 억제한다. 복제 applier의 정상 적용은 허용한다. 권한자가 설정을 해제할 수 있으므로 외부 fencing의 대체재는 아니다. |
relay_log_recovery, relay_log_purge |
장애 후 relay log 복구와 정리를 함께 설계한다. 재수신할 source 로그가 이미 삭제되었다면 자동 복구가 불가능할 수 있다. |
skip_replica_start |
재시작 후 승인된 점검 절차가 채널을 시작하도록 한다. 점검 자동화가 없으면 재부팅 때마다 복제가 멈춘 채 남는다. |
replica_parallel_workers |
worker 수를 명시한다. 값 4는 예제이지 용량 산정 결과가 아니며, 더 많은 worker가 처리량을 보장하지 않는다. |
SET GLOBAL로 변경한 값과 재시작 후 값은 같다고 가정하지 않는다. 설정 파일과 SET PERSIST를 혼용한다면 영속 설정의 우선순위를 포함해 변경 이력을 관리해야 한다. 특히 승격 당시 런타임에서 읽기 전용을 해제했더라도 이전 replica 설정 파일이 남아 있으면 재시작 후 역할이 다시 바뀔 수 있다.
8.4는 ROW 기반 복제 방향을 유지하되, 8.0의 튜닝 옵션을 일괄 복사하지 않는다. 예를 들어 8.0의 binlog_transaction_dependency_tracking은 8.4에서 제거되었다. replica_parallel_type은 이와 달리 8.4에서 deprecated된 변수다. 지원 여부와 폐기 상태를 같은 의미로 취급하지 않는다.
3. 최초 연결보다 중요한 것은 초기 데이터와 GTID의 일치다
기존 데이터가 있는 source에 빈 replica를 연결하는 것은 일반적인 초기 적재 절차가 아니다. source에 남아 있는 binary log만으로 전체 데이터가 재구성된다는 보장이 없기 때문이다. 다음 순서를 하나의 작업 단위로 관리한다.
- 복구 가능한 초기 이미지 확보: 같은 시점의 데이터와 복제 시작 경계를 보존한다. 논리 백업은 InnoDB의 일관된 스냅샷과 백업 중 DDL 통제가 필요하고, 물리 백업은 사용 도구의 버전 호환성과 복원 절차가 필요하다.
- 대상 격리와 복원: 애플리케이션 연결을 차단한 새 대상에서 복원하고, 로컬 업무 쓰기를 허용하지 않는다. 복원 과정의 관리자 쓰기와 최종 읽기 전용 정책을 구분한다.
- GTID 이력 확인: 데이터에 포함된 변경과 복원된 GTID 집합이 같은 출발점을 가리키는지 확인한다.
gtid_purged를 오류를 없애기 위한 숫자처럼 임의 조정하지 않는다. - 보안 채널 구성과 시작: 계정, TLS, 접속 경로를 검증한 뒤 auto-position을 사용한다. 계정 생성 명령과 비밀번호가 셸 이력·작업 로그에 노출되지 않게 한다.
- 적용 경계와 업무 데이터 대조: source에서 정한 경계까지 적용했는지 확인하고, 핵심 테이블의 행·합계·업무 불변조건을 별도로 비교한다.
mysqldump --single-transaction은 트랜잭션 테이블의 일관된 읽기를 위한 도구이지, MyISAM이나 동시 DDL까지 포함한 무조건적인 전체 백업 보장이 아니다. 부분 DB 덤프에서는 포함된 데이터 범위와 덤프에 기록되는 서버 GTID 이력이 다를 수 있다. --set-gtid-purged 정책을 모르고 다른 목적의 기존 서버에 복원하면, 해당 서버에 실제 데이터가 없는 트랜잭션을 실행 이력에 포함시킬 수 있다.
아래는 환경별로 작성해야 하는 채널 설정 템플릿이다. 초기 적재가 끝난 replica에서만 검토하며, 자리표시자를 포함하므로 실행 검증된 복사·붙여넣기 예제가 아니다. 인증서와 비밀번호는 승인된 비밀정보 전달 경로로 주입한다. 이 글의 격리 실험에서는 TLS 암호화 연결을 확인했지만, 운영 CA와 호스트명 검증까지 재현한 것은 아니다.
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='<인증서와 일치하는 source DNS 이름>',
SOURCE_PORT=3306,
SOURCE_USER='<전용 복제 계정>',
SOURCE_PASSWORD='<보안 경로로 주입>',
SOURCE_AUTO_POSITION=1,
SOURCE_SSL=1,
SOURCE_SSL_CA='<신뢰 CA 파일 경로>',
SOURCE_SSL_VERIFY_SERVER_CERT=1
FOR CHANNEL 'main';
START REPLICA FOR CHANNEL 'main';
복제 계정은 전용으로 분리하고 REPLICATION SLAVE 권한, 허용 접속 출발지, TLS 요구를 검토한다. 애플리케이션 계정에 복제 관리 권한을 주지 않는다. 상태 조회 계정과 채널 변경·중지 권한을 가진 계정도 분리한다. GET_SOURCE_PUBLIC_KEY는 인증에 사용하는 RSA 공개키 취득 옵션이지 TLS 암호화와 서버 신원 검증의 대체재가 아니다.
4. 정상 판정은 설정·수신·적용·데이터 네 단계로 한다
다음 쿼리는 표준 MySQL 8.0 테스트 인스턴스에서 문법과 객체를 확인하고, 별도의 두 인스턴스 실험에서 동일 쿼리를 다시 실행한다. 공개 결과 중 설정과 채널 상태는 실제 복제를 구성한 replica에서 얻은 값이다. 복제 채널을 만들지 않은 서버에서는 채널 조회가 빈 결과를 반환하며, 이를 정상 복제로 판정해서는 안 된다.
4.1 재시작 가능한 역할인지 확인한다
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_variables
WHERE VARIABLE_NAME IN (
'server_id', 'gtid_mode', 'enforce_gtid_consistency',
'log_bin', 'log_replica_updates', 'read_only', 'super_read_only',
'relay_log_recovery', 'skip_replica_start',
'replica_parallel_workers', 'replica_preserve_commit_order'
)
ORDER BY VARIABLE_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT VARIABLE_NAME, VARIABLE_VALUE
-> FROM performance_schema.global_variables
-> WHERE VARIABLE_NAME IN (
-> 'server_id', 'gtid_mode', 'enforce_gtid_consistency',
-> 'log_bin', 'log_replica_updates', 'read_only', 'super_read_only',
-> 'relay_log_recovery', 'skip_replica_start',
-> 'replica_parallel_workers', 'replica_preserve_commit_order'
-> )
-> ORDER BY VARIABLE_NAME;
+-------------------------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+-------------------------------+----------------+
| enforce_gtid_consistency | ON |
| gtid_mode | ON |
| log_bin | ON |
| log_replica_updates | ON |
| read_only | ON |
| relay_log_recovery | ON |
| replica_parallel_workers | 4 |
| replica_preserve_commit_order | ON |
| server_id | 102 |
| skip_replica_start | ON |
| super_read_only | ON |
+-------------------------------+----------------+
11 rows in set (0.00 sec)
이 조회는 런타임 값이다. 설정 파일과 값이 일치하는지는 별도로 확인해야 하며, 최종 검증은 재시작 후 반복한다. 읽기 전용 여부뿐 아니라 후보의 binlog 보유, relay log 복구, 재시작 시 채널 시작 정책을 함께 본다.
4.2 수신 연결의 존재와 상태를 확인한다
SELECT c.CHANNEL_NAME,
c.AUTO_POSITION,
c.SSL_ALLOWED,
s.SERVICE_STATE,
s.LAST_ERROR_NUMBER
FROM performance_schema.replication_connection_configuration AS c
JOIN performance_schema.replication_connection_status AS s
ON s.CHANNEL_NAME = c.CHANNEL_NAME
ORDER BY c.CHANNEL_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT c.CHANNEL_NAME,
-> c.AUTO_POSITION,
-> c.SSL_ALLOWED,
-> s.SERVICE_STATE,
-> s.LAST_ERROR_NUMBER
-> FROM performance_schema.replication_connection_configuration AS c
-> JOIN performance_schema.replication_connection_status AS s
-> ON s.CHANNEL_NAME = c.CHANNEL_NAME
-> ORDER BY c.CHANNEL_NAME;
+--------------+---------------+-------------+---------------+-------------------+
| CHANNEL_NAME | AUTO_POSITION | SSL_ALLOWED | SERVICE_STATE | LAST_ERROR_NUMBER |
+--------------+---------------+-------------+---------------+-------------------+
| main | 1 | YES | ON | 0 |
+--------------+---------------+-------------+---------------+-------------------+
1 row in set (0.00 sec)
SERVICE_STATE=ON은 receiver가 실행 중이라는 의미다. 모든 데이터가 적용되었거나 최신 업무 데이터를 읽을 수 있다는 뜻이 아니다. SSL_ALLOWED=YES도 채널의 TLS 설정이므로 이 열 하나로 서버 인증서 검증까지 완료했다고 주장하지 않는다. LAST_ERROR_NUMBER는 현재 상태와 함께 해석하고, 값이 0이 아니면 상세 오류 메시지와 발생 시각을 확인한다.
운영 인벤토리에 main 채널이 있어야 한다면 반환 행이 없는 경우도 장애 조건이다. 수집 쿼리가 성공했다는 사실과 관측 대상이 존재한다는 사실을 분리해야 한다.
4.3 coordinator와 worker를 별도로 관측한다
SELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER
FROM performance_schema.replication_applier_status_by_coordinator
ORDER BY CHANNEL_NAME;
SELECT CHANNEL_NAME,
COUNT(*) AS worker_count,
SUM(SERVICE_STATE = 'ON') AS running_workers,
SUM(LAST_ERROR_NUMBER <> 0) AS workers_with_error
FROM performance_schema.replication_applier_status_by_worker
GROUP BY CHANNEL_NAME
ORDER BY CHANNEL_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER
-> FROM performance_schema.replication_applier_status_by_coordinator
-> ORDER BY CHANNEL_NAME;
+--------------+---------------+-------------------+
| CHANNEL_NAME | SERVICE_STATE | LAST_ERROR_NUMBER |
+--------------+---------------+-------------------+
| main | ON | 0 |
+--------------+---------------+-------------------+
1 row in set (0.00 sec)
mysql> SELECT CHANNEL_NAME,
-> COUNT(*) AS worker_count,
-> SUM(SERVICE_STATE = 'ON') AS running_workers,
-> SUM(LAST_ERROR_NUMBER <> 0) AS workers_with_error
-> FROM performance_schema.replication_applier_status_by_worker
-> GROUP BY CHANNEL_NAME
-> ORDER BY CHANNEL_NAME;
+--------------+--------------+-----------------+--------------------+
| CHANNEL_NAME | worker_count | running_workers | workers_with_error |
+--------------+--------------+-----------------+--------------------+
| main | 4 | 4 | 0 |
+--------------+--------------+-----------------+--------------------+
1 row in set (0.00 sec)
이 쿼리는 병렬 복제를 켠 구성을 전제로 해석한다. worker가 모두 ON이어도 락 대기나 큰 트랜잭션으로 적용이 지연될 수 있다. LAST_ERROR_NUMBER도 과거 오류의 흔적일 수 있으므로 상태·시간·오류 전문을 함께 확인한다. 오류가 있는 worker는 WORKER_ID, LAST_ERROR_MESSAGE, LAST_ERROR_TIMESTAMP를 추가 조회한다.
SHOW REPLICA STATUS의 Seconds_Behind_Source만으로 읽기 트래픽을 허용하거나 승격하지 않는다. 연결 단절 시 NULL이거나, 수신이 뒤처진 상황을 충분히 반영하지 못할 수 있다. coordinator·worker 상태, source에서 확보한 GTID 경계, heartbeat의 갱신 시각, 핵심 업무 데이터 검증을 결합한다. heartbeat 역시 시계 오차와 측정 쿼리 부하를 고려해야 한다.
5. GTID는 진행 경계이지 데이터 체크섬이 아니다
source에서 쓰기를 차단하고 얻은 최종 집합을 S, 후보의 실행 집합을 C라고 하자. GTID_SUBSET(S, C)=1은 후보가 해당 실행 이력을 포함한다는 뜻이다. GTID_SUBTRACT(C, S)가 비어 있지 않으면 후보 고유 이력이 있는지 조사한다. 필터링, 빈 GTID 커밋, binlog를 남기지 않은 로컬 변경이 있었다면 집합이 맞아도 데이터가 같다고 할 수 없다.
다음은 가상의 UUID와 경계를 사용하는 집합 함수의 실행 가능한 축소 예제다. 실제 운영 이력을 측정한 숫자는 아니며, 누락과 초과가 서로 다른 검사라는 점을 보여준다.
SET @source_boundary = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-12';
SET @candidate = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10';
SELECT GTID_SUBSET(@source_boundary, @candidate) AS caught_up,
GTID_SUBTRACT(@source_boundary, @candidate) AS missing_gtids;
SET @candidate = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-13';
SELECT GTID_SUBSET(@source_boundary, @candidate) AS caught_up,
GTID_SUBTRACT(@candidate, @source_boundary) AS extra_gtids;
실행 결과(MySQL 8.0.x):
mysql> SET @source_boundary = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-12';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @candidate = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT GTID_SUBSET(@source_boundary, @candidate) AS caught_up,
-> GTID_SUBTRACT(@source_boundary, @candidate) AS missing_gtids;
+-----------+--------------------------------------------+
| caught_up | missing_gtids |
+-----------+--------------------------------------------+
| 0 | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:11-12 |
+-----------+--------------------------------------------+
1 row in set (0.00 sec)
mysql> SET @candidate = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-13';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT GTID_SUBSET(@source_boundary, @candidate) AS caught_up,
-> GTID_SUBTRACT(@candidate, @source_boundary) AS extra_gtids;
+-----------+-----------------------------------------+
| caught_up | extra_gtids |
+-----------+-----------------------------------------+
| 1 | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:13 |
+-----------+-----------------------------------------+
1 row in set (0.00 sec)
후보에 13번 이력이 있으면 caught_up은 참이면서 extra_gtids는 비어 있지 않을 수 있다. 따라서 “source를 포함한다”와 “source와 같은 계보의 안전한 후보다”를 동일시하지 않는다. 별도 UUID의 이력이 있다면 초기화·관리 작업인지, 독립적인 업무 쓰기인지 분류해야 한다.
실제 전환에서는 source에서 S를 확보한 뒤 후보에서 WAIT_FOR_EXECUTED_GTID_SET으로 한정된 시간만 기다릴 수 있다. 반환값 0은 해당 경계까지 실행되었다는 뜻이고 1은 timeout이다. source의 쓰기가 계속 진행 중이면 한 번 확보한 S 이후에도 변경이 생기므로, 이 성공만으로 전환 준비가 끝나지 않는다.
6. 격리 실험으로 확인할 최소 운영 계약
두 개의 폐기용 MySQL 8.0.46 인스턴스를 외부 포트 없이 구성하여 다음 항목을 확인했다. 빈 인스턴스에서 만든 소량의 테스트 데이터만 사용하므로, 대용량 백업 복원과 운영 네트워크 단절을 재현한 시험은 아니다. replica의 읽기 전용 설정은 초기화를 완료한 뒤 SET PERSIST로 영속화했으며, 데이터 볼륨을 유지한 컨테이너 재시작으로 재확인했다. 따라서 위 설정 파일의 배포 절차 자체를 시험한 것과는 구분한다.
| 시험 | 실제 관측 및 통과 기준 |
|---|---|
| 초기 복제 | source에서 만든 첫 행이 replica에 나타났고, 지정 GTID 경계 대기 결과가 0이며 후보의 초과 GTID 집합이 비어 있었다. |
| 암호화 연결 | source의 복제 연결에서 Ssl_cipher가 비어 있지 않았다. 운영 CA·호스트명 검증은 시험 범위에서 제외했다. |
| 적용만 중단 | SQL thread를 멈춘 뒤 두 번째 행을 source에 추가했다. receiver는 ON이고 해당 GTID를 수신했지만 replica 행 수는 1이며 실행 중 worker는 0이었다. |
| 로컬 쓰기 차단 | replica에 직접 INSERT를 시도하자 super-read-only에 의한 오류 1290으로 거부되었다. |
| 적용 재개 | SQL thread 재시작 후 GTID 경계 대기 결과가 0이 되었고 두 번째 행이 나타났다. |
| 서버 재시작 | 읽기 전용 두 변수가 모두 1로 유지되고 기존 두 행이 보존되었다. receiver는 정책대로 OFF였다. |
| 승인 후 채널 시작 | 세 번째 행을 source에 추가하고 채널을 시작했다. 최종 세 행의 ID와 값이 양쪽에서 정확히 일치했다. |
최종 데이터 대조 결과는 다음과 같다. MySQL batch 출력의 열 구분만 표로 옮겼으며, 가상의 성공 결과가 아니라 실험에서 조회한 값이다.
| id | value_text |
|---|---|
| 1 | initial |
| 2 | after_pause |
| 3 | after_restart |
이 시험에서 중요한 것은 “접속 가능”보다 수신은 정상인데 적용은 멈춘 상태를 구별하는 것, 그리고 재시작 후 채널이 자동 시작되지 않는 정책을 운영 절차로 마무리하는 것이다. skip_replica_start=ON을 안전 설정으로 넣고 시작 절차를 빠뜨리면 오히려 장기 지연을 만든다.
실제 서비스 투입 전에는 여기에 디스크 공간 부족, source 로그 보존 기간 초과, 인증서 교체, 대용량 초기 적재, 백업 복원, 애플리케이션 연결 전환을 추가 시험한다. 로컬 컨테이너의 정상 결과로 그 시험을 대체하지 않는다.
7. 중단·재시작·승격은 서로 다른 절차다
재시작
설정과 디스크 상태를 점검하고 서버를 시작한 뒤 read_only와 super_read_only, GTID, 채널 구성을 확인한다. skip_replica_start=ON이면 승인된 절차에서 채널을 시작한다. receiver와 applier가 시작되었는지, 오류와 backlog가 해소되는지까지 확인해야 재시작이 완료된다.
binary log 보존은 “며칠이면 충분하다”가 아니라 최대 장애 시간, 초기 적재 시간, 추격 시간, 안전 여유로 산정한다. replica가 필요로 하는 로그가 source에서 삭제되었다면 GTID auto-position도 없는 로그를 생성하지 못한다. 백업과 별도 보존 로그를 이용한 재구성 여부를 판단해야 한다.
계획된 전환
- 애플리케이션 쓰기를 멈추고 기존 연결·진행 중 트랜잭션을 정리한다.
- 이전 writer를 쓰기 금지 상태로 만들고 실제 쓰기 거부를 확인한다.
- 최종 GTID 경계 S를 확보하고 후보가 S를 실행했는지 확인한다.
- 후보 고유 이력과 업무 데이터를 검사한 뒤 복제를 정지한다.
- 이전 writer에 대한 외부 차단을 유지하며 후보의 쓰기 역할과 진입점을 전환한다.
- 제한된 업무 쓰기로 검증한 후 트래픽을 재개하고 영속 설정과 인벤토리를 갱신한다.
비계획 장애 전환
source에 접속할 수 없다는 사실은 source가 쓰기를 수행할 수 없다는 증거가 아니다. 다른 네트워크 경로에서 기존 애플리케이션이 계속 쓰고 있을 수 있다. 이전 writer의 전원·스토리지·네트워크·접속 경로를 통해 실효성 있는 fencing을 확보하지 못하면, 새 writer를 열 때 이중 쓰기가 발생할 수 있다.
source의 최종 경계를 얻을 수 없는 경우에는 replica가 받은 이력과 적용 이력, 다른 후보의 상태를 비교하고 손실 가능성을 명시적으로 승인해야 한다. 실패한 source를 되살린 뒤 바로 역방향 복제를 연결하지 않는다. 새 writer 이후의 계보를 기준으로 재구축할지 판정한다.
오류 1062나 1032를 만났을 때 무조건 GTID를 스킵하거나 채널을 초기화하는 대응도 금지한다. 실패 트랜잭션의 업무 변경과 감사 기록이 빠질 수 있다. RESET REPLICA ALL은 채널 정보를 지우는 작업이지 불일치 데이터를 수리하는 명령이 아니다.
8. Aurora MySQL에 같은 절차를 그대로 적용하지 않는다
Aurora 클러스터 내부 reader는 일반 Community MySQL 서버에 수동 구성한 binlog replica와 다르다. 클러스터의 공유 스토리지 및 관리형 복제·장애 전환 구조를 사용하므로, 내부 reader에 이 글의 CHANGE REPLICATION SOURCE TO를 적용하는 방식으로 구성하지 않는다.
Aurora와 외부 MySQL 사이의 binary log 복제는 별도 구성이다. 이때는 지원 엔진 버전, 클러스터 파라미터 그룹, binlog 활성화·보존, AWS가 제공하는 복제 관리 저장 프로시저와 권한 모델을 확인한다. 호스트의 my.cnf를 직접 편집하거나 Community의 관리자 권한이 그대로 제공된다고 가정하지 않는다.
Aurora reader의 읽기 지연과 외부 binlog replica의 수신·적용 지연도 같은 지표가 아니다. 내부 장애 전환은 클러스터 endpoint와 승격 정책을, 외부 복제는 source 연결·GTID·필터·로그 보존을 중심으로 운영 기준을 따로 만든다.
9. 서비스 투입 승인 체크리스트
- 노드별
server_id와server_uuid
결론
MySQL Shell 없이도 Community MySQL의 GTID 복제를 운영할 수 있다. 다만 복제 명령의 대체가 아니라 초기 상태, 보안, 관측, 재시작, 단일 writer 유지라는 운영 계약을 직접 구현하는 일이다. SQL 실행 성공과 안전한 서비스 상태를 구분하고, 각 단계의 완료 조건을 데이터로 증명해야 한다.
이 기준은 이후 백업·시점 복구와 고가용성 설계의 출발점이 된다. 복제는 변경을 전달하고, 백업은 과거 상태를 보존하며, 장애 전환 제어는 누가 쓰기를 수행할지 결정한다. 세 기능을 구분해야 복제가 있다는 이유로 복구와 가용성을 과대평가하지 않는다.