Apache Druid 空间索引与空间过滤器(Spatial Filter)实战指南
2026/9/23 21:29:21 网站建设 项目流程
  • 数据库
  • OLAP
  • 大数据
  • 后端

【免费下载链接】druid

Apache Druid: a high performance real-time analytics database.

项目地址:https://gitcode.com/gh_mirrors/druid6/druid
点击查看免费下载

本文围绕 Apache Druid 原生查询语言中的空间过滤能力展开,系统讲解如何在摄入阶段构建空间维度(Spatial Dimension)、在查询阶段使用矩形(rectangular)、半径(radius)与多边形(polygon)三种空间边界(Bound)过滤数据,并结合仓库源码深入剖析其底层 RTree 索引与点包含判定原理。读完本文,你将能够配置带空间索引的数据摄入规格,并在 native 查询中编写正确、高效的空间过滤条件。

:::info 说明 Apache Druid 支持 Druid SQL 与 native 查询 两种查询语言。本文所述的空间过滤特性仅适用于 native 语言,Druid SQL 中尚无对应的空间过滤语法。关于 Druid 支持的其他过滤器,参见 Query filters。 :::

空间索引(Spatial indexing)

空间索引(Spatial indexing)是指将空间数据类型(如 geometry、geography)的数据摄入到 Druid 中,使后续查询可以基于"原点 + 边界"(origin and bound)的方式对空间索引列进行过滤。

在 Druid 中,空间维度(spatial dimension)本质上是字符串列,其取值是用逗号分隔的坐标序列,例如"30.0,40.0"。空间维度可以来自输入数据中已有的坐标数组,也可以由多个普通维度拼接构造而来。

空间维度在摄入规格(ingestion spec)的dataSchema组件下的dimensionsSpec对象中进行配置,并且可以采用 Druid 支持的任意数据格式提供。下面是一个使用 Hadoop 摄入(type 为hadoop)的示例,它定义了名为coordinates的空间维度,由输入字段xy构造而成:

{ "type": "hadoop", "dataSchema": { "dataSource": "DatasourceName", "parser": { "type": "string", "parseSpec": { "format": "json", "timestampSpec": { "column": "timestamp", "format": "auto" }, "dimensionsSpec": { "dimensions": [ { "type": "double", "name": "x" }, { "type": "double", "name": "y" } ], "spatialDimensions": [ { "dimName": "coordinates", "dims": [ "x", "y" ] } ] } } } } }

spatialDimensions 配置字段

spatialDimensions数组中的每个空间维度对象由以下字段定义:

属性描述必填
dimName空间维度的名称。空间维度可以由其他维度构造,也可以作为事件的已有字段存在;如果空间维度已经存在,那么它必须是一个坐标值数组。
dims构成该空间维度的维度名称列表。

一个值得注意的细节是:空间维度与普通维度是互斥的。从源码 SpatialDimensionRowTransformer 的实现可以看出,在摄入阶段 Druid 会把所有空间维度以及构成空间维度的部分维度从普通维度集合中剔除,并将它们拼接为逗号分隔的字符串存储;同时该转换器会"丢弃所有无效的空间维度"(源码注释原文:We throw away all invalid spatial dimensions)。因此,同一列不应同时出现在dimensionsspatialDimensions中。

关于摄入规格的完整用法,参见 Ingestion spec reference;关于 Druid 数据加载的一般性介绍,参见 Ingestion。

空间索引的底层存储

从源码结构看,空间维度的索引基于RTree(R 树)实现:

  • 段内索引通过 SpatialIndex 接口暴露,其核心方法getRTree()返回一棵不可变的 R 树(BaseImmutableRTee)。
  • 查询执行时,SpatialFilter 从列的索引供应器(ColumnIndexSupplier)中取出SpatialIndex,调用spatialIndex.getRTree().search(bound)在 R 树上搜索与边界相交的所有叶子节点,再通过bitmapResultFactory.unionDimensionValueBitmaps(search)将命中的位图(bitmap)合并为最终结果。

这意味着空间过滤在大多数情况下可以直接走索引而非逐行扫描,这也是将空间数据建模为空间维度的价值所在。

