ClickHouse 23.8 广告归因平台 11 个 MV 同步写放大导致存储 24TB 涨到 67TB + P99 480ms 飙到 38 秒的 14 天复盘:Refreshable MV + AggregatingMergeTree + Projection 6 套修法 + 12 条 OLAP 工程纪律

2026 年 4 月,我们一组 ClickHouse 23.8 + Kafka Engine + 物化视图(MV)的实时数据平台(广告投放归因分析,日入仓事件 84 亿、SQL 查询日均 280 万、20 节点 3 副本集群)在一次业务方新增 11 个 MV 后遭遇了双重雪崩:存储从 24TB 在 14 天内涨到 67TB(同样的原始数据)、报表 P99 从 480ms 飙到 38 秒、Background Merge 队列堆积 4700 个、CPU 长期 95%、磁盘 IO util 持续 100%。诡异之处在于业务事件数没增长,只是新增了几个聚合 MV。最终用 system.parts + system.merges + system.metric_log 三表联查 + EXPLAIN PIPELINE 定位根因是:每个 MV 都在写入路径同步触发 + 11 个 MV 共享同一个源表的 INSERT 形成"写放大 11 倍"+ ReplacingMergeTree 在高频更新下 Merge 永远追不上写入速度 + Background Pool 默认 16 线程不够用 + part 数从 800 涨到 9 万触发 too_many_parts 限速,这是教科书级的"OLAP 写入路径放大 + 后台 Merge 饥饿 + parts 爆炸"组合事故。修复路径是引入异步 MV(Ma

2026 年 4 月,我们一组 ClickHouse 23.8 + Kafka Engine + 物化视图(MV)的实时数据平台(广告投放归因分析,日入仓事件 84 亿、SQL 查询日均 280 万、20 节点 3 副本集群)在一次业务方新增 11 个 MV 后遭遇了双重雪崩:存储从 24TB 在 14 天内涨到 67TB(同样的原始数据)、报表 P99 从 480ms 飙到 38 秒、Background Merge 队列堆积 4700 个、CPU 长期 95%、磁盘 IO util 持续 100%。诡异之处在于业务事件数没增长,只是新增了几个聚合 MV。最终用 system.parts + system.merges + system.metric_log 三表联查 + EXPLAIN PIPELINE 定位根因是:每个 MV 都在写入路径同步触发 + 11 个 MV 共享同一个源表的 INSERT 形成"写放大 11 倍"+ ReplacingMergeTree 在高频更新下 Merge 永远追不上写入速度 + Background Pool 默认 16 线程不够用 + part 数从 800 涨到 9 万触发 too_many_parts 限速,这是教科书级的"OLAP 写入路径放大 + 后台 Merge 饥饿 + parts 爆炸"组合事故。修复路径是引入异步 MV(Materialized View REFRESH)+ ReplacingMergeTree 改 ReplicatedAggregatingMergeTree + Background Pool 调到 64 + 分区策略重设计 + Projection 替代部分 MV,存储压回 28TB,P99 回到 620ms,Background Merge 队列稳定 < 50,但也暴露出团队对 ClickHouse MergeTree 家族 + Background Merge + Write Amplification 的认知盲区。

这次 14 天复盘让我们重新认识了"ClickHouse 不是无限扩展的银弹,它的 LSM-like 架构对写入模式极其敏感"。MergeTree 家族(MergeTree / ReplacingMergeTree / SummingMergeTree / AggregatingMergeTree / VersionedCollapsingMergeTree)各有适用场景,选错就是事故。MV 看似优雅但同步触发的设计让写放大成为常态。这篇文章详细复盘事故时间线、5 个反模式、6 套修法、12 条 ClickHouse 生产工程纪律,以及对 StarRocks 3.x / Doris 2.x / Druid / Pinot 的横向对比与选型建议。

项目背景:ClickHouse 实时广告归因平台规模

维度 规模
业务 广告投放实时归因 + 转化漏斗 + 多维报表
技术栈 ClickHouse 23.8 + Kafka 3.7 + Airflow 2.9 + Superset 4.0
集群 20 节点,3 副本,512GB RAM + 8TB NVMe SSD/节点
日入仓 84 亿事件,峰值 280 万/秒
表数 主表 12 个,MV 47 个,Projection 23 个
查询量 日均 280 万 SQL,P99 SLO 1.5 秒
事故前 存储 24TB,P99 480ms,稳态
事故时 存储 67TB,P99 38 秒,part 数 9 万

事故时间线:从"新增 MV"到"写放大根因定位"

时间 事件
D1 业务方新增 11 个 MV 上线
D3 磁盘告警,存储增长 2.3 倍
D5 查询变慢,Background Merge 队列堆积
D7 P99 飙到 8 秒,parts 数突破 3 万
D9 触发 too_many_parts 限速,INSERT 报错
D11 system.merges 定位 ReplacingMergeTree 重灾
D13 EXPLAIN PIPELINE 定位 MV 写放大根因
D14 5 反模式 + 6 套修法全量上线

反模式 1:MV 同步触发 + 11 个 MV 写放大 11 倍

-- 源表
CREATE TABLE ads_events (
    event_time DateTime,
    user_id UInt64,
    campaign_id UInt32,
    ad_id UInt32,
    cost Decimal(10, 4),
    revenue Decimal(10, 4),
    properties Map(String, String)
) ENGINE = MergeTree
ORDER BY (event_time, user_id)
PARTITION BY toYYYYMM(event_time);

-- 反模式:11 个同步 MV 全挂在 ads_events 上
CREATE MATERIALIZED VIEW mv_campaign_hour TO mt_campaign_hour AS
SELECT toStartOfHour(event_time) AS hour, campaign_id,
       sum(cost) AS cost_sum, sum(revenue) AS rev_sum, count() AS cnt
FROM ads_events
GROUP BY hour, campaign_id;

CREATE MATERIALIZED VIEW mv_ad_hour TO mt_ad_hour AS
SELECT toStartOfHour(event_time) AS hour, ad_id, campaign_id,
       sum(cost) AS cost_sum, sum(revenue) AS rev_sum
FROM ads_events
GROUP BY hour, ad_id, campaign_id;

-- ... 另外 9 个类似的 MV,每个都用 SummingMergeTree 或 AggregatingMergeTree

ClickHouse 的 MV 是同步触发的:每次 INSERT 到源表都会立即执行所有挂在源表上的 MV 的 SELECT + INSERT 到目标表。11 个 MV 意味着1 次源 INSERT → 12 次实际写入(1 源 + 11 MV),写放大 12 倍。当源表 280 万行/秒,实际产生 3360 万行/秒写入,Background Merge 完全跟不上,parts 数指数级增长。

反模式 2:ReplacingMergeTree 在高频更新场景

-- 反模式:用 ReplacingMergeTree 存储"用户行为最新状态"
CREATE TABLE user_latest_state (
    user_id UInt64,
    last_event_time DateTime,
    last_campaign_id UInt32,
    total_cost Decimal(10, 4),
    total_revenue Decimal(10, 4),
    version UInt64
) ENGINE = ReplacingMergeTree(version)
ORDER BY user_id
PARTITION BY toYYYYMM(last_event_time);

-- 每秒 280 万次 upsert,因为每个事件都触发 user_id 维度更新
INSERT INTO user_latest_state SELECT ... FROM ads_events;

ReplacingMergeTree 的"去重"是异步在 Background Merge 期间执行,新写入的 part 都包含全量数据,Merge 后才去重。在 280 万行/秒 + 用户 ID 高基数(5 亿用户)场景下,Merge 永远追不上,实际存储是"理论存储 × 重复倍数",我们这次 ReplacingMergeTree 占了 67TB 中的 41TB,其中 80% 是未去重的历史数据副本。这是ReplacingMergeTree 在 OLAP 场景的典型误用

反模式 3:Background Pool 默认配置不够

<!-- config.xml(出问题版本) -->
<background_pool_size>16</background_pool_size>
<background_merges_mutations_concurrency_ratio>2</background_merges_mutations_concurrency_ratio>
<background_schedule_pool_size>128</background_schedule_pool_size>
<background_fetches_pool_size>8</background_fetches_pool_size>

<!-- merge_tree settings -->
<merge_tree>
    <parts_to_throw_insert>300</parts_to_throw_insert>  <!-- 默认 300,被打爆 -->
    <max_parts_in_total>100000</max_parts_in_total>
    <merge_max_block_size>8192</merge_max_block_size>
</merge_tree>

ClickHouse 默认 background_pool_size=16 是2015 年的默认值,完全跟不上现代硬件(64 核 / NVMe SSD)能力。在 280 万行/秒 + 12 倍写放大场景下,16 线程的 Merge 池每秒处理能力远低于写入速度,parts 数从 800 涨到 9 万,触发 parts_to_throw_insert 限制后 INSERT 直接报错"Too many parts"。

反模式 4:分区策略与查询模式不匹配

-- 反模式:按月分区,但查询 80% 是过去 7 天
PARTITION BY toYYYYMM(event_time)
-- 每个分区 28 亿行,Merge 时间长达数小时
-- 查询过去 7 天必须扫整个月分区

按月分区在"日均事件数 84 亿 + 查询窗口主要是 7 天"场景下完全错配:1) 单分区 28 亿行,Merge 时间长达数小时,期间 IO 满载;2) 查询过去 7 天必须扫整个月分区(28 亿行),即使有 ORDER BY 索引也要读大量无关数据;3) TTL 删除按月生效,粒度太粗。我们后来改成按天分区,Merge 时间从 4 小时降到 18 分钟,查询性能提升 7 倍。

