Cấu hình Write-Ahead Log (WAL) để đảm bảo tính bền vững
YugabyteDB sử dụng t-Skyline (một biến thể của Raft) để đảm bảo tính nhất quán, nhưng việc cấu hình WAL ở tầng PostgreSQL giúp tối ưu hóa hiệu năng ghi đồng thời đảm bảo dữ liệu không bị mất nếu node gặp sự cố phần cứng.
Chúng ta sẽ chỉnh sửa file cấu hình của PostgreSQL để tăng dung lượng WAL và bật chế độ ghi bền vững (fsync).
Mở file cấu hình PostgreSQL của YugabyteDB, thường nằm tại đường dẫn mặc định của cluster node.
sudo nano /var/lib/yugabyte/data/postgres/data/postgresql.conf
Tìm và thay đổi các tham số sau trong file. Ghi chú: Các dòng bị comment bằng dấu # cần được bỏ comment (xóa dấu #).
wal_level = replica
fsync = on
synchronous_commit = on
wal_buffers = 64MB
max_wal_size = 2GB
min_wal_size = 512MB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
Giải thích: Tham số `synchronous_commit = on` buộc mỗi giao dịch phải được ghi vào WAL và xác nhận bởi leader trước khi trả về kết quả cho client, đảm bảo tính Bền vững (Durability) của ACID. `max_wal_size` lớn hơn giúp giảm tần suất checkpoint, cải thiện hiệu năng ghi.
Áp dụng thay đổi bằng cách restart service YugabyteDB.
sudo systemctl restart yb-master
sudo systemctl restart yb-tserver
Kiểm tra xem các tham số đã áp dụng chưa bằng cách truy cập vào tserver và chạy lệnh `SHOW`.
yb_tserver --master_addresses=localhost:7100 --tserver_port=9042 --tserver_rpc_port=9050 --tserver_status_port=9060 --tserver_http_port=9070 --tserver_grpc_port=9080 --tserver_mem_limit=4G --tserver_disk_path=/var/lib/yugabyte/data/postgres/data --tserver_flags="--enable_wal_replay=false" --tserver_flags="--enable_wal_sync=true"
Để verify nhanh hơn, dùng ysqlsh để kiểm tra tham số runtime.
ysqlsh -h localhost -p 5433 -d yugabyte -c "SHOW synchronous_commit; SHOW wal_buffers; SHOW max_wal_size;"
Kết quả mong đợi: Lệnh trả về giá trị `on` cho `synchronous_commit`, `64MB` cho `wal_buffers` và `2GB` cho `max_wal_size`. Nếu giá trị khác, cấu hình chưa.
Tinh chỉnh mức độ nhất quán (Consistency Level)
Trong môi trường phân tán, việc chọn Consistency Level ảnh hưởng trực tiếp đến độ trễ (latency) và tính nhất quán (consistency). YugabyteDB cho phép thay đổi mức độ này theo từng bảng hoặc từng câu lệnh.
Chúng ta sẽ tạo một bảng giao dịch tài chính yêu cầu độ nhất quán cao (Strong) và một bảng log yêu cầu độ trễ thấp (Local).
Truy cập vào shell giao dịch của YugabyteDB.
ysqlsh -h localhost -p 5433 -d yugabyte
Tạo bảng `transactions` với mức độ nhất quán mặc định là Strong (Quorum) để đảm bảo ACID chặt chẽ cho các giao dịch tiền tệ.
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
amount DECIMAL(10, 2) NOT NULL,
user_id INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT positive_amount CHECK (amount > 0)
) WITH (consistency_level = 'strong');
Giải thích: Tham số `WITH (consistency_level = 'strong')` buộc mọi ghi và đọc trên bảng này phải được xác nhận bởi đa số (quorum) của các replica trước khi hoàn tất. Điều này tăng độ trễ nhưng đảm bảo tính nhất quán tuyệt đối.
Tạo bảng `audit_logs` với mức độ nhất quán là `local` để ghi log nhanh, chấp nhận sự trễ nhất quán (eventual consistency).
CREATE TABLE audit_logs (
id SERIAL PRIMARY KEY,
action VARCHAR(255),
details TEXT,
timestamp TIMESTAMPTZ DEFAULT NOW()
) WITH (consistency_level = 'local');
Giải thích: `consistency_level = 'local'` chỉ ghi vào replica địa phương (leader) và trả về ngay. Dữ liệu sẽ được replicate về các node khác trong nền. Phù hợp cho logging, metrics.
Verify cấu hình của bảng bằng cách truy vấn hệ thống catalog `pg_yb_tables`.
SELECT name, consistency_level FROM yb_catalog.pg_yb_tables WHERE name IN ('transactions', 'audit_logs');
Kết quả mong đợi: Bảng `transactions` hiển thị `strong`, bảng `audit_logs` hiển thị `local`. Nếu không thấy, kiểm tra lại cú pháp `WITH`.
Sử dụng BEGIN, COMMIT, ROLLBACK trong kịch bản thực tế
Để đảm bảo tính Nguyên tử (Atomicity) và Cách ly (Isolation), chúng ta phải đóng gói các thao tác vào một khối giao dịch (Transaction). YugabyteDB hỗ trợ chuẩn SQL ACID thông qua các lệnh này.
Kịch bản: Chuyển tiền từ User A sang User B. Nếu User A không đủ tiền hoặc mạng lưới lỗi, toàn bộ giao dịch phải bị hủy (Rollback).
Trước tiên, khởi tạo dữ liệu thử nghiệm cho hai tài khoản.
INSERT INTO transactions (amount, user_id) VALUES (1000.00, 1);
INSERT INTO transactions (amount, user_id) VALUES (500.00, 2);
Bắt đầu một giao dịch chuyển tiền 200 từ User 1 sang User 2.
BEGIN;
UPDATE transactions SET amount = amount - 200.00 WHERE user_id = 1;
UPDATE transactions SET amount = amount + 200.00 WHERE user_id = 2;
COMMIT;
Giải thích: `BEGIN` khởi tạo một khối giao dịch mới. Các lệnh `UPDATE` bên trong chỉ ảnh hưởng đến phiên làm việc hiện tại. `COMMIT` cam kết thay đổi và làm cho chúng trở nên vĩnh viễn (durable) trên tất cả các node (nếu consistency_level là strong).
Verify kết quả sau khi commit.
SELECT user_id, amount FROM transactions WHERE user_id IN (1, 2) ORDER BY user_id;
Kết quả mong đợi: User 1 có 800.00, User 2 có 700.00. Tổng số tiền vẫn là 1500.00.
Kịch bản 2: Thực hiện giao dịch sai (chuyển tiền vượt quá số dư) để kích hoạt Rollback.
BEGIN;
UPDATE transactions SET amount = amount - 900.00 WHERE user_id = 1;
-- Giả sử có logic kiểm tra ở ứng dụng, nhưng ở đây ta cố tình làm sai để demo ROLLBACK
COMMIT;
Chạy lại lệnh trên, nhưng lần này dùng `ROLLBACK` ngay sau khi phát hiện lỗi (trong thực tế ứng dụng sẽ gọi ROLLBACK khi catch exception).
BEGIN;
UPDATE transactions SET amount = amount - 900.00 WHERE user_id = 1;
SELECT * FROM transactions WHERE user_id = 1; -- Xem giá trị tạm thời (sẽ âm)
ROLLBACK;
Giải thích: `ROLLBACK` hủy bỏ tất cả các thay đổi trong giao dịch hiện tại. Dữ liệu trở về trạng thái trước khi `BEGIN` được gọi. Tính Atomicity được đảm bảo: hoặc tất cả thay đổi xảy ra, hoặc không có gì thay đổi.
Verify lại số dư sau khi rollback.
SELECT user_id, amount FROM transactions WHERE user_id = 1;
Kết quả mong đợi: Số dư của User 1 vẫn là 800.00 (giá trị sau lần commit trước), không bị giảm xuống -100.00.
Đo lường độ trễ (Latency) khi thực hiện các giao dịch ACID
Chúng ta cần đo lường sự khác biệt về độ trễ khi thực hiện giao dịch với cấu hình `synchronous_commit = on` (Strong) so với `local` hoặc khi có sự cố mạng giả lập.
Sử dụng công cụ `psql` với chế độ timing để đo thời gian thực thi từng câu lệnh.
Bật chế độ hiển thị thời gian trong ysqlsh.
\timing on
Thực hiện 100 lần ghi vào bảng `transactions` (Strong consistency) và ghi nhận thời gian trung bình.
DO $$
DECLARE
i INT;
BEGIN
FOR i IN 1..100 LOOP
INSERT INTO transactions (amount, user_id) VALUES (1.00, 3);
END LOOP;
END $$;
Giải thích: Vòng lặp `DO` thực hiện 100 lần chèn dữ liệu trong một phiên. Vì mỗi lần chèn đều là một giao dịch riêng lẻ (autocommit mặc định của INSERT), tổng thời gian sẽ phản ánh overhead của việc đồng bộ hóa WAL và Raft.
Thực hiện tương tự với bảng `audit_logs` (Local consistency) để so sánh.
DO $$
DECLARE
i INT;
BEGIN
FOR i IN 1..100 LOOP
INSERT INTO audit_logs (action, details) VALUES ('test_write', 'local consistency write');
END LOOP;
END $$;
Phân tích kết quả: Thời gian cho bảng `audit_logs` thường nhanh hơn đáng kể (có thể 2-5 lần) so với bảng `transactions` do không cần chờ xác nhận từ các follower trong cụm.
Để đo độ trễ giao dịch phức tạp (Multiple operations trong một BEGIN/COMMIT), sử dụng lệnh sau.
BEGIN;
UPDATE transactions SET amount = amount + 10 WHERE user_id = 3;
UPDATE transactions SET amount = amount - 10 WHERE user_id = 3;
COMMIT;
Kết quả mong đợi: Bạn sẽ thấy dòng thời gian (time) hiển thị ngay sau mỗi lệnh. Lệnh `COMMIT` trong môi trường Strong Consistency thường chiếm phần lớn thời gian do phải chờ Raft consensus. Nếu thời gian `COMMIT` > 50ms trong môi trường nội bộ (local), có thể cần điều chỉnh lại `max_wal_size` hoặc kiểm tra độ trễ mạng giữa các node.
Xóa dữ liệu test để làm sạch môi trường.
DELETE FROM transactions WHERE user_id = 3;
DELETE FROM audit_logs WHERE action = 'test_write';
Điều hướng series:
Mục lục: Series: Triển khai Database ACID với YugabyteDB trên Ubuntu 24.04
« Phần 3: Tạo và quản lý không gian làm việc (Tablespaces) trong YugabyteDB
Phần 5: Mở rộng cụm YugabyteDB lên 3 node và cấu hình Replication »