Geohash符号化の実践原理とMySQL空間インデックス最適化手法

1. Geohash符号化:緯度経度から文字列への変換

Geohashは地球上の任意の位置座標を短い英数字に変換する手法である。本質は二次元の緯度経度を二分探索によって一次元の文字列に変換することにある。経度範囲[-180,180]、緯度範囲[-90,90]をそれぞれ繰り返し二分し、各区間に属するかを0/1で表現する。

例えば東京駅付近の座標(139.7670, 35.6812)で考える。緯度35.68は[0,90]に入るため最初のビットは1、次に[0,45]で35.68は左側なので0、という処理を20回繰り返す。経度も同様にビット列を生成し、奇数ビットに緯度、偶数ビットに経度を配置して交番結合を行い、最後にBase32(紛らわしい文字a/i/l/oを除外)でエンコードする。結果として"xn77"のような文字列が得られる。

実測精度の目安:6文字で約1.2km、8文字で約19m。配送システムでは7文字(約76m)を基準に採用している。

2. MySQL空間インデックスの実践

従来のWHERE句による距離計算ではパフォーマンスが著しく低下する。MySQLの空間関数とGeohashを組み合わせることで劇的に改善できる。

テーブル設計例(GEOMETRY型と空間インデックスを使用):

CREATE TABLE stations (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  geo_location GEOMETRY NOT NULL,
  geo_hash CHAR(8),
  SPATIAL INDEX idx_location(geo_location)
);

データ挿入とGeohash自動生成:

INSERT INTO stations 
VALUES (1, 'ステーションA', 
        ST_PointFromText('POINT(139.7670 35.6812)'), 
        ST_GeoHash(139.7670, 35.6812, 8));

3km圏内検索(Geohash前方一致で絞り込み):

SELECT 
  id, name,
  ST_Distance_Sphere(geo_location, ST_PointFromText('POINT(139.7670 35.6812)')) AS dist
FROM stations
WHERE geo_hash LIKE CONCAT(SUBSTRING(ST_GeoHash(139.7670, 35.6812, 6), 1, 6), '%')
HAVING dist < 3000
ORDER BY dist;

性能比較(10万件データ):

手法応答時間走査行数
従来の距離計算1100ms100000
Geohash前方一致32ms148

3. 精度制御と境界問題の解決

Geohashは符号長が長いほど精度が高くなる。実際の運用では以下の基準で長さを決定する:

function 最適精度(radiusMeters):
  if radiusMeters >= 5000: return 5  # ±2.4km
  else if radiusMeters >= 1000: return 6  # ±610m
  else if radiusMeters >= 100: return 7  # ±76m
  else: return 8  # ±19m

境界問題の対策:近接した二点が異なるグリッドに属する場合がある。中心点の周囲8近傍グリッドを併せて検索する。

# Pythonでの8近傍取得例
def get_neighbors(geohash):
    dirs = ['top','bottom','left','right','topleft','topright','bottomleft','bottomright']
    return [adjacent(geohash, d) for d in dirs]

MySQLでの複合インデックス最適化:

ALTER TABLE stations ADD INDEX idx_prefix (geo_hash(6));

4. 実戦的な性能チューニング経験

大規模物流システム(日数百万件のオーダー)で得られた教訓:

  • ホットデータとコールドデータの分離:繁華街エリアのデータは別テーブルに格納
  • 多段キャッシュ:Redisで500mグリッドのホットデータをキャッシュ、さらにローカルキャッシュで200mグリッドを保持
  • バッチ最適化:車両群をGridでグループ化してから一括検索
  • 経度±180度境界の考慮:
SELECT * FROM locations 
WHERE (geo_hash LIKE 'zb%' OR geo_hash LIKE '01%')
  AND ST_Distance_Sphere(location, POINT(-179.999, 65.123)) < 5000;

以上の最適化により、位置情報クエリが秒単位からミリ秒単位に短縮された。Geohashの本質は正確な座標ではなく、空間関係を高速に絞り込むインデックスであることを理解することが重要である。

タグ: geohash MySQL 空間インデックス 位置情報 パフォーマンスチューニング

9月27日 15:48 投稿