BÀI 01 · NỀN TẢNG

Tổng quan & Kiến trúc MySQL / MariaDB

Hiểu bản chất fork MariaDB, storage engine, và cách các thành phần bên trong phối hợp.

MySQL và MariaDB khác nhau thế nào?

MariaDB là bản fork của MySQL (2009, sau khi Oracle mua Sun/MySQL), do chính các thành viên sáng lập MySQL cũ tạo ra. Về cơ bản MariaDB vẫn tương thích giao thức và cú pháp SQL với MySQL, nhưng có driver, storage engine và tính năng phát triển độc lập theo thời gian:

Tiêu chíMySQL (Oracle)MariaDB
Giấy phépGPL + Enterprise (trả phí cho bản thương mại)GPL hoàn toàn mở, cộng đồng dẫn dắt
Storage engine mặc địnhInnoDBInnoDB (tương thích), có thêm Aria, ColumnStore, MyRocks
Cluster gốcMySQL NDB Cluster (riêng)Galera Cluster tích hợp sẵn
ReplicationGTID (MySQL-style)GTID (MariaDB-style, khác cú pháp), tương thích ngược binlog
Migrate qua lạiThường tương thích ở mức cơ bản, nhưng KHÔNG đảm bảo 100% từ MySQL 8.x → MariaDB (khác biệt về data dictionary, roles, JSON functions...)
Lưu ý thực tế: Từ MySQL 8.0 và MariaDB 10.5+ trở đi, hai nhánh phân kỳ khá rõ (MySQL dùng data dictionary mới, MariaDB vẫn dùng FRM + kết hợp). Không nên coi hai bản là "thay thế 1-1" khi lập kế hoạch migrate — cần test kỹ.

Kiến trúc tổng thể

Server chia làm 2 lớp chính:

Các storage engine quan trọng

EngineTransactionLockingDùng khi nào
InnoDBCó (ACID)Row-levelMặc định cho hầu hết production OLTP
Aria (MariaDB)Không (crash-safe)Table/row tùy cấu hìnhThay thế MyISAM cho bảng hệ thống
MyISAMKhôngTable-levelLegacy, hạn chế dùng mới do không transaction-safe
Memory (Heap)KhôngTable-levelBảng tạm, cache trong RAM, mất dữ liệu khi restart
ColumnStore/MyRocksTùy loại-Analytics (columnar) hoặc write-heavy (LSM-tree)

Các tiến trình/thành phần chạy nền InnoDB cần nhớ

Vì sao cần hiểu kiến trúc này? Khi troubleshoot production (như các ca ORA tương tự bên Oracle anh hay xử lý), phần lớn sự cố MySQL/MariaDB đều quy về 3 nhóm: (1) I/O tại buffer pool/redo log, (2) lock contention ở tầng row/table, (3) replication lag ở tầng binlog/relay log. Nắm chắc luồng dữ liệu giúp định vị nhanh nguyên nhân.

📝 Câu hỏi ôn tập – Bài 01

1.MariaDB và MySQL có tương thích 100% để migrate qua lại không? Vì sao?
Xem đáp án
Không đảm bảo 100%, đặc biệt từ MySQL 8.0 trở đi. MySQL 8.0 dùng data dictionary mới (lưu trong InnoDB), khác cách MariaDB quản lý metadata; ngoài ra khác biệt về roles, JSON functions, GTID format. Cần test kỹ trước khi migrate, đặc biệt với ứng dụng dùng nhiều tính năng đặc thù (window functions, CTE, JSON).
2.Buffer pool và Redo log khác nhau ở vai trò gì?
Xem đáp án
Buffer pool là vùng cache trong RAM chứa dữ liệu/index để tăng tốc đọc/ghi, giảm I/O đĩa. Redo log là log vật lý ghi thay đổi trước khi commit (WAL) để đảm bảo durability — khi crash, InnoDB dùng redo log để replay lại các thay đổi chưa kịp flush xuống datafile.
3.Vì sao MyISAM không phù hợp cho hệ thống OLTP có giao dịch quan trọng?
Xem đáp án
MyISAM không hỗ trợ transaction (không rollback được), dùng table-level locking (một ghi sẽ khóa toàn bảng, giảm concurrency mạnh), và không crash-safe hoàn toàn — dễ mất/hỏng dữ liệu khi hệ thống crash giữa chừng.
4.Binlog thuộc tầng nào trong kiến trúc, và vai trò chính là gì?
Xem đáp án
Binlog nằm ở tầng SQL Layer (Server layer), không thuộc riêng storage engine nào. Vai trò chính: ghi lại các thay đổi dữ liệu (statement hoặc row-based) để dùng cho replication (slave đọc và apply lại) và point-in-time recovery (khôi phục tới một thời điểm cụ thể sau khi restore full backup).
BÀI 02 · NỀN TẢNG

Cài đặt hệ thống

Cài MariaDB/MySQL trên RHEL/Rocky/Ubuntu, qua repo chính thức, và bằng Docker.

Cài đặt MariaDB trên RHEL/Rocky/AlmaLinux (dnf/yum)

Nên dùng repo chính thức của MariaDB thay vì repo mặc định của distro để chủ động chọn version:

# Thêm repo chính thức MariaDB (ví dụ 10.11 LTS)
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="mariadb-10.11"

# Cài server + client
sudo dnf install -y MariaDB-server MariaDB-client

# Khởi động và enable service
sudo systemctl enable --now mariadb

# Cấu hình bảo mật ban đầu (đặt root pass, xóa test db, disable remote root...)
sudo mysql_secure_installation
Copy

Cài đặt trên Ubuntu/Debian (apt)

sudo apt update
sudo apt install -y mariadb-server mariadb-client

sudo systemctl enable --now mariadb
sudo mysql_secure_installation

# Kiểm tra version và trạng thái
mysql --version
sudo systemctl status mariadb
Copy

Cài đặt offline (không có Internet) — dùng RPM local

