暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

用于 PostGIS 数据性能的 H3 索引

原创 谭磊Terry 恩墨学院 2022-08-27
2417

我最近开始在 Postgres 中使用 H3 十六进制网格扩展,目的是让一些不太快的查询更快。我之前的文章 Using Uber’s H3 hex grid in PostGIS 介绍了 H3 扩展。诚然,该帖子的重点是 PostGIS 重点视图,而不是 H3 重点视图。这篇文章仔细研究了使用 H3 扩展来提高空间搜索的性能。

本文考虑的两种常见空间查询模式是:

  • 最近邻样式搜索
  • 区域分析

设置和焦点

这篇文章使用两个表来检查性能。以下查询将一h3_ix列添加到osm.natural_point 和 osm.building_polygon 表。这种方法使用 GENERATED 列,并向列添加索引。通过这些步骤,我们可以在查询时消除对 PostGIS 连接的需要,以进行粗略的距离搜索。有关安装 H3 扩展及其工作原理的详细信息,请参阅我 之前的帖子。

这篇文章中使用的数据是使用PgOSM Flex加载的 Colorado OpenStreetMap 数据。

首先,将 H3 索引添加到 osm.natural_point 表中。osm.natural_point 计算 H3 索引并在表上创建索引大约需要3秒。

ALTER TABLE osm.natural_point ADD h3_ix H3INDEX
    GENERATED ALWAYS AS (
        h3_geo_to_h3(ST_Transform(geom, 4326), 7)
    ) STORED
;
CREATE INDEX gix_natural_point_h3_ix
    ON osm.natural_point (h3_ix);

要在表上创建 H3 索引,osm.building_polygon 我们需要将ST_Centroid() 步骤添加到定义中。在构建多边形表上运行这些步骤大约需要 12 秒。

ALTER TABLE osm.building_polygon ADD h3_ix H3INDEX
    GENERATED ALWAYS AS (
        h3_geo_to_h3(ST_Transform(ST_Centroid(geom), 4326), 7)
    ) STORED
;
CREATE INDEX gix_building_polygon_h3_ix
    ON osm.building_polygon (h3_ix);

现在为这两个表计算 H3 索引。我的下一步是选择一座建筑物,作为即将到来的空间搜索示例的焦点。我使用它的 osm_id. 以下查询返回 h3_ix,该点显示在以下屏幕截图中的地图上。

SELECT osm_id, name, h3_ix, geom
    FROM osm.building_polygon
    WHERE osm_id = 392853178
;

┌───────────┬────────────────────────┬─────────────────┐
│  osm_id   │          name          │      h3_ix      │
╞═══════════╪════════════════════════╪═════════════════╡
│ 392853178 │ Pangea Coffee Roasters │ 872681364ffffff │
└───────────┴────────────────────────┴─────────────────┘

image.png

上面的查询返回包含 Pangea Coffee Roasters 的分辨率 7 十六进制的 h3_ix 值 ( 872681364ffffff)。我们可以使用该h3_to_geo_boundary_geometry() 函数查看十六进制的多边形。

SELECT h3_to_geo_boundary_geometry('872681364ffffff');

image.png

使用 H3 的 网格遍历函数 很容易找到六边形的环数和半径大小之间的基本关系。以下查询使用该函数包括围绕焦点 ( )h3_k_ring()的六角形的 3 个六角形环。872681364ffffff 碰巧的是,这个 3 环十六进制缓冲区与使用 SRID 3857 计算的 10 公里缓冲区大致对齐。还显示了 10 公里缓冲区以进行比较。

-- Resolution 7, looking out 3 rings -- Roughly 10 km radius!
SELECT b.osm_id,
        ST_Buffer(b.geom, 10000) AS buffer_10km,
        b.geom,
        h3_to_geo_boundary_geometry(
            h3_k_ring(h3_geo_to_h3(ST_Transform(b.geom, 4326), 7), 3)
            ) AS ix_ring
    FROM osm.vbuilding_all b
    WHERE b.osm_id = 392853178
;

image.png

这h3_k_ring()是 H3 指数提供的众多功能之一。查看完整的 API 参考以了解其余功能。

