1. 为什么列式存储天生适合压缩
列式存储把同一列的数据连续存放,天然具备高压缩潜力:
行式(一行混存多类型,难压缩)
1, alice, 2026-09-28, 88.5, true, 'VIP'
2, bob, 2026-09-28, 92.1, true, 'STD'
3, carol, 2026-09-28, 85.0, false,'VIP'
→ 每行类型混杂,压缩率有限
列式(同列连续,类型一致,规律性强)
user_id : 1,2,3,4,5,... → 高度有序
region : 华东,华东,华北,华北 → 大量重复
ts : 连续递增时间戳 → Delta 可预测
→ 每列独立压缩,规律被最大化利用
1.1 两阶段压缩
阶段一:专用编解码器(Codec)— 把数据转换为"更易压缩"的形式
例:Delta → 存储相邻差值而非原始值
阶段二:通用压缩算法(LZ4 / ZSTD)— 对转换后字节做熵压缩
一句话总结:列式存储让同列数据集中呈现规律,专用编解码把规律变成"更小数值",通用压缩再榨一次——两级压缩是 ClickHouse 高压缩率的根基。
2. 通用压缩:LZ4 与 ZSTD
2.1 原理简介
| 算法 | 类型 | 特点 |
|---|---|---|
| LZ4 | LZ 家族 | 极快解压,压缩率中等 |
| ZSTD | LZ + FSE | 高压缩率,速度可控(level 1-22) |
两者都是"查找重复字节串 + 用引用替代"的字典类压缩,区别在熵编码与搜索策略。
2.2 默认配置
<clickhouse>
<compression>
<case>
<method>lz4</method> <!-- 默认通用压缩 -->
<min_part_size>1000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<level>1</level>
</case>
<case>
<method>zstd</method> <!-- 大数据 Part 用 zstd -->
<min_part_size>1000000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<level>3</level>
</case>
</compression>
</clickhouse>
默认策略:小 Part 用 LZ4(快),达到阈值的大 Part 用 ZSTD(更省空间)。
2.3 对比
| 维度 | LZ4 | ZSTD |
|---|---|---|
| 压缩率 | 中等(~2-3x) | 高(~3-6x) |
| 写入速度 | 快 | 中 |
| 查询解压 | 极快 | 中 |
| 适用 | 高频写入/热数据 | 冷数据/大 Part |
| level | 固定 | 1-22 可调 |
一句话总结:LZ4 买速度、ZSTD 买空间——ClickHouse 默认按 Part 大小自动切换,把两种算法用在最合适的阶段。
3. 专用编解码器(Codec)
编解码器在通用压缩之前对列数据做语义级转换,大幅提升可压缩性。
3.1 常用编解码器
| Codec | 原理 | 适用数据类型 |
|---|---|---|
Delta | 存相邻差值 | 递增/递变整数 |
DoubleDelta | 存差值之差 | 二次平滑序列 |
Gorilla | 异或 + 前导/尾随零优化 | 浮点、时间戳 |
T64 | 分块转二进制位域 | 窄范围整数枚举 |
LZ4HC/ZSTD | 高压缩比变体 | 任意 |
NONE | 不压缩 | 随机性高的列 |
3.2 Delta 示例
原始:100, 102, 105, 111, 118
Delta: 2, 3, 6, 7 (差值明显更小 → 更易压缩)
CREATE TABLE metrics (
ts DateTime CODEC(DoubleDelta, ZSTD),
cpu Float64 CODEC(Gorilla, ZSTD),
temp Float64 CODEC(Delta, ZSTD),
status UInt8 CODEC(T64, LZ4)
) ENGINE = MergeTree()
ORDER BY ts;
3.3 Gorilla 原理(浮点专用)
浮点数(如 CPU 温度)高位长期相同、低位小幅波动。Gorilla 用异或运算:
XOR = 当前值 ^ 前值
连续值相似 → XOR 多为 0 或低几位不同
→ 只编码"差异位 + 位置",压缩率极高
一句话总结:编解码器是对"列的语义规律"做预转换——时间戳用 Delta、浮点用 Gorilla、枚举用 T64,把规律变成通用压缩更喜欢的形态。
4. 编解码器选择矩阵
4.1 按数据类型
| 数据类型 | 推荐 Codec | 理由 |
|---|---|---|
| 单调递增时间戳 | Delta/DoubleDelta | 差值收敛 |
| 浮点采样(传感器) | Gorilla | XOR 优化 |
| 整型计数/ID | Delta | 差值小 |
| 低基数枚举 | T64 | 位域压缩 |
| 字符串(随机) | LZ4/ZSTD | 通用压缩 |
| 高随机 UUID/哈希 | NONE | 无规律,编码反而浪费 |
4.2 压缩率经验值
| 场景 | 原始→压缩 |
|---|---|
| 时间戳(Delta+ZSTD) | 可达 1:20+ |
| 浮点采样(Gorilla) | 约 1:5~1:15 |
| 低基数字符串(ZSTD) | 约 1:5~1:10 |
| 随机 ID(LZ4) | 约 1:1.5~1:3 |
4.3 如何决定
1. 先建默认表,观察 system.parts 的实际压缩率
2. 对高增长列尝试专用 Codec
3. 对比 size_on_disk 与查询延迟
4. 用最优组合重建表(或 ALTER MODIFY)
一句话总结:编解码器没有银弹——用数据采样 + 实测压缩率来选,而不是猜。
5. 压缩与查询性能的权衡
5.1 双向影响
压缩率高(ZSTD/Delta)
→ 磁盘 IO 少(读更少字节)
→ 但解压 CPU 开销增加
→ 适合:磁盘 IO 是瓶颈、宽列扫描
压缩率低(LZ4/NONE)
→ 解压快、CPU 省
→ 但 IO 更多
→ 适合:CPU 是瓶颈、热数据高频查询
5.2 调优原则
| 场景 | 策略 |
|---|---|
| 磁盘空间紧张 | ZSTD + Delta/Gorilla 提压缩率 |
| 查询延迟敏感 | LZ4 保解压速度 |
| 大宽表扫描 | 高压缩率减少 IO |
| 高频点查 | 压缩率适中,靠索引而非压缩 |
5.3 监控压缩效果
-- 各表压缩率
SELECT
table,
count() AS parts,
sum(rows) AS rows,
sum(bytes_on_disk) AS compressed_bytes,
round(sum(bytes) / sum(bytes_on_disk), 2) AS ratio
FROM system.parts
WHERE active
GROUP BY table
ORDER BY compressed_bytes DESC;
-- 各列压缩详情(表级)
SELECT * FROM system.columns
WHERE table = 'metrics'
ORDER BY bytes_on_disk DESC;
一句话总结:压缩是"空间换 CPU、字节数换解压开销"的连续光谱——瓶颈在 IO 就压狠些,瓶颈在 CPU 就放轻些。
6. 实践:调优一个真实表
6.1 初始表
CREATE TABLE metrics (
ts DateTime,
server String,
cpu Float64,
mem Float64,
status UInt8
) ENGINE = MergeTree()
ORDER BY ts;
6.2 查看基线
SELECT
column, type,
bytes_compressed_on_disk,
bytes_on_disk
FROM system.columns
WHERE table = 'metrics' AND database = 'default';
6.3 应用编解码器
ALTER TABLE metrics
MODIFY COLUMN ts DateTime CODEC(DoubleDelta, ZSTD),
MODIFY COLUMN cpu Float64 CODEC(Gorilla, ZSTD),
MODIFY COLUMN mem Float64 CODEC(Gorilla, ZSTD),
MODIFY COLUMN status UInt8 CODEC(T64, LZ4);
-- 触发合并使新编码生效
OPTIMIZE TABLE metrics FINAL;
6.4 对比结果
| 列 | 修改前(默认 LZ4) | 修改后 | 收益 |
|---|---|---|---|
| ts | 基准 | DoubleDelta+ZSTD | 压缩率提升 3-8x |
| cpu | 基准 | Gorilla+ZSTD | 压缩率提升 2-5x |
| status | 基准 | T64+LZ4 | 进一步压缩 |
一句话总结:调优路径 = 查基线 → 按类型选 Codec → MODIFY → 合并生效 → 对比收益;一次 ALTER 即可完成,无需重建表。
7. 常见陷阱
| 陷阱 | 说明 |
|---|---|
| 对所有列用 ZSTD | 写放大 + 解压 CPU 升高,收益边际递减 |
| 对随机数据用 Delta | 差值无规律,反而增加开销 |
| 忽略合并 | MODIFY 后不合并,旧 Part 未用新编码 |
| 只看压缩率 | 忽略查询延迟变化 |
| 字符串误用 T64 | T64 适用于窄整数,不适用于字符串 |
7.1 验证生效
-- 检查 Part 使用的编码
SELECT
partition,
count() AS parts,
sum(bytes_on_disk) AS bytes
FROM system.parts
WHERE table = 'metrics' AND active
GROUP BY partition;
8. 生产实践清单
| 主题 | 核心结论 |
|---|---|
| 两级压缩 | Codec(语义转换)→ 通用压缩(LZ4/ZSTD) |
| 通用选择 | LZ4 保速、ZSTD 省空间,按 Part 大小切换 |
| 类型选码 | 时间戳 Delta/DoubleDelta、浮点 Gorilla、枚举 T64 |
| 权衡 | IO 瓶颈压狠,CPU 瓶颈放轻 |
| 调优流程 | 基线 → 选 Codec → MODIFY → 合并 → 对比 |
| 监控 | system.parts/system.columns 看压缩率 |
| 误区 | 不全上 ZSTD、不忽略合并、不只看压缩率 |
压缩是 ClickHouse 存储成本与查询性能的共同支点:编解码器把规律变成压缩率,通用算法把压缩率变成磁盘节省——把这两级做对,同样的硬件能装下多得多的数据。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。