SingleStore 8 — Transaction, HA & DR

15 thg 7, 2026 3 lượt xem
#sql
#disaster-recovery
#high-availability
#singlestore
#htap
#transactions

SingleStore 8 — Transaction, HA & DR

Ở các bài trước, ta đã thấy SingleStore chia dữ liệu thành partitions trải trên nhiều leaf node và điều phối bởi aggregator (xem Kiến trúc cluster). Kiến trúc phân tán đó đặt ra ba câu hỏi sống còn với một hệ trọng yếu như core banking:

  1. Đúng — nhiều giao dịch chạy song song trên cùng dữ liệu thì kết quả có nhất quán không? (Transaction & isolation)
  2. Không mất khi máy chết — một leaf hỏng thì dữ liệu trên đó có còn không, hệ có tiếp tục phục vụ không? (High Availability)
  3. Không mất khi sập cả site — cháy nguyên data center thì sao? (Disaster Recovery)

Ba câu hỏi này ánh xạ vào ba cơ chế riêng biệt nhưng chồng lớp lên nhau: ACID + durability (đúng và bền trong một node), HA replication (chịu lỗi cấp node/leaf trong một cụm), và DR (chịu lỗi cấp site). Bài này đi lần lượt từ trong ra ngoài.

Hai chỉ số luôn xuất hiện khi bàn về HA/DR — nhớ để đối chiếu suốt bài:

  • RPO (Recovery Point Objective) — chấp nhận mất tối đa bao nhiêu dữ liệu, đo bằng thời gian.
  • RTO (Recovery Time Objective) — mất bao lâu để phục vụ trở lại sau sự cố.

Transaction & tính đúng — ACID trên hệ phân tán

SingleStore hỗ trợ transaction ACID với cú pháp tương thích MySQL: START TRANSACTION / BEGIN, COMMIT, ROLLBACK. Một chuyển khoản ngân hàng kinh điển:

-- Ví dụ ngân hàng: chuyển 1.000.000đ từ tài khoản A sang B
START TRANSACTION;

UPDATE accounts SET balance = balance - 1000000
WHERE account_id = 'ACC_A';

UPDATE accounts SET balance = balance + 1000000
WHERE account_id = 'ACC_B';

INSERT INTO transactions (txn_id, from_acc, to_acc, amount, ts)
VALUES (UUID(), 'ACC_A', 'ACC_B', 1000000, NOW());

COMMIT;   -- cả ba thao tác cùng thành công, hoặc cùng không

Rowstore MVCC — đọc không chặn ghi

Với rowstore (bảng in-memory), SingleStore dùng MVCC (Multi-Version Concurrency Control): khi một dòng bị sửa, hệ tạo ra một phiên bản mới thay vì ghi đè tại chỗ, và giữ lại phiên bản cũ đủ lâu cho các transaction đang cần bản cũ. Hệ quả cốt lõi giống PostgreSQL: reader không chặn writer và writer không chặn reader — một truy vấn đọc dài không khoá cứng các giao dịch ghi, và ngược lại. Các phiên bản không còn ai tham chiếu sẽ được dọn dần (garbage collection).

Mức cô lập mặc định: READ COMMITTED

SingleStore chạy ở mức cô lập READ COMMITTED — mỗi câu lệnh nhìn thấy dữ liệu đã commit tại thời điểm nó bắt đầu. Điều này có nghĩa:

  • Không có dirty read — bạn không bao giờ đọc phải thay đổi chưa commit của giao dịch khác.
  • Có thể xảy ra non-repeatable read — đọc cùng một dòng hai lần trong một transaction dài có thể ra hai giá trị nếu giao dịch khác commit ở giữa.

Đây là mức cô lập cân bằng giữa tính đúng và thông lượng, phù hợp cho khối lượng OLTP lớn. Khi cần bảo vệ chặt hơn một dòng cụ thể, dùng SELECT ... FOR UPDATE để lấy khoá ghi trên dòng đó trong phạm vi transaction (ví dụ khoá dòng số dư trước khi trừ tiền để tránh cập nhật mất mát).

