diff --git "a/kimhyeonjae/8\354\243\274\354\260\250. MySQL GTID Replication.md" "b/kimhyeonjae/8\354\243\274\354\260\250. MySQL GTID Replication.md" new file mode 100644 index 0000000..5799db3 --- /dev/null +++ "b/kimhyeonjae/8\354\243\274\354\260\250. MySQL GTID Replication.md" @@ -0,0 +1,451 @@ +### 복제 방식 : 바이너리 로그 포지션 vs GTID + +MySQL 복제 방식에는 크게 두 가지가 있다. + +#### 바이너리 로그 포지션 방식 + +```sql +CHANGE REPLICATION SOURCE TO + SOURCE_HOST='source_server_host', + SOURCE_PORT=3306, + SOURCE_USER='repl_user', + SOURCE_PASSWORD='repl_user_password', + SOURCE_LOG_FILE='binary-log.000002', + SOURCE_LOG_POS=2708, + GET_SOURCE_PUBLIC_KEY=1; +``` + +파일명과 오프셋을 직접 지정해야 한다. 페일오버 시 포지션을 정확히 맞춰야 해서 까다롭다. + +#### GTID 방식 + +트랜잭션마다 고유한 ID(GTID)가 부여되어, "어디까지 실행했는지" 자동으로 추적한다. 페일오버나 토폴로지 변경이 훨씬 쉽다. + +```sql +CHANGE REPLICATION SOURCE TO + SOURCE_HOST='source_server_host', + SOURCE_PORT=3306, + SOURCE_USER='repl_user', + SOURCE_PASSWORD='repl_user_password', + SOURCE_AUTO_POSITION=1, + GET_SOURCE_PUBLIC_KEY=1; +``` + +위 바이너리 로그 방식과 `SOURCE_AUTO_POSITION` 옵션이 있다는 점이 다르다. 이 옵션으로 인해 레플리카 서버는 자신의 `gtid_executed` 값 (MySQL 서버에서 실행되어 바이너리 로그 파일에 기록된 모든 트랜잭션들의 GTID 셋을 나타냄) 을 참조해 해당 시점부터 소스 서버와 복제를 연결해서 데이터를 동기화하게 된다. + + +**현재는 GTID가 표준이다.** 바이너리 로그 포지션 방식은 레거시 시스템에서나 볼 수 있고, 새로 구축한다면 GTID를 쓰는 것이 맞다. + +
+ +### 실습 + +#### docker-compose.yml + +```yaml +version: '3.8' + +services: + mysql-source: + image: mysql:8.0 + container_name: mysql-source + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: testdb + ports: + - "3306:3306" + command: + - --server-id=1 + - --log-bin=mysql-bin + - --gtid-mode=ON + - --enforce-gtid-consistency=ON + - --binlog-format=ROW + volumes: + - source-data:/var/lib/mysql + networks: + - mysql-network + + mysql-replica: + image: mysql:8.0 + container_name: mysql-replica + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: testdb + ports: + - "3307:3306" + command: + - --server-id=2 + - --log-bin=mysql-bin + - --gtid-mode=ON + - --enforce-gtid-consistency=ON + - --binlog-format=ROW + - --read-only=ON + volumes: + - replica-data:/var/lib/mysql + networks: + - mysql-network + depends_on: + - mysql-source + +volumes: + source-data: + replica-data: + +networks: + mysql-network: + driver: bridge +``` + +여기서 중요한 점은 설정파일에서 반드시 `gtid-mode=ON` 과 `enforce-gtid-consistency=ON` 을 함께 명시해야 한다. + +만약 `gtid-mode` 만 `ON` 으로 설정되고 `enforce-gtid-consistency` 가 설정되지 않으면 에러가 발생한다. + +#### 컨테이너 실행 + +```bash +docker-compose up -d +``` + +![](https://velog.velcdn.com/images/kguswo/post/ca9a72f9-b844-48e5-996c-622471766a0b/image.png) + + +```bash +docker ps +``` +![](https://velog.velcdn.com/images/kguswo/post/9ce7882d-0b65-4785-9073-1a57664b6015/image.png) + + +두 컨테이너가 모두 떴고 성공이다. + +
+ +### Source 서버 설정 + +#### 복제 계정 생성 + +```sql +CREATE USER 'repl_user'@'%' IDENTIFIED WITH mysql_native_password BY 'repl_password'; +GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%'; +FLUSH PRIVILEGES; +``` + +> MySQL 8.0의 기본 인증 플러그인은 `caching_sha2_password`인데, 복제 연결 시 SSL 없이는 인증 에러가 발생할 수 있다. `mysql_native_password`를 명시적으로 지정하면 이 문제를 피할 수 있다. + + +#### GTID 모드 확인 + +```sql +SHOW VARIABLES LIKE 'gtid_mode'; +``` +![](https://velog.velcdn.com/images/kguswo/post/eab9c08b-ed3d-4e51-bfaf-454b6e4ae94a/image.png) + + +
+ +### Replica 서버 설정 + +#### 복제 설정 + +```sql +CHANGE REPLICATION SOURCE TO + SOURCE_HOST='mysql-source', + SOURCE_USER='repl_user', + SOURCE_PASSWORD='repl_password', + SOURCE_AUTO_POSITION=1; +``` + +`SOURCE_AUTO_POSITION=1`이 GTID 기반 자동 포지셔닝의 핵심이다. 바이너리 로그 파일명이나 포지션을 지정할 필요가 없다. + +![](https://velog.velcdn.com/images/kguswo/post/43a13126-8c38-459a-b440-17f2691452de/image.png) + + +#### 복제 시작 + +```sql +START REPLICA; +``` + +로그는 다음과 같이 나왔다. +![](https://velog.velcdn.com/images/kguswo/post/1ac71e96-260c-45d7-8090-90fe852b6c6f/image.png) + + +```shell +2026-01-04 10:51:52 mysql-replica | 2026-01-04T01:51:52.763802Z 9 +[System] [MY-014002] [Repl] Replica receiver thread for channel '': connected to source 'repl_user@mysql-source:3306' with server_uuid=661772cd-e90f-11f0-ba5c-0242ac130002, server_id=1. Starting GTID-based replication. +``` + +#### 상태 확인 + +```sql +SHOW REPLICA STATUS\G +``` + +```sql +mysql> SHOW REPLICA STATUS\G +*************************** 1. row *************************** + Replica_IO_State: Waiting for source to send event + Source_Host: mysql-source + Source_User: repl_user + Source_Port: 3306 + Connect_Retry: 60 + Source_Log_File: mysql-bin.000003 + Read_Source_Log_Pos: 877 + Relay_Log_File: b8ad115814d2-relay-bin.000003 + Relay_Log_Pos: 1093 + Relay_Source_Log_File: mysql-bin.000003 + Replica_IO_Running: Yes + Replica_SQL_Running: Yes + Replicate_Do_DB: + Replicate_Ignore_DB: + Replicate_Do_Table: + Replicate_Ignore_Table: + Replicate_Wild_Do_Table: + Replicate_Wild_Ignore_Table: + Last_Errno: 0 + Last_Error: + Skip_Counter: 0 + Exec_Source_Log_Pos: 877 + Relay_Log_Space: 2996112 + Until_Condition: None + Until_Log_File: + Until_Log_Pos: 0 + Source_SSL_Allowed: No + Source_SSL_CA_File: + Source_SSL_CA_Path: + Source_SSL_Cert: + Source_SSL_Cipher: + Source_SSL_Key: + Seconds_Behind_Source: 0 +Source_SSL_Verify_Server_Cert: No + Last_IO_Errno: 0 + Last_IO_Error: + Last_SQL_Errno: 0 + Last_SQL_Error: + Replicate_Ignore_Server_Ids: + Source_Server_Id: 1 + Source_UUID: 661772cd-e90f-11f0-ba5c-0242ac130002 + Source_Info_File: mysql.slave_master_info + SQL_Delay: 0 + SQL_Remaining_Delay: NULL + Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates + Source_Retry_Count: 86400 + Source_Bind: + Last_IO_Error_Timestamp: + Last_SQL_Error_Timestamp: + Source_SSL_Crl: + Source_SSL_Crlpath: + Retrieved_Gtid_Set: 661772cd-e90f-11f0-ba5c-0242ac130002:1-9 + Executed_Gtid_Set: 661772cd-e90f-11f0-ba5c-0242ac130002:1-9, +664037aa-e90f-11f0-8055-0242ac130003:1-6 + Auto_Position: 1 + Replicate_Rewrite_DB: + Channel_Name: + Source_TLS_Version: + Source_public_key_path: + Get_Source_public_key: 0 + Network_Namespace: +1 row in set (0.00 sec) +``` + +**여기서 확인해 볼 것은** + +- `Replica_IO_Running: Yes` - Source에서 이벤트를 가져오는 스레드 +- `Replica_SQL_Running: Yes` - 가져온 이벤트를 실행하는 스레드 +- `Seconds_Behind_Source: 0` - 복제 지연 시간 + +둘 다 Yes면 복제가 정상 동작 중이다. + +#### Read-Only 설정 + +복제가 정상 동작하는 것을 확인했으면, Replica에 쓰기를 방지하기 위해 `read-only` 를 설정한다. (`.yml` 파일에서 옵션을 추가해뒀지만 잘 반영이 되지 않아 명시적으로 다시 설정하자) + +```sql +SET GLOBAL read_only = ON; +SET GLOBAL super_read_only = ON; +``` +```sql +SHOW VARIABLES LIKE 'read_only'; +SHOW VARIABLES LIKE 'super_read_only'; +``` +![](https://velog.velcdn.com/images/kguswo/post/95dfc7d2-1025-4520-ab3e-2c16921d5856/image.png) + + +> `read_only`는 일반 유저의 쓰기만 차단하고 root는 쓰기 가능하다. `super_read_only`까지 설정해야 root도 차단된다. + + +> Docker Compose의 command에 `--super-read-only=ON`을 넣으면 초기화 단계에서 실패한다. 복제 설정 완료 후 수동으로 설정하는 것이 안전하다. + + +
+ +### 복제 테스트 + +#### Source에서 데이터 생성 + +```sql +USE testdb; + +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO users (name) VALUES ('test1'); +INSERT INTO users (name) VALUES ('test2'); +INSERT INTO users (name) VALUES ('test3'); + +SELECT * FROM users; +``` + +``` ++----+-------+---------------------+ +| id | name | created_at | ++----+-------+---------------------+ +| 1 | test1 | 2026-01-04 00:41:37 | +| 2 | test2 | 2026-01-04 00:41:44 | +| 3 | test3 | 2026-01-04 00:41:46 | ++----+-------+---------------------+ +``` +![](https://velog.velcdn.com/images/kguswo/post/2fc14857-d47f-40de-9478-86dea76fb766/image.png) + + +#### Replica에서 확인 + +```sql +USE testdb; + +SELECT * FROM users; +``` + +``` ++----+-------+---------------------+ +| id | name | created_at | ++----+-------+---------------------+ +| 1 | test1 | 2026-01-04 00:41:37 | +| 2 | test2 | 2026-01-04 00:41:44 | +| 3 | test3 | 2026-01-04 00:41:46 | ++----+-------+---------------------+ +``` + +![](https://velog.velcdn.com/images/kguswo/post/e767e0e2-bebc-4adf-9ada-29330baa401f/image.png) + + +Source에서 넣은 데이터가 Replica에서 즉시 확인된다. + +실시간으로 데이터를 하나 더 추가해 동기화가 즉시 됐는지 검증해보자. + +Source에서 다음과 같이 current_test 를 추가했고 +![](https://velog.velcdn.com/images/kguswo/post/67305c16-13a4-4366-babf-de9551b06b89/image.png) + +바로 Replica에서 확인해보면 + +![](https://velog.velcdn.com/images/kguswo/post/7c4b84ae-4805-4c29-b69d-acec09236f7f/image.png) + +일치하는 결과가 나오는 것을 볼 수 있다. + +#### GTID 확인 + +양쪽에서 실행: + +```sql +SHOW MASTER STATUS\G +``` + +결과는 다음과 같다. + +**Source:** + +```shell +mysql> SHOW MASTER STATUS\G +*************************** 1. row *************************** + File: mysql-bin.000003 + Position: 2396 + Binlog_Do_DB: + Binlog_Ignore_DB: +Executed_Gtid_Set: 661772cd-e90f-11f0-ba5c-0242ac130002:1-14 +1 row in set (0.00 sec) +``` + +**Replica:** + +```shell +mysql> SHOW MASTER STATUS\G +*************************** 1. row *************************** + File: mysql-bin.000003 + Position: 2997046 + Binlog_Do_DB: + Binlog_Ignore_DB: +Executed_Gtid_Set: 661772cd-e90f-11f0-ba5c-0242ac130002:1-14, +664037aa-e90f-11f0-8055-0242ac130003:1-6 +1 row in set (0.00 sec) +``` + +`Executed_Gtid_Set` 값이 동일하고 완벽하게 동기화된 상태다! + +Source의 GTID 1번부터 14번이 일치하는것을 볼 수 있고, MySQL 초기화 과정에서 일부 추가 트랜잭션 1-6이 있는 걸 볼 수 있다. 컨테이너 시작할 때 자동으로 데이터베이스를 생성하는데, 이 과정에서 트랜잭션이 발생해서 GTID가 생긴다고 볼 수 있다. + +이렇게 Replica를 새로 구축하면 초기화 과정에서 자체 GTID가 생길 수 있으니 염두해두자. + +**중요한 점은 Source의 GTID가 Replica에 모두 포함되어있는가** 이다. + +
+ +### 주의사항 + +#### 1. Replica에는 반드시 read-only 설정 + +Replica에 직접 쓰기를 하면 Source와 데이터 불일치가 발생한다. 게다가 Replica에서 새로운 트랜잭션이 생기는 형태이므로 GTID도 꼬여서 복제가 깨질 수 있다. + +```yaml +command: + - --read-only=ON +``` + +실수로 쓰기를 시도하면: + +```sql +mysql> INSERT INTO users (name) VALUES ('readonly_test'); +ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement +``` +![](https://velog.velcdn.com/images/kguswo/post/e5387097-726e-4d00-a49f-dc434eb9a248/image.png) + + +#### 2. 인증 플러그인 문제 + +MySQL 8.0에서 복제 계정 생성 시 `mysql_native_password`를 명시하지 않으면 다음 에러가 발생할 수 있다. + +```shell +Authentication plugin 'caching_sha2_password' reported error: +Authentication requires secure connection. +``` + +#### 3. 복제 지연 + +Replica는 Source의 변경사항을 비동기로 가져온다. 트래픽이 많으면 지연이 발생할 수 있다. + +```sql +SHOW REPLICA STATUS\G +-- Seconds_Behind_Source: 10 <- 10초 뒤처짐 +``` + +실시간성이 중요한 읽기는 Source에서 처리해야 한다. + +#### 4. mysql_native_password deprecated + +실습에서 `mysql_native_password`를 사용했지만, 이는 deprecated 되었고 향후 제거될 예정이다. + +```shell +[Warning] Plugin mysql_native_password reported: 'mysql_native_password' is deprecated +and will be removed in a future release. Please use caching_sha2_password instead' +``` + +운영 환경에서는 SSL을 설정하고 `caching_sha2_password`를 사용하는 것이 권장된다. + +--- + +### References + +- [MySQL 8.0 Reference Manual - Replication](https://dev.mysql.com/doc/refman/8.0/en/replication.html) +- [GitHub Engineering - MySQL High Availability at GitHub](https://github.blog/engineering/infrastructure/mysql-high-availability-at-github/) +- [Shopify Engineering - Read Consistency with Database Replicas](https://shopify.engineering/read-consistency-database-replicas) \ No newline at end of file