こんにちは。クライアントチームサーバサイドエンジニア、コウです。最近、 Rails で位置ゲーのサーバサイドを実装しました。
なので、 Rails で位置ゲーサーバの実装方法について、3つの Part に分けて紹介させていただきます。
ポーリング API で、スマホのリアルタイム位置情報にもとづいて、何キロ範囲内の全てのマーカーデータをデータベースからとるには、いくつの実装方法があります。
Geohash
最初におすすめされたのは Geohash です。
Geohash は経緯度に基づくジオコーディング方法の一つです。
Geohash の gem を使えば、データベースは MySQL のままでも、実装できます。
でも、 Geohash の gem が大分古かったので、使えるかどうか試しませんでした。
なぜなら、もっと簡単な方法があるからです!
Geohashに興味のある方は、杉山さんの記事「サーバーで付近の情報を通知するサービスのつくり方」がおすすめです。
ElasticSearch
ElasticSearch はオープンソース全文検索エンジンです。
ElasticSearch はリアルタイム位置情報検索が得意だそうです。
あまり詳しくないので、今回はスルーさせてください。
なので、ElasticSearch Geolocation のドキュメント URL だけを貼りますね。
RedisとMongoDB
Redis 3.2 から Geo メソッドがいくつか追加されました。 Sorted Set で Geo 検索できます。2016年にリリースされたばかりなので、使った例がおおくないです。
リアルタイム位置情報のアップデートとセレクトが多い場合、MongoDB の Geospatial がよく使われています。スピードがかなり速いです。
でも今回のマーカーデータは全部事前に登録できる上に、リレーションデータもあります。
MongoDB を使うメリットはそんなに大きくないです。
MySQL Spatial Data
そういうわけで、もう既に MySQL をインストールしたので、 MySQL のSpatial Data についても調べました。
最初は、 MySQL の Spatial Data がいいと思いました。
インデックスも付けられるし、点と点の間の距離計算できます。
だけど!
「ユーザーから N キロメートル範囲内の全てのマーカーを取り出す」という簡単なことができるけど!
SELECT文が(私にとって)かなりわかりづらいです。
SELECT *, ( 6371 * acos ( cos ( radians(78.3232) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(65.3234) ) + sin ( radians(78.3232) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 10 ORDER BY distance;
ref: stackoverflow (英語, published at 2014/02/18)
SET @user = ST_GeomFromText('POINT(139.777254 35.713768)'); SELECT *, ST_Distance_Sphere(@user, lonlat) AS distance FROM markers WHERE ST_Distance_Sphere(@user, lonlat) <= 10000 AND ST_Within( lonlat, ST_Buffer(@user, DEGREES(300/(6370986*COS(RADIANS(ST_Y(@user))))), ST_Buffer_Strategy('point_square')) ) ORDER BY distance;
ref: Shogo’s Blog (日本語, published at 2017/03/28)
sin
cos
acos
radians
が多くないでしょうか?
6371
は何の数字でしょうか?
ST_Distance_Sphere
は遅い、Geometry 型しか使えないことも記事に書いてあります。
そんなこと思いつつ、別の解決方法を探り始めました。
PostgreSQL の PostGIS
「世界で最も人気なオープンソースデータベース」の MySQL から離れて、 ようやく、「世界で最も先進的なオープンソースのデータベース」の PostgreSQL までたどり着きました。 簡単に PostgreSQL + PostGIS のメリットを紹介しましょう。
- リレーションデータベース
- ドキュメント ( HTML / PDF / EPUB ) が完璧
- PostGIS の使いやすさ (Geography 対応)
- RDS の PostgreSQL に PostGIS のイクステンションがあらかじめインストールされている
- ActiveRecord 専用 gem activerecord-postgis-adapter あり、その上、ずっとメンテナンスされている
- 何よりセレクトが速い!!!
ベンチマークの数字からPostGISの性能のよさを証明しましょう。
下記は「railsでユーザーの緯度経度から10キロ以内のレコードを取り出して、距離でオーダーする」のセレクト文のベンチマークです:
User .select("users.*, st_distance(location, 'point(116.458104 39.966293)') as distance") .where("st_dwithin(location, 'point(116.458104 39.966293)', 10000)") .order("distance")
レコード件数 | 10w | 20w | 50w | 100w | 260w | 1000w |
---|---|---|---|---|---|---|
かかった秒数 | 1.2ms | 1.4ms | 1.8ms | 2.5ms | 4.2ms | 15.3ms |
ref: Ruby China #22059 (中国語, published at 2014/10/15)
もう一度いいます!
何よりセレクトが速いです!
結論
なので、今回の仕様に一番ぴったりな解決方法は、 PostgreSQL の PostGIS で実装することでした。
めでたし〜めでたし〜
次回の Part 2 で、 上記にも出てた謎な英単語 「 Geography 」、 と「 Geometry 」について、簡単に話します。
お楽しみにしてください〜(<ゝω・)☆
(/・ω・)/ ★☆★☆★ サーバサイドエンジニア大絶賛募集中 ★☆★☆★ \(・ω・\)