Skip to content

9. Replication và CDC ​

Khi dữ liệu bắt đầu di chuyển: nhân bản database để scale đọc và chịu lỗi, và biến mọi thay đổi thành dòng sự kiện cho các hệ thống khác.

Replication để làm gì ​

  • Tăng khả năng phục vụ truy vấn đọc (read replica).
  • Tách tải phân tích, báo cáo khỏi database chính.
  • Bản sao để backup mà không làm nặng source.
  • Sẵn sàng cao: source hỏng thì đưa một replica lên thay (failover).

Cơ chế ​

SOURCEREPLICAApp ghiCOMMITbinlogROW formatreceiver thread(I/O thread)dump threadrelay logtrên disk replicaapplier threadsreplica_parallel_workersdữ liệu replicaphục vụ đọcMặc định async: source commit xongkhông chờ replica. Failover có thể mấtgiao dịch chưa kịp sang.
Source ghi thay đổi vào binlog; binlog dump thread gửi sự kiện; receiver thread của replica ghi vào relay log; applier thread áp dụng relay log vào dữ liệu. Nút cổ chai thường nằm ở applier (mũi tên đậm).
  • Binlog format: STATEMENT (ghi câu SQL, gọn nhưng có câu không tất định), ROW (ghi giá trị từng dòng thay đổi, mặc định từ 5.7.7, bắt buộc cho CDC), MIXED.
  • GTID: mỗi transaction có định danh server_uuid:số_thứ_tự duy nhất toàn cụm. Replica biết chính xác đã áp dụng tới đâu, nên failover và đổi source không cần dò vị trí file binlog.

Replication lag ​

Luôn có độ trễ giữa source và replica. Lag tăng khi replica không áp dụng kịp lượng thay đổi. Nguyên nhân hay gặp:

  • Applier không đủ song song: source ghi bằng hàng trăm connection song song, replica phải áp dụng lại cùng lượng đó. Bật multi-threaded replica (replica_parallel_workers, mặc định 4 từ 8.0.27).
  • Transaction lớn: DELETE hàng triệu dòng một lần chạy bao lâu trên source thì cũng chừng đó trên replica, và các transaction sau phải chờ. Chia batch.
  • Bảng không có PRIMARY KEY với binlog ROW: mỗi UPDATE/DELETE trên replica có thể phải quét cả bảng để tìm dòng.
  • DDL chạy lâu, replica bận phục vụ đọc nặng, I/O hoặc CPU của replica yếu hơn source, mạng chậm.
Thử: mô phỏng lag

Giả định để minh hoạ: mỗi applier thread áp dụng 400 tx/s, chạy song song đạt 75% hiệu suất. Transaction lớn chặn các transaction sau vì phải giữ thứ tự commit.

400tx/s replica áp dụng được
242 slag ở giây 120
Tụt lại
01252503755000s30s60s90s120slag (giây)

Source ghi 1.200 tx/s nhưng replica chỉ áp dụng được 400 tx/s. Backlog tăng đều, lag tăng mãi không tự hết. Tăng applier threads cho tới khi vượt tốc độ ghi.

Đọc lại thứ vừa ghi ​

User vừa sửa hồ sơ, tải lại trang đọc từ replica và thấy dữ liệu cũ. Cách xử lý:

  • Đọc từ source trong vài giây sau khi user ghi.
  • Lưu GTID sau khi ghi rồi đọc replica bằng WAIT_FOR_EXECUTED_GTID_SET(gtid, timeout).
  • Sticky session cho các request ngay sau khi ghi.

Hệ thống lớn luôn giám sát lag và có chiến lược đọc phù hợp.

Async, semi-sync, group replication ​

Async (mặc định):

  1. Client gửi COMMIT.
  2. Source ghi binlog, commit InnoDB.
  3. Trả OK cho client.
  4. Source sập trước khi dump thread kịp gửi sự kiện.
  5. Failover lên replica: giao dịch client đã nhận OK không còn.

Semi-sync (AFTER_SYNC):

  1. Client gửi COMMIT.
  2. Source ghi và fsync binlog.
  3. Chờ ít nhất 1 replica ghi sự kiện vào relay log và gửi ACK.
  4. Commit InnoDB, trả OK cho client.
  5. Source sập: replica đã có sự kiện, failover không mất giao dịch.

Semi-sync thêm một vòng mạng vào mỗi commit. Quá rpl_semi_sync_source_timeout (mặc định 10 giây) không có ACK thì source tự hạ về async, nên cần giám sát.

Group Replication đi xa hơn: các node đồng thuận (Paxos) trước khi commit, tự bầu primary mới; là nền của InnoDB Cluster. Đổi lại latency commit cao hơn và cần mạng ổn định giữa các node.

Mô hình triển khai ​

  • Một source, nhiều replica: phổ biến nhất.
  • Replica phân tầng: replica có replica riêng, giảm tải gửi binlog cho source; lag cộng dồn qua các tầng.
  • Hai source: thường chạy active–passive, chỉ một node nhận ghi, node kia bật super_read_only. Ghi đồng thời cả hai rất dễ xung đột.
  • Nhiều source về một replica (multi-source): gom dữ liệu nhiều database để báo cáo.
  • Replica trễ có chủ đích: CHANGE REPLICATION SOURCE TO SOURCE_DELAY = 3600 giữ replica chậm 1 giờ, cứu được khi lỡ tay xoá dữ liệu.

