【実務・中級編】 Geospatial:地理空間情報 – Redis

Redis地理空間情報(Geospatial)の深層:数百万の座標をミリ秒で裁く実務設計の流儀

テックリードの私だ。コードレビューや設計レビューで、「とりあえず地理情報だからPostGISで」「いや、手軽にRedisのGEO系コマンドで」という議論が交わされるシーンによく遭遇する。

言っておくが、RedisのGeospatial機能を単なる「緯度経度を保存するおもちゃ」だと思って使っているなら、今すぐそのコードを止めてほしい。

RedisのGeospatialは、内部的にはSorted Set(ZSET)とGeohash(ゲオハッシュ)という極めてプリミティブかつ洗練されたデータ構造のラッパーに過ぎない。しかし、この「割り切り」こそが、RDBでは絶対に真似できない圧倒的なスループットとレイテンシを生み出している。

今回は、実務の現場で数百万件のロケーションデータを扱い、ミリ秒単位の近傍検索を求められるシステムを構築するエンジニアに向けて、RedisのGeospatialの正体と、現場で生き残るための設計パターンを叩き込む。

—

1. Redis Geospatialの正体を暴く:裏側では何が起きているのか?

まず、Redisの公式ドキュメントには載っていない(あるいは見落とされがちな)事実から共有しよう。

Redisには「地理空間データ型」というネイティブなデータ型は存在しない。

`GEOADD` コマンドを叩いた瞬間、Redis内部で何が起きているか?
答えはシンプルだ。緯度・経度が「52ビットの整数(Geohash)」にエンコードされ、それが通常の Sorted Set(ZSET)のスコアとして格納されている。

[ 緯度・経度 ] —> (Geohashアルゴリズム) —> [ 52bitの整数値 ] —> (ZSETのScoreとして保存)
[ メンバー名 (店舗IDなど) ] —> (ZSETのValueとして保存)

この設計を知っているかどうかで、エンジニアとしての格が決まる。
つまり、RedisのGEO系コマンドは、本質的には 「ZSETに対する高度な数値範囲クエリの糖衣構文(シンタックスシュガー)」 なのだ。

このアーキテクチャが生み出すメリットとデメリットを整理する。

  • メリット: ZSETの超高速なインメモリ操作の恩恵をそのまま受けるため、O(N)(Nは検索範囲内の要素数)の爆速な検索が可能。メモリ効率も極めて高い。
  • デメリット: あくまで球面上の近似値(Mercator図法やWGS84に基づく計算)であり、厳密なポリゴン判定や高低差(Z軸)を考慮した立体的なルーティングには向かない。

—

2. 主要コマンドの実務的解釈と罠

ここでは、主要なコマンドの挙動を実務の文脈で分解する。生半可な理解で使うと、プロダクション環境で痛い目を見るコマンドもある。

`GEOADD`:空間へのデータ投入

渋谷駅と新宿駅の座標を登録する
127.0.0.1:6379> GEOADD locations 139.701636 35.658034 “shibuya” 139.700439 35.689606 “shinjuku”
(integer) 2

実務の知見:
キー `locations` の実体はZSETだ。したがって、`ZRANGE` や `ZREM` といった通常のZSET用コマンドで直接操作・削除することも技術的には可能だ(推奨はしないが、デバッグ時には知っておくと救われることがある)。

`GEODIST`:2点間の距離計算

渋谷から新宿までの直線距離をキロメートル(km)で取得
127.0.0.1:6379> GEODIST locations shibuya shinjuku km
“3.5343”

実務の知見:
計算にはHaversine(ハヴァサイン)公式が使われる。地球を完全な球体と仮定しているため、厳密な測地線距離とはわずかな誤差が生じるが、通常の商用サービス(店舗検索など)で問題になるレベルではない。

`GEOPOS`:座標の逆引き

127.0.0.1:6379> GEOPOS locations shibuya
1) 1) “139.70163459157943762”
2) “35.65803380063273155”

実務の知見:
保存した座標がそのまま返ってくるわけではない。Geohashのビット精度に丸められた値が復元されるため、微小な浮動小数点のズレが発生する点に注意せよ。

`GEORADIUS` vs `GEOSEARCH`:歴史的背景と現代の正解

ここが最大のポイントだ。

かつては近傍検索といえば `GEORADIUS` だった。しかし、Redis 6.2以降、`GEOSEARCH` および `GEOSEARCHSTORE` が標準のモダンなクエリインターフェースとなった。

【モダン】中心地から半径5km以内の要素を、距離情報付きで取得
127.0.0.1:6379> GEOSEARCH locations FROMLONLAT 139.702 35.659 BYRADIUS 5 km WITHDIST ASC
1) 1) “shibuya”
2) “3.0012”