反模式 5:监控指标缺失 parts 数 / Merge 队列 / 写放大

# Grafana 看板(出问题版本)
# 只监控 CPU / 内存 / 磁盘 / QPS / P99
# 完全没监控:
# - system.parts active 数 / inactive 数
# - system.merges 队列长度
# - system.metric_log MergeTask 计数
# - 各表的写放大倍数(写入 bytes / 持久化 bytes)
# - Background Pool 利用率

ClickHouse 的"可观测性核心指标"是 system.parts 数量、system.merges 队列、system.metric_log 各类后台任务计数。如果只盯传统监控,parts 从 800 涨到 9 万的过程完全没预警,只能等到查询变慢 / INSERT 报错才发现,而那时已经无法补救。这是 ClickHouse 团队普遍踩坑的可观测性盲区。

问题本质:ClickHouse MV 同步写放大 + Merge 饥饿的因果链

修法 1:Refreshable MV(异步刷新)替代部分同步 MV

-- ClickHouse 23.12+ 支持 REFRESHABLE MATERIALIZED VIEW
CREATE MATERIALIZED VIEW mv_campaign_hour_refreshable
REFRESH EVERY 5 MINUTE  -- 每 5 分钟全量刷新一次
APPEND  -- 增量模式
TO mt_campaign_hour
AS
SELECT toStartOfHour(event_time) AS hour, campaign_id,
       sum(cost) AS cost_sum, sum(revenue) AS rev_sum, count() AS cnt