Mô hình càng đơn giản càng dễ vận hành và an toàn.

CDC: vì sao theo dõi từng thay đổi ​

Khi hệ thống lớn dần, một thay đổi trong database kéo theo nhiều việc: đẩy bản ghi mới sang hệ thống phân tích, xoá cache để không trả dữ liệu cũ, publish sự kiện cho service khác, cấp dữ liệu cho pipeline realtime. Change Data Capture (CDC) ghi nhận từng thay đổi ngay khi nó xảy ra, như theo dõi một dòng log; khác với cron job hay ETL chạy theo lô mỗi giờ.

Cách phát hiện thay đổiƯu điểmNhược điểm
Cột audit (updated_at, version) + quét định kỳDễ làmKhông thấy dòng bị xoá; phụ thuộc app cập nhật đúng; quét tốn tài nguyên; không realtime
So sánh snapshotChính xácPhải lưu snapshot và quét toàn bảng mỗi lần; độ trễ lớn
Trigger ghi vào bảng thay đổiTự động, đủ INSERT/UPDATE/DELETEChạy đồng bộ trong transaction nên làm chậm mọi lệnh ghi; khó vận hành khi nhiều bảng
Đọc binlog (log-based)Đủ mọi thay đổi kể cả DELETE, gần realtime, không sửa schema hay code appCần binlog ROW và binlog_row_image=FULL (binlog to hơn); thêm một dump thread; snapshot ban đầu quét toàn bảng; binlog phải giữ đủ lâu; cần hệ thống chuyên dụng để parse

Log-based là chuẩn ở hệ thống lớn.

CDC hoạt động thế nào ​

  1. MySQL ghi mọi thay đổi vào binlog.
  2. Công cụ đọc binlog (Debezium) kết nối như một replica và nhận sự kiện liên tục.
  3. Mỗi thay đổi thành một bản ghi sự kiện có trạng thái trước, sau và metadata (vị trí binlog, GTID, thời gian).
  4. Sự kiện được publish vào message system, thường là Kafka.
  5. Các service downstream tiêu thụ theo nhu cầu.
json
{
  "op": "u",
  "before": { "id": 42, "status": 1 },
  "after":  { "id": 42, "status": 2 },
  "source": { "table": "orders", "file": "binlog.000042", "pos": 1337,
              "gtid": "3e11fa47-71ca-11e1-9e33-c80aa9429562:1234" },
  "ts_ms": 1760342400000
}

Ứng dụng của CDC ​

  • Invalidate cache đúng lúc: mỗi thay đổi sinh sự kiện để xoá key tương ứng (Cache).
  • Streaming sang kho phân tích (data warehouse, data lake) gần realtime, không cần job ETL nặng.
  • Outbox pattern: giải bài toán “ghi DB rồi publish message”. Nếu làm hai bước riêng, publish thất bại sau khi ghi DB là mất sự kiện.
1 transactionINSERT ordersINSERT outboxbinlogcommitDebeziumKafkaat-least-once:có thể gửi trùngConsumerdedupeevent_id
Service ghi dữ liệu nghiệp vụ và một dòng vào bảng outbox trong cùng một transaction: cả hai cùng commit hoặc cùng không. CDC đọc bảng outbox và publish. Bảo đảm at-least-once: không mất sự kiện, nhưng có thể gửi trùng khi connector khởi động lại, nên consumer phải idempotent (bỏ qua event_id đã xử lý).

Khi schema thay đổi ​

Thường tương thích:

  • Thêm cột nullable hoặc có default.
  • Mở rộng kiểu số (TINYINT → INT → BIGINT).
  • Đổi qua lại CHAR và VARCHAR, tăng độ dài.
  • Thêm giá trị ENUM vào cuối.
  • Một số chuyển đổi kiểu thời gian.

Dễ làm gãy luồng CDC:

  • Đổi cột nullable thành NOT NULL không có default.
  • Đổi sang kiểu không chuyển đổi được.
  • Đổi tên hoặc xoá cột mà consumer đang dùng.
  • Công cụ online DDL (gh-ost, pt-osc) tạo bảng tạm: cần cấu hình connector bỏ qua.

Thay đổi gây gãy cần phối hợp giữa team sở hữu dữ liệu, team hạ tầng và các consumer; schema registry giúp kiểm tra tương thích trước khi triển khai.

CDC và replication khác nhau thế nào ​

ReplicationCDC
Đầu vàoBinlogBinlog
Đầu raBản sao đầy đủ của database MySQLDòng sự kiện thay đổi cho hệ thống khác
Phục vụScale đọc, tách tải phân tích, backup, failoverĐồng bộ cache, outbox, tích hợp microservice, streaming sang kho phân tích
Người dùng đầu raChính MySQL (replica)Kafka, search engine, warehouse, service khác

Hệ thống lớn thường dùng cả hai: replication để phục vụ đọc, CDC để tạo dòng sự kiện realtime.

Ghi nhớ

  1. Replication mặc định async: failover có thể mất giao dịch; semi-sync khắc phục với giá một vòng mạng.
  2. Lag do applier, transaction lớn, bảng không PK; đọc-sau-ghi cần chiến lược riêng.
  3. CDC log-based là chuẩn; outbox = ghi cùng transaction + consumer idempotent.