一、引言
Hudi 作为主流的数据湖表格式之一,凭借其增量处理能力和ACID 事务支持成为企业数据平台的核心组件。然而,直接通过 Spark/Trino 查询 Hudi 表往往面临以下痛点:
- 交互式查询延迟高:Spark 等引擎启动开销大,无法满足秒级响应需求
- 并发查询能力有限:大数据引擎的并发模型不适合高并发 BI 查询场景
- 缺乏高效 CBO 优化:通用查询引擎对湖表的统计信息利用不够充分
StarRocks 从2.x版本引入了External Catalog 框架,支持通过创建 Hudi Catalog 直接查询 Hudi 表数据,无需数据导入,这种架构实现了:
- 计算存储分离,StarRocks 作为纯查询引擎接入
- 借助 StarRocks 的 MPP 向量化引擎,显著提升查询性能
- 支持物化视图加速、Data Cache 等本地缓存机制
二、核心原理解析
StarRocks 通过 External Catalog 机制将 Hudi 表视为外部数据源。

查询流程简述:
- Catalog 创建:用户通过 DDL 注册 Hudi Catalog,指向 Hive Metastore
- 元数据获取:FE 从 HMS 拉取库表信息、分区信息,缓存在本地
- 查询优化:FE 的 CBO 优化器基于统计信息进行分区裁剪、谓词下推
- 执行下发:优化后的查询计划被分发到各 BE 节点
- 数据读取:BE 根据表类型(COW/MOR)和快照状态,选择 Native Reader 或 JNI Reader 读取数据
典型的 Catalog 创建语句:
CREATE EXTERNAL CATALOG hudi_catalog
PROPERTIES (
"type" = "hudi",
"hive.metastore.type" = "hive",
"hive.metastore.uris" = "thrift://hms-host:9083"
);
Hudi 的两种表类型(COW/MOR)在 StarRocks 中有着截然不同的读取路径。
|------|---------------|----------------|----------------|
| 维度 | COW 表 | MOR 表(无 Delta) | MOR 表(有 Delta) |
| 读取器 | Native Reader | Native Reader | JNI Reader |
| 执行引擎 | C++ 向量化 | C++ 向量化 | Java + C++ 混合 |
| 性能特征 | 最优 | 与 COW 相当 | 有 Merge 开销 |
| 适用场景 | 读多写少 | Compaction 后查询 | 写多读少的实时场景 |