Lưu ý — đừng nhầm mức cô lập: mặc định là READ COMMITTED, không phải SERIALIZABLE hay REPEATABLE READ. Nếu logic nghiệp vụ cần bất biến mạnh trên nhiều dòng, hãy dựng nó bằng khoá tường minh (FOR UPDATE) hoặc thiết kế lại, chứ đừng kỳ vọng snapshot isolation kiểu REPEATABLE READ.

Transaction đơn-partition vs phân tán

Đây là điểm khác biệt so với một database đơn máy. Nếu mọi dòng mà transaction chạm tới nằm trên cùng một partition (thường vì chúng chung giá trị SHARD KEY), giao dịch chạy cục bộ trên một leaf — nhanh và rẻ. Nếu transaction đụng dữ liệu ở nhiều partition trên nhiều leaf, nó trở thành distributed transaction, cần aggregator điều phối nhiều leaf commit đồng bộ với nhau — tốn hơn về độ trễ và phối hợp.

Bài học thiết kế: chọn SHARD KEY sao cho các thực thể hay giao dịch cùng nhau nằm chung partition (xem Sharding & phân phối dữ liệu). Ví dụ nếu hay chuyển tiền trong nội bộ một khách hàng, shard theo customer_id giúp nhiều giao dịch thành đơn-partition.

Durability — transaction log + snapshot

ACID còn chữ D (Durability): đã commit thì mất điện cũng không được quên. Rowstore nằm trong RAM, nên nếu chỉ có RAM thì mất điện là mất sạch. SingleStore đảm bảo bền vững bằng hai thành phần bổ trợ nhau ghi xuống đĩa:

  • Transaction log (write-ahead log) — mọi thay đổi được ghi tuần tự vào log trên đĩa trước/khi commit. Log là dạng ghi nối tiếp (append) nên nhanh.
  • Snapshot — định kỳ, SingleStore chụp ảnh toàn bộ trạng thái của một database (rowstore) xuống đĩa. Có snapshot rồi thì phần log cũ trước snapshot không còn cần nữa và được cắt bỏ (truncate), giữ cho log không phình vô hạn.

Khi node khởi động lại sau sự cố, quá trình recovery là: nạp snapshot gần nhất rồi replay phần transaction log sinh ra sau snapshot đó để dựng lại đúng trạng thái tới giao dịch cuối cùng đã commit. (Columnstore ghi thẳng segment ra đĩa nên bản thân đã bền; log/snapshot chủ yếu bảo vệ phần rowstore và metadata.)

Đánh đổi durability: đồng bộ vs không đồng bộ flush

Điểm commit chạm đĩa chính là chỗ đánh đổi độ bền vs độ trễ. Nếu commit phải chờ log flush xong xuống đĩa mới báo thành công (durability đồng bộ), bạn được RPO cục bộ = 0 nhưng mỗi commit gánh thêm độ trễ ghi đĩa. Nếu flush được gom/ghi nền (không đồng bộ), commit nhanh hơn nhưng có cửa sổ rất nhỏ có thể mất giao dịch vừa commit nếu mất điện đúng lúc. Đây là cùng một triết lý đánh đổi như synchronous_commit của PostgreSQL — chọn theo mức chấp nhận rủi ro của nghiệp vụ.

High Availability — partition master + replica

Durability bảo vệ bạn khi một node khởi động lại. Nhưng nếu một leaf chết hẳn (đĩa hỏng) thì cần dữ liệu vẫn còn ở nơi khác và hệ vẫn phục vụ ngay. Đó là việc của High Availability.

Cơ chế: khi bật HA (redundancy level 2), mỗi partition tồn tại thành hai bản — một master và một replica — và SingleStore luôn đặt chúng trên hai leaf khác nhau. Master phục vụ đọc/ghi; replica nhận luồng transaction log từ master và áp dụng liên tục để giữ mình là bản sao cập nhật. Trong một cụm, việc nhân bản này mặc định là đồng bộ (sync replication): master chờ replica xác nhận đã nhận log trước khi báo commit — nhờ đó khi failover không mất giao dịch đã commit (RPO trong cụm = 0).

