ClickHouse — Cơ sở dữ liệu phân tích thời gian thực cho hệ thống quy mô lớn

Posted on: 4/25/2026 10:10:31 AM

Khi hệ thống của bạn bắt đầu xử lý hàng tỷ sự kiện mỗi ngày — từ log truy cập, metric giám sát, đến dữ liệu clickstream — các cơ sở dữ liệu truyền thống bắt đầu "nghẹt thở". PostgreSQL, MySQL hay SQL Server đều được thiết kế cho OLTP (xử lý giao dịch), không phải cho việc scan hàng triệu dòng trong tích tắc. Đó chính là lúc ClickHouse xuất hiện — một cơ sở dữ liệu dạng cột (columnar) mã nguồn mở, được thiết kế từ đầu cho phân tích thời gian thực ở quy mô khổng lồ.

100xNhanh hơn PostgreSQL cho truy vấn phân tích
1B+Dòng dữ liệu insert/giây (cluster)
6xÍt dung lượng lưu trữ hơn so với row-store
<100msLatency truy vấn trên petabyte dữ liệu

1. Tại sao cần cơ sở dữ liệu dạng cột?

Trong cơ sở dữ liệu dạng dòng (row-oriented) truyền thống, mỗi dòng được lưu liền kề trên disk. Khi bạn chạy truy vấn phân tích kiểu SELECT AVG(price) FROM orders WHERE created_at > '2026-01-01', database phải đọc toàn bộ mỗi dòng — bao gồm cả những cột bạn không cần (customer_name, address, phone...) — rồi mới trích xuất cột price.

Cơ sở dữ liệu dạng cột lật ngược logic này: dữ liệu của mỗi cột được lưu liên tiếp. Truy vấn trên chỉ cần đọc 2 cột (pricecreated_at), bỏ qua hoàn toàn phần còn lại. Với bảng 50 cột, bạn chỉ đọc 4% dữ liệu.

