如果你用过Doris,大概率遇到过这种场景:业务方在群里喊,“能帮我看下为什么数仓里没有XX表的数据吗?”你点开Hive一看,原始数据明明在,可就为了一个临时查询,要么现场丢一个Spark任务,要么催数据开发手工同步,最快也要等上十分钟。我从Doris 1.2版本开始追Remote Catalog这个功能,一路用到现在,差不多两年时间。这个能力说白了就是让Doris能直连外部数据源——Hive、Iceberg、Paimon、MySQL、PostgreSQL——直接在Doris里建外部表做查询,不需要先把数据导入进来。
这一篇是“Doris Remote Catalog 解码”系列的第四篇。前面几篇聊了整体架构、元数据对接原理、权限与安全设计,这篇不再讲概念,全部是实际操作。我会先交代真实测试环境与版本选择,然后分别测Hive Catalog、Iceberg/Paimon Catalog、JDBC Catalog,给出性能画像;接着完整复盘我在工作中踩过的高频坑,从现象到排查链路再到解决方案;最后给出选型建议——什么场景应该用Remote Catalog直查,什么场景应该老老实实把数据Load进Doris。
适合谁看?正在调研或者已经开始用Doris Multi-Catalog的读者;做湖仓一体、联邦查询选型的读者;以及反复被业务“借数”的数仓工程师。文章里的测试数字来自我自己的环境,不同集群会有差异,但画像和结论大概率是通用的。
1. 实测环境与三类Catalog建法
1.1 硬件与版本选择
先说测试环境,都是实际跑过的配置:
- Doris版本:2.1.5,FE共3个节点,BE共5个节点,混合部署在同一批物理机上
- Hive:3.1.3,文件格式ORC,分区表为主
- Iceberg:1.4.3,文件格式Parquet
- Paimon:0.8,文件格式ORC
- MySQL:8.0,PostgreSQL:15
- 测试数据:TPC-DS 1TB规模数据 + 少量实际业务数据
- 查询工具:Doris的MySQL协议客户端,关闭查询结果集缓存,冷启动测试
版本选择上有个考虑值得说一下。为什么Doris选2.1.5而不是更高的版本?一方面2.1.x的Multi-Catalog已经相对成熟,Paimon Catalog从2.1开始才从实验特性走向基本可用;另一方面2.1.5修复了不少元数据缓存和谓词下推相关的问题,比早期2.0版本稳定很多。如果你还在用1.2.x,部分语法和Catalog属性不兼容,建议直接升级后再参考这篇文章。
Hive侧我们统一用ORC格式,没用TextFile和Parquet做对比。原因是ORC在生产环境用得最多,并且自带索引和压缩,对远程查询的场景更友好。Iceberg和Paimon的表则保持它们默认的存储格式。
1.2 Catalog创建语句
下面是我实际使用的建Catalog语句,可以直接抄。
-- Hive Catalog:对接Hive Metastore CREATE CATALOG hive_catalog PROPERTIES ( 'type' = 'hms', 'hive.metastore.uris' = 'thrift://10.0.0.20:9083' ); -- Iceberg Catalog:Hive Catalog类型 CREATE CATALOG iceberg_catalog PROPERTIES ( 'type' = 'iceberg', 'iceberg.catalog.type' = 'hive', 'iceberg.catalog.hive.metastore.uris' = 'thrift://10.0.0.20:9083' ); -- Paimon Catalog:配置warehouse路径 CREATE CATALOG paimon_catalog PROPERTIES ( 'type' = 'paimon', 'warehouse' = 'hdfs://nameservice/user/paimon/warehouse' ); -- JDBC Catalog:连接MySQL业务库 CREATE CATALOG jdbc_mysql PROPERTIES ( 'type' = 'jdbc', 'user' = 'doris_readonly', 'password' = 'your_password', 'jdbc_url' = 'jdbc:mysql://10.0.0.30:3306/biz_db', 'driver_class' = 'com.mysql.cj.jdbc.Driver' );有两个细节容易踩:Hive Catalog的type要写成'hms',不是'hive',这是Doris多Catalog体系中Hive Metastore类型的固定写法;JDBC Catalog连接PostgreSQL时driver_class要换成org.postgresql.Driver,同时保证PostgreSQL的JDBC驱动包放在每个FE节点的lib目录下,这个坑后面单独展开。
1.3 测试口径
远程查询的性能受很多因素影响,单一查询说明不了问题。我设计了三类标准查询:
- 全表COUNT(*):测试基础扫描能力
- 带分区等值过滤的聚合查询:测试分区裁剪与谓词下推效果
- 远程表与Doris内表的JOIN查询:测试跨源JOIN的可行性与性能
每类查询在同一个集群连续跑3次,取中间值,尽量减少缓存影响。测试时间选在凌晨1点到2点,避开在线业务高峰期,避免和Doris内表自身的Compaction任务抢占磁盘IO。这里多提一句:远程表本身不需要Doris做Compaction,但BE节点上如果同时有其他内表的合并任务,会影响远程扫描的稳定性,所以测试时最好错开业务窗口。
2. 实测结果:三类远程数据源的真实性能画像
2.1 Hive Catalog:能查,但大查询别指望快
先看Hive Catalog最基础的查询能力,对TPC-DS的store_sales表做COUNT(*)全表扫描,这张表约2.8亿行,物理分区68个,数据量大约120GB。Doris内表同样一份数据做对比:
| 查询 | Hive Catalog | Doris内表 |
|---|---|---|
| 全表COUNT(*) | 25.3s | 4.1s |
| 单分区等值过滤聚合 | 9.2s | 1.3s |
| WHERE未带分区条件的过滤聚合 | 22.8s | 3.9s |
差距非常明显。原因不难理解:Hive表走的是远程HDFS读取和ORC原生解析,绕过了Doris的列存索引、压缩编码和向量化执行的优势。Doris内表是本地数据,扫描路径短,优化器能做的事也多得多。
但有一个现象值得注意:带分区等值过滤的查询,Hive Catalog也能把扫描量压到很低的水平,说明HMS的分区元数据下发是有效的。这个能力我反而觉得比查询性能本身更关键——它决定了Doris能不能精准跳过不需要的分区文件。
所以我对Hive Catalog的定位是:适合做元数据探查、抽样预览、临时取数,以及小数据量下的“查一下有没有”“数一下多少条”。不适合把大聚合、大JOIN直接跑在远程表上,尤其是多张远程大表之间互相JOIN,那会非常折磨人。
2.2 Iceberg与Paimon:湖表的扫描能力有差异
Iceberg和Paimon都用Hive Catalog作为元数据服务,但两者的底层文件组织形式完全不一样。
Iceberg的manifest列表机制在定位文件上比Hive的纯分区扫描更精细。实测对一张按日期分月的Iceberg表做单月数据聚合,表规模约2400万行,扫描耗时大约6秒。对比同样数据的Hive ORC表,大概要8到9秒。差距主要在于Iceberg能通过manifest和统计信息更精准地跳过不需要读的文件。
Paimon的情况比较特殊。Paimon底层是LSM结构,数据文件按主键排序,查询天然支持主键点查。实测用主键做等值查询,Paimon Catalog表大约300ms返回,这在湖表里是非常好的成绩。但范围扫描就吃亏了,LSM的merge-on-read会把多个文件的数据在读取时合并,查询越宽、数据跨度越大,merge开销越明显。
这里给一个实操建议:如果你在湖上建Paimon表,尽量让主键设计贴合业务查询模式。Doris侧通过Paimon Catalog查点查是很舒服的,但别拿它当宽表随便扫,那个性能可能还不如Hive ORC。
2.3 JDBC Catalog:小表维表好使,大表直查要谨慎
JDBC Catalog是三类Catalog里上手最快、反馈最直观的。测试结论也很干脆:表越小越好用,表一大就得小心。
对一张几万行的MySQL配置表做单表点查,延迟在50到100ms之间;做几万行的全表扫描,也基本在几百毫秒内完成。这个量级完全能接受,非常适合做维表关联。我在实际业务里经常写类似“Doris大宽表LEFT JOIN jdbc_mysql.码表”的查询,小码表实时性很好,业务方很满意。
但当JDBC源的表超过百万行并且查询条件复杂时,情况就不一样了。Doris会把下推不了的查询条件拉回本地处理,如果命中数据量很大,BE和MySQL之间会有大量数据传输,双方CPU和网络都被拖垮。实测一张800万行的MySQL大表,在Doris侧做无分区条件的聚合查询,耗时超过40秒,而直接在MySQL客户端执行只要5秒。这个结果说明,Doris的JDBC Catalog并没有把计算压力全部下推到MySQL,有一部分聚合是远程表扫描完后在Doris侧完成的。
JDBC Catalog的合理边界是:小表、维表、低频查询、可下推的简单过滤。大表导入Doris,或者直接在源库查询,都比绕一层Doris要合适。
3. 踩坑实录:四个高频问题的完整排查链路
3.1 元数据缓存:Hive建了新表,Doris里看不到
现象:数据开发在Hive里新加了一张分区表,Doris这边执行SHOW TABLES FROM hive_catalog.tpcds;却看不到,部分已有表的新分区也查不出来。
排查过程:我一开始以为是Catalog没建对,重新执行SHOW CATALOGS;确认hive_catalog还在。然后USE hive_catalog;切过去查列表,还是看不到。接着我去Hive侧用SHOW TABLES IN tpcds;,新表明明就在。最后在Doris官网文档里找到关键信息:Doris的Multi-Catalog元数据是有缓存的,FE不会每次查询都实时拉取HMS的元数据。
根因:外部系统的DDL变更后,Doris侧的元数据缓存不会自动同步。
解决方案:手动执行
REFRESH CATALOG hive_catalog;刷新之后,新表马上就能看到了。对于生产环境,可以加一个定时任务每天凌晨执行一次,但注意错开HMS的高峰时段,避免频繁刷新对Metastore造成压力。不同Doris版本对自动刷新的支持程度不一样,最保险的方案永远是脚本定期REFRESH。
这个坑的教训是:外部Catalog永远不是“实时”的。凡是依赖外部表做报表的,一定要考虑元数据延迟,必要时在上游任务完成后主动触发一次刷新。
3.2 谓词下推失效:加了WHERE还是全分区扫描
现象:一条查询Hive外部表的语句,WHERE条件能过滤掉大部分分区,但从Doris的Profile里看到扫描的分区数接近全表,查询慢得离谱。
排查过程:先用EXPLAIN SELECT ...查看执行计划,发现TableScan节点上没有预期的分区过滤条件,HdfsScanNode下面直接是全表文件列表。我又对比了Hive侧执行同样条件,分区裁剪是正常的,说明问题出在Doris和HMS之间的元数据对接上。后来我检查了表结构,发现Hive表的分区字段是string类型,存储值格式是'20240101',而我在查询时写的是ds = '2024-01-01',Doris对字符串的等值判断没法直接匹配到具体分区目录。
根因:分区字段类型或格式不匹配,导致Doris无法把过滤条件下推到分区裁剪层;另外,在WHERE条件里对分区字段做函数运算,例如substr(ds, 1, 7) = '2024-01',也会直接让下推失效。
解决方案:统一Hive表的字段类型和值格式,查询时保持与分区目录完全一致;不要在谓词条件里对分区字段套函数。这个坑花了半天才定位出来,之后我给自己定了条规矩:外部Catalog的表结构核对工作不要省,尤其是分区字段的类型和格式,建完Catalog先做一轮元数据比对再开放查询。
3.3 JDBC Catalog驱动漂移:多FE节点只有部分生效
现象:JDBC Catalog创建成功,但是查询时有时报ClassNotFoundException: com.mysql.cj.jdbc.Driver,有时又能正常返回,报错完全没有规律。
排查过程:一开始以为是连接池问题,重启了FE和BE还是偶发。直到我逐台登录FE节点检查,才发现问题:当时只往一台FE的lib目录放了mysql-connector-java-8.0.30.jar,其他FE节点没有放。Doris的FE是集群模式,请求会随机分发到不同FE,请求路由到没放驱动的FE上就直接报类找不到。
根因:JDBC驱动JAR包没有在所有FE节点上保持一致,且Java驱动版本和driver_class需要匹配:mysql-connector-java 5.x对应com.mysql.jdbc.Driver,8.x对应com.mysql.cj.jdbc.Driver。
解决方案:把驱动JAR包同步到所有FE节点的lib目录,然后重启FE。如果是用Docker部署,最好在镜像构建时就把驱动打进去,避免运行时再手动拷贝。这个坑在从单FE测试环境迁移到多FE生产环境时特别容易触发。
3.4 权限穿透:低权限用户绕过了Doris的授权体系
现象:某天业务同学反馈,一个只有Doris内表查询权限的低权限账号,居然能通过JDBC Catalog直接查到MySQL业务库一张敏感配置表的内容。我当时头皮一紧。
排查过程:我先用该账号登录Doris,执行SELECT * FROM jdbc_mysql.sensitive_table LIMIT 10;,数据直接返回。这说明外部Catalog的表并没有被Doris现有的表权限模型完全约束住。Doris自身的数据库和表授权管的是Doris内表,外部Catalog的权限管控在实际行为上存在“宽口径”,不同版本对Catalog表细粒度授权的支持程度也不一样。
根因:Doris侧权限模型只负责Doris自身资源,外部数据源的安全边界需要数据源自身配合。
解决方案:我做的是三层收敛。第一,数据源侧限制账号权限:给Doris单独创建一个只读账号,只授予必要库表的SELECT权限,绝不给DDL和写权限。第二,Doris侧对能访问外部Catalog的用户做白名单管理,不给普通开发账号开放Catalog的查询权限。第三,接入Ranger统一授权,把Doris内表和外部表纳入同一套策略体系。如果公司没有Ranger,至少做到第一层和第二层。
这个坑最重要的启示是:Remote Catalog是一扇门,门开得越大,越要确认门后面到底有什么。
4. Remote Catalog与数据导入的选型边界
4.1 该导入的场景
我见过不少团队把Remote Catalog当银弹,恨不得所有查数都走外部表,最后查询慢、资源打满,又回头骂Doris不行。实际上,以下场景应该优先考虑把数据Load进Doris:
第一,高频报表和交互式查询。毫秒级的响应依赖Doris的列存、索引、分桶裁剪和物化视图,这些能力外部表都享受不到。
第二,复杂多表JOIN和大聚合。TPC-DS的实测已经说明问题,远程表扫描本身就有25秒的基础成本,再叠加大聚合和JOIN,性能曲线会非常难看。
第三,需要行级更新、事务语义或者精确一次写入的场景。Doris内表的Unique模型和Merge-on-Write机制能处理这类需求,外部Hive/Iceberg表要么不支持行级更新,要么得走merge-on-read,链路长且不可控。
第四,数据量可控且查询频次很高。比如单表几亿行以内,导入成本不大,产生的收益却很明显。我自己的判断标准是:如果一张表每天要被查询几十次以上,且数据量在单表百GB以内,直接导进来;如果一个月才被查两三次,数据量又大,才考虑远程Catalog。
4.2 该用Catalog直查的场景
远程Catalog的甜点区,是低频率、低成本、实时性要求不高的探查类应用。
数据湖探查和临时取数是最典型的场景。业务方临时要一份几个月前的数据,你不想为了这一两个查询跑ETL,Doris外部表一条SQL就能搞定,代价低很多。
维表关联也非常值得用Catalog。MySQL或PostgreSQL里的配置表、码表,往往只有几千行,变化不算频繁,用JDBC Catalog直接JOIN,不需要维护一份Doris副本,省掉了同步链路。前提是这类维表不能太大,超过几百万行就要考虑导入或者用Doris的自动周期同步。
还有一个场景是数据量极大且压缩收益低的宽表。百亿行以上的日志明细,每一行字段又多,即使导入Doris也要占用大量BE存储。如果这类数据的查询频率不高,放湖上直查是更经济的选择。
4.3 和Trino/Presto等联邦查询引擎的对比
团队在选型时经常纠结:已经有了Trino,还要不要用Doris的Remote Catalog?
我的看法是:如果公司已经稳定运行了Trino,没必要强迁;但如果Doris已经是核心数仓,Remote Catalog的性价比显然更高。它能复用Doris的权限体系、审计日志、连接管理和运维体系,业务方不用再记一套Trino的连接方式和SQL方言差异。成本上少维护一套引擎,收益上多一个数据入口。
当然Trino的优势在于对接数据源种类更多、联邦查询优化器更成熟、对复杂异构JOIN的支持经验更丰富。如果查询场景以三五个联邦数据源互相JOIN为主,Trino仍然是更稳妥的选择。Doris的Remote Catalog比较适合“Doris为主管,外围数据通过Catalog延展”的架构。
5. 部署形态、升级路线与CCR配套经验
5.1 元数据放在外部HMS
我强烈建议,凡是正式环境的Doris Multi-Catalog,元数据一定要统一收敛到外部Hive Metastore,不要用Doris内置的元数据库存Catalog元数据。原因很简单:Catalog本身是逻辑对象,真正的表结构、分区信息都在HMS里,外部HMS能保证Doris集群和Spark/Flink/Trino看到的是同一份元数据。如果Doris侧再去维护一份拷贝,很容易出现元数据分叉。
如果你的Doris集群存在多环境,比如开发、测试、生产各一套,建议每套环境对接同一个HMS测试实例或不同HMS,但Catalog命名规范要统一。生产环境切换HMS地址的时候记住要重建或修改Catalog,不要直接复用,否则元数据会乱。
5.2 版本选型与升级注意点
Doris 2.x版本的Multi-Catalog是和版本强相关的。2.0相比1.2修复了大量外部表查询的问题,尤其是谓词下推和文件扫描的稳定性;2.1之后Paimon Catalog才真正可用。如果你要用Paimon,最低建议是2.1.4以上,最好是跟进到最新的2.1.x补丁版本。
升级前最好做三件事:第一,检查所有Catalog的建表语句是否在新版本有废弃属性;第二,跑一遍核心外部表查询,对比升级前后的Profile和耗时;第三,确认Ranger或权限插件是否兼容新版本。
另一个容易被忽略的点是,Doris 3.x引入了一些新的Catalog管理和元数据刷新方式,升级时不要指望旧脚本原封不动还能用,要提前在测试环境把刷新、授权、监控脚本全部回归一遍。
5.3 CCR场景下Catalog的同步问题
我在生产环境遇到过一个很实际的需求:主集群和备集群做了Doris CCR跨集群复制,业务方默认认为备集群也能查到外部Catalog的数据。实测下来,CCR同步的是Doris内部表的数据,外部Catalog作为一个逻辑对象并不会被CCR同步过去。
所以你在备集群需要手动创建同样的Catalog,并且把元数据刷新任务也部署一份。另外,外部数据源侧的账号权限,主备集群要用不同的账号,或者至少用同一个只读账号但要在监控里区分来源,否则出了问题不好排查到底是谁在请求。
还要注意主备集群的Catalog刷新任务不要同时跑,避免对HMS造成冲击。我们当时的做法是主集群凌晨1点刷新,备集群凌晨3点刷新,中间留足间隔。
最后说点实在的
我见过太多团队把Remote Catalog当成“万能查询加速器”,最后还是回归到老老实实做数据分层。外部表的天花板不在SQL能力,而在存储引擎和优化器的天然边界。Doris内表能拍胸脯保证的毫秒级查询,放到远程表上能秒级返回就算不错了。
根据我的个人经验,正确的使用心态是把Remote Catalog当成“轻量级数据入口”,解决能不能查的问题,而不是多快的问题。核心报表、核心分析,数据还是要在Doris内部落一份,用内表的性能兜底;外部Catalog用来做探查、临时取数、维表关联和低频访问的大数据湖表访问。这样分工,系统稳定,团队协作也顺畅。
最后分享一个运维细节:外部Catalog一旦多了,建议按业务域命名,比如cat_hive_dwd、cat_jdbc_biz、cat_iceberg_dws,并在命名里带环境标识。不然等Catalog数量超过20个的时候,你看着一堆叫hive1、hive2的名字,连自己都分不清哪个是哪个。别问我怎么知道的。