GTID 개요: UUID:sequence와 executed/purged set의 의미
MySQL GTID의 UUID:sequence 구조와 gtid_executed·gtid_purged 집합을 복제, 장애 전환, 백업 복구 관점에서 해석한다.
GTID(Global Transaction Identifier)는 복제 트랜잭션을 binary log 파일명과 position이 아니라 원본 서버와 트랜잭션 번호의 조합으로 식별한다. 이 식별자가 있으면 replica는 자신이 이미 실행한 트랜잭션과 아직 받지 못한 트랜잭션을 집합 연산으로 비교할 수 있다. source 교체, replica 재연결, 백업 복원, 장애 전환에서 GTID가 중요한 이유다.
그러나 GTID를 단순히 “복제용 일련번호”로 이해하면 운영 판단을 잘못하기 쉽다. UUID:sequence에서 UUID는 현재 접속한 source가 아니라 트랜잭션이 처음 발생한 서버를 나타내며, sequence는 모든 서버를 관통하는 전역 시간 순서가 아니다. 또한 gtid_executed와 gtid_purged는 각각 “실행 완료 이력”과 “로컬 binary log에서 더 이상 제공할 수 없는 이력”을 표현한다. 두 집합을 구분해야 자동 위치 지정이 가능한지, 누락분을 binary log로 공급할 수 있는지, 새 source 후보에 errant transaction이 있는지를 판단할 수 있다.
1. GTID가 해결하는 문제
file/position 방식에서는 replica가 mysql-bin.000417의 position 9834421까지 실행했다고 기록한다. 이 좌표는 특정 source의 특정 binary log 파일에만 의미가 있다. 장애로 source가 바뀌면 새 source의 파일명과 position은 같은 트랜잭션을 가리키지 않는다. 운영자가 양쪽 binary log를 대조해 공통 실행 지점을 찾아야 한다.
GTID 방식에서는 트랜잭션이 복제 경로를 지나도 원래 GTID를 유지한다. replica가 어떤 GTID 집합을 실행했는지 새 source에 전달하면, 새 source는 자신이 보유한 이력 중 replica에 없는 부분을 계산할 수 있다. SOURCE_AUTO_POSITION=1이 가능한 기반이다.
flowchart LR
C[클라이언트 트랜잭션] --> A[원본 서버 A]
A -->|GTID A-UUID:125 부여| BA[binary log A]
BA --> R[replica B에서 적용]
R -->|같은 GTID 보존| BB[binary log B]
BB --> F[새 replica 또는 하위 replica]
E[gtid_executed<br/>서버가 실행한 전체 집합] --> D{집합 비교}
P[gtid_purged<br/>로컬 binlog에 없는 부분집합] --> D
D --> N[필요 GTID 계산]
D --> V[공급 가능 여부 판단]
D --> X[errant transaction 탐지]
GTID가 file/position을 없애는 것은 아니다. MySQL은 여전히 binary log 파일과 position으로 이벤트를 저장하고 전송한다. GTID는 그 위에 트랜잭션 단위의 논리적 식별 계층을 추가한다. 장애 분석이나 PITR에서는 GTID와 file/position을 함께 보게 되는 경우가 많다.
2. UUID:sequence의 정확한 의미
GTID의 기본 형태는 다음과 같다.
3e11fa47-71ca-11e1-9e33-c80aa9429562:23
콜론 왼쪽은 SID(Source Identifier)이며 일반적으로 트랜잭션을 최초 생성한 서버의 server_uuid다. 오른쪽은 그 SID 안에서 부여된 양의 정수 sequence number다. sequence 0은 GTID에 사용하지 않는다.
2.1 UUID는 현재 source의 주소가 아니다
서버 A에서 생성한 A-UUID:125가 A → B → C 순서로 복제되어도 B와 C가 적용하는 식별자는 계속 A-UUID:125다. B가 relay 역할을 하거나 자신의 binary log에 다시 기록하더라도 원본 SID를 B의 UUID로 바꾸지 않는다.
따라서 한 서버의 gtid_executed에는 여러 UUID 영역이 함께 나타날 수 있다.
A-UUID:1-125,
B-UUID:1-18,
C-UUID:4-9
이는 해당 서버가 A에서 유래한 1~125, B에서 유래한 1~18, C에서 유래한 4~9 트랜잭션을 실행했다는 뜻이다. multi-source replication, source 전환, 이전 primary의 이력, 로컬 쓰기가 섞이면 여러 SID가 자연스럽게 누적된다.
2.2 sequence는 SID 내부 순서다
같은 SID에서 sequence가 증가하면 그 원본 서버가 부여한 commit 순서를 추적하는 데 도움이 된다. 그러나 서로 다른 UUID의 sequence를 숫자만으로 비교해서는 안 된다.
A-UUID:900이B-UUID:100보다 최근이라고 단정할 수 없다.- sequence 차이가 초 단위 지연 시간이나 트랜잭션 수 처리율을 뜻하지 않는다.
A-UUID:1-100과B-UUID:1-100은 전혀 다른 200개 GTID다.- 실제 commit 시각과 적용 완료 시각은 binary log timestamp, replication status, Performance Schema를 함께 봐야 한다.
GTID는 논리적 식별자이지 wall-clock timestamp가 아니다.
3. 단일 GTID에서 GTID set으로 확장하기
운영 상태는 개별 GTID보다 GTID set으로 표현한다. 같은 UUID의 연속 sequence는 구간으로 압축하고, 불연속 구간은 콜론으로 이어 쓴다. 서로 다른 UUID 영역은 쉼표로 구분한다.
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10:15:20-25,
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-7
이 집합은 다음을 포함한다.
- A SID의
1~10 - A SID의
15 - A SID의
20~25 - B SID의
1~7
1-10은 10개 트랜잭션을 의미하지만, sequence 최댓값만 보고 전체 트랜잭션 수를 계산해서는 안 된다. 불연속 구간과 여러 SID가 있을 수 있기 때문이다. 운영 자동화에서는 문자열을 직접 분해하기보다 MySQL의 GTID_SUBSET()과 GTID_SUBTRACT()를 사용한다.
4. gtid_executed: 이 서버가 완료한 이력
@@GLOBAL.gtid_executed는 서버가 실행 완료한 GTID의 집합이다. 일반적으로 다음 이력이 포함된다.
- 이 서버에서 생성되어 commit된 GTID 트랜잭션
- replication applier가 적용한 원본 GTID 트랜잭션
gtid_purged설정을 통해 실행된 것으로 명시적으로 등록된 GTID
핵심은 현재 binary log 파일에 남아 있는가와 무관하게 실행 사실을 나타낸다는 점이다. 오래된 binary log가 삭제되어도 해당 트랜잭션의 GTID가 gtid_executed에서 사라지는 것은 아니다.
서버의 기본 GTID 상태는 다음처럼 확인한다.
SELECT VERSION() AS mysql_version,
@@GLOBAL.server_uuid AS server_uuid,
@@GLOBAL.gtid_mode AS gtid_mode,
@@GLOBAL.enforce_gtid_consistency AS enforce_gtid_consistency;
SELECT @@GLOBAL.gtid_executed AS gtid_executed,
@@GLOBAL.gtid_purged AS gtid_purged;
실행 결과(MySQL 8.0.x):
mysql> SELECT VERSION() AS mysql_version,
-> @@GLOBAL.server_uuid AS server_uuid,
-> @@GLOBAL.gtid_mode AS gtid_mode,
-> @@GLOBAL.enforce_gtid_consistency AS enforce_gtid_consistency;
+---------------+--------------------------------------+-----------+--------------------------+
| mysql_version | server_uuid | gtid_mode | enforce_gtid_consistency |
+---------------+--------------------------------------+-----------+--------------------------+
| 8.0.46 | 8362cdaf-a986-11f1-b3c4-0242ac110010 | OFF | OFF |
+---------------+--------------------------------------+-----------+--------------------------+
1 row in set (0.00 sec)
mysql> SELECT @@GLOBAL.gtid_executed AS gtid_executed,
-> @@GLOBAL.gtid_purged AS gtid_purged;
+---------------+-------------+
| gtid_executed | gtid_purged |
+---------------+-------------+
| | |
+---------------+-------------+
1 row in set (0.00 sec)
검증 컨테이너는 binary log를 끈 독립 인스턴스이므로 gtid_mode=OFF이고 두 집합이 비어 있을 수 있다. 운영 서버에서는 문자열 길이가 매우 커질 수 있으므로 모니터링 수집기가 값을 잘라 저장하지 않는지 확인한다.
mysql.gtid_executed system table도 GTID 구간을 저장한다. 이 테이블은 서버 내부 관리 대상이다. 직접 INSERT, UPDATE, DELETE하여 GTID 이력을 고치려 해서는 안 된다. 운영자는 시스템 변수와 공식 GTID 절차를 사용해야 한다.
5. gtid_purged: 실행했지만 로컬 binlog에는 없는 이력
@@GLOBAL.gtid_purged는 commit된 것으로 서버가 알고 있지만 현재 그 서버의 어떤 binary log 파일에도 존재하지 않는 GTID 집합이다. 항상 gtid_executed의 부분집합이어야 한다.
대표적인 포함 원인은 다음과 같다.
- 해당 GTID가 들어 있던 오래된 binary log가 purge되었다.
- replica가 binary logging 없이 원본 GTID를 적용했다.
- 백업 복원 과정에서
SET @@GLOBAL.gtid_purged로 이미 반영된 이력을 등록했다.
이 정의에서 중요한 운영 결론은 다음 식이다.
로컬 binary log에서 제공 가능한 GTID
= gtid_executed - gtid_purged
아래 예제는 실제 전역 상태를 바꾸지 않고 같은 집합 연산을 재현한다.
SET @executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100';
SET @purged = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-60';
SELECT GTID_SUBSET(@purged, @executed) AS purged_is_subset,
GTID_SUBTRACT(@executed, @purged) AS available_in_local_binlog;
실행 결과(MySQL 8.0.x):
mysql> SET @executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @purged = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-60';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT GTID_SUBSET(@purged, @executed) AS purged_is_subset,
-> GTID_SUBTRACT(@executed, @purged) AS available_in_local_binlog;
+------------------+---------------------------------------------+
| purged_is_subset | available_in_local_binlog |
+------------------+---------------------------------------------+
| 1 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:61-100 |
+------------------+---------------------------------------------+
1 row in set (0.00 sec)
결과에서 purged_is_subset=1이어야 상태 관계가 성립하고, 61-100은 로컬 binary log에 남아 있다고 해석할 수 있는 범위다. 실제 서버에서는 binary log 보존 정책과 purge 시점이 계속 상태를 바꾸므로 같은 시각에 값을 수집해 비교한다.
5.1 purge는 실행 이력을 지우는 작업이 아니다
PURGE BINARY LOGS 또는 보존 정책에 따른 자동 삭제는 파일을 제거한다. 그 파일에만 있던 GTID는 gtid_purged로 이동해 “실행했지만 더 이상 제공할 파일이 없음”을 나타낸다. gtid_executed에서 과거가 사라지는 것이 아니다.
이 차이는 새 replica 구성에서 결정적이다. 새 replica가 A:1-50까지만 실행했고 source가 A:1-80을 이미 purge했다면, source의 최신 gtid_executed가 A:1-100이라고 해도 필요한 51-80을 binary log로 공급할 수 없다. GTID 자동 위치 지정은 좌표를 계산해 주지만 삭제된 이벤트를 복원하지는 않는다.
5.2 gtid_purged는 임의의 복제 오류 우회 수단이 아니다
SET @@GLOBAL.gtid_purged는 백업이 이미 포함한 트랜잭션을 새 인스턴스의 GTID 이력에 등록하는 등 제한된 초기화 절차에 사용한다. 잘못 추가하면 데이터에는 반영되지 않은 트랜잭션을 서버가 실행했다고 믿게 되어 replication applier가 필요한 변경을 건너뛸 수 있다.
MySQL 8.0은 전체 집합 대입과 +를 이용한 추가를 지원하지만, 현재 gtid_executed, gtid_purged, 실행 중인 gtid_owned와의 교집합 제약이 있다. 운영 중인 topology에서 오류를 없애기 위해 값을 맞추는 식으로 사용하지 말고, 백업 메타데이터·데이터 정합성·source의 집합을 함께 검증한 승인된 복구 절차에서만 변경한다.
6. 두 집합을 비교하는 기본 함수
6.1 GTID_SUBSET(set1, set2)
set1의 모든 GTID가 set2에 포함되면 1, 아니면 0을 반환한다. “replica가 기준 집합을 모두 실행했는가” 같은 포함 관계 확인에 적합하다.
SET @required = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10';
SET @candidate = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10,
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-3';
SELECT GTID_SUBSET(@required, @candidate) AS required_is_complete,
GTID_SUBSET(@candidate, @required) AS candidate_is_subset,
GTID_SUBTRACT(@candidate, @required) AS candidate_only;
실행 결과(MySQL 8.0.x):
mysql> SET @required = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @candidate = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-10,
-> bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-3';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT GTID_SUBSET(@required, @candidate) AS required_is_complete,
-> GTID_SUBSET(@candidate, @required) AS candidate_is_subset,
-> GTID_SUBTRACT(@candidate, @required) AS candidate_only;
+----------------------+---------------------+------------------------------------------+
| required_is_complete | candidate_is_subset | candidate_only |
+----------------------+---------------------+------------------------------------------+
| 1 | 0 | bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1-3 |
+----------------------+---------------------+------------------------------------------+
1 row in set (0.00 sec)
required_is_complete=1은 후보가 요구 집합을 모두 포함한다는 뜻이다. 반대 방향인 candidate_is_subset은 B SID의 추가 이력 때문에 0이 된다. 부분집합 검사는 방향이 있는 연산이므로 인자 순서를 이름으로 명확히 표시하는 편이 안전하다.
6.2 GTID_SUBTRACT(set1, set2)
set1에는 있고 set2에는 없는 GTID를 반환한다. 복제 운영에서 가장 자주 사용하는 해석은 다음 두 가지다.
source에는 있고 replica에는 없음
= GTID_SUBTRACT(source_executed, replica_executed)
replica에는 있고 source에는 없음
= GTID_SUBTRACT(replica_executed, source_executed)
첫 번째는 replica가 더 적용해야 할 누락 후보 집합이다. 두 번째는 기준 source 이력에 없는 errant transaction 후보 집합이다. 단, replication filter, multi-source topology, 의도적으로 분리된 쓰기 도메인이 있다면 차집합이 곧 장애라는 뜻은 아니다. topology 설계와 허용된 SID를 함께 평가해야 한다.
불연속 구간을 포함한 차집합은 다음처럼 확인할 수 있다.
SET @source_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-20';
SET @replica_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-7:9-15';
SELECT GTID_SUBTRACT(@source_executed, @replica_executed) AS missing_on_replica,
GTID_SUBTRACT(@replica_executed, @source_executed) AS errant_on_replica,
GTID_SUBSET(@replica_executed, @source_executed) AS replica_is_subset;
실행 결과(MySQL 8.0.x):
mysql> SET @source_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-20';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @replica_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-7:9-15';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT GTID_SUBTRACT(@source_executed, @replica_executed) AS missing_on_replica,
-> GTID_SUBTRACT(@replica_executed, @source_executed) AS errant_on_replica,
-> GTID_SUBSET(@replica_executed, @source_executed) AS replica_is_subset;
+----------------------------------------------+-------------------+-------------------+
| missing_on_replica | errant_on_replica | replica_is_subset |
+----------------------------------------------+-------------------+-------------------+
| aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:8:16-20 | | 1 |
+----------------------------------------------+-------------------+-------------------+
1 row in set (0.00 sec)
이 예제에서 replica의 누락분은 sequence 8과 16-20이다. replica 쪽에만 있는 GTID는 없으므로 errant 집합은 빈 문자열이고, replica의 기존 이력은 source 집합의 부분집합이다.
7. 새 source가 누락분을 실제로 공급할 수 있는가
“후보 source의 gtid_executed가 더 크다”만으로 failover 가능성을 판단해서는 안 된다. 후보가 필요한 GTID를 실행했더라도 해당 이벤트가 이미 purge되었다면 자동 위치 지정 연결은 필요한 구간을 전송할 수 없다.
다음 네 집합을 같은 시점에 수집한다.
S_exec: 후보 source의gtid_executedS_purged: 후보 source의gtid_purgedR_exec: replica의gtid_executedS_available = S_exec - S_purged: 후보 source의 local binlog에 남은 집합
판단식은 다음과 같다.
R_needed = S_exec - R_exec
R_unavailable = R_needed - S_available
R_errant = R_exec - S_exec
R_needed가 비어 있으면 source 기준으로 추가 적용할 GTID가 없다.R_unavailable이 비어 있지 않으면 replica가 필요로 하지만 source가 local binlog로 공급할 수 없는 이력이 있다.R_errant가 비어 있지 않으면 후보 source에 없는 이력을 replica가 실행했다. 승격·재연결 전에 원인을 규명해야 한다.
축소된 정상 사례는 다음과 같다.
SET @source_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100';
SET @source_purged = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-60';
SET @replica_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-75';
SET @source_available = GTID_SUBTRACT(@source_executed, @source_purged);
SET @replica_needed = GTID_SUBTRACT(@source_executed, @replica_executed);
SELECT @source_available AS source_available,
@replica_needed AS replica_needed,
GTID_SUBTRACT(@replica_needed, @source_available) AS replica_unavailable,
GTID_SUBTRACT(@replica_executed, @source_executed) AS replica_errant;
실행 결과(MySQL 8.0.x):
mysql> SET @source_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @source_purged = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-60';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @replica_executed = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-75';
Query OK, 0 rows affected (0.00 sec)
mysql> SET @source_available = GTID_SUBTRACT(@source_executed, @source_purged);
Query OK, 0 rows affected (0.00 sec)
mysql> SET @replica_needed = GTID_SUBTRACT(@source_executed, @replica_executed);
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @source_available AS source_available,
-> @replica_needed AS replica_needed,
-> GTID_SUBTRACT(@replica_needed, @source_available) AS replica_unavailable,
-> GTID_SUBTRACT(@replica_executed, @source_executed) AS replica_errant;
+---------------------------------------------+---------------------------------------------+---------------------+----------------+
| source_available | replica_needed | replica_unavailable | replica_errant |
+---------------------------------------------+---------------------------------------------+---------------------+----------------+
| aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:61-100 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:76-100 | | |
+---------------------------------------------+---------------------------------------------+---------------------+----------------+
1 row in set (0.00 sec)
이 경우 source에는 61-100이 남아 있고 replica는 76-100이 필요하므로 공급 가능하다. replica가 1-50까지만 실행한 상태라면 필요한 51-60은 이미 purge되어 같은 source에서 이어받을 수 없다. 새 base backup, 다른 binary log 보관소, 아직 해당 이력을 가진 다른 source가 필요하다.
8. 복제 상태와 함께 읽는 법
집합 변수는 이력의 범위를 보여주지만 replication channel이 현재 정상인지, 어느 worker가 막혔는지, 지연 시간이 얼마인지는 별도 상태에서 확인해야 한다.
운영 점검에서는 다음 정보를 함께 수집한다.
SHOW REPLICA STATUS\G
SELECT CHANNEL_NAME,
SERVICE_STATE,
RECEIVED_TRANSACTION_SET,
LAST_ERROR_NUMBER,
LAST_ERROR_MESSAGE
FROM performance_schema.replication_connection_status;
SELECT CHANNEL_NAME,
WORKER_ID,
SERVICE_STATE,
LAST_APPLIED_TRANSACTION,
LAST_ERROR_NUMBER,
LAST_ERROR_MESSAGE
FROM performance_schema.replication_applier_status_by_worker
ORDER BY CHANNEL_NAME, WORKER_ID;
위 쿼리는 실제 replication channel이 구성된 서버에서 실행해야 의미가 있는 운영 예시이므로 검증 컨테이너용 sql fence가 아니라 절차용 text로 제시했다.
해석할 때는 다음을 구분한다.
Retrieved_Gtid_Set또는RECEIVED_TRANSACTION_SET: receiver가 받아 relay log에 기록한 범위Executed_Gtid_Set또는gtid_executed: applier가 실행 완료한 범위LAST_APPLIED_TRANSACTION: worker가 마지막으로 적용한 트랜잭션- receiver/applier error: 전송 중단인지 적용 실패인지 구분하는 근거
수신 집합이 실행 집합보다 앞서면 relay log에는 도착했지만 아직 적용하지 못한 작업이 있을 수 있다. 반대로 두 집합 차이가 작더라도 대형 트랜잭션 하나가 오래 실행 중일 수 있으므로 sequence 개수만으로 지연 시간을 환산하지 않는다.
9. 백업과 복원에서의 GTID
GTID 기반 복구에서는 백업 데이터와 GTID 메타데이터가 같은 논리적 시점을 나타내야 한다. 논리 백업 도구가 내보낸 SET @@GLOBAL.gtid_purged 문을 무심코 기존 서버에 적용하면 현재 topology의 이력과 충돌할 수 있다.
9.1 새 인스턴스 provisioning
- 백업이 나타내는 데이터 시점과 GTID 집합을 확인한다.
- 빈 대상 인스턴스인지, 기존 GTID 이력이 있는지 확인한다.
- dump의
set-gtid-purged정책을 의도적으로 선택한다. - 복원 후
gtid_executed와gtid_purged가 백업 메타데이터와 맞는지 검증한다. - source의
gtid_executed - gtid_purged가 복원 시점 이후 누락분을 제공하는지 계산한다. - 그 뒤에 GTID auto-position으로 replication channel을 연결한다.
9.2 부분 백업의 함정
일부 schema만 담은 논리 백업이라도 dump에 서버 전체의 GTID 집합이 포함될 수 있다. 대상이 그 집합 전체를 실행했다고 기록하면 백업에 포함되지 않은 schema의 트랜잭션까지 이미 처리한 것으로 간주할 위험이 있다. 부분 복원의 목적, 이후 replication 연결 여부, 기존 대상의 GTID 이력을 함께 검토해야 한다.
9.3 PITR에서의 의미
base backup을 복원한 뒤 binary log를 재생할 때 GTID는 중복 적용 방지와 종료 지점 식별에 유용하다. 하지만 업무 사고 트랜잭션을 제외한 새 이력을 만들면 원본과 복구 인스턴스의 집합 관계가 달라질 수 있다. mysqlbinlog --skip-gtids나 수동 gtid_next 조작은 단순 오류 회피 옵션이 아니라 향후 replication topology를 바꾸는 결정이다.
10. 장애 전환과 errant transaction
replica를 승격하기 전에 최소한 다음을 확인한다.
- 기준 primary의 실행 집합이 승격 후보에 모두 포함되는가.
- 승격 후보에 기준 primary가 모르는 GTID가 있는가.
- 아직 적용 중이거나 relay log에만 있는 트랜잭션이 있는가.
- 기준 primary만 가진 GTID가 이미 다른 모든 후보에서 purge되었는가.
- replication filter 또는 multi-source 정책이 집합 차이를 의도적으로 만들었는가.
승격 후보의 GTID_SUBTRACT(candidate_executed, primary_executed)가 비어 있지 않으면 errant transaction 후보가 있다. 원인은 replica에서의 직접 쓰기, 잘못된 sql_log_bin 사용, 이전 topology의 잔여 SID, 일부 channel만 적용한 multi-source 이력일 수 있다.
errant transaction을 발견했다고 즉시 빈 트랜잭션으로 GTID를 주입하거나 gtid_purged를 조정해서는 안 된다. 먼저 해당 GTID가 실제 데이터를 변경했는지, 어떤 객체와 업무 상태에 영향을 줬는지, 어느 서버의 데이터가 정본인지 확인한다. 집합을 맞추는 것과 데이터를 맞추는 것은 다른 작업이다.
11. 흔한 오해와 실패 모드
11.1 gtid_executed가 크면 가장 최신 서버다
여러 SID와 불연속 구간이 있으므로 문자열 길이와 최댓값은 최신성 기준이 아니다. 기준 집합에 대한 GTID_SUBSET()과 양방향 GTID_SUBTRACT()로 비교한다.
11.2 gtid_purged는 삭제해도 되는 트랜잭션 목록이다
gtid_purged는 데이터 삭제 목록이 아니다. 서버가 실행 사실은 기억하지만 local binary log 이벤트를 더 이상 제공하지 못하는 집합이다.
11.3 GTID auto-position이면 binlog 보존이 짧아도 된다
auto-position은 필요한 GTID를 계산한다. source가 그 이벤트를 이미 purge했다면 전송할 수 없다. 장애 대응 RTO/RPO와 replica 재구축 시간을 기준으로 보존 기간을 정해야 한다.
11.4 UUID가 서버의 영구적인 업무 식별자다
server_uuid는 인스턴스 식별에 쓰이지만 VM image나 data directory를 잘못 복제하면 중복 UUID 문제가 생길 수 있다. 새 서버는 고유 UUID를 가져야 하며, 기존 data directory의 auto.cnf를 무분별하게 복제하지 않는다.
11.5 sequence 차이가 replication lag 초다
sequence는 트랜잭션 식별 범위다. 트랜잭션 크기와 실행 시간이 모두 다르므로 차이 개수로 초 단위 지연을 계산할 수 없다. timestamp, applier worker 상태, relay log, 실제 적용 시간을 함께 본다.
11.6 GTID 집합이 같으면 데이터도 반드시 같다
GTID는 실행 이력 메타데이터다. 비결정적 statement, 복제 필터, 수동 데이터 변경, 백업 불일치, 잘못된 GTID 주입이 있으면 집합이 같아도 데이터가 다를 수 있다. 중요 전환에서는 checksum, row count, 업무 불변식, 애플리케이션 검증을 별도로 수행한다.
12. Aurora MySQL에서의 운영 해석
Aurora MySQL에서 GTID는 주로 Aurora cluster와 외부 MySQL 또는 다른 Aurora cluster 사이의 binary log replication에 적용된다. 같은 Aurora cluster의 reader 인스턴스는 공유 cluster volume 기반 구조를 사용하므로 일반 MySQL replica의 relay log와 GTID 집합만으로 cluster 내부 복제 상태를 설명할 수 없다. Aurora Global Database의 storage 기반 cross-Region 복제도 외부 binlog channel과 구분해 관찰해야 한다.
운영 시 다음 차이를 반영한다.
gtid-mode와enforce_gtid_consistency는 DB cluster parameter group에서 관리하며, inbound와 outbound binlog replication에 함께 영향을 줄 수 있다.- GTID 전환은
OFF에서 곧바로ON으로 뛰는 작업이 아니다. 대상 Aurora MySQL major/minor version의 지원 절차에 따라 permissive 단계를 거치고 모든 트랜잭션의 호환성을 확인한다. - Aurora의 managed backup/restore와 cluster clone은 일반 MySQL의 파일 복사 절차와 다르다. 복원 후 외부 binlog replication을 연결할 때 복원 시점, GTID 집합, source의 binlog 보존 범위를 함께 확인한다.
- cluster 내부 reader 지연, Global Database 지연, 외부 binlog replica 지연은 서로 다른 데이터 경로다. CloudWatch 지표, Aurora 상태, 외부 replication channel 상태를 각각 확인한다.
- parameter 변경의 적용 유형과 재부팅 필요 여부는 engine version과 parameter group 상태에서 확인한다. production topology 전체가 준비되기 전에 한쪽만 GTID mode를 변경하지 않는다.
즉 Aurora에서도 GTID 집합 연산의 의미는 같지만, 어떤 복제 경로가 GTID를 사용하는가를 먼저 구분해야 한다.
13. 운영 점검 체크리스트
설정과 식별자
- 모든 replication 참여 서버의
server_uuid -
gtid_mode와enforce_gtid_consistency
집합 해석
-
gtid_purged가gtid_executed - source와 replica의 차이를 양방향
GTID_SUBTRACT() - 누락 집합이 source의
gtid_executed - gtid_purged
장애 전환
백업과 복구
-
gtid_purged
14. 결론
GTID의 핵심은 번호 자체가 아니라 집합으로 비교할 수 있는 트랜잭션 이력에 있다. UUID:sequence는 트랜잭션의 최초 발생 서버와 그 서버 안의 순서를 나타내고, gtid_executed는 서버가 완료했다고 알고 있는 전체 이력, gtid_purged는 그중 local binary log에서 더 이상 제공할 수 없는 부분을 나타낸다.
운영자는 항상 executed - purged로 공급 가능한 이력을 계산하고, source와 replica의 차집합을 양방향으로 확인해야 한다. 이 원칙을 지키면 auto-position 가능 여부, replica 재구축 필요성, errant transaction, 장애 전환 후보의 적합성을 숫자 감각이 아니라 검증 가능한 집합 관계로 판단할 수 있다. 이후 GTID 기반 replication 설정과 source auto-position을 다룰 때도 이 집합 모델이 모든 절차의 기준이 된다.