
2026/02/05 20:19
**ジオジョインをH3インデックスで400 倍高速化した手法** - **問題点:** 大規模な空間データセットに対する従来のジオジョインクエリは、ポイント‐イン‐ポリゴン判定やテーブル全体のスキャンが必要だったため遅延が大きかった。 - **解決策:** Uber の H3 ヘキサゴナル階層インデックスシステムを利用し、空間情報を固定サイズセルへ事前集約した。 - **実装手順:** 1. すべてのジオメトリ(点・線・多角形)を適切な解像度で対応する H3 インデックスに変換する。 2. 生成されたインデックスを別テーブルに格納し、H3 キーで索引付けする。 3. ジョイン時には、重複した H3 インデックスをキーとしてマッチさせ、膨大な空間判定処理を回避する。 - **結果:** クエリ遅延が数時間から数分へと短縮され、約 400 倍の高速化を実現。また、選択した解像度内であれば空間的正確性は維持された。 ジオメトリ比較を単純な整数キー検索に置き換えることで、データの忠実度を損なうことなく大幅なパフォーマンス向上を達成しました。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary
この記事は、コストの高い空間述語をH3ベースの集合演算に置き換えることで、遅い二次元空間結合をコンパクトなキーで高速ハッシュ結合へと変換する方法を示しています。各ジオメトリを解像度 3 の少数の H3 セルで覆うことにより、結合は最初にセルを共有する候補ペアをフィルタリングし、その後で正確な
ST_Intersects をその候補のみに適用します。これにより、潜在的に何百万もの交差チェックが、フィルタ済みセットだけに減少し、テストで 400 倍の速度向上を実現しています。この手法は CTE、ビュー、およびサブクエリとシームレスに機能し、追加のマテリアライズドテーブルやスキーマ変更は不要です。したがって、精度を下げるなどの実験も容易になります。高い H3 解像度では偽陽性が減少しますが、形状ごとのセル数が増加し、低解像度ではインデックス作成が簡単ですが、解像度 4 を超えるとセル数の増加により急激に大きくなります。実際には、この書き換えにより 15 ワーカーの Xeon クラスターで結合時間を約 459 秒から約 1.2 秒へ短縮し、正確な一致精度(最終的な ST_Intersects によって偽陽性が除去される)を維持したまま高速な空間分析を可能にしています。本文
Geo ジョインは無害に見える
SELECT * FROM A JOIN B ON ST_Intersects(A.geo, B.geo);
しかしスケールが大きくなると、日常を台無しにするクエリになることがあります。
ジオスペーシャル関数はコストが高く、テーブルが増えるにつれて二次的なループジョインを強制されるため、計算量が指数的に膨れ上がります。
この記事の核心はシンプルです:Floe がこのようなクエリを自動で書き換え、H3 インデックスを活用して劇的に高速化する仕組みを見ていきます。
ジオジョインとは?
ジオジョイン とは
ON 節が空間述語(spatial predicate)になっている任意の結合です。
ST_IntersectsST_CoversST_DWithin- …
代表例:
SELECT * FROM A JOIN B ON ST_Intersects(A.geo, B.geo);
なぜスケールで苦しいのか
現代のデータベースは結合をハッシュジョインに変換して高速化します。
結合キーで両入力をハッシュパーティションすると、各ワーカーは自分の部分だけを比較すればよくなり、計算量が二次から一次へと減ります。
空間述語は明確な結合キーを与えないため、以下のような悪い状況に陥ります:
- すべての値同士を比較しなければならず(二次計算)
- 各候補ペアに対して高価な述語がかかる
この「逃げたい」状態です。
H3 に出会う
H3(元は Uber が開発)は地球をほぼ六角形セルの階層構造で分割します。
重要な2点:
- 階層的解像度 – 粗から細へとレベルを選べる
- コンパクトキー – 各セルは
で、ハッシュ可能・ソート可能・分散可能BIGINT
最も重要なのは、ジオメトリをそのカバーするセル ID の集合として表せる点です。
二つの形状が交差すれば、その H3 カバーセットは少なくとも一つ以上のセルを共有します。
これにより「この二つの図形は交差するか?」という問いを「この二つの集合は重なっているか?」へ書き換え、データベースが単純な等値結合として実行できます。
収束的近似(偽陽性のみ)
- セルカバレッジは正確なジオメトリの近似です。
- 余分な候補を残しておく(偽陽性)は OK:後で正確述語で除去されます。
- 真のマッチを逃す(偽陰性)は NG:事前フィルタで落としてしまえば、後段で回復できません。
したがってカバレッジは形状を過剰に近似します(カバー内に形状が含まれる)。
ジオメトリ述語から集合演算へ
書き換え:正確な結合 → 速いフィルタ + 正確再検査
ベースライン
SELECT * FROM A JOIN B ON ST_Intersects(A.geo, B.geo);
H3 を使う(プランナーがフィルタフェーズを挿入)
の H3 カバレッジを生成A
の H3 カバレッジを生成B- セルで等値結合(高速整数ジョイン)
- 重複候補の除外(同じペアが複数セルにマッチする場合)
- 正確述語を候補だけに適用
実際のテンプレート
WITH a_cells AS ( SELECT a.id, a.geo, c.cell FROM A a JOIN h3_coverage(a.geo, /* resolution */ 3, /* full cover */ true) c ON TRUE ), b_cells AS ( SELECT b.id, b.geo, c.cell FROM B b JOIN h3_coverage(b.geo, 3, true) c ON TRUE ), candidates AS ( SELECT DISTINCT a_cells.id AS a_id, a_cells.geo AS a_geo, b_cells.id AS b_id, b_cells.geo AS b_geo FROM a_cells JOIN b_cells USING (cell) ) SELECT * FROM candidates WHERE ST_Intersects(a_geo, b_geo);
データベースが「無料」で得られるもの
この書き換えにより、重い作業は
(cell) 上の等値結合になります:
- 整数ジョイン → ハッシュコストが低い
- 自然に分散可能 →
でハッシュパーティションできるcell - 高価な述語は後処理になるだけ
読者がよく問う三つの質問
| 質問 | 回答 |
|---|---|
| 近似ってどうなの? | はい、H3 ステップは近似で事前フィルタです。正確性は最終的な再チェックで保証されます。 |
| 偽陽性が増えるのでは? | そうです。期待通りに動きます。目的は候補セットを十分減らし、正確チェックを安価にすることです。 |
| 解像度はどう選ぶの? | 解像度はトレードオフノブです。高いほど偽陽性が減りますが、形状ごとのセル数が増えます。(後述の Numbers で測定方法を紹介) |
効果を見る
ユーザーが「国とその中にある都市」を結合する簡単なクエリを入力すると、プランナーは自動的に書き換えを適用します:
EXPLAIN ANALYZE SELECT * FROM world_cities JOIN countries ON ST_Intersects(world_cities.geo, countries.geo);
出力
Planning time: 2.291 ms rows_actual node 142141 SELECT 142141 FILTER WHERE ST_INTERSECTS(MAX(MAX(world_cities.geo)), MAX(MAX(countries.geo))) geojoin filtered rows ratio: 99.62% 199848 GROUP BY (countries.rowunique, world_cities.rowunique) 199848 DISTRIBUTE ON HASH(world_cities.rowunique),HASH(countries.rowunique)) 199848 GROUP BY PARTIAL (countries.rowunique, world_cities.rowunique) 224075 INNER HASH JOIN ON (COALESCE(h3_coverage_geodesic.h3_coverage_geodesic , $1 , const ) = COALESCE(h3_coverage_geodesic.h3_coverage_geodesic , $0 , const ) ) 147043 |-DISTRIBUTE ON HASH(COALESCE(h3_coverage_geodesic.h3_coverage_geodesic , $0 , const ) ) 147043 | LEFT OUTER FUNCTION JOIN H3_COVERAGE_GEODESIC(world_cities.geo, 3, t, t) ON true 147043 | SCAN world_cities 17223 |-BUILD HASH 17223 DISTRIBUTE ON HASH(COALESCE(h3_coverage_geodesic.h3_coverage_geodesic , $1 , const ) ) 17223 LEFT OUTER FUNCTION JOIN H3_COVERAGE_GEODESIC(countries.geo, 3, t, t) ON true 256 SCAN countries 142141 rows returned Read: 16.92MiB, Distributed: 5.85GiB, Network: 8.00GiB Execution time: 1220.118 ms
書き換えが無ければ、国×都市のペアは 256 × 147043 = 37.6 million 通りに膨れ上がります。
対照的に、今回の実装では
ST_Intersects を 199 848 回 呼び出し、そのうち 142 141 ペア を残すことで 99.6 % の削減 を達成しています。
オンザフライインデックス
一つの方法は、行 ID → H3 セルリストというインデックスタブルを作り、維持することです。
これにより、H3 インデックス計算のやや高価なステップを省けます。
しかし我々は別の道を選びました:クエリ時にカバレッジを生成し、書き換えに組み込みます。
実用性が高い理由
- ビュー・CTE・サブクエリにも適用できる
- 追加ストレージやインデックスメンテナンス不要
- 解像度・カバレッジモード・述語タイプを試しやすい
これにより、クエリ内で直接データクリーニングを行うことが簡単になります:
WITH cleaned_cities AS ( SELECT DISTINCT st_reduceprecision(geo, 100) AS geo FROM world_cities -- 100 m 未満の距離にある都市を重複除去 ) SELECT COUNT(*) FROM countries JOIN cleaned_cities ON ST_Intersects(countries.geo, cleaned_cities.geo);
数値実験
対象データ:国を表す 256 個のポリゴン と、世界都市を表す ポイント の結合。
SELECT * FROM world_cities JOIN countries ON ST_Intersects(world_cities.geo, countries.geo);
このセットでは
countries.geo は平均約 418 頂点の多角形(またはマルチポリゴン)です。15 台のワーカー(Xeon E5‑2695、16 コア @ 2.10 GHz、1 TB メモリ)で実験しました。
まず H3 解像度を変えたときの効果を見てみましょう。
解像度を 1 増やすごとにセルの平均サイズは約 6 倍 減ります。
| Resolution | ベースラインクエリ時間 (s) | GeoJoin 時間 (s) | Speedup | 国インデックス時間 (s) | 都市インデックス時間 (s) | 合計インデックスタイム (s) | GeoJoin‑Index 時間 (s) | インデックス時間 / GeoJoin 時間 | 平均六角形面積 (km²) |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 459.7 | 16.2 | 28.3 | 0.2 | 0.03 | 0.2 | 16.0 | 0.0 | 4 357 449 |
| 1 | 459.7 | 3.0 | 152.7 | 0.1 | 0.0 | 0.1 | 2.9 | 0.0 | 609 788 |
| 2 | 459.7 | 1.4 | 338.8 | 0.1 | 0.0 | 0.1 | 1.2 | 0.1 | 86 801 |
| 3 | 459.7 | 1.1 | 392.5 | 0.3 | 0.0 | 0.3 | 0.9 | 0.3 | 12 393 |
| 4 | 459.7 | 2.2 | 205.0 | 1.4 | 0.0 | 1.4 | 0.9 | 0.6 | 1 770 |
| 5 | 459.7 | 13.2 | 34.8 | 8.2 | 0.0 | 8.3 | 4.9 | 0.6 | 252 |
**最高解像度(3)では GeoJoin が 1.17 秒、つまり 400 倍 の高速化を実現しています。
さらに時間の内訳を見ると:
| 観察 |
|---|
| 低解像度ではインデックス作成が非常に速い(解像度2で全体時間の10%未満)。 |
| 解像度4以降、インデックスタイムは急増。 |
| 高解像度でも行数が多くなると GeoJoin 時間が再び増加する。 |
結論
空間述語を H3 セル上の集合演算に書き換えることで、データベースが得意とする 並列ハッシュジョイン を活用できます。
これにより地理情報操作の速度は劇的に向上します。
さらに改善できる点として、例えば「大きなポリゴン内に十分に深く入っているポイント」では空間述語を呼び出さないようにするなどがあります(次回 Numbers で実装例を紹介予定)。