ElasticSearchでの位置情報取り扱いの性能を検証する
背景
“ElasticSearchで地理情報も扱う"で、ElasticSearchで全文検索だけで無く位置情報の検索も同時に取り扱うことを試しました。
今回はそのElasticSearchでの位置情報利用が実用に足るかの検証を行います。
テストデータ
今回ベンチマークで使用するデータは、国土交通省が公開している位置参照情報ダウンロードサービスを利用させて頂きます。
今回はその中でも東京都のデータを使用しました。
東京都のデータだけでも解凍したCSVで約30MB、データは291104件あるのでテストデータとしては十分でしょう。
ちなみに、都道府県別データの中で最大なのは愛知県で、解凍したCSVが約116MB、データは1127623件あります。
ダウンロードしたCSVファイルの文字コードはShift-JISなので、後で扱いやすいようにnkf -Sw
などして変換しておきます。
また、ヘッダーが日本語なので適当にアルファベットに変更しました。
データサンプル
"pref","city","town","number","ref","x","y","lat","lon","house","rep","before_update","after_update"
"東京都","千代田区","麹町六丁目","5","9","-34965.0","-9246.0","35.684800","139.731181","1","1","0","0"
"東京都","千代田区","神田神保町一丁目","58","9","-33443.9","-6898.1","35.698530","139.757108","1","1","0","0"
"東京都","千代田区","神田神保町一丁目","60","9","-33427.9","-6863.2","35.698675","139.757493","1","1","0","0"
"東京都","千代田区","神田神保町二丁目","42","9","-33505.0","-7006.9","35.697979","139.755906","1","1","0","0"
"東京都","千代田区","神田神保町二丁目","44","9","-33493.4","-6981.9","35.698084","139.756182","1","1","0","0"
テストデータはrailsルートのdata/test_data.csv
に配置しました。
データ取り込み
今回、取り扱うCSVのファイルサイズが大きめなので、smarter_csv
のgemを使います。
Gemfile
にgem 'smarter_csv'
を追加してbundle install
します。
次に、データの取り込み用にrakeタスクを作成してみます。
bundle exec rails g task data
lib/tasks/data.rake
namespace :data do
desc "テストデータの取り込み"
task :import => :environment do
smarter_csv_options = {
convert_values_to_numeric: true,
headers_in_file: true,
col_sep: ",",
skip_blanks: true,
chunk_size: 100,
header_converters: :symbol
}
file = "data/test_data.csv"
SmarterCSV.process file, smarter_csv_options do |chunk|
chunk.each do |c|
hash = {
address: "#{c[:pref]}#{c[:city]}#{c[:town]}#{c[:number]}",
latitude: c[:lat],
longitude: c[:lon]
}
Pin.create hash
end
end
end
end
これで以下のコマンドで全データをインポートします。
bundle exec rake data:import
ページネーション
29万件のデータを取り込んだので、そのままindexにアクセスすると大変なことになりそうです。
そこでページネーション機能を追加します。
ページネーションにはkaminariを使います。
tireがkaminariと互換性があるため、tire.search
にpage: (params[:page] || 1)
を追加するだけです。
app/models/pin.rb
def self.search(params)
tire.search(load: true, page: (params[:page] || 1)) do
query {
string "name:#{params[:search]} address:#{params[:search]}"
} if params[:search].present?
filter :geo_distance, {
distance: "#{params[:distance].present? ? params[:distance].to_f : 10}km",
location: {lat: params[:lat].to_f, lon: params[:lon].to_f}
} if params[:lat].present? && params[:lon].present?
end
end
viewもページネーションに対応させるので、app/views/pins/index.html.haml
の下部に以下のコードを追加します。
app/views/pins/index.html.haml
= paginate @pins
これでページネーション機能が追加できました。
ベンチマーク
東京駅を中心として、半径1km以内で、住所に「八重洲」を含むピンを検索してみました。
検証の環境はMacBook Air 13-inch Mid 2011, プロセッサ:1.7GHz Intel Core i5, メモリ 4GBです。
Processing by PinsController#index as HTML
Parameters: {"utf8"=>"✓", "search"=>"八重洲", "lat"=>"35.680685897019096", "lon"=>"139.76755142211914", "distance"=>"1"}
Pin Load (0.5ms) SELECT `pins`.* FROM `pins` WHERE `pins`.`id` IN (1994, 2872, 2973, 4049, 4051, 4056, 2627, 2691, 2797, 2831)
Rendered pins/index.html.haml within layouts/application (32.1ms)
Completed 200 OK in 106ms (Views: 89.9ms | ActiveRecord: 0.5ms)
100msで返ってくるなら十分ではないでしょうか。
半径の指定や検索キーワードをいろいろ試してみても十分な速度でした。