Harbor

Cassandra

Cassandra là database wide-column phân tán, ghi rất nhanh nhờ LSM tree, mở rộng gần tuyến tính bằng ring và consistent hashing. Bài này giải thích partition key, clustering column, cách mô hình hoá theo câu truy vấn, tunable consistency và những cái bẫy như tombstone.

16 phút đọcCông nghệ chủ chốt

Mẹo: Cân nhắc Cassandra so với Postgres, DynamoDB, MongoDB và NewSQL ở bài Chọn NoSQL và Database scale.

Cassandra là gì và mô hình cốt lõi

Apache Cassandra là database phân tán kiểu wide-column (Grokking xếp nó vào nhóm columnar database).1 Nó ghép hai ý tưởng: cách phân tán dữ liệu kiểu Dynamo (ring, replication, không có master, tunable consistency) và cách lưu trữ kiểu Bigtable/HBase (column family, write-ahead log).23 Nguồn gốc lịch sử của Cassandra là kiến thức chung, chưa có sách trong thư viện kiểm chứng.4

Hãy hình dung mỗi bảng Cassandra như một map hai tầng:

partition key  ->  { clustering key  ->  các cột }
  • Partition là đơn vị phân tán. Mọi row cùng partition key nằm trên cùng một nhóm node, được lưu liền nhau trên đĩa.4
  • Trong một partition, các row được sắp xếp sẵn theo clustering column. Đọc "50 tin nhắn mới nhất của kênh X" là một lần đọc tuần tự trong một partition, rất rẻ.
  • Không có join, không có foreign key, transaction rất hạn chế. Bạn đổi những thứ đó lấy khả năng ghi lớn và thêm node để tăng sức chứa. Building Microservices cũng nhận xét Cassandra thêm shard vào cụm đang chạy rất tốt và có replication sang nhiều node trong ring.5

Ba tính chất làm Cassandra nổi bật trong phỏng vấn:

  1. Không có master. Node nào cũng nhận được request. Mất một node không làm dừng cluster.2
  2. Ghi rẻ hơn đọc. Ghi chỉ là append vào commit log và cập nhật bảng trong RAM.
  3. Consistency điều chỉnh được theo từng câu lệnh. Bạn chọn ONE, QUORUM hay ALL tuỳ use case; Building Microservices dùng chính Cassandra làm ví dụ cho việc đổi consistency lấy availability theo từng lời gọi.6

Nếu bạn chưa quen với việc chia dữ liệu theo key, đọc trước Sharding và Consistent hashing.

Kiến trúc: ring, replication và đường đi của một request

Mỗi partition key được băm ra một token. Các token tạo thành một vòng tròn. Mỗi node sở hữu nhiều vị trí trên vòng (virtual node, vnode) nên tải phân bố đều hơn, và node mạnh hơn có thể được giao nhiều virtual node hơn.7

Replication factor (RF) quyết định mỗi partition có bao nhiêu bản sao. Với n = 3, ba bản sao nằm trên node sở hữu key và hai node kế tiếp theo chiều kim đồng hồ trên ring (cách mô tả của Grokking về hệ key-value kiểu Dynamo).8 RF = 3 là lựa chọn phổ biến (giả định của khoá học). Cấu hình RF riêng cho từng datacenter và rack là tính năng cụ thể của Cassandra, chưa có sách trong thư viện.4

Đang vẽ sơ đồ…

Luồng một câu lệnh (mô hình coordinator là chi tiết của Cassandra, chưa có sách kiểm chứng):4

  1. Driver (thường là token-aware) gửi request thẳng tới một node giữ bản sao. Node đó đóng vai coordinator.
  2. Coordinator gửi request tới tất cả replica của partition.
  3. Coordinator chờ đủ số phản hồi theo consistency level rồi trả kết quả cho client.

Các node biết nhau qua gossip: hai node chọn ngẫu nhiên nhau và đồng bộ thông tin membership.9 Khi một replica tạm chết, một node khác giữ tạm bản ghi dưới dạng hinted handoff và chuyển lại khi node sống dậy (sloppy quorum).1011 Lâu dài, dữ liệu lệch nhau giữa các replica được sửa bằng anti-entropy dùng Merkle tree, so sánh hash để chỉ truyền phần khác biệt.12 Tên lệnh cụ thể (nodetool repair, read repair) là của Cassandra.4

Đường ghi: LSM tree

Đang vẽ sơ đồ…