FROM ads_events
WHERE event_time >= now() - INTERVAL 10 MINUTE
GROUP BY hour, campaign_id;

-- 优势:不在 INSERT 路径同步触发,无写放大
-- 代价:数据延迟 5 分钟,但对小时级报表完全可接受

Refreshable MV 是 ClickHouse 23.12+ 引入的核心特性,把"写入路径同步触发"改为"定时刷新"。11 个 MV 改 8 个为 Refreshable + 3 个保留同步(实时性要求高的),写放大从 12 倍降到 4 倍,存储增长归零,Background Merge 队列从 4700 降到 120。这是最关键的根因修法。

修法 2:ReplacingMergeTree 改 AggregatingMergeTree + SimpleAggregateFunction

-- 修法:用 AggregatingMergeTree 存"持续累加的最新状态"
CREATE TABLE user_state_v2 (
    user_id UInt64,
    last_event_time SimpleAggregateFunction(max, DateTime),
    total_cost SimpleAggregateFunction(sum, Decimal(18, 4)),
    total_revenue SimpleAggregateFunction(sum, Decimal(18, 4)),
    event_count SimpleAggregateFunction(sum, UInt64)
) ENGINE = AggregatingMergeTree
ORDER BY user_id
PARTITION BY toYYYYMMDD(last_event_time);  -- 按天分区

-- INSERT 时不需要查老值,直接 INSERT 增量
INSERT INTO user_state_v2
SELECT user_id, event_time AS last_event_time,
       cost AS total_cost, revenue AS total_revenue, 1 AS event_count
FROM ads_events;

-- 查询时用 GROUP BY 触发 SimpleAggregateFunction 合并
SELECT user_id,
       max(last_event_time),
       sum(total_cost),
       sum(total_revenue)
FROM user_state_v2
WHERE user_id IN (?, ?, ?)
GROUP BY user_id;

AggregatingMergeTree + SimpleAggregateFunction 的优势:1) 写入时不需要查老值,纯 append-only;2) Background Merge 自动合并相同 ORDER BY key 的行,无需保留全量副本;3) 存储效率提升 4-6 倍;4) 查询时 GROUP BY 自然触发聚合。我们把 user_state 表改造后,存储从 41TB 降到 6.8TB,查询 P99 从 12 秒降到 380ms。

修法 3:Background Pool 扩容 + 关键参数调优

<!-- config.xml(修复后) -->
<background_pool_size>64</background_pool_size>  <!-- 16 → 64 -->
<background_merges_mutations_concurrency_ratio>3</background_merges_mutations_concurrency_ratio>
<background_schedule_pool_size>256</background_schedule_pool_size>
<background_fetches_pool_size>32</background_fetches_pool_size>
<background_move_pool_size>16</background_move_pool_size>

<merge_tree>
    <parts_to_throw_insert>3000</parts_to_throw_insert>  <!-- 300 → 3000 -->
    <parts_to_delay_insert>1500</parts_to_delay_insert>
    <max_parts_in_total>100000</max_parts_in_total>
    <merge_max_block_size>16384</merge_max_block_size>
    <max_bytes_to_merge_at_max_space_in_pool>536870912000</max_bytes_to_merge_at_max_space_in_pool>  <!-- 500GB -->
    <min_bytes_for_wide_part>10485760</min_bytes_for_wide_part>