要使半径大 4 倍,我们可以将这两个值乘以 4,以在h3_k_ring()函数中使用 40 公里缓冲区和 12 环十六进制缓冲区。这会产生一个由 469 个六边形组成的网格。

-- Looking out 3*4 (12) rings -- Roughly 10*4 km (40km) radius!
SELECT b.osm_id,
        ST_Buffer(b.geom, 10000*4) AS buffer_10km,
        b.geom,
        h3_to_geo_boundary_geometry(
            h3_k_ring(h3_geo_to_h3(ST_Transform(b.geom, 4326), 7), 3*4)
            ) AS ix_ring
    FROM osm.vbuilding_all b
    WHERE b.osm_id = 392853178
;

image.png

上面的例子表明,我能够在我们已知的测量(10 公里)和更抽象的测量(分辨率为 7 的 3 个六角环)之间快速建立粗略的关系。有一个有点熟悉的参考框架对我们人类和可用性是有帮助的。

最近邻样式搜索

此示例使用 PostGIS 的ST_DWithin()功能来查找距焦点 10 公里范围内的树木。这个使用传统空间连接的查询需要 14 - 25 毫秒才能运行。它返回大约 12,000 行。

EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON)
SELECT b.osm_id, b.name, b.height, n.geom
    FROM osm.building_polygon b
    INNER JOIN osm.natural_point n
        ON n.osm_type = 'tree' AND ST_DWithin(b.geom, n.geom, 10000)
    WHERE b.osm_id = 392853178
;

由 pgMustard 可视化的查询计划 表明该查询非常有效。以下屏幕截图显示了节点的主要概述以及总时间和节点计时。使用了 GIST 索引natural_point,占用了 8ms 的查询时间。
image.png

如果您仔细查看行估计(打开指向 pgMustard 的链接!),您会注意到返回的估计行natural_point与实际行数有很大差异。在此查询的情况下,它“超出了 390.4 倍”。空间连接通常具有查询计划器使用的较差行估计,并且通常是空间查询性能较差的原因。

下一个查询使用来自所选建筑物的 H3 索引,并传递h3_ix给h3_k_ring()函数。这将创建用于连接到osm.natural_point表的 3 环十六进制缓冲区,替换空间连接。使用 H3 索引的查询速度提高了 73%,在 4 - 6 毫秒内返回。查询计划 显示再次使用了两个索引。此查询还返回大约 12,000 行。

EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON)
WITH rng AS (
SELECT h3_k_ring(h3_ix, 3)
            AS ix_ring
    FROM osm.building_polygon b
    WHERE b.osm_id = 392853178
)
SELECT *
    FROM rng b
    INNER JOIN osm.natural_point n
        ON n.osm_type = 'tree'
            AND n.h3_ix = b.ix_ring
;

image.png

增加结果大小

此部分将查询切换为查找 40 公里半径内的建筑物,而不是 10 公里内的树木。通过此更改,以下查询每个返回大约 340,000 行。与 40 公里半径的空间连接ST_DWithin()在 459 毫秒内返回。使用 12 环十六进制缓冲区在大约 100 毫秒内返回,快 78%。

ELECT b.osm_id, b.name, b.height, n.geom
    FROM osm.building_polygon b
    INNER JOIN osm.building_polygon n
        ON ST_DWithin(b.geom, n.geom, 10000*4)
    WHERE b.osm_id = 392853178
;

WITH rng AS (
SELECT h3_k_ring(h3_ix, 3*4)
            AS ix_ring
    FROM osm.building_polygon b
    WHERE b.osm_id = 392853178
)
SELECT *
    FROM rng b
    INNER JOIN osm.building_polygon n
        ON n.h3_ix = b.ix_ring
;

区域分析

上面的查询使用在“this”的“a distance”内查找“those”的模式。本节切换到查询以回答“向我展示跨区域的这种模式”问题。我的帖子使用 PostGIS 在 OpenStreetMap 中查找丢失的过境点 就是这种类型的分析示例。

由于我的测试数据库已经有可用于树木和建筑物的 H3 索引,因此此示例计算每个十六进制内每个建筑物的树木数量。第一个查询使用要素表和h3.hex表之间的空间连接。

