Sổ tay kỹ thuật

ClickHouse – MergeTree, ORDER BY, PARTITION BY, TTL và materialized view

concept#clickhouse#telemetry· cập nhật 2026-11-06

Hiểu cách chọn sorting key, partition, TTL và bảng tổng hợp bằng materialized view khi thiết kế bảng telemetry/log trên ClickHouse.

Sửa trang này
Mục lục

Bảng MergeTree lưu dữ liệu thành các part bất biến: mỗi lần INSERT tạo một part mới, đã sắp xếp theo ORDER BY; nền tảng chạy merge gộp part nhỏ thành part lớn. Bốn quyết định khi tạo bảng ảnh hưởng tới tốc độ query và dung lượng nhiều nhất: ORDER BY, PRIMARY KEY, PARTITION BY, TTL.

Dữ liệu đi qua bảng như thế nào

flowchart LR I["INSERT batch"] --> P1["Part mới (đã sort theo ORDER BY)"] P1 --> M["Merge nền"] M --> P2["Part lớn trong partition"] P2 -->|"TTL hết hạn"| D["Xoá dòng / drop part"] I -->|"trigger"| MV["Materialized view"] MV --> AGG["Bảng tổng hợp (SummingMergeTree)"] Q[SELECT] -->|"primary index bỏ qua granule"| P2

Thành phần

Mệnh đề Vai trò Cách chọn
ORDER BY Thứ tự lưu trên disk, cũng là primary key mặc định Cột hay lọc bằng = và cardinality thấp đứng trước, thời gian đứng sau: (source, name, time)
PRIMARY KEY Index thưa (sparse): mỗi granule (mặc định 8192 dòng) một entry, nằm trong RAM Bỏ qua nếu giống ORDER BY; chỉ khai báo khi muốn index là tiền tố ngắn hơn của ORDER BY
PARTITION BY Chia part theo thư mục logic; merge chỉ diễn ra trong cùng partition Thô: theo ngày toDate(t) hoặc tháng toYYYYMM(t). Không partition theo thiết bị/giờ (gây TOO_MANY_PARTS)
TTL Tự xoá dòng (hoặc chuyển volume, gộp dòng) khi hết hạn TTL time + INTERVAL 90 DAY; kết hợp ttl_only_drop_parts = 1 để drop cả part thay vì viết lại
CODEC / kiểu dữ liệu Nén theo cột LowCardinality(String) cho tên thiết bị/interface, Delta/DoubleDelta cho counter và thời gian

Partition không phải index. Query nhanh là nhờ điều kiện WHERE khớp tiền tố ORDER BY (primary index) và khoảng thời gian (partition pruning). Kiểm tra bằng EXPLAIN indexes = 1 SELECT ....

Bảng counter interface

Cùng schema với phần còn lại của section (source, name, in_octets...):

CREATE DATABASE IF NOT EXISTS telemetry;
 