</merge_tree>

Background Pool 从 16 提到 64,配合 NVMe SSD 64 通道并发,Merge 速度提升 4 倍。parts_to_throw_insert 从 300 提到 3000 缓冲突发写入,但更重要的是从根上降低写放大(修法 1 + 2)。这套配置在 280 万行/秒源 + 4 倍写放大下能长期稳定运行 parts 数 < 800。

修法 4:分区策略改为按天 + TTL 自动归档

-- 主表改造
ALTER TABLE ads_events MODIFY ORDER BY (event_time, campaign_id, user_id);

-- 新表按天分区
CREATE TABLE ads_events_v2 (
    event_time DateTime,
    user_id UInt64,
    campaign_id UInt32,
    ad_id UInt32,
    cost Decimal(10, 4),
    revenue Decimal(10, 4),
    properties Map(String, String)
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/ads_events_v2', '{replica}')
ORDER BY (event_time, campaign_id, user_id)
PARTITION BY toYYYYMMDD(event_time)  -- 按天分区
TTL event_time + INTERVAL 90 DAY DELETE,  -- 90 天后删除
    event_time + INTERVAL 30 DAY TO VOLUME 'cold'  -- 30 天后归档冷盘
SETTINGS storage_policy = 'hot_cold';

-- storage_policy 定义
<policies>
    <hot_cold>
        <volumes>
            <hot><disk>nvme_ssd</disk></hot>
            <cold><disk>sata_hdd</disk></cold>
        </volumes>
    </hot_cold>
</policies>

按天分区 + TTL 冷热分离的多重收益:1) Merge 范围限定单天,时间从 4 小时降到 18 分钟;2) 查询 7 天窗口只扫 7 个分区,而非 1 个月分区;3) 30 天前数据自动 TO VOLUME 冷盘,NVMe 只存热数据;4) 90 天自动删除,无需手动维护。这套策略让单节点 NVMe 占用从 4.8TB 降到 1.4TB,查询性能提升 6-9 倍。

修法 5:Projection 替代部分 MV

-- 不再新增 MV,改用 Projection
ALTER TABLE ads_events_v2 ADD PROJECTION proj_campaign_hour (
    SELECT
        toStartOfHour(event_time),
        campaign_id,
        sum(cost),
        sum(revenue),
        count()
    GROUP BY toStartOfHour(event_time), campaign_id
);

-- ClickHouse 自动维护 projection,查询时自动路由到最匹配的 projection
SELECT toStartOfHour(event_time) AS hour, campaign_id, sum(cost), sum(revenue)
FROM ads_events_v2
WHERE event_time >= today() - 7
GROUP BY hour, campaign_id;
-- EXPLAIN PIPELINE 显示自动选中 proj_campaign_hour,无需扫主表

Projection 是 ClickHouse 21.3+ 引入的特性,本质是"内嵌在表内的预聚合 / 重排序版本",与 MV 的区别是:1) Projection 是主表的一部分,Merge 路径整合,无写放大;2) 查询时优化器自动选择最优 projection;3) 不需要业务方查询时显式指定;4) DDL 操作自动同步。我们把 5 个简单聚合 MV 改成 Projection 后,写入路径完全无放大,查询性能反而更好。

修法 6:全维度可观测性体系

-- 关键监控 SQL(每分钟执行,送到 Prometheus)
-- 1) parts 数监控
SELECT table, sum(active) AS active_parts, sum(1-active) AS inactive_parts
FROM system.parts
WHERE database='ads' AND active=1
GROUP BY table;

-- 2) Merge 队列监控
SELECT database, table, count() AS pending_merges,
       sum(num_parts) AS total_parts_to_merge,
       max(elapsed) AS longest_merge_sec
FROM system.merges
GROUP BY database, table;

-- 3) 写入速率 vs Merge 速率
SELECT event_time,
       sum(value) FILTER (WHERE event='InsertedBytes') / 60 AS insert_bytes_per_sec,
       sum(value) FILTER (WHERE event='MergedRows') / 60 AS merged_rows_per_sec
FROM system.metric_log
WHERE event_time >= now() - INTERVAL 1 HOUR
GROUP BY event_time
ORDER BY event_time;

-- 4) 写放大倍数
SELECT table,
       sum(rows) AS total_rows,
       sum(data_uncompressed_bytes) AS uncompressed,
       sum(data_compressed_bytes) AS compressed
FROM system.parts
WHERE active=1 GROUP BY table;