StarRocks 的混合读取机制是其查询 Hudi MOR 表的核心创新之一。具体原理如下:
Native Reader(C++ Parquet/ORC Reader)
- 直接读取 Parquet/ORC 格式的 Base File
- 完全在 C++ 侧以向量化方式执行,性能最优
- 支持谓词下推到文件层面(Row Group / Stripe 级别过滤)
- 适用于 COW 表全部场景,以及 MOR 表中已完成 Compaction 的文件
JNI Reader(Java Hudi MergeOnRead Scanner)
当 MOR 表存在尚未合并的 Delta Log 文件时,StarRocks BE 启动 JNI Reader:
- BE (C++) 通过 JNI 调用 Java 侧的 Hudi SDK
- Java 侧完成 Base File 与 Delta Log 的 Merge 操作
- 合并结果通过 Unsafe 共享内存机制传递回 C++ 侧
- C++ 侧接收数据后继续进行向量化执行
另外,Starrocks FE 的元数据缓存机制对查询性能至关重要,StarRocks 采用多级缓存策略:
|-------------------|----------|--------------------------------------|-------------|
| 缓存类型 | 缓存内容 | 关键参数 | 默认值 |
| Metastore Cache | 库表/分区元信息 | enable_metastore_cache | true |
| Remote File Cache | 远端文件列表信息 | enable_remote_file_cache | true |
| Cache Refresh | 缓存刷新间隔 | metastore_cache_refresh_interval_sec | 7200 (2h) |
| Cache TTL | 缓存过期时间 | metastore_cache_ttl_sec | 86400 (24h) |
查询优化阶段,FE 会执行:
- 分区裁剪(Partition Pruning):根据 WHERE 条件排除无关分区
- 文件裁剪(File Pruning):基于 Hudi Metadata Table(3.3+ 版本)跳过无关文件
- 谓词下推(Predicate Pushdown):将过滤条件下推至存储层
- 列裁剪(Column Pruning):仅读取查询涉及的列
三、常见踩坑点与排查
1.元数据缓存不一致(查不到最新数据)
现象:Spark 写入 Hudi 新分区后,StarRocks 查询仍返回旧数据或报分区不存在。
根因:Spark 直接写 Hudi 表时,可能不会触发 HMS 的 Partition Event 通知。StarRocks FE 缓存未被更新,因此"看不到"新分区。
解决方案:
-- 手动刷新 External Table 的缓存
REFRESH EXTERNAL TABLE hudi_catalog.db_name.table_name;
-- 或刷新指定分区
REFRESH EXTERNAL TABLE hudi_catalog.db_name.table_name
PARTITION ('dt=2024-01-15');
#在生产环境中,建议将 REFRESH EXTERNAL TABLE 命令集成到 Hudi 写入 Pipeline 的后置步骤中,
#确保每次写入完成后自动触发元数据刷新。
也可通过缩短缓存刷新间隔来缓解(但会增加 HMS 压力):
-- 设置 Catalog 级别参数
ALTER CATALOG hudi_catalog SET PROPERTIES (
"metastore_cache_refresh_interval_sec" = "60",
"metastore_cache_ttl_sec" = "300"
);
2.Schema 不匹配报错(Hudi column not exists)
现象:查询时报错 Column 'xxx' not exists in hive metastore。
根因:Hudi 支持 Schema Evolution(加列、改类型等),但如果 Hudi 表的 Schema 变更未同步到 HMS,StarRocks 通过 HMS 获取的 Schema 就是过时的。
排查步骤:
-
确认 Hudi 表是否使用了 hoodie.datasource.hive_sync.mode=hms 配置
-
检查 HMS 中表的 Schema 是否与 Hudi .hoodie 目录下的最新 Schema 一致
-
执行 REFRESH EXTERNAL TABLE 强制重新同步
-
若仍不一致,需在 Hudi 端重新触发 Hive Sync
-- 确认 StarRocks 看到的表结构
DESC hudi_catalog.db_name.table_name;-- 与 Hive 中的 Schema 对比
-- (在 Hive CLI 中)
DESCRIBE FORMATTED db_name.table_name;
3.MOR 表 JNI Reader 性能退化
现象:MOR 表查询性能远低于预期,延迟显著高于相同数据量的 COW 表。
根因:大量 Delta Log 未合并,导致每次查询都需要 JNI Reader 执行 Merge 操作。
优化方向:
-
加快 Compaction 频率:调整 Hudi 的 Compaction 策略,减少未合并 Log 的数量
-
使用 Read Optimized 查询:如果业务可以接受非实时一致性,可只查询已合并的 Base File
-
增大 BE JVM 内存:通过 be.conf 中的 JVM 参数确保 JNI Reader 有足够内存
be.conf JVM 参数示例
JAVA_OPTS="-Xmx4g -Xms4g -XX:+UseG1GC"
4.HDFS HA / Kerberos 认证配置遗漏
现象:连接 Hudi 存储时报 java.io.IOException: Failed to connect to namenode 或 Kerberos 认证失败。
解决方案:
-
确保 hdfs-site.xml 和 core-site.xml 正确放置在 fe/conf 和 be/conf 目录下
-
HDFS HA 场景需要在所有 FE/BE 节点配置 nameservice
-
Kerberos 场景需要确保 keytab 文件在所有节点可访问,且 principal 配置正确
-- Catalog 创建时指定 HA 配置
CREATE EXTERNAL CATALOG hudi_catalog
PROPERTIES (
"type" = "hudi",
"hive.metastore.uris" = "thrift://hms-host:9083",
"dfs.nameservices" = "mycluster",
"dfs.ha.namenodes.mycluster" = "nn1,nn2",
"dfs.namenode.rpc-address.mycluster.nn1" = "nn1-host:8020",
"dfs.namenode.rpc-address.mycluster.nn2" = "nn2-host:8020",
"dfs.client.failover.proxy.provider.mycluster" =
"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
);
5.物化视图数据一致性问题
现象:基于 Hudi Catalog 创建的物化视图数据与源表不一致。
根因:外部 Catalog 上的物化视图采用异步刷新策略,存在最终一致性窗口。刷新期间查询可能命中旧数据。
应对策略:
-
设置合理的刷新间隔(REFRESH ASYNC EVERY (INTERVAL ...))
-
对实时性要求高的查询,可通过 Hint 强制跳过物化视图改写
-
监控物化视图刷新状态,确保刷新任务不积压
-- 查看物化视图刷新状态
SHOW MATERIALIZED VIEWS WHERE Name = 'mv_hudi_orders'\G-- 强制手动刷新
REFRESH MATERIALIZED VIEW mv_hudi_orders;
四、最佳实践
1.Catalog 配置推荐参数
CREATE EXTERNAL CATALOG hudi_catalog
PROPERTIES (
"type" = "hudi",
"hive.metastore.type" = "hive",
"hive.metastore.uris" = "thrift://hms-host:9083",
-- 元数据缓存优化
"enable_metastore_cache" = "true",
"metastore_cache_refresh_interval_sec" = "600",
"metastore_cache_ttl_sec" = "3600",
-- 远程文件缓存
"enable_remote_file_cache" = "true",
"remote_file_cache_ttl_sec" = "1800"
);
#缓存参数需要根据数据更新频率来调整。
#如果 Hudi 表每小时写入一次,建议 metastore_cache_refresh_interval_sec 设置为 300-600 秒。
#如果写入更频繁,考虑使用手动 REFRESH 而非依赖自动刷新。
2.物化视图加速策略
StarRocks 从v2.5 起支持在外部 Catalog 上创建物化视图,v3.0 后进一步增强了对 Hudi 的物化视图能力:

