Khi SQL “đầu hàng” trước những cơn bão dữ liệu
Team mình từng vận hành một hệ thống tracking hành vi người dùng bằng PostgreSQL. Mọi thứ vẫn ổn cho đến khi traffic chạm mốc 50.000 logs mỗi giây. Lúc này, Database bắt đầu “thở dốc”. Disk I/O nhảy vọt lên hơn 90% do cơ chế B-Tree của SQL phải liên tục sắp xếp lại index mỗi khi có dữ liệu mới đổ về.
Sau khi loay hoay với MongoDB nhưng vẫn gặp khó ở bài toán phân tán, mình quyết định chuyển sang Apache Cassandra. Đây là lựa chọn hàng đầu cho các hệ thống quy mô lớn, từng được Facebook dùng để xử lý tính năng tìm kiếm Inbox và sau đó là xương sống cho hạ tầng của Netflix hay Apple.
Nếu bạn làm IoT, Logging hay Messaging cần tốc độ ghi cực nhanh và khả năng mở rộng (scale-out) chỉ bằng cách cắm thêm server, Cassandra chính là câu trả lời.
Kiến trúc Cassandra: Tại sao nó ghi nhanh đến thế?
Hãy tạm quên tư duy Master-Slave của MySQL hay Redis Sentinel. Cassandra vận hành theo kiểu Peer-to-Peer, nơi mọi node đều có vai trò như nhau. Thiết kế này giúp loại bỏ hoàn toàn “điểm chết đơn lẻ” (Single Point of Failure). Một node sập? Các node còn lại vẫn gánh tải bình thường mà không cần chờ bầu chọn Master mới.
Cơ chế ghi dữ liệu: Bí mật nằm ở LSM-Tree
Nhiều bạn ngạc nhiên vì Cassandra ghi dữ liệu nhanh hơn hẳn các DB truyền thống. Đó là nhờ cấu trúc LSM-Tree. Quy trình ghi diễn ra như sau:
- Dữ liệu được ghi ngay vào CommitLog trên đĩa để chống mất mát.
- Đồng thời, nó được lưu vào MemTable trên RAM.
- Khi MemTable đầy, Cassandra flush toàn bộ xuống đĩa thành một file SSTable (Sorted String Table).
Điểm mấu chốt là việc ghi xuống đĩa diễn ra theo tuần tự (sequential access). Nó giống như việc bạn viết tiếp vào trang cuối của cuốn sổ thay vì phải lật tìm từng trang để sửa (random seek). Nhờ đó, tốc độ ghi gần như chạm ngưỡng giới hạn phần cứng.
Thiết kế Data Model: Tư duy ngược với SQL
Sai lầm phổ biến nhất khi mới dùng Cassandra là bê nguyên tư duy Normalization (chuẩn hóa) từ SQL sang. Trong Cassandra, chúng ta không thiết kế bảng theo thực thể. Chúng ta thiết kế theo câu truy vấn (Query-driven modeling).
Cassandra không hỗ trợ JOIN. Vì thế, bạn phải chấp nhận Denormalization (phi chuẩn hóa). Đừng ngại lặp dữ liệu. Ổ cứng hiện nay rất rẻ, cái chúng ta ưu tiên là tốc độ truy xuất cực nhanh.
Partition Key và Clustering Key
Một Primary Key gồm hai thành phần sống còn:
- Partition Key: Quyết định dữ liệu nằm ở node nào. Nếu bạn chọn key có độ phân tán thấp như “giới tính”, dữ liệu sẽ dồn cục vào một node (hotspot), gây quá tải cục bộ.
- Clustering Key: Quyết định cách sắp xếp dữ liệu bên trong node. Nó là trợ thủ đắc lực cho các lệnh lọc theo dải (Range query).
Kinh nghiệm thực chiến: Hãy chọn Partition Key có độ phân tán cao (High cardinality) như user_id hoặc sensor_id thay vì các trường chung chung như status.
Cài đặt nhanh với Docker
Cách nhanh nhất để thử nghiệm là dựng một cluster 2 node bằng Docker Compose.
version: '3.9'
services:
cassandra-node1:
image: cassandra:latest
container_name: cassandra1
ports:
- "9042:9042"
environment:
- CASSANDRA_CLUSTER_NAME=MyCluster
- CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch
cassandra-node2:
image: cassandra:latest
container_name: cassandra2
environment:
- CASSANDRA_CLUSTER_NAME=MyCluster
- CASSANDRA_SEEDS=cassandra-node1
- CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch
depends_on:
- cassandra-node1
Chỉ cần gõ docker-compose up -d và kiểm tra bằng lệnh docker exec -it cassandra1 nodetool status. Khi thấy trạng thái UN (Up/Normal) xuất hiện là hệ thống đã sẵn sàng.
Thực hành Query với ngôn ngữ CQL
Cassandra sử dụng CQL (Cassandra Query Language). Cú pháp khá giống SQL nhưng bị giới hạn để đảm bảo hiệu năng. Dưới đây là cách tạo bảng lưu log nhiệt độ cho thiết bị IoT:
-- Tạo Keyspace (tương đương Database)
CREATE KEYSPACE sensor_data
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2};
USE sensor_data;
-- Tạo bảng: device_id là Partition Key, captured_at là Clustering Key
CREATE TABLE temperature_logs (
device_id uuid,
captured_at timestamp,
value double,
PRIMARY KEY (device_id, captured_at)
) WITH CLUSTERING ORDER BY (captured_at DESC);
-- Insert dữ liệu thử nghiệm
INSERT INTO temperature_logs (device_id, captured_at, value)
VALUES (uuid(), toTimestamp(now()), 25.5);
Trong ví dụ này, dữ liệu của cùng một thiết bị sẽ được lưu cạnh nhau trên đĩa và sắp xếp theo thời gian mới nhất, giúp việc truy vấn lịch sử cực kỳ hiệu quả.
Mẹo tối ưu từ kinh nghiệm thực chiến
Sau nhiều lần “trả giá” bằng việc sập hệ thống, mình rút ra 4 bài học quan trọng:
- Nói không với
ALLOW FILTERING: Nếu bạn phải dùng lệnh này trong Production, Data Model của bạn đã sai. Nó sẽ ép Cassandra quét toàn bộ cluster, gây sụt giảm hiệu năng nghiêm trọng. - Kiểm soát Partition Size: Một Partition không nên vượt quá 100MB. Partition quá lớn sẽ khiến việc di chuyển dữ liệu giữa các node (Rebalancing) trở thành ác mộng.
- Đừng lạm dụng Batch: Khác với SQL, Batch trong Cassandra dùng để đảm bảo tính nguyên tử (Atomicity) giữa nhiều bảng. Nó không giúp tăng tốc độ ghi, thậm chí làm chậm nếu các bản ghi nằm rải rác ở nhiều node.
- Cấu hình Java Heap chuẩn: Hãy dành khoảng 1/4 đến 1/2 RAM cho JVM Heap (nhưng không nên quá 32GB). Điều này giúp tránh tình trạng Garbage Collection treo máy quá lâu.
Lời kết
Cassandra không phải là giải pháp vạn năng. Nó cực mạnh cho bài toán write-heavy nhưng sẽ là thảm họa nếu bạn cố làm báo cáo thống kê phức tạp (SUM/AVG) hay dùng các truy vấn cần JOIN liên tục. Nắm vững tư duy Query-driven modeling, việc quản lý hàng tỷ bản ghi sẽ không còn là bài toán khó. Chúc các bạn thành công!