把这 4 个查询接入 Grafana,核心指标:1) active parts 数 / 表(阈值 1000 告警);2) pending merges 数(阈值 200 告警);3) 写入速率 vs Merge 速率比(阈值 1.5 告警);4) 写放大倍数(阈值 3 告警)。这套监控让我们能在事故发生前 3 小时看到 parts 数缓慢爬升的趋势,而不是事故发生后被动救火。

性能基准:6 套修法效果对比

指标 修复前 修复后
存储总量 67 TB 28 TB
写入路径放大倍数 12× 3.5×
active parts 数 90000 < 800
Background Merge 队列 4700 < 50
报表查询 P99 38 秒 620 ms
单分区 Merge 时间 4 小时 18 分钟
CPU 利用率 95% 52%
磁盘 IO util 100% 34%

我们立的 12 条 ClickHouse 生产工程纪律

  1. MV 优先 Refreshable:除非实时性要求 < 1 分钟,否则用 REFRESH EVERY 模式。
  2. 同步 MV 数量上限 ≤ 3 个/表:超过 3 个写放大开始失控。
  3. ReplacingMergeTree 仅用于低频 upsert:每秒 > 1000 次更新场景必须用 AggregatingMergeTree。
  4. Background Pool ≥ CPU 核数:NVMe SSD 集群推荐 = CPU 核数。
  5. 分区粒度匹配查询窗口:查询窗口 7 天用按天分区,30 天用按周分区。
  6. TTL 自动冷热分层:NVMe 只存 30 天热数据,90 天后归档冷盘或删除。
  7. Projection 优于简单聚合 MV:无写放大 + 自动路由,优先级高于 MV。
  8. parts 数监控阈值 1000/表:超过 1000 立即告警 + 自动 OPTIMIZE FINAL。
  9. Merge 队列监控阈值 200:超过 200 自动扩 Background Pool。
  10. EXPLAIN PIPELINE 必走 Code Review:任何新查询上线前必看 pipeline 是否走 Projection / MV。
  11. 大表 DDL 必在维护窗口:ALTER TABLE MODIFY ORDER BY 等耗时操作不在业务高峰执行。
  12. 容量规划三倍缓冲:磁盘使用率超 70% 立即扩容,避免 Merge 期间空间不足。

引申一:ClickHouse vs StarRocks vs Doris 选型矩阵

维度 ClickHouse StarRocks Doris
架构 Shared-nothing MPP Shared-data + 计算存储分离 Shared-nothing MPP
SQL 兼容 方言强,JOIN 弱 MySQL 兼容好,JOIN 强 MySQL 兼容好
写入吞吐 极强(批量)
实时性 分钟级 秒级 秒级
JOIN 性能 弱(需要预聚合) 强(CBO + Runtime Filter)
运维复杂度
适用场景 日志 / 监控 / 单表大宽表分析 实时数仓 + JOIN 多 实时报表 + Ad-hoc

选型建议:大宽表 + 高吞吐写入 + 单表聚合 → ClickHouse;实时数仓 + 多表 JOIN + 复杂查询 → StarRocks;实时报表 + 兼顾 BI + 国产化 → Doris。我们的归因平台主要是单表聚合 + 高吞吐写入,ClickHouse 是最佳选择;但同公司的 BI 看板系统 2025 年从 ClickHouse 切到 StarRocks,JOIN 性能提升 8 倍,开发效率提升 60%。

引申二:ClickHouse 25.x 新特性预览

ClickHouse 25.x(2025 Q4 / 2026 Q1 发布)有几个关键新特性:1) Native Replicated MergeTree 取消 ZooKeeper 依赖,引入 Keeper 内嵌模式;2) MergeTree V2 引擎,part 元数据优化,Merge 速度提升 40%;3) Vector Search 扩展,支持 HNSW 索引;4) SQL/JSON 路径查询增强;5) PartitionedTablesUDF 简化跨分区写入。我们在测试环境跑了 25.x beta,Merge 速度提升明显,Vector Search 让"广告内容相似度推荐"成为可能,正在评估生产升级路径。

引申三:Kafka Engine + 异步 Materialized View 最佳实践

-- 推荐架构
CREATE TABLE kafka_ads_raw (
    raw_json String
) ENGINE = Kafka
SETTINGS
    kafka_broker_list = 'kafka:9092',
    kafka_topic_list = 'ads_events',
    kafka_group_name = 'ch_consumer_v2',
    kafka_format = 'JSONAsString',
    kafka_num_consumers = 8;

CREATE MATERIALIZED VIEW kafka_to_ads
TO ads_events_v2 AS
SELECT
    JSONExtractInt(raw_json, 'event_time') AS event_time,
    JSONExtractUInt(raw_json, 'user_id') AS user_id,
    JSONExtractUInt(raw_json, 'campaign_id') AS campaign_id,
    JSONExtractDecimal64(raw_json, 'cost', 4) AS cost,
    JSONExtractDecimal64(raw_json, 'revenue', 4) AS revenue
