はじめに
趣味でわいわい作っているサービスがあります。このサービスに検索機能をつけたいという話になりました。
最新の人気Webサービス・アプリが見つかる Service Safari
このサービスは新サービスがキュレーターによって毎日投稿されて、それがアプリとかWebとかメールで見れるというサービスです。現状でも名前で検索したり、タグで検索したりはできるのですが、今回は複数タグでの検索や紹介文の全文検索機能をつけたいという要件です。MySQLだとつらそうなのでElasticsearchを使ってみることにしました。
- サービス名の部分一致で検索できる
- サービス紹介文の全文検索ができる
- 複数のタグでの検索ができる
- サービスURLのドメインで検索ができる
上記の項目で複合検索ができることをゴールとします。
まったくのElasticsearch初心者がやっていることなど間違っているところもあるかもしれません。あらかじめご了承くださいm(__)m
VPSに環境構築
Elasticsearchインストール
インストール自体はめちゃくちゃ簡単でyumでできます。Javaが必要です。
[elasticsearch-2.x]
name=Elasticsearch repository for 2.x packages
baseurl=http://packages.elastic.co/elasticsearch/2.x/centos
gpgcheck=1
gpgkey=http://packages.elastic.co/GPG-KEY-elasticsearch
enabled=1
# yum install java
# yum install elasticsearch
プラグインのインストール
Elasticsearchにはプラグインの仕組みがあり、簡単にいろん以下の3つのプラグインをインストールします。
- analysis-kuromoji: 日本語を全文検索用
- elasticsearch-analysis-url: URL解析用
- elasticsearch-kopf: 管理画面
# cd /usr/share/elasticsearch/
# bin/plugin install analysis-kuromoji
# bin/plugin install lmenezes/elasticsearch-kopf/v2.1.1
# bin/plugin install https://github.com/jlinn/elasticsearch-analysis-url/releases/download/v5.0.0.0/elasticsearch-analysis-url-5.0.0.0.zip
バージョンなどを合わせないとインストール失敗するらしいのでgithubのページなどを参考にしつついれます。
Elasticsearchの基礎
インストールが終わったら、Elasticsearchの基礎について学んでおきます。
用語や概念
Elasticsearch システム概要 – Hello! Elasticsearch. – Medium
この辺の記事を読めばなんとなくわかった気になれます。要点としては、
- Index: RDBのデータベース
- Type: RDBのテーブル
- Document: RDBのレコード
--
- Cluster: Elasticsearchのプロセスの集合体(Nodeの集まり)
- Node: Elasticsearchプロセスの1つ1つ
- Shard: Indexを物理領域的に分割したもの
--
- Filter: 与えられた文字列を前後処理するもの。例えばスペースを取り除くなど。
- Tokenizer: 与えられた文字列を分割するもの。分割した文字列でインデックスが作成される。例えばURLを受け取って、ドメインに分割するなど。
- Analyzer: FilterとTokenizerを組み合わせた処理機構。
という感じでしょうか。正直Node数やShard数をどのくらいに設定するのが適切なのかはまったくわかってません。今回試した例では、2 Nodeでやってますが、お遊びサービスなので本番では1 Nodeで運用しようかと思っています。
Index設計について
Elasticsearchのインデックス定義を設計する手順 - $shibayu36->blog;
こちらの記事がとても参考になりました。要は想定される入力を、
- どのように整形するか
- どのように分割するか
みたいなことを考えてそれを設定に落とし込む作業をします。
設定
Elasticsearchの設定
ザクッと書くとこんな感じになりました。
- network.bind_host: ElasticsearchがbindするIPアドレスで複数設定できる。
- network.publish_host: これは同クラスタの他のノードと通信するようのIPアドレス(たぶん)
- discovery.zen.ping.unicast.hosts: 同クラスタの他のノードのIP(もっとスマートな方法はないものか)
- index以下はアナライザーの設定になります
今回は2Nodeを想定しているので、2つのサーバに同じ設定で discovery.zen.ping.unicast.hosts
だけ変更したものを設定します。つまりどころとしては、Firewallで9200と9300ポートを開けるのを忘れないこと。あと今回はグローバルIP降ってますが、通常運用の場合は開けないと思います。
cluster.name: service-safari
node.name: ss1
network.bind_host: ["グローバルIP", "プライベートIP"]
network.publish_host: "プライベートIP"
discovery.zen.ping.multicast.enabled: false
discovery.zen.ping.unicast.hosts: ["クラスタリングするサーバのプライベートIP"]
index :
analysis :
analyzer :
ja :
type : custom
tokenizer : ja_tokenizer
char_filter : [
html_strip,
kuromoji_iteration_mark
]
filter : [
lowercase,
cjk_width,
katakana_stemmer,
kuromoji_part_of_speech
]
ja_ngram :
type : custom
tokenizer : ngram_ja_tokenizer
char_filter : [html_strip]
filter : [
cjk_width,
lowercase
]
url_host:
tokenizer: url_host
tokenizer :
ja_tokenizer :
type : kuromoji_tokenizer
mode : search
# user_dictionary : /etc/elasticsearch/userdict_ja.txt
ngram_ja_tokenizer :
type : nGram
min_gram : 2
max_gram : 3
token_chars : [letter, digit]
url_host:
type: url
part: host
filter :
katakana_stemmer :
type : kuromoji_stemmer
Index設定
サービスサファリという名前のIndexを作る場合
PUT /servicesafari
{
"settings": {
"index": {
"number_of_shards": "3",
"number_of_replicas": "1"
}
},
"mappings": {
"posts": {
"_source": {
"enabled": false
},
"_all": {
"enabled": false
},
"properties": {
"name": {
"analyzer": "ja_ngram",
"type": "string"
},
"tags": {
"type": "string"
},
"description": {
"analyzer": "ja_ngram",
"type": "string"
},
"url": {
"type": "string",
"analyzer": "url_host"
},
"related_links": {
"type": "string",
"analyzer": "url_host"
},
"created_at": {
"format": "YYYY-MM-dd HH:mm:ss",
"type": "date"
}
}
}
}
}
データの登録
POST /servicesafari/posts/1
{
"created_at" : "2016-11-01 12:00:00",
"name": "サービスサファリ - ServiceSafari",
"description": "最新のサービスが知れるメディアです。",
"tags": ["メディア", "ポータル"],
"url": "http://www.service.safari.com",
"related_links": [
"https://www.producthunt.com/",
"https://techcrunch.com/",
"http://eiei19.hatenablog.com/"
]
}
データの検索
POST /servicesafari/posts/_search
{
"query":{"match":{"name":"Service"}}
}
この辺の記事が参考になります。
elasticsearchとSQL対比しながら理解 - Qiita
Elasticsearchのbool queryを利用してAND OR NOTを書いてみる - Qiita
bulkでインポートとか
{ "create" : { "_index" : "ss", "_type" : "posts", "_id" : "1" } }
{ "created_at" : "2016-11-01 12:00:00", "name": "サービスサファリ - ServiceSafari", "description": "最新のサービスが知れるメディアです。", "url": "http://www.service.safari.com", "related_links": [ "https://www.producthunt.com/", "https://techcrunch.com/", "http://eiei19.hatenablog.com/" ]}
{ "create" : { "_index" : "ss", "_type" : "posts", "_id" : "2" } }
{ "created_at" : "2016-11-01 12:00:00", "name": "Yahoo Japan", "description": "昔からある老舗のポータル", "url": "http://yahoo.co.jp", "related_links": [ "http://news.yahooo.com", "http://nifty.com", "http://google.co.jp" ]}
curl -s -XPOST http://IPアドレス:9200/_bulk --data-binary "@requests.json"; echo
公式ドキュメントを読むべし。
Bulk API | Elasticsearch Reference [5.0] | Elastic
まとめ
以上、Elasticsearchの設定をまとめてみました。あとはアプリケーション側から操作する仕組みを整えていけば使えそうな感じがします。
最新の人気Webサービス・アプリが見つかる Service Safari
Service Safariは趣味のサービスですが、一緒に開発やサービスグロースしてくれるメンバーを募集しています。未経験歓迎なので興味があれば @eiei19 までご連絡ください。Twitterも同じです。