Các leaf được ghép thành cặp/nhóm khả dụng (availability groups) sao cho master của leaf này có replica nằm trên leaf thuộc nhóm kia. Khi một leaf chết, các replica tương ứng trên leaf còn sống được thăng cấp thành master (failover) và cụm tiếp tục phục vụ; aggregator định tuyến truy vấn sang master mới. Khi leaf hỏng được thay/khởi động lại, nó được rebalance để nhận lại vai trò và khôi phục mức dư thừa.

Điểm cần nhớ về HA trong cụm:

  • HA chống lỗi cấp leaf trong cùng một cụm — không bảo vệ khi mất cả cụm/site.
  • Nhân bản sync trong cụm cho RPO = 0 khi failover, đổi lại commit gánh thêm một vòng mạng nội cụm tới replica.
  • Aggregator (đặc biệt Master Aggregator giữ metadata) cũng cần dư thừa; nếu không có aggregator dự phòng thì mất Master Aggregator sẽ mất khả năng điều phối cụm dù dữ liệu leaf còn nguyên.

Disaster Recovery — vượt ra ngoài một cụm

HA lo được chuyện mất một leaf. Nhưng cháy cả data center thì mất luôn mọi leaf lẫn aggregator của cụm — HA bó tay. Disaster Recovery giải quyết lỗi cấp site bằng cách giữ một bản dữ liệu ở nơi khác. SingleStore có hai hướng, thường dùng kết hợp:

1. Replication liên cụm (cross-cluster / secondary cluster)

Nhân bản toàn bộ một database sang một cụm thứ hai đặt ở site khác. Khác với replication trong cụm (đồng bộ, độ trễ thấp), replication DR sang site xa thường chạy không đồng bộ (async): cụm chính không chờ cụm DR xác nhận rồi mới commit — nhờ đó độ trễ mạng liên site không cộng vào thời gian commit của giao dịch. Đánh đổi là RPO nhỏ > 0: nếu site chính sập đột ngột, một cửa sổ dữ liệu chưa kịp truyền sang có thể mất. Cụm DR ở chế độ chỉ-đọc và có thể được thăng cấp thành cụm chính khi cần.

2. Backup & restore

Sao lưu database ra kho lưu trữ tách biệt (S3, filesystem, blob store) rồi khôi phục khi cần:

-- Sao lưu database sang object store (minh hoạ)
BACKUP DATABASE core_bank TO S3 's3://bank-backups/core_bank/'
  CONFIG '{"region":"ap-southeast-1"}'
  CREDENTIALS '{"aws_access_key_id":"...","aws_secret_access_key":"..."}';

-- Khôi phục sang một cụm mới khi thảm hoạ
RESTORE DATABASE core_bank FROM S3 's3://bank-backups/core_bank/'
  CONFIG '{"region":"ap-southeast-1"}'
  CREDENTIALS '{"aws_access_key_id":"...","aws_secret_access_key":"..."}';

Backup/restore là lớp bảo vệ chống cả lỗi logic (một DELETE/DROP sai) mà replication không chống được — vì replication nhân bản y nguyên cả câu lệnh sai sang cụm DR. Backup định kỳ cho phép quay về một mốc trước sự cố; replication liên cụm cho RTO thấp hơn nhiều nhưng không cứu được lỗi logic. Vì thế thực tế người ta dùng cả hai.

Ba lớp bù cho nhau

Loại sự cốCơ chế bảo vệRPO điển hình
Node khởi động lại (mất điện tạm)Transaction log + snapshot (durability)0 (nếu flush đồng bộ)
Một leaf chết trong cụmHA: partition master/replica + failover (sync)0
Mất cả cụm/siteDR: replication liên cụm (async)nhỏ > 0
Lỗi logic (DROP/DELETE sai)Backup & restore về mốc trước sự cố= khoảng cách tới backup gần nhất

Mỗi lớp xử lý loại sự cố mà lớp kia không lo được — đúng tinh thần "phòng thủ theo chiều sâu".