FROM kafka_ads_raw;

Kafka Engine + MV 模式是 ClickHouse 入仓的标准做法,但有几个坑:1) kafka_num_consumers 不要超过 Kafka 分区数;2) MV 内不要做复杂聚合,只做格式转换;3) 出错时 raw_json 保留便于追查;4) max_block_size 调到 65536 提高批量效率。我们这套架构稳定运行 18 个月,日均处理 84 亿事件零丢失。

引申四:ClickHouse on Kubernetes 的 Operator 选型

ClickHouse Operator 选型主要有两个:Altinity ClickHouse Operator(社区主流)、Bytebase ClickHouse Manager(国内)。Altinity Operator 提供 CRD 管理 Cluster / User / PVC / Backup,生态成熟,推荐生产使用。关键配置:1) StatefulSet 必须用 anti-affinity 让副本分散到不同节点;2) PVC storageClass 必须支持 ext4 / XFS,避免 ceph(IOPS 不足);3) podDisruptionBudget 设 maxUnavailable=1;4) liveness probe 用 SELECT 1 而非 TCP。这些配置让我们的 K8s ClickHouse 集群 SLA 达到 99.95%。

引申五:ClickHouse 备份恢复方案

ClickHouse 备份的三套方案:1) BACKUP TABLE ... TO S3 命令(23.3+)原生支持,推荐;2) clickhouse-backup 工具(第三方),支持增量备份 + S3 / GCS;3) 文件级 cp + freeze partition,最底层但最可靠。我们的策略:每天凌晨 BACKUP 到 S3,每周一次全量 clickhouse-backup + freeze partition 三重保险。RTO 4 小时,RPO 1 天,经历过 2 次磁盘故障 + 1 次误删表恢复,全部 4 小时内完成。

引申六:ClickHouse JOIN 性能优化的 5 个套路

ClickHouse 的 JOIN 性能历史上是短板,但通过以下套路能显著优化:1) 小表 JOIN 大表用 dictionary;2) 大表 JOIN 大表用 LEFT JOIN + GLOBAL;3) 多次 JOIN 同一张表用 ARRAY JOIN 预展开;4) 历史维度数据用 SCD2 + asof JOIN;5) 高频 JOIN 改写成预聚合 MV / Projection。我们的报表系统通过这 5 个套路把 JOIN 查询 P99 从 12 秒降到 1.4 秒,业务方再无吐槽。

引申七:ClickHouse Cloud vs 自建集群成本对比

维度 ClickHouse Cloud 自建集群
初始成本 零(按量付费) 高(硬件 + IDC)
运维人力 0.5 人 3-5 人
SLA 99.95% 自负责
性能 计算存储分离,弹性强 本地 NVMe,延迟低
20 节点月成本 约 8 万美元 约 12 万美元(含人力)
定制化

选型建议:中小团队 + 业务波动大 → Cloud;大团队 + 稳定大规模 + 数据合规要求 → 自建。我们这套 20 节点自建集群成本比 Cloud 高 50%,但能定制 Background Pool / 自定义 UDF / 完全数据私有,合规和性能优势明显,长期看 ROI 更优。

引申八:ClickHouse + Iceberg 湖仓一体实践

2025 年 ClickHouse 加强了与 Apache Iceberg 的集成,可以直接读写 Iceberg 表。这开启了"湖仓一体"的新架构:原始数据存 Iceberg(S3 / OSS),热数据进 ClickHouse 加速查询,冷数据归档 Iceberg,通过 Federation Query 跨引擎查询。我们正在试点这套架构,把 90 天前数据归档到 Iceberg,ClickHouse 只存近 90 天热数据,集群规模从 20 节点降到 10 节点,存储成本下降 70%,Ad-hoc 查询通过 Trino 联邦执行。

引申九:ClickHouse 的 UDF 与 ML 集成

ClickHouse 支持三种 UDF:1) SQL UDF(CREATE FUNCTION);2) Executable UDF(Python / Shell 等);3) 内嵌 catboost / ONNX 模型推理。我们的归因模型(GBDT 训练 + 在线推理)直接用 ClickHouse 内嵌 catboost,推理 QPS 单节点 12 万,延迟 P99 8ms,完全无需独立的 ML serving 集群。这套"数据库 + ML 推理一体"架构是 ClickHouse 的独特优势,值得深挖。

引申十:ClickHouse 集群规模化的瓶颈

ClickHouse 集群规模化到 50+ 节点会遇到几个瓶颈:1) ZooKeeper / Keeper 元数据压力大;2) DDL 同步慢(秒级到分钟级);3) Distributed 表 INSERT 延迟高;4) 跨副本 Replication 延迟可能数分钟;5) 监控数据量本身成为负担。我们 20 节点已经接近 ZooKeeper 单实例上限,正在评估升级到 Keeper Raft 集群 + Shard 拆分。规模化是 ClickHouse 的核心挑战之一,需要专门的 SRE 团队投入。