Sơ đồ trên là mô hình LSM tree của Cassandra; thư viện không có sách mô tả riêng nên các ý dưới đây chỉ nêu khái niệm.4 HBase trong Seven Databases dùng write-ahead log tương tự để phục hồi sau sự cố.13

  • Ghi = append commit log (tuần tự) + cập nhật memtable. Không có đọc trước khi ghi, không có update tại chỗ.
  • Memtable đầy thì flush thành SSTable, file sắp xếp sẵn và không bao giờ sửa.
  • Compaction gộp nhiều SSTable, bỏ bản cũ và bản đã xoá. Có nhiều chiến lược compaction, trong đó có loại theo cửa sổ thời gian hợp với dữ liệu chuỗi thời gian có TTL.
  • Đọc phải ghép kết quả từ memtable và có thể nhiều SSTable. Bloom filter giúp bỏ qua những file chắc chắn không chứa partition cần tìm: nó dùng dung lượng cố định, đổi lại thỉnh thoảng báo nhầm là có (false positive).14

Vì vậy Cassandra ghi rất nhanh, còn đọc nhanh hay chậm phụ thuộc vào việc bạn thiết kế partition tốt đến đâu.

Mô hình dữ liệu: partition key, clustering column và query-first

Với PostgreSQL, bạn chuẩn hoá dữ liệu trước rồi viết query sau. Với Cassandra, thứ tự đảo ngược: liệt kê các câu truy vấn trước, mỗi câu truy vấn thường là một bảng. Trùng lặp dữ liệu là chuyện bình thường. Grokking cũng nêu NoSQL đổi chuẩn hoá và tính nhất quán chặt lấy khả năng scale và linh hoạt.15 Cách mô hình hoá query-first là thực hành riêng của Cassandra, chưa có sách kiểm chứng.4

Ví dụ hệ thống chat cần: "lấy tin nhắn mới nhất của một kênh, phân trang lùi về quá khứ".

