Khi học hoặc làm việc với database, bạn có từng gặp các chữ viết tắt ACID, BASE, CAP rồi tự hỏi chúng có nghĩa là gì không? Mình đã từng như vậy, và bài này là kết quả tìm hiểu của mình về chủ đề đó.

CAP nghĩa là cái nón?⌗

CAP là viết tắt của Consistency, Availability, Partition-tolerance, được Eric Brewer giới thiệu trong một buổi diễn thuyết vào năm 2000. Theo Brewer, khi xảy ra partition trong một hệ thống phân tán, tức là kết nối mạng hoặc liên lạc giữa các node bị chia cắt, hệ thống chỉ có thể chọn consistency hoặc availability. Nó không thể đảm bảo cả hai cùng lúc. Vậy những tính chất này là gì, và tại sao chúng ta không thể chọn cả hai?

C có nghĩa là consistency⌗

Trong khoa học máy tính, nhất là khi nói về dữ liệu, consistency được dùng với nhiều nghĩa khác nhau. CAP, ACID và BASE đều có chữ này, nên rất dễ nhầm lẫn. Trong CAP, một hệ thống có strong consistency khi mọi node luôn giữ cùng một phiên bản dữ liệu tại bất kỳ thời điểm nào. Nếu ghi dữ liệu mới thành công vào một node, dữ liệu ấy cũng phải có trên tất cả các node còn lại. Điều này khác với eventual consistency trong BASE: các node có thể tạm thời giữ những phiên bản khác nhau, nhưng cuối cùng sẽ hội tụ về cùng một phiên bản.

Consistency là tính chất mà nhiều hệ thống mong muốn có. Thử tưởng tượng bạn gửi 1.000.000 USD vào ngân hàng (ghê chưa!), rồi kiểm tra tài khoản mà không thấy khoản tiền đó vì độ trễ hoặc partition tạm thời. Đúng là thảm họa!

Khi chọn availability: sync chậm làm Node 2 bị tụt lại, nên client bị báo là khoản tiền chính nó vừa gửi không tồn tại

A có nghĩa là availability⌗

Theo Seth Gilbert và Nancy Lynch, một hệ thống phân tán được coi là highly available khi mọi yêu cầu gửi đến một node không bị lỗi đều nhận được phản hồi hợp lệ. Nói đơn giản, theo định nghĩa này, hệ thống phải có uptime 100% mới được coi là có tính sẵn sàng cao.

Định nghĩa ấy khó áp dụng trong thực tế. Chẳng hạn, nhiều sản phẩm Google Cloud có SLA với uptime 99,99%, tương đương khoảng 52 phút downtime mỗi năm. Chúng không thỏa mãn định nghĩa trên nhưng vẫn được coi là có tính sẵn sàng cao. Dù vậy, mình thấy định nghĩa của Gilbert và Lynch dễ hiểu hơn cách Brewer diễn đạt: dữ liệu có tính sẵn sàng cao nếu bên sử dụng dữ liệu luôn có thể truy cập một bản sao nào đó.

Availability cũng là tính chất mà nhiều hệ thống phân tán mong muốn có. Các hệ thống hiện đại được kỳ vọng luôn sẵn sàng. Nếu hệ thống e-commerce ngừng hoạt động, chúng ta có thể mất khách hàng và đơn hàng vào tay đối thủ. Trong y tế, sự cố tương tự có thể gây xáo trộn và trì hoãn việc điều trị.

P có nghĩa là partition-tolerance⌗

Một hệ thống có partition-tolerance khi vẫn tiếp tục hoạt động bình thường dù xảy ra partition, chẳng hạn do mạng chậm hoặc một số node crash.

Tại sao không thể thoả mãn đồng thời CAP?⌗

Giả sử hệ thống có hai node A và B. Khi xảy ra partition, A không thể liên lạc với B. Hệ thống vẫn tiếp tục hoạt động vì có partition-tolerance, và người dùng vẫn tương tác được như bình thường, thỏa mãn availability. Nhưng hai node đang hoạt động độc lập nên dữ liệu vừa ghi vào A chưa có ngay trên B, và ngược lại. Hệ thống vì vậy không còn đảm bảo consistency. Đây là hệ thống AP.

