1. 压缩整体设计思路拆解
1.1 为什么OLAP数据库非要死磕压缩
先直接说结论:Doris这类OLAP数据库,压缩不是锦上添花,而是命根子。你在Doris里跑查询,本质上是把数据从磁盘或者内存里捞出来算,IO开销往往是最大的瓶颈。数据压缩了,磁盘占用变小、扫描的IO变少、缓存能装下更多数据,链路每个环节都受益。
但如果单纯为了压得狠,选一个最高压缩比的通用算法(比如gzip的极限模式),那查询时解压开销又会拖后腿。所以Doris的压缩设计,核心矛盾就两个:压缩率要够高,解压速度要够快。而且这两个指标还得根据你的实际使用场景做取舍,没有一套配置能通吃所有业务。
有朋友可能问,Doris底层不是有列存吗?列存天然就比行存好压,但还不够。列存只是让同一列的数据聚集在一起,提供了压缩的“素材”,真正压得狠不狠,还要靠列存基础上的编码算法——比如前缀编码、字典编码、ZigZag这些。Doris的压缩体系是“列编码 + 块压缩”两层结构,编码解决的是数据本身的信息冗余,块压缩解决的是字节流层面的符号冗余。
1.2 编码与压缩的双层架构
我画了个很朴素的逻辑链帮大家理解:原始列数据 → 列编码(Prefix/Dict/Bitmap/ZigZag) → 压缩算法(LZ4/ZSTD/Snappy) → 落盘。
第一层的列编码,只存在于列存表里,它根据列的数据分布特点,把整数、字符串、日期这类常见类型做一次“预压缩”。比如整数列里有大量0,那就用BitMap或者RLE(游程编码)思路处理;字符串列有大量共享前缀,就用前缀编码。
第二层的块压缩,类似通用压缩工具做的事,对编码后的字节流再压一遍。Doris默认采用LZ4,兼顾速度和压缩率。如果你的场景里磁盘比较紧张,可以切ZSTD,压缩率明显更高,代价是CPU开销上浮。
这两层必须配合使用。如果只做块压缩,很多列级冗余(比如相同前缀、字段取值集合很小)是压不下去的;如果只做列编码,原始数据里的字节级重复还得不到清理。理解这一点,后面优化才有方向。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心编码与压缩算法实操解析
2.1 每一种编码算法的适用场景与原理
Prefix Encoding(前缀编码)
大概思路是:把数据按字典序排列后,同一数据块内相邻行的公共前缀只存一份,后面的行只记录差异部分。通俗讲,像你存一批用户地址,全国几十个省份开头的地址一堆一模一样的“省市区”,按前缀编码之后,前面相同的部分不用反复写。
这种编码对字符串有序列非常有效,尤其是排序键的前缀列。Doris对STRING类型做了优化,如果你把高基数字符串放在ORDER BY前列,前缀编码效果一般;但低基数且排序良好的列,效果立竿见影。
Dict Encoding(字典编码)
当一个字符串列的取值种类不多(比如状态字段只有success、failed、pending)时,字典编码会把这些枚举值建成一个字典,每行只存一个整数编号。相当于把字符串比较变成了整数比较,压缩和查询速度都受益。
实现时Doris会在数据块内部构建局部字典,不一定需要全局唯一。这就引出一个小技巧:如果你的枚举值很多,但每个数据块内部的取值分布很集中,字典编码依然有不错的效果,因为它按块建字典。
BitMap Encoding与ZigZag Encoding
这两个常配合使用,对整数列特别友好。BitMap适合枚举值少、重复多的列;ZigZag则把有符号整数映射为无符号整数,使得正负数交替出现的数据也能压缩,适合存放差值、增量这类数据。比如你用BIGINT存时间戳差值,负数很多,ZigZag会先把数据转换成更容易压的形式。
