Back to blog

MySQL Replication Cơ Bản: Async, Semi-Sync & GTID Giải Thích Từ A đến Z

mysqlreplicationgtiddatabasehigh-availability
MySQL Replication Cơ Bản: Async, Semi-Sync & GTID Giải Thích Từ A đến Z

Trước khi hiểu Galera Cluster làm gì, bạn cần hiểu nó đang thay thế cái gì — và tại sao việc thay thế đó lại cần thiết.

MySQL replication đã tồn tại từ MySQL 3.23 (năm 2001). Nó hoạt động tốt cho nhiều use case, scale read traffic một cách hiệu quả, và đang vận hành hàng nghìn production system hiện nay. Nhưng nó cũng có những failure mode trở nên thảm khốc vào đúng thời điểm tệ nhất. Hiểu những failure mode đó là điều làm cho các trade-off trong design của Galera trở nên có lý.

Post này cover MySQL replication từ đầu: cách binary log hoạt động, sự khác biệt giữa statement-based và row-based replication, GTID là gì và tại sao quan trọng, trade-off của async vs semi-sync, và các failure scenario thực tế mà mọi operator đều gặp phải.


MySQL Replication Hoạt Động Như Thế Nào

MySQL replication được xây dựng trên một cơ chế nền tảng: binary log (binlog).

Mọi thay đổi dữ liệu trên primary — INSERT, UPDATE, DELETE, DDL — đều được ghi vào binlog trước hoặc trong quá trình transaction commit. Replica kết nối tới primary, stream binlog, và replay lại những thay đổi theo thứ tự.

Ba thread chịu trách nhiệm toàn bộ công việc:

  • IO Thread (trên replica): kết nối tới primary, đọc binlog event, ghi vào relay log local
  • SQL Thread (trên replica): đọc relay log, apply event vào data file của replica
  • Binlog Dump Thread (trên primary): serve binlog event cho IO thread

Architecture này có một đặc tính quan trọng — primary ghi vào binlog và reply cho client trước khi IO thread kịp fetch event. Đó chính là async replication theo định nghĩa.


Binlog Format: Statement, Row, và Mixed

MySQL hỗ trợ ba binlog format, được kiểm soát bởi binlog_format:

Statement-Based Replication (SBR)

Binlog ghi lại câu SQL gốc:

-- Cái được ghi vào binlog:
UPDATE orders SET status = 'shipped' WHERE created_at < NOW() - INTERVAL 1 DAY;

Vấn đề: NOW() trên replica được evaluate khi SQL thread apply câu lệnh — tức là muộn hơn trên primary. Bất kỳ non-deterministic function nào (NOW(), UUID(), RAND(), USER()) đều có thể cho kết quả khác trên replica.

Dùng khi: SQL của bạn hoàn toàn deterministic và bạn muốn binlog nhỏ gọn.

Row-Based Replication (RBR)

Binlog ghi lại giá trị before/after thực tế của mỗi row bị ảnh hưởng:

-- Cái được ghi vào binlog (về mặt khái niệm):
-- Row update: orders.id=42: status 'pending' -> 'shipped'
-- Row update: orders.id=87: status 'pending' -> 'shipped'
-- Row update: orders.id=103: status 'pending' -> 'shipped'
-- ... (một entry cho mỗi row bị ảnh hưởng)

Ưu điểm: Perfectly deterministic. Replica apply đúng những giá trị dữ liệu đó bất kể nó chạy khi nào.

Nhược điểm: Batch lớn sinh ra binlog khổng lồ. Một UPDATE ảnh hưởng 1 triệu row tạo ra 1 triệu binlog entry.

Đây là format Galera sử dụng. PXC yêu cầu binlog_format=ROW — write-set chứa row image, không phải câu SQL.

Mixed Mode

MySQL bắt đầu với statement mode và tự động chuyển sang row mode khi detect thấy non-deterministic statement. Đây là default trong MySQL 8.0.

Setting khuyến nghị cho Galera/PXC: Luôn dùng ROW.


Async Replication: Hành Vi Mặc Định

Trong async replication tiêu chuẩn, primary commit transaction và return cho client mà không đợi bất kỳ replica nào xác nhận đã nhận.

Ý Nghĩa Thực Tế

Replica luôn lag sau primary một khoảng thời gian — replication lag. Lag này thường là millisecond đến giây trong điều kiện bình thường. Khi write load nặng, nó có thể tăng lên phút hoặc giờ.

Bạn có thể đo replication lag bằng:

-- Trên replica:
SHOW SLAVE STATUS\G
-- Tìm: Seconds_Behind_Master
 
-- Chính xác hơn (tính cả network/clock skew):
-- Dùng phương pháp heartbeat table hoặc pt-heartbeat

Vấn Đề Failover

Giả sử primary bị crash. Bạn muốn promote Replica 1 lên làm primary. Nhưng:

Primary:  executed GTID set = 0-1-100
Replica1: executed GTID set = 0-1-97   ← chậm 3 transaction
Replica2: executed GTID set = 0-1-95   ← chậm 5 transaction

Nếu bạn promote Replica 1 ngay lập tức, 3 transaction bị mất vĩnh viễn. Những row đó, những order update đó, những payment đó — mất đứt. Nếu application đã nói với user "payment của bạn đã được xử lý" (vì primary đã nói COMMIT OK), bây giờ bạn có một cái dối trong hệ thống.

Các operator xử lý điều này theo hai cách:

  1. Chấp nhận data loss — với dữ liệu không critical
  2. Đợi replica catch up — không thể nếu primary đã chết (bạn không thể fetch event còn thiếu từ server đã chết)

Đây là vấn đề cốt lõi mà semi-synchronous replication và Galera giải quyết theo những cách khác nhau.


Semi-Synchronous Replication

Semi-sync thay đổi commit protocol để primary đợi ít nhất một replica xác nhận đã nhận binlog event trước khi return cho client.

Điểm phân biệt quan trọng: replica ACK việc nhận event trong relay log của nó, không phải việc apply vào data file. Data chưa được apply khi client nhận OK. Nhưng các byte đã nằm trên disk của ít nhất một replica.

Guarantee Mà Semi-Sync Đảm Bảo

Nếu primary crash sau một semi-sync commit, ít nhất một replica có transaction đó trong relay log và có thể apply nó. Không có committed transaction nào bị mất — miễn là replica được promote là replica đã gửi ACK.

Vấn Đề: Fallback Về Async

MySQL semi-sync có một timeout (rpl_semi_sync_master_timeout, default 10 giây). Nếu không có replica nào ACK trong thời gian timeout, MySQL silently fallback về async.

-- Kiểm tra semi-sync có thực sự active không:
SHOW STATUS LIKE 'Rpl_semi_sync_master_status';
-- Value: ON = semi-sync đang active, OFF = đã fallback về async

Dưới load nặng, network partition, hoặc khi lag đột biến, bạn có thể đang chạy async mà không biết — cho đến khi primary crash và bạn phát hiện data loss.

Cấu Hình Semi-Sync

-- Trên primary (MySQL 8.0):
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;
SET GLOBAL rpl_semi_sync_source_timeout = 1000;     -- timeout 1 giây
SET GLOBAL rpl_semi_sync_source_wait_for_replica_count = 1;  -- đợi 1 ACK
 
-- Trên replica:
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = 1;
 
-- Kiểm tra:
SHOW STATUS LIKE 'Rpl_semi_sync%';

Semi-Sync vs Galera

Semi-sync cho bạn durability (không mất committed data khi failover) nhưng không cho bạn:

  • Automatic failover
  • Multi-master writes
  • Cluster membership management
  • Loại bỏ replication lag (replica vẫn apply async sau khi ACK)

Bạn vẫn cần Orchestrator hoặc MHA để tự động hóa failover. Và replica vẫn lag cho reads.


GTID: Global Transaction Identifiers

GTID là concept quan trọng nhất để hiểu cả hoạt động của MySQL replication và Galera internals.

GTID Là Gì?

Mỗi transaction được execute trên MySQL server đều nhận một Global Transaction Identifier:

GTID = source_uuid:transaction_id
 
Ví dụ: 3E11FA47-71CA-11E1-9E33-C80AA9429562:23

source_uuid là UUID của server (tự động tạo khi startup, lưu trong auto.cnf). transaction_id là một số nguyên tăng dần đơn điệu per server.

Một server theo dõi tập hợp tất cả GTID mà nó đã execute trong biến gtid_executed:

SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
-- Output: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100
-- Nghĩa là: server này đã execute transaction 1 đến 100 từ source đó

Tại Sao GTID Quan Trọng Cho Replication

Trước GTID, replication position được track theo (binlog_file, binlog_position):

CHANGE MASTER TO
  MASTER_HOST='primary',
  MASTER_LOG_FILE='mysql-bin.000003',
  MASTER_LOG_POS=4;