空间过滤器(Spatial filters)

在 native 查询中,filter 是一个 JSON 对象,用于指明哪些行应被纳入查询计算。Druid 提供了spatial类型过滤器,可以对矩形、半径圆、多边形等空间结构进行过滤。

空间过滤器具有如下结构:

"filter": { "type": "spatial", "dimension": <name_of_spatial_dimension>, "bound": <bound_type> }

一个使用矩形边界的完整示例:

"filter" : { "type": "spatial", "dimension": "spatialDim", "bound": { "type": "rectangular", "minCoords": [10.0, 20.0], "maxCoords": [30.0, 40.0] } }

注意:空间过滤器中维度坐标的顺序必须与spatialDimensions数组中维度坐标的顺序一致。例如,摄入时spatialDimensions["x", "y"]的顺序构造了coordinates,那么过滤时minCoordsmaxCoords也必须是[x, y]的顺序。

在源码层面,native 查询中的空间过滤器由 SpatialDimFilter 表示,它通过 Jackson 注解接收dimensionbound以及可选的filterTuning三个属性,并校验dimensionbound均不可为 null;toFilter()方法将其转换为段执行层使用的 SpatialFilter。SpatialFilter同时实现了位图索引路径与值匹配(ValueMatcher)路径:

  • 索引路径:优先尝试SpatialIndex.getRTree().search(bound)走 RTree 索引(前提是列上存在SpatialIndex,否则退化为全未知位图或逐值匹配)。
  • 匹配路径:内部类BoundDruidPredicateFactory仅支持字符串与对象谓词(makeStringPredicate/makeObjectPredicate),对于 long、float、double 类型的谓词一律返回ALWAYS_FALSE——从源码注释看,"SpatialFilter 当前不支持 long/float/double"(见 SpatialFilter.java),因此空间维度应建模为字符串/对象列。
  • 该过滤器支持向量化(canVectorizeMatcher返回true)。

边界类型(Bound types)

bound属性决定对维度值范围的过滤方式,共支持三种:矩形(rectangular)、半径(radius)与多边形(polygon)。这三种类型在 Bound 接口的 Jackson 子类型声明中注册,对应实现类为RectangularBoundRadiusBoundPolygonBound

矩形(Rectangular)

rectangular边界包含以下元素:

属性描述必填
minCoords最小维度坐标列表,形式为 [x, y]
maxCoords最大维度坐标列表,形式为 [x, y]

其判定逻辑最为直接:点在 R 树搜索时,凡落在[minCoords, maxCoords]包围盒内的叶子节点即被命中。矩形边界也是其他两类边界的"底座"——例如半径边界正是先构造一个外接矩形进行粗筛,再在矩形内做精确的距离判定。

半径(Radius)

radius边界包含以下元素:

属性描述必填
coords圆心坐标,形式为 [x, y]
radius依据指定单位解释的浮点半径值
radiusUnit半径单位的小写字符串,默认值为euclidean。允许的单位:euclideanmetersmileskilometers

从 RadiusBound 的实现可以看到半径过滤的完整流程:

  1. 构造外接矩形粗筛:构造时通过getMinCoords/getMaxCoords将圆心坐标各维分别减去/加上radius,得到一个内接于圆的矩形作为 R 树搜索的"包围盒"(super(getMinCoords(coords, radius), getMaxCoords(coords, radius), limit)),用于在 R 树上快速定位候选点集。
  2. 精确距离判定contains()方法按单位区分两种算法:
    • 单位euclidean(欧氏距离):直接比较各维坐标差平方和与radius²的大小(total <= Math.pow(radius, 2));
    • 单位meters/miles/kilometers(地理距离):使用 RTreeUtils.calculateHaversineDistance 计算两点间的大圆距离(Haversine 公式),并与按单位换算后的米制半径(radius * radiusUnit.getMetersMultiFactor())比较。