引申十一:ClickHouse 在 OLAP 之外的边界探索

ClickHouse 近两年扩展边界明显:1) Time-Series(替代 Prometheus 长期存储);2) Vector Search(HNSW 索引);3) Geospatial(H3 / S2 索引);4) Log Analytics(替代 Elasticsearch);5) Real-time Recommendation(KV + 聚合)。但要清醒认识到:ClickHouse 的核心优势是"列存 + 向量化 + 高吞吐",非 OLAP 场景不要硬上。我们的实践是日志走 ClickHouse、Time-Series 走 VictoriaMetrics、Vector Search 走 Qdrant,各司其职。

引申十二:OLAP 工程师的成长路径

OLAP 工程师的成长可以分四阶段:1) 入门:理解列存 vs 行存、MergeTree 家族选型、基本 SQL 优化;2) 进阶:掌握 Background Merge 机制、分区与 ORDER BY 设计、MV / Projection 选型;3) 高级:能调优 Background Pool / 写放大 / 容量规划,定位生产事故;4) 专家:能设计湖仓一体架构、推进 Iceberg / Kafka 集成、主导集群规模化。从入门到专家通常需要 24-36 个月,每个阶段都会踩坑积累经验,这是 OLAP 工程师值得长期投入的成长曲线。

引申十三:OLAP + AI 时代的数据栈演化

2026 年 OLAP + AI 融合趋势明显:1) Text-to-SQL 让业务方直接用自然语言查 ClickHouse;2) RAG 系统用 ClickHouse 存 embedding + 元数据;3) AI Agent 自动生成监控指标 + 异常根因分析;4) AutoML 自动选 MergeTree 引擎 + ORDER BY。我们正在试点 Claude + ClickHouse 的 Text-to-SQL 系统,业务方满意度从 6 分提升到 8.5 分,DBA 工作量下降 40%。这是 OLAP 工程师需要主动拥抱的新工作模式,而不是被 AI 取代,真正的杠杆是"AI 辅助 + 工程师把控架构与可观测性"。

决策树:ClickHouse 表引擎与 MV 选型路径

引申十四:OLAP 工程师面对生产事故的快速诊断框架

当 ClickHouse 出现 P99 飙升 / Background Merge 队列堆积 / parts 爆炸时,建议按以下顺序排查:第一步看 system.parts 统计各表 active parts 数;第二步看 system.merges 队列长度 + 最久 Merge 时间;第三步看 system.metric_log 写入速率 vs Merge 速率;第四步看 system.processes 当前慢查询 + memory_usage;第五步用 EXPLAIN PIPELINE 看具体查询路径。这套五步法在过去 2 年帮我们定位过 15 次类似故障,平均定位时间从 2 天压缩到 4 小时。"系统表熟练度"是 ClickHouse 工程师的核心能力,值得花专门时间研究 system.* 的每一张表,这是从"会用 ClickHouse"到"精通 ClickHouse"的关键转折点。

引申十五:ClickHouse 与传统数仓(Hive / Spark)的演进对比

过去十年大数据栈经历了 Hive(T+1 离线) → Spark(分钟级) → ClickHouse / StarRocks(秒级实时)的演进。每一代的核心驱动力是"用户对查询响应时间的耐心阈值不断降低":Hive 时代用户能等 30 分钟,Spark 时代能等 5 分钟,ClickHouse 时代必须 < 5 秒,2026 年的实时 BI 用户期望 < 1 秒。这种演进不只是性能提升,更是计算模式从"批 batch"到"流 stream"再到"持续计算 continuous"的范式转变。ClickHouse 的 Refreshable MV + Projection + Live View 都是这种持续计算思想的体现。未来五年,我们预期"AI Agent 驱动的自动查询优化 + 自适应 schema 演化"会成为数据平台的核心能力,工程师的角色从"写 SQL"演化为"设计数据契约 + 把控 AI 产出质量",这是数据工程师必须主动拥抱的趋势。

引申十六:ClickHouse 在跨地域多活场景的实践

跨地域多活对 ClickHouse 是巨大挑战,因为 ReplicatedMergeTree 依赖 ZooKeeper / Keeper 强一致同步,跨地域延迟会让写入吞吐暴跌。我们的实践是:1) 每地域独立集群 + 各自 Keeper,通过 Kafka 跨地域复制原始数据;2) 查询路由按用户归属地域;3) 全局聚合表用 Refreshable MV 每 10 分钟跨地域汇总;4) 灾备地域用冷备 + 异步追赶,RPO 5 分钟、RTO 30 分钟。这套架构稳定运行 14 个月,经历过 1 次主地域整体故障 28 分钟切换备地域,业务影响降到最低。跨地域 ClickHouse 是高阶能力,需要专门的架构设计 + 反复演练。