Cái này gắn với một file binlog cụ thể trên một server cụ thể. Nếu bạn promote một replica lên làm primary, mọi replica khác phải tự tìm position tương đương trong binlog của primary mới — một quá trình dễ sai.

Với GTID, position là universal:

CHANGE MASTER TO
  MASTER_HOST='new-primary',
  MASTER_AUTO_POSITION=1;   -- "cho tôi mọi thứ tôi đang thiếu"

MySQL tự động tính toán gap bằng cách so sánh gtid_executed set.

GTID Trong Failover

Primary UUID:  A
Replica1 UUID: B
Replica2 UUID: C
 
Trước crash:
  Primary (A): A:1-100 (tất cả 100 transaction)
  Replica1 (B): A:1-97  (thiếu A:98-100)
  Replica2 (C): A:1-95  (thiếu A:96-100)

Replica1 là replica "tiên tiến nhất". Sau khi promote:

-- Replica2 kết nối lại với Replica1 (giờ là primary):
CHANGE MASTER TO MASTER_HOST='replica1', MASTER_AUTO_POSITION=1;
-- MySQL tính toán: Replica1 có A:1-100, Replica2 có A:1-95
-- Tự động fetch và apply A:96-100

Không cần tính toán binlog position thủ công. Đây là lý do tại sao GTID-based replication là standard cho mọi HA setup nghiêm túc.

Bật GTID

# my.cnf (cả primary và replica)
[mysqld]
gtid_mode                = ON
enforce_gtid_consistency = ON
log_bin                  = mysql-bin
binlog_format            = ROW
server_id                = 1   # unique per server
-- Kiểm tra trên primary:
SHOW GLOBAL VARIABLES LIKE 'gtid_mode';
-- gtid_mode | ON
 
SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
-- Tập hợp tất cả committed GTID trên server này

Giới Hạn Của GTID

Với enforce_gtid_consistency = ON, một số operation bị cấm vì không thể được track đáng tin cậy như atomic transaction:

-- Những lệnh này sẽ fail khi GTID được bật:
CREATE TABLE ... SELECT ...;   -- kết hợp DDL + DML trong một statement
CREATE TEMPORARY TABLE ...;    -- bên trong transaction
DROP TEMPORARY TABLE ...;      -- bên trong transaction

Điều này quan trọng với Galera — PXC áp dụng cùng giới hạn và thêm nhiều giới hạn nữa qua PXC Strict Mode.


Replication Lag: Nguyên Nhân Và Cách Đo

Replication lag là thời gian giữa khi primary commit transaction và khi replica đã apply nó.

Nguyên Nhân Chính

1. SQL thread single-threaded (MySQL < 5.6)

Trước MySQL 5.6, SQL thread apply event theo thứ tự tuần tự. Nếu primary xử lý 1.000 write/giây, SQL thread phải xử lý từng cái một và không thể theo kịp.

2. Multi-threaded replication (MySQL 5.6+)

MySQL 5.6 giới thiệu parallel replication — nhiều SQL thread làm việc song song, mỗi thread cho một database schema. MySQL 5.7 cải thiện điều này với logical-clock-based parallelism (các transaction không conflict có thể apply song song).

# Bật multi-threaded replication:
slave_parallel_workers   = 4
slave_parallel_type      = LOGICAL_CLOCK
slave_preserve_commit_order = ON

3. Transaction lớn

Một UPDATE orders SET ... ảnh hưởng 5 triệu row tạo ra một binlog event khổng lồ. SQL thread apply nó như một operation — không có parallelism.

4. Băng thông mạng

Write workload nặng sinh ra binlog lớn. Nếu IO thread của replica không thể fetch event đủ nhanh, nó bị tụt hậu ở layer mạng.

5. Backup operation

Chạy mysqldump --single-transaction trên replica giữ SQL thread của replica trong consistent read view, có thể khiến nó lag trong khi backup chạy.

Đo Lag Chính Xác

Seconds_Behind_Master trong SHOW SLAVE STATUS đo sự chênh lệch timestamp giữa event hiện tại của SQL thread và primary. Nó thiếu độ tin cậy vì:

  • Reset về 0 khi SQL thread idle (dù IO thread vẫn đang fetch)
  • Clock skew giữa các server làm sai lệch kết quả
  • Hiển thị 0 nếu replication bị broken

Tool production-grade là pt-heartbeat từ Percona Toolkit:

# Trên primary: insert heartbeat row mỗi giây
pt-heartbeat --update --daemonize \
  --database heartbeat --host primary \
  --user repl --password secret
 