graph LR
    subgraph ROW["Row-Oriented Storage"]
        R1["Row 1: id|name|price|date|addr"]
        R2["Row 2: id|name|price|date|addr"]
        R3["Row 3: id|name|price|date|addr"]
    end
    subgraph COL["Column-Oriented Storage"]
        C1["id: 1, 2, 3, 4, 5..."]
        C2["name: A, B, C, D, E..."]
        C3["price: 10, 20, 15, 30..."]
        C4["date: 01, 02, 03, 04..."]
    end
    Q["SELECT AVG(price)
WHERE date > X"] --> COL Q -.->|"Đọc toàn bộ row"| ROW style ROW fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50 style COL fill:#e8f5e9,stroke:#4CAF50,color:#2c3e50 style Q fill:#e94560,stroke:#fff,color:#fff

So sánh cách đọc dữ liệu giữa row-store và column-store

2. ClickHouse hoạt động như thế nào?

2.1. Kiến trúc MergeTree Engine

Trái tim của ClickHouse là MergeTree engine family — engine lưu trữ được thiết kế cho việc ghi dữ liệu cực nhanh và truy vấn phân tích hiệu suất cao. Khi bạn INSERT dữ liệu, ClickHouse không chèn từng dòng một vào B-tree index như database truyền thống. Thay vào đó:

  1. Ghi thành "parts" bất biến (immutable): Mỗi batch INSERT tạo ra một "part" — một thư mục chứa dữ liệu đã sắp xếp theo primary key, mỗi cột là một file riêng.
  2. Merge nền (background merge): ClickHouse liên tục gộp các part nhỏ thành part lớn hơn trong background, giống cách LSM-tree hoạt động — nhưng tối ưu cho dạng cột.
  3. Sparse primary index: Thay vì index mọi dòng, ClickHouse chỉ lưu index cho mỗi 8.192 dòng (granule). Một bảng 1 tỷ dòng chỉ cần ~122.000 entry trong index — đủ nhỏ để fit trong RAM.
graph TD
    INSERT["INSERT batch"] --> P1["Part 1
(sorted, immutable)"] INSERT --> P2["Part 2"] INSERT --> P3["Part 3"] P1 --> MERGE["Background Merge"] P2 --> MERGE P3 --> MERGE MERGE --> BIG["Merged Part
(larger, optimized)"] BIG --> QUERY["Query Engine"] QUERY --> IDX["Sparse Index
(1 entry / 8192 rows)"] IDX --> SCAN["Column Scan
(chỉ đọc cột cần thiết)"] style INSERT fill:#e94560,stroke:#fff,color:#fff style MERGE fill:#2c3e50,stroke:#fff,color:#fff style BIG fill:#16213e,stroke:#fff,color:#fff style QUERY fill:#4CAF50,stroke:#fff,color:#fff style IDX fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style SCAN fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Luồng dữ liệu trong MergeTree Engine

2.2. Vectorized Query Execution

ClickHouse xử lý dữ liệu theo batch (chunk) thay vì từng dòng một — kỹ thuật được gọi là vectorized execution, lấy cảm hứng từ mô hình MonetDB/X100. Mỗi operator trong query pipeline nhận vào một block gồm hàng nghìn dòng, áp dụng phép tính lên toàn bộ block cùng lúc, tận dụng tối đa SIMD instructions (Single Instruction, Multiple Data) của CPU hiện đại.

Tại sao vectorized execution nhanh hơn?

Trong mô hình Volcano (row-at-a-time) truyền thống, mỗi dòng phải đi qua chuỗi virtual function calls. Với 1 tỷ dòng, đó là 1 tỷ lần gọi hàm ảo. Vectorized execution giảm xuống chỉ còn ~122.000 lần gọi (1 tỷ / 8.192) — giảm 99.99% overhead. Kết hợp với SIMD, CPU có thể xử lý 4-16 giá trị cùng lúc trong một instruction cycle.

2.3. Nén dữ liệu thông minh

Vì dữ liệu cùng cột có kiểu giống nhau và thường có pattern lặp lại, ClickHouse đạt tỷ lệ nén cực cao:

CodecPhù hợp vớiTỷ lệ nén
LZ4Mặc định, cân bằng tốc độ và nén3-5x
ZSTDDữ liệu cold, ưu tiên dung lượng5-10x
Delta + ZSTDCột timestamp, giá trị tăng dần10-20x
T64Cột integer có range nhỏ8-15x
DoubleDeltaChuỗi thời gian (time-series)15-40x

Một bảng log 1TB trong PostgreSQL có thể chỉ còn 60-150GB trong ClickHouse — tiết kiệm đáng kể chi phí lưu trữ, đặc biệt trên cloud.

3. Các engine phái sinh từ MergeTree

ClickHouse không chỉ có MergeTree cơ bản — family này bao gồm nhiều biến thể phục vụ các use case khác nhau:

EngineChức năngUse Case
ReplacingMergeTreeTự động deduplicate dòng trùng primary key khi mergeCDC (Change Data Capture), upsert pattern
AggregatingMergeTreePre-aggregate dữ liệu trong quá trình mergeMaterialized views, metric rollup
CollapsingMergeTreeHỗ trợ update/delete logic bằng cách insert dòng "cancel"Dữ liệu có trạng thái thay đổi
SummingMergeTreeTự cộng dồn giá trị cột numeric khi mergeCounter, revenue aggregation
VersionedCollapsingMergeTreeNhư Collapsing nhưng hỗ trợ insert không theo thứ tựDistributed ingestion không đảm bảo order

4. Thực hành: Thiết kế schema cho hệ thống log analytics

Giả sử bạn đang xây dựng hệ thống giám sát API với hàng trăm triệu request/ngày. Đây là cách thiết kế schema tối ưu trong ClickHouse:

CREATE TABLE api_logs
(
    timestamp DateTime64(3),
    date Date DEFAULT toDate(timestamp),
    service LowCardinality(String),
    endpoint LowCardinality(String),
    method Enum8('GET' = 1, 'POST' = 2, 'PUT' = 3, 'DELETE' = 4, 'PATCH' = 5),
    status_code UInt16,
    response_time_ms UInt32,
    request_size UInt32,
    response_size UInt32,
    user_id UInt64,
    trace_id UUID,
    error_message Nullable(String),
    tags Map(String, String)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (service, endpoint, timestamp)
TTL date + INTERVAL 90 DAY DELETE
SETTINGS index_granularity = 8192;

Những nguyên tắc thiết kế quan trọng

  • LowCardinality: Dùng cho cột string có ít giá trị unique (service name, endpoint). Tạo dictionary encoding tự động, giảm 5-10x dung lượng và tăng tốc filter.
  • Enum8/Enum16: Cho cột có tập giá trị cố định (HTTP method). Chỉ lưu 1-2 byte thay vì chuỗi ký tự.
  • ORDER BY: Xếp theo thứ tự từ cardinality thấp → cao. Filter theo service + endpoint sẽ cực nhanh nhờ sparse index.
  • PARTITION BY: Phân vùng theo tháng. TTL tự động xóa dữ liệu cũ hơn 90 ngày — không cần cron job.
  • Map type: Linh hoạt cho metadata không cố định (tags, labels) mà không cần ALTER TABLE.

4.1. Truy vấn phân tích mẫu

-- Top 10 endpoint chậm nhất trong 24h qua
SELECT
    service,
    endpoint,
    quantile(0.95)(response_time_ms) AS p95_ms,
    quantile(0.99)(response_time_ms) AS p99_ms,
    count() AS total_requests,
    countIf(status_code >= 500) AS errors,
    round(countIf(status_code >= 500) * 100.0 / count(), 2) AS error_rate
FROM api_logs
WHERE timestamp >= now() - INTERVAL 24 HOUR
GROUP BY service, endpoint
ORDER BY p95_ms DESC
LIMIT 10;
-- Phân tích traffic pattern theo giờ (heatmap data)
SELECT
    toHour(timestamp) AS hour,
    toDayOfWeek(timestamp) AS day_of_week,
    count() AS requests,
    avg(response_time_ms) AS avg_latency
FROM api_logs
WHERE date >= today() - 7
GROUP BY hour, day_of_week
ORDER BY day_of_week, hour;

5. Materialized Views — Pre-compute để tăng tốc

Một trong những tính năng mạnh nhất của ClickHouse là Materialized Views — cho phép pre-aggregate dữ liệu ngay khi nó được insert, không cần ETL pipeline riêng.

-- Tạo bảng đích cho metric tổng hợp mỗi phút
CREATE TABLE api_metrics_1m
(
    timestamp DateTime,
    service LowCardinality(String),
    endpoint LowCardinality(String),
    requests AggregateFunction(count, UInt64),
    p95_latency AggregateFunction(quantile(0.95), UInt32),
    errors AggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (service, endpoint, timestamp);

-- Materialized view tự động aggregate khi có INSERT
CREATE MATERIALIZED VIEW api_metrics_1m_mv
TO api_metrics_1m AS
SELECT
    toStartOfMinute(timestamp) AS timestamp,
    service,
    endpoint,
    countState() AS requests,
    quantileState(0.95)(response_time_ms) AS p95_latency,
    sumState(if(status_code >= 500, 1, 0)) AS errors
FROM api_logs
GROUP BY timestamp, service, endpoint;

Hiệu quả thực tế

Truy vấn trên bảng gốc api_logs (1 tỷ dòng): ~2-5 giây. Cùng truy vấn trên api_metrics_1m (chỉ chứa dữ liệu đã aggregate): ~10-50ms. Tăng tốc 100-500 lần, không tốn thêm công sức ETL.

6. So sánh ClickHouse với các giải pháp phân tích khác

Tiêu chíClickHousePostgreSQLElasticsearch
Kiểu lưu trữColumnar (OLAP)Row-oriented (OLTP)Inverted Index + Doc Store
Tốc độ truy vấn phân tích⚡ Cực nhanh (~ms trên tỷ dòng)🐢 Chậm (phút-giờ trên tỷ dòng)🔄 Trung bình (giây-phút)
Tốc độ INSERTHàng triệu dòng/giâyHàng nghìn dòng/giâyHàng chục nghìn docs/giây
Nén dữ liệuXuất sắc (6-20x)Trung bình (2-3x)Kém (1.5-3x)
Full-text searchCơ bản (bloom filter)Tốt (tsvector)Xuất sắc (inverted index)
UPDATE/DELETEHạn chế (async mutation)Xuất sắc (MVCC)Tốt (doc-level)
Chi phí vận hànhThấp (ít RAM, ít storage)Trung bìnhCao (RAM-heavy)
SQL supportRất đầy đủĐầy đủ nhấtHạn chế (DSL-first)

7. Kiến trúc triển khai production

Một hệ thống analytics production thường kết hợp ClickHouse với các thành phần khác theo mô hình Kappa architecture:

graph LR
    APP["Application
Services"] --> KAFKA["Apache Kafka
/ Redpanda"] KAFKA --> CH["ClickHouse
Cluster"] CH --> GRAFANA["Grafana
Dashboard"] CH --> API["Analytics API
(.NET / Node.js)"] API --> UI["Frontend
(Vue.js)"] CH --> MV["Materialized
Views"] MV --> CH KAFKA --> REDIS["Redis
(real-time cache)"] REDIS --> API style APP fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style KAFKA fill:#2c3e50,stroke:#fff,color:#fff style CH fill:#e94560,stroke:#fff,color:#fff style GRAFANA fill:#16213e,stroke:#fff,color:#fff style API fill:#4CAF50,stroke:#fff,color:#fff style UI fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style MV fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style REDIS fill:#ff9800,stroke:#fff,color:#fff

Kiến trúc Kappa với ClickHouse làm trung tâm phân tích

Giải thích luồng dữ liệu

  1. Ingestion: Application services gửi event vào Kafka/Redpanda. Không ghi trực tiếp vào ClickHouse để tránh nhiều INSERT nhỏ (anti-pattern).
  2. Batch insert: ClickHouse Kafka engine hoặc consumer service đọc batch từ Kafka, insert theo block lớn (10.000-100.000 dòng/batch) — đây là cách tối ưu nhất.
  3. Pre-aggregation: Materialized Views tự động tổng hợp dữ liệu real-time khi insert, phục vụ dashboard nhanh hơn.
  4. Query layer: Grafana kết nối trực tiếp (plugin chính thức). API layer phục vụ custom analytics cho frontend.
  5. Hot cache: Redis cache kết quả truy vấn thường xuyên, giảm tải cho ClickHouse ở concurrent queries cao.

8. Tối ưu hiệu suất truy vấn

8.1. Nguyên tắc vàng: Đọc ít dữ liệu hơn

ClickHouse nhanh nhất khi nó đọc ít dữ liệu nhất. Mọi tối ưu đều xoay quanh nguyên tắc này:

Checklist tối ưu truy vấn

  • Filter trước, transform sau: Luôn đặt WHERE clause lọc theo cột trong ORDER BY key. ClickHouse dùng sparse index để skip toàn bộ granule không liên quan.
  • Dùng PREWHERE: Cho cột filter nhỏ. ClickHouse đọc cột PREWHERE trước, loại bỏ dòng không cần, rồi mới đọc các cột còn lại — giảm I/O đáng kể.
  • Tránh SELECT *: Chỉ chọn cột cần thiết. Mỗi cột thêm vào = thêm I/O.
  • Projection: Tạo physical sort order phụ cho truy vấn với pattern filter khác PRIMARY KEY.
  • Skip index: Bloom filter index cho cột high-cardinality (trace_id, user_id) không nằm trong ORDER BY.

8.2. Ví dụ PREWHERE và Skip Index

-- Thêm bloom filter index cho trace_id
ALTER TABLE api_logs
ADD INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 4;

-- ClickHouse tự động chuyển filter nhẹ sang PREWHERE
-- Nhưng có thể force nếu cần:
SELECT timestamp, endpoint, response_time_ms, error_message
FROM api_logs
PREWHERE service = 'payment-service'
WHERE timestamp >= now() - INTERVAL 1 HOUR
  AND status_code >= 500;

9. ClickHouse Cloud và SharedMergeTree

ClickHouse Cloud (dịch vụ managed) giới thiệu SharedMergeTree — engine mới tách biệt hoàn toàn storage và compute:

Đặc điểmMergeTree (self-hosted)SharedMergeTree (Cloud)
StorageLocal disk hoặc attached volumeObject storage (S3/GCS)
Compute scalingPhải rebalance data khi thêm nodeThêm stateless node, query ngay
Merge processChạy trên query node, ảnh hưởng latencyChạy trên node riêng, không ảnh hưởng query
Chi phíTrả cho server 24/7Pay-per-query + storage
Phù hợpWorkload ổn định, muốn kiểm soátWorkload biến động, muốn tối ưu chi phí

10. Khi nào KHÔNG nên dùng ClickHouse?

ClickHouse không phải silver bullet

  • Ứng dụng OLTP: Cần UPDATE/DELETE thường xuyên, transaction ACID → dùng PostgreSQL, SQL Server.
  • Full-text search phức tạp: Cần relevance scoring, fuzzy matching → dùng Elasticsearch/OpenSearch.
  • Dữ liệu nhỏ (<100GB): PostgreSQL với index tốt đã đủ nhanh, không cần thêm complexity.
  • JOIN nhiều bảng lớn: ClickHouse hỗ trợ JOIN nhưng không tối ưu bằng RDBMS truyền thống cho complex join patterns.
  • Point lookups: Truy vấn lấy 1 dòng theo primary key — row-store nhanh hơn.

Kết luận

ClickHouse đã chứng minh vị thế là giải pháp hàng đầu cho phân tích dữ liệu thời gian thực ở quy mô lớn. Với kiến trúc columnar, vectorized execution, và hệ thống nén thông minh, nó cho phép xử lý hàng tỷ dòng dữ liệu trong vài mili-giây — điều mà các database truyền thống cần hàng phút hoặc hàng giờ. Khi kết hợp với Kafka để ingestion, Redis để caching, và Grafana để visualization, ClickHouse trở thành trung tâm của một kiến trúc analytics hiện đại, đáp ứng nhu cầu observability và business intelligence ngày càng phức tạp của các hệ thống phần mềm.

Tham khảo