该h3.hex表是按照我在上一篇文章“保存六角形”下概述的步骤创建的,以涵盖科罗拉多州。

WITH bldgs AS (
SELECT h.ix AS h3_ix, 'building' AS src, COUNT(*) AS cnt
    FROM osm.building_polygon b
    INNER JOIN h3.hex h ON ST_Contains(h.geom_3857, ST_Centroid(b.geom))
    GROUP BY h.ix
), trs AS (
SELECT h.ix AS h3_ix, n.osm_type AS src, COUNT(*) AS cnt
    FROM osm.natural_point n
    INNER JOIN h3.hex h ON ST_Contains(h.geom_3857, n.geom)
    WHERE n.osm_type = 'tree'
    GROUP BY h.ix, n.osm_type
)
SELECT COALESCE(b.h3_ix, t.h3_ix) AS h3_ix,
        CASE WHEN b.cnt > 0
            THEN 1.0 * t.cnt / b.cnt
            ELSE NULL
            END AS tree_per_bldg
    FROM bldgs b
    FULL JOIN trs t ON b.h3_ix = t.h3_ix
;

下一个查询使用 H3 索引。通过使用这些索引,聚合查询不需要知道数据的“位置”,只需要知道 H3 索引。该h3_ix列基本上只是一小段文字。

WITH bldgs AS (
SELECT h3_ix, 'building' AS src, COUNT(*) AS cnt
    FROM osm.building_polygon
    GROUP BY h3_ix
), trs AS (
SELECT h3_ix, osm_type AS src, COUNT(*) AS cnt
    FROM osm.natural_point
    WHERE osm_type = 'tree'
    GROUP BY h3_ix, osm_type
)
SELECT COALESCE(b.h3_ix, t.h3_ix) AS h3_ix,
        CASE WHEN b.cnt > 0
            THEN 1.0 * t.cnt / b.cnt
            ELSE NULL
            END AS tree_per_bldg
    FROM bldgs b
    FULL JOIN trs t ON b.h3_ix = t.h3_ix
;

使用空间连接的查询耗时 22 秒(查询计划)。

使用 H3 索引的查询只用了 182 毫秒……快了 99%!(查询计划)

缓冲区总和:工作量度

当查看之前在 pgMustard 中使用空间连接的查询计划 时,“缓冲区总和”细节引起了我的注意。以下屏幕截图显示报告使用了 51 GB 的缓冲区!
image.png

我知道所涉及的表的总大小远低于 1 GB。这让我很好奇,所以我问了 pgMustard 的联合创始人 Michael Christofides,缓冲区总和计算的背后是什么。

“pgMustard 做了一些算术来计算(并显示)这些在每个节点的基础上,在缓冲区部分。它现在还粗略地计算出它们中的每一个在 kB/MB/GB/TB 中代表多少数据,假设一个 8kB 块尺寸。

总和只是将所有这些每个节点的数字加在一起的粗略相加!自然地,对于同一个查询,块可以被多次读取,并且对磁盘的读取和写入是双重计算的,但是由于我认为它是“完成工作”的一个非常粗略的衡量标准,我认为总和的效果非常好。它并不完美,我仍然更关注时间安排,但我认为它可以帮助人们理解为什么有些事情比他们预期的要慢!”——迈克尔·克里斯托菲德斯

诚然,这个总数一开始就让我失望了。在查看了许多查询计划的这个值之后,它在我身上成长了!这似乎是一个不错的指标,一个查询正在使用多少数据,这是一件值得关注的事情。已经很强大的工具的另一个重要补充。

概括

这篇文章探索了在 Postgres 中使用 H3 扩展,目的是提高特定类型空间查询的性能。

  • 最近邻样式搜索
  • 区域分析

我为最近邻搜索测试的两个查询使用 H3 索引执行速度提高了 73% - 77%。区域分析速度提高了 99%。这些都是巨大的胜利,尤其是随着数据量的增长和空间查询的复杂性增加。使用这些模式的查询通常需要几分钟才能运行。减少 70-99% 的查询时间对我来说听起来不错!

原文标题:H3 indexes for performance with PostGIS data
原文作者:Ryan Lambert
原文地址:https://blog.rustprooflabs.com/2022/06/h3-indexes-on-postgis-data

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论