# ClickHouse 入門：MergeTree 與用 S3 做冷熱儲存

> ClickHouse MergeTree 把每次 INSERT 寫成排序好的 part 再背景合併，storage policy 加 TTL 就能把舊資料自動搬到 S3，附 Docker 快速上手。

原文：https://sheng.page/posts/clickhouse-mergetree-and-s3-tiering/ · 發布：2025-07-13 · 作者：Sheng

ClickHouse 是一個 columnar 的 OLAP 資料庫，掃幾億列做聚合快得誇張。這篇講最低限度的上手、MergeTree 幾個有趣的地方，以及怎麼用 AWS S3 把舊資料丟到冷儲存省錢。

## 用 Docker 快速跑起來

最快的方式是 compose，用現成範本（[clickhouse_4.8](https://github.com/yshengliao/docker-compose-templates/tree/main/clickhouse_4.8)，跑 `clickhouse/clickhouse-server:24.8`）：

```bash
git clone https://github.com/yshengliao/docker-compose-templates
cd docker-compose-templates/clickhouse_4.8
docker compose up -d
```

起來後有兩個介面：`8123` 是 HTTP、`9000` 是原生 TCP。連進去下 SQL：

```bash
docker exec -it clickhouse clickhouse-client
# 或走 HTTP 介面
curl 'http://localhost:8123/?query=SELECT%20version()'
```

範本把 `clickhouse-server/` 掛成設定目錄，等一下加 S3 設定就丟這裡。

## MergeTree：parts 與背景合併

MergeTree 是 ClickHouse 的主力引擎，核心行為是：每次 INSERT 會產生一個**獨立、依 primary key 排序好的 part**；背景的 merge 作業再把這些 part 慢慢**合併**成更大的 part。名字 MergeTree 就是這麼來的，概念上接近 LSM-tree。

```sql
CREATE TABLE events
(
    ts       DateTime,
    user_id  UInt64,
    event    LowCardinality(String),
    payload  String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (user_id, ts);
```

`ORDER BY` 是排序鍵，也預設兼任 primary key。MergeTree 家族還有幾個有趣的變體：`ReplacingMergeTree`（合併時依鍵去重）、`SummingMergeTree` / `AggregatingMergeTree`（合併時就把同鍵的數值加總 / 聚合）。把「合併」這個動作賦予語意，是 ClickHouse 很聰明的地方。

## 稀疏主鍵索引

ClickHouse 的 primary index 是**稀疏的**：它每 `index_granularity`（預設 8192）列為一個 granule，不會每列建一筆索引，只記每個 granule 的起點。好處是即使幾十億列，索引也小到能整個放進記憶體。

查詢時若條件落在 `ORDER BY` 的前綴上，ClickHouse 直接跳過不相干的 granule（data skipping），只讀需要的區塊。所以 `ORDER BY` 怎麼挑，幾乎決定了查詢快不快，把最常拿來過濾的欄位放前面。

## PARTITION BY 與 TTL

`PARTITION BY toYYYYMM(ts)` 把資料按月切。它讓你能整塊操作，本身並非索引：查特定月份時自動 partition pruning、要刪舊資料 `DROP PARTITION` 一秒搞定、跨儲存層搬移也以 partition 為單位。

`TTL` 有兩種用法。一是讓列過期被刪：

```sql
ALTER TABLE events MODIFY TTL ts + INTERVAL 90 DAY;
```

二是更有趣的，時間到就把資料**搬到別的儲存層**，這正是冷熱分層的 hook（下一節）。

## 用 S3 做冷熱暖儲存

熱資料放本機 SSD 查得快，舊資料丟 S3 省錢，ClickHouse 用 `storage_configuration` 把這件事做成宣告式。在 `clickhouse-server/config.d/` 放一個 `storage.xml`（範本已經把這個目錄掛進去了）：

```xml
<clickhouse>
  <storage_configuration>
    <disks>
      <s3_cold>
        <type>s3</type>
        <endpoint>https://YOUR_BUCKET.s3.ap-northeast-1.amazonaws.com/clickhouse/</endpoint>
        <access_key_id>YOUR_KEY</access_key_id>
        <secret_access_key>YOUR_SECRET</secret_access_key>
      </s3_cold>
    </disks>
    <policies>
      <hot_cold>
        <volumes>
          <hot><disk>default</disk></hot>   <!-- 本機 SSD -->
          <cold><disk>s3_cold</disk></cold> <!-- AWS S3 -->
        </volumes>
        <move_factor>0.2</move_factor>
      </hot_cold>
    </policies>
  </storage_configuration>
</clickhouse>
```

`move_factor` 是「某個 volume 剩餘空間低於這個比例（0.2 = 20%）時，自動把資料往下一層搬」。表掛上這個 policy，再用 TTL 指定多久搬到冷層：

```sql
CREATE TABLE events ( /* …同上… */ )
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (user_id, ts)
TTL ts + INTERVAL 30 DAY TO VOLUME 'cold'
SETTINGS storage_policy = 'hot_cold';
```

30 天內的資料留在本機 SSD（熱），之後背景自動搬到 S3（冷），查詢語法完全不變，只是冷資料慢一點。想要三層「熱 / 溫 / 冷」，就在中間多一個本機 HDD 的 volume，TTL 寫成 `… + 7 DAY TO VOLUME 'warm', … + 30 DAY TO VOLUME 'cold'`。


## 補充筆記

- ClickHouse 是 columnar OLAP，MergeTree 是主力引擎：INSERT 產生排序好的 part，背景合併成大 part（概念近 LSM）。
- primary index 稀疏（每 8192 列一個 granule），索引小、可常駐記憶體；`ORDER BY` 的選法決定查詢能跳過多少資料。
- `PARTITION BY` 讓你以分區為單位 pruning、刪除、搬移；MergeTree 家族的 Replacing / Summing / Aggregating 把「合併」變成有語意的動作。
- `TTL ... TO VOLUME` + `storage_configuration` 就能做冷熱分層：熱資料留 SSD、舊資料自動搬 S3，查詢語法不變。
- 用 [clickhouse_4.8](https://github.com/yshengliao/docker-compose-templates/tree/main/clickhouse_4.8) 範本 `docker compose up -d` 最快上手，S3 設定丟進 `clickhouse-server/config.d/` 即生效。

---

> Sheng 撰寫，Claude 協助文字與範例 · 環境 ClickHouse 24.8（LTS）+ Docker · 列入 20260613 blog 翻新計劃，新漆未乾。