StarRocks 中间结果落盘(Spill to Disk)实战指南:从本地磁盘到对象存储的内存保护方案
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
本文是一份针对 StarRocks 的中间结果落盘(intermediate result spilling)技术指南。当聚合(Aggregate)、排序(Sort)、Hash Join 等内存密集型算子在超大数据集上执行时,StarRocks 可能因内存耗尽(OOM)而强制终止查询;落盘功能允许将这些算子的中间结果暂存到本地磁盘或对象存储,用可接受的性能损耗换取内存占用的大幅下降。读完本文,你将掌握spill_local_storage_dir等 BE/CN 配置项的设置、enable_spill与spill_mode会话变量的完整用法、对象存储落盘的开启方式,以及落盘功能的边界与限制。
概述:为什么需要中间结果落盘
StarRocks 是典型的依赖内存计算执行查询的数据库系统。当针对大数据集执行包含聚合(aggregate)、排序(sort)、连接(join)算子的查询时,内存资源的消耗会非常可观。一旦达到内存上限,这些查询会被强制终止,即发生 OOM。
然而,在实际运维中,仍有一些场景希望某些内存密集型任务稳定完成,而对性能不是首要诉求,例如:
- 构建物化视图(materialized view);
- 执行轻量级 ETL(例如
INSERT INTO SELECT语句)。
这类任务很容易耗尽集群内存资源,从而阻塞集群中其他正在运行的查询。传统上,只能逐一对这些任务进行调优,并依赖资源隔离策略(如资源组 Resource Group)控制查询并发度,这在某些极端场景下既不方便,也容易失败。
从 StarRocks v3.0.1 起,StarRocks 支持将部分内存密集型算子的中间结果落盘(spill)。借助这一能力,可以"用可接受的性能下降,换取内存占用的大幅削减",从而显著提升系统可用性。
从源码结构看,该能力在 BE 侧由 compute_env/spill 目录下的一整套落盘组件支撑,包括块管理(block_manager.h、file_block_manager.h、log_block_manager.h、hybird_block_manager.h)、目录管理(dir_manager.h)、数据流(data_stream.h)、序列化(serde.h)、内存表(mem_table.h)以及查询级落盘管理器(query_spill_manager.h)等;FE 侧则由 SessionVariable.java 中的一系列会话变量控制开关与行为。
支持落盘的算子
目前,StarRocks 的落盘功能支持以下算子:
- 聚合算子(Aggregate)
- 排序算子(Sort)
- Hash Join 算子(LEFT JOIN、RIGHT JOIN、FULL JOIN、OUTER JOIN、SEMI JOIN、INNER JOIN)
- CTE 算子(自 v3.3.4 起支持)
对应的可落盘算子类型定义在 InternalService.thrift 的TSpillableOperatorType枚举中(HASH_JOIN = 0、AGG = 1、AGG_DISTINCT = 2、SORT等)。FE 侧的spillable_operator_mask会话变量(默认-1,表示所有算子均可落盘)通过位掩码按算子类型控制落盘是否生效,其注释说明目前仅最低 4 位有意义,分别对应 HASH_JOIN、AGG、AGG_DISTINCT 和 SORT(参见 SessionVariable.java)。
启用中间结果落盘
启用中间结果落盘分为三步:配置落盘目录(BE 静态配置)、开启落盘开关(会话变量)、设置落盘模式(会话变量)。
步骤一:配置本地落盘目录spill_local_storage_dir
在 BE 配置文件be.conf或 CN 配置文件cn.conf中指定本地落盘目录,然后重启集群使修改生效:
spill_local_storage_dir=/<dir_1>[;/<dir_2>]要点:
- 可以通过分号(
;)分隔,同时指定多个spill_local_storage_dir,实现跨多块磁盘的负载分担。 - 生产环境强烈建议为数据存储和落盘使用不同的磁盘。当中间结果落盘时,写入负载和磁盘占用都可能显著上升;若与数据共用同一块盘,这种突增可能影响集群中其他查询或任务的运行。
从配置源码看,该配置项定义于 config.h:CONF_String(spill_local_storage_dir, "${STARROCKS_HOME}/spill"),默认目录为${STARROCKS_HOME}/spill。围绕目录与磁盘使用,仓库中还提供了一批可选的调优参数(同样位于 config.h):
| BE/CN 配置项 | 默认值 | 说明 |
|---|---|---|
spill_local_storage_dir | ${STARROCKS_HOME}/spill | 本地落盘目录,可配置多个,用分号分隔 |
spill_max_dir_bytes_ratio | 0.8(80%) | 单个落盘目录的最大使用比例;当已用空间超过该比例时,落盘将返回错误(当落盘目录与存储目录重合时尤其重要) |
spill_max_partition_level | 7 | 落盘分区最大层级(需保证2^spill_max_partition_level < spill_max_partition_size) |
spill_max_partition_size | 1024 | 落盘分区最大大小 |
spill_init_partition | 16 | 落盘初始分区数 |
spill_max_log_block_container_bytes | 10737418240(10GB) | 单个日志块容器文件的最大尺寸(非硬限制,超出会新建文件) |
spill_read_buffer_min_bytes | 1048576 | 落盘读缓冲的最小字节数,小于该值将禁用缓冲读 |
experimental_spill_skip_sync | true | 发生落盘时是否跳过同步刷盘 |
enable_spill_sort_events | false | 是否将可落盘排序算子调度到管道事件调度器而非忙轮询 |
注意:以上参数属于内部实现细节,日常使用只需关注
spill_local_storage_dir;其余参数在官方文档未展开说明前,建议保持默认值,仅在排查性能/容量问题时结合源码评估。
步骤二:开启落盘开关enable_spill
执行以下语句开启中间结果落盘:
SET enable_spill = true;步骤三:设置落盘模式spill_mode
执行以下语句配置落盘执行模式:
SET spill_mode = { "auto" | "force" };两个会话变量的含义与默认值如下:
| 变量 | 默认值 | 说明 |
|---|---|---|
enable_spill | false | 是否启用中间结果落盘。设为true后,StarRocks 在处理查询中的聚合、排序或连接算子时,会将中间结果落盘以减少内存占用。 |
spill_mode | auto | 中间结果落盘的执行模式。合法取值:
enable_spill设为true时生效。 |
说明:每次涉及落盘的查询完成后,StarRocks 会自动清理该查询产生的落盘数据。如果 BE 在清理前崩溃,StarRocks 会在 BE 重启时清理残留数据。
从 FE 源码可以确认上述默认值:SessionVariable.java 中enableSpill默认false(L1756-1757)、spillMode默认字符串"auto"(L1780-1781),且二者均通过@VariableMgr.VarAttr暴露为可动态设置的会话变量,因此无需重启 FE/BE 即可通过SET语句即时生效——这与spill_local_storage_dir必须重启集群才能生效形成了鲜明对比。
[预览] 将中间结果落盘到对象存储
自v3.3.0起,StarRocks 支持将中间结果落盘到对象存储。
:::tip 在启用对象存储落盘之前,必须先创建一个存储卷(storage volume)以定义要使用的对象存储。创建存储卷的详细说明,参见 CREATE STORAGE VOLUME。 :::
完成上一节的落盘启用步骤后,进一步设置以下系统变量,即可让中间结果落盘到对象存储:
SET enable_spill_to_remote_storage = true; -- 将 <storage_volume_name> 替换为你要使用的存储卷名称 SET spill_storage_volume = '<storage_volume_name>';开启对象存储落盘后,触发落盘的查询所产生的中间结果会首先存储在 BE 或 CN 节点的本地磁盘,当本地磁盘容量达到限制后,再写入对象存储。
请注意:如果你为spill_storage_volume指定的存储卷不存在,则不会启用对象存储落盘。
从 BE 源码可以印证这一"本地优先、远端兜底"的实现策略:在 query_spill_manager.cpp 的init_block_manager中,当enable_spill_to_remote_storage开启时,会构建一个HyBirdBlockManager,它同时持有一个基于LogBlockManager的本地块管理器和一个基于FileBlockManager的远端块管理器,从而支持数据先写本地、容量受限后写对象存储的混合模式;若进一步设置disable_spill_to_local_disk(FE 侧存在该会话变量,见 SessionVariable.java),则直接使用远端块管理器、完全绕过本地磁盘。远端文件系统的创建经由FileSystemFactory::CreateUniqueFromString依据存储卷配置完成。
相关会话变量速查
FE 侧围绕落盘还提供了若干与本文主题相关的会话变量(位于 SessionVariable.java):
| 会话变量 | 默认值 | 说明 |
|---|---|---|
enable_spill | false | 落盘总开关 |
spill_mode | auto | 落盘模式:auto/force |
enable_spill_to_remote_storage | false | 是否允许落盘到对象存储 |
disable_spill_to_local_disk | false | 是否禁用本地磁盘落盘(开启后仅落盘到对象存储) |
spill_storage_volume | 空字符串 | 指定用于对象存储落盘的存储卷名称 |
spillable_operator_mask | -1(全部算子) | 以位掩码控制哪些算子可以落盘(HASH_JOIN / AGG / AGG_DISTINCT / SORT),默认所有算子均可 |
spill_operator_max_bytes | — | 单个算子触发落盘的内存上限 |
spill_revocable_max_bytes | — | 可回收(revocable)内存上限 |
spill_enable_compaction | — | 是否启用落盘文件的压缩合并 |
spill_enable_direct_io | false | 是否启用直接 I/O 读写 |
其中spillable_operator_mask、enable_spill_buffer_read、spill_partitionwise_agg等标注为INVISIBLE的变量主要供内部与测试使用,生产环境不建议手动调整。
物化视图刷新中的落盘应用
落盘功能在构建物化视图这一典型场景中已得到原生集成。FE 的 MVRefreshProcessor.java 中实现了如下逻辑:当Config.enable_materialized_view_spill开启、且用户既未显式设置session.enable_spill会话变量、也未在物化视图属性中配置session.enable_spill时,物化视图刷新会自动将enable_spill置为true。这意味着在默认配置下,重型物化视图刷新任务会自动受益于落盘机制,避免因内存不足导致刷新失败或拖垮集群。
限制与注意事项
在使用落盘功能时,需要清楚以下边界:
- 并非所有 OOM 问题都能靠落盘解决。例如,StarRocks 无法释放表达式求值(expression evaluation)所占用的内存。
- 涉及落盘的查询通常会有约一个数量级(十倍)的查询延迟增加。建议通过设置会话变量
query_timeout为这些查询延长查询超时时间。 - 落盘到对象存储的性能下降相比落盘到本地磁盘更为显著。
- 每个 BE 或 CN 节点的
spill_local_storage_dir由该节点上运行的所有查询共享。目前 StarRocks 不支持为单个查询单独设置落盘数据的大小上限。因此,并发执行涉及落盘的查询时,彼此之间可能相互影响(例如某节点上落盘数据量过大会挤占其他查询的落盘空间)。
小结
中间结果落盘是 StarRocks 在内存计算与稳定性之间提供的关键权衡机制:它允许聚合、排序、Hash Join、CTE 等内存密集型算子在大数据集上以性能换内存,显著降低 OOM 风险,尤其适合物化视图构建与INSERT INTO SELECT这类轻量 ETL 场景。启用路径清晰——静态配置spill_local_storage_dir后,通过SET enable_spill = true与SET spill_mode = 'auto'/'force'即可动态控制;自 v3.3.0 起还可借助存储卷将中间结果进一步溢写至对象存储,实现本地与远端的混合落盘。在部署使用时,务必结合"不同盘存放数据与落盘、延长query_timeout、控制并发落盘查询"等最佳实践,并接受十倍量级的延迟代价与表达式求值内存不可释放的局限。
延伸阅读
- CREATE STORAGE VOLUME:创建存储卷以支持对象存储落盘
- ALTER STORAGE VOLUME / SHOW STORAGE VOLUMES:管理存储卷
- BE 落盘实现:compute_env/spill(含块管理、目录管理、序列化、数据流等核心组件)
- BE 落盘配置定义:config.h
- FE 落盘会话变量定义:SessionVariable.java
- 物化视图刷新自动启用落盘:MVRefreshProcessor.java
- 可落盘算子类型定义:InternalService.thrift
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考