CREATE TABLE telemetry.interfaces
(
    time        DateTime CODEC(DoubleDelta, ZSTD),
    source      LowCardinality(String),
    name        LowCardinality(String),
    in_octets   UInt64 CODEC(Delta, ZSTD),
    out_octets  UInt64 CODEC(Delta, ZSTD),
    in_errors   UInt64 CODEC(Delta, ZSTD)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(time)
ORDER BY (source, name, time)
TTL time + INTERVAL 90 DAY
SETTINGS ttl_only_drop_parts = 1;
  • Query WHERE source = 'pe1-hni' AND time > now() - INTERVAL 1 HOUR chỉ đọc vài granule.
  • Query chỉ lọc theo name (cột thứ hai) vẫn chạy nhưng kém hiệu quả hơn. Cần lọc nhiều theo cột khác thì thêm projection hoặc data skipping index, đừng đổi ORDER BY vội.
  • ORDER BY không đổi được sau khi tạo (chỉ thêm cột mới vào cuối). Sai thì tạo bảng mới và INSERT INTO ... SELECT.

TTL

-- Đổi TTL bảng có sẵn. Mặc định (materialize_ttl_after_modify = 1) lệnh này
-- còn chạy MATERIALIZE TTL trên toàn bộ dữ liệu cũ: nặng IO với bảng lớn
ALTER TABLE telemetry.interfaces MODIFY TTL time + INTERVAL 30 DAY;
 
-- TTL theo cột: xoá nội dung message sau 7 ngày, giữ dòng
ALTER TABLE logs.syslog MODIFY COLUMN message String TTL toDateTime(timestamp) + INTERVAL 7 DAY;
 
-- Ép áp TTL cho dữ liệu cũ khi đã tắt materialize_ttl_after_modify
ALTER TABLE telemetry.interfaces MATERIALIZE TTL;

TTL được xử lý lúc merge, nên dữ liệu hết hạn có thể còn thêm một thời gian (điều khiển bởi merge_with_ttl_timeout). Biểu thức TTL phải trả về Date hoặc DateTime: cột DateTime64 thì bọc toDateTime(...).

Materialized view: bảng tổng hợp

Materialized view (MV) trong ClickHouse là trigger lúc INSERT: mỗi batch ghi vào bảng nguồn được chạy qua SELECT của MV, kết quả ghi vào bảng đích. Ví dụ đếm syslog theo phút từ bảng logs.syslog của pipeline Vector:

CREATE TABLE logs.syslog_1m
(
    t         DateTime,
    source    LowCardinality(String),
    mnemonic  LowCardinality(String),
    cnt       UInt64
)
ENGINE = SummingMergeTree
ORDER BY (source, mnemonic, t)
TTL t + INTERVAL 1 YEAR;
 
CREATE MATERIALIZED VIEW logs.syslog_1m_mv TO logs.syslog_1m AS
SELECT toStartOfMinute(timestamp) AS t, source, mnemonic, count() AS cnt
FROM logs.syslog
GROUP BY t, source, mnemonic;
 
-- Backfill dữ liệu đã có trước khi tạo MV
INSERT INTO logs.syslog_1m
SELECT toStartOfMinute(timestamp) AS t, source, mnemonic, count() AS cnt
FROM logs.syslog
GROUP BY t, source, mnemonic;
 
-- Query: luôn sum() lại vì merge gộp dòng không tức thời
SELECT t, source, sum(cnt) AS n
FROM logs.syslog_1m
WHERE t > now() - INTERVAL 1 DAY
GROUP BY t, source ORDER BY t;

Với giá trị không cộng dồn được (max, last, quantile) dùng AggregatingMergeTree:

CREATE TABLE telemetry.interfaces_5m
(
    t          DateTime,
    source     LowCardinality(String),
    name       LowCardinality(String),
    in_octets_max  SimpleAggregateFunction(max, UInt64),
    out_octets_max SimpleAggregateFunction(max, UInt64)
)
ENGINE = AggregatingMergeTree
ORDER BY (source, name, t)
TTL t + INTERVAL 2 YEAR;
 
CREATE MATERIALIZED VIEW telemetry.interfaces_5m_mv TO telemetry.interfaces_5m AS
SELECT toStartOfFiveMinutes(time) AS t, source, name,
       max(in_octets) AS in_octets_max, max(out_octets) AS out_octets_max
FROM telemetry.interfaces
GROUP BY t, source, name;

Counter tăng dần nên max mỗi 5 phút tương đương giá trị cuối kỳ. Đặt alias khác tên cột nguồn (in_octets_max), vì max(in_octets) AS in_octets có thể bị ClickHouse thay alias vào chính đối số và báo lỗi aggregate lồng nhau; tính bps từ bảng này giống bảng raw (xem ClickHouse SQL cheatsheet). Hàm phức tạp hơn (quantile, uniq) thì dùng kiểu AggregateFunction(...) với -State khi ghi và -Merge khi đọc.

Lưu ý về MV:

  • Chỉ thấy dữ liệu INSERT mới vào bảng ở FROM; không tự backfill (tránh POPULATE, dễ mất dữ liệu insert trong lúc tạo).
  • Lỗi trong SELECT của MV làm INSERT vào bảng nguồn thất bại.
  • Dùng cú pháp TO bảng_đích để có thể sửa/xoá MV mà không mất dữ liệu tổng hợp.

Khi nào dùng

Nhu cầu Lựa chọn
Log/flow/counter lưu lâu, query SQL tuỳ ý MergeTree + TTL
Dashboard dài ngày chạy chậm trên bảng raw MV ghi vào SummingMergeTree / AggregatingMergeTree
Giữ raw ngắn, bản tổng hợp dài TTL khác nhau cho bảng raw và bảng đích
Lọc nhiều theo cột không nằm đầu ORDER BY Projection hoặc skipping index (bloom_filter, minmax)
Cần bỏ trùng bản ghi gửi lặp ReplacingMergeTree (khử trùng khi merge, query cần FINAL nếu muốn chính xác ngay)

Lỗi vận hành (TOO_MANY_PARTS, disk đầy, mutation kẹt) xem ClickHouse xử lý sự cố.