ClickHouse 深度实战:剖析下一代分析型数据库的架构内核
在大模型推理监控、实时业务指标分析、用户行为日志处理等场景中,一个核心诉求是:海量数据上的亚秒级聚合查询。传统行式数据库(如 MySQL、PostgreSQL)在亿级数据上做 GROUP BY 往往需要数十秒甚至分钟级响应,而 ClickHouse 通过彻底重构存储与执行引擎,将这一耗时压缩到毫秒级别。本文将深入剖析 ClickHouse 的架构内核,带你理解它为何能成为 OLAP 领域的事实标准。
一、OLAP 的结构性困境与 ClickHouse 的设计哲学
1.1 行存 vs 列存的本质差异
传统 OLTP 数据库采用行式存储(Row-oriented Storage),一行数据连续落盘。这种模式适合"读取单行多列"的事务处理,但对于分析型查询"读取单列全部值"的场景,行存会加载大量无用列到内存,造成严重的 I/O 浪费。
列式存储(Column-oriented Storage)将同一列的数据连续存放,带来三个关键优势:
- I/O 效率:查询只涉及的列被读取,10 列中查 2 列只需 20% 的 I/O 量
- 压缩率:同一列的数据类型、分布高度相似,压缩比可达 10:1 甚至更高
- 向量化执行友好:连续内存布局天然适配 SIMD 批量处理
行存布局: [行1: id=1, name='Alice', age=30] | [行2: id=2, name='Bob', age=25] | ...
列存布局: id列: [1, 2, ...] | name列: ['Alice', 'Bob', ...] | age列: [30, 25, ...]
1.2 ClickHouse 的设计取舍
ClickHouse 自 2016 年开源以来,做出了几个关键的设计决策:
- 完全舍弃 UPDATE/DELETE:Append-Only 的写入模式,避免了 LSM-Tree 的写放大和锁竞争
- 单机优先架构:单查询首先榨干单机所有 CPU/内存资源,再通过分布式并行扩展
- 不做通用数据库:专注分析场景,牺牲事务能力换取极致的查询速度
- SQL 方言接近标准:降低学习成本,同时扩展分析专用语法
这些取舍使得 ClickHouse 在 Benchmark 中轻松达到每秒数十亿行的扫描吞吐量。
二、MergeTree 存储引擎:LSM 家族的列存变体
2.1 数据分区与排序键
MergeTree 是 ClickHouse 的核心存储引擎,理解它的关键在于 Partition(分区)和 Order By(排序键):
-- 创建一张 AI 推理请求日志表
CREATE TABLE inference_logs (
ts DateTime,
model_id UInt32,
user_id UInt64,
latency_ms Float32,
tokens_input UInt32,
tokens_output UInt32,
status_code UInt16,
region LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(ts) -- 按天分区,查询时可做分区裁剪
ORDER BY (model_id, ts) -- 按模型+时间排序,数据在磁盘上有序存放
SETTINGS index_granularity = 8192; -- 每 8192 行一个索引标记
主键 ORDER BY (model_id, ts) 的设计决定了数据的物理排布方式:同一模型的数据聚集在一起,且按时间递增。查询 WHERE model_id = 42 AND ts > now() - INTERVAL 1 HOUR 时,ClickHouse 可以快速定位数据所在的 granule 区间,跳过无关数据块。
2.2 Part 合并机制
写入时,数据以 Part(数据部分)为单位落盘。每个 Part 是一个独立的有序数据集。后台进程会异步将多个小 Part 合并为大 Part,减少文件碎片:
写入流程:
INSERT data → MemTable (内存排序) → flush → Part_000001
合并流程:
Part_000001 + Part_000002 + Part_000003 → Part_000001_2_3
合并过程中按 ORDER BY 重排序,并生成最终的跳数索引
工程启示:应避免小批量高频写入,建议批量插入(每次 10万-100万行),否则产生大量小 Part 导致合并压力过大。监控 system.parts 中 Active Part 数量是运维的基本功。
2.3 跳数索引(Data Skipping Index)
除了主键的 min-max 索引,ClickHouse 支持多种跳数索引进一步减少扫描量:
-- 添加布隆过滤器索引,加速等值查询
ALTER TABLE inference_logs
ADD INDEX bf_status status_code TYPE bloom_filter(0.01) GRANULARITY 1;
-- 添加 set 索引,适合低基数列的 IN 查询
ALTER TABLE inference_logs
ADD INDEX set_region region TYPE set(100) GRANULARITY 1;
-- 添加 TTL 表达式,自动过期旧数据(省存储)
ALTER TABLE inference_logs
MODIFY TTL ts + INTERVAL 30 DAY;
跳数索引的原理是:每个 granule(默认 8192 行)记录一个索引摘要,查询时先检查索引是否可能包含目标值,若不可能则直接跳过整个 granule。这种"粗筛"机制可以将随机查询的扫描量减少 10-100 倍。
三、向量化执行引擎:榨干每一颗 CPU
3.1 批量生产(Block-based Processing)
传统火山模型(Volcano Model)逐行处理数据,函数调用开销大。ClickHouse 采用 Block 作为最小处理单元,每个 Block 包含若干列的多行数据(通常几万到几十万行):
// 简化的向量化执行概念
struct Block {
ColumnPtr columns[]; // 列数据
size_t rows; // 行数
};
// 聚合操作直接在列数组上做批量计算
void executeSum(Block& block, size_t col_idx) {
auto& col = block.columns[col_idx];
float sum = 0;
// 连续内存遍历,CPU 预取高效
for (size_t i = 0; i < block.rows; i++) {
sum += col->getFloat(i);
}
}
这种批量处理模式有三个加速器:
- CPU 缓存友好:连续内存访问,L1/L2 缓存命中率接近 100%
- 编译器自动向量化:GCC/Clang 可将简单循环编译为 AVX2/AVX-512 指令
- 减少函数调用开销:一次函数调用处理数万行 vs 逐行调用
3.2 聚合操作的算法优化
ClickHouse 为聚合函数实现了大量算法特化:
-- uniqCombined: HyperLogLog 近似去重,误差 ~1.625%,速度比精确 COUNT DISTINCT 快 100x
SELECT model_id, uniqCombined(user_id) AS unique_users
FROM inference_logs
GROUP BY model_id;
-- quantileTDigest: 精确分位数近似,支持任意分位点
SELECT model_id,
quantileTDigest(0.99)(latency_ms) AS p99_latency
FROM inference_logs
GROUP BY model_id;
-- 某些聚合支持增量合并,天然适配分布式查询
-- 例:CountMinSketch、uniqCombined、quantileTDigest 都是可合并的
3.3 内存限制与外部排序
当 GROUP BY 的基数超过内存限制时,ClickHouse 自动触发外部排序(External Sorting),将中间结果溢写到磁盘:
-- 设置单查询最大内存使用(默认 10GB)
SET max_memory_usage = 20000000000; -- 20GB
-- 开启溢出到磁盘,避免 OOM
SET max_bytes_before_external_group_by = 10000000000; -- 10GB 时触发
四、物化视图:实时预计算的利器
4.1 流式聚合管道
ClickHouse 的物化视图(Materialized View)不只是一个视图,它本质上是一个流式聚合管道。数据写入源表时,触发器自动将聚合结果写入目标表:
-- 创建分钟级汇总的物化视图
CREATE MATERIALIZED VIEW inference_minute_stats
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMMDD(minute)
ORDER BY (model_id, minute)
AS SELECT
toStartOfMinute(ts) AS minute,
model_id,
count() AS request_count,
sum(tokens_input) AS total_input_tokens,
sum(tokens_output) AS total_output_tokens,
max(latency_ms) AS max_latency
FROM inference_logs
GROUP BY model_id, minute;
注意这里用的是 SummingMergeTree 而非普通 MergeTree——ClickHouse 在 Part 合并时会对相同排序键的数值列自动求和,这意味着你写入的是明细,存储的是汇总。查询时因为数据已预聚合,响应时间是毫秒级。
4.2 漏斗分析案例
利用 WindowFunnel 函数做用户行为漏斗分析,是 ClickHouse 的经典场景:
-- 分析用户从浏览→加入购物车→下单的转化率
SELECT
level,
count() AS users
FROM (
SELECT
user_id,
windowFunnel(3600)(ts,
event = 'page_view',
event = 'add_to_cart',
event = 'purchase'
) AS level
FROM user_events
WHERE ts >= today() - 7
GROUP BY user_id
)
GROUP BY level
ORDER BY level;
五、高性能基础设施:从单机到分布式
5.1 分布式查询引擎
ClickHouse 的分布式表(Distributed Engine)提供透明的分片查询能力:
-- 在每个节点上创建本地表
CREATE TABLE inference_logs_local ON CLUSTER my_cluster (...)
ENGINE = MergeTree() ...;
-- 创建分布式表,作为查询入口
CREATE TABLE inference_logs_dist ON CLUSTER my_cluster AS inference_logs_local
ENGINE = Distributed(my_cluster, default, inference_logs_local, rand());
查询分布式表时,ClickHouse 会自动将查询下发到各分片,在协调节点做二次聚合。关键是 SELECT 中的聚合操作会被拆分为两阶段:
第一阶段(各分片并行):
本地聚合 → 产生中间结果(可合并的聚合状态)
第二阶段(协调节点):
合并所有分片的聚合状态 → 返回最终结果
5.2 副本与高可用
通过 ReplicatedMergeTree 实现多副本:
CREATE TABLE inference_logs ON CLUSTER my_cluster (...)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/inference_logs', '{replica}')
...;
ClickHouse 使用 ZooKeeper(或 ClickHouse Keeper)协调副本间的: - 写入同步:数据写入一个副本后,通过 ZK 通知其他副本下载 - 主副本选举:自动检测故障并选举新主 - 分布式 DDL:一条建表语句跨所有节点执行
六、生产级部署与性能调优
6.1 关键配置参数
# config.xml 核心调优项
<yandex>
<max_memory_usage>100000000000</max_memory_usage> <!-- 100GB -->
<max_concurrent_queries>100</max_concurrent_queries>
<max_thread_pool_size>128</max_thread_pool_size>
<!-- 压缩编码器选择 -->
<compression>
<case>
<min_part_size>1000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<method>zstd</method> <!-- LZ4 速度快,zstd 压缩率高 -->
</case>
</compression>
</yandex>
6.2 数据生命周期管理
-- Tiered Storage:热数据 SSD + 冷数据 S3/OSS
ALTER TABLE inference_logs
MODIFY SETTING storage_policy = 'tiered';
-- 存储策略定义
<storage_configuration>
<disks>
<s3_disk>
<type>s3</type>
<endpoint>https://oss-cn-beijing.aliyuncs.com/bucket/</endpoint>
</s3_disk>
</disks>
<policies>
<tiered>
<volumes>
<hot><disk>default</disk></hot>
<cold><disk>s3_disk</disk></cold>
</volumes>
</tiered>
</policies>
</storage_configuration>
TTL(Time To Live)结合多级存储,使得 ClickHouse 在成本可控的前提下管理 PB 级数据:
-- 最近 7 天在 SSD,7-30 天迁到对象存储,90 天后自动删
ALTER TABLE inference_logs
MODIFY TTL
ts + INTERVAL 7 DAY TO VOLUME 'cold',
ts + INTERVAL 90 DAY DELETE;
6.3 写入优化最佳实践
-- 1. 批量写入:每次至少 1 万行,推荐 10-100 万行
INSERT INTO inference_logs FORMAT CSV ...;
-- 2. 异步写入(重要!):降低客户端等待,ClickHouse 端批量处理
SET async_insert = 1;
SET wait_for_async_insert = 0; -- 不等待落盘,写入即刻返回
-- 3. 使用 Buffer 引擎解决小写入问题
CREATE TABLE inference_logs_buffer AS inference_logs
ENGINE = Buffer(default, inference_logs, 16, 10, 10, 1000000, 10000000, 100000000);
七、AI 推理监控:ClickHouse 的杀手级场景
7.1 为什么 AI 推理系统需要 OLAP?
大模型生产系统中的监控需求具有典型的 OLAP 特征:
- 写入量大:每秒数十万甚至百万推理请求
- 查询延迟要求高:运维 Dashboard 需要在 1-2 秒内渲染
- 聚合维度多元:按模型、版本、用户、地域、时间切片分析
- 保留周期长:30-90 天的明细数据用于问题回溯
ClickHouse 的列存+向量化+预聚合架构完美匹配这些需求。
7.2 监控面板的核心查询
-- 实时 QPS 与 P99 延迟(Dashboard 刷新用,需亚秒级响应)
SELECT
toStartOfInterval(ts, INTERVAL 1 MINUTE) AS minute,
model_id,
count() / 60 AS qps,
quantileTDigest(0.99)(latency_ms) AS p99,
avg(tokens_output / latency_ms * 1000) AS tokens_per_sec
FROM inference_logs
WHERE ts >= now() - INTERVAL 1 HOUR
GROUP BY minute, model_id
ORDER BY minute DESC;
-- Token 消耗趋势(用于计费分析)
SELECT
toStartOfDay(ts) AS day,
model_id,
sum(tokens_input) AS total_input,
sum(tokens_output) AS total_output,
round(sum(tokens_input) * 0.0015 + sum(tokens_output) * 0.002, 2) AS cost_usd
FROM inference_logs
WHERE ts >= today() - 30
GROUP BY day, model_id;
八、从数据湖走向湖仓一体
ClickHouse 正从纯数仓向"湖仓一体"演进,原生支持查询外部数据源:
-- 直接查询 S3 上的 Parquet 文件
SELECT count()
FROM s3('https://s3.amazonaws.com/bucket/logs/*.parquet', 'access_key', 'secret')
WHERE ts >= '2026-01-01';
-- 查询 Iceberg 表
SELECT * FROM icebergS3('https://host/warehouse/db/inference_logs') LIMIT 10;
-- 与 Spark/Presto 联邦查询
SELECT a.*, b.user_name
FROM clickhouse_table a
JOIN postgresql('pg-host:5432', 'db', 'users', 'user', 'pass') b
ON a.user_id = b.id;
结合 EXPLAIN PIPELINE 分析查询执行计划,可以精确诊断性能瓶颈:
EXPLAIN PIPELINE
SELECT model_id, count()
FROM inference_logs
GROUP BY model_id
ORDER BY count() DESC
LIMIT 10;
输出展示了从存储读取、聚合、排序到返回结果的完整管道,以及各阶段的并行度和内存分配。
总结
ClickHouse 之所以能在 OLAP 领域独占鳌头,核心在于三个工程取舍:列式存储消除 I/O 浪费、向量化执行榨干 CPU、预聚合视图将计算前移。对于 AI 推理监控、日志分析、实时业务分析等需要海量数据快速聚合的场景,ClickHouse 是当前技术栈中的最优解。
但需要注意它的适用边界:单行 UPDATE、高 QPS 的点查、强事务一致性——这些场景应选择其他方案。ClickHouse 不是万能的,但在它擅长的领域,几乎无可替代。
关键参考:ClickHouse 官方 Benchmark 显示,在 SSB(Star Schema Benchmark)1000 倍数据集上,ClickHouse 的查询响应中位数比 Redshift 快 5-10 倍,成本仅为后者的 1/5。

发表评论 取消回复