列式压缩算法深入:LZ4/ZSTD、Delta/Gorilla 编解码与压缩率调优

系统讲解 ClickHouse 列式存储的压缩原理:通用压缩 LZ4/ZSTD 的对比与权衡、专用编解码器(Delta/DoubleDelta/Gorilla)如何针对数据类型优化、压缩率与查询性能的平衡、编解码器选择矩阵(时间戳/数值/字符串/浮点)、压缩率监控与调优实践。

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 原理简介

算法类型特点
LZ4LZ 家族极快解压,压缩率中等
ZSTDLZ + 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 对比

维度LZ4ZSTD
压缩率中等(~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差值收敛
浮点采样(传感器)GorillaXOR 优化
整型计数/IDDelta差值小
低基数枚举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 未用新编码
只看压缩率忽略查询延迟变化
字符串误用 T64T64 适用于窄整数,不适用于字符串

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 存储成本与查询性能的共同支点:编解码器把规律变成压缩率,通用算法把压缩率变成磁盘节省——把这两级做对,同样的硬件能装下多得多的数据。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据库」更多文章

  1. 查询缓存与预热:缓存策略、热点治理与查询加速
  2. 时序分析最佳实践:时间序列建模、降采样与异常检测 SQL
  3. 字典与维度表 JOIN:Dictionaries、dictGet 与星型模型优化