引申十七:ClickHouse 工程师的工具栈与高效工作流

资深 ClickHouse 工程师的标配工具栈包括:1) clickhouse-client 命令行(基础);2) DBeaver / DataGrip(SQL 开发);3) clickhouse-keeper-status 监控 Keeper 状态;4) ch-bench 压测;5) clickhouse-flamegraph 性能火焰图;6) altinity-clickhouse-operator(K8s 部署);7) chproxy 做请求路由 + 限流;8) ClickHouse Visualizer 看 MergeTree 内部结构。这套工具栈在过去 3 年帮我们快速定位过几十次生产事故,每位 ClickHouse 工程师都值得花一个月时间逐一上手,把工具熟练度提升到肌肉记忆,这是从"会查 ClickHouse"到"精通 ClickHouse 生产环境"的关键投入,也是大型数据平台长期稳定运行的人力资本基础。

引申十八:ClickHouse 社区演进的关键节点观察

ClickHouse 的版本演进有几个里程碑值得每位工程师留意:20.x 引入分布式 DDL on cluster、21.x Projection、22.x Refreshable MV、23.x Native Keeper 取代 ZooKeeper、24.x Native Replicated 自治、25.x MergeTree V2 + Vector Search。基本每个版本都有重大新特性能从根本上简化生产架构,建议团队每年至少升级一次大版本,保持与社区同步,这是 ClickHouse 工程师对自己技术栈持续投入与对企业数据基础设施长期负责的基本态度。

总结

这次 14 天事故复盘,核心教训是"OLAP 系统的优雅 SQL 接口背后,是工程师必须主动建立的写入路径意识"。ClickHouse 把"声明式 SQL + 自动优化"做得太好,以至于业务方写 MV 时根本不思考"每条 INSERT 会触发多少次实际写入"。修复路径不是回到 PostgreSQL 这类 OLTP,而是补足 Refreshable MV + AggregatingMergeTree + Background Pool 扩容 + 分区策略重设计 + Projection + 全维度可观测性六层防护,让 ClickHouse 真正进入"千亿级数据生产可靠"的成熟阶段。

更要紧的是,我们要意识到OLAP 与 OLTP 的本质区别在于"写入路径的可控性"。OLTP 数据库的 INSERT 是确定性的一行写入,OLAP 的 INSERT 可能触发 N 倍写放大 + 后台 Merge 链式反应 + 跨副本同步。这要求 OLAP 工程师在每次 SCHEMA 设计、MV 创建、INSERT 优化时都问一句"这会让 Background Merge 队列多长"、"写放大倍数是多少"、"parts 数会涨多快",这是 OLAP 时代必须养成的工程习惯。

最后想说,ClickHouse 走到 2026 年正在成为大数据 OLAP 的事实标准,在广告归因、日志分析、监控聚合、实时报表四大场景占据主流地位。每一位数据工程师都值得深入理解 MergeTree 家族 + Background Merge 机制 + MV / Projection 选型 + 写放大治理,这是数据工程师在 AI + 大数据时代依然能保持核心竞争力的根本依凭,也是企业级数据平台长期稳定运行的工程根基。愿每一位数据工程师都能在 OLAP 与湖仓一体并存的时代找到属于自己的工程美学与写入路径意识,把每一段 ClickHouse SQL 都打磨成既高性能又可观测的现代数据作品,这是技术人对自己职业生涯的真正负责与对数据基础设施深沉的热爱与执着信念,也是我们在 AI 浪潮与数据洪流中保持清醒与定力的内在底色,值得每一位数据工程师用持续的学习与生产实战去守护这份对工程质量的执着追求,在每一次 MergeTree 引擎选型 + MV 设计 + 分区策略调优中都见证自己技术能力的不断成长与对系统稳定性的真正用心。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

Spring Boot 3.4 + Reactor 3.7 实时风控引擎 backpressure 失控导致 5 分钟 16 Pod OOMKilled 的 5 天复盘:onBackpressureBuffer 限额 + 调度器隔离 + R2DBC 反压闭环 6 套修法 + 12 条响应式工程纪律

2026-5-27 2:13:47

技术教程

全球 SaaS 网关 HTTP/3 全量迁移东南亚区域吞吐反而暴跌 52% + P99 110ms 飙到 1.4 秒的 8 天复盘:initial packet 1200 + DPLPMTUD + 0-RTT 严格语义 + connection migration 降级 + GREASE 6 套修法 + 12 条 QUIC 工程纪律

2026-5-27 2:27:48

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索