Nguyên tắc thiết kế Data Model trong Cassandra
1. Làm gì: Áp dụng tư duy "Query-first approach" (Ưu tiên truy vấn) ngay từ đầu thay vì mô hình quan hệ truyền thống.
2. Tại sao: Cassandra không hỗ trợ JOIN hay ORDER BY trên toàn bộ bảng. Schema phải được thiết kế sao cho dữ liệu cần truy xuất nằm cùng một Partition Key. Việc thiết kế sai sẽ dẫn đến "Full Table Scan" gây sập cluster.
3. Kết quả mong đợi: Xác định rõ các truy vấn (Read/Write patterns) trước khi viết lệnh CREATE TABLE.
Xác định các mẫu truy vấn cho bài toán
Giả sử ta cần xây dựng hệ thống log monitoring. Ta cần 2 truy vấn chính:
- Liệt kê tất cả log của một user trong 24h qua.
- Liệt kê tất cả log của toàn hệ thống theo mức độ nghiêm trọng (Severity) trong 1 giờ qua.
Không được tạo một bảng chung chung kiểu logs (user_id, timestamp, severity, message) vì không thể tối ưu cả hai truy vấn trên.
Tạo Keyspace và Cấu hình Replication Strategy
1. Làm gì: Tạo Keyspace với SimpleStrategy (cho dev/test) hoặc NetworkTopologyStrategy (cho production multi-datacenter).
2. Tại sao: Replication Strategy quyết định dữ liệu được nhân bản ở đâu. NetworkTopologyStrategy cho phép cân bằng dữ liệu theo rack và datacenter, tăng tính chịu lỗi.
3. Kết quả mong đợi: Keyspace được tạo thành công, dữ liệu sẽ được nhân bản theo số lượng replica đã định.
Cấu hình Keyspace cho môi trường Production
Trước khi tạo, hãy kết nối vào CQL shell của Cassandra (cqlsh) hoặc sử dụng cqlsh trên terminal Ubuntu 24.04.
cqlsh -u cassandra -p cassandra -k system
Nội dung lệnh tạo Keyspace với 3 replica phân tán trên 3 datacenter giả định (dc1, dc2, dc3). Nếu chỉ có 1 node hoặc 1 datacenter, dùng SimpleStrategy.
CREATE KEYSPACE monitoring_logs
WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3,
'dc2': 3,
'dc3': 3
}
AND durable_writes = true;
Kết quả: Cassandra sẽ trả về thông báo thành công và hiển thị cấu hình replication. Chạy lệnh sau để verify:
DESCRIBE KEYSPACE monitoring_logs;
Thiết kế Partition Key và Clustering Key
1. Làm gì: Xây dựng bảng dựa trên truy vấn đã xác định ở phần 1, chọn đúng trường làm Partition Key (PK) và Clustering Key (CK).
2. Tại sao:
- Partition Key: Quyết định dữ liệu nằm ở node nào. Dữ liệu có cùng PK sẽ nằm cùng một partition. Đây là yếu tố quan trọng nhất để tránh "Hotspot".
- Clustering Key: Quyết định thứ tự dữ liệu trong cùng một partition. Cassandra lưu trữ dữ liệu theo thứ tự CK này.
3. Kết quả mong đợi: Bảng được tạo với cấu trúc tối ưu, cho phép truy vấn nhanh O(1) cho PK và O(log N) cho CK.
Bảng 1: Logs theo User (Tối ưu cho truy vấn theo user)
Truy vấn: "Cho tôi log của user_id = 'user_123' trong khoảng thời gian X".
Thiết kế:
- Partition Key:
user_id (Để gom tất cả log của 1 user vào 1 partition).
- Clustering Key:
timestamp (Để sắp xếp log theo thời gian, hỗ trợ LIMIT và WHERE theo thời gian).
USE monitoring_logs;
CREATE TABLE user_logs (
user_id UUID,
event_time TIMESTAMP,
severity TEXT,
message TEXT,
ip_address INET,
PRIMARY KEY ((user_id), event_time)
) WITH CLUSTERING ORDER BY (event_time DESC)
AND gc_grace_seconds = 864000; -- Giữ 10 ngày để phục hồi dữ liệu
Kết quả: Dữ liệu của mỗi user nằm riêng biệt. Khi query theo user_id, Cassandra chỉ cần đọc 1 partition duy nhất. Việc sắp xếp DESC giúp lấy log mới nhất đầu tiên mà không cần scan toàn bộ.
Bảng 2: Logs theo Severity (Tối ưu cho giám sát toàn hệ thống)
Truy vấn: "Cho tôi tất cả log có severity = 'ERROR' trong 1 giờ qua".
CẢNH BÁO: Nếu dùng severity làm Partition Key duy nhất, toàn bộ log ERROR của hệ thống sẽ nằm trong 1 partition khổng lồ gây "Hotspot".
Thiết kế:
- Partition Key:
severity + day_bucket (Thêm một trường băm theo ngày để chia nhỏ dữ liệu).
- Clustering Key:
timestamp.
CREATE TABLE severity_logs (
severity TEXT,
day_bucket TEXT, -- Định dạng 'YYYY-MM-DD'
event_time TIMESTAMP,
user_id UUID,
message TEXT,
PRIMARY KEY ((severity, day_bucket), event_time)
) WITH CLUSTERING ORDER BY (event_time DESC)
AND gc_grace_seconds = 864000;
Kết quả: Dữ liệu ERROR được chia nhỏ theo ngày. Nếu có 1 triệu lỗi trong 1 ngày, chúng vẫn được phân tán đều (tránh quá tải 1 partition). Tuy nhiên, khi query cần biết trước day_bucket.
Tránh Hotspot và Phân vùng không đều
1. Làm gì: Sử dụng kỹ thuật "Salting" (Muối) hoặc "Bucketing" khi Partition Key có entropy thấp (ít biến thể).
2. Tại sao: Nếu Partition Key là một trường có giá trị cố định (ví dụ: status='active' cho 10 triệu user), toàn bộ 10 triệu dòng sẽ rơi vào 1 partition duy nhất trên 1 node. Node đó sẽ chết và hệ thống tê liệt.
3. Kết quả mong đợi: Dữ liệu được phân tán đều (Uniform Distribution) trên toàn bộ cluster.
Kỹ thuật Bucketing cho dữ liệu nóng
Giả sử ta muốn lưu "Top 100 sản phẩm bán chạy nhất". Nếu dùng status='hot' làm PK, sẽ gặp lỗi.
Giải pháp: Chia nhỏ "hot" thành nhiều bucket bằng cách thêm một số ngẫu nhiên hoặc số chỉ mục vào PK.
CREATE TABLE hot_products (
category TEXT,
bucket INT, -- Giá trị từ 0 đến 9
product_id UUID,
sales_count INT,
PRIMARY KEY ((category, bucket), product_id)
) WITH CLUSTERING ORDER BY (sales_count DESC);
Để chèn dữ liệu, ứng dụng (Backend) phải tính toán bucket trước. Ví dụ: bucket = hash(product_id) % 10.
INSERT INTO hot_products (category, bucket, product_id, sales_count)
VALUES ('electronics', 3, 550e8400-e29b-41d4-a716-446655440000, 1500);
Khi cần đọc tất cả sản phẩm hot, phải query tất cả 10 bucket (từ 0 đến 9) và gộp kết quả ở client.
Verify thiết kế Schema
Chạy các lệnh sau để kiểm tra cấu trúc bảng đã đúng chưa:
DESCRIBE TABLE monitoring_logs.user_logs;
DESCRIBE TABLE monitoring_logs.severity_logs;
Kiểm tra số lượng partition (sử dụng CQLSH shell hoặc cdc log):
SELECT count(*) FROM system_distributed.keyspace_distributed
WHERE keyspace_name = 'monitoring_logs';
Nếu count trả về 0 hoặc quá ít so với dữ liệu đã chèn, có thể do Partition Key chưa được tính toán đúng hoặc dữ liệu nằm cùng 1 partition (Hotspot).
Điều hướng series:
Mục lục: Series: Triển khai Database phân tán với Apache Cassandra và Ubuntu 24.04
« Phần 3: Mở rộng cluster: Thiết lập mạng và thêm node thứ hai
Phần 5: Quản lý dữ liệu: Chèn, đọc và các mẫu truy vấn hiệu suất cao »