# Trên replica: đo lag thực tế
pt-heartbeat --monitor --host replica \
  --database heartbeat --user repl --password secret
# Output: 0.00s [  0.00s,  0.00s,  0.00s ]
# Giây thực tế  <1m avg  <5m avg  <15m avg

Tool Automatic Failover

Async replication cần tooling bên ngoài để tự động hóa failover. Hai tool thống trị lĩnh vực này:

Orchestrator

MySQL topology manager được sử dụng rộng rãi nhất:

  • Tự động discover topology replication
  • Detect primary failure qua health check
  • Promote replica tiên tiến nhất
  • Trỏ lại các replica khác về primary mới
  • Cung cấp REST API và web UI

Orchestrator + semi-sync replication là setup HA chắc chắn — nhưng bạn vẫn đang quản lý một external service, xử lý VIP switch, và chấp nhận failover mất khoảng 30 giây trong điều kiện tốt nhất.

MySQL Router (InnoDB Cluster)

Official router của MySQL cho InnoDB Cluster setup (Group Replication). Được cover trong Post 10 của series này.


Mental Model Về Replication: Galera Thay Đổi Điều Gì

Đây là so sánh những gì mỗi loại replication đảm bảo:

PropertyAsyncSemi-SyncGalera
Write committed = durable trên replicaKhôngTrên 1+ replica (chỉ relay log)Có, trên quorum
Replication lagCó (ms đến giờ)Có (cùng lag, chỉ là event durable)Không có (synchronous)
Multi-master writesKhôngKhôngCó
Auto failoverCần external toolCần external toolBuilt-in
Replica đọc không có lagKhôngKhôngCó
Write overhead mỗi commitKhông cóNetwork round-tripNetwork round-trip + certification
WAN-friendlyCóPhần nàoNhạy cảm với latency

Trade-off mà Galera thực hiện: mỗi commit tốn một certification round-trip đến tất cả node. Trên LAN, thường là 1–5ms. Galera không phù hợp cho các node phân tán địa lý khi latency vượt quá ~30ms.


Thực Hành: Thiết Lập Async Replication Cổ Điển

Hãy cùng thiết lập một cặp async replication hoạt động được bằng Docker Compose — đây là nền tảng mà Galera thay thế.

# docker-compose.yml
services:
  mysql-primary:
    image: percona/percona-server:8.0
    container_name: mysql-primary
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_REPLICATION_USER: replicator
      MYSQL_REPLICATION_PASSWORD: replpass
    command: >
      --server-id=1
      --gtid-mode=ON
      --enforce-gtid-consistency=ON
      --log-bin=mysql-bin
      --binlog-format=ROW
      --log-replica-updates=ON
    ports:
      - "3306:3306"
    volumes:
      - primary-data:/var/lib/mysql
 
  mysql-replica:
    image: percona/percona-server:8.0
    container_name: mysql-replica
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
    command: >
      --server-id=2
      --gtid-mode=ON
      --enforce-gtid-consistency=ON
      --log-bin=mysql-bin
      --binlog-format=ROW
      --log-replica-updates=ON
      --skip-replica-start
    ports:
      - "3307:3306"
    volumes:
      - replica-data:/var/lib/mysql
    depends_on:
      - mysql-primary
 
volumes:
  primary-data:
  replica-data:

Khởi động và cấu hình replication:

docker compose up -d
 
# Đợi cả hai server sẵn sàng
sleep 20
 
# Tạo replication user trên primary
docker exec mysql-primary mysql -uroot -prootpass -e "
  CREATE USER IF NOT EXISTS 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'replpass';
  GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';
  FLUSH PRIVILEGES;
"
 
# Cấu hình replica kết nối tới primary
docker exec mysql-replica mysql -uroot -prootpass -e "
  CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='mysql-primary',
    SOURCE_USER='replicator',
    SOURCE_PASSWORD='replpass',
    SOURCE_AUTO_POSITION=1;
  START REPLICA;
"
 
# Kiểm tra replication đang chạy
docker exec mysql-replica mysql -uroot -prootpass -e "SHOW REPLICA STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind"

Output mong đợi:

Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 0

Test replication:

# Tạo dữ liệu trên primary
docker exec mysql-primary mysql -uroot -prootpass -e "
  CREATE DATABASE testdb;
  USE testdb;
  CREATE TABLE messages (id INT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
  INSERT INTO messages (content) VALUES ('Hello từ primary'), ('Message thứ hai');
"
 
# Kiểm tra nó xuất hiện trên replica
docker exec mysql-replica mysql -uroot -prootpass -e "
  USE testdb; SELECT * FROM messages;
"

Mô Phỏng Replication Lag

Một bài tập hữu ích: mô phỏng lag và quan sát Seconds_Behind_Master.

# Trên primary: tạo một write lớn
docker exec mysql-primary mysql -uroot -prootpass -e "
  USE testdb;
  CREATE TABLE big_table (id INT AUTO_INCREMENT PRIMARY KEY, data VARCHAR(255));
  INSERT INTO big_table (data)
  SELECT REPEAT('x', 255) FROM information_schema.COLUMNS a
  CROSS JOIN information_schema.COLUMNS b
  LIMIT 100000;
"
 
# Trong khi insert đang chạy, kiểm tra replica lag:
docker exec mysql-replica mysql -uroot -prootpass -e "
  SHOW REPLICA STATUS\G" | grep Seconds_Behind

Bạn sẽ thấy Seconds_Behind_Source tăng lên. Khi SQL thread bắt kịp, nó giảm về 0. Đây là cửa sổ "eventual consistency" khi reads từ replica trả về dữ liệu cũ.


Các Status Variable Quan Trọng Để Monitor

-- Trên replica: health của replication
SHOW REPLICA STATUS\G
 
-- Các field quan trọng:
--   Replica_IO_Running     → YES nghĩa là IO thread đang kết nối và chạy
--   Replica_SQL_Running    → YES nghĩa là SQL thread đang apply event
--   Seconds_Behind_Source  → lag tính bằng giây (0 = đã bắt kịp)
--   Last_Error             → lỗi replication cuối cùng (trống = khỏe mạnh)
--   Executed_Gtid_Set      → GTID đã apply trên replica này
 
-- Trên primary: replica đang kết nối
SHOW PROCESSLIST;
-- Tìm state "Slave: has sent all binlog to slave"
 
-- Trạng thái binlog:
SHOW BINARY LOGS;
SHOW MASTER STATUS;

Lỗi Replication Phổ Biến

Lỗi 1062: Duplicate entry

Last_Error: Could not execute Write_rows event on table testdb.messages;
Duplicate entry '42' for key 'PRIMARY'

Nguyên nhân: Một row được ghi trực tiếp lên replica (bypass primary) và bây giờ conflict với replication đến.

Fix:

-- Skip failing event (biện pháp tạm thời, cần điều tra nguyên nhân gốc):
STOP REPLICA;
SET GLOBAL SQL_REPLICA_SKIP_COUNTER = 1;
START REPLICA;
 
-- Fix lâu dài: bật read-only trên replica
SET GLOBAL read_only = 1;
SET GLOBAL super_read_only = 1;   -- ngăn cả SUPER user ghi

Lỗi 1032: Row not found

Last_Error: Could not execute Update_rows event; Can't find record in 'orders'

Nguyên nhân: Replica thiếu một row mà primary kỳ vọng tồn tại. Data divergence.

Fix: Dùng pt-table-checksum và pt-table-sync từ Percona Toolkit để xác định và fix dữ liệu bị diverge.

IO Thread Dừng

Replica_IO_Running: No
Last_IO_Error: error connecting to master

Nguyên nhân: Network partition, primary restart, hoặc firewall thay đổi.

Fix: Thường tự hồi phục. Nếu không:

STOP REPLICA;
START REPLICA;

Chuẩn Bị Mental Model Cho Galera

Sau khi đi qua post này, bạn đã hiểu:

✅ Binlog event là đơn vị nguyên tử của MySQL replication
✅ Row-based format bắt buộc với Galera
✅ Async replication có durability gap — committed data có thể bị mất khi failover
✅ Semi-sync đóng gap durability nhưng không đóng gap lag, và có thể silently fallback
✅ GTID cung cấp universal transaction position qua topology change
✅ Replication lag là thực tế, có thể đo được, và ảnh hưởng đến read consistency
✅ Failover automation với async replication cần external tool và cửa sổ downtime ~30 giây

Trong post tiếp theo, chúng ta nhìn vào cách Galera giải quyết những vấn đề này bằng cách từ bỏ hoàn toàn mô hình binlog streaming và thay thế bằng write-set certification — một replication protocol hoàn toàn khác.

Tiếp tục Post 3 → Galera Cluster Architecture


Điều hướng series:
← Post 1: MySQL HA Roadmap Overview
→ Post 3: Galera Cluster Architecture

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.