Khái niệm cơ bản về MergeTree và các biến thể
Trong ClickHouse, engine MergeTree là nền tảng cho hầu hết các trường hợp sử dụng dữ liệu lớn, đặc biệt là dữ liệu thực thời (Real-time). Nó hoạt động dựa trên nguyên lý viết theo lô (batch) và hợp nhất (merge) các phần dữ liệu (parts) theo thời gian.
Các biến thể quan trọng cần nắm:
- MergeTree: Cơ bản, giữ nguyên dữ liệu theo thứ tự ghi.
- ReplacingMergeTree: Tự động loại bỏ các bản ghi trùng lặp dựa trên một khóa (key), chỉ giữ lại bản mới nhất trong quá trình merge.
- AggregatingMergeTree: Tự động gộp các bản ghi có cùng key và tính toán tổng hợp (SUM, COUNT, AVG) ngay khi lưu trữ, giảm đáng kể kích thước dữ liệu.
So sánh hành vi lưu trữ
Để hiểu rõ sự khác biệt, hãy xem cách hệ thống xử lý khi ghi hai dòng dữ liệu có cùng user_id nhưng giá trị khác nhau:
ReplacingMergeTree sẽ giữ lại dòng có version cao hơn (thường là timestamp mới nhất). AggregatingMergeTree sẽ cộng dồn giá trị số (ví dụ: view_count) của hai dòng đó thành một dòng duy nhất.
Thiết lập Database và Schema Table
Bước tiếp theo là tạo cơ sở dữ liệu và định nghĩa bảng với các kiểu dữ liệu tối ưu cho ClickHouse. ClickHouse yêu cầu mọi cột phải có kiểu dữ liệu rõ ràng.
1. Tạo Database
Tạo database riêng biệt cho dữ liệu thực thời để dễ quản lý và tách biệt với các dữ liệu khác.
Kết nối vào ClickHouse client và thực thi lệnh:
clickhouse-client -q "CREATE DATABASE IF NOT EXISTS realtime_analytics ENGINE = Atomic;"
Kết quả mong đợi: Lệnh thực thi thành công, không báo lỗi. Database realtime_analytics được tạo với engine Atomic (đảm bảo tính nhất quán giao dịch đơn giản).
2. Định nghĩa Schema cho bảng Logs (ReplacingMergeTree)
Chúng ta sẽ tạo bảng user_activity_logs để lưu nhật ký hoạt động. Bảng này cần xử lý việc cập nhật trạng thái người dùng (ví dụ: đăng nhập lại nhiều lần) nên sử dụng ReplacingMergeTree.
Trước khi tạo bảng, cần xác định các cột:
event_time: Thời gian xảy ra sự kiện (Datetime).
user_id: ID người dùng (UInt64).
event_type: Loại sự kiện (LowCardinality(String) để nén tốt).
version: Dấu thời gian dùng để xác định bản ghi mới nhất (DateTime).
payload: Dữ liệu JSON thô (String).
Thực thi lệnh tạo bảng:
clickhouse-client -q "
CREATE TABLE IF NOT EXISTS realtime_analytics.user_activity_logs
(
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
version DateTime,
payload String
)
ENGINE = ReplacingMergeTree(version)
PARTITION BY toYYYYMM(event_time)
ORDER BY (user_id, event_time)
SETTINGS index_granularity = 8192;
"
Kết quả mong đợi: Bảng được tạo thành công. Engine ReplacingMergeTree được cấu hình với tham số version để xác định bản ghi mới nhất.
3. Định nghĩa Schema cho bảng Metrics (AggregatingMergeTree)
Tạo bảng server_metrics để lưu các chỉ số hiệu năng. Vì dữ liệu này thường được tổng hợp (ví dụ: tổng request/giây), ta dùng AggregatingMergeTree kết hợp với hàm aggregate state.
ClickHouse yêu cầu các cột số phải ở dạng State (ví dụ: SimpleAggregateFunction(sum, UInt64)) khi dùng engine này.
clickhouse-client -q "
CREATE TABLE IF NOT EXISTS realtime_analytics.server_metrics
(
event_time DateTime,
server_id String,
endpoint LowCardinality(String),
request_count SimpleAggregateFunction(sum, UInt64),
total_latency SimpleAggregateFunction(sum, Float64)
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (server_id, endpoint, event_time)
SETTINGS index_granularity = 8192;
"
Kết quả mong đợi: Bảng được tạo với các cột aggregate state. Dữ liệu sẽ được gộp tự động theo server_id và endpoint trong cùng một khoảng thời gian.
Cấu hình Partition Key và Sorting Key
Hiệu suất của ClickHouse phụ thuộc 90% vào cách bạn thiết kế Partition Key và Sorting Key. Sai lầm ở đây sẽ dẫn đến việc truy vấn chậm hoặc không thể xóa dữ liệu hiệu quả.
1. Nguyên tắc Partition Key
Partition Key quyết định cách dữ liệu được chia vào các thư mục vật lý trên đĩa. ClickHouse lưu mỗi partition vào một thư mục riêng.
- Khuyến nghị: Sử dụng hàm
toYYYYMM(date_column) để phân vùng theo tháng. Điều này giúp xóa dữ liệu cũ (DROP PARTITION) rất nhanh chỉ trong vài mili giây.
- Cảnh báo: Không phân vùng theo ngày (
toYYYYMMDD) vì sẽ tạo quá nhiều thư mục, gây tải nặng cho hệ thống file (inode) và làm chậm quá trình merge.
Trong các bảng ở trên, ta đã dùng PARTITION BY toYYYYMM(event_time). Đây là chuẩn mực cho dữ liệu log.
2. Nguyên tắc Sorting Key
Sorting Key quyết định thứ tự sắp xếp dữ liệu trong mỗi partition. Đây là yếu tố quan trọng nhất để tối ưu truy vấn.
- Dữ liệu phải được sắp xếp theo thứ tự bạn thường xuyên query nhất.
- Nếu query thường lọc theo
user_id và time, thì ORDER BY phải là (user_id, event_time).
- Thứ tự trong
ORDER BY rất quan trọng: Cột đầu tiên sẽ được index hóa mạnh nhất.
Trong bảng user_activity_logs, ta đặt ORDER BY (user_id, event_time). Điều này cho phép ClickHouse chỉ quét các phần dữ liệu liên quan đến user_id cụ thể mà không cần scan toàn bộ partition tháng đó.
3. Kiểm tra cấu trúc và hiệu suất
Sau khi tạo bảng, cần verify xem dữ liệu có được phân vùng và sắp xếp đúng ý muốn hay không. Sử dụng hệ thống bảng system để kiểm tra.
Lệnh kiểm tra cấu trúc partition và số lượng phần dữ liệu:
clickhouse-client -q "
SELECT
name,
engine,
partition,
rows,
bytes
FROM system.parts
WHERE table = 'user_activity_logs'
AND database = 'realtime_analytics'
ORDER BY partition;
"
Kết quả mong đợi: Danh sách các partition (ví dụ: 202405, 202406) với số hàng và dung lượng. Nếu dữ liệu mới được thêm vào, bạn sẽ thấy một phần mới (part) thuộc partition hiện tại.
Lệnh kiểm tra chỉ số (Primary Key) đang hoạt động:
clickhouse-client -q "
SELECT
name,
primary_key,
sorting_key
FROM system.tables
WHERE database = 'realtime_analytics'
AND name IN ('user_activity_logs', 'server_metrics');
"
Kết quả mong đợi: Cột sorting_key hiển thị chính xác chuỗi các cột bạn đã khai báo trong ORDER BY (ví dụ: user_id, event_time). Điều này xác nhận ClickHouse đang sắp xếp dữ liệu đúng theo thiết kế.
Verify kết quả thực tế
Để chắc chắn schema hoạt động, hãy chèn dữ liệu mẫu và truy vấn lại.
Chèn dữ liệu mẫu vào bảng Logs:
clickhouse-client -q "
INSERT INTO realtime_analytics.user_activity_logs (event_time, user_id, event_type, version, payload)
VALUES
('2024-05-20 10:00:00', 1001, 'login', '2024-05-20 10:00:00', '{\"ip\": \"192.168.1.1\"}'),
('2024-05-20 10:05:00', 1001, 'logout', '2024-05-20 10:05:00', '{\"ip\": \"192.168.1.1\"}'),
('2024-05-20 10:06:00', 1001, 'login', '2024-05-20 10:06:00', '{\"ip\": \"192.168.1.2\"}');
"
Truy vấn tất cả dữ liệu để kiểm tra:
clickhouse-client -q "SELECT * FROM realtime_analytics.user_activity_logs ORDER BY event_time;"
Kết quả mong đợi: Bạn thấy 3 dòng dữ liệu. Lưu ý rằng ReplacingMergeTree chưa tự động xóa dòng trùng ngay lập tức (chỉ xóa khi merge diễn ra), nên bạn vẫn thấy cả 3 dòng.
Thực hiện lệnh force merge để kích hoạt cơ chế loại bỏ bản cũ (dành cho môi trường test/demo):
clickhouse-client -q "SYSTEM MUTATE realtime_analytics.user_activity_logs FINAL;"
Truy vấn lại:
clickhouse-client -q "SELECT * FROM realtime_analytics.user_activity_logs FINAL ORDER BY event_time;"
Kết quả mong đợi: Nếu dùng từ khóa FINAL, ClickHouse sẽ trả về chỉ 1 dòng duy nhất cho mỗi user_id (trong trường hợp này là dòng login cuối cùng có version cao nhất). Điều này xác nhận engine ReplacingMergeTree đang hoạt động chính xác theo thiết kế.
Điều hướng series:
Mục lục: Series: Triển khai Database Real-time với ClickHouse trên Ubuntu 24.04
« Phần 3: Cấu hình bảo mật và tài khoản người dùng
Phần 5: Triển khai thu thập dữ liệu Real-time từ nguồn bên ngoài »