
1. 先把场景摆清楚多源数据目录到底在解决谁的痛多源数据目录Multi Catalog这个词第一次听容易跟数据目录服务元数据中心混在一起。在 Doris 的语境里它说的是一件很具体的事让一个 Doris 集群同时挂载多个外部数据源用统一的 SQL 语法直接查这些源里的数据而不必先把数据搬进 Doris 自己的存储。挂 Hive Metastore 是一类挂 MySQL、PostgreSQL 是一类挂 Iceberg、Hudi、Paimon 这类湖上表格式又是一类只要连接器支持它们在你的查询里看起来就像同一个库里的不同 schema。这个能力从 1.2 版本开始成型往后每个大版本都在补连接器、补下推、补缓存策略到现在已经是很多人选型时最先问的一个功能点。我接触这个功能的起点很朴素一条报表链路指标口径在数仓里明细日志在对象存储上的湖表里还有一份维度表常年躺在业务库的 MySQL 里没人愿意同步因为业务方随时改字段。以前的做法是每天凌晨跑一轮同步任务把三份数据拉到一个地方再算。这个方案能跑但问题一眼能看出来时效差一天链路长了以后任何一环挂掉整条报表就出不来而且中间那份冗余存储的成本没人愿意长期承担。Multi Catalog 给的是另一条路——不搬数据把查询发过去。1.1 一个典型的两难搬数据还是连数据数据集成这件事本质上一直在这两个选项之间摇摆。搬数据的好处是查询快、可控、可以随意建索引和分区坏处是链路长、存储翻倍、时效滞后而且一旦上游改表结构同步任务就得跟着改。连数据的好处是零冗余、时效实时、口径跟着源走坏处是查询性能受制于源端跨源 Join 的代价不好估网络和权限边界也更复杂。Doris 的 Multi Catalog 是明确站在连数据这一侧的但它没有把搬这条路堵死。你完全可以在同一个集群里把高频访问的热数据用 Doris 内表存一份把低频的冷数据留在湖上直接查两者在一个 SQL 里 Join。我觉得这才是它真正的价值定位不是一个替代 ETL 的方案而是一个让你能把哪些数据值得搬、哪些不值得这个判断做得更细的工具。以前你只有搬和不查两个选项现在中间多了一档。这个判断在实操里非常有用。我一般的划分标准是日查询频次超过几百次、参与多表 Join、对延迟敏感的表走内表同步一周查不了几次、主要用于排查和追溯的明细表直接挂 Catalog 查口径表、维度表这种体量小但变动频繁的也直接挂 Catalog省掉同步任务维护成本。1.2 Multi Catalog 的定位从复制数据转向路由查询从执行层面看一次跨 Catalog 的查询Doris 做的事情可以拆成三步。第一步是解析把catalog.db.table这个三段式名字解析到具体的连接器和元数据第二步是生成分布式计划FE 拿到外表的分区、行数、列统计信息后决定 Join 顺序和算子放置位置第三步是执行能下推到源端的条件下推不能下推的拉回 BE 本地算。这三步里第三步是决定性能的关键。很多人第一次用会觉得怎么这么慢八成是因为某个本可以下推的过滤条件没下推成导致全表数据被拉进了 Doris 的内存里做过滤。所以理解 Multi Catalog重点不是记住建 Catalog 的语法而是搞清楚哪些算力在源端、哪些算力在 Doris 端、边界在哪里。这个边界每个连接器都不一样Hive 和 Iceberg 支持分区裁剪JDBC 类支持部分谓词和函数下推但跨源的 Join 基本都要在 Doris 端完成。我见过一个比较极端的例子有人在 Doris 里挂了一个千万级的 MySQL 表然后跟本地内表做 Join过滤条件写在了 Join 之后而不是子查询里结果每次查询都把整张 MySQL 表拉了过来。这条 SQL 在 MySQL 里跑只要两秒在 Doris 里跑要两分钟。不是功能有问题是执行路径没想清楚。1.3 它不适合谁三种常见的误用有两种用法我建议直接劝退。第一种是把 Multi Catalog 当实时同步用。JDBC Catalog 每次查询都是直连源库源库的负载会跟着你的查询量线性上涨业务库上跑分析查询这事DBA 一般不会同意。第二种是拿它做高频点查。外表查询没有 Doris 内表那种分区裁剪加索引的组合拳延迟下限比内表高一个量级如果 QPS 要求上去了还是要落到内表。第三种更隐蔽一些把跨源 Join 当成常规操作。两个大表跨源 JoinDoris 只能把其中一侧的数据拉到本地做 Hash Join拉的那一侧如果是几千万行网络和内存都吃不消。这种场景要么先把一侧同步成内表要么把 Join 拆成两步在应用层做。我在几个项目里都吃过这个亏后面就养成习惯了——写跨源 SQL 之前先看两边的行数差两个数量级以上才放心写。2. 架构拆解一个 Catalog 背后是什么要理解 Multi Catalog 的行为得先把它在 Doris 里的对象模型理清楚。Doris 原来的命名空间是两级Database 和 Table。引入 Catalog 之后变成三级最外层是 Catalog往下是 Database再往下是 Table完整写法是catalog_name.database_name.table_name。这个改动看着只是加了一层实际上影响了很多东西——权限模型、SQL 解析、元数据缓存的粒度全都跟着变了。系统里有一个内置的internalCatalog就是你建内表的地方它不能被删除也一般不用显式指定。所有通过CREATE CATALOG建出来的都是 External Catalog彼此平级。你可以在一个会话里用SWITCH切换当前 Catalog切换之后写 SQL 就不用带前缀了这个在交互式排查的时候很方便。2.1 Catalog / Database / Table 三层命名空间三层命名空间带来的第一个实际影响是跨源 SQL 的写法变长了但好处是歧义消失了。以前用 External Table 的时候外表和内表在同一个 Database 下名字容易撞而且外表的元数据是跟着表走的改一个连接串就得重建表。改成 Catalog 之后连接信息集中在 Catalog 这一层改密码、换 Metastore 地址、调缓存时间都只需要ALTER CATALOG不影响上层查询。第二个影响在权限上。Doris 的权限体系是跟着命名空间走的Catalog 这一层现在也纳入了授权范围。多租户场景下你可以给 A 业务线只授mysql_orders这个 Catalog 的 SELECT给 B 业务线只授hive_prod的 SELECT物理上是一个集群逻辑上互相看不见。这个能力在以前用外表方案的时候要绕不少弯子。第三个影响容易被忽略Catalog 是有状态的。它维护着一份元数据缓存包含库表列表、列信息、分区信息、行数估算。这份缓存不是你查询的瞬间从源端实时拉的而是按刷新策略周期性更新的。所以源端新建了一张表你在 Doris 里可能过几分钟才看得见这个延迟是设计使然不是 bug。2.2 元数据缓存性能与一致性的跷跷板元数据缓存这块是整个功能里最需要调的地方我见过的问题一半以上跟它有关。缓存的意义在于如果每次查询都去 Hive Metastore 拉一遍库表清单 Metastore 会被打爆查询延迟也下不来。所以 Doris 默认对 Hive Catalog 是开着元数据缓存的刷新间隔按版本不同有差异常见默认是 3600 秒也就是一小时。一小时的延迟在很多场景下是不能接受的。上游新分区写进来了你这边查不到报表就少一天数据。这时候有两个办法一是把metadata_refresh_interval_sec调小比如调到 300 秒二是在 ETL 调度里显式调一次REFRESH CATALOG让缓存主动失效。我更推荐第二种因为它是确定性的——上游任务跑完立刻刷一次比赌一个固定间隔靠谱得多。但刷新这事也不是越频繁越好。缓存刷新会去访问 Metastore如果 Metastore 本身压力就大你把间隔调到几十秒等于给它加了一份不该有的负载。我的经验阈值是分区按小时更新的表缓存间隔设在 10 到 30 分钟加上 ETL 后置刷新兜底按天更新的一小时足够变化极少的配置表甚至可以调到几小时。另外一个细节是 Doris 支持指定刷新某个库或某张表的元数据粒度可以细到表别图省事整个 Catalog 全刷。提示分区信息在部分版本里是查询时按需拉取的表级缓存和分区级缓存的刷新行为不完全一致。遇到表结构更新了但分区看不到的情况先确认分区元数据是不是走的另一条路径别急着改全局参数。2.3 下推算力的边界在哪里下推是联邦查询性能的核心但很多人对它的理解停留在能下推就快这个层面具体哪些能推、哪些不能推说不太清。我按经验归一下类虽然每个连接器的支持程度有差异但大方向是这样的。第一类是分区裁剪这类下推收益最大也最可靠。Hive、Iceberg、Hudi、Paimon 这些湖上的表只要你过滤条件写在分区列上Doris 会把分区条件传下去只扫命中的分区。数据量大十倍扫描量可能只多一倍因为分区没变。第二类是列裁剪这个基本所有连接器都支持。SELECT只取三列源端就只读三列不会把整行拉过来。这个在宽表上收益非常明显尤其是那种几百个字段的日志表。第三类是谓词下推即把 WHERE 里的过滤条件下推到源端执行。湖格式和 JDBC 都支持但支持的函数范围不一样。简单的比较、IN、BETWEEN一般没问题但如果你在过滤条件里套了自定义函数或者类型转换很可能推不下去就退化成把数据拉回来再过滤。第四类是聚合下推支持度最参差。部分连接器能把COUNT、SUM这类简单聚合推到源端复杂的GROUP BY加上HAVING基本都要回 Doris 算。注意判断下推有没有生效最直接的办法是看EXPLAIN输出里对应扫描节点的信息看它有没有把过滤条件带下去。别靠猜也别靠这条 SQL 在源端跑得快来推断。3. 接入实操三类数据源从零到能查下面这部分是我自己反复做过的流程照着走基本能通。整体顺序是先确认网络连通性和账号权限再建 Catalog然后验证元数据能不能读到最后验证数据能不能查。这四步里前两步出问题的概率最高因为报错信息往往不直观。3.1 Hive Catalog最常见也最容易踩坑的一类建 Hive Catalog 需要三样东西Metastore 的 thrift 地址、一个能读 HDFS 的用户名、以及存储的实际路径信息。语法大致如下参数名以你部署的版本为准不同版本略有出入。CREATE CATALOG hive_prod PROPERTIES ( type hms, hive.metastore.uris thrift://10.0.0.11:9083, hadoop.username doris_query, metadata_refresh_interval_sec 600 );这里有三个坑值得单独说。第一个是hadoop.username。这个用户名不是用来连 Metastore 的是用来读 HDFS 文件的。很多人配了 Metastore 的账号密码结果元数据能读、表能看见一查数据就报权限错误问题就在这里。如果集群开了 Kerberos还要额外配 principal 和 keytab这块配置量不小建议提前跟平台团队确认清楚。第二个坑是 MetaStore 的高可用。如果你的 Metastore 是多实例部署hive.metastore.uris里要按逗号分隔把所有地址都写上否则单点挂了整个 Catalog 就不可用。这个参数很多人只写一个地址上线之后才发现问题。第三个坑是 HDFS 的访问路径。Doris 的 BE 节点需要能直接访问 HDFS不是通过 FE 中转。所以如果你的集群是 FE 和 BE 分离部署网络策略要确保 BE 到 NameNode 和 DataNode 的连通性光开 FE 的防火墙是不够的。我遇到过一次FE 上建 Catalog 一切正常一查数据就超时查了半天发现是 BE 到 DataNode 的端口没通。3.2 JDBC CatalogMySQL 与 PostgreSQL 的接法差异JDBC 类的 Catalog 建起来最简单但细节最多。以 MySQL 为例CREATE CATALOG mysql_orders PROPERTIES ( type jdbc, user doris_ro, password ******, jdbc_url jdbc:mysql://10.0.0.21:3306, driver_url mysql-connector-java-8.0.30.jar, driver_class com.mysql.cj.jdbc.Driver, only_specified_database true, include_database_list order_db,user_db, connection_pool_min_size 5, connection_pool_max_size 30 );几个参数值得展开。driver_url支持两种写法一种是本地路径需要你把 jar 包提前放到每个 FE 和 BE 节点的指定目录另一种是 HTTP 地址Doris 自己去拉。生产上我倾向于统一放本地路径因为 HTTP 拉取依赖网络和外部服务多一个故障点。only_specified_database和include_database_list这两个参数是配合用的作用是限定 Doris 只去同步你关心的几个库。为什么重要MySQL 实例上常年躺着一堆information_schema、备份库、临时库如果全量同步元数据加载会很慢查询的时候列表也乱。加上白名单之后加载速度和可读性都会好很多。连接池参数是后加的但很重要。默认池子小并发查询一上来就排队。我一般的配置是最小 5、最大 30具体看你源库能承受多少连接。这里有个必须提醒的点Doris 的连接会持续占用源库的会话如果源库的连接数上限本来就紧张一定要提前跟 DBA 打招呼并且把connection_pool_max_size压到一个安全值。PostgreSQL 的接法差异主要在驱动和 URL 格式上jdbc_url写成jdbc:postgresql://host:port/db驱动类换成org.postgresql.Driver。另外 PostgreSQL 的 schema 概念和 MySQL 的 database 不完全对应Doris 在解析 PG 的库表结构时会做一层映射跨 schema 查表的时候要留意名字对不上。还有一个容易漏的点是lower_case_table_names如果源端表名大小写混用Doris 这边默认转小写之后可能找不到表这个参数在建 Catalog 的时候就要想好。3.3 湖格式 CatalogIceberg 与 Hudi 的注意点湖格式的接入比 Hive 复杂一层因为除了 Metastore还要处理表格式本身的元数据。以 Iceberg 挂 Hive Metastore 为例CREATE CATALOG iceberg_lake PROPERTIES ( type iceberg, iceberg.catalog.type hms, hive.metastore.uris thrift://10.0.0.11:9083, warehouse hdfs://nameservice1/warehouse/iceberg, hadoop.username doris_query );Iceberg 这边最需要注意的是表格式版本和删除文件类型。Iceberg v1 和 v2 的元数据结构不一样部分特性只在 v2 支持。如果表上开了行级删除merge-on-read查询时要额外读删除文件性能会比 copy-on-write 差一截这个在选择表格式的时候就要定下来后面改成本很高。另外 Doris 对 Iceberg 的支持是分版本递进的早期的版本只支持读后来的版本逐步支持写入和更新你用之前一定要翻一下对应版本的文档别按最新文档写 SQL 结果在自己集群上报语法错。Hudi 和 Paimon 的接法类似差异在type和连接参数上。Hudi 有个额外要注意的点是表的类型COW 表和 MOR 表的查询路径不一样MOR 表要合并增量日志延迟和资源消耗都更高。如果你的 Hudi 表是 MOR建议评估一下是不是要定期做 compaction否则查询会越来越慢。提示湖格式的 Catalog 建好之后第一件事不是查数据而是DESC一张表看看列类型映射对不对。Decimal 精度、Timestamp 时区、嵌套结构这几类字段最容易出问题提前发现比后面排查 SQL 结果不对要省事得多。3.4 建完之后怎么验证五条必跑 SQL建完 Catalog 不要急着写业务 SQL按这个顺序验一遍能把绝大多数配置问题挡在前面。-- 1. 确认 Catalog 建出来了 SHOW CATALOGS; -- 2. 确认元数据能读到 SHOW DATABASES FROM hive_prod; -- 3. 确认表能看见 SHOW TABLES FROM hive_prod.some_db; -- 4. 确认列信息正确 DESC hive_prod.some_db.some_table; -- 5. 确认数据能读并且看执行计划 SELECT COUNT(*) FROM hive_prod.some_db.some_table; EXPLAIN SELECT * FROM hive_prod.some_db.some_table WHERE dt 2024-01-01;第五条里的EXPLAIN是关键别跳过。看两件事扫描节点有没有把dt这个分区条件下推以及估算的行数是不是离谱。如果估算行数是 0 或者一个明显不对的数字说明统计信息缺失后面做 Join 的时候优化器会选错顺序。这种情况下需要对外表收集统计信息Doris 支持对部分 Catalog 表做ANALYZE具体支持范围按版本确认。4. 查询性能联邦查询最容易翻车的地方功能能跑通只是第一步真正决定这套东西能不能上生产的是性能。联邦查询的性能问题有个特点单看每条 SQL 都不算慢但放到并发场景下、放到大表 Join 场景下问题会突然放大。我在几个项目里踩过的坑基本都集中在这一块。4.1 跨源 Join 的三种执行路径与选择跨源 Join 在 Doris 里的执行路径我按代价从低到高归成三类。第一类是小表广播。一侧数据量很小比如几万行以内的维度表Doris 会把小表全量拉到内存做成 Hash 表然后另一侧的数据流式过来做探测。这条路径代价最低只要小表确实小基本没风险。第二类是大表拉取。两侧都不小Doris 只能选一侧整体拉过来。这时候网络传输和内存占用都是问题几千万行的表拉过来BE 的内存很容易顶不住而且拉取本身就是串行的瓶颈。这种情况我一般直接判定为不该这么写要么改架构要么先同步。第三类是分片并行拉取。部分连接器支持按分片并行读把大表的读取拆到多个 BE 上同时进行能缓解单点瓶颈但总量还是那么多。这个能力在湖格式上支持得比较好JDBC 类相对弱一些。选择的原则很朴素先看行数再看过滤后的行数最后看能不能把 Join 挪到源端。如果两个表在同一个源里比如都在同一个 Hive 库可以让 Join 在源端完成但 Doris 默认不会这么做还是要拉回本地算。这点要有心理预期别指望它能自动把整个查询推回 Hive 执行。4.2 统计信息为什么你的 Join 顺序总是错的优化器选 Join 顺序靠的是行数估算。行数估算靠的是统计信息。外表如果没有统计信息优化器只能给一个默认值这个默认值往往错得离谱结果就是大表被当成小表广播或者小表被当成大表拉取性能直接崩。湖格式的统计信息一般存在表的元数据里Iceberg 和 Hudi 都有列级统计Doris 能读到情况会好一些。Hive 表的情况参差如果没有跑过 analyze基本就是抓瞎。JDBC 类的外表Doris 能拿到一些基础的行数信息但列的分布信息通常拿不到。实操上的建议是对参与跨源 Join 的外表主动收集一次统计信息。Doris 提供了对外表的 analyze 能力具体命令和ANALYZE TABLE类似执行之后优化器的估算会准很多。这件事做一次能管很久除非数据分布发生剧变。另外一个小技巧是如果你明确知道某张表很小可以在 SQL 里加 hint 引导优化器比赌它的估算要稳。4.3 并发与资源隔离Multi Catalog 的查询走的是 Doris 的普通查询资源池跟内表查询抢同一份 CPU 和内存。这意味着一个失控的外表查询可能把整个集群拖慢这个风险在内表场景下小得多因为内表查询受分区裁剪和索引保护代价相对可控。我一般的做法是给外表查询单独划一个资源组限制它的并发数和内存上限。这样即使有人写了一条全表扫描的 SQL影响范围也被圈住了。同时配合查询超时设置超过阈值直接 kill别让它一直挂着占资源。还有一个容易被忽略的点是连接池和源库的负载。外表查询的并发上去之后Doris 到源库的连接数会跟着涨源库那边的慢查询会拖累 Doris 这边的响应形成互相拖累。这时候要做的是限制单 Catalog 的最大并发而不是加 Doris 的节点。加节点解决不了源库慢的问题只会让源库更慢。5. 故障排查实录这部分是我这些年攒下来的排查记录按现象归类遇到问题可以对号入座。5.1 连接类问题速查连接问题占了所有报错的一大半而且报错信息经常指向不明确的地方。我的排查顺序固定是四步先测网络再测认证再测权限最后测路径。网络这步最容易被跳过。FE 上能 ping 通不代表 BE 能通Doris 的架构里 BE 才是真正读数据的那一端。所以测试连通性一定要在 BE 节点上做telnet或者nc都行端口一个都不能漏——Metastore 是 9083NameNode 是 8020DataNode 是 9866 或者 50010JDBC 类就是对应的数据库端口。认证这步主要看账号密码和 Kerberos。有个坑是密码里有特殊字符写在 PROPERTIES 里需要转义很多人直接贴进去结果密码被截断报认证失败查半天以为是账号权限问题。权限这步区分两件事读元数据的权限和读数据的权限。Hive 里这两个权限是分开的前者在 Metastore后者在 HDFS。能够SHOW TABLES但SELECT报错基本就是这个原因。路径这步针对湖格式。warehouse路径写错、HDFS 上的目录权限不对、对象的存储 endpoint 配错都会表现为表存在但读不到数据。5.2 数据对不上的排查顺序比连接问题更难查的是数据对不上SQL 能跑结果不对。这类问题我按这个顺序查。第一查元数据缓存。源端加了数据Doris 这边还读的是旧的分区列表结果就少了。先REFRESH CATALOG再查一遍如果结果对了问题就在这里去调缓存策略。第二查类型映射。Decimal 的精度被截断、Timestamp 的时区被转换、大整数被当成浮点这些都会导致数值对不上但又不会报错。对比一下DESC出来的列类型和源端的定义。第三查下推后的语义差异。这个最隐蔽。比如源端某个字段是字符串你在过滤条件里写了数字比较源端做了隐式转换Doris 端没做两边结果就不同。还有一种情况是 NULL 的处理源端和 Doris 对 NULL 参与比较的语义可能有差异涉及 NULL 的过滤条件要格外小心。第四查分区。有些表的分区字段值不规范有空字符串、有大小写不一致过滤条件写得太死会漏数据。这个在大数据量场景下很常见建议先SELECT DISTINCT看一下分区字段的取值分布。5.3 一张常见问题速查表现象最可能的原因处理方式建 Catalog 报连接超时BE 到源端的网络不通在 BE 节点上测端口连通性能看到库表但查不到数据HDFS 或对象存储的读权限不足检查hadoop.username对应的账号权限新建的表在 Doris 里看不到元数据缓存未刷新手动REFRESH CATALOG或调小刷新间隔查询慢且扫描行数巨大分区条件未下推看EXPLAIN确认过滤条件在扫描节点上跨源 Join 内存溢出大表被整体拉取先把一侧同步成内表或改写 SQLJoin 结果顺序异常、性能抖动统计信息缺失导致估算错误对外表收集统计信息或用 hint 引导查询报驱动类找不到驱动 jar 未分发到所有节点检查每个 FE 和 BE 的驱动目录并发上来后大量超时连接池耗尽或源库负载过高调连接池上限并给外表查询限流数值精度对不上类型映射时精度被截断对比DESC结果和源端定义分区数据少了几天分区元数据缓存滞后刷新分区元数据检查 ETL 后置刷新这张表覆盖不了所有情况但能挡住八成常见问题。真正难查的是那些偶尔出现一次的问题这类问题我建议直接开 profile 看执行详情别靠猜。6. 落地时我坚持的几条规则用了几年下来有几条规则是我现在做任何 Multi Catalog 项目都会坚持的跟具体业务无关但能省很多事。Catalog 命名带源类型和用途。别叫catalog1、test叫hive_prod_log、mysql_order_readonly这种。原因很简单半年后你回头看一堆 Catalog根本分不清哪个是哪个更别说让同事接手。连接一律只读账号。分析型查询连业务库这是最容易出事的组合。只读账号能挡住大部分误操作虽然挡不住慢查询拖垮源库但至少不会把数据改坏。写入能力单独开一个 Catalog权限单独管。上线前跑一遍全链路压测。不是测单条 SQL 有多快是测并发场景下源库和 Doris 两边的表现。我见过太多次单条查询 3 秒很漂亮十个并发一起来变成 30 秒因为源库那边撑不住。给每个外表查询设超时。联邦查询的代价模型不好估一条 SQL 可能因为分区条件写错而扫全表。设个 60 秒或 120 秒的超时让它自己失败比一直挂着占资源好。别把 Multi Catalog 当成不需要维护的能力。它维护成本比内表低但不是零。驱动版本要跟源库对齐元数据缓存策略要跟着上游调度调连接池要跟着并发量调。把它当成一个需要持续照看的东西而不是装完就不管的开关。最后分享一个我反复用到的小技巧排查跨源查询问题时先把 SQL 拆开单独查每个源确认各自的过滤和结果都正常再拼起来查。跨源问题难查就难在变量太多拆开之后每次只动一个变量定位速度快得多。这个习惯帮我省下的时间比我学过的任何调优技巧都多。