RadiusUnit枚举(见 RadiusBound.java)中,euclideanmeters的换算系数为 1,miles为 1609.344 米,kilometers为 1000 米;当radiusUnit未指定时默认取euclidean。这意味着:如果你的坐标是经纬度(如[lon, lat]),应使用meters/miles/kilometers单位以获得符合地理语义的距离;如果你的坐标是平面直角坐标,则使用默认的euclidean即可。

多边形(Polygon)

polygon边界包含以下元素:

属性描述必填
abscissa多边形各角点的水平坐标(横坐标)
ordinate多边形各角点的垂直坐标(纵坐标)

abscissaordinate按角点一一对应,共同描述多边形顶点序列。多边形至少需要 3 个角点(源码 PolygonBound.from 中通过Preconditions.checkArgument(abscissa.length > 2)强制要求),且abscissaordinate的长度必须相等。

从 PolygonBound.contains 的实现可以看出,多边形包含判定采用经典的射线法(odd-even rule)

  1. 若点恰为某个角点(abscissa[i] == coords[0] && ordinate[i] == coords[1]),直接返回true
  2. 若点落在某条水平边上(isPointLayingOnHorizontalBound),返回true
  3. 否则,从点出发向水平方向引射线,统计与多边形边界的交点个数,交点个数为奇数则点在多边形内(oddNodes变量翻转),否则在外;当交点恰好经过顶点横坐标时(intersectionPointX == coords[0])也判定为点在多边形上。

因此多边形边界支持任意凸多边形与凹多边形,且"点在边界上"的情况会被判定为命中。

实战:完整的空间过滤查询示例

将上述内容组合起来,一个完整的时间序列(timeseries)native 查询可以写成:

{ "queryType": "timeseries", "dataSource": "DatasourceName", "intervals": ["2013-01-01/2013-01-07"], "granularity": "day", "aggregations": [ { "type": "count", "name": "rows" } ], "filter": { "type": "spatial", "dimension": "coordinates", "bound": { "type": "radius", "coords": [10.0, 20.0], "radius": 1000.0, "radiusUnit": "kilometers" } } }

该查询会从coordinates空间维度中筛选出以(10.0, 20.0)为圆心、半径 1000 公里(按 Haversine 大圆距离计算)范围内的所有行。若将bound换成rectangularpolygon,即可实现矩形范围或任意多边形范围的空间过滤。

验证与测试

仓库中提供了丰富的测试用例,可用于验证空间过滤的语义与索引行为:

  • SpatialFilterTest:构造了 5000 个随机点(NUM_POINTS = 5000),在增量索引段(IncrementalIndexSegment)与磁盘段(QueryableIndexSegment)两种段形态上,对RectangularBoundRadiusBound等进行参数化测试,并交叉验证索引位图结果与逐行匹配结果的一致性。
  • SpatialFilterBonusTest:对空间过滤的补充性场景测试。
  • IndexMergerV9WithSpatialIndexTest:验证 V9 段合并(IndexMergerV9)过程中空间索引的正确构建与持久化。

小结与使用建议

  • 空间维度是字符串列,值为逗号分隔的坐标序列;在dimensionsSpec.spatialDimensions中声明,可由其他维度构造,也可直接使用事件中已有的坐标数组。
  • 空间过滤是 native 查询专属能力,Druid SQL 暂不支持;过滤器通过dimension+bound描述,坐标顺序必须与摄入时的spatialDimensions顺序一致。
  • 三种边界各有所长rectangular适合规则矩形范围;radius提供欧氏距离与米/英里/千米三种地理距离单位,经纬度场景请使用地理单位;polygon支持任意凸凹多边形,采用射线法判定。
  • 性能上优先走 RTree 索引:只要列上存在空间索引,SpatialFilter就会通过SpatialIndex.getRTree().search(bound)直接产出命中位图,无需全量扫描;同时它支持向量化匹配路径,可被集成到向量化查询执行中。

结合查询过滤器与摄入规格文档,你可以在自己的 Druid 集群中快速落地基于空间范围的数据筛选能力。

  • 数据库
  • OLAP
  • 大数据
  • 后端

【免费下载链接】druid

Apache Druid: a high performance real-time analytics database.

项目地址:https://gitcode.com/gh_mirrors/druid6/druid
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询