MySQL 8 GTID·TLS Primary Replica 구축과 수동 Failover 선택
MySQL 8 GTID·TLS 복제, Read Only Replica와 수동 Failover를 선택한 이유, 복제 비밀번호 제한과 일시적 Connecting 상태를 해결한 과정을 정리했다.
프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: MySQL 8 GTID·TLS Primary/Replica와 수동 Failover
MeetBack의 database는 MySQL 8.0.46 두 대를 사용해 Primary와 Replica로 구성했다. 목표는 단순히 두 server에 같은 data가 보이게 만드는 것이 아니라, 평상시 쓰기 경로를 하나로 통제하고 Primary 장애 시 확인 가능한 복제본과 수동 전환 절차를 갖추는 것이었다.
자동 Failover보다 수동 승격을 선택한 이유
처음에는 Primary가 내려가면 Replica가 자동으로 쓰기를 받게 만들 수 있는지 고민했다. 하지만 두 node만으로 장애를 판단하면 실제 Primary 장애와 network partition을 안전하게 구분하기 어렵다. 잘못된 자동 승격은 두 server가 동시에 Primary처럼 동작하는 split-brain으로 이어질 수 있다.
자동 failover를 제대로 하려면 quorum, health 판단, client endpoint 전환, fencing과 기존 Primary 재편입까지 함께 설계해야 한다. 프로젝트 규모에서는 복잡도에 비해 얻는 이점이 크지 않다고 판단했다.
그래서 다음 원칙을 정했다.
- application write는 DB1 Primary에만 보낸다.
- DB2는
read_only=ON,super_read_only=ON으로 보호한다. - 장애 시 replication과 GTID를 확인한 뒤 사람이 DB2를 승격한다.
- endpoint, backup timer, monitoring role label을 하나의 failover 절차로 전환한다.
이 구조는 자동 HA가 아니다. 대신 장애 중 무엇을 확인하고 어떤 위험을 감수하는지 분명하다.
GTID 기반 복제를 구성했다
두 server에 서로 다른 server_id를 부여하고 다음 항목을 활성화했다.
[mysqld]
server-id = <unique-id>
log_bin = mysql-bin
gtid_mode = ON
enforce_gtid_consistency = ON
log_replica_updates = ON
Replica는 특정 binary log filename과 position을 수동으로 추적하지 않고 SOURCE_AUTO_POSITION=1로 연결했다. GTID는 실행된 transaction을 source UUID와 sequence로 식별하므로 failover와 재편입 시 어느 transaction까지 적용됐는지 확인하기 쉽다.
복제 계정은 Replica의 private address에서만 접속할 수 있게 만들고 TLS를 요구했다. 연결 시험에서 실제 cipher가 협상되는지 확인한 뒤 channel을 구성했다.
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = '<primary-private-address>',
SOURCE_PORT = 3306,
SOURCE_USER = '<replication-user>',
SOURCE_PASSWORD = '<secret>',
SOURCE_AUTO_POSITION = 1,
SOURCE_SSL = 1;
START REPLICA;
첫 번째 문제: 일반 로그인은 되는데 Replication Channel은 실패했다
처음 만든 복제 비밀번호는 충분히 긴 random 문자열이었다. MySQL client로 Primary에 TLS 로그인하는 데는 성공했다. 하지만 Replica에서 CHANGE REPLICATION SOURCE TO를 실행하자 다음 오류가 발생했다.
ERROR 3056 (HY000):
The password provided for the replication user exceeds the maximum length of 32 characters
이어 실행한 START REPLICA도 실패했다.
ERROR 1200 (HY000):
The server is not configured as replica
원인은 network나 계정 권한이 아니었다. 일반 인증에는 사용할 수 있던 비밀번호가 replication metadata에 저장 가능한 길이를 초과했다. 앞의 CHANGE REPLICATION SOURCE TO가 실패했기 때문에 channel 자체가 생성되지 않았고, 시작할 대상도 없었다.
복제 전용 비밀번호를 제한 안에서 다시 생성하고 Primary 계정과 Replica channel 양쪽을 함께 변경했다. 이 경험으로 “MySQL 로그인 성공”과 “replication channel 구성 성공”이 같은 검증이 아니라는 것을 배웠다.
복제가 실제로 동작하는지 확인했다
SHOW REPLICA STATUS\G에서 다음을 기준으로 확인했다.
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 0
Last_IO_Error:
Last_SQL_Error:
Auto_Position: 1
IO thread는 source에서 event를 가져오고 SQL thread는 relay log를 실행한다. SQL thread가 Yes라고 해서 현재 source network 연결까지 정상이라는 뜻은 아니다. 두 thread를 따로 봐야 한다.
Primary에 probe table과 row를 만들고 Replica에서 같은 data와 gtid_executed를 확인했다. Replica에서 insert를 시도해 super_read_only 오류가 발생하는 것도 확인했다. 단순히 변수만 ON인지 보는 것보다 실제 write 차단을 시험한 것이다.
두 번째 문제: 주소 전환 직후 IO Thread가 Connecting이었다
DB network를 private network로 바꾼 직후 Replica 상태가 다음처럼 보였다.
Replica_IO_Running: Connecting
Replica_SQL_Running: Yes
Seconds_Behind_Source: NULL
Last_IO_Error: Can't connect to MySQL server ... (111)
잠시 뒤 별도 변경 없이 Yes/Yes, lag 0으로 회복됐다. Source MySQL이 listener를 다시 여는 시점보다 Replica의 첫 연결 시도가 빨랐고, channel은 설정된 간격으로 재연결을 시도하고 있었다.
이때 바로 RESET REPLICA ALL을 실행했다면 정상 channel metadata까지 지우고 더 큰 작업을 만들 수 있었다. 나는 다음 순서로 확인했다.
- Primary MySQL service가 active인지 확인
- private address의 3306 listener 확인
- Replica에서 TCP 연결 확인
- 재시도 주기를 기다린 뒤 status 재확인
- 계속 실패할 때만 account Host, TLS, route와 source 설정 조사
재시작 직후의 일시적인 Connecting과 지속 장애를 구분한 것이 핵심이었다.
DB망 전환 시 관련 설정을 한 번에 바꿨다
DB address를 바꾸면 MySQL bind만 수정해서는 끝나지 않는다.
- Replica
SOURCE_HOST - replication account의 Host
- application·migration account Host
- mysqld_exporter endpoint
- Prometheus target과 role label
- NFS backup export client
- monitoring server의 route
MySQL 기본 설정에는 loopback bind가 남아 있었고 별도 zz- 설정 파일이 뒤에서 private address로 override하고 있었다. 그래서 파일 검색 결과만 믿지 않고 runtime을 확인했다.
sudo ss -lntp '( sport = :3306 )'
sudo mysql -NBe "SHOW VARIABLES LIKE 'bind_address';"
최종 listener는 private address 하나만 사용했다.
Failover는 DB 명령만의 문제가 아니었다
수동 failover runbook에는 다음 항목을 함께 넣었다.
1. Primary가 실제로 중지됐는지 확인
2. Replica IO/SQL 상태와 GTID 비교
3. 남은 relay log 적용 확인
4. Replica의 read_only와 super_read_only 해제
5. application DB endpoint 전환
6. backup timer를 새 Primary로 이동
7. Prometheus role label 전환
8. 기존 Primary가 돌아오면 바로 쓰기 허용하지 않고 Replica로 재편입
아직 실제 Primary 장애부터 원래 구조로 돌아오는 전체 훈련은 남아 있다. runbook 작성과 실제 disaster recovery 검증은 구분해야 한다.
MySQL 복제를 구축하며 얻은 가장 큰 교훈은 thread가 두 개라는 사실이나 GTID 문법보다 역할 전환의 영향 범위였다. database role이 바뀌면 application, backup, monitoring도 함께 바뀌어야 실제 운영 상태가 일치한다.