Với một hệ thống khác có cùng cấu hình, khi phát hiện không thể liên lạc với nhau, hai node ngừng nhận yêu cầu từ client và tìm cách kết nối lại. Cách này giữ hệ thống ở trạng thái consistent, nhưng người dùng không tương tác được nên hệ thống không còn available. Đây là hệ thống CP.

ACID là chất có tính ăn mòn?⌗

ACID là nhóm tính chất thường gặp ở database quan hệ, viết tắt của Atomicity, Consistency, Isolation, Durability. Gần như mọi database đều được kỳ vọng hỗ trợ những tính chất này ở một mức độ nào đó. ACID cũng thường được dùng để đánh giá độ tin cậy của database.

A có nghĩa là Atomicity⌗

Atomicity cho biết đơn vị công việc nhỏ nhất của database là transaction, chứ không phải query. Một transaction có thể gồm một hoặc nhiều query. Transaction chỉ thành công khi tất cả các query bên trong đều thành công.

Tính chất này rất quan trọng vì giao dịch nghiệp vụ hiếm khi chỉ cần một query. Chẳng hạn, chuyển khoản ngân hàng gồm việc trừ tiền từ tài khoản gửi và cộng tiền vào tài khoản nhận. Nếu một bước thất bại thì toàn bộ giao dịch phải được coi là thất bại. Nhờ atomicity, chúng ta có thể quản lý trạng thái của cả transaction mà không phải tự xử lý trạng thái từng query.

C có nghĩa là Consistency⌗

Consistency trong ACID khác với strong consistency hay eventual consistency trong CAP và BASE. Ở đây, database đảm bảo dữ liệu luôn tuân thủ schema và các ràng buộc đã định nghĩa. Chúng bao gồm kiểu dữ liệu, khóa chính, unique key, khóa ngoại, trigger, procedure, số cột và các ràng buộc khác.

I có nghĩa là Isolation⌗

Isolation là cách database cô lập các transaction với nhau để kiểm soát việc xử lý đồng thời. Mục tiêu là tránh data race khi nhiều transaction cùng sửa một vùng dữ liệu, như page, bảng, bản ghi hoặc index. Database thường dùng lock cho dữ liệu được truy cập bởi các query DML (như INSERT, UPDATE, DELETE), hoặc tạo snapshot cho các query DQL (như SELECT). Có bốn isolation level chính. Level càng cao thì càng ít khả năng xảy ra data race, nhưng phải đánh đổi hiệu năng.

Read-committed⌗

READ COMMITTED: Transaction 2 không thấy được row mà Transaction 1 vừa insert cho tới khi transaction đó commit

Read-committed là isolation level thứ hai, nhưng mình nghĩ nên hiểu nó trước để dễ nắm các level còn lại. Ở level này, các transaction khác không thấy thay đổi của transaction hiện tại cho đến khi nó commit thành công. Vì vậy, dữ liệu đọc được luôn là dữ liệu đã commit.

-- Bắt đầu một transaction
BEGIN;

-- Thêm một record mới vào bảng 'example' của database 'acid'
INSERT INTO 'acid'.'example'('id', 'name')
VALUES 
    -- Các transaction khác sẽ không thấy được record này trong bảng example của database acid
    -- do transaction hiện tại chưa được commit thành công
    (2, "Chuồn chuồn bay cao thì nắng, bay vừa thì râm");

-- Commit transaction. Nếu thành công, các transaction khác sẽ thấy record ở trên được thêm vào bảng
COMMIT;

Repeatable-read⌗

REPEATABLE READ: Transaction 1 đọc lại row đó vẫn không thay đổi, vì UPDATE của Transaction 2 bị chặn cho tới khi Transaction 1 commit

Repeatable-read là isolation level thứ ba. Ngoài những đảm bảo của read-committed, nó còn đảm bảo các bản ghi mà transaction hiện tại sử dụng hoặc đọc được không bị transaction khác sửa trong suốt thời gian transaction hiện tại tồn tại.

-- Bắt đầu một transaction
BEGIN;

-- Record này sẽ bị khoá, đảm bảo không bị transaction khác thay đổi
-- cho đến khi transaction hiện tại được commit
SELECT 'name' FROM 'acid'.'example' WHERE 'id' = 1;

