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
+```
+
+
+
+
+```bash
+docker ps
+```
+
+
+
+두 컨테이너가 모두 떴고 성공이다.
+
+
+
+### 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';
+```
+
+
+
+
+
+### 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 기반 자동 포지셔닝의 핵심이다. 바이너리 로그 파일명이나 포지션을 지정할 필요가 없다.
+
+
+
+
+#### 복제 시작
+
+```sql
+START REPLICA;
+```
+
+로그는 다음과 같이 나왔다.
+
+
+
+```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';
+```
+
+
+
+> `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 |
++----+-------+---------------------+
+```
+
+
+
+#### 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 |
++----+-------+---------------------+
+```
+
+
+
+
+Source에서 넣은 데이터가 Replica에서 즉시 확인된다.
+
+실시간으로 데이터를 하나 더 추가해 동기화가 즉시 됐는지 검증해보자.
+
+Source에서 다음과 같이 current_test 를 추가했고
+
+
+바로 Replica에서 확인해보면
+
+
+
+일치하는 결과가 나오는 것을 볼 수 있다.
+
+#### 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
+```
+
+
+
+#### 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