ããã«ã¡ã¯ãããªãã¼æ ªå¼ä¼ç¤¾ã®å è¤ã§ãã
ããªãã¼æ ªå¼ä¼ç¤¾ã§ã¯ Holidayï¼https://haveagood.holidayï¼ ã¨ããæ°è¦ãµã¼ãã¹ã®éçºã»éå¶ãè¡ã£ã¦ãã¾ãã*1
以åæ稿ããè¨äºã§ãç´¹ä»ããããã«ãHoliday ã§ã¯å ¨ææ¤ç´¢ã¨ã³ã¸ã³ã¨ã㦠Elasticsearch ãå©ç¨ãã¦ãã¾ãã
Ruby on Rails ã§æ§ç¯ãããã¢ããªã±ã¼ã·ã§ã³ãã Elasticsearch ãæä½ããã«ã¯ãå ¬å¼ gem ã§ãã elasticsearch-rails ã使ãã®ãã¨ã¦ã便å©ã§ãã ãã¡ãããHoliday ã§ãæ´»ç¨ããã¦ããã£ã¦ãã¾ãã
大æ¹ã®æ©è½ã«ã¤ãã¦ã¯ãã® gem ã§æä¾ããããã®ã§æºè¶³ã ã£ãã®ã§ãããä¸ç¹ã ããHoliday ã®éç¨ããã¦ããä¸ã§å°ããã¨ãããã¾ããã ãããããµã¼ãã¹å ¬éå¾ã®ã¤ã³ããã¯ã¹ã®åæ§ç¯ã§ãã
elasticsearch-rails
gem ã«ã¯ããã¼ã¿ã®ã¤ã³ãã¼ãç¨ã® Rake Task ãæ¢ã«ç¨æããã¦ãã¾ãã
使ãæ¹ã¯é常ã«ç°¡åã§ãä¸è¨ã®ããã«ã¿ã¹ã¯ãã¡ã¤ã«ãä½æãrequire
æãä¸è¡å ããã ãã§ã
# lib/tasks/elasticsearch.rake require 'elasticsearch/rails/tasks/import'
ãããã³ã°ã®åæ§ç¯ããã³ãã¼ã¿ã®ã¤ã³ãã¼ããè¡ãå¦çãå¼ã³åºããã¨ãã§ãã¾ãã
$ bundle exec rake environment elasticsearch:import:model CLASS='Spot' FORCE=y
ãããããã®ã¿ã¹ã¯ãå®è¡ããã¨æ¢åã®ã¤ã³ããã¯ã¹ãä¸æ¸ãããã¦ãã¾ããã¾ã£ãããªç¶æ ã«ä¸åº¦åæåããã¦ããããããã³ã°å®ç¾©ããã¼ã¿ã®ã¤ã³ãã¼ããè¡ããããã¨ã«ãªãã¾ãã ã¤ã¾ãããã®éã¯é©åãªæ¤ç´¢çµæãè¿ããã¨ãã§ããªããªãããããµã¼ãã¹ãåæ¢ããããå¾ãªãã¨ããç¶æ³ã«ãªã£ã¦ãã¾ãã¾ãã
ãµã¼ãã¹ãéç¨ãã¦ããã¨ãããããã³ã°å®ç¾©ãå¤æ´ãããããã¢ãã©ã¤ã¶ã¼ã®å®ç¾©ãè¦ç´ãã¦ã¤ã³ããã¯ã¹ãä½ããªãããããã¨ãããã¨ã度ã èµ·ãã¾ãã ãã®åº¦ã«ã¡ã³ããã³ã¹ç»é¢ãæ²åºããã¨ãªãã¨ãç¶ç¶çã«ã¦ã¼ã¶ã«ãµã¼ãã¹ãæä¾ãããã¨ãã§ããªããªã£ã¦ãã¾ãã¾ãã
ããã§ããµã¼ãã¹ãåãããã¨ç¡ãã¤ã³ããã¯ã¹ãä½ãç´ãããã«ã¯ã©ãããã°ããã®ãã«ã¤ãã¦èããå¿ è¦ãããã¾ãã
æ¬ç¨¿ã§ã¯ãelasticsearch-rails
gem ã使ãåæã§ãåè¿°ã®åé¡ã解決ããæ¹æ³ãå®è£
ä¾ã交ãã¦ç´¹ä»ãããã¨æãã¾ãã
åºæ¬çãªèãæ¹
ç¡åæ¢ã§ã®ã¤ã³ããã¯ã¹åæ§ç¯ãè¡ãããã®ã¢ã¤ãã¢ã¯ãElasticsearch ã®å ¬å¼ããã°ã§ç´¹ä»ããã¦ãã¾ãã Changing Mapping with Zero Downtime
ãã®è¨äºã«ããã¨ãIndex Aliases ã®ä»çµã¿ãå©ç¨ãããã¨ã§ãããå®ç¾ã§ããããã§ãã
Elasticsearch ã§ã¯ãã¤ã³ããã¯ã¹ã«å¯¾ãã¦ãã¨ã¤ãªã¢ã¹ï¼å¥åï¼ãã¤ãããã¨ãã§ãã¾ãã
ä¾ãã°ãspots-v1
ã¨ããã¤ã³ããã¯ã¹ã«å¯¾ãã¦ãspots
ã¨ããã¨ã¤ãªã¢ã¹ãä»ä¸ããå ´åãspots
ã«å¯¾ãã¦è¡ã£ãæä½ã¯ãå®éã«ã¯ spots-v1
ã«å¯¾ãã¦è¡ãããããã«ãªãã¾ãã
ãã®ããã«ãã¦ããã°ãæ°ãããããã³ã°å®ç¾©ã§ã¤ã³ããã¯ã¹ãä½ãç´ãå ´åã«ã¯ãè£å´ã§ spots-v2
ãä½ã£ã¦ãããæºåå®äºå¾ã« spots-v2
ã« spots
ã¨ã¤ãªã¢ã¹ãè²¼ãæ¿ãããã¨ã§ããµã¼ãã¹ãåæ¢ãããã¨ãªãã¤ã³ããã¯ã¹ã®åæ§ç¯ãã§ããã¨ããããã§ãã
ã§ã¯ãä¸è¨ã®ä»çµã¿ãå®è£ ã«è½ã¨ãè¾¼ãã§ã¿ã¾ãã
â» æ¬ç¨¿ã§ç´¹ä»ãããµã³ãã«ã³ã¼ãã¯ãGitHub ä¸ã§ãå ¬éãã¦ãã¾ãã https://github.com/9toon/es-reindexing-sample
ããããã®ä¾ã§ä½¿ã Spot
ã¢ãã«ã®åºæ¬çãªè¨å®ã¯ä»¥ä¸ã®éãã§ãã
# app/models/spot.rb class Spot < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks index_name "#{Rails.env}-#{Rails.application.class.to_s.downcase}-#{self.name.downcase}" mapping do indexes :id, type: 'string', index: 'not_analyzed' indexes :spot_name, type: 'string', analyzer: 'kuromoji' indexes :address, type: 'string', analyzer: 'kuromoji' indexes :location, type: 'geo_point' end settings index: { number_of_shards: 1, number_of_replicas: 0, } def as_indexed_json(options = {}) { 'id' => id, 'spot_name' => name, 'address' => address, 'location' => "#{lat},#{lon}", } end end
index_name
ã¯åç
§ããã¤ã³ããã¯ã¹åãæå®ããã®ã«ç¨ãã¾ãã
å®éã«ã¯ãä¸è¨ã§èª¬æããããã«ååã®ã¨ã¤ãªã¢ã¹ãä½æãããããåç
§ãããã¨ã«ãªãã¾ãã
ã¤ã³ããã¯ã¹ã®ä½æ
ã¾ãã¯ãã¤ã³ããã¯ã¹ãä½æããå¦çãè¦ã¦ã¿ã¾ãã
æ°ããã¤ã³ããã¯ã¹ãä½æãã Rake Task ã¯ä»¥ä¸ã®ããã«ãªãã¾ãã
# lib/tasks/elasticsearch.rake namespace :elasticsearch do namespace :index do desc "Create a new index. Specify IMPORT=1 for rebuilding from resource" task create: :environment do new_index_name = "#{Spot.index_name}_#{Time.now.strftime("%Y%m%d_%H%M%S")}" puts "========== create #{new_index_name} ==========" Spot.create_index!(name: new_index_name) if ENV['IMPORT'].to_i.nonzero? puts "========== import #{new_index_name} from data sources ==========" batch_size = ENV['BATCH_SIZE'] || 1000 Spot.__elasticsearch__.import(index: new_index_name, type: Spot.document_type, batch_size: batch_size) end end end end
ã¤ã³ããã¯ã¹åã«ã¤ãã¦ã¯ãnew_index_name = "#{Spot.index_name}_#{Time.now.strftime("%Y%m%d_%H%M%S")}"
ã¨ãã¦ããããã«ãã¢ãã«å
ã§æå®ãã index_name
ã®æ«å°¾ã«æ¥æãå ãã¦ãã¾ãã
ãããããã¨ã§ãä¸æã«ã¤ã³ããã¯ã¹åãå®ãããã¨ãã§ãã¾ãããããã¤ãã®ä¸ä»£ã®ã¤ã³ããã¯ã¹ããã£ãå ´åã«ãã©ã¡ããããæ°ããã®ããåããããããªãã¨ããå¹æãããã¾ãã
ä¸è¨ã¿ã¹ã¯ã«å«ã¾ãã Spot.create_index!
ã®ä¸èº«ã¯ä»¥ä¸ã®éãã§ãã
# app/models/spot.rb class Spot < ActiveRecord::Base ... class << self def create_index!(name: ) client = __elasticsearch__.client client.indices.create( index: name, body: { settings: self.settings.to_hash, mappings: self.mappings.to_hash } ) end end end
ã§ã¯ããã®ã¿ã¹ã¯ãå®è¡ãã¾ãã
$ bundle exec rake environment elasticsearch:index:create IMPORT=1 ========== create development-esreindexingsample::application-spot_20150924_141353 ========== ========== import development-esreindexingsample::application-spot_20150924_141353 from data sources ========== [INDEX][Spot] Created: development-esreindexingsample::application-spot_20150924_141353
ã¤ã³ããã¯ã¹ãæ£å¸¸ã«ä½æãããã確ããã¦ã¿ã¾ãã
# GET http://localhost:9200/development-esreindexingsample::application-spot_20150924_141353?pretty=1 { "development-esreindexingsample::application-spot_20150924_141353" : { "aliases" : { }, "mappings" : { "spot" : { "properties" : { "address" : { "type" : "string", "analyzer" : "kuromoji" }, "id" : { "type" : "string", "index" : "not_analyzed" }, "location" : { "type" : "geo_point" }, "spot_name" : { "type" : "string", "analyzer" : "kuromoji" } } } }, "settings" : { "index" : { "creation_date" : "1443071633270", "number_of_shards" : "1", "number_of_replicas" : "0", "version" : { "created" : "1070199" }, "uuid" : "TCiNzSnuRIqsj1ZIwM1iOg" } }, "warmers" : { } } }
ãã®ããã«ãdevelopment-esreindexingsample::application-spot_20150924_141353
ã¨ããååã§ãæ£å¸¸ã«æ°ããã¤ã³ããã¯ã¹ãä½ããããã¨ã確èªã§ãã¾ããã
ã¨ã¤ãªã¢ã¹ã®è²¼ãæ¿ã
ãã¨ã¯ããã®ã¤ã³ããã¯ã¹ã«å¯¾ãã¦ã¨ã¤ãªã¢ã¹ãä»ä¸ãããã¨ã§ãæ°æ§ã®ã¤ã³ããã¯ã¹ãåãæ¿ããããããã«ãã¾ãã ã¨ã¤ãªã¢ã¹ãåãæ¿ããã¿ã¹ã¯ã¯ä»¥ä¸ã®éãã§ãã
# lib/tasks/elasticsearch.rake namespace :elasticsearch do namespace :alias do task switch: :environment do raise "INDEX should be given" unless ENV['INDEX'] new_index_name = ENV['INDEX'] puts "========== put an alias named #{Spot.index_name} to #{new_index_name} ==========" Spot.switch_alias!(alias_name: Spot.index_name, new_index: new_index_name) end end end
Spot.switch_alias!
ã®ä¸èº«ã¯æ¬¡ã®ããã«ãªã£ã¦ãã¾ãã
# app/models/spot.rb class Spot < ActiveRecord::Base ... class << self def switch_alias!(alias_name: , new_index: ) client = __elasticsearch__.client old_indexes = client.indices.get_alias(index: alias_name).keys actions = [] actions << { add: { index: new_index, alias: alias_name } } old_indexes.each do |old_index| actions << { remove: { index: old_index, alias: alias_name } } end client.indices.update_aliases(body: { actions: actions }) end end end
ãã®ã¡ã½ããã¯ãå ã»ã©ä½æããã¤ã³ããã¯ã¹ã«å¯¾ãã¦ã¨ã¤ãªã¢ã¹ãä»ä¸ããå¤ããªã£ãã¤ã³ããã¯ã¹ããã¨ã¤ãªã¢ã¹ãé¤å»ããå½¹å²ãæ ãã¾ãã
ãã®ã¡ã½ããã®å é¨ã§ã¯ãupdate_aliases ã¡ã½ãããå¼ã°ãã¾ãã ãã®ã¡ã½ããã«ãã£ã¦ãã¨ã¤ãªã¢ã¹ã®è¿½å ã»åé¤ãä¸åã®ãªã¯ã¨ã¹ãã§åæã«è¡ããã¨ãã§ãã¾ãã
ã§ã¯ãã®ã¿ã¹ã¯ãå¼ã³åºãã¦ã¿ã¾ãããã
$ bundle exec rake environment elasticsearch:alias:switch INDEX='development-esreindexingsample::application-spot_20150924_141353' ========== put an alias named development-esreindexingsample::application-spot to development-esreindexingsample::application-spot_20150924_141353 ==========
ã¨ã¤ãªã¢ã¹ã®è²¼ãæ¿ããæ£å¸¸ã«è¡ããããã確èªãã¾ãã
# GET http://localhost:9200/development-esreindexingsample::application-spot?pretty=1 { "development-esreindexingsample::application-spot_20150924_141353" : { "aliases" : { "development-esreindexingsample::application-spot" : { } }, "mappings" : { "spot" : { "properties" : { "address" : { "type" : "string", "analyzer" : "kuromoji" }, "id" : { "type" : "string", "index" : "not_analyzed" }, "location" : { "type" : "geo_point" }, "spot_name" : { "type" : "string", "analyzer" : "kuromoji" } } } }, "settings" : { "index" : { "creation_date" : "1443071633270", "number_of_shards" : "1", "number_of_replicas" : "0", "version" : { "created" : "1070199" }, "uuid" : "TCiNzSnuRIqsj1ZIwM1iOg" } }, "warmers" : { } } }
ãã®ããã«ãå ã»ã©ä½æããã¤ã³ããã¯ã¹ã«å¯¾ãã¦ãã¨ã¤ãªã¢ã¹ãè²¼ããã¦ããã®ã確èªã§ãã¾ããã
ã¾ã¨ã
ããã¾ã§ã§ãç¡åæ¢ã§ã®ã¤ã³ããã¯ã¹åæ§ç¯ãè¡ããç°å¢ãæ´ãã¾ããã
ãããã³ã°ã®åå®ç¾©ãã¢ãã©ã¤ã¶ã¼ã®è¨å®å¤æ´ãç¡åæ¢ã§è¡ããããã«ãªãã¨ãæ¤ç´¢æ¹åã®æ½çãæ°è»½ã«è©¦ããã¨ãã§ããããã«ãªãã¾ãã æ°ããä½ã£ãå®ç¾©ãã¡ãã£ã¨éããªã¼ã¨ãªãã°ãã²ã¨ã¤å¤ãã¤ã³ããã¯ã¹ã«ã¨ã¤ãªã¢ã¹ãè²¼ãæ¿ãããã¨ã§ãå³åº§ã«ãã¼ã«ããã¯ãããã¨ãå¯è½ã§ãã
ãããã¯ã·ã§ã³ç°å¢ã§ Elasticsearch ã使ãéã«ã¯ãã¤ã³ããã¯ã¹ãç´ã«æå®ããã®ã§ã¯ãªããã¨ã¤ãªã¢ã¹ã使ã£ã¦æå®ããããã«ãã¦ããã°ããµã¼ãã¹ã®éç¨ãé常ã«ããããããªãã®ã§ãªã¹ã¹ã¡ã§ãã
æ¬ç¨¿ããRails 㨠Elasticsearch ã使ã£ã¦ããæ¹ã ã«ã¨ã£ã¦å°ãã§ãåèã«ãªã£ã¦ããã°å¹¸ãã§ãã
ãªã Holiday ã§ã¯ãæ¥æ¬ã®ä¼æ¥ããã£ã¨æ¥½ãããããã¨ã³ã¸ãã¢ãåéãã¦ãã¾ãã ãããèå³ããæã¡ããã ããæ¹ãããã£ãããã¾ãããããã²ãã²ãå¿åãã ããï¼
ãå¿åã¯ãã¡ããã â
*1:å ã ã¯ããªãã¼äºæ¥å®¤ã¨ããã¯ãã¯ããã社å ã®ä¸é¨ç½²ã¨ãã建ã¦ä»ãã§æ´»åãã¦ãã¾ããããä»å¹´ã®4æããã¯ãã¯ãããã®å®å ¨åä¼ç¤¾ã¨ãã¦åå²ããã¾ãã