-- Các câu query khác
-- ...

-- Giá trị vẫn sẽ giữ nguyên không thay đổi
SELECT 'name' FROM 'acid'.'example' WHERE 'id' = 1;

-- Commit transaction, và thả các khoá
COMMIT;

Serializable⌗

SERIALIZABLE: khoảng dữ liệu Transaction 1 đã đọc được giữ nguyên, vì INSERT của Transaction 2 vào khoảng đó bị chặn tới khi commit

Serializable là isolation level cao nhất. Ngoài những đảm bảo của repeatable-read, nó còn ngăn transaction khác thêm hoặc xóa bản ghi trong các tập kết quả mà transaction hiện tại sử dụng, cho đến khi transaction này kết thúc. Nói cách khác, serializable ngăn được phantom read.

-- Bắt đầu một transaction
BEGIN;

-- Lần query đầu tiên.
SELECT 'name' FROM 'acid'.'example' WHERE 'id' < 9999;

-- Các câu query khác
-- ...

-- Giá trị các record vẫn sẽ giữ nguyên không thay đổi
-- và số lượng record không đổi
SELECT 'name' FROM 'acid'.'example' WHERE 'id' < 9999;

-- Commit transaction, và thả các khoá
COMMIT;

Dirty-read⌗

Dirty-read là isolation level thấp nhất. Ở level này, các transaction hoàn toàn không được cô lập với nhau.

D có nghĩa là Durability⌗

Tính durable: MySQL báo COMMIT thành công ngay khi binlog đã nằm trên disk, nên crash trước lúc ghi page cũng không mất gì và quá trình phục hồi sẽ replay lại binlog

Durability thể hiện độ tin cậy của database khi gặp sự cố. Khi transaction đã commit thành công, dữ liệu phải được giữ lại ngay cả khi database crash ngay sau đó, với điều kiện ổ cứng không hỏng. Những kỹ thuật như two-phase commit, backup hoặc replication được dùng để đảm bảo tính chất này. Với mỗi lần commit, MySQL ghi dữ liệu vào binlog trước khi ghi vào các page trên ổ đĩa. Nếu gặp sự cố, MySQL có thể khôi phục dữ liệu từ binlog. Binlog cũng được dùng để replicate dữ liệu giữa các node. MongoDB có oplog với vai trò tương tự.

BASE!?⌗

BASE thường gặp ở các database phân tán. Thay vì tập trung vào strong consistency, BASE ưu tiên availability và chấp nhận eventual consistency.

BA có nghĩa là Basically Available⌗

Hệ thống luôn sẵn sàng, và mọi yêu cầu gửi đến hệ thống đều nhận được phản hồi hợp lệ.

S có nghĩa là Soft-State⌗

Soft-state có nghĩa là trạng thái hệ thống có thể thay đổi ngay cả khi người dùng không tương tác. Đây có thể là hệ quả của eventual consistency: sau khi người dùng ghi dữ liệu, trạng thái không thay đổi ngay lập tức mà cần thời gian để thay đổi được lan truyền và hội tụ.

E có nghĩa là Eventual Consistency⌗

Khác với strong consistency, eventual consistency cho phép dữ liệu giữa các node chưa nhất quán ngay lập tức. Sau một khoảng thời gian đủ dài, các node sẽ hội tụ về cùng một phiên bản dữ liệu.

Đọc thêm⌗

  • Seth Gilbert and Nancy Lynch, “Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services”, comp.nus.edu.sg
  • ScylaDB, “CAP Theorem”, scylladb.com
  • Martin Kleppmann, “A Critique of the CAP Theorem”, arXiv:1509.05393
  • MySQL, “InnoDB and the ACID Model”, dev.mysql.com
  • PostgreSQL, “Concurrency Control”, postgresql.org
  • MongoDB, “A Guide to ACID Properties in Database Management Systems”, mongodb.com
  • MongoDB, “Multi-Document ACID Transactions on MongoDB”, mongodb.com
  • AWS, “Điểm khác biệt giữa ACID và cơ sở dữ liệu BASE là gì?”, aws.amazon.com