Apache Druid 物化视图(Materialized View):基于 derivativeDataSource 的查询加速实战指南

发布时间:2026/9/23 1:19:19
Apache Druid 物化视图(Materialized View):基于 derivativeDataSource 的查询加速实战指南 Apache Druid 物化视图Materialized View基于 derivativeDataSource 的查询加速实战指南【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid6/druid导读Apache Druid 的物化视图Materialized View功能用于解决一类典型场景当查询的 dataSource 包含大量维度而实际查询只涉及其中少数几个维度时每次查询仍须扫描整张表的全部列造成不必要的开销。本文基于当前仓库中的官方文档 docs/development/extensions-contrib/materialized-view.md系统讲解物化视图的两大组成部分——materialized-view-maintenance物化视图维护与materialized-view-selection物化视图选择并深入其源码实现从derivativeDataSourcesupervisor 的提交与增量构建到view查询类型的自动改写与多子查询合并。读完本文你将掌握物化视图扩展的加载方式、supervisor spec 的完整配置、view查询的编写方法以及命中率监控等运维要点。功能概览与适用前提物化视图的本质是用空间换时间为一个维度很多的基表base-dataSource预先构建若干个只包含查询所需维度子集与指标子集的派生表derived-dataSource查询时自动路由到更小的派生表上执行从而显著降低扫描量与计算成本。使用该功能需要满足两个前提见 materialized-view.md加载两个扩展materialized-view-selection与materialized-view-maintenance当前依赖 Hadoop 集群派生数据源的构建走的是 Hadoop 批式索引任务HadoopIndexTask因此本功能目前要求环境中存在可用的 Hadoop 集群。从当前仓库的模块结构可以印证这一点materialized-view-maintenance 负责生成并提交HadoopIndexTask而 materialized-view-selection 负责在 Broker 端改写查询。扩展加载方式遵循 Druid 的标准机制将两个扩展目录放入 Druid 的extensions目录并在common.runtime.properties中配置druid.extensions.loadList使其包含druid-materialized-view-maintenance与druid-materialized-view-selection。Materialized-view-maintenance派生数据源的创建与维护基本概念base-dataSource 与 derived-dataSource在materialized-view-maintenance中用户摄入的数据源被称为base-dataSource。对于每一个 base-dataSource可以提交一个derivativeDataSourcesupervisor用于创建并持续维护其他数据源这些数据源被称为derived-dataSource。派生数据源的维度dimensions和指标metrics必须是 base-dataSource 相应集合的子集。每个derivativeDataSourcesupervisor 只负责一个派生数据源其核心职责是保持派生数据源的时间线timeline与 base-dataSource 保持一致当基表新增、更新或删除了某个时间区间的数据时supervisor 会调度 Hadoop 索引任务重建派生数据源对应区间的 segment。derivativeDataSource supervisor 配置示例以下是一个完整的derivativeDataSourcesupervisor spec 示例来自官方文档基于 wikiticker 数据{ type: derivativeDataSource, baseDataSource: wikiticker, dimensionsSpec: { dimensions: [ isUnpatrolled, metroCode, namespace, page, regionIsoCode, regionName, user ] }, metricsSpec: [ { name: count, type: count }, { name: added, type: longSum, fieldName: added } ], tuningConfig: { type: hadoop } }在 MaterializedViewMaintenanceDruidModule.java 中MaterializedViewSupervisorSpec以derivativeDataSource类型名注册到 Jackson 序列化体系同时DerivativeDataSourceMetadata也注册为同一类型名该模块带有LoadScope(roles NodeRole.OVERLORD_JSON_NAME)注解说明 supervisor 相关逻辑只在 Overlord 节点加载。Supervisor 配置字段说明字段描述是否必填typesupervisor 类型固定为derivativeDataSource。是baseDataSourcebase-dataSource 名称。该数据源的数据必须已经存在于 Druid 中并将作为输入数据。是dimensionsSpec指定数据的维度必须是 base-dataSource 维度的子集。是metricsSpec聚合器列表必须是 base-dataSource 指标的子集。详见 聚合aggregations。是tuningConfig必须是 HadoopTuningConfig。详见 Hadoop tuning config。是dataSource该派生数据源的名称。否默认 baseDataSource supervisor 的 hashCodehadoopDependencyCoordinatesHadoop 依赖坐标的 JSON 数组Druid 将使用它覆盖默认 Hadoop 坐标一旦指定Druid 会从druid.extensions.hadoopDependenciesDir指定的位置查找这些 Hadoop 依赖。否classpathPrefix为 Peon 进程前置追加的 classpath。否context见下文。否关于默认 dataSource 名称的源码细节官方文档描述默认名称为baseDataSource-hashCode of supervisor从源码看更精确的生成逻辑位于 MaterializedViewSupervisorSpec.javathis.dataSourceName dataSourceName null ? StringUtils.format( %s-%s, baseDataSource, DigestUtils.sha1Hex(dimensionsSpec.toString()).substring(0, 8) ) : dataSourceName;即未显式指定dataSource时默认名称为{baseDataSource}-{dimensionsSpec.toString() 的 SHA-1 哈希前 8 位}。这意味着维度组合不同的 supervisor 会得到不同的派生数据源名称而维度组合完全相同的 supervisor 会产生相同的默认名称多个 supervisor 提交到同一派生数据源时需注意避免冲突。此外源码中supervisorId的格式为MaterializedViewSupervisor-{dataSourceName}见 MaterializedViewSupervisor.java提交的任务 ID 前缀为index_materialized_view。Context 配置字段字段描述是否必填maxTaskCountsupervisor 同时提交的最大任务数。否默认 1源码中除maxTaskCount外还支持另一个 context 参数minDataLagMsMaterializedViewSupervisor.javamaxTaskCount默认值为1DEFAULT_MAX_TASK_COUNTminDataLagMs默认值为1 天DEFAULT_MIN_DATA_LAG_MS TimeUnit.DAYS.toMillis(1)其作用是派生数据源与基表之间存在滞后为防止延迟数据导致反复重建时间区间起点距最新区间不足minDataLagMs的区间不会被构建对应hasEnoughLag方法。派生数据源的构建流程源码级解析在 MaterializedViewSupervisor.java 中supervisor 通过scheduleWithFixedDelay周期性执行run()间隔由druid.materialized.view.task.taskCheckDuration控制默认PT1M见 MaterializedViewTaskConfig.java。每次执行的完整链路为校验元数据从元数据存储读取派生数据源的DataSourceMetadata必须是DerivativeDataSourceMetadata且其 baseDataSource、dimensions、metrics 与 spec 完全一致run()。首次启动时supervisor 会调用insertDataSourceMetadata写入该元数据。清理已结束任务遍历runningTasks将状态非 runnable 的任务从运行表中移除checkSegmentsAndSubmitTasks()。计算缺失区间checkSegments对比基表与派生表的 segment 时间线基表有数据而派生表没有的区间 → 需要构建entriesOnlyOnLeft派生表 segment 的 version 不是基表 segment 最大created_date的区间 → 需要重建entriesDiffering且 base version 更大派生表有数据而基表已无数据的区间 → 将对应 segment 标记为 unusedmarkSegmentAsUnused。构建顺序按区间开始时间倒序排列即最新区间的数据最先构建。提交 Hadoop 任务submitTasks在runningTasks.size() maxTaskCount的前提下为每个待构建区间调用 createTask() 生成HadoopIndexTask并加入任务队列。从createTask的实现可以看到任务的内部构造解析格式为timeAndDims的map类型 parser使用ArbitraryGranularitySpecGranularities.NONE即以单一 interval 为粒度构建 segment输入为dataSource类型的HadoopIOConfigDatasourceIngestionSpec指向 base-dataSource 及对应区间的 segment 列表派生 segment 的 version 取基表该区间所有 segment 中最大的created_date。supervisor 的状态与报告可通过 Overlord 的 supervisor API 获取/druid/indexer/v1/supervisor/{supervisorId}报告MaterializedViewSupervisorReport.java中包含 base-dataSource、dimensions、metrics、缺失区间condensed intervals以及健康状态等字段。Materialized-view-selectionview 查询类型与自动优化基本概念在materialized-view-selection中实现了一种新的查询类型view。当发起一个 view 查询时Druid 会基于查询的 dataSource 与 intervals 尽力优化将原始查询改写为一个或多个子查询用更小的派生数据源替换基表。该模块通过 MaterializedViewSelectionDruidModule.java 注册注解LoadScope(roles NodeRole.BROKER_JSON_NAME)表明该逻辑只在Broker 节点加载。模块内部注册MaterializedViewQuery的 Jackson 子类型类型名为view将MaterializedViewQuery绑定到 MaterializedViewQueryQueryToolChest以生命周期方式注册 DerivativeDataSourceManager并绑定单例 DataSourceOptimizer注册DataSourceOptimizerMonitor用于指标上报配置项绑定前缀为druid.manager.derivatives。view 查询示例以下是一个完整的 view 查询示例来自官方文档内层为一个 groupBy 查询取added最大的 1 个user{ queryType: view, query: { queryType: groupBy, dataSource: wikiticker, granularity: all, dimensions: [ user ], limitSpec: { type: default, limit: 1, columns: [ { dimension: added, direction: descending, dimensionOrder: numeric } ] }, aggregations: [ { type: longSum, name: added, fieldName: added } ], intervals: [ 2015-09-12/2015-09-13 ] } }view 查询的字段说明view 查询由两部分组成字段描述是否必填queryType查询类型固定为view。是queryview 查询内包裹的真实查询。真实查询必须是 groupBy、topN 或 timeseries 类型之一。是这一点在源码中得到强制校验MaterializedViewQuery.java 的构造函数通过Preconditions.checkArgument仅接受TopNQuery、TimeseriesQuery、GroupByQuery三种类型其他查询类型会直接抛出 Only topN/timeseries/groupby query are supported 异常。因此view 查询内层不支持 Scan、Search 等其他查询类型。优化执行流程源码级解析view 查询的执行分为两层外层由MaterializedViewQueryQueryToolChest委托给真实查询的 ToolChest 处理merge、排序、指标等均复用真实查询的实现内层则由MaterializedViewQueryRunner调用DataSourceOptimizer.optimize()完成改写。DataSourceOptimizer.optimize() 的核心逻辑如下类型与数据源检查仅当查询是 TopN/Timeseries/GroupBy 且 dataSource 为TableDataSource时才可能被优化否则原样返回。获取候选派生表从DerivativeDataSourceManager.getDerivatives(baseName)取该基表的所有派生数据源集合。该集合是有序的——DerivativeDataSource实现了Comparable按每个 segment 粒度上的平均数据大小avgSizeBasedGranularity升序排列DerivativeDataSource.java平均数据量最小的派生表优先级最高因为它的扫描成本最低。提取必需字段通过 MaterializedViewUtils.getRequiredFields() 从查询中提取所有依赖的列包括 filter 所需列、聚合器所需字段requiredFields()若是FilteredAggregatorFactory还会计入其过滤列、topN 的维度列、groupBy 的维度列。筛选可用派生表仅当派生表getColumns()dimensions ∪ metrics见 DerivativeDataSourceMetadata.java包含全部必需字段时该派生表才可作为候选。若没有候选则记录missFields统计并原样返回查询。按区间切分与改写按优先级遍历候选派生表对查询的每个 interval通过 Broker 的TimelineServerView查找派生表在该区间的实际覆盖serverView.getTimeline(...).lookup(interval)将覆盖到的子区间改写为指向该派生表的新子查询query.withDataSource(...).withQuerySegmentSpec(...)并从剩余区间集合中减去MaterializedViewUtils.minus实现为两个区间列表的差集运算支持不连续区间的拆分与合并。兜底基表所有派生表都覆盖完后若仍有剩余区间则追加一个仍指向基表、仅包含剩余区间的子查询保证结果完整。结果合并MaterializedViewQueryRunner使用MergeSequence按原始查询的结果排序规则将所有子查询结果合并返回MaterializedViewQueryRunner.java。典型场景示例基表base覆盖2011-04-01/2011-04-06派生表derivative只覆盖2011-04-01/2011-04-04。当用户对base发起查询2011-04-01/2011-04-06时优化器会将其拆分为两个子查询一个在derivative上执行2011-04-01/2011-04-04另一个在base上执行2011-04-04/2011-04-06最终合并结果。这一行为在测试 DatasourceOptimizerTest.testOptimize 中有完整的断言验证。派生表信息的同步机制Broker 侧需要知道当前存在哪些派生表及其字段集合这一信息由 DerivativeDataSourceManager 负责它以固定延迟周期性地扫描元数据库的dataSource表SELECT DISTINCT dataSource, commit_metadata_payload FROM ...将commit_metadata_payload反序列化为DataSourceMetadata仅保留DerivativeDataSourceMetadata类型的记录对每个派生表调用getAvgSizePerGranularity计算其每个时间粒度区间的平均 segment 大小查询segments表中used true的记录用总大小除以不同 interval 数并过滤掉大小为 0 的派生表结果以baseDataSource → SortedSetDerivativeDataSource的映射保存在静态引用DERIVATIVES_REF中供优化器实时读取轮询周期由配置项druid.manager.derivatives.pollDuration控制默认PT1M见 MaterializedViewConfig.java。这意味着新建/删除派生数据源后Broker 最多需要等待一个轮询周期默认 1 分钟才能感知变化。命中率与性能监控Broker 的DataSourceOptimizerMonitorDataSourceOptimizerMonitor.java定期调用optimizer.getAndResetStats()并上报以下指标指标含义/materialized/view/query/totalNum可优化查询针对有派生表的基表的总次数/materialized/view/query/hits成功命中派生表的查询次数/materialized/view/query/hitRate命中率 hits / totalNum/materialized/view/select/avgCostMS优化器平均耗时毫秒/materialized/view/derivative/numSelected每个派生表被选中的次数维度为 derivative 名称/materialized/view/missNum因缺少必需字段而无法命中时缺失字段组合的出现次数维度为 fields这些指标可以帮助判断物化视图配置是否合理若hitRate长期偏低且missNum显示某些字段组合频繁缺失说明现有派生表的维度/指标子集与实际查询负载不匹配应调整派生表的字段设计。使用流程小结综合以上两部分在一个已具备 Hadoop 集群的 Druid 环境中启用物化视图的完整流程为加载扩展在common.runtime.properties的druid.extensions.loadList中加入druid-materialized-view-maintenance和druid-materialized-view-selection重启相关服务Overlord 加载维护模块Broker 加载选择模块。确保基表就绪先完成 base-dataSource 的摄入确保其 segment 已发布。提交 supervisor通过 Overlord 的 Supervisor APIPOST /druid/indexer/v1/supervisor提交derivativeDataSource类型的 spec指定维度子集、指标子集与 Hadoop tuningConfig。supervisor 会周期性地对比基表与派生表时间线自动补齐缺失区间并随基表更新而重建。发起 view 查询通过 Broker 的查询 APIPOST /druid/v2提交queryType: view的查询内层为 groupBy/topN/timeseries 之一。Broker 自动完成派生表选择、区间切分与结果合并应用层无需改动。监控与调优通过hitRate、avgCostMS、missNum等指标评估物化视图收益按需调整派生表的字段集合与数量。注意事项与限制官方文档在结尾给出明确提醒Materialized View 目前被标记为 experimental实验特性。使用时请务必保证所有进程Overlord、Broker、Historical、Peon 等的系统时间一致且单调递增否则查询结果可能出现意外错误。这一要求与实现机制直接相关派生表 segment 的 version 取自基表 segment 的created_date时间不一致会导致版本比较失效进而引发错误的区间重建或查询结果错乱。其他限制总结如下依赖 Hadoop 集群构建任务为HadoopIndexTask派生表字段必须是基表字段的子集且至少覆盖查询实际用到的全部字段才能命中内层查询仅支持 groupBy、topN、timeseries 三种类型同一时间最多并行构建的任务数默认只有 1 个可通过context.maxTaskCount调大受minDataLagMs默认 1 天影响最新区间存在滞后派生表数据可能落后于基表Broker 感知派生表变更存在最长一个pollDuration默认 1 分钟的延迟派生表数据是基表数据的预聚合子集若查询需要基表独有的列则无法命中物化视图仍会回退到基表执行。相关仓库资源官方文档docs/development/extensions-contrib/materialized-view.md维护模块主类MaterializedViewSupervisorSpec.java、MaterializedViewSupervisor.java、DerivativeDataSourceMetadata.java选择模块主类MaterializedViewQuery.java、DataSourceOptimizer.java、DerivativeDataSourceManager.java、MaterializedViewUtils.java相关测试MaterializedViewSupervisorTest.java、DatasourceOptimizerTest.java扩展模块定义materialized-view-maintenance/pom.xml、materialized-view-selection/pom.xml【免费下载链接】druidApache Druid: a high performance real-time analytics database.项目地址: https://gitcode.com/gh_mirrors/druid6/druid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考