Đánh đổi nhất quán vs độ trễ — bức tranh chung

Xuyên suốt bài là một đánh đổi duy nhất lặp lại ở ba tầng: muốn không mất dữ liệu (RPO thấp) thì phải chờ ai đó xác nhận đã ghi bền trước khi báo commit, và cái chờ đó cộng vào độ trễ commit.

  • Tầng đĩa: commit chờ log flush (bền hơn) vs flush nền (nhanh hơn).
  • Tầng cụm (HA): commit chờ replica trong cụm ack — sync, RPO=0, thêm một vòng mạng nội cụm.
  • Tầng site (DR): chờ cụm DR xa ack sẽ cho RPO≈0 nhưng cộng độ trễ liên site rất lớn → nên DR thường async, chấp nhận RPO nhỏ để giữ độ trễ commit thấp.

Không có lời giải "miễn phí": bạn chọn điểm cân bằng theo giá trị dữ liệu. Với giao dịch tiền tệ, RPO=0 trong cụm gần như bắt buộc; với DR liên vùng địa lý, async + RPO vài giây thường là đánh đổi hợp lý.

Lưu ý một liên hệ với ingest: Pipelines nạp dữ liệu exactly-once dựa trên chính tính giao dịch ở đây — offset nguồn (ví dụ Kafka) được lưu cùng transaction với dữ liệu nạp, nên "đúng-một-lần" thực chất là hệ quả của ACID cộng với việc commit atomic offset + data.

Use case thực tế — core banking chịu lỗi nhiều tầng

(Số liệu dưới đây là minh hoạ để hình dung, không phải benchmark chính thức.)

Một ngân hàng chạy sổ cái giao dịch trên SingleStore, yêu cầu RPO=0 trong vùngRTO vài phút, 24/7.

Kiến trúc:

  • Cụm chính (DC1): 6 leaf ghép thành 2 availability group, redundancy = 2. Bảng accounts, transactions shard theo customer_id nên phần lớn chuyển khoản nội bộ là transaction đơn-partition, độ trễ commit ~ vài ms. HA sync trong cụm → mất bất kỳ 1 leaf nào, replica thăng cấp master trong vài giây, không mất giao dịch đã commit.
  • Durability: commit chờ transaction log flush đồng bộ; snapshot nền mỗi vài phút để recovery nhanh nếu cả cụm khởi động lại.
  • Cụm DR (DC2, vùng địa lý khác): nhận replication async từ DC1, RPO ~ vài giây. Nếu DC1 sập toàn bộ, đội vận hành thăng cấp DC2 thành cụm chính, RTO ~ vài phút.
  • Backup: BACKUP DATABASE sang S3 mỗi giờ + giữ nhiều bản, để chống lỗi logic (job batch xoá nhầm) — thứ mà replication DR sẽ nhân bản y nguyên.

Kịch bản 1 — hỏng đĩa một leaf lúc cao điểm: replica partition trên leaf cặp lên làm master, aggregator định tuyến lại; người dùng chỉ thấy một nhịp chậm rất ngắn. RPO=0 nhờ sync replication trong cụm.

Kịch bản 2 — mất điện toàn DC1 lúc 2h sáng: HA vô dụng vì mất cả cụm. Đội trực thăng cấp DC2; do replication là async, chấp nhận mất cửa sổ ~ vài giây giao dịch cuối chưa kịp truyền — nằm trong RPO cam kết.

Kịch bản 3 — 10:05 một job chạy sai UPDATE toàn bảng: lỗi đã nhân bản sang DC2, nên replication vô dụng. Dùng backup gần nhất trước 10:05 để khôi phục phần dữ liệu bị hỏng.