Thường gặp trong datacenter kín mạng (giống mô hình anh làm với NGINX Plus HA offline). Quy trình chuẩn:

  1. Trên máy có Internet: dnf download --resolve --alldeps MariaDB-server MariaDB-client để tải toàn bộ gói + dependency.
  2. Copy thư mục RPM sang máy đích qua SCP/USB/SMB.
  3. Tạo repo local bằng createrepo_c hoặc cài trực tiếp bằng dnf install ./*.rpm (dnf tự resolve dependency nội bộ thư mục nếu đủ gói).
  4. Kiểm tra rpm -qa | grep -i maria để xác nhận đã cài đủ.
Mẹo: Luôn tải kèm gói galera-* nếu dự định dùng Galera Cluster sau này — tránh phải tải lại lần 2 khi mở rộng HA.

Cài đặt bằng Docker (môi trường test/dev)

docker run -d --name mariadb-test \
  -e MARIADB_ROOT_PASSWORD=StrongPass123! \
  -e MARIADB_DATABASE=appdb \
  -p 3306:3306 \
  -v mariadb_data:/var/lib/mysql \
  mariadb:10.11

# Kết nối thử
docker exec -it mariadb-test mariadb -uroot -p
Copy
Lưu ý: Docker phù hợp cho dev/test/CI. Cho production nên cân nhắc kỹ về persistent volume, network mode, resource limit (cgroup) và backup chiến lược riêng — không nên coi Docker container là "cài đặt production" mặc định.

Kiểm tra sau cài đặt

mysql -u root -p -e "SELECT VERSION();"
mysql -u root -p -e "SHOW ENGINES\G"
mysql -u root -p -e "SHOW VARIABLES LIKE 'datadir';"
sudo ss -lntp | grep 3306
Copy

📝 Câu hỏi ôn tập – Bài 02

1.Vì sao nên dùng repo chính thức MariaDB thay vì repo mặc định của RHEL/Ubuntu?
Xem đáp án
Repo mặc định của distro thường chậm cập nhật, đôi khi khóa cứng ở một phiên bản cũ do chính sách LTS của distro. Repo chính thức MariaDB cho phép chọn chính xác version (kể cả minor version), nhận security patch nhanh hơn, và nhất quán giữa các distro khác nhau khi triển khai đa nền tảng.
2.Trong môi trường không có Internet, cần chuẩn bị gì trước khi cài offline?
Xem đáp án
Cần tải trước toàn bộ RPM/DEB kèm dependency (dùng máy có mạng, lệnh như dnf download --resolve --alldeps), copy sang máy đích, và có thể dựng repo local (createrepo_c) để dnf/yum resolve đúng thứ tự cài đặt. Nên tải kèm cả gói mở rộng dự kiến dùng sau này (ví dụ Galera) để tránh phải lặp lại quy trình.
3.mysql_secure_installation thực hiện những gì?
Xem đáp án
Đặt/đổi mật khẩu root, xóa user anonymous, disallow remote root login, xóa database test mặc định, và reload privilege table — nhằm đưa server về trạng thái bảo mật tối thiểu ngay sau khi cài đặt.
4.Khi dùng Docker cho MariaDB, vì sao cần mount volume ra ngoài container?
Xem đáp án
Vì filesystem bên trong container là ephemeral — nếu container bị xóa/tái tạo mà không mount volume ra ngoài (ví dụ /var/lib/mysql), toàn bộ dữ liệu sẽ mất. Mount volume (named volume hoặc bind mount) đảm bảo dữ liệu tồn tại độc lập với vòng đời container.
BÀI 03 · NỀN TẢNG

Quản trị cơ bản

User, privilege, database/schema, và các thao tác quản trị hàng ngày.

Quản lý user và phân quyền

-- Tạo user chỉ cho phép kết nối từ 1 host cụ thể (best practice)
CREATE USER 'app_user'@'10.0.1.%' IDENTIFIED BY 'StrongPass!23';

-- Cấp quyền theo nguyên tắc least-privilege
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_user'@'10.0.1.%';

-- Quyền replication cho user dùng để dựng slave
CREATE USER 'repl'@'10.0.2.%' IDENTIFIED BY 'ReplPass!23';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.2.%';

-- Áp dụng thay đổi
FLUSH PRIVILEGES;

-- Xem quyền hiện có của 1 user
SHOW GRANTS FOR 'app_user'@'10.0.1.%';

-- Thu hồi quyền
REVOKE DELETE ON appdb.* FROM 'app_user'@'10.0.1.%';
Copy
Tránh dùng 'user'@'%' cho production. Wildcard host mở kết nối từ mọi IP — chỉ nên dùng cho môi trường test cô lập. Với production, luôn giới hạn theo subnet hoặc IP cụ thể, tương tự nguyên tắc anh áp dụng khi hạn chế RDP/SMB access ở VNET.

Roles (MariaDB 10.0.5+ / MySQL 8.0+)

CREATE ROLE 'readonly_role';
GRANT SELECT ON appdb.* TO 'readonly_role';

CREATE USER 'reporting'@'10.0.3.%' IDENTIFIED BY 'Pass!23';
GRANT 'readonly_role' TO 'reporting'@'10.0.3.%';
SET DEFAULT ROLE 'readonly_role' FOR 'reporting'@'10.0.3.%';
Copy

Roles giúp quản lý quyền theo nhóm chức năng thay vì gán trực tiếp từng user — khi thay đổi chính sách chỉ cần sửa role, không phải sửa từng tài khoản.

Quản lý database/schema

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
SHOW DATABASES;
USE appdb;
SHOW TABLES;
SHOW TABLE STATUS\G
SHOW CREATE TABLE orders\G
Copy
Luôn dùng utf8mb4, không dùng utf8. utf8 trong MySQL/MariaDB thực chất chỉ hỗ trợ tối đa 3 byte/ký tự, không đủ cho emoji và một số ký tự Unicode mở rộng — dễ gây lỗi khi ứng dụng xử lý dữ liệu quốc tế.

Xử lý user hết hạn / khóa tài khoản

-- Khóa / mở khóa tài khoản
ALTER USER 'app_user'@'10.0.1.%' ACCOUNT LOCK;
ALTER USER 'app_user'@'10.0.1.%' ACCOUNT UNLOCK;

-- Đặt chính sách hết hạn mật khẩu
ALTER USER 'app_user'@'10.0.1.%' PASSWORD EXPIRE INTERVAL 90 DAY;

-- Đổi mật khẩu ngay
ALTER USER 'app_user'@'10.0.1.%' IDENTIFIED BY 'NewPass!23';
Copy

Các lệnh quản trị thường dùng hàng ngày

-- Xem process/connection đang chạy
SHOW FULL PROCESSLIST;

-- Kill 1 session/query bị treo
KILL 12345;

-- Xem trạng thái server tổng quan
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Innodb_buffer_pool%';

-- Xem biến cấu hình đang áp dụng
SHOW VARIABLES LIKE '%timeout%';
Copy

📝 Câu hỏi ôn tập – Bài 03

1.Vì sao không nên tạo user với host là '%' trong môi trường production?
Xem đáp án
Vì '%' cho phép kết nối từ bất kỳ địa chỉ IP nào, mở rộng bề mặt tấn công (attack surface) không cần thiết. Nếu credential bị lộ, kẻ tấn công có thể kết nối từ bất kỳ đâu. Nên giới hạn theo subnet nội bộ hoặc IP application server cụ thể.
2.Roles trong MariaDB/MySQL giải quyết vấn đề gì so với gán quyền trực tiếp?
Xem đáp án
Roles cho phép nhóm nhiều quyền lại thành một đơn vị logic (ví dụ readonly_role, admin_role) và gán role đó cho nhiều user. Khi cần thay đổi chính sách phân quyền, chỉ cần sửa role một lần thay vì phải REVOKE/GRANT lặp lại trên từng tài khoản riêng lẻ — giảm rủi ro sai sót và dễ audit.
3.utf8mb4 khác gì so với utf8 trong MySQL/MariaDB, và tại sao nên ưu tiên utf8mb4?
Xem đáp án
"utf8" trong MySQL/MariaDB là bảng mã giới hạn tối đa 3 byte/ký tự (không đúng chuẩn UTF-8 đầy đủ), không biểu diễn được các ký tự 4-byte như emoji hoặc một số chữ Hán/Nhật hiếm. utf8mb4 hỗ trợ đầy đủ tới 4 byte/ký tự theo đúng chuẩn Unicode, tránh lỗi "Incorrect string value" khi lưu dữ liệu quốc tế.
4.Làm sao xác định và xử lý một session đang treo, chiếm lock lâu?
Xem đáp án
Dùng SHOW FULL PROCESSLIST (hoặc information_schema.PROCESSLIST) để xem danh sách session, cột Time và State giúp xác định session chạy lâu hoặc ở trạng thái "Locked". Sau khi xác định đúng ID gây vấn đề (đối chiếu thêm với information_schema.INNODB_LOCK_WAITS nếu nghi ngờ deadlock/lock chờ), dùng KILL để chấm dứt session đó.
BÀI 04 · NỀN TẢNG

Backup & Restore

mysqldump, mariabackup/xtrabackup, binlog cho point-in-time recovery.

So sánh các phương pháp backup

Phương phápLoạiƯu điểmNhược điểm
mysqldumpLogicalĐơn giản, portable giữa version/engineChậm với DB lớn, restore chậm, khóa bảng khi không dùng --single-transaction
mariabackup / xtrabackupPhysical (hot backup)Nhanh, không khóa (InnoDB), phù hợp DB lớnPhức tạp hơn, chỉ dùng lại được trên cùng loại engine/version tương thích
Binlog + full backupIncremental/PITRKhôi phục tới thời điểm bất kỳ (point-in-time)Cần quản lý chuỗi binlog liên tục, mất binlog = mất khả năng PITR
Snapshot (LVM/ZFS/SAN)PhysicalRất nhanh, gần như tức thờiPhụ thuộc hạ tầng storage, cần quiesce đúng cách

mysqldump — backup logic

# Backup 1 database, dùng --single-transaction để không khóa bảng InnoDB
mysqldump -u root -p --single-transaction --routines --triggers --events \
  --master-data=2 appdb > appdb_$(date +%F).sql

# Backup toàn bộ instance (tất cả database)
mysqldump -u root -p --single-transaction --all-databases --routines --triggers \
  > full_backup_$(date +%F).sql

# Restore
mysql -u root -p appdb < appdb_2026-07-07.sql
Copy
--master-data=2 ghi vị trí binlog (file + position) tại thời điểm backup dưới dạng comment trong file dump — rất quan trọng nếu backup này dùng để dựng slave hoặc làm điểm khởi đầu cho PITR.

mariabackup — backup vật lý (hot backup, không khóa)

# Full backup
mariabackup --backup --target-dir=/backup/full \
  --user=root --password=xxxx

# Chuẩn bị (prepare) trước khi restore — apply log để đưa về trạng thái consistent
mariabackup --prepare --target-dir=/backup/full

# Restore: dừng service, xóa datadir cũ, copy-back
sudo systemctl stop mariadb
sudo mv /var/lib/mysql /var/lib/mysql_old
mariabackup --copy-back --target-dir=/backup/full
sudo chown -R mysql:mysql /var/lib/mysql
sudo systemctl start mariadb

# Incremental backup (dựa trên full backup trước đó)
mariabackup --backup --target-dir=/backup/inc1 \
  --incremental-basedir=/backup/full \
  --user=root --password=xxxx
Copy

Point-in-time Recovery (PITR) bằng binlog

Kịch bản: full backup lúc 00:00, sự cố xóa nhầm dữ liệu lúc 09:15, cần khôi phục tới ngay trước 09:15.

# 1. Restore full backup (từ mariabackup hoặc mysqldump) trước

# 2. Xác định binlog position/thời điểm cần dừng lại
mysqlbinlog --start-datetime="2026-07-07 00:00:00" \
            --stop-datetime="2026-07-07 09:14:59" \
            /var/log/mysql/mysql-bin.000123 | mysql -u root -p

# Hoặc theo GTID range nếu dùng GTID replication
mysqlbinlog --include-gtids="0-1-100:1-500" mysql-bin.000123 | mysql -u root -p
Copy
Nguyên tắc PITR: Full/incremental backup định kỳ + giữ toàn bộ binlog liên tục kể từ backup gần nhất = có thể khôi phục tới bất kỳ giây nào. Nếu thiếu một binlog trong chuỗi, chuỗi PITR bị đứt tại đó.

Kiểm tra tính toàn vẹn bản backup

📝 Câu hỏi ôn tập – Bài 04

1.Khi nào nên chọn mariabackup thay vì mysqldump?
Xem đáp án
Khi database có kích thước lớn (hàng chục/trăm GB trở lên), cần backup nhanh mà không ảnh hưởng tới tải ghi của production (mariabackup là hot backup vật lý, không khóa bảng InnoDB), và cần restore nhanh (physical restore thường nhanh hơn nhiều so với replay lại toàn bộ câu lệnh SQL từ mysqldump).
2.--single-transaction trong mysqldump có tác dụng gì và giới hạn của nó?
Xem đáp án
Nó cho phép mysqldump chạy trong một transaction duy nhất với isolation REPEATABLE READ, tận dụng MVCC của InnoDB để lấy snapshot nhất quán mà không cần khóa bảng (LOCK TABLES). Giới hạn: chỉ áp dụng đúng cho bảng InnoDB (transactional); nếu database có bảng MyISAM, tính nhất quán giữa các bảng không được đảm bảo hoàn toàn vì MyISAM không tham gia transaction.
3.Điều kiện tiên quyết để thực hiện Point-in-Time Recovery thành công là gì?
Xem đáp án
Cần có một bản full (hoặc full+incremental) backup hợp lệ tại một thời điểm mốc, và phải giữ được TOÀN BỘ binlog liên tục không đứt quãng kể từ thời điểm mốc đó cho tới thời điểm muốn khôi phục. Nếu thiếu bất kỳ file binlog nào trong chuỗi, không thể replay tiếp các thay đổi sau điểm đó.
4.Vì sao "có backup" chưa đủ, mà cần restore-test định kỳ?
Xem đáp án
Vì backup có thể bị lỗi âm thầm (file corrupt, thiếu quyền đọc, thiếu dependency, sai version tương thích, log chưa được prepare đúng cách) mà không hiện lỗi rõ ràng cho tới khi thực sự cần restore trong sự cố thật. Restore-test định kỳ trên môi trường riêng là cách duy nhất xác nhận backup thực sự dùng được khi cần.
BÀI 05 · NỀN TẢNG

Cấu hình & Tuning (my.cnf)

Các tham số quan trọng nhất ảnh hưởng tới hiệu năng và độ ổn định.

Vị trí file cấu hình

/etc/my.cnf
/etc/my.cnf.d/*.cnf         # RHEL/Rocky
/etc/mysql/my.cnf
/etc/mysql/mariadb.conf.d/*.cnf   # Ubuntu/Debian

# Xem thứ tự file được load
mysql --help --verbose | grep -A1 "Default options"
Copy

Các tham số InnoDB quan trọng nhất

Tham sốKhuyến nghịÝ nghĩa
innodb_buffer_pool_size60-75% RAM (server chuyên dụng)Cache dữ liệu+index chính; ảnh hưởng lớn nhất tới hiệu năng đọc
innodb_buffer_pool_instances1 instance mỗi 1-2GB buffer poolChia buffer pool thành nhiều phần để giảm mutex contention
innodb_log_file_size256M-2G tùy write workloadKích thước redo log; lớn hơn giảm tần suất checkpoint nhưng tăng thời gian crash recovery
innodb_flush_log_at_trx_commit1 (an toàn nhất) hoặc 2 (cân bằng hiệu năng)1 = fsync mỗi commit (ACID đầy đủ); 2 = ghi OS cache mỗi commit, fsync mỗi giây (nhanh hơn, rủi ro mất ~1s data khi OS crash)
innodb_flush_methodO_DIRECT (Linux)Tránh double buffering giữa OS cache và InnoDB buffer pool
innodb_file_per_tableON (mặc định từ 5.6+)Mỗi bảng 1 file riêng (.ibd), dễ quản lý dung lượng/OPTIMIZE hơn
innodb_io_capacity200 (HDD) / 2000+ (SSD/NVMe)Số I/O operations/giây InnoDB được phép dùng cho background flush

Tham số Connection & Timeout

[mysqld]
max_connections = 500
max_connect_errors = 1000000
wait_timeout = 600
interactive_timeout = 600
connect_timeout = 10

# Thread pool (giảm overhead khi nhiều connection đồng thời - MariaDB)
thread_handling = pool-of-threads
thread_pool_size = 8   # thường = số CPU core
Copy

Ví dụ my.cnf cho server production tầm trung (32GB RAM, SSD)

[mysqld]
innodb_buffer_pool_size = 20G
innodb_buffer_pool_instances = 10
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

max_connections = 500
table_open_cache = 4000
tmp_table_size = 256M
max_heap_table_size = 256M

# Binlog (bắt buộc nếu dùng replication)
log_bin = mysql-bin
binlog_format = ROW
server_id = 101
expire_logs_days = 7

# Slow query log — luôn bật để phục vụ tuning
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
Copy
Đừng copy nguyên mẫu cấu hình này vào production. Đây là điểm khởi đầu tham khảo — luôn benchmark trên tải thực tế (dùng sysbench hoặc replay traffic thật) trước khi áp dụng, đặc biệt innodb_buffer_pool_sizemax_connections cần điều chỉnh theo RAM thực tế và pattern ứng dụng.

Áp dụng thay đổi runtime (không cần restart)

-- Với biến dynamic, có thể set ngay không cần restart
SET GLOBAL innodb_io_capacity = 2000;
SET GLOBAL max_connections = 500;

-- Kiểm tra biến nào là dynamic
SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; -- KHÔNG dynamic, cần restart để đổi (trừ khi dùng online resize từ 5.7+/10.2+)
Copy

📝 Câu hỏi ôn tập – Bài 05

1.innodb_flush_log_at_trx_commit=1 và =2 khác nhau thế nào về mức độ an toàn dữ liệu?
Xem đáp án
=1: mỗi transaction commit đều fsync redo log xuống đĩa ngay lập tức — đảm bảo ACID đầy đủ, không mất dữ liệu kể cả khi OS/hardware crash, nhưng tốn I/O nhiều hơn. =2: ghi redo log vào OS cache mỗi commit nhưng chỉ fsync xuống đĩa mỗi giây — hiệu năng cao hơn nhưng có rủi ro mất tối đa khoảng 1 giây dữ liệu nếu OS/server crash (MySQL crash đơn thuần vẫn an toàn vì OS cache còn nguyên).
2.Tại sao innodb_buffer_pool_size lại là tham số ảnh hưởng lớn nhất tới hiệu năng?
Xem đáp án
Vì đây là vùng cache chính chứa dữ liệu và index trong RAM. Nếu buffer pool đủ lớn để chứa phần lớn working set của ứng dụng, phần lớn truy vấn đọc sẽ hit cache thay vì phải đọc từ đĩa (vốn chậm hơn RAM nhiều bậc) — giảm đáng kể latency và tăng throughput tổng thể.
3.innodb_flush_method = O_DIRECT giải quyết vấn đề gì?
Xem đáp án
Nó tránh double buffering — mặc định, dữ liệu vừa được cache trong InnoDB buffer pool (application-level cache) vừa được cache lại trong OS page cache, gây lãng phí RAM và giảm hiệu quả sử dụng bộ nhớ. O_DIRECT bỏ qua OS page cache khi ghi/đọc datafile, để InnoDB tự quản lý cache của riêng nó.
4.Vì sao nên luôn bật slow_query_log ngay cả khi hệ thống đang chạy ổn định?
Xem đáp án
Vì slow query log là dữ liệu nền tảng để phát hiện sớm các truy vấn có xu hướng chậm dần theo thời gian (do dữ liệu tăng trưởng, thiếu index, hoặc thay đổi execution plan) — trước khi chúng trở thành sự cố production nghiêm trọng. Chi phí ghi log không đáng kể so với giá trị chẩn đoán mang lại, miễn là long_query_time được đặt hợp lý để tránh log quá nhiều.
BÀI 06 · REPLICATION & HA

Replication cơ bản — Master-Slave

Dựng replication truyền thống từng bước, từ binlog-position tới xác minh hoạt động.

Nguyên lý hoạt động

Master ghi mọi thay đổi vào binlog. Slave có 2 thread chính:

Từ MySQL 5.6+/MariaDB 10.0+, có thể tách SQL thread thành nhiều worker thread song song (slave_parallel_threads) để giảm replication lag trên hệ thống ghi nhiều.

Bước 1 — Cấu hình Master

/etc/my.cnf.d/server.cnf trên MASTER
[mysqld]
server_id = 101
log_bin = mysql-bin
binlog_format = ROW
binlog_do_db = appdb          # tùy chọn, giới hạn DB replicate
expire_logs_days = 7
sync_binlog = 1
Copy
sudo systemctl restart mariadb

-- Tạo user replication
CREATE USER 'repl'@'10.0.2.%' IDENTIFIED BY 'ReplPass!23';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.2.%';
FLUSH PRIVILEGES;
Copy

Bước 2 — Lấy snapshot dữ liệu ban đầu từ Master

-- Khóa toàn bộ để lấy vị trí binlog nhất quán (nếu dùng mysqldump)
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;   -- Ghi lại File và Position hiển thị ra

-- Ở terminal khác, dump dữ liệu (giữ lock)
mysqldump -u root -p --all-databases --routines --triggers > full_dump.sql

-- Sau khi dump xong, mở khóa
UNLOCK TABLES;
Copy
Với DB lớn, nên dùng mariabackup --backup thay vì FLUSH TABLES WITH READ LOCK để tránh khóa ghi kéo dài trên production.

Bước 3 — Cấu hình & khởi tạo Slave

/etc/my.cnf.d/server.cnf trên SLAVE
[mysqld]
server_id = 102
relay_log = relay-bin
read_only = 1
log_bin = mysql-bin      # bật nếu slave này có thể làm master của slave khác (chained replication)
Copy
sudo systemctl restart mariadb

-- Import dữ liệu đã dump từ master
mysql -u root -p < full_dump.sql

-- Trỏ slave về master, dùng File/Position ghi lại ở Bước 2
CHANGE MASTER TO
  MASTER_HOST='10.0.1.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='ReplPass!23',
  MASTER_LOG_FILE='mysql-bin.000005',
  MASTER_LOG_POS=15420;

START SLAVE;
Copy

Bước 4 — Xác minh replication hoạt động

SHOW SLAVE STATUS\G
Copy

Các trường quan trọng cần kiểm tra:

TrườngGiá trị mong đợiÝ nghĩa nếu sai
Slave_IO_RunningYesNo = không kết nối được tới master (network, sai user/pass, firewall)
Slave_SQL_RunningYesNo = lỗi khi apply 1 câu lệnh cụ thể (xem Last_SQL_Error)
Seconds_Behind_Master0 (hoặc số nhỏ)Số lớn/tăng dần = replication đang bị lag
Last_IO_Error / Last_SQL_Error(rỗng)Có nội dung = cần xử lý lỗi cụ thể

Xử lý lỗi replication dừng giữa chừng

-- Xem chi tiết lỗi
SHOW SLAVE STATUS\G   -- xem Last_SQL_Errno, Last_SQL_Error

-- Cách 1 (rủi ro): bỏ qua 1 lỗi cụ thể rồi tiếp tục — CHỈ dùng khi đã hiểu rõ hậu quả
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

-- Cách 2 (an toàn hơn): rebuild lại slave từ backup mới của master
STOP SLAVE;
RESET SLAVE ALL;
-- Restore lại full backup mới + CHANGE MASTER TO như bước 3
Copy
sql_slave_skip_counter chỉ nên dùng tạm thời. Bỏ qua lỗi đồng nghĩa dữ liệu giữa master-slave có thể lệch nhau tại điểm đó. Luôn kiểm tra kỹ nguyên nhân gốc (thường do khác biệt schema, hoặc row đã bị sửa thủ công trên slave) trước khi quyết định skip.

📝 Câu hỏi ôn tập – Bài 06

1.IO Thread và SQL Thread trên slave khác nhau về vai trò như thế nào?
Xem đáp án
IO Thread chịu trách nhiệm kết nối tới master, đọc dữ liệu từ binlog của master và ghi vào relay log cục bộ trên slave. SQL Thread đọc nội dung từ relay log đó và thực thi lại (apply) các thay đổi vào dữ liệu thực tế trên slave. Hai thread hoạt động độc lập — nếu chỉ SQL Thread dừng, dữ liệu binlog vẫn được tải về relay log chờ apply.
2.Seconds_Behind_Master tăng dần liên tục thường do những nguyên nhân nào?
Xem đáp án
Thường do: (1) tải ghi trên master quá lớn so với khả năng apply đơn luồng của SQL thread trên slave (đặc biệt khi chưa bật parallel replication), (2) slave có phần cứng/I/O yếu hơn master, (3) có truy vấn dài chạy trên slave gây tranh chấp lock với SQL thread, hoặc (4) network latency giữa master-slave cao khiến IO thread nhận dữ liệu chậm.
3.Vì sao dùng sql_slave_skip_counter để bỏ qua lỗi được coi là rủi ro?
Xem đáp án
Vì lệnh này bỏ qua transaction/event gây lỗi mà không thực sự áp dụng thay đổi đó vào slave — dẫn tới dữ liệu giữa master và slave lệch nhau tại đúng bản ghi bị bỏ qua. Nếu dùng lặp lại nhiều lần mà không hiểu rõ nguyên nhân gốc, độ lệch dữ liệu sẽ tích lũy dần, khiến slave không còn đáng tin cậy để failover.
4.Tại sao cần FLUSH TABLES WITH READ LOCK (hoặc dùng mariabackup) trước khi lấy snapshot dữ liệu ban đầu cho slave mới?
Xem đáp án
Để đảm bảo dữ liệu được dump ra và vị trí binlog (File/Position) ghi nhận tại SHOW MASTER STATUS là nhất quán với nhau tại cùng một thời điểm. Nếu không khóa, dữ liệu có thể tiếp tục thay đổi trong lúc dump, khiến vị trí binlog ghi lại không khớp chính xác với trạng thái dữ liệu đã export — khi slave bắt đầu replicate từ vị trí đó sẽ có nguy cơ trùng lặp hoặc bỏ sót thay đổi.
BÀI 07 · REPLICATION & HA

Replication nâng cao — GTID, Semi-sync, Multi-source

Loại bỏ phụ thuộc File/Position thủ công, tăng độ an toàn dữ liệu.

GTID (Global Transaction ID) là gì?

Thay vì phải nhớ chính xác File/Position như replication truyền thống, mỗi transaction được gán một định danh duy nhất toàn cục (GTID). Khi failover hoặc dựng slave mới, không cần tính toán position thủ công — server tự đồng bộ dựa trên GTID đã apply.

MariaDB GTID và MySQL GTID không tương thích định dạng. MariaDB dùng format domain-server-sequence (ví dụ 0-1-100); MySQL dùng UUID-based (ví dụ 3E11FA47-...-E14C:1-5). Không thể trộn lẫn 2 kiểu này trong cùng một topology.

Cấu hình GTID trên MariaDB

Trên Master (my.cnf)
[mysqld]
server_id = 101
log_bin = mysql-bin
binlog_format = ROW
gtid_domain_id = 1
Copy
Trên Slave — trỏ về master bằng GTID, không cần File/Position
CHANGE MASTER TO
  MASTER_HOST='10.0.1.10',
  MASTER_USER='repl',
  MASTER_PASSWORD='ReplPass!23',
  MASTER_USE_GTID=slave_pos;

START SLAVE;
SHOW SLAVE STATUS\G   -- kiểm tra Using_Gtid: Slave_Pos, Gtid_IO_Pos
Copy

Failover với GTID — promote slave thành master mới

Trên slave được chọn làm master mới:
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only = 0;

Các slave còn lại trỏ về master mới, vẫn dùng GTID — MariaDB tự tính điểm tiếp tục chính xác
CHANGE MASTER TO MASTER_HOST='10.0.1.11', MASTER_USE_GTID=slave_pos;
START SLAVE;
Copy

Semi-synchronous Replication

Replication mặc định là asynchronous: master commit xong không chờ slave xác nhận đã nhận — nếu master chết ngay sau đó, dữ liệu vừa commit có thể chưa kịp tới bất kỳ slave nào (mất dữ liệu khi failover).

Semi-sync yêu cầu master chờ ít nhất 1 slave xác nhận đã nhận (ACK) binlog trước khi trả kết quả commit về client — giảm nguy cơ mất dữ liệu, đổi lại tăng độ trễ (latency) mỗi lần commit.

Cài plugin (Master)
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; # ms, timeout rơi về async nếu không ACK kịp

Cài plugin (Slave)
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;

-- Restart slave thread để plugin có hiệu lực
STOP SLAVE IO_THREAD;
START SLAVE IO_THREAD;

-- Kiểm tra trạng thái trên master
SHOW STATUS LIKE 'Rpl_semi_sync_master_status';
SHOW STATUS LIKE 'Rpl_semi_sync_master_clients';
Copy
Nếu rpl_semi_sync_master_timeout hết hạn mà không slave nào ACK, master tự động fallback về async để tránh treo toàn bộ hệ thống — đây là hành vi thiết kế, không phải lỗi.

Multi-source Replication

Một slave nhận dữ liệu từ nhiều master khác nhau cùng lúc (thường dùng để hợp nhất dữ liệu từ nhiều nguồn vào 1 kho tổng hợp/reporting).

MariaDB dùng khái niệm "connection name" để phân biệt các nguồn
CHANGE MASTER 'source1' TO
  MASTER_HOST='10.0.1.10', MASTER_USER='repl', MASTER_PASSWORD='xxx',
  MASTER_USE_GTID=slave_pos;

CHANGE MASTER 'source2' TO
  MASTER_HOST='10.0.1.20', MASTER_USER='repl', MASTER_PASSWORD='xxx',
  MASTER_USE_GTID=slave_pos;

START SLAVE 'source1';
START SLAVE 'source2';

SHOW ALL SLAVES STATUS\G
Copy
Cẩn thận xung đột dữ liệu. Multi-source không tự động giải quyết conflict nếu 2 master ghi cùng 1 bảng/khóa chính trùng nhau — cần thiết kế namespace/schema tách biệt hoặc partition dữ liệu theo nguồn ngay từ đầu.

📝 Câu hỏi ôn tập – Bài 07

1.Vì sao không thể trộn lẫn GTID của MariaDB và MySQL trong cùng một topology?
Xem đáp án
Vì hai bên dùng định dạng GTID hoàn toàn khác nhau: MariaDB dùng cấu trúc domain-server-sequence (số), còn MySQL dùng UUID của server kết hợp dải sequence. Cơ chế theo dõi và tính toán vị trí tiếp theo giữa hai định dạng không tương thích, nên một slave MariaDB không thể hiểu GTID phát ra từ MySQL master và ngược lại.
2.Semi-sync replication đánh đổi điều gì để giảm rủi ro mất dữ liệu?
Xem đáp án
Đánh đổi độ trễ (latency) của mỗi lần commit trên master — vì master phải chờ ít nhất một slave xác nhận đã nhận (ACK) binlog event trước khi trả kết quả commit về client, thay vì trả về ngay lập tức như replication bất đồng bộ thông thường.
3.Điều gì xảy ra khi rpl_semi_sync_master_timeout hết hạn mà chưa có slave nào ACK?
Xem đáp án
Master tự động chuyển tạm thời về chế độ replication bất đồng bộ (async) thay vì tiếp tục chờ vô hạn — đây là cơ chế bảo vệ để tránh toàn bộ hệ thống ghi bị treo khi slave chậm hoặc mất kết nối. Khi có slave ACK trở lại, master có thể quay lại chế độ semi-sync.
4.Rủi ro lớn nhất khi triển khai multi-source replication là gì và cách giảm thiểu?
Xem đáp án
Rủi ro lớn nhất là xung đột dữ liệu (data conflict) khi nhiều master ghi vào cùng một bảng/khóa chính trùng lặp, vì cơ chế multi-source không tự phát hiện hay giải quyết conflict. Cách giảm thiểu: thiết kế schema/namespace tách biệt theo từng nguồn ngay từ đầu (ví dụ mỗi source ghi vào schema hoặc range khóa chính riêng), tránh để các nguồn ghi chồng lấn cùng một tập dữ liệu.
BÀI 08 · REPLICATION & HA

Galera Cluster (Multi-master)

Đồng bộ đồng thời (synchronous multi-master), khác biệt căn bản so với replication truyền thống.

Khác biệt cốt lõi so với Master-Slave

Tiêu chíMaster-Slave (async/semi-sync)Galera Cluster
Mô hình ghiChỉ 1 master ghi đượcMọi node đều ghi được (multi-master thật)
Đồng bộBất đồng bộ (hoặc semi-sync)Đồng bộ (synchronous) — certification-based
Cơ chếBinlog + relay log, replay tuần tựWrite-set replication qua wsrep API, certification trên tất cả node
Yêu cầu tối thiểu2 node3 node (để tránh split-brain, dùng quorum)
ConflictKhông có (chỉ 1 nơi ghi)Có thể bị "deadlock certification failure" nếu ghi trùng key đồng thời trên 2 node

Cài đặt cụm Galera 3 node (MariaDB)

# Cài gói trên cả 3 node
sudo dnf install -y MariaDB-server MariaDB-client galera-4

# Mở firewall các port cần thiết trên cả 3 node
sudo firewall-cmd --permanent --add-port={3306,4444,4567,4568}/tcp
sudo firewall-cmd --permanent --add-port=4567/udp
sudo firewall-cmd --reload
Copy
/etc/my.cnf.d/galera.cnf — GIỐNG NHAU trên cả 3 node, trừ wsrep_node_address/name
[galera]
wsrep_on = ON
wsrep_provider = /usr/lib64/galera-4/libgalera_smm.so
wsrep_cluster_name = "prod_cluster"
wsrep_cluster_address = "gcomm://10.0.1.11,10.0.1.12,10.0.1.13"
wsrep_node_address = "10.0.1.11"     # đổi theo từng node
wsrep_node_name = "galera-node1"     # đổi theo từng node
wsrep_sst_method = mariabackup
wsrep_sst_auth = "sst_user:SstPass!23"

binlog_format = ROW
default_storage_engine = InnoDB
innodb_autoinc_lock_mode = 2
Copy
# Bootstrap CHỈ node đầu tiên (khởi tạo cluster mới, chỉ chạy 1 lần duy nhất)
sudo galera_new_cluster
# hoặc: sudo mariadb -e "SET GLOBAL wsrep_provider_options='pc.bootstrap=true';"

# Các node 2, 3: khởi động bình thường, tự join cluster
sudo systemctl start mariadb

# Kiểm tra trạng thái cluster trên bất kỳ node nào
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_status';"  # cần = Primary
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_local_state_comment';" # cần = Synced
Copy
Chỉ bootstrap 1 lần duy nhất, đúng 1 node. Nếu chạy galera_new_cluster trên nhiều node cùng lúc, mỗi node sẽ nghĩ mình là cluster riêng biệt (split-brain ngay từ đầu). Sau lần khởi tạo đầu tiên, mọi lần restart tiếp theo dùng systemctl start mariadb bình thường — cluster đã tồn tại sẽ tự bầu chọn quorum.

SST vs IST — cách node mới đồng bộ dữ liệu khi join

Xử lý sự cố phổ biến: Cluster không đạt Primary (split-brain)

-- Khi mất quá nhiều node, cluster có thể rơi vào trạng thái "non-Primary"
SHOW STATUS LIKE 'wsrep_cluster_status';  -- trả về "non-Primary"

-- Nếu chắc chắn đây là node còn "sống" duy nhất và cần khôi phục thủ công (RỦI RO):
SET GLOBAL wsrep_provider_options='pc.bootstrap=true';
Copy
pc.bootstrap=true là thao tác nguy hiểm nếu dùng sai. Chỉ thực hiện khi chắc chắn 100% các node khác thực sự đã chết (không phải do network partition tạm thời) — nếu không, có thể tạo ra 2 "primary component" song song, dẫn tới phân kỳ dữ liệu (split-brain thật sự) không thể tự động hợp nhất lại.

📝 Câu hỏi ôn tập – Bài 08

1.Vì sao Galera Cluster yêu cầu tối thiểu 3 node thay vì 2?
Xem đáp án
Vì Galera dùng cơ chế quorum để xác định "Primary Component" hợp lệ khi xảy ra network partition. Với số node lẻ (tối thiểu 3), cluster luôn có thể xác định được nhóm nào chiếm đa số (>50%) để tiếp tục hoạt động an toàn, tránh split-brain. Với chỉ 2 node, khi mất kết nối giữa 2 node đó, không bên nào có đa số rõ ràng để tự quyết định tiếp tục làm Primary.
2.SST và IST khác nhau như thế nào, và khi nào Galera chọn dùng cái nào?
Xem đáp án
SST đồng bộ toàn bộ dữ liệu từ một node donor sang node mới join (tốn thời gian, tải I/O cao). IST chỉ chuyển phần write-set còn thiếu dựa trên gcache của donor, nhanh hơn nhiều. Galera tự động chọn IST nếu donor còn lưu đủ lịch sử write-set cần thiết trong gcache tương ứng với khoảng thời gian node kia bị lệch (ví dụ restart ngắn); nếu không đủ hoặc node hoàn toàn mới, Galera bắt buộc phải dùng SST.
3.Tại sao chỉ được chạy galera_new_cluster (bootstrap) đúng một lần trên một node duy nhất?
Xem đáp án
Vì lệnh này khởi tạo một Primary Component hoàn toàn mới. Nếu chạy đồng thời trên nhiều node, mỗi node sẽ tự coi mình là điểm khởi đầu của một cluster riêng biệt (mỗi node là "Primary" độc lập của chính nó), dẫn đến split-brain ngay từ lúc khởi tạo — dữ liệu ghi vào các node sẽ phân kỳ ngay từ đầu mà không có cơ chế certification chung nào kiểm soát.
4.Vì sao ứng dụng ghi đồng thời vào 2 node khác nhau trên cùng 1 dòng dữ liệu (row) có thể gặp lỗi certification, dù Galera hỗ trợ multi-master?
Xem đáp án
Vì Galera dùng cơ chế certification-based replication: mỗi transaction sau khi commit cục bộ sẽ được "chứng thực" (certify) trên toàn cluster để đảm bảo không xung đột. Nếu 2 transaction từ 2 node khác nhau cùng sửa một row gần như đồng thời, một trong hai sẽ bị certification từ chối (rollback) ở giai đoạn chứng thực để đảm bảo tính nhất quán — đây là chi phí đánh đổi để có multi-master thực sự, không giống với việc chỉ có 1 điểm ghi duy nhất như Master-Slave truyền thống.
BÀI 09 · REPLICATION & HA

MaxScale & ProxySQL

Load balancer / proxy tầng database: readwritesplit, monitor, failover tự động.

Vai trò của MaxScale trong kiến trúc HA

MaxScale đứng giữa application và cụm database (Master-Slave hoặc Galera), đảm nhận:

Cài đặt MaxScale

curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-maxscale
sudo dnf install -y maxscale

# Tạo user chuyên dụng cho MaxScale trên các node DB (quyền monitor + đọc metadata)
-- Chạy trên MASTER:
CREATE USER 'maxscale_monitor'@'10.0.5.%' IDENTIFIED BY 'MaxPass!23';
GRANT REPLICATION CLIENT, REPLICATION SLAVE, SUPER, RELOAD, PROCESS, SHOW DATABASES ON *.* TO 'maxscale_monitor'@'10.0.5.%';

CREATE USER 'maxscale_router'@'10.0.5.%' IDENTIFIED BY 'RoutPass!23';
GRANT SELECT ON mysql.* TO 'maxscale_router'@'10.0.5.%';
Copy

Cấu hình cơ bản /etc/maxscale.cnf

[maxscale]
threads = auto

[server1]
type = server
address = 10.0.1.10
port = 3306
protocol = MariaDBBackend

[server2]
type = server
address = 10.0.1.11
port = 3306
protocol = MariaDBBackend

[server3]
type = server
address = 10.0.1.12
port = 3306
protocol = MariaDBBackend

# Monitor — theo dõi topology Master-Slave và tự động failover
[MariaDB-Monitor]
type = monitor
module = mariadbmon
servers = server1,server2,server3
user = maxscale_monitor
password = MaxPass!23
monitor_interval = 2000ms
auto_failover = true
auto_rejoin = true
failcount = 3

# Service — định nghĩa routing logic
[RW-Split-Service]
type = service
router = readwritesplit
servers = server1,server2,server3
user = maxscale_router
password = RoutPass!23
max_slave_connections = 100%

# Listener — cổng application kết nối vào
[RW-Split-Listener]
type = listener
service = RW-Split-Service
protocol = MariaDBClient
port = 4006
Copy
sudo systemctl enable --now maxscale

# CLI quản trị MaxScale
sudo maxctrl list servers
sudo maxctrl list services
sudo maxctrl list monitors
sudo maxctrl show server server1
Copy
Application chỉ cần biết 1 điểm kết nối duy nhất: host = IP/VIP của MaxScale, port = 4006. Toàn bộ logic chọn master/slave, failover, health-check được MaxScale xử lý minh bạch phía sau — đây chính là mô hình anh đã triển khai thực tế cho hạ tầng SSO (PostgreSQL/Patroni tương tự về mặt ý tưởng, còn MaxScale là lớp tương đương cho MySQL/MariaDB).

So sánh nhanh MaxScale vs ProxySQL

Tiêu chíMaxScaleProxySQL
Nhà phát triểnMariaDB Corporation (chính chủ)Cộng đồng/bên thứ 3, độc lập với Oracle/MariaDB
Tích hợp Galera/MariaDBRất tốt, module riêng (galeramon)Hỗ trợ nhưng cần cấu hình rule thủ công nhiều hơn
LicenseBSL (giới hạn số node free) hoặc GPL bản cũGPLv3 hoàn toàn miễn phí
Query caching/rewriteCó (cache filter, rewrite filter)Rất mạnh (query rules linh hoạt, cache tầng query)
Phù hợp nhấtToàn bộ stack MariaDB (server+monitor+proxy đồng bộ)Môi trường mixed MySQL/MariaDB, cần custom routing rule phức tạp

Xử lý sự cố thường gặp với MaxScale

# Server bị đánh dấu Down dù DB vẫn chạy — thường do sai user/pass hoặc firewall
sudo maxctrl show server server1
# Xem log chi tiết lý do health-check fail
sudo tail -f /var/log/maxscale/maxscale.log

# Failover không tự động xảy ra dù master chết
# Kiểm tra: auto_failover=true đã bật? failcount đã đạt ngưỡng chưa?
sudo maxctrl list monitors --details

# Buộc failover thủ công (khẩn cấp)
sudo maxctrl call command mariadbmon failover MariaDB-Monitor
Copy

📝 Câu hỏi ôn tập – Bài 09

1.readwritesplit router trong MaxScale hoạt động dựa trên nguyên tắc gì để quyết định route query?
Xem đáp án
Router phân tích loại câu lệnh SQL: các câu lệnh ghi (INSERT/UPDATE/DELETE/DDL) và các câu trong transaction luôn được route tới master (server writable duy nhất trong topology). Các câu SELECT thuần túy (không nằm trong transaction ghi) được phân tán (load balance) sang các slave để giảm tải cho master, tận dụng khả năng đọc song song của nhiều node.
2.Vì sao cần user riêng (maxscale_monitor) với quyền cao hơn (SUPER, REPLICATION CLIENT) so với user route query thông thường?
Xem đáp án
Vì monitor cần các quyền đặc biệt để kiểm tra trạng thái replication chi tiết (SHOW SLAVE STATUS cần REPLICATION CLIENT), và để thực hiện các thao tác quản trị khi failover (như đổi CHANGE MASTER, SET GLOBAL read_only — cần quyền SUPER). User dùng để route query của application chỉ cần quyền tối thiểu để kết nối và thực thi truy vấn, tách biệt theo nguyên tắc least-privilege.
3.Nếu application kết nối trực tiếp vào IP của một node DB cụ thể thay vì qua MaxScale, điều gì sẽ mất đi?
Xem đáp án
Sẽ mất khả năng tự động failover và load balancing minh bạch — nếu node đó chết hoặc bị demote (không còn là master), ứng dụng sẽ gặp lỗi kết nối trực tiếp và phải tự xử lý logic reconnect/tìm master mới, thay vì để MaxScale xử lý trong suốt phía sau một điểm kết nối cố định duy nhất.
4.Sự khác biệt chính giữa MaxScale và ProxySQL về mặt license và hệ sinh thái là gì?
Xem đáp án
MaxScale do chính MariaDB Corporation phát triển, tích hợp rất sâu và mượt với hệ sinh thái MariaDB/Galera (dùng license BSL với giới hạn số node cho bản miễn phí). ProxySQL là dự án cộng đồng độc lập, license GPLv3 hoàn toàn miễn phí không giới hạn, linh hoạt hơn về query rule/cache nhưng cần cấu hình thủ công nhiều hơn để tích hợp tốt với MariaDB/Galera so với MaxScale.
BÀI 10 · REPLICATION & HA

Keepalived & VIP Failover

Đảm bảo application luôn kết nối được qua 1 địa chỉ IP ảo (VIP), bất kể node MaxScale nào đang sống.

Vì sao cần Keepalived phía trước MaxScale?

MaxScale bản thân cũng là 1 điểm chịu lỗi đơn (single point of failure) nếu chỉ chạy 1 instance. Giải pháp phổ biến: chạy 2+ instance MaxScale (active-passive), đặt Keepalived phía trước để quản lý 1 VIP (Virtual IP) chung — application chỉ cần biết đúng 1 địa chỉ VIP duy nhất.

Đây đúng là mô hình anh đã triển khai với hạ tầng SSO PostgreSQL/Patroni/HAProxy/Keepalived — với MySQL/MariaDB, HAProxy có thể thay bằng MaxScale (đã có readwritesplit + monitor tích hợp sẵn), Keepalived vẫn giữ vai trò quản lý VIP y hệt.

Cài đặt Keepalived (trên cả 2 node MaxScale)

sudo dnf install -y keepalived
sudo systemctl enable keepalived
Copy

Cấu hình /etc/keepalived/keepalived.conf — Node MASTER (priority cao)

vrrp_script chk_maxscale {
    script "/usr/bin/systemctl is-active maxscale"
    interval 2
    weight -20      # giảm priority 20 điểm nếu MaxScale service down
    fall 2
    rise 2
}

vrrp_instance VI_MAXSCALE {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass Secret123
    }

    virtual_ipaddress {
        10.0.5.100/24
    }

    track_script {
        chk_maxscale
    }
}
Copy

Cấu hình Node BACKUP (priority thấp hơn)

vrrp_script chk_maxscale {
    script "/usr/bin/systemctl is-active maxscale"
    interval 2
    weight -20
    fall 2
    rise 2
}

vrrp_instance VI_MAXSCALE {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100          # THẤP hơn node MASTER
    advert_int 1

    authentication {
        auth_type PASS
        auth_pass Secret123
    }

    virtual_ipaddress {
        10.0.5.100/24
    }

    track_script {
        chk_maxscale
    }
}
Copy
virtual_router_id phải giống nhau, priority phải khác nhau giữa 2 node trong cùng 1 VRRP instance. Nếu 2 node cùng priority hoặc đặt sai state, dễ xảy ra tình huống cả 2 cùng nhận VIP (split-brain ở tầng network) hoặc không node nào giữ VIP.

Áp dụng & kiểm tra

sudo systemctl restart keepalived

# Trên node đang giữ VIP, kiểm tra bằng ip addr
ip addr show eth0 | grep 10.0.5.100

# Xem log VRRP
sudo journalctl -u keepalived -f

# Test failover: dừng maxscale trên node MASTER, quan sát VIP chuyển sang BACKUP
sudo systemctl stop maxscale
# Trong vài giây, VIP phải xuất hiện trên node BACKUP
Copy

Lỗi thường gặp khi triển khai Keepalived

Triệu chứngNguyên nhân thường gặpCách xử lý
Cả 2 node cùng có VIP (split-brain)Firewall chặn multicast/VRRP protocol (IP protocol 112) giữa 2 nodeMở firewall cho VRRP: firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept'
VIP không tồn tại ở node nàoSai virtual_router_id trùng với instance Keepalived khác trong cùng subnetĐổi virtual_router_id sang giá trị không trùng (1-255)
Failover không xảy ra khi MaxScale chếtThiếu hoặc sai track_script, hoặc script kiểm tra trả về sai exit codeTest thủ công script kiểm tra, đảm bảo trả về 0 khi service sống, khác 0 khi chết
ufw báo lỗi cú pháp VRRP (Ubuntu)ufw không hỗ trợ trực tiếp protocol 112 qua cú pháp thông thườngDùng iptables/nftables trực tiếp hoặc rule raw trong ufw cho protocol vrrp

📝 Câu hỏi ôn tập – Bài 10

1.Vai trò của vrrp_script (track_script) trong Keepalived là gì?
Xem đáp án
Cho phép Keepalived kiểm tra thêm điều kiện ngoài trạng thái VRRP thuần túy — cụ thể là kiểm tra xem service thực sự cần bảo vệ (ở đây là MaxScale) có đang chạy hay không. Nếu script phát hiện service down, nó giảm priority của node đó (thông qua "weight"), khiến VRRP tự động chuyển VIP sang node khác có priority cao hơn — đảm bảo VIP luôn trỏ tới node có service thực sự hoạt động, không chỉ đơn thuần node có network còn sống.
2.Nguyên nhân phổ biến nhất gây ra tình trạng split-brain (2 node cùng giữ VIP) trong Keepalived là gì?
Xem đáp án
Phổ biến nhất là firewall chặn gói tin VRRP (IP protocol 112, hoặc multicast) giữa 2 node — khiến mỗi node không nhận được advertisement từ node kia, tưởng rằng mình là node duy nhất còn sống nên cả 2 đều tự nhận VIP về mình.
3.virtual_router_id dùng để làm gì, và điều gì xảy ra nếu 2 cụm Keepalived khác nhau trong cùng subnet vô tình dùng chung giá trị này?
Xem đáp án
virtual_router_id là định danh của một VRRP instance, dùng để các node phân biệt "mình thuộc nhóm VRRP nào" khi nhận advertisement multicast trong cùng subnet. Nếu 2 cụm Keepalived khác nhau (ví dụ 1 cụm cho MaxScale, 1 cụm cho dịch vụ khác) vô tình dùng chung virtual_router_id, các node của 2 cụm sẽ hiểu nhầm advertisement của nhau, dẫn tới tranh chấp VIP hoặc hành vi failover không đúng ý định.
4.Tại sao ứng dụng chỉ nên kết nối vào địa chỉ VIP thay vì IP thật của từng node MaxScale?
Xem đáp án
Vì VIP là địa chỉ logic không gắn cố định với một node vật lý nào — Keepalived tự động di chuyển VIP sang node còn sống khi node hiện tại (hoặc service trên đó) gặp sự cố. Nếu ứng dụng cấu hình cứng IP thật của một node MaxScale cụ thể, khi node đó chết, ứng dụng sẽ mất kết nối hoàn toàn dù đã có node MaxScale dự phòng khác đang chạy tốt — làm mất hoàn toàn ý nghĩa của thiết kế HA.
BÀI 11 · VẬN HÀNH NÂNG CAO

Giám sát & Troubleshooting

performance_schema, slow log, các lỗi thường gặp trong production và cách chẩn đoán.

Các nguồn dữ liệu giám sát chính

NguồnDùng để
SHOW STATUS / SHOW GLOBAL STATUSSố liệu tổng quan runtime: connections, queries, buffer pool hit rate...
performance_schemaChi tiết theo từng statement, wait event, lock, I/O — chuẩn để deep-dive hiệu năng
information_schemaMetadata: bảng, index, process list, innodb lock waits
Slow query logDanh sách query chạy chậm hơn ngưỡng, kèm thời gian, số row scan
Error logSự kiện hệ thống: crash, restart, lỗi replication, warning storage engine

Phân tích slow query log với mysqldumpslow / pt-query-digest

# Công cụ built-in đơn giản
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# Công cụ mạnh hơn (Percona Toolkit) — phân tích chi tiết, gom nhóm theo pattern
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
Copy

Phát hiện query thiếu index / full table scan

EXPLAIN SELECT * FROM orders WHERE customer_id = 12345;
-- Chú ý cột "type": ALL = full table scan (xấu), ref/range/const = có dùng index (tốt)
-- Chú ý cột "rows": ước lượng số dòng phải quét — càng lớn càng đáng lo

-- Từ MySQL 8.0.18+ / MariaDB 10.5+: EXPLAIN ANALYZE cho số liệu thực tế thay vì ước lượng
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 12345;
Copy

Chẩn đoán Lock & Deadlock

-- Xem transaction đang mở và lock đang chờ (InnoDB)
SELECT * FROM information_schema.INNODB_TRX\G
SELECT * FROM information_schema.INNODB_LOCK_WAITS\G

-- Xem chi tiết deadlock gần nhất (rất hữu ích khi debug)
SHOW ENGINE INNODB STATUS\G
-- Tìm section "LATEST DETECTED DEADLOCK"
Copy

Bảng lỗi thường gặp trong production

Lỗi / triệu chứngNguyên nhân thường gặpHướng xử lý
Error 1040: Too many connectionsmax_connections vượt ngưỡng, hoặc connection leak từ applicationTăng max_connections tạm thời; kiểm tra application có đóng connection đúng cách không; xem xét connection pooling (MaxScale/ProxySQL)
Error 1205: Lock wait timeout exceededTransaction khác giữ lock quá lâu (thường do quên COMMIT/ROLLBACK)Tìm transaction đang mở lâu qua INNODB_TRX, xử lý ứng dụng đóng transaction kịp thời
Error 1213: Deadlock found2 transaction khóa chéo nhau theo thứ tự khác nhauĐảm bảo ứng dụng luôn truy cập bảng/row theo cùng 1 thứ tự cố định; retry logic ở tầng application
Disk full do binlog tăng liên tụcThiếu expire_logs_days hoặc slave không kịp đọc binlog để giải phóngĐặt expire_logs_days/binlog_expire_logs_seconds hợp lý; kiểm tra slave có đang lag không trước khi purge thủ công
"Table is marked as crashed"Thường gặp với MyISAM sau khi shutdown không sạch (unclean shutdown)CHECK TABLE rồi REPAIR TABLE; về lâu dài nên chuyển sang InnoDB
Replication lag tăng dầnSQL thread đơn luồng không theo kịp write workload trên masterBật slave_parallel_threads + slave_parallel_mode=optimistic; kiểm tra I/O slave có chậm hơn master không

Checklist khi nhận cảnh báo "DB chậm" (triage nhanh)

  1. SHOW FULL PROCESSLIST — có query nào chạy quá lâu hoặc bị Lock không?
  2. SHOW ENGINE INNODB STATUS\G — kiểm tra section TRANSACTIONS, xem có deadlock gần đây không.
  3. Kiểm tra tài nguyên hệ thống: iostat -x 2, vmstat 2, top — CPU/I/O/RAM có đang bão hòa không.
  4. Kiểm tra SHOW STATUS LIKE 'Innodb_buffer_pool_reads' tăng nhanh bất thường → buffer pool miss nhiều, có thể do query mới thiếu index hoặc buffer pool quá nhỏ.
  5. Nếu là slave: SHOW SLAVE STATUS\G — kiểm tra Seconds_Behind_Master.
  6. Đối chiếu với thời điểm deploy gần nhất — nhiều sự cố "DB chậm" thực chất bắt nguồn từ query mới hoặc migration schema thiếu index.

📝 Câu hỏi ôn tập – Bài 11

1.EXPLAIN và EXPLAIN ANALYZE khác nhau ở điểm cốt lõi nào?
Xem đáp án
EXPLAIN chỉ hiển thị execution plan dự kiến của optimizer dựa trên số liệu thống kê (ước lượng số row, chi phí) mà KHÔNG thực sự chạy câu query. EXPLAIN ANALYZE thực sự thực thi câu query và trả về số liệu thực tế (số row thực sự quét, thời gian thực thi từng bước) — hữu ích hơn để phát hiện trường hợp optimizer ước lượng sai lệch nhiều so với thực tế.
2.Lỗi 1205 (Lock wait timeout) và 1213 (Deadlock) khác nhau như thế nào về bản chất?
Xem đáp án
Lỗi 1205 xảy ra khi một transaction chờ lock quá lâu (vượt quá innodb_lock_wait_timeout) do transaction khác đang giữ lock đó — không nhất thiết có chu trình khóa chéo, chỉ đơn giản là chờ quá lâu. Lỗi 1213 (deadlock) xảy ra khi 2 (hoặc nhiều) transaction khóa chéo lẫn nhau theo một chu trình khép kín (A chờ lock của B, B chờ lock của A) — InnoDB tự phát hiện và chủ động rollback một trong các transaction để phá vỡ chu trình, gần như ngay lập tức thay vì phải đợi tới timeout.
3.Vì sao cần kiểm tra tình trạng lag của slave trước khi thực hiện PURGE BINARY LOGS thủ công trên master?
Xem đáp án
Vì nếu slave đang bị lag và chưa kịp đọc hết các binlog event, việc xóa (purge) binlog trên master có thể xóa mất những file mà slave vẫn cần để tiếp tục đồng bộ — khiến slave rơi vào trạng thái lỗi "binlog file not found" và buộc phải rebuild lại từ đầu bằng full backup mới.
4.Trong quy trình triage "DB chậm", tại sao nên đối chiếu với thời điểm deploy gần nhất?
Xem đáp án
Vì phần lớn sự cố hiệu năng đột ngột trong production thực chất không phải do hạ tầng tự nhiên xuống cấp, mà bắt nguồn từ thay đổi ở tầng ứng dụng — ví dụ một query mới chưa được tối ưu, một migration schema làm mất index cũ, hoặc một thay đổi logic khiến khối lượng truy vấn tăng đột biến. Đối chiếu thời gian giúp khoanh vùng nguyên nhân nhanh hơn nhiều so với chỉ nhìn vào số liệu hệ thống đơn thuần.
BÀI 12 · VẬN HÀNH NÂNG CAO

Bảo mật & Hardening

SSL/TLS, firewall, least-privilege, và checklist hardening trước khi go-live.

Bật SSL/TLS cho kết nối client-server

# Tạo certificate (tự ký, hoặc dùng CA nội bộ cho production thực)
sudo mysql_ssl_rsa_setup --datadir=/var/lib/mysql

# my.cnf
[mysqld]
ssl_cert = /var/lib/mysql/server-cert.pem
ssl_key  = /var/lib/mysql/server-key.pem
ssl_ca   = /var/lib/mysql/ca.pem
require_secure_transport = ON   # bắt buộc mọi kết nối phải dùng SSL
Copy
-- Bắt buộc SSL riêng cho 1 user cụ thể (thay vì toàn server)
ALTER USER 'app_user'@'10.0.1.%' REQUIRE SSL;

-- Kiểm tra kết nối hiện tại có dùng SSL không
SHOW STATUS LIKE 'Ssl_cipher';
Copy

Firewall — chỉ mở đúng port cần thiết

# Chỉ cho phép subnet application server kết nối tới 3306, chặn toàn bộ nguồn khác
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" port protocol="tcp" port="3306" accept'
sudo firewall-cmd --permanent --zone=public --remove-port=3306/tcp  # đảm bảo không có rule mở toàn bộ
sudo firewall-cmd --reload
Copy

Nguyên tắc phân quyền (least-privilege) đã học ở Bài 03, áp dụng lại có hệ thống

Audit log (theo dõi ai làm gì)

# MariaDB Audit Plugin (thường có sẵn từ 10.0+)
INSTALL PLUGIN server_audit SONAME 'server_audit';

SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY_DDL,QUERY_DML,QUERY_DCL';
SET GLOBAL server_audit_file_path = '/var/log/mysql/audit.log';
Copy

Checklist hardening trước khi go-live production

Hạng mụcĐã thực hiện?
Đã chạy mysql_secure_installation, xóa user anonymous/test db
Không còn user nào dùng host '%' ngoại trừ trường hợp bắt buộc có lý do rõ ràng
root chỉ cho phép đăng nhập từ localhost hoặc IP quản trị cụ thể
Firewall chỉ mở port 3306 (và các port HA liên quan) cho đúng subnet cần thiết
SSL/TLS bật cho kết nối client-server, đặc biệt qua mạng công cộng/liên site
Backup tự động đã cấu hình + đã restore-test thành công ít nhất 1 lần
Replication user chỉ có đúng quyền REPLICATION SLAVE, không dư thừa
Slow query log + error log được giám sát/gửi cảnh báo (alerting)
Audit log bật cho các thao tác DDL/DCL nhạy cảm
Đã test kịch bản failover (master chết, node Galera chết, MaxScale chết, VIP chuyển) ít nhất 1 lần trên staging

📝 Câu hỏi ôn tập – Bài 12

1.require_secure_transport=ON ở cấp server và REQUIRE SSL ở cấp user khác nhau ở phạm vi áp dụng như thế nào?
Xem đáp án
require_secure_transport=ON áp dụng cho TOÀN BỘ kết nối vào server, bắt buộc mọi client phải dùng SSL/TLS mới kết nối được, không có ngoại lệ. REQUIRE SSL đặt riêng theo từng user (qua ALTER USER) chỉ bắt buộc SSL cho đúng user đó — cho phép linh hoạt hơn khi một số user (ví dụ kết nối nội bộ qua localhost/unix socket) không cần SSL trong khi user khác kết nối qua mạng bắt buộc phải mã hóa.
2.Vì sao nên tách riêng user cho backup, cho monitor, và cho application thay vì dùng chung 1 tài khoản?
Xem đáp án
Theo nguyên tắc least-privilege: mỗi loại tác vụ chỉ cần một tập quyền cụ thể (backup cần SELECT+LOCK/RELOAD, monitor cần PROCESS+REPLICATION CLIENT, application chỉ cần CRUD trên schema của mình). Tách riêng giúp giới hạn thiệt hại nếu một credential bị lộ (chỉ ảnh hưởng đúng phạm vi quyền của tài khoản đó), đồng thời dễ audit và thu hồi quyền theo từng mục đích sử dụng mà không ảnh hưởng tới các tác vụ khác.
3.Audit log (server_audit plugin) mang lại giá trị gì mà slow query log hay error log không cung cấp được?
Xem đáp án
Slow query log chỉ ghi các query chậm hơn ngưỡng, error log chỉ ghi sự kiện lỗi/hệ thống — cả hai không tập trung vào việc "ai đã làm gì". Audit log ghi lại đầy đủ các sự kiện kết nối, DDL, DML, DCL kèm thông tin user/host thực hiện, phục vụ mục đích truy vết bảo mật (ai đã sửa cấu trúc bảng, ai đã cấp quyền cho ai, ai đã đăng nhập từ đâu) — cần thiết cho compliance và điều tra sự cố bảo mật.
4.Vì sao "đã cấu hình backup tự động" chưa đủ điều kiện để coi là hoàn thành hạng mục hardening trong checklist go-live?
Xem đáp án
Vì một backup chưa từng được restore-test thành công là một backup chưa được kiểm chứng — có thể bị lỗi âm thầm (thiếu quyền, sai cấu hình, file corrupt) mà không ai biết cho tới khi cần dùng thật trong sự cố. Checklist yêu cầu rõ "đã cấu hình VÀ đã restore-test thành công ít nhất 1 lần" mới coi là đạt, vì mục tiêu cuối cùng là đảm bảo khả năng khôi phục thực tế, không chỉ sự tồn tại của file backup.
BÀI 13 · ÔN TẬP

Ôn tập tổng hợp toàn bộ khóa học

Câu hỏi tổng hợp xuyên suốt 12 bài — dùng để tự đánh giá trước khi vào thực chiến.

Cách dùng: Đọc câu hỏi, tự trả lời trong đầu (hoặc viết ra) trước khi bấm "Xem đáp án". Nếu sai hoặc thiếu ý, quay lại bài học tương ứng để ôn lại.

📝 Bộ câu hỏi tổng hợp (20 câu)

1.Trình bày sự khác biệt giữa asynchronous, semi-synchronous, và Galera (synchronous) replication về mặt đảm bảo dữ liệu khi failover.
Xem đáp án
Async: master không chờ slave xác nhận, nguy cơ mất dữ liệu cao nhất nếu master chết ngay sau commit. Semi-sync: master chờ ít nhất 1 slave ACK trước khi trả kết quả, giảm đáng kể nguy cơ mất dữ liệu nhưng vẫn có khoảng trễ rất nhỏ và có cơ chế timeout fallback về async. Galera: mọi thay đổi phải được certify (chứng thực) trên toàn bộ node trước khi coi là commit thành công ở bất kỳ đâu — đảm bảo dữ liệu nhất quán mạnh nhất giữa các node, đổi lại latency cao hơn và có thể gặp certification conflict khi ghi đồng thời cùng 1 row từ nhiều node.
2.Một hệ thống dùng Master-Slave GTID, slave báo Seconds_Behind_Master tăng liên tục trong giờ cao điểm. Liệt kê ít nhất 3 hướng kiểm tra.
Xem đáp án
(1) Kiểm tra xem slave_parallel_threads đã bật và đủ lớn chưa — nếu SQL thread chạy đơn luồng sẽ không theo kịp write workload đa luồng trên master. (2) So sánh tài nguyên I/O/CPU giữa master và slave (iostat, vmstat) — slave có thể yếu hơn hoặc đang chạy thêm tải khác (ví dụ backup, báo cáo). (3) Kiểm tra có transaction/query dài đang chạy trên slave gây tranh chấp lock với SQL thread apply hay không, qua SHOW FULL PROCESSLIST và INNODB_TRX trên slave.
3.Vì sao Galera Cluster yêu cầu tối thiểu 3 node, còn Master-Slave chỉ cần 2? Liên hệ khái niệm quorum.
Xem đáp án
Galera là hệ multi-master đồng bộ, cần quorum (đa số node) để xác định Primary Component hợp lệ khi có network partition, tránh split-brain — với số lẻ tối thiểu 3, luôn xác định được nhóm chiếm đa số. Master-Slave không cần quorum vì kiến trúc vốn chỉ có 1 điểm ghi (master) tại một thời điểm; việc chọn master không dựa trên bỏ phiếu giữa các node mà thường do cấu hình/công cụ điều phối bên ngoài (như MaxScale) quyết định.
4.Trình bày luồng dữ liệu đầy đủ khi ứng dụng gửi 1 câu UPDATE trong kiến trúc: Application → MaxScale (VIP qua Keepalived) → Master-Slave cluster.
Xem đáp án
Application kết nối tới địa chỉ VIP (do Keepalived quản lý, trỏ về node MaxScale đang active). MaxScale nhận kết nối qua listener, router readwritesplit phân tích câu lệnh là UPDATE (ghi) nên route thẳng tới server được đánh dấu là Master trong monitor (mariadbmon). Master ghi dữ liệu, ghi vào binlog. Slave's IO thread đọc binlog này qua kết nối replication, ghi vào relay log; SQL thread apply lại thay đổi vào dữ liệu slave. Đồng thời, MaxScale monitor liên tục health-check để cập nhật trạng thái từng node, sẵn sàng failover nếu master gặp sự cố.
5.Phân biệt SST và IST trong Galera — điều gì quyết định Galera chọn phương án nào khi 1 node rời rồi quay lại cluster?
Xem đáp án
Quyết định dựa trên việc donor (node cung cấp dữ liệu) có còn giữ đủ lịch sử write-set trong gcache tương ứng với khoảng thời gian node kia đã rời cluster hay không. Nếu gcache còn đủ dữ liệu chênh lệch (node rời trong thời gian ngắn) → dùng IST (nhanh, chỉ chuyển phần thiếu). Nếu gcache đã bị ghi đè mất phần cần thiết (node rời quá lâu, hoặc node hoàn toàn mới chưa từng có dữ liệu) → bắt buộc dùng SST (chuyển toàn bộ dữ liệu, chậm hơn nhiều).
6.Tại sao innodb_flush_log_at_trx_commit=1 lại quan trọng hơn khi hệ thống có tích hợp thanh toán/giao dịch tài chính so với hệ thống log/analytics thông thường?
Xem đáp án
Với hệ thống tài chính, mỗi transaction commit thành công đồng nghĩa với một cam kết không thể đảo ngược về mặt nghiệp vụ (tiền đã chuyển, đơn hàng đã xác nhận) — mất dữ liệu dù chỉ 1 giây do OS/hardware crash có thể gây sai lệch số dư hoặc tranh chấp pháp lý nghiêm trọng. Với hệ thống log/analytics, mất một khoảng dữ liệu nhỏ trong trường hợp crash hiếm gặp thường chấp nhận được, nên có thể đánh đổi lấy hiệu năng cao hơn bằng giá trị =2 hoặc =0.
7.Một slave báo lỗi Last_SQL_Error do duplicate key. Nêu ít nhất 2 cách xử lý và phân tích rủi ro từng cách.
Xem đáp án
Cách 1: dùng SET GLOBAL sql_slave_skip_counter=1 rồi START SLAVE — nhanh nhưng rủi ro là bỏ qua đúng transaction đó khiến dữ liệu slave lệch khỏi master tại điểm này (thường do có ai đó ghi trực tiếp vào slave trước đó, phá vỡ tính read-only). Cách 2: dừng slave, RESET SLAVE ALL, rebuild lại toàn bộ từ backup mới nhất của master và CHANGE MASTER TO lại — an toàn hơn về tính nhất quán dữ liệu nhưng tốn thời gian và có thể cần downtime đọc trên slave đó trong lúc rebuild.
8.Vì sao MaxScale cần một user riêng cho monitor với quyền SUPER, khác với user router chỉ cần quyền đọc?
Xem đáp án
User monitor không chỉ đọc trạng thái mà còn có thể cần thực hiện các thao tác quản trị khi auto_failover được kích hoạt (như thay đổi read_only, thực hiện CHANGE MASTER trên node được promote) — các thao tác này đòi hỏi quyền SUPER. User router chỉ làm nhiệm vụ forward query của application, không cần và không nên có quyền quản trị cao, tuân theo nguyên tắc least-privilege để giảm rủi ro nếu credential router bị lộ.
9.Giải thích vì sao "có backup hàng ngày" không đồng nghĩa với "có khả năng khôi phục production trong X giờ" (RTO). Cần thêm những gì?
Xem đáp án
Có backup chỉ đảm bảo dữ liệu tồn tại ở đâu đó, nhưng RTO (Recovery Time Objective) phụ thuộc vào: tốc độ restore thực tế (physical backup như mariabackup thường nhanh hơn nhiều so với replay mysqldump trên DB lớn), băng thông di chuyển file backup nếu lưu off-site, thời gian prepare/apply log trước khi dùng được, và quan trọng nhất là quy trình đã được diễn tập (runbook rõ ràng, từng bước đã test) hay chưa. Restore-test định kỳ đo thời gian thực tế mới là cách duy nhất biết RTO thực sự đạt được bao nhiêu.
10.Trong topology Keepalived + MaxScale, nếu network giữa 2 node Keepalived bị cắt đứt hoàn toàn (nhưng cả 2 node vẫn sống, vẫn kết nối được DB), điều gì sẽ xảy ra và tại sao đây là kịch bản nguy hiểm?
Xem đáp án
Cả 2 node sẽ không nhận được VRRP advertisement của nhau (do mất kết nối mạng giữa chúng), mỗi node sẽ tự cho rằng mình là node duy nhất còn sống và tự nhận VIP về mình — dẫn tới tình huống cả 2 node cùng claim VIP (split-brain ở tầng network). Đây là kịch bản nguy hiểm vì application có thể kết nối ngẫu nhiên tới 1 trong 2 node MaxScale tùy theo route mạng, dẫn tới hành vi không nhất quán, và nếu 2 MaxScale trỏ tới các trạng thái master khác nhau (do cũng mất kết nối để đồng bộ trạng thái), có thể gây ghi dữ liệu vào 2 nơi khác nhau.
11.So sánh chi phí vận hành (operational overhead) giữa việc duy trì Galera Cluster và duy trì Master-Slave + MaxScale cho cùng một yêu cầu HA.
Xem đáp án
Galera đơn giản hơn về mặt "không cần công cụ điều phối failover riêng" (mọi node đều ghi được, không cần promote), nhưng phức tạp hơn khi debug certification conflict, quản lý SST/IST khi node lớn cần đồng bộ lại, và yêu cầu ứng dụng thiết kế tránh ghi trùng key đồng thời. Master-Slave + MaxScale cần thêm tầng điều phối (MaxScale + Keepalived) và quy trình failover rõ ràng, nhưng mô hình dữ liệu đơn giản hơn (chỉ 1 nơi ghi tại 1 thời điểm, không lo conflict multi-master) — phù hợp khi ứng dụng không được thiết kế sẵn để chịu được multi-master.
12.Tại sao expire_logs_days (hoặc binlog_expire_logs_seconds) cần được cấu hình cẩn thận trong hệ thống có cả replication lẫn PITR backup?
Xem đáp án
Nếu đặt thời gian giữ binlog quá ngắn, có 2 rủi ro: (1) slave bị lag lâu hơn khoảng thời gian đó sẽ mất binlog cần thiết để tiếp tục đồng bộ, buộc phải rebuild toàn bộ; (2) chuỗi PITR bị đứt quãng nếu khoảng cách giữa lần full backup gần nhất và thời điểm cần khôi phục vượt quá thời gian giữ binlog. Cần đặt đủ dài để bao phủ cả chu kỳ backup lẫn dung sai lag tối đa chấp nhận được của slave, đồng thời cân đối với dung lượng đĩa còn trống.
13.Nêu sự khác biệt về bảo mật giữa việc cấp quyền qua GRANT trực tiếp cho từng user và cấp qua ROLE.
Xem đáp án
Về bản chất, quyền hiệu lực cuối cùng là như nhau, nhưng ROLE giúp quản lý tập trung: khi cần thay đổi chính sách (thêm/bớt quyền cho một nhóm chức năng), chỉ cần sửa định nghĩa của ROLE một lần, mọi user được gán role đó tự động cập nhật theo — giảm rủi ro sai sót khi phải REVOKE/GRANT thủ công lặp lại trên hàng chục tài khoản, đồng thời dễ audit hơn (chỉ cần xem role nào được gán cho ai).
14.Một node Galera báo wsrep_cluster_status = non-Primary. Trước khi chạy pc.bootstrap=true, cần xác minh điều gì để tránh split-brain?
Xem đáp án
Cần xác minh chắc chắn rằng các node khác trong cluster thực sự đã ngừng hoạt động hoàn toàn (không phải chỉ mất kết nối mạng tạm thời/network partition) — ví dụ kiểm tra qua kênh giám sát độc lập (ping, SSH trực tiếp) rằng các node kia đúng là đã down. Nếu chỉ là network partition tạm thời và các node kia vẫn đang chạy, việc bootstrap thủ công trên node hiện tại sẽ tạo ra một Primary Component thứ hai song song, dẫn tới 2 nhóm ghi dữ liệu độc lập không đồng bộ với nhau — chính là split-brain thực sự.
15.Vì sao việc chọn binlog_format=ROW thường được khuyến nghị hơn STATEMENT cho hệ thống có replication?
Xem đáp án
ROW format ghi lại chính xác giá trị dữ liệu đã thay đổi ở từng row, đảm bảo kết quả trên slave giống hệt master bất kể context thực thi (ví dụ hàm non-deterministic như NOW(), RAND(), hoặc trigger/stored procedure phức tạp). STATEMENT format ghi lại câu lệnh SQL gốc để replay lại trên slave — có thể cho kết quả khác nhau giữa master và slave nếu câu lệnh chứa yếu tố không xác định (non-deterministic), gây ra tình trạng dữ liệu lệch nhau (data drift) khó phát hiện.
16.Trình bày ngắn gọn quy trình đầy đủ để dựng 1 slave mới cho hệ thống Master-Slave GTID đang chạy production (không dùng downtime dài).
Xem đáp án
(1) Cài đặt MariaDB trên node slave mới, cấu hình server_id riêng biệt và gtid_domain_id khớp với cluster. (2) Lấy backup hot (mariabackup --backup) từ master hoặc từ 1 slave hiện có để tránh khóa ghi kéo dài trên master. (3) mariabackup --prepare rồi copy-back dữ liệu vào datadir của slave mới. (4) Khởi động MariaDB, dùng CHANGE MASTER TO ... MASTER_USE_GTID=slave_pos trỏ về master (GTID tự động xác định điểm tiếp tục chính xác dựa trên gtid_binlog_pos đã ghi trong backup). (5) START SLAVE, kiểm tra SHOW SLAVE STATUS cho tới khi Seconds_Behind_Master về 0 và không có lỗi.
17.Phân tích: hệ thống dùng semi-sync với chỉ 1 slave duy nhất, slave này bị mất kết nối đột ngột. Ứng dụng ghi vào master có bị ảnh hưởng ngay lập tức không?
Xem đáp án
Có ảnh hưởng tạm thời: mỗi lần commit, master sẽ chờ ACK từ slave duy nhất đó cho tới khi hết rpl_semi_sync_master_timeout, sau đó mới fallback về chế độ async để tiếp tục xử lý bình thường. Trong khoảng thời gian chờ timeout đó, các commit sẽ bị delay đúng bằng giá trị timeout đã cấu hình — ảnh hưởng trực tiếp tới latency ứng dụng cho tới khi hệ thống tự chuyển sang async hoặc slave kết nối lại.
18.Vì sao innodb_buffer_pool_size không nên đặt gần sát 100% RAM vật lý của server?
Xem đáp án
Vì server còn cần RAM cho: hệ điều hành, các connection thread (mỗi kết nối tốn thêm bộ nhớ cho sort_buffer, join_buffer, read_buffer...), các tiến trình khác đang chạy trên server (monitoring agent, backup tool...), và một phần dự phòng để tránh OOM (Out of Memory) khi tải tăng đột biến. Đặt buffer pool quá sát 100% RAM dễ dẫn tới swap hoặc bị OOM-killer chấm dứt tiến trình MySQL/MariaDB trong lúc tải cao — hậu quả nghiêm trọng hơn nhiều so với việc buffer pool hơi nhỏ.
19.Trong checklist hardening, vì sao "test kịch bản failover trên staging" được liệt kê là hạng mục riêng, không thể thay thế bằng việc chỉ đọc tài liệu hoặc xem cấu hình?
Xem đáp án
Vì cấu hình đúng trên giấy tờ không đảm bảo hành vi thực tế đúng khi sự cố thật xảy ra — có nhiều yếu tố chỉ lộ ra khi thực hiện thật: thời gian failover thực tế mất bao lâu, ứng dụng có tự động reconnect đúng cách hay không, có bước thủ công nào bị thiếu trong runbook hay không, firewall/network có chặn ngầm điều gì không. Test thực tế trên staging là cách duy nhất xác nhận toàn bộ chuỗi (từ phát hiện sự cố tới khôi phục hoàn toàn) hoạt động đúng như thiết kế, đồng thời giúp đội vận hành làm quen quy trình trước khi gặp áp lực thời gian thực trong sự cố production thật.
20.Tổng hợp: liệt kê đúng thứ tự các lớp trong một kiến trúc HA hoàn chỉnh đã học (từ application tới storage engine) và vai trò của từng lớp.
Xem đáp án
(1) Application: gửi query qua 1 điểm kết nối duy nhất. (2) Keepalived (VIP): đảm bảo điểm kết nối đó luôn trỏ tới 1 node MaxScale còn sống, tự chuyển VIP khi cần. (3) MaxScale: routing thông minh (readwritesplit), health-check monitor, tự động failover promote slave/xử lý topology Galera. (4) Cụm database (Master-Slave hoặc Galera): nơi dữ liệu thực sự được lưu trữ và đồng bộ — Master-Slave dùng binlog/GTID replication (async/semi-sync), Galera dùng certification-based synchronous replication. (5) Storage Engine (InnoDB): tầng thấp nhất, quản lý buffer pool, redo/undo log, đảm bảo ACID cho từng transaction tại chỗ trên mỗi node.