物化视图创建示例:
-- 创建基于 Hudi 表的物化视图
CREATE MATERIALIZED VIEW mv_hudi_orders
DISTRIBUTED BY HASH(order_id)
REFRESH ASYNC EVERY (INTERVAL 30 MINUTE)
AS
SELECT
dt,
region,
COUNT(*) AS order_cnt,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount
FROM hudi_catalog.lakehouse.orders
GROUP BY dt, region;
-- 开启透明查询改写
SET enable_materialized_view_rewrite = true;
物化视图支持透明查询改写:当用户查询逻辑可被物化视图覆盖时,优化器会自动路由到 MV,无需修改原始 SQL。
3.Data Cache 配置
StarRocks 从v3.0 起引入 Data Cache,可将远端存储的数据块缓存到 BE 本地磁盘(推荐 SSD),对重复查询提升显著:
# be.conf Data Cache 配置(StarRocks 3.0+ 参数名)
# 启用 Data Cache
datacache_enable = true
# 缓存存储目录(建议使用 SSD)
datacache_disk_path = /data/ssd/datacache
# 单盘缓存容量上限
datacache_disk_size = 200G
# 内存缓存上限
datacache_mem_size = 10G
# 注意:2.5 版本使用 block_cache_enable / block_cache_disk_path 等旧参数名
# 请务必参考所用版本的官方文档确认参数名称
Data Cache 适合热数据重复查询场景,如果查询模式高度随机且数据量远大于缓存空间,Cache 命中率会很低,反而增加额外 IO 开销,建议监控datacache_hit_ratio指标来评估效果。
4.Hudi 表设计建议(COW vs MOR 选型)
|----------------|---------------------|---------------------------------|
| 场景特征 | 推荐表类型 | 理由 |
| 读多写少,T+1 批量更新 | COW | Native Reader 全程向量化,查询性能最优 |
| 写多读少,近实时写入 | MOR | 写入延迟低,Compaction 后查询性能也可接受 |
| 要求实时可见 + 高查询性能 | MOR + 高频 Compaction | 通过快速合并 Delta 减少 JNI Reader 使用比例 |
| 数据量极大,查询以分析为主 | COW + 物化视图 | MV 预聚合 + Native Reader,综合性能最优 |
Hudi 表设计时还应注意:
- 分区策略:分区键应与查询的 WHERE 条件对齐,便于分区裁剪
- 文件大小:建议单个 Base File 在 128MB~256MB 之间,过小会导致文件列表膨胀
- Compaction 策略:MOR 表建议配置 num_delta_commits_before_compaction = 5 以控制 Log 堆积
5.监控与运维建议
|---------------------------|-----------------|-------------------|
| 监控项 | 含义 | 告警阈值建议 |
| hudi_jni_reader_open_time | JNI Reader 打开耗时 | > 5s 需关注 |
| datacache_hit_ratio | Data Cache 命中率 | < 50% 需优化 |
| metastore_cache_hit_count | 元数据缓存命中次数 | 持续为 0 说明缓存异常 |
| 物化视图刷新延迟 | MV 最近刷新距今时长 | > 2x 刷新间隔需告警 |
| BE JVM GC 时间 | JNI Reader 内存压力 | Full GC > 1 次/分钟 |
运维建议:
- 定期执行 REFRESH EXTERNAL TABLE 确保元数据时效性
- 监控 Hudi Compaction 任务执行状态,避免 Log 堆积影响查询
- 物化视图刷新失败时应有告警通知,及时排查
- 升级 StarRocks 版本时需确认 Hudi Catalog 的兼容性变更