列式存储的块大小并非越大越好,也不是越小越聪明;它直接决定压缩率与扫描速度的成反比关系,选型必须在两者之间找平衡点。
列式存储块大小多少合适?先看它在压缩率和扫描速度中的角色
列式存储把每一列的数据拆成连续块存放,块大小就是一次读写的最小粒度,这个参数看似简单,却同时左右着两个核心指标:压缩率决定存储成本,扫描速度决定查询耗时,想回答“列式存储块大小多少合适”,就得先弄清它在这两项指标里分别做了什么。
块大小如何改变压缩率
压缩算法靠的是数据内部的重复模式,块越大,同一列里相邻值进入同一个压缩上下文的概率就越高,字典编码、游程编码、Snappy这类算法都能找到更长的重复片段,据Parquet官方文档,默认块大小通常与文件系统块保持一致,这样能减少跨块引用带来的额外开销,但“块大必然压缩率高”是个错觉。
当某列的值基数很高时,过大的块会让字典急剧膨胀,甚至触发字典溢出,编码器只能退回普通二进制存储,行业共识认为,块大小与压缩率的关系曲线在中等大小处会出现拐点,继续调大收益非常有限,反而可能让内存压力上升,相反,块如果很小,每个块内可用的重复模式变短,块头部的元数据开销占比又高,压缩率会明显下滑,多数情况下,把块大小控制在默认值附近,压缩率已经能到理想区间,刻意调大并不会带来惊喜。
块大小如何改变扫描速度
扫描速度要拆成两层看:第一层是IO吞吐,第二层是过滤精度。
- 大块在顺序读取时表现好,尤其机械盘和对象存储上,连续IO比随机IO快得多。
- 小块的优势在于过滤精准,Parquet的页统计信息、ORC的行组索引,都依赖块内数据的min/max值,块越小,统计范围越窄,查询条件可以跳过更多无关数据。
- 但块太小也有代价:块数量暴增后,元数据膨胀,打开文件时读取统计信息的时间会反超节省的扫描时间,在HDFS上还会制造大量小文件,拖累NameNode。
所以扫描速度并不是“越大越快”或“越小越快”,而是看你的查询到底在扫描数据还是在过滤数据,全表聚合类查询,大块更占优;点查和条件过滤多的查询,小块更灵活。
按场景调块大小:从归档冷数据到云上热查询
没有万能配置,只有适不适合当下场景,实际项目中,块大小通常跟着数据温度走。
归档冷数据优先压缩率:调大块
历史日志、订单快照这类数据,写入后基本不再改动,查询频率极低,这时候压缩率就是第一目标,把块调到256MB甚至更大,能更充分地压缩重复值,存储费用明显降低,如果你用的是云对象存储按量付费模式,更小的压缩后体积直接反映在账单上。
热数据查询优先扫描速度:调小块
实时报表、交互式分析这类场景,查询条件往往带时间范围或用户ID过滤,把块调小到几MB甚至更小,让min/max索引覆盖更窄的行区间,系统可以迅速跳过不相干数据,但注意,小块带来的好处只在谓词下推生效时存在,如果查询引擎不支持细粒度裁剪,调小反而损害吞吐,业内专家指出,这是很多人调整块大小后查询变慢的主要原因。
云上数据仓库的取舍:请求数也要算钱
用国内云厂商的列式存储服务时,成本并不只算容量,对象存储既按存储容量计费,也按读写请求次数计费,块太小意味着请求数急剧上升,即使扫描量下降,费用也可能更高,所以在云上优化块大小,建议同时估算容量费用和请求费用,别只看一面。
实操:调整列式存储块大小的三个常用入口
不同引擎的参数名不一样,但思路相通,下面给出最常见的三个配置入口,你可以直接照着试。
Parquet的parquet.block.size
在Spark中写入Parquet时,块大小由parquet.block.size控制,默认值是128MB,调整方法如下:
spark.conf.set("parquet.block.size", 64 1024 1024)
或者使用DataFrame写入选项:
df.write.option("parquet.block.size", 64 1024 1024).parquet("path")
调小到64MB适合过滤性强的查询,调大到256MB适合归档场景,修改后要做一次实际数据测试,因为Parquet的页面压缩还受编码方式影响,块大小只是第一层。
ORC的orc.stripe.size
ORC文件的块叫stripe,默认大小64MB,在Hive或Spark中设置:
SET hive.exec.orc.default.stripe.size=134217728;
调大stripe能压缩得更狠,减少stripe footer的元数据开销;调小则让行组索引更细,ORC与Parquet块大小对比时,常发现ORC对数值列压缩优于Parquet,而Parquet对嵌套结构支持更好,这不是块大小单一因素造成的,而是编码器的整体设计差异。
ClickHouse的index_granularity怎么调
ClickHouse没有传统意义上的文件块,对应概念是颗粒(granule),由index_granularity控制,默认值是8192行,这个参数影响主键索引的标记粒度,而不是字节数,调整方法:
<merge_tree>
<index_granularity>4096</index_granularity>
</merge_tree>
调小颗粒行数,可以让WHERE条件更精准地跳过数据区间,但索引文件会变大,扫描时的标记读取开销也会增加,适合频繁按主键做范围查询的场景;如果主要做大宽表聚合,保持默认更稳妥。
关于列式存储块大小的常见问答
列式存储的块大小设成多少才能同时兼顾压缩率和扫描速度?
先接受一个现实:没有两全其美的值,一般建议从默认值开始,然后压测两种极端:压缩率优先、过滤优先,以Parquet为例,在128MB默认值基础上,分别试64MB和256MB,记录压缩后体积和典型查询耗时,多数情况下,默认值已经是你业务里最均衡的点,因为默认值本身就是针对通用场景调过的。
Parquet的block size太大压缩率会更好吗?
不一定,块增大时压缩率会上升,但存在上限,当列基数高且数据有序性差时,过大的块可能导致字典丢失,压缩率反而下降,过大的块在单次扫描时会占用更多内存缓冲,如果内存不足,系统可能降级为无压缩存储,实际调参时,块大小要配合编码器设置一起看,单纯调大块很难持续带来收益。
为什么我在云数据仓库里调块大小效果不明显?
云上产品经常隐藏底层文件格式参数,例如直接从Parquet文件导入云数仓时,数据文件可能被重写为标准压缩格式,块大小由服务端统一控制,云数仓的扫描计费机制通常依赖谓词下推和文件裁剪,块大小只在裁剪精度足够高时才发挥作用,真正决定扫描量的,是文件裁剪与谓词下推的工作方式,块大小只是其中一环。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/775128.html


