Mình đã làm việc với cả MySQL, PostgreSQL và MongoDB trong các dự án khác nhau — mỗi cái có điểm mạnh riêng. Nhưng khi team cần phân tích log access 500 triệu dòng mỗi ngày, tất cả đều bắt đầu đuối. Query GROUP BY trên PostgreSQL mất 40 giây, MySQL thì thôi khỏi nói. Lúc đó mình thử ClickHouse và câu query đó chạy xong trong 0.3 giây — từ đó không quay lại nữa.
Cài xong, chạy thử trong 5 phút
Cài ClickHouse trên Ubuntu/Debian:
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg
curl -fsSL 'https://packages.clickhouse.com/rpm/lts/repodata/repomd.xml.key' | sudo gpg --dearmor -o /usr/share/keyrings/clickhouse-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/clickhouse-keyring.gpg] https://packages.clickhouse.com/deb stable main" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
Start service và kết nối ngay:
sudo systemctl start clickhouse-server
sudo systemctl enable clickhouse-server
clickhouse-client
Tạo bảng và insert thử 1 triệu dòng dữ liệu giả:
CREATE TABLE access_logs (
event_time DateTime,
user_id UInt32,
page String,
status_code UInt16,
response_ms UInt32
) ENGINE = MergeTree()
ORDER BY (event_time, user_id);
-- Insert 1 triệu dòng test
INSERT INTO access_logs
SELECT
now() - randBinomial(1000000, 0.5),
rand() % 10000,
arrayElement(['/home', '/api', '/login', '/product'], rand() % 4 + 1),
arrayElement([200, 301, 404, 500], rand() % 4 + 1),
rand() % 2000
FROM numbers(1000000);
Giờ query thử:
SELECT page, count() AS hits, avg(response_ms) AS avg_ms
FROM access_logs
WHERE status_code = 200
GROUP BY page
ORDER BY hits DESC;
Kết quả về trong chưa đầy 1 giây. Đó là ClickHouse.
Tại sao ClickHouse nhanh đến vậy?
Bí quyết nằm ở columnar storage. MySQL/PostgreSQL lưu dữ liệu theo hàng — đọc 1 row là kéo theo toàn bộ cột. ClickHouse làm ngược lại: lưu theo cột, query SELECT avg(response_ms) chỉ đọc đúng cột đó, không đụng vào phần còn lại. Bảng 20 cột mà bạn chỉ query 2-3 cột? I/O giảm ngay 6-10 lần.
Ba thứ khác nữa cộng hưởng vào:
- Vectorized query execution: Không xử lý từng row một mà xử lý theo batch 8192 row mặc định, tận dụng SIMD instruction của CPU — lý do ClickHouse nhanh kể cả khi data đã nằm trong RAM
- Data compression: Mỗi cột nén riêng với codec phù hợp, data thực tế thường nhỏ hơn 5-10 lần so với raw — đọc ít hơn đồng nghĩa nhanh hơn
- MergeTree engine: Tự động sort + merge data trong background, range scan theo time hoặc user_id cực nhanh vì data đã được sắp xếp sẵn
Engine phù hợp cho từng use case
ClickHouse có nhiều table engine, nhưng 90% trường hợp bạn chỉ cần biết 3 cái này:
MergeTree — Engine mặc định cho mọi thứ
CREATE TABLE events (
date Date,
user_id UInt64,
action String,
value Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, user_id)
TTL date + INTERVAL 90 DAY; -- Tự xóa data cũ hơn 90 ngày
PARTITION BY chia data theo tháng — query filter theo tháng chỉ scan partition liên quan, bỏ qua phần còn lại hoàn toàn. TTL là tính năng mình hay dùng cho log data: không cần cron job xóa data cũ nữa, ClickHouse tự lo.
ReplacingMergeTree — Khi cần upsert
CREATE TABLE user_profiles (
user_id UInt64,
name String,
email String,
updated_at DateTime
) ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;
Engine này giữ lại bản ghi có updated_at mới nhất khi có cùng ORDER BY key. Có một điểm cần nhớ: deduplication xảy ra async trong background merge, không xảy ra ngay lập tức. Vì vậy khi query cần thêm FINAL để đảm bảo kết quả chính xác:
SELECT * FROM user_profiles FINAL WHERE user_id = 12345;
SummingMergeTree — Aggregation tự động
CREATE TABLE daily_stats (
date Date,
page String,
views UInt64,
clicks UInt64
) ENGINE = SummingMergeTree()
ORDER BY (date, page);
Mỗi lần merge, ClickHouse tự cộng dồn views và clicks của các row có cùng (date, page) thành một. Thay vì giữ nguyên hàng trăm row nhỏ, bạn chỉ còn 1 row đã tổng hợp sẵn — truy vấn dashboard nhanh hơn đáng kể.
Import data thực tế từ CSV và MySQL
Import từ file CSV:
clickhouse-client --query="INSERT INTO access_logs FORMAT CSVWithNames" < access_logs.csv
Kéo data từ MySQL sang ClickHouse — mình hay dùng cách này khi migrate hoặc sync data phân tích:
-- Tạo bảng nguồn từ MySQL
CREATE TABLE mysql_orders
ENGINE = MySQL('mysql-host:3306', 'mydb', 'orders', 'user', 'password');
-- Copy sang ClickHouse local table
INSERT INTO ch_orders SELECT * FROM mysql_orders WHERE created_at >= '2025-01-01';
Một số tips thực tế khi deploy production
Cấu hình memory limit
Mặc định ClickHouse dùng thoải mái RAM. Trên server shared, cần giới hạn:
<!-- /etc/clickhouse-server/users.d/limits.xml -->
<clickhouse>
<profiles>
<default>
<max_memory_usage>8589934592</max_memory_usage> <!-- 8GB -->
<max_execution_time>60</max_execution_time> <!-- 60 giây -->
</default>
</profiles>
</clickhouse>
Monitoring query chậm
-- Xem query đang chạy
SELECT query_id, elapsed, query
FROM system.processes
ORDER BY elapsed DESC;
-- Xem query log (lịch sử)
SELECT query, query_duration_ms, read_rows, memory_usage
FROM system.query_log
WHERE event_date = today() AND query_duration_ms > 1000
ORDER BY query_duration_ms DESC
LIMIT 20;
HTTP interface — Tiện cho script và monitoring
# Query qua HTTP (port 8123)
curl 'http://localhost:8123/?query=SELECT+count()+FROM+access_logs'
# Hoặc dùng với Python
pip install clickhouse-connect
import clickhouse_connect
client = clickhouse_connect.get_client(host='localhost', port=8123)
result = client.query('SELECT page, count() FROM access_logs GROUP BY page')
for row in result.result_rows:
print(row)
Khi nào KHÔNG dùng ClickHouse
Đây là điểm mình phải nhắc rõ vì hay bị hỏi: ClickHouse không phải OLTP. Nếu workload của bạn là:
- Update/delete nhiều (row-level)
- Transaction ACID
- Query theo primary key đơn lẻ (lookup 1 row)
Thì vẫn dùng PostgreSQL hoặc MySQL. ClickHouse tỏa sáng khi bạn cần aggregate hàng triệu dòng nhanh — analytics dashboard, log analysis, time-series metrics, BI reports.
Stack mình đang chạy thực tế: PostgreSQL cho transactional data, ClickHouse cho analytics — sync qua Kafka hoặc batch ETL mỗi 5 phút. Mỗi cái làm đúng việc của nó, không cái nào thay thế được cái kia.

