Triển khai ClickHouse Server và Client trong Container
Bước đầu tiên là khởi động dịch vụ ClickHouse để làm nơi lưu trữ và xử lý dữ liệu Time-Series. Chúng ta sẽ sử dụng Docker Compose để chạy cả server và client, đảm bảo môi trường cách ly và dễ dàng reset.
Tạo file docker-compose.yml ở thư mục dự án của bạn với nội dung sau để định nghĩa service ClickHouse với port 8123 (HTTP) và 9000 (Native):
version: '3.8'
services:
clickhouse-server:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
volumes:
- clickhouse_data:/var/lib/clickhouse
environment:
- CLICKHOUSE_DB=analytics_db
- CLICKHOUSE_USER=default
- CLICKHOUSE_PASSWORD=
- TZ=Asia/Ho_Chi_Minh
restart: unless-stopped
clickhouse-client:
image: clickhouse/clickhouse-client:latest
container_name: clickhouse-client
depends_on:
- clickhouse-server
command: ["--host", "clickhouse-server", "--query", "SELECT 'ClickHouse is ready'"]
volumes:
clickhouse_data:
Kết quả mong đợi: File cấu hình đã được tạo sẵn sàng để khởi động cả server và client test.
Chạy lệnh khởi động dịch vụ ở chế độ detached (chạy ngầm) và chờ server sẵn sàng:
docker-compose up -d && sleep 5 && docker-compose ps
Kết quả mong đợi: Trạng thái của container clickhouse-server và clickhouse-client hiện lên là Up.
Verify kết quả bằng cách truy vấn trực tiếp vào server qua HTTP API để đảm bảo dịch vụ đáp ứng:
curl "http://localhost:8123/?query=SELECT+version()"
Kết quả mong đợi: Trả về một số phiên bản ClickHouse (ví dụ: 24.3.2.66).
Tạo Database và Bảng Time-Series với MergeTree
Sau khi server hoạt động, ta cần tạo cơ sở dữ liệu và bảng lưu trữ dữ liệu Time-Series. Đối với dữ liệu theo thời gian, Engine MergeTree là lựa chọn tối ưu nhất nhờ khả năng nén cao và phân vùng hiệu quả.
Định nghĩa cấu trúc bảng với các cột metadata (timestamp, metric_name, value) và cấu hình phân vùng theo ngày (TODAY()) cùng khóa sắp xếp theo thời gian để tối ưu truy vấn.
Chạy lệnh SQL sau trong terminal hoặc qua CLI của ClickHouse để tạo database và bảng:
docker exec -it clickhouse-server clickhouse-client --query "
CREATE DATABASE IF NOT EXISTS metrics_db;
CREATE TABLE IF NOT EXISTS metrics_db.time_series_data
(
timestamp DateTime,
metric_name String,
host_id String,
value Float64,
tags String
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (metric_name, timestamp)
TTL timestamp + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;
"
Kết quả mong đợi: ClickHouse trả về Ok. và không báo lỗi cú pháp. Dữ liệu sẽ tự động bị xóa sau 30 ngày để tiết kiệm dung lượng.
Verify kết quả bằng cách liệt kê các bảng trong database mới tạo:
docker exec -it clickhouse-server clickhouse-client --query "SHOW TABLES FROM metrics_db"
Kết quả mong đợi: Hiển thị tên bảng time_series_data.
Kiểm tra cấu trúc bảng để xác nhận Engine và Partition key đã đúng:
docker exec -it clickhouse-server clickhouse-client --query "SHOW CREATE TABLE metrics_db.time_series_data"
Kết quả mong đợi: Hiển thị toàn bộ câu lệnh CREATE TABLE với Engine là MergeTree và ORDER BY (metric_name, timestamp).
Tối ưu hóa Primary Key và Order By cho truy vấn thời gian
Trong ClickHouse, ORDER BY đóng vai trò là Primary Key vật lý. Việc chọn sai trường sắp xếp sẽ làm giảm hiệu năng truy vấn Time-Series nghiêm trọng. Quy tắc vàng là trường thời gian phải nằm cuối cùng trong tuple sắp xếp.
Chúng ta đã cấu hình ORDER BY (metric_name, timestamp) ở bước trước. Điều này có nghĩa:
- Dữ liệu được nhóm theo
metric_name trước (ví dụ: CPU, Memory).
- Trong mỗi nhóm metric, dữ liệu được sắp xếp theo
timestamp.
Cấu hình này cho phép ClickHouse loại bỏ nhanh các phân vùng không chứa metric cần tìm và quét dữ liệu theo thứ tự thời gian liên tục (sequential scan) khi thực hiện query WHERE timestamp BETWEEN ....
Để minh họa hiệu năng, hãy chèn một lượng dữ liệu mẫu và chạy query so sánh:
docker exec -it clickhouse-server clickhouse-client --query "
INSERT INTO metrics_db.time_series_data (timestamp, metric_name, host_id, value, tags)
SELECT
now() - (number % 86400) AS timestamp,
'cpu_usage' AS metric_name,
'host-01' AS host_id,
(number % 100) / 10.0 AS value,
'env=prod' AS tags
FROM numbers(1000000);
"
Kết quả mong đợi: Chèn thành công 1,000,000 dòng dữ liệu giả lập.
Chạy query chọn lọc theo thời gian và metric để kiểm tra hiệu năng:
docker exec -it clickhouse-server clickhouse-client --query "
EXPLAIN PIPELINE
SELECT avg(value)
FROM metrics_db.time_series_data
WHERE metric_name = 'cpu_usage'
AND timestamp >= now() - INTERVAL 1 HOUR;
"
Kết quả mong đợi: Output của EXPLAIN sẽ chỉ ra rằng ClickHouse chỉ đọc các phần (part) dữ liệu cần thiết dựa trên ORDER BY key, không quét toàn bộ bảng.
Cấu hình ClickHouse Kafka Engine để tự động Consume
Bước cuối cùng là biến bảng Time-Series thành một điểm đích (sink) tự động nhận dữ liệu từ Kafka. ClickHouse có Engine Kafka giúp consume message và tự động chèn vào bảng đích mà không cần code middleware phức tạp.
Đầu tiên, tạo bảng Kafka Engine để nhận dữ liệu từ topic time-series-raw. Bảng này chỉ dùng để đọc từ Kafka, không lưu trữ dữ liệu vật lý.
Chạy lệnh tạo bảng Kafka Engine với cấu hình kết nối đến Kafka Broker (giả định Kafka đã chạy ở host network hoặc container mạng chung, địa chỉ kafka-broker:9092):
docker exec -it clickhouse-server clickhouse-client --query "
CREATE TABLE IF NOT EXISTS metrics_db.kafka_consumer
(
timestamp DateTime,
metric_name String,
host_id String,
value Float64,
tags String
)
ENGINE = Kafka()
SETTINGS
kafka_broker_list = 'kafka-broker:9092',
kafka_topic_list = 'time-series-raw',
kafka_group_name = 'clickhouse-consumer-group',
kafka_format = 'JSONEachRow',
kafka_max_block_size = 1048576;
"
Kết quả mong đợi: Bảng kafka_consumer được tạo thành công. Lưu ý: Bạn không thể INSERT vào bảng này, nó chỉ nhận dữ liệu từ Kafka.
Tiếp theo, tạo Materialized View để tự động chuyển dữ liệu từ bảng Kafka Engine vào bảng MergeTree đã tạo ở phần trước. Đây là cơ chế Kafka -> Materialized View -> MergeTree.
docker exec -it clickhouse-server clickhouse-client --query "
CREATE MATERIALIZED VIEW IF NOT EXISTS metrics_db.kafka_to_merge
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (metric_name, timestamp)
SETTINGS index_granularity = 8192
AS SELECT *
FROM metrics_db.kafka_consumer;
"
Kết quả mong đợi: Materialized View được tạo. Khi có dữ liệu mới vào topic Kafka, ClickHouse sẽ đọc từ bảng kafka_consumer và ghi ngay vào bảng vật lý metrics_db.kafka_to_merge (có cấu trúc tương tự bảng Time-Series ban đầu).
Verify kết quả bằng cách kiểm tra trạng thái của Materialized View:
docker exec -it clickhouse-server clickhouse-client --query "
SELECT name, engine_full
FROM system.tables
WHERE database = 'metrics_db'
AND name = 'kafka_to_merge';
"
Kết quả mong đợi: Trả về thông tin bảng với engine là MaterializedView và target table là MergeTree.
Để test luồng dữ liệu thực tế, hãy gửi một message JSON vào Kafka và quan sát dữ liệu xuất hiện trong bảng đích:
docker exec -i kafka-broker kafka-console-producer --topic time-series-raw --property "parse.key=true" --property "key.separator=:" < /dev/stdin
{"timestamp":"2023-10-27 10:00:00","metric_name":"memory_usage","host_id":"host-02","value":45.5,"tags":"env=dev"}
Kết quả mong đợi: Sau vài giây, dữ liệu này sẽ tự động xuất hiện trong bảng metrics_db.kafka_to_merge hoặc bảng time_series_data nếu bạn cấu hình MV chèn vào đó. Kiểm tra bằng:
docker exec -it clickhouse-server clickhouse-client --query "SELECT * FROM metrics_db.kafka_to_merge LIMIT 1"
Kết quả mong đợi: Trả về dòng dữ liệu JSON đã được parse và lưu trữ dưới dạng columnar.
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Data Streaming Time-Series với Kafka, ClickHouse và Vector
« Phần 3: Thiết lập Vector để thu thập và định dạng dữ liệu
Phần 5: Tích hợp luồng dữ liệu: Từ Vector đến ClickHouse qua Kafka »