Câu chuyện xương máu: Khi Cassandra “hụt hơi” trước traffic lớn
Hồi mình làm dự án AdTech, team từng đối mặt với bài toán cực kỳ căng thẳng. Lượng log và event người dùng đổ về theo thời gian thực lên tới hơn 200.000 request mỗi giây. Cassandra lúc đó là lựa chọn số một nhờ khả năng mở rộng ngang. Thế nhưng, đời không như là mơ khi traffic bắt đầu chạm ngưỡng đỉnh điểm.
Dashboard lúc đó đỏ rực. Các node Cassandra thỉnh thoảng lại “khựng” lại vài giây khiến ứng dụng báo lỗi timeout liên tục. Lạ ở chỗ CPU chưa bao giờ quá 50%, nhưng độ trễ P99 (latency) cứ thế vọt lên hàng giây. Nếu bạn từng thức trắng đêm để cứu cụm Database đang “thở oxy”, bạn sẽ hiểu cảm giác bất lực này.
Nguyên nhân sâu xa: Kẻ phá bính mang tên Garbage Collection
Sau khi debug kỹ, mình nhận ra nút thắt cổ chai nằm ở chính nền tảng JVM (Java Virtual Machine). Cassandra chạy trên Java, đồng nghĩa với việc nó phải chung sống với Garbage Collection (GC). Với các hệ thống ghi dữ liệu liên tục (write-heavy), JVM phải dọn dẹp bộ nhớ với tần suất dày đặc.
Những đợt “Stop-the-world” của GC khiến mọi hoạt động đọc/ghi bị đóng băng hoàn toàn. Thực tế cay đắng là chúng ta thường lãng phí khoảng 30% sức mạnh server chỉ để chạy các tác vụ quản lý bộ nhớ của Java. Cấu hình Heap size 32GB hay đổi sang ZGC cũng chỉ là giải pháp tình thế. Cái bóng của JVM vẫn ngăn cản chúng ta khai thác triệt để hiệu năng phần cứng.
Ba giải pháp phổ biến (và tại sao chúng thường thất bại)
Team mình đã thử đủ mọi cách trước khi tìm thấy chân ái:
- Tuning GC: Tốn cả tuần để tinh chỉnh thông số nhưng hiệu quả mang lại không đáng kể. Nó cực kỳ phức tạp và dễ gây side-effect.
- Nâng cấp phần cứng: Thuê máy chủ RAM lớn hơn hoặc dùng ổ NVMe xịn nhất. Cách này đúng kiểu “lấy tiền đè lỗi”, tốn kém nhưng hiệu suất trên mỗi core CPU vẫn dậm chân tại chỗ.
- Dùng Managed Service: Chuyển sang AWS DynamoDB. Tiện thật đấy, nhưng hóa đơn cuối tháng gửi về khiến sếp mình suýt ngất vì chi phí ghi dữ liệu quá đắt đỏ.
ScyllaDB: Kiến trúc Shard-per-core đầy uy lực
ScyllaDB xuất hiện như một bản nâng cấp hoàn hảo: giữ nguyên API của Cassandra nhưng viết lại hoàn toàn bằng C++. Điểm ăn tiền nhất là kiến trúc shard-per-core. Thay vì các luồng tranh chấp tài nguyên, ScyllaDB gán mỗi core CPU quản lý một phần dữ liệu riêng biệt. Không tranh chấp. Không khóa (lockless). Và quan trọng nhất là không còn GC.
Triển khai nhanh ScyllaDB với Docker
Nếu muốn vọc thử, Docker là cách nhanh nhất để bạn có một cụm ScyllaDB trong 30 giây. Mình thường dựng lab nhỏ thế này để benchmark trước khi quyết định chuyển đổi.
# Chạy một instance ScyllaDB duy nhất
docker run --name scylla-node -d scylladb/scylla
# Kiểm tra trạng thái node (đợi khoảng 10 giây để node sẵn sàng)
docker exec -it scylla-node nodetool status
Khi thấy trạng thái UN (Up/Normal), bạn đã có thể truy cập vào database bằng lệnh cqlsh thần thánh.
docker exec -it scylla-node cqlsh
Thiết kế bảng dữ liệu tối ưu với CQL
Cú pháp ScyllaDB (CQL) giống hệt SQL, nhưng bạn cần tư duy theo hướng NoSQL để đạt tốc độ dưới 1ms.
-- Tạo không gian lưu trữ (Keyspace)
CREATE KEYSPACE itfromzero
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};
USE itfromzero;
-- Bảng lưu log với cấu trúc tối ưu cho truy vấn mới nhất
CREATE TABLE user_logs (
user_id uuid,
action_time timestamp,
action_name text,
PRIMARY KEY (user_id, action_time)
) WITH CLUSTERING ORDER BY (action_time DESC);
Mẹo nhỏ: user_id (Partition Key) sẽ giúp ScyllaDB biết dữ liệu nằm ở core nào. Thiết kế khóa này phân tán đều sẽ giúp hệ thống chịu tải cực tốt.
Kinh nghiệm thực tế khi xử lý dữ liệu lớn
Khi cần test hiệu năng với file CSV hàng triệu dòng, mình không bao giờ làm thủ công. Một thủ thuật nhỏ là dùng công cụ chuyển đổi tại toolcraft.app/vi/tools/data/csv-to-json để chuẩn hóa format file mẫu nhanh chóng. Sau đó, dùng Python script để đẩy dữ liệu vào với tốc độ chóng mặt.
import uuid
from datetime import datetime
from cassandra.cluster import Cluster
# Kết nối tới node local
cluster = Cluster(['127.0.0.1'])
session = cluster.connect('itfromzero')
# Insert mẫu một bản ghi
query = "INSERT INTO user_logs (user_id, action_time, action_name) VALUES (%s, %s, %s)"
session.execute(query, (uuid.uuid4(), datetime.now(), 'USER_LOGIN'))
Lời kết
ScyllaDB thực sự là một con quái vật về tốc độ. Nếu bạn đang đau đầu vì Cassandra chậm chạp hoặc chi phí DynamoDB quá cao, hãy thử chuyển sang ScyllaDB. Sự khác biệt về hiệu năng trên mỗi đồng USD bạn bỏ ra sẽ rất đáng kinh ngạc. Tuy nhiên, hãy nhớ: nó chỉ thực sự tỏa sáng trên các dòng CPU nhiều nhân và ổ cứng tốc độ cao thôi nhé!

