이 섹션에서는 GTID (Global Transaction Identifier)를 사용한 트랜잭션 기반 복제에 대해 설명합니다. GTID를 사용할 때 각 트랜잭션은 원래 서버에서 커밋되고 슬레이브에 의해 적용될때 식별되고 추적될 수 있습니다. 이는 새 슬레이브를 시작하거나 새 마스터로 Failover가 되었을때 GTID를 사용하여 해당 파일 내에서 로그 파일 또는 위치를 참조 하기 때문에 기존방법인 로그파일과 포지션을 찾아야 하는 작업을 크게 단순화합니다. GTID 기반 복제는 완전히 트랜잭션 기반이기 때문에 마스터와 슬레이브가 일치하는지 간단하게 판단 할 수 있습니다. 마스터에서 커밋된 모든 트랜잭션이 슬레이브에서도 커밋되는 경우 이 둘 사이의 일관성이 보장됩니다. GTID와 함께 Statement 기반 또는 ROW 기반 복제를 사용할 수 있습니다. 성능 혹은 이득면에서 ROW 기반 형식을 사용하는 것이 좋습니다.
GTID는 항상 마스터와 슬레이브간에 유지됩니다. 이는 바이너리 로그를 검사하여 슬레이브에 적용된 모든 트랜잭션의 소스를 항상 확인합니다. 또한 주어진 GTID를 가진 트랜잭션이 주어진 서버에서 커밋되면 같은 GTID를 가진 후속 트랜잭션은 해당 서버에서 무시됩니다. 따라서 마스터에서 커밋된 트랜잭션을 슬레이브에 한번만 적용할 수 있으므로 일관성을 보장 할 수 있습니다.
여기에서는 다음 사항들에 대해 알아봅니다.
- GTID 정의, 생성 방법 및 MySQL 서버에서 표시되는 방법
- GTID의 수명주기.
- GTID를 사용하는 슬레이브와 마스터를 동기화하기 위한 자동 포지셔닝 기능.
- GTID 기반 복제를 설정하고 시작하는 일반적인 절차.
- GTID 사용시 새로운 복제 서버를 프로비저닝하기위한 권장 방법.
- GTID 기반 복제를 사용할 때 알아야 할 제한사항 및 범위.
- GTID 작업에 사용할 수있는 저장 함수.
■ GTID 포맷과 저장공간
GTID (Global Transaction Identifier)는 생성된 서버(마스터)에서 커밋된 각 트랜잭션과 관련되고 그 트랜잭션에 의해 생성된 고유 식별자입니다. 이 식별자는 시작된 서버뿐만 아니라 지정된 복제 토폴로지의 모든 서버에서 고유합니다.
GTID 할당은 마스터에서 커밋된 클라이언트 트랜잭션과 슬레이브에서 재생성되는 복제된 트랜잭션을 구분합니다. 클라이언트 트랜잭션이 마스터에서 커밋되면 트랜잭션이 바이너리 로그에 기록된 경우 새 GTID가 할당됩니다. 클라이언트 트랜잭션은 생성된 숫자 사이에 공백없이 순서대로 증가하는 GTID를 갖도록 보장됩니다. 클라이언트 트랜잭션이 바이너리 로그에 기록되지 않은 경우 (예 : 트랜잭션이 필터링되었거나 트랜잭션이 읽기 전용이기 때문에) 원래 서버(트랜잭션을 수행한 서버)에 GTID가 할당되지 않습니다.
복제된 트랜잭션은 원래 서버의 트랜잭션에 지정된 것과 동일한 GTID를 유지합니다. GTID는 복제된 트랜잭션이 실행되기 전에 존재하며 복제된 트랜잭션이 슬레이브의 이진 로그에 기록되지 않거나 슬레이브에서 필터링된 경우에도 지속됩니다. MySQL 시스템 테이블 mysql.gtid_executed는 현재 활성 이진 로그 파일에 저장된 트랜잭션을 제외하고 MySQL 서버에 적용된 모든 트랜잭션의 할당 된 GTID를 유지하는 데 사용됩니다.
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 894c1e20-e34b-11ec-944f-0050562bebcc | 1 | 16 |
| 894c1e20-e34b-11ec-944f-0050562bebcc | 17 | 17 |
| 894c1e20-e34b-11ec-944f-0050562bebcc | 18 | 18 |
| 894c1e20-e34b-11ec-944f-0050562bebcc | 19 | 19 |
+--------------------------------------+----------------+--------------+
4 rows in set (0.00 sec)
GTID의 자동 건너뛰기 기능은 마스터에서 커밋된 트랜잭션을 슬레이브에서 한번만 적용할 수 있어 일관성을 보장합니다. 주어진 GTID를 가진 트랜잭션이 주어진 서버에서 커밋되면 같은 GTID를 가진 후속 트랜잭션을 실행하려는 시도는 해당 서버에서 무시됩니다. 오류가 발생하지 않으며 트랜잭션의 명령문이 실행되지 않습니다.
주어진 GTID를 가진 트랜잭션이 서버에서 실행되기 시작했어도, 만약 아직 커밋 또는 롤백되지 않은 경우 동일한 GTID를 가진 서버에서 동시 트랜잭션을 시작하려는 시도는 차단됩니다. 서버는 동시 트랜잭션 실행을 시작하지 않으며 클라이언트에게 제어를 리턴하지 않습니다. 트랜잭션에서 첫 번째 시도가 커밋되거나 롤백되면 동일한 GTID에서 차단된 동시 세션이 진행될 수 있습니다. 첫 번째 시도가 롤백되면 하나의 동시 세션이 트랜잭션 시도를 진행하고 동일한 GTID에서 차단 된 다른 동시 세션은 계속 차단됩니다. 첫 번째 시도가 커밋되면 모든 동시에 세션이 차단되는 것을 중지하고 트랜잭션의 모든 명령문을 자동 건너 뜁니다.
GTID는 다음과 같이 한쌍의 좌표로 콜론문자(:)로 구분됩니다.
GTID = source_id:transaction_id
source_id는 원본 서버를 식별합니다. 일반적으로 마스터의 server_uuid가 이 목적으로 사용됩니다. transaction_id는 마스터에서 트랜잭션이 커밋 된 순서에 따라 결정된 시퀀스 번호입니다. 예를 들어, 커밋 할 첫 번째 트랜잭션의 transaction_id는 1이고, 동일한 원래 서버에서 커밋 할 10 번째 트랜잭션에는 transaction_id가 10으로 할당됩니다. GTID내에서 트랜잭션의 시퀀스 번호로 0을 가질 수 없습니다. 예를 들어, UUID 3E11FA47-71CA-11E1-9E33-C80AA9429562 를 사용하여 서버에서 원래 커밋 된 23 번째 트랜잭션에는 다음 GTID가 있습니다.
예) 3E11FA47-71CA-11E1-9E33-C80AA9429562:23
트랜잭션의 GTID는 mysqlbinlog의 출력에 표시되며 Performance Schema의 복제 상태 테이블에서 개별 트랜잭션을 식별하는 데 사용됩니다 (예 : replication_applier_status_by_worker). gtid_next 시스템 변수 (@@GLOBAL.gtid_next)에 의해 저장된 값은 단일 GTID입니다.
▶ GTID Sets
GTID 세트는 하나 이상의 단일 GTID 또는 GTID 범위로 구성되는 세트입니다. GTID 세트는 여러 가지 방법으로 MySQL 서버에서 사용됩니다. 예를 들어, gtid_executed 및 gtid_purged 시스템 변수에 의해 저장된 값은 GTID 세트입니다. START SLAVE 절들인 UNTIL SQL_BEFORE_GTIDS 및 UNTIL SQL_AFTER_GTIDS를 사용하여 GTID 세트의 첫 번째 GTID까지만 슬레이브 프로세스 트랜잭션을 만들거나, GTID 세트의 마지막 GTID 이후에 중지 할 수 있습니다. 내장 함수 GTID_SUBSET() 및 GTID_SUBTRACT()에는 GTID 세트가 입력으로 필요합니다.
동일한 서버에서 시작된 다양한 GTID는 다음과 같이 단일 표현식으로 축소 될 수 있습니다.
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
위의 예는 server_uuid가 3E11FA47-71CA-11E1-9E33-C80AA9429562인 MySQL 서버에서 시작된 첫 번째부터 다섯 번째 트랜잭션을 나타냅니다. 동일한 서버에서 시작하는 여러 단일 GTID 또는 범위 GTID는 다음 예와 같이 콜론으로 구분하여 단일 표현식에 포함 할 수도 있습니다.
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-3:11:47-49
마지막에 보면 1-3,47-49(범위값), 11(단일값)을 연속으로 표현한것을 확인할 수 있습니다.
GTID 세트는 단일 GTID와 다양한 GTID의 조합을 포함할 수 있으며 다른 서버에서 생성된 GTID를 포함 할 수 있습니다. 이 예는 둘 이상의 마스터에서 트랜잭션을 적용한 슬레이브의 gtid_executed 시스템 변수(@@ GLOBAL.gtid_executed)에 저장된 GTID 세트를 보여줍니다.
2174B383-5441-11E8-B90A-C80AA9429562:1-3, 24DA167-0C0C-11E8-8442-00059A3C7B00:1-19
서버 변수에서 GTID 세트가 리턴되면 UUID는 알파벳순으로 표시되며 숫자 간격은 병합되고 오름차순으로 정렬됩니다.
GTID 세트의 구문은 다음과 같습니다.
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
uuid:interval[:interval]...
uuid:
hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
[0-9|A-F]
interval:
n[-n]
(n >= 1)
▶ mysql.gtid_executed Table
GTID는 mysql 데이터베이스의 gtid_executed 테이블에 저장됩니다. 이 테이블의 행에는 각 GTID 또는 그것이 나타내는 GTID 세트, 원래 서버의 UUID 및 세트의 시작 및 종료 트랜잭션 ID가 포함됩니다. 단일 GTID 만 참조하는 행의 경우, 이 마지막 두 값은 동일합니다.
mysql.gtid_executed 테이블은 다음과 같은 CREATE TABLE 문을 사용하여 MySQL 서버를 설치하거나 업그레이드 할 때 생성됩니다(아직없는 경우).
절대로 아래 테이블을 직접 만들거나 수정하면 안됩니다. MySQL 서버에서 직접 컨트롤 합니다.
CREATE TABLE gtid_executed (
source_uuid CHAR(36) NOT NULL,
interval_start BIGINT(20) NOT NULL,
interval_end BIGINT(20) NOT NULL,
PRIMARY KEY (source_uuid, interval_start)
)
mysql.gtid_executed 테이블은 MySQL 서버에서 내부 용으로 제공됩니다. 슬레이브에서 바이너리 로깅이 비활성화 된 경우 슬레이브가 GTID를 사용할 수 있게하고 바이너리 로그가 손실된 경우 GTID 상태를 유지할 수 있습니다. RESET MASTER를 실행하면 mysql.gtid_executed 테이블이 지워집니다.
GTID는 gtid_mode가 ON 또는 ON_PERMISSIVE인 경우에만 mysql.gtid_executed 테이블에 저장됩니다. GTID가 저장되는 시점은 바이너리 로깅의 활성화 여부에 따라 다릅니다.
- 바이너리 로깅을 사용하지 않거나(log_bin=OFF) log_slave_updates 파라미터를 사용하지 않으면 서버는 각 트랜잭션에 속하는 GTID를 트랜잭션과 함께 테이블에 저장합니다. 또한 테이블은 사용자가 다룰 수 있는 속도로 주기적으로 압축됩니다. 이 상황은 바이너리 로깅 또는 슬레이브 업데이트 로깅이 비활성화된 복제 슬레이브에만 적용할 수 있습니다. 마스터에서는 복제가 수행 되려면 바이너리 로깅이 사용 가능해야하므로 복제 마스터에는 적용되지 않습니다.
- 바이너리 로깅이 활성화된 경우 (log_bin=ON), 바이너리 로그가 Rotate(사용되던 로그 파일이 닫히고 새로운 로그파일이 생성)되거나 MySQL이 종료될때마다 서버는 이전 바이너리 로그에 기록된 모든 트랜잭션에 대한 GTID를 mysql.gtid_executed 테이블에 기록합니다. 이 상황은 복제 마스터 또는 바이너리 로깅이 활성화된 복제 슬레이브에 적용됩니다.
서버가 예기치 않게 중지되는 경우, 현재 바이너리 로그 파일의 GTID 세트가 mysql.gtid_executed 테이블에 저장되지 않습니다. 이 GTID는 복구 중에 바이너리 로그 파일에서 테이블에 추가됩니다. 서버를 다시 시작할 때 바이너리 로깅을 사용할 수 없는 경우는 예외입니다. 이 경우 서버는 바이너리 로그 파일에 액세스하여 GTID를 복구할 수 없으므로 복제를 시작할 수 없습니다.
바이너리 로깅이 활성화되면 mysql.gtid_executed 테이블은 실행된 모든 트랜잭션에 대한 GTID의 전체 레코드를 보유하지 않습니다. 이 정보는 gtid_executed시스템 변수의 전역 값으로 제공됩니다. MySQL 서버의 GTID 상태를 나타내기 위해 항상 모든 커밋 후에 업데이트되는 @@GLOBAL.gtid_executed를 사용하고 mysql.gtid_executed 테이블을 SQL로 직접 조회하면 안됩니다.
▶ mysql.gtid_executed Table Compression
시간이 지남에 따라 mysql.gtid_executed테이블은 동일한 서버에서 시작되는 개별 GTID를 참조하고 여기에 표시된 것과 유사한 트랜잭션 ID가 범위를 구성하는 많은 행으로 채워질 수 있습니다.
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 37 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 38 | 38 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 39 | 39 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 40 | 40 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 41 | 41 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 42 | 42 |
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 43 | 43 |
...
공간을 절약하기 위해 MySQL서버는 이러한 각 행 집합을 다음과 같이 전체 트랜잭션 식별자 간격에 걸쳐있는 단일 행으로 교체하여 mysql.gtid_executed 테이블을 주기적으로 압축합니다.
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 3E11FA47-71CA-11E1-9E33-C80AA9429562 | 37 | 43 |
...
gtid_executed_compression_period 시스템 변수를 설정하여 테이블 압축 전에 사용할 트랜잭션 개수와 압축률을 제어할 수 있습니다. 이 변수의 기본값은 1000입니다. 즉, 기본적으로 테이블 압축은 각 1000 트랜잭션 후에 수행됩니다. gtid_executed_compression_period를 0으로 설정하면 압축이 전혀 수행되지 않으므로 gtid_executed 테이블에 필요할 수있는 디스크 공간이 크게 증가할 수 있도록 준비해야 합니다.
그리고 바이너리 로깅이 활성화되면 gtid_executed_compression_period의 값이 사용되지 않고 mysql.gtid_executed 테이블이 각 바이너리 로그 Rotate마다 압축됩니다.
mysql.gtid_executed 테이블의 압축은 thread/sql/compress_gtid_table이라는 전용 포그라운드 스레드에 의해 수행됩니다. 이 스레드는 SHOW PROCESSLIST의 출력에 나열되지 않지만 다음과 같이 스레드 테이블에서 행으로 볼 수 있습니다.
mysql> SELECT * FROM performance_schema.threads WHERE NAME LIKE '%gtid%'\G
*************************** 1. row ***************************
THREAD_ID: 26
NAME: thread/sql/compress_gtid_table
TYPE: FOREGROUND
PROCESSLIST_ID: 1
PROCESSLIST_USER: NULL
PROCESSLIST_HOST: NULL
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
PROCESSLIST_TIME: 1509
PROCESSLIST_STATE: Suspending
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: NULL
THREAD_OS_ID: 18677
thread/sql/compress_gtid_table 스레드는 일반적으로 gtid_executed_compression_period 트랜잭션이 실행될 때까지 일시정지 상태가 된 후, 앞서 설명한대로 mysql.gtid_executed 테이블의 압축을 수행하기 위해 다시 수행합니다. 그런 다음 다른 gtid_executed_compression_period 트랜잭션이 발생할 때까지 일시정지 상태가 된 다음 이 루프를 무기한 반복하여 압축을 다시 수행하기 위해 다시 수행합니다. 바이너리 로깅을 사용하지 않을때 이 값을 0으로 설정하면 스레드가 항상 잠자기 상태가되지 않습니다.
■ GTID 라이프사이클
GTID의 수명주기는 다음 단계로 구성됩니다.
1. 트랜잭션이 마스터에서 실행되고 커밋됩니다. 이 클라이언트 트랜잭션에는 소스의 UUID와 이 서버에서 아직 사용되지 않은 0이 아닌 가장 작은 트랜잭션 시퀀스 번호로 구성된 GTID가 할당됩니다. GTID는 마스터의 바이너리 로그에 기록됩니다 (로그에서 트랜잭션 자체 바로 앞). 클라이언트 트랜잭션이 바이너리 로그에 기록되지 않은 경우 (예 : 트랜잭션이 필터링되었거나 트랜잭션이 읽기 전용이기 때문에) GTID가 할당되지 않습니다.
2. 트랜잭션에 GTID가 할당 된 경우 트랜잭션 시작시 바이너리 로그에 Gtid_log_event로 기록하여 GTID가 커밋시 원자적(Atomic)으로 유지됩니다. 바이너리 로그가 회전되거나 서버가 종료될 때마다 서버는 이전 바이너리 로그 파일에 기록된 모든 트랜잭션에 대한 GTID를 mysql.gtid_executed 테이블에 기록합니다.
3. GTID가 트랜잭션에 할당된 경우, GTID는 gtid_executed 시스템 변수 (@@GLOBAL.gtid_executed)의 GTID 세트에 GTID를 추가하여 비 원자적(non-atomically)으로 (트랜잭션이 커밋된 직후) 외부화됩니다. 이 GTID 세트는 커밋된 모든 GTID 트랜잭션 세트의 표현을 포함하며 서버 상태를 나타내는 토큰으로 복제에 사용됩니다. 바이너리 로깅이 활성화되면(마스터의 필요나 옵션에 따라) gtid_executed 시스템 변수의 GTID 세트는 적용된 트랜잭션의 전체 레코드이지만 mysql.gtid_executed 테이블은 그렇지 않습니다. 가장 최근의 히스토리는 여전히 현재 바이너리 로그 파일에 있기 때문입니다.
4. 바이너리 로그 데이터가 슬레이브로 전송되고 슬레이브의 릴레이 로그에 저장된 후 슬레이브는 GTID를 읽고 이 GTID들을 gtid_next라는 시스템 변수의 값에 설정합니다. 이것은이 GTID를 사용하여 다음 트랜잭션을 기록해야 한다는 것을 슬레이브에게 알려줍니다. 슬레이브는 세션 컨텍스트에서 gtid_next를 설정한다는 점에 유의해야 합니다.
5. 슬레이브는 트랜잭션을 처리하기 위해 아직 스레드가 gtid_next에서 GTID를 소유하지 않았는지 확인합니다. 트랜잭션 자체를 처리하기 전에 복제된 트랜잭션의 GTID를 먼저 읽고 확인함으로써 슬레이브는 이 GTID를 가진 바이너리 트랜잭션이 슬레이브에 적용되지 않았을뿐만 아니라 다른 세션이 이 GTID를 읽지 않았지만 아직 관련 트랜잭션을 커밋되지 않았음을 보증합니다. 따라서 여러 클라이언트가 동일한 트랜잭션을 동시에 적용하려고하면 서버는 클라이언트중 하나만 실행하여 이를 해결합니다. 슬레이브에 대한 gtid_owned 시스템 변수 (@@ GLOBAL.gtid_owned)는 현재 사용중인 각 GTID와 이를 소유한 스레드의 ID를 보여줍니다. GTID가 이미 사용 된 경우 오류가 발생하지 않으며 자동 건너 뛰기 기능을 사용하여 트랜잭션을 무시합니다.
6. GTID가 사용되지 않은 경우 슬레이브는 복제된 트랜잭션을 적용합니다. gtid_next가 마스터에 의해 이미 할당 한 GTID로 설정되어 있기 때문에 슬레이브는 이 트랜잭션에 대해 새로운 GTID를 생성하지 않고 대신 gtid_next에 저장된 GTID를 사용합니다.
7. 슬레이브에서 바이너리 로깅이 활성화된 경우 트랜잭션 시작시 바이너리 로그에 Gtid_log_event로 기록하여 GTID가 커밋시 원자(Atomic) 적으로 유지됩니다. 바이너리 로그가 회전되거나 서버가 종료될때 마다 서버는 이전 바이너리 로그 파일에 기록된 모든 트랜잭션에 대한 GTID를 mysql.gtid_executed 테이블에 기록합니다.
8. 슬레이브에서 바이너리 로깅이 비활성화된 경우 GTID는 mysql.gtid_executed 테이블에 직접 작성하여 원자적(Atomic)으로 유지됩니다. MySQL은 트랜잭션에 명령문을 추가하여 GTID를 테이블에 삽입합니다. 이 상황에서 mysql.gtid_executed 테이블은 슬레이브에 적용된 트랜잭션의 완전한 레코드입니다. MySQL 5.7에서 GTID를 테이블에 삽입하는 작업은 DML문에 대해서는 원자적(Atomic)이지만 DDL문에 대해서는 원자적(Atomic)이지 않으므로 DDL 문과 관련된 트랜잭션 후에 서버가 예기치 않게 종료되면 GTID 상태가 일치하지 않을 수 있습니다. MySQL 8.0부터는 DDL문과 DML문에 대한 작업이 기본적입니다.
9. 복제된 트랜잭션이 슬레이브에서 매우 짧게 커밋된 직후, GTID는 슬레이브에 대한 gtid_executed 시스템 변수(@@ GLOBAL.gtid_executed)의 GTID 세트에 추가하여 비 원자적(non-atomic)으로 외부화됩니다. 마스터와 관련하여이 GTID 세트에는 커밋된 모든 GTID 트랜잭션 세트의 표현이 포함됩니다. 슬레이브에서 바이너리 로깅이 비활성화 된 경우 mysql.gtid_executed 테이블은 슬레이브에 적용된 트랜잭션의 전체 레코드이기도 합니다. 슬레이브에서 바이너리 로깅이 활성화된 경우, 일부 GTID가 바이너리 로그에만 기록됨을 의미합니다. gtid_executed 시스템 변수의 GTID 세트는 유일한 완전한 레코드입니다.
마스터에서 완전히 필터링된 클라이언트 트랜잭션에는 GTID가 할당되지 않으므로 gtid_executed 시스템 변수의 트랜잭션 세트에 추가되거나 mysql.gtid_executed 테이블에 추가되지 않습니다. 그러나 슬레이브에서 완전히 필터링된 복제된 트랜잭션의 GTID는 유지됩니다. 슬레이브에서 바이너리 로깅이 활성화된 경우 필터링된 트랜잭션은 바이너리 로그에 Gtid_log_event로 기록된 다음 BEGIN 및 COMMIT 문만 포함된 빈 트랜잭션이 기록됩니다. 바이너리 로깅이 비활성화되면 필터링된 트랜잭션의 GTID가 mysql.gtid_executed 테이블에 기록됩니다. 필터링된 트랜잭션에 대한 GTID를 유지하면 mysql.gtid_executed 테이블과 gtid_executed 시스템 변수의 GTID 세트를 압축할 수 있습니다. 또한 슬레이브가 마스터에 다시 연결될 경우 필터링 된 트랜잭션이 다시 검색되지 않도록합니다.
멀티 스레드 복제 슬레이브 (slave_parallel_workers> 0)에서 트랜잭션을 병렬로 적용 할 수 있으므로 복제된 트랜잭션이 순서를 벗어나 커밋될 수 있습니다 (slave_preserve_commit_order = 1이 설정되지 않은 경우). 이 경우 gtid_executed 시스템 변수의 GTID 세트에는 간격이 있는 여러 GTID 범위가 포함됩니다.(마스터 또는 단일 스레드 복제 슬레이브에서는 숫자 사이에 공백없이 단조롭게 증가하는 GTID가 있습니다.) 다중 스레드 복제 슬레이브의 간격은 가장 최근에 적용된 트랜잭션 사이에서만 발생하며 복제가 진행됨에 따라 채워집니다. STOP SLAVE 문을 사용하여 복제 스레드가 완전히 중지되면 진행중인 트랜잭션이 적용되어 간격이 채워집니다. 서버 장애와 같은 종료 또는 KILL 문을 사용하여 복제 스레드를 중지하는 경우 간격이 발생할 수 있습니다.
▶ GTID에 할당되면 무엇이 변경되는가?
일반적인 시나리오는 서버가 커밋된 트랜잭션에 대한 새 GTID를 생성하는 것입니다. 그러나 GTID는 트랜잭션 이외의 다른 변경 사항에도 할당될 수 있으며 경우에 따라 단일 트랜잭션에 여러 GTID를 할당 할 수도 있습니다.
바이너리 로그에 기록된 모든 데이터베이스 변경(DDL 또는 DML)에는 GTID가 할당됩니다. 여기에는 자동 커밋된 변경 사항과 BEGIN 및 COMMIT 또는 START TRANSACTION 문을 사용하여 커밋된 변경 사항이 포함됩니다. GTID는 또한 데이터베이스, 프로 시저, 함수, 트리거, 이벤트,보기, 사용자, 역할 또는 권한 부여와 같은 테이블이 아닌 데이터베이스 오브젝트의 작성, 변경 또는 삭제에도 지정됩니다.
비 트랜잭션 업데이트 및 트랜잭션 업데이트에는 GTID가 할당됩니다. 또한 비 트랜잭션 업데이트의 경우 바이너리 로그 캐시에 쓰려고 시도하는 동안 디스크 쓰기 오류가 발생하여 바이너리 로그에 간격이 생기면 결과로 발생하는 사건(incident) 로그 이벤트에 GTID가 할당됩니다.
바이너리 로그에서 생성된 명령문에 의해 테이블이 자동으로 삭제되면 GTID가 명령문에 지정됩니다. 임시 테이블은 복제 슬레이브가 방금 시작된 마스터의 이벤트를 적용하기 시작하고 명령문 기반 복제가 사용중일 때 (binlog_format = STATEMENT) 임시 테이블을 연 사용자 세션의 연결이 끊어지면 자동으로 삭제됩니다. MEMORY 스토리지 엔진을 사용하는 테이블은 서버가 시작된 후 처음 액세스할 때 자동으로 삭제됩니다. 종료 중에 행이 유실되었을 수 있습니다.
전체(서버 설정에서 --skip-log-bin 적용) 혹은 세션(SET @@SESSION.sql_log_bin = 0)에서 롤백된 트랜잭션과 바이너리 로깅이 될때 수행된 트랜잭션은 수행된 원래 서버에서 비활성화됩니다.
여기에는 ROW 기반 복제가 사용 중일 때 트랜잭션이 아닌것도 포함됩니다 (binlog_format = ROW).
XA 트랜잭션에는 트랜잭션의 XA PREPARE 단계와 트랜잭션의 XA COMMIT 또는 XA ROLLBACK 단계에 대해 별도의 GTID가 할당됩니다. XA 트랜잭션은 지속적으로 준비되므로 사용자는 실패시 (복제 토폴로지에서 다른 서버에 대한 페일 오버가 포함될 수 있음) 트랜잭션을 커밋하거나 롤백할 수 있습니다. 따라서 트랜잭션의 두 부분은 별도로 복제되므로 롤백된 비 XA 트랜잭션에 GTID가 없더라도 자체 GTID가 있어야합니다.
다음과 같은 특수한 경우 단일 명령문으로 여러 트랜잭션을 생성 할 수 있으므로 여러 GTID가 지정됩니다.
- 여러 트랜잭션을 커밋하는 저장 프로시저가 호출됩니다. 프로시저가 커밋하는 각 트랜잭션마다 하나의 GTID가 생성됩니다.
- 여러 테이블을 삭제하는 DROP TABLE 문은 다른 유형의 테이블을 삭제합니다.
- 행 기반 복제가 사용 중일 때 CREATE TABLE ... SELECT 문이 발행됩니다(binlog_format = ROW). CREATE TABLE 수행에대해 하나의 GTID가 생성되고 행 삽입 조치에 대해 하나의 GTID가 생성됩니다.
▶ gtid_next 시스템 변수
기본적으로 사용자 세션에서 커밋된 새 트랜잭션의 경우, 서버는 자동으로 새 GTID를 생성하고 할당합니다. 트랜잭션이 복제 슬레이브에 적용되면 원래 서버의 GTID가 유지됩니다. gtid_next 시스템 변수의 세션 값을 설정하여이 동작을 변경할 수 있습니다.
- gtid_next가 기본값인 AUTOMATIC으로 설정되고 트랜잭션이 커밋되어 바이너리 로그에 기록되면 서버는 자동으로 새 GTID를 생성하고 할당합니다. 다른 이유로 트랜잭션이 롤백되거나 바이너리 로그에 기록되지 않으면 서버는 GTID를 생성 및 할당하지 않습니다.
- gtid_next를 유효한 GTID(UUID 및 트랜잭션 순서 번호로 구성되고 콜론으로 구분)로 설정하면 서버는 해당 GTID를 트랜잭션에 지정합니다. 이 GTID는 트랜잭션이 바이너리 로그에 기록되지 않거나 트랜잭션이 비어있는 경우에도 gtid_executed에 지정되어 추가됩니다.
gtid_next를 특정 GTID로 설정하고 트랜잭션이 커밋 또는 롤백된 후 명시적인 SET @@ SESSION.gtid_next 문이 다른 명령문보다 먼저 발행되어야합니다. 더 이상 GTID를 명시 적으로 지정하지 않으려는 경우, 이를 사용하여 GTID 값을 AUTOMATIC으로 다시 설정할 수 있습니다.
복제 적용 스레드가 복제된 트랜잭션을 적용할 때 이 기술을 사용하여 @@SESSION.gtid_next를 원본 서버에 할당된 복제된 트랜잭션의 GTID로 명시적으로 설정합니다. 이는 복제 슬레이브가 새 GTID를 생성하여 할당하는 대신 원래 서버의 GTID가 유지됨을 의미합니다. 또한 바이너리 로깅 또는 슬레이브 업데이트 로깅이 슬레이브에서 비활성화되어 있거나 트랜잭션이 비 트랜잭션이거나 슬레이브에서 필터링된 경우에도 복제 슬레이브에서 GTID가 gtid_executed에 추가됨을 의미합니다.
트랜잭션을 실행하기 전에 @@ SESSION.gtid_next를 특정 GTID로 설정하여 클라이언트가 복제된 트랜잭션을 시뮬레이트 할 수 있습니다. 이 기술은 mysqlbinlog에서 클라이언트가 GTID를 보존하기 위해 재생할 수 있는 바이너리 로그 덤프를 생성하는 데 사용됩니다. 클라이언트를 통해 커밋된 시뮬레이트 된 복제된 트랜잭션은 복제를 적용하는 스레드를 통해 커밋된 복제된 트랜잭션과 완전히 동일하며, 사실적으로 구별할 수 없습니다.
▶ gtid_purged 시스템 변수
gtid_purged 시스템 변수(@@ GLOBAL.gtid_purged)의 GTID 세트에는 서버에서 커미트되었지만 서버의 바이너리 로그 파일에는 없는 모든 트랜잭션의 GTID가 포함됩니다. gtid_purged는 gtid_executed의 서브 세트입니다. gtid_purged에는 다음과 같은 GTID 카테고리가 있습니다.
- 복제본에서 비활성화된 바이너리 로깅으로 커밋된 복제된 트랜잭션의 GTID
- 현재 제거된 바이너리 로그 파일에 기록된 트랜잭션의 GTID
- SET @@ GLOBAL.gtid_purged문에 의해 세트에 명시적으로 추가된 GTID.
서버의 바이너리 로그에는 존재하지 않지만 특정 GTID 세트의 트랜잭션이 적용된 서버에 기록하기 위해 gtid_purged의 값을 변경할 수 있습니다. gtid_purged에 GTID를 추가하면 gtid_executed에도 추가됩니다. 이 조치의 사용 사례는 서버에서 하나 이상의 데이터베이스 백업을 복원하지만 서버의 트랜잭션을 포함하는 관련 바이너리 로그가 없는 경우입니다. MySQL 5.7에서는 gtid_executed(그리고 거기엔 gtid_purged 변수)가 비어있을 때만 gtid_purged의 값을 변경할 수 있습니다. 이를 수행하는 방법에 대한 자세한 내용은 gtid_purged에 대한 설명을 참조하십시오.
gtid_executed 및 gtid_purged 시스템 변수의 GTID세트는 서버가 시작될 때 초기화됩니다. 모든 바이너리 로그 파일은 Previous_gtids_log_event 이벤트로 시작합니다. 이 이벤트는 모든 이전 바이너리 로그 파일의 GTID 세트 (이전 파일의 Previous_gtids_log_event의 GTID 및 이전 파일 자체의 모든 Gtid_log_event의 GTID로 구성됨)를 포함합니다. 가장 오래된 최신 바이너리 로그 파일에서 Previous_gtids_log_event의 내용은 서버 시작시 gtid_executed 및 gtid_purged 세트를 계산하는 데 사용됩니다.
- gtid_executed는 최신 바이너리 로그 파일의 Previous_gtids_log_event에있는 GTID, 해당 바이너리 로그 파일에 있는 트랜잭션의 GTID 및 mysql.gtid_executed 테이블에 저장된 GTID의 합집합(Union)으로 계산됩니다. 이 GTID 세트에는 현재 서버의 바이너리 로그 파일에 있는지 여부에 관계없이 서버에서 사용되었거나 명시적으로 gtid_purged에 추가된 모든 GTID가 포함됩니다. 현재 서버에서 처리중인 트랜잭션의 GTID(@@ GLOBAL.gtid_owned)는 포함되지 않습니다.
- gtid_purged는 최신 바이너리 로그 파일에서 Previous_gtids_log_event의 GTID와 해당 바이너리 로그 파일에있는 트랜잭션의 GTID를 먼저 추가하여 계산됩니다. 이 단계는 서버에서 바이너리 로그(gtids_in_binlog)에 현재 또는 한번 기록된 GTID 세트를 제공합니다. 다음으로 가장 오래된 바이너리 로그 파일에서 Previous_gtids_log_event의 GTID가 gtids_in_binlog에서 뺍니다. 이 단계는 현재 서버의 바이너리 로그에 기록된 GTID 세트를 제공합니다(gtids_in_binlog_not_purged). 마지막으로 gtids_in_binlog_not_purged는 gtid_executed에서 뺍니다. 결과는 서버에서 사용되었지만 현재 서버의 바이너리 로그 파일에 기록되지 않은 GTID 세트이며이 결과는 gtid_purged를 초기화하는 데 사용됩니다.
MySQL 5.7.7 또는 그 이전 버전의 바이너리 로그가 이러한 계산에 관련되는 경우, gtid_executed 및 gtid_purged에 대해 잘못된 GTID 세트가 계산될 수 있으며 서버가 나중에 다시 시작 되더라도 올바르지 않은 상태로 유지됩니다. 이 변수는 GTID 세트를 계산하기 위해 바이너리 로그가 반복되는 방법을 제어합니다. 설명된 상황중 하나가 서버에 적용되는 경우, 서버를 시작하기 전에 서버 구성 파일에서 binlog_gtid_simple_recovery = FALSE를 설정합니다. 이 설정은 서버가 GTID 이벤트가 나타나기 시작하는 위치를 찾기 위해 모든 바이너리 로그 파일(최신 파일과 가장 오래된 파일이 아닌)을 반복하도록 합니다. 서버에 GTID 이벤트가 없는 많은 바이너리 로그 파일이 있는 경우 이 프로세스에 시간이 오래 걸릴 수 있습니다.
▶ GTID 실행이력 재설정
서버에서 GTID 실행 히스토리를 재설정해야하는 경우 RESET MASTER 문을 사용합니다. 예를 들어, 새로운 GTID 사용 서버에서 복제 설정을 확인하기 위해 테스트 쿼리를 수행한 후, 또는 새 서버를 복제 그룹에 추가 시키려고하지만 그룹 복제에 의해 허용되지 않는, 원치 않는 로컬 트랜잭션이 포함된 경우 이를 수행해야 합니다. 또한 원하는 GTID 실행 기록 및 바이너리 로그 파일이 손실되지 않도록 주의해서 RESET MASTER를 사용해야 합니다.
RESET MASTER를 수행하기 전에 서버의 바이너리 로그파일 및 바이너리 로그 인덱스파일(있는 경우)의 백업이 있는지 확인하고 gtid_executed 시스템 변수의 글로벌 값에 보유된 GTID 세트를 확보하여 저장합니다. (예 : SELECT @@GLOBAL.gtid_executed를 발행하여 나온 명령문 및 결과 저장). 해당 GTID 세트에서 원치 않는 트랜잭션을 제거하는 경우 mysqlbinlog를 사용하여 트랜잭션 내용을 검사하여 값이 없고, 저장 또는 복제해야하는 데이터가 없고, 서버에서 데이터가 변경되지 않았는지 확인합니다.
RESET MASTER를 수행하면 다음과 같은 재설정 조작이 실행됩니다.
- gtid_purged 시스템 변수의 값이 빈 문자열 ('')로 설정되어 있습니다.
- gtid_executed 시스템 변수의 전역값(세션 값은 아님)이 빈 문자열로 설정됩니다.
- mysql.gtid_executed 테이블이 지워졌습니다(mysql.gtid_executed 테이블 참조).
- 서버에서 바이너리 로깅이 활성화된 경우 기존 바이너리 로그 파일이 삭제되고 바이너리 로그 인덱스 파일이 지워집니다.
RESET MASTER는 서버가 바이너리 로깅이 비활성화된 복제 슬레이브인 경우에도 GTID 실행 기록을 재설정하는 방법입니다. RESET SLAVE는 GTID 실행 기록에 영향을 미치지 않습니다.
■ GTID 자동 위치조정
GTID는 마스터와 슬레이브 간의 데이터 흐름을 시작, 중지 또는 재개하기 위한 포인트를 결정하기 위해 이전에 필요한 파일 오프셋 쌍을 대체합니다. GTID가 사용중일 때 슬레이브와 마스터 동기화에 필요한 모든 정보는 복제 데이터 스트림에서 직접 가져옵니다.
GTID 기반 복제를 사용하여 슬레이브를 시작하려면 지정된 마스터에서 슬레이브로 복제하도록 지시하는데 사용되는 CHANGE MASTER TO 문에 MASTER_LOG_FILE 또는 MASTER_LOG_POS 옵션을 포함하면 안됩니다. 이 옵션은 로그 파일 이름과 파일 내 시작 위치를 지정하지만 GTID를 사용하면 슬레이브에 이 nonlocal(비 로컬) 데이터가 필요하지 않습니다. 대신 MASTER_AUTO_POSITION 옵션을 활성화해야 합니다.
MASTER_AUTO_POSITION 옵션은 기본적으로 비활성화되어 있습니다. 슬레이브에서 다중 소스 복제가 활성화된 경우 적용 가능한 각 복제 채널에 대한 옵션을 설정해야합니다. MASTER_AUTO_POSITION 옵션을 다시 비활성화하면 슬레이브가 파일 기반 복제로 되돌아갑니다.이 경우 MASTER_LOG_FILE 또는 MASTER_LOG_POS 옵션 중 하나 또는 둘 다를 지정해야 합니다.
복제 슬레이브에 GTID가 활성화(GTID_MODE = ON, ON_PERMISSIVE 또는 OFF_PERMISSIVE)되고 MASTER_AUTO_POSITION 옵션이 활성화되면 마스터에 연결하기 위해 자동 위치 지정이 활성화됩니다. 연결에 성공하려면 마스터에 GTID_MODE=ON이 설정되어 있어야 합니다. 초기 핸드 셰이크에서 슬레이브는 이미 수신, 커밋 또는 둘 다 수행한 트랜잭션이 포함된 GTID 세트를 보냅니다. 이 GTID 세트는 gtid_executed 시스템 변수(@@ GLOBAL.gtid_executed)의 GTID 세트와 성능 스키마 replication_connection_status 테이블에 수신 된 트랜잭션으로 기록 된 GTID 세트의 합집합(union)과 같습니다(SELECT RECEIVED_TRANSACTION_SET FROM PERFORMANCE_SCHEMA.replication_connection_status 쿼리를 수행한 결과).
마스터는 슬레이브가 보낸 GTID 세트에 포함되지 않은 GTID의 바이너리 로그에 기록된 모든 트랜잭션을 전송하여 응답합니다. 이 교환은 마스터가 슬레이브로부터 아직 받지못한 GTID와의 거래만을 전송하도록 보장합니다. 다이아몬드 토폴로지의 경우와 같이 슬레이브가 둘 이상의 마스터로부터 트랜잭션을 수신하면 자동 건너 뛰기(Auto Skip) 기능은 트랜잭션이 두번 적용되지 않도록합니다.
마스터가 전송해야하는 트랜잭션이 마스터의 바이너리 로그에서 제거되었거나, 다른 방법으로 gtid_purged 시스템 변수의 GTID 세트에 추가된 경우, 마스터는 오류 ER_MASTER_HAS_PURGED_REQUIRED_GTIDS를 슬레이브로 보내고 복제는 시작하지 않습니다. 제거된 트랜잭션의 GTID가 식별되어 경고 메시지 ER_FOUND_MISSING_GTIDS의 마스터 오류 로그에 표시됩니다. 마스터를 따라 잡는데 필요한 트랜잭션 히스토리 부분이 제거되었으므로 슬레이브가 이 오류에서 자동으로 복구할 수 없습니다. MASTER_AUTO_POSITION 옵션을 사용하지 않고 다시 연결하려고하면 슬레이브에서 제거된 트랜잭션만 손실됩니다. 이 상황에서 복구하는 올바른 방법은 슬레이브가 다른 소스에서 ER_FOUND_MISSING_GTIDS 메시지에 나열된 누락된 트랜잭션을 복제하거나 슬레이브를 최신 백업에서 작성된 새 슬레이브로 교체하는 것입니다. 상황이 다시 발생하지 않도록 마스터에서 바이너리 로그 만료 기간을 수정하여야 합니다.
트랜잭션 교환 중에 슬레이브가 GTID에서 마스터의 UUID와 트랜잭션을 수신 또는 커밋했지만 마스터 자체에 레코드가 없는 경우 마스터는 오류 ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER를 슬레이브로 보내고 복제는 시작하지 않습니다. sync_binlog=1로 설정되지 않은 마스터에 정전 또는 운영 체제 충돌이 발생하고 아직 바이너리 로그 파일과 동기화되지 않았지만 슬레이브가 수신한 커밋된 트랜잭션이 손실된 경우 이러한 상황이 발생할 수 있습니다. 다시 시작한 후 클라이언트가 마스터에서 트랜잭션을 커밋하면 마스터와 슬레이브가 분기 될 수 있으며, 이로 인해 마스터와 슬레이브가 서로 다른 트랜잭션에 동일한 GTID를 사용하는 상황이 발생할 수 있습니다. 이 상황에서 복구하는 올바른 방법은 마스터와 슬레이브가 분기되었는지 수동으로 확인하는 것입니다. 동일한 트랜잭션이 다른 트랜잭션에 사용중인 경우 필요에 따라 개별 트랜잭션에 대해 수동 충돌 해결을 수행하거나 복제 토폴로지에서 마스터 또는 슬레이브를 제거해야합니다. 문제가 마스터에서 트랜잭션 만 누락된 경우 마스터를 대신 슬레이브로 만들고 복제 토폴로지의 다른 서버를 따라 잡을 수 있도록 한 다음 필요한 경우 다시 마스터로 만들 수 있습니다.
GTID 구성방법
https://myinfrabox.tistory.com/30
GTID 에러 해결 방법
https://myinfrabox.tistory.com/246
https://myinfrabox.tistory.com/263
'Databases > MySQL' 카테고리의 다른 글
[MySQL][Backup n Recovery] mysqlimport 프로그램 제대로 파해치기 (0) | 2020.04.17 |
---|---|
[MySQL][Master-Slave] GTID를 이용한 복제 - 구성방법 (0) | 2020.04.09 |
[MySQL][Backup n Recovery] mysqldump 프로그램 제대로 파해치기 (0) | 2020.04.02 |
[MySQL][Master-Slave] 복제 필터링 - 명령문 및 적용방법 (0) | 2020.03.28 |
[MySQL][Master-Slave] 복제 필터링 - 복제평가방법 (0) | 2020.03.28 |