テクニカルリードの指示:
新規にシステムを設計するなら、`GEORADIUS` は使うな。全て `GEOSEARCH` に置き換えろ。
`GEORADIUS` はオプションが煩雑であり、将来的に非推奨(Deprecated)になる可能性が高い。`GEOSEARCH` はクエリ条件の分離が洗練されており、可読性もパフォーマンスも優れている。

—

3. 【実務設計パターン】数百万の店舗をさばくスケーラブルな構成

では、実際に我々がプロダクション環境でこの機能をどう設計すべきか。架空の「全国チェーン店舗検索システム」を例に、堅牢な設計パターンを提示する。

データの粒度とメモリ管理の罠

Redisはインメモリデータベースである。日本全国の数百万人のユーザーの現在地や、無数の配送車両の座標をすべて単一のRedisインスタンスのGEOキーに突っ込もうものなら、OOM(Out of Memory)で即座にクラッシュする。

アンチパターン:

  • すべてのユーザーやデバイスの座標をリアルタイムで一つのキーに `GEOADD` し続ける(メモリリークの温床。古いデータの削除(`ZREM` や有効期限設定)を忘れると死ぬ)。

正しい設計パターン:

1. 静的データ(店舗など):
変更頻度が低いマスターデータ(例: `stores:geo`)は、Redisに永続的に保持してOK。
2. 動的データ(ユーザー・車両など):
一時的な位置情報は、必ず `EXPIRE`(有効期限) を併用するか、Redis Streams / Pub/Sub と組み合わせて揮発性を持たせよ。数分更新がないデータは自動消滅させる設計が鉄則だ。

シャーディング(Sharding)の戦略

もし管理すべき地理データが数千万件規模に達する場合、単一のRedisノードではCPUのシングルスレッド限界(ジオハッシュの計算と範囲走査のコスト)にぶつかる。

Redis Clusterを用いる場合、注意が必要だ。
通常のRedis Clusterでは、キー名(Hash Tag `{…}` を含まない場合)に基づいてクラスタ内のどのノード(スロット)にデータが配置されるかが決まる。
つまり、`locations` という一つのキーに全データを詰めると、特定の1ノードに負荷が集中(ホットスポット化)し、Clusterの意味がなくなる。

解決策:グリッド(Geohashのプレフィックス)によるキー分割
エリアごと(例: 関東、関西など)にキーを分ける、あるいはGeohashの精度(上位数ビット)ごとにキーをシャーディングする設計アプローチを取り入れよ。

—

4. パフォーマンス上の注意点と限界を知る

最後に、RedisのGeospatialを実務で使う上で絶対に知っておくべき「チューニングと限界」を伝える。

1. ZSETのサイズと計算量
1つのZSETキーに数百万件を詰め込むと、`GEOSEARCH` のスキャン時にCPUがスパイクする。1つのキーあたり数万〜数十万件程度を目安に分割を検討すること。
2. メモリオーバヘッドの最適化
RedisのZSETは、要素数が少なく、かつメンバーやスコアが特定の条件を満たす場合、内部データ構造として `ziplist`(Redis 7以降は `listpack`) というメモリ効率の極めて高い圧縮表現を使用する。
`zset-max-ziplist-entries` などの設定値をチューニングし、メモリフットプリントを最小限に抑えよ。
3. 厳密な円形・矩形検索の限界
Redisの検索範囲はあくまで「球面上の円(Radius)」または「ボックス(Box)」だ。例えば「渋谷区の境界線(ポリゴン)の中にいるか判定したい」という要件には、RedisのGEO単体では応えられない。
その場合は、Redisで大まかな近傍候補(Rough Filtering)を数件〜数十件に絞り込み、アプリケーション側やRDB(PostGIS等)で正確な境界判定(Precise Filtering)を行う、というハイブリッドなアーキテクチャを採用するのがシニアエンジニアの選択だ。

—

チーフアーキテクトからの総括

RedisのGeospatialは、魔法の杖ではない。しかし、その内部構造(ZSET + Geohash)の本質を正しく理解し、適切なデータ分割と揮発性のコントロールを行えば、RDBでは絶対に到達できない爆速の近傍検索エンジンに化ける。

「手軽さ」に溺れて設計をサボるな。
メモリの限界を見据え、クエリのコストを計算し、必要に応じてリレーショナルデータベースとの役割分担を明確にする。その泥臭いエンジニアリングの積み重ねこそが、落ちない、止まらない、スケールするシステムを作り上げる。

次の設計レビューでは、君たちの口から「このGEOデータ、ZSETの特性を考慮してこう分割します」という言葉が出ることを期待している。

コメント

タイトルとURLをコピーしました