CREATE TABLE messages_by_channel (
  channel_id  uuid,
  day         date,        -- bucket theo ngày để partition không phình vô hạn
  message_id  timeuuid,
  author_id   uuid,
  body        text,
  PRIMARY KEY ((channel_id, day), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC)
  AND compaction = {'class': 'TimeWindowCompactionStrategy'};

-- Đọc 50 tin mới nhất của hôm nay
SELECT message_id, author_id, body
FROM messages_by_channel
WHERE channel_id = ? AND day = '2026-11-02'
LIMIT 50;

-- Trang tiếp theo: lấy các tin cũ hơn message_id cuối cùng đã thấy
SELECT * FROM messages_by_channel
WHERE channel_id = ? AND day = '2026-11-02' AND message_id < ?
LIMIT 50;

Đọc primary key ((channel_id, day), message_id) như sau:

Thành phầnVai tròQuy tắc
(channel_id, day)Partition key (composite)Câu WHERE phải có đủ các cột này với dấu =
message_idClustering columnQuyết định thứ tự trong partition, cho phép <, >, LIMIT
Các cột còn lạiGiá trịKhông lọc được nếu không có index

Vài quy tắc mô hình hoá nên nhớ:

  • Mỗi query đọc đúng một partition (hoặc vài partition đã biết trước). Query quét nhiều partition là dấu hiệu sai mô hình.
  • Giữ partition nhỏ (ngưỡng cụ thể cần đo, không có số trong sách). Kênh chat sôi động ghi mãi vào một partition sẽ thành "partition khổng lồ", nên ta thêm bucket day.
  • Phân bố đều. Partition key có ít giá trị (ví dụ country) tạo hot partition; ưu và nhược của sharding theo hash được nói ở Grokking ch.9.16
  • Cần query khác thì tạo bảng khác. Muốn "tin nhắn của một user" thì có bảng messages_by_author, ghi song song từ ứng dụng. Có thể gom hai lệnh ghi trong một BATCH logged để đảm bảo cả hai cuối cùng đều được áp dụng, nhưng batch không phải transaction cô lập.
  • Ghi đè là upsert. INSERT và UPDATE đều ghi một phiên bản mới, bản có timestamp lớn hơn thắng (last-write-wins, một cách giải quyết xung đột được Grokking mô tả).17

Khi cần kiểm tra điều kiện kiểu "chỉ tạo nếu chưa tồn tại", Cassandra có lightweight transaction (INSERT ... IF NOT EXISTS) chạy Paxos. Nó đắt hơn ghi thường vì cần nhiều vòng trao đổi giữa các replica, nên chỉ dùng cho thao tác hiếm như đăng ký username.4

Tunable consistency: ONE, QUORUM và phép tính R + W > RF

Mỗi câu lệnh chọn số replica phải phản hồi. Mô hình chung là n bản sao, đọc cần r node, ghi cần w node.8 Tên các level bên dưới là của Cassandra.4

LevelSố replica cần trả lời (RF = 3)Đặc điểm
ONE1Nhanh nhất, có thể đọc dữ liệu cũ
QUORUM2 trên toàn clusterCân bằng, chịu mất 1 replica
LOCAL_QUORUM2 trong datacenter hiện tạiLựa chọn chuẩn cho multi-DC, không chờ round trip liên vùng
EACH_QUORUMQuorum ở mọi DC (chỉ cho ghi)Ghi chắc chắn ở mọi vùng, chậm
ALL3Một replica chết là lỗi

Quy tắc: nếu R + W > RF thì tập replica đọc và tập replica ghi luôn giao nhau, nên lần đọc thấy được lần ghi gần nhất đã thành công. Với RF = 3, QUORUM ghi + QUORUM đọc cho 2 + 2 > 3. Grokking viết quy tắc này là r + w > n; Seven Databases viết W + R > N và định nghĩa quorum là N/2 + 1.818

Các tổ hợp hay gặp:

  • Ghi LOCAL_QUORUM, đọc LOCAL_QUORUM: mặc định an toàn cho hầu hết hệ thống.
  • Ghi ONE, đọc ONE: log, metrics, event analytics, nơi mất vài bản ghi hoặc đọc chậm vài giây không sao.
  • Ghi ALL, đọc ONE: đọc nhanh, nhưng ghi sẽ lỗi khi bất kỳ replica nào chết. Hiếm khi đáng.

Lưu ý: "Strong" ở đây vẫn là theo từng partition và theo timestamp. Hai client ghi đồng thời cùng một row thì bản có timestamp lớn hơn thắng, không có cơ chế phát hiện xung đột bằng vector clock như mô tả Dynamo trong Grokking và Riak.19 Đồng hồ các node lệch nhau có thể làm mất một lần ghi. Xem thêm CAP.

Con số và sizing

Các con số dưới đây là giả định của khoá học (ước lượng, cần load test) cho phần cứng phổ biến, dùng để tính nhẩm; thư viện không có sách đo hiệu năng Cassandra.4 Benchmark thật phụ thuộc kích thước row, compaction và consistency level.

Thông sốGiá trị tham khảo (ước lượng)
Node điển hìnhCỡ một server thương mại (giả định)
Dữ liệu mỗi node~2 TB cho ví dụ bên dưới (giả định của khoá học)
Đọc/ghi mỗi nodeChọn mốc ~10.000 QPS cho một server key-value theo Grokking; cần load test
Độ trễ cùng DCMột round trip cùng datacenter cỡ 0,5 ms theo bảng latency của Grokking, cộng thời gian xử lý

Grokking dùng con số khoảng 10.000 QPS cho một server key-value, và khoảng 1.000 QPS cho MySQL.20

Ví dụ tính nhanh: lưu tin nhắn chat

Các đầu vào dưới đây là giả định; cách chia theo ngày, theo giây và ước lượng dung lượng theo Grokking ch.5.21

  • 10 triệu DAU, mỗi người gửi 20 tin/ngày → 200 triệu tin/ngày.
  • Ghi trung bình: 200.000.000 / 86.400 ≈ 2.300 tin/giây. Peak ×5 ≈ 12.000 tin/giây.
  • Mỗi tin khoảng 500 B kể cả metadata → 100 GB/ngày. Với RF = 3 → 300 GB/ngày.
  • Nén khoảng 2 lần (giả định) → 150 GB/ngày, một năm khoảng 55 TB.
  • Mỗi node giữ khoảng 2 TB (giả định) → cần khoảng 28–30 node cho dữ liệu một năm. Về throughput, 12.000 ghi/giây × 3 replica = 36.000 ghi replica/giây, với mốc 10.000 QPS mỗi node chỉ cần vài node.20 Ở bài toán này dung lượng đĩa mới là thứ quyết định số node.

Kết luận kiểu này rất được đánh giá cao trong phỏng vấn: chỉ ra chiều nào (đĩa, CPU, mạng) là nút thắt.

Những cái bẫy hay gặp

Tombstone

Cassandra không xoá tại chỗ. DELETE ghi một tombstone, dấu hiệu "row này đã chết". TTL hết hạn cũng sinh tombstone. Tombstone chỉ được dọn trong compaction, sau một khoảng chờ để các replica từng bị miss lệnh xoá kịp được sửa. Thư viện không có sách về tombstone nên bài không đưa tham số mặc định.4

Hệ quả: một bảng dùng như hàng đợi (ghi vào, đọc ra, xoá đi) sẽ chất đầy tombstone. Mỗi lần đọc phải quét qua hàng nghìn tombstone trước khi tìm được row sống. Cassandra có ngưỡng cảnh báo và ngưỡng huỷ query khi quét quá nhiều tombstone.

Cách tránh:

  • Không dùng Cassandra làm queue. Dùng Kafka hoặc Redis (giả định của khoá học).
  • Với dữ liệu hết hạn theo thời gian, dùng TTL ở mức bảng + Time-Window Compaction, để cả SSTable hết hạn được bỏ nguyên khối.
  • Thiết kế bucket theo thời gian để query không chạm vào partition cũ đầy tombstone.
  • Không ghi giá trị null vào cột qua prepared statement, vì null cũng tạo tombstone.

Các bẫy khác

Sai lầm hay gặp: Dùng ALLOW FILTERING hoặc secondary index để "query linh hoạt như SQL". Lệnh đó phải hỏi gần như mọi node, độ trễ tăng theo kích thước cluster (giải thích của khoá học). Nếu cần tìm kiếm linh hoạt, đẩy dữ liệu sang Elasticsearch.

  • Đọc trước khi ghi (read-modify-write) phá đi ưu thế ghi nhanh và có race condition. Kiểu counter của Cassandra cũng không idempotent khi retry (Grokking ch.24 Sharded Counters dùng Cassandra để lưu tổng đã tính, không phải để tăng tại chỗ).22
  • Partition khổng lồ làm compaction và repair chậm, gây GC pause trên JVM.
  • Quên chạy repair định kỳ (trong khoảng gc_grace_seconds) có thể làm dữ liệu đã xoá "sống lại".
  • Nhiều bảng phụ thuộc nhau để giả lập join: mỗi lần ghi phải cập nhật nhiều bảng, dễ lệch.

Khi nào dùng và khi nào không

Tình huốngCassandraLựa chọn khác
Ghi rất nhiều, truy vấn theo key biết trước (chat, feed timeline, IoT, event log)Rất hợpDynamoDB nếu đã ở AWS và muốn managed
Multi-region active-active, chịu được eventual consistencyRất hợpDynamoDB Global Tables
Dữ liệu chuỗi thời gian có TTLHợp, với TWCSTime-series database nếu cần downsampling, query tổng hợp
Giao dịch tiền, tồn kho cần ACID nhiều bảngKhông hợpPostgreSQL
Query ad-hoc, báo cáo, joinKhông hợpPostgreSQL, data warehouse
Dữ liệu dưới vài trăm GB, team nhỏThường quá mứcPostgreSQL rồi tính sau
Hàng đợi công việcKhông hợp (tombstone)Kafka, SQS, Redis

Một quy tắc đơn giản để nói trong phỏng vấn: chọn Cassandra khi bạn biết rõ các access pattern, ghi nhiều hơn đọc, và dữ liệu vượt quá một node Postgres (ngưỡng cụ thể là giả định của khoá học). Building Microservices cũng gợi ý xem Cassandra, Mongo hay Riak khi giới hạn ghi của database hiện tại bị chạm.5 Xem thêm Chọn database. Grokking dùng Cassandra cho kho dữ liệu ghi nhiều và có tính sẵn sàng cao như dữ liệu vị trí tài xế, chuyến đi và tổng counter.23

Ghi chú về ScyllaDB

ScyllaDB là một database khác tương thích mô hình và giao thức của Cassandra, nên mô hình dữ liệu trong bài này áp dụng tương tự. Thư viện chưa có sách về ScyllaDB, nên bài không nêu khác biệt hiệu năng hay ví dụ khách hàng.4 Trong phỏng vấn, nhắc nó như một phương án "cùng mô hình" là đủ.

Vận hành ở quy mô nhỏ và lớn

Quy mô nhỏ (vài node, một DC). RF = 3 cần ít nhất 3 node để đủ ba bản sao.8 Nếu không có người hiểu vận hành cụm (compaction, repair), nên cân nhắc dịch vụ managed. Thư viện không có sách về các dịch vụ managed hay giá của chúng nên bài không liệt kê.4 Với dữ liệu nhỏ, Postgres rẻ và dễ vận hành hơn nhiều (nhận định của khoá học).

Quy mô lớn (hàng chục đến hàng trăm node, nhiều DC). Những việc phải có:

  • Repair định kỳ (anti-entropy) để các replica không lệch nhau.12
  • Giám sát p99 latency, số SSTable mỗi đọc, pending compaction, tombstone mỗi query, kích thước partition lớn nhất.4
  • Thêm node cẩn thận, từng bước: Grokking lưu ý không nên rebalance vội khi một node chỉ tạm offline.9
  • Để dư đĩa cho compaction (giả định của khoá học, cần đo).
  • Nâng version theo kiểu rolling, từng node.

Mẹo: Chi phí lớn nhất của Cassandra tự host thường là con người và đĩa, ít khi là CPU. Khi ước lượng, nhân dung lượng thô với RF, cộng thêm headroom cho compaction (giả định của khoá học).

Điểm nói chuyện trong phỏng vấn

Trong phỏng vấn: Khi chọn Cassandra, nói ngay partition key và clustering key của từng bảng, và giải thích vì sao partition không phình vô hạn. Đây là điều người phỏng vấn muốn nghe nhất.

Checklist ngắn:

  1. Vì sao chọn: ghi nhiều, access pattern cố định, cần scale ngang và chịu lỗi không master.2
  2. Bảng theo query: liệt kê 2–3 query chính, mỗi query một bảng, ghi rõ PRIMARY KEY ((pk), ck).
  3. Bucket thời gian cho dữ liệu tăng mãi như tin nhắn, event.
  4. Consistency: LOCAL_QUORUM cho cả ghi và đọc với RF = 3, nêu công thức R + W > RF.8
  5. Bẫy: tombstone, không dùng làm queue, không ALLOW FILTERING.
  6. Sizing: tính dung lượng × RF, chia cho khoảng 2 TB mỗi node.

Các bài toán thường dùng Cassandra hoặc wide-column store:

Nguồn trích dẫn

  1. Grokking Modern System Design · ch.9 Databases · mục "Types of NoSQL databases", "Columnar database". ↩

  2. Grokking Modern System Design · ch.10 Key-value Store · thiết kế key-value store kiểu Dynamo (ring, virtual node, replication, sloppy quorum, vector clock). ↩ ↩2 ↩3

  3. Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.4 HBase · phần mở đầu (HBase là database column-oriented) và mục "Compression and Bloom Filters". ↩

  4. Chưa có nguồn trong thư viện: thư viện không có sách riêng về Cassandra (cơ chế LSM, compaction, tombstone, CQL, các consistency level); phần này chỉ nêu khái niệm cơ bản, không đưa số liệu, mặc định hay giá của sản phẩm. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14

  5. Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "Scaling for Writes" (tr. 223), đoạn nhắc Cassandra thêm shard vào cụm đang chạy và replication trong ring. ↩ ↩2

  6. Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "CAP Theorem" (tr. 232), đoạn "with Cassandra I can make different trade-offs for individual calls". ↩

  7. Grokking Modern System Design · ch.10 Key-value Store · mục "Use virtual nodes" và "Advantages of virtual nodes". ↩

  8. Grokking Modern System Design · ch.10 Key-value Store · mục về consistency ("Usage of r and w", r + w > n) và replication trên ring. ↩ ↩2 ↩3 ↩4 ↩5

  9. Grokking Modern System Design · ch.10 Key-value Store · phần membership và gossip protocol. ↩ ↩2

  10. Grokking Modern System Design · ch.10 Key-value Store · mục "Handle temporary failures" (sloppy quorum, hinted handoff). ↩

  11. Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.3 Riak · ô "A Note on Hinted Handoff". ↩

  12. Grokking Modern System Design · ch.10 Key-value Store · mục "Anti-entropy with Merkle trees". ↩ ↩2

  13. Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.4 HBase · phần nói về write-ahead log (WAL). ↩

  14. Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.4 HBase · mục "Compression and Bloom Filters", ô "How Do Bloom Filters Work?". ↩

  15. Grokking Modern System Design · ch.9 Databases · mục "Drawbacks of NoSQL databases" và "Relational and Non-relational Databases". ↩

  16. Grokking Modern System Design · ch.9 Databases · mục "Sharding", "Hash-based sharding", "Consistent hashing". ↩

  17. Grokking Modern System Design · ch.9 Databases · mục "Handle conflicts", "Last-write-wins". ↩

  18. Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.3 Riak · mục "Consistency by quorum: W+R > N" (tr. 77). ↩

  19. Grokking Modern System Design · ch.10 Key-value Store · mục "Vector clock usage example" và "Compromise with vector clocks limitations". ↩

  20. Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations · mục "Standard numbers to remember" (bảng Important Rates và Important Latencies). ↩ ↩2

  21. Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations · mục "Requests estimation", "Storage requirements". ↩

  22. Grokking Modern System Design · ch.24 Sharded Counters · phần thiết kế dùng Cassandra store cho tổng counter. ↩

  23. Grokking Modern System Design · ch.30 Uber · phần chọn Cassandra lưu vị trí tài xế và thông tin chuyến đi (yêu cầu sẵn sàng cao, scale lớn). ↩