块越大,单列内可参考的连续模式越多,压缩率通常越高,但扫描时容易读入大量无关数据;块越小,扫描粒度越细,压缩率会下降,元数据开销也会上升。
列式存储块大小设置多少合适:先理解块的双重身份
列式存储里的“块”不是一个简单的存储容器,它同时扮演两个角色:压缩的基本单元和扫描的最小读取单位,把这两个角色拆开看,才知道块大小为什么这么拧巴。
块是压缩的基本容器
列式存储之所以压缩率高,核心原因是同一列的数据连续放在一起,字典编码、RLE、Delta、前缀编码这些算法,都依赖相邻数据之间存在重复或递增模式。
- 块越大,单列连续数据越多,字典编码能命中的键值比例越高。
- RLE可以跨更多行合并相同值,压缩收益更明显。
- 小块的字典需要频繁重建,很多重复模式还没展开就被切断了。
以ZSTD、Snappy、LZ4为例,它们对块大小的敏感度并不一样,ZSTD在大块下能利用更长的历史窗口,压缩率提升更明显;LZ4本身定位快速压缩,块大小变化带来的压缩率波动相对小一些。
块是扫描的最小读取单位
查询执行器不会一行一行地判断读不读,而是按块跳过或读取,每个块通常带有min/max统计信息,谓词下推依赖这些信息决定整个块是否可以跳过。
- 块太大时,过滤条件命中的行可能只占块内很小比例,但整个块仍要被读进内存解压。
- 块太小时,过滤精度上去了,但块数量暴增,元数据管理成本、文件索引开销都会上升。
- 扫描引擎在块之间切换本身也有代价,尤其是对象存储或分布式文件系统上,频繁的小块读取会放大延迟。
所以块大小是在“压缩率”和“扫描精度”之间做调节,单纯偏向哪一头,都可能让查询整体变慢。
列式存储压缩率对比:大块与小块的实测逻辑
把不同块大小放在一起比较,不能只看压缩率数字,还要结合扫描延迟和适用场景,下面用一个常见的对比框架来说明。
| 块大小范围 | 压缩率表现 | 扫描表现 | 更适合的场景 |
|---|---|---|---|
| 小快(8MB-64MB) | 较低,字典编码收益有限 | 点查、小范围过滤较灵敏 | 日志检索、用户画像点查 |
| 中等块(128MB-256MB) | 较高,多数压缩算法进入甜区 | 聚合扫描与过滤扫描较均衡 | 通用OLAP分析、报表看板 |
| 大块(512MB以上) | 多数情况下最高,但提升趋缓 | 全量扫描友好,过滤扫描易读入噪声 | 大表全量跑批、顺序扫描任务 |
从表中能看出来,大块压缩率多数情况下更高,但不是线性增长,超过一定阈限后,可捕获的新增重复模式有限,解压CPU开销却会继续上升。
为什么大块不总是扫描更慢
顺序扫描场景下,大块反而更友好,连续读大块能减少随机IO和元数据切换,吞吐量更高,真正让大块变慢的是带过滤条件的扫描,因为块内有效数据占比可能很低,判断块大小是否合理,可以看一个简单指标:扫描行数与返回行数的比例,比例越高,说明过滤选择性越强,通常更适合偏小块。
列式存储扫描慢怎么优化:从块大小入手的排查路径
查询变慢的原因很多,但如果存储格式本身块大小和查询粒度错配,第一步就应该先排查块设置。
先看块大小是否和查询模式错配
- 聚合多列、大范围扫描的OLAP查询,偏大块通常吞吐更高。
- 单行点查、小范围时间过滤、用户ID过滤,偏小块能避免大量无效读取。
- 使用Parquet时,可以执行
parquet-tools meta /path/file.parquet查看每个Row Group的行数和字节数。 - 使用ClickHouse时,可以查询
system.parts里的marks数量,结合index_granularity判断粒度是否合适。
例如一个按小时过滤的监控表,每次查询只取最近5分钟数据,但Parquet Row Group设置为1GB,那每次扫描都会被迫读入大量无关数据,这就是典型的块大小错配。
再调整压缩算法与块大小的组合
压缩算法和块大小不是孤立参数,高压缩率算法配大块,压缩收益高,但解压CPU消耗也大,过滤扫描时可能因为解压开销把IO收益吃光。
- 追求扫描速度,可以用LZ4或Snappy配中等块。
- 追求存储成本,可以用ZSTD配偏大块。
- 混合负载下,多数系统默认参数已经踩在平衡点上,不要盲目调大。
调整前建议用真实查询做基准,找几条代表性SQL,对比调整前后扫描行数、执行时间和CPU占用,再看是否值得改。
杭州数据仓库列式存储优化里的一个常见误区
杭州地区互联网企业密集,数据仓库、BI看板、实时分析需求都很常见,相当一部分数据团队在列式存储优化时会陷入一个误区:把Parquet Row Group或ClickHouse压缩块一味调大,希望用更高压缩率降低存储成本。
很多杭州本地SaaS、电商和游戏公司的查询模式是高频过滤+小范围聚合,一旦块太大,看板接口响应时间可能从几百毫秒涨到几秒,压缩省下的存储费用,往往被查询延迟和机器扩容成本抵消。
更合理的做法是:OLAP通用场景从128MB左右起步,再用查询日志统计扫描行数和返回行数比例,逐步微调,不要直接照搬云厂商默认参数,也不要把单个参数当成性能开关。
实操步骤:调整Parquet Row Group和ClickHouse Granularity
不同列式存储系统里的块参数名称不同,但调整逻辑一致。
Apache Parquet
Parquet的块单位是Row Group,写入时由行数或字节数控制。
- 在PyArrow中写Parquet时,可设置
row_group_size参数,例如pq.write_table(table, file, row_group_size=1000000),表示每个Row Group约100万行。 -
在Spark中,参数名与版本有关,部分版本使用
parquet.block.size,默认在128MB左右。 - 检查已有文件:
parquet-tools meta /path/file.parquet,可以直接看到每个Row Group的行数、字节数和列统计信息。
调参后需要重写数据,已有文件不会自动改变。
ClickHouse
ClickHouse里影响扫描粒度的核心参数是index_granularity和index_granularity_bytes。
index_granularity默认8192行,index_granularity_bytes默认10MB,两者先到先触发。- 压缩块大小由
min_compress_block_size和max_compress_block_size控制,默认64KB到1MB。 - 要提升压缩率,可以增大
max_compress_block_size;要加快点查,可以调小index_granularity。 - 修改表设置:
ALTER TABLE表名 MODIFY SETTING index_granularity=16384,仅对新写入数据生效。
这两个系统的参数调整都不需要改变业务SQL,属于底层存储侧优化,适合在低峰期操作。
Q&A:列式存储块大小与压缩扫描的常见疑问
列式存储块太小会影响压缩吗
会,块太小会让单列内可复用的连续模式变少,字典编码和RLE收益下降,压缩率降低,同时文件元数据和索引数量增加,管理成本上升。
列式存储块大小设置多少合适有没有统一标准
没有统一标准,OLAP聚合扫描通常取128MB到256MB的Row Group,点查和日志检索可以取8MB到64MB,需要结合Parquet、ORC或ClickHouse的具体实现和查询模式调整。
列式存储压缩率对比大块一定更高吗
多数情况下大块压缩率更高,但提升幅度会随块继续增大而趋缓,超过一定阈限后,解压CPU开销上升,反而可能拖慢查询,因此不能只为了压缩率无限调大。
列式存储的块大小没有银弹,它同时牵动压缩率与扫描效率,把块大小当成查询模式与存储成本之间的调节阀,比记住某个固定数值更有用。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/637699.html