Ghi nhớ

  • SingleStore hỗ trợ transaction ACID cú pháp kiểu MySQL; rowstore dùng MVCC nên reader không chặn writer và ngược lại.
  • Mức cô lập mặc định là READ COMMITTED — không có dirty read, nhưng có thể non-repeatable read. Cần chặt hơn thì dùng SELECT ... FOR UPDATE. Đừng nhầm là SERIALIZABLE/REPEATABLE READ.
  • Durability = transaction log (write-ahead) + snapshot định kỳ; recovery = nạp snapshot gần nhất rồi replay log. Log/snapshot chủ yếu bảo vệ rowstore & metadata; columnstore ghi segment ra đĩa nên đã bền.
  • Giao dịch chạm một partition thì cục bộ và rẻ; chạm nhiều partition thành distributed transaction đắt hơn → chọn SHARD KEY để gom giao dịch cùng nhau vào một partition.
  • HA trong cụm = mỗi partition có master + replica trên leaf khác, sync replication cho RPO=0, failover khi leaf chết. Chống lỗi cấp leaf, không chống mất cả site. Nhớ dư thừa cả aggregator.
  • DR = replication liên cụm (async, RPO nhỏ) cho lỗi cấp site + backup/restore cho lỗi logic. Hai thứ bù cho nhau; DR không cứu lỗi logic.
  • Đánh đổi lặp lại ở cả ba tầng: RPO thấp ⇄ độ trễ commit cao — chọn theo giá trị dữ liệu (sync trong vùng, async liên vùng).

Nguồn tham khảo

  • SingleStore Documentation — Managing High Availability (docs.singlestore.com)
  • SingleStore Documentation — Disaster Recovery (docs.singlestore.com)
  • SingleStore Documentation — Backup and Restore Data (docs.singlestore.com)
  • SingleStore Documentation — Transactions / Isolation Levels (docs.singlestore.com)
  • SingleStore Documentation — Rowstore và MVCC (docs.singlestore.com)
  • SingleStore Documentation — Cluster Architecture / Availability Groups (docs.singlestore.com)
  • MySQL Documentation — Transactional and Locking Statements (dev.mysql.com) — tham chiếu cú pháp tương thích

Bài viết liên quan

Kiến trúc shared-nothing của SingleStore: Master Aggregator giữ metadata và điều phối, Child Aggregator scale kết nối, Leaf node chứa dữ liệu chia thành partition. Bài mổ xẻ luồng một query (aggregator nhận → pushdown xuống leaf → gộp kết quả) và cơ chế High Availability master/replica, failover, redundancy level.

15 thg 7, 2026 5

SingleStore (tiền thân MemSQL) là database quan hệ phân tán HTAP, tương thích giao thức MySQL, gộp OLTP và OLAP trong một hệ thống. Bài mở màn dựng mô hình tinh thần về HTAP, giải thích vấn đề nó giải quyết (tránh ETL sang warehouse riêng), chỉ rõ khi nào NÊN và KHÔNG NÊN dùng, định vị so với PostgreSQL, ClickHouse, BigQuery, TiDB/CockroachDB, và vẽ bản đồ toàn series 10 bài.

15 thg 7, 2026 5

Điểm khác biệt lớn nhất của SingleStore: nó BIÊN DỊCH truy vấn ra mã máy (code generation) rồi chạy song song MPP trên leaf, thay vì diễn giải từng dòng. Bài dựng luồng SQL → tối ưu → sinh mã → plan biên dịch, giải thích plan cache tái dùng (biên dịch 1 lần), query pushdown xuống leaf và aggregator gộp; cách đọc EXPLAIN/PROFILE (thời gian, rows, bộ nhớ, network/reshuffle) và SHOW PLANCACHE để nhận diện reshuffle/broadcast, tối ưu truy vấn.

15 thg 7, 2026 5

Khoá chính/ngoại/tổng hợp, ràng buộc (NOT NULL, UNIQUE, CHECK, FK) và cách mô hình hoá quan hệ 1:1, 1:n, n:n cho hệ khách hàng — tài khoản — giao dịch. Đi qua chuẩn hoá 1NF/2NF/3NF bằng ví dụ trước/sau cụ thể, rồi bàn khi nào nên cố tình phi chuẩn hoá để đọc nhanh — giúp thiết kế lược đồ đúng ngay từ đầu.

13 thg 7, 2026 5

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!