SlideShare ist ein Scribd-Unternehmen logo
1 von 41
Tritonn から
Elasticsearch
への移行話
2019/06/07
MySQL Casual Talks vol.11
do_aki
@do_aki
@do_aki
http://do-aki.net/
脱Tritonn
Tritonn
• Tritonn := MySQL + Senna
• MyISAM の全文検索をフックするパッチで
実現 (ストレージエンジンではない)
• MyISAM + Senna インデックス
• 検索は MATCH…AGAINST 構文を利用
CREATE TABLE items (
item_id INT,
name TEXT NOT NULL,
description TEXT NOT NULL,
FULLTEXT KEY USING NGRAM,
NORMALIZE, SECTIONALIZE (name,description)
) ENGINE=MyISAM
SELECT item_id, name, description
FROM items
WHERE MATCH (name, description)
AGAINST ('*E-1*D+*W1:10,2:3 検索語 –除外' IN BOOLEAN MODE)
DDL
SQL
Tritonn の今
• 開発停止
• Groonga ストレージエンジン (現在の
Moronga ストレージエンジン)に移行
• Trironn 最終リリースは 2009/11
(MySQL 5.0.87)
Tritonn の今
• 開発停止
• Groonga ストレージエンジン (現在の
Moronga ストレージエンジン)に移行
• Trironn 最終リリースは 2009/11
(MySQL 5.0.87)
現役で稼働中!!
10年前のMySQL
• ハードウェア特性の相違
• Clientライブラリとの互換性
• OS (glibc) との互換性 / 脆弱性
さすがに MySQL の
バージョンアップを
しないとね!
(脱 Tritonn)
移行にあたって重要視したこと
• 従来の検索エクスペリエンスを大きくは変えない
=> 漏れより網羅性重視 / 正規化 / OR,AND,NOT
• 検索速度が従来より極端に遅くならないようにする
=> 厳密な基準は設けなかったが、現状同等以下
• 運用コストはなるべく抑える
=> Tritonn 自体を運用するコストはほぼ0だった
移行案
LIKE に置き換え
転置インデックステーブル自作
InnoDB 全文検索
Mroonga
MySQLで解決
HyperEstraier
Groonga
Solr
Elasticsearch
他 Middle ware 導入
優先して検討
LIKE に置き換え
• 複数語検索が必要な場合は LIKE を OR で結合していく感じ
• 試しに200万件程度のデータに対して MATCH..AGAINST を 単純な
LIKE に置き換えてみたところ ミリ秒単位で返ってきていたクエリ
が数十秒単位に
• データ量が少なく、今後も急には増えないところに対しての妥協案
としてあり -> 他の条件で十分に絞り込め、単純検索で問題ない
ケースについての置き換え策として採用
InnoDB Full Text Search
• MySQL5.6 以降で利用可能な全文検索インデックス(ngram parser
は 5.7 以降)
• Senna 同様 MATCH..AGAINT を使うので、改修箇所が少なくて済
むかもという期待
↓ ↓ ↓
• ngram parser (bigram)だと十分な速度が出なかった (LIKE同様
のデータで数秒程度)
• 現状の挙動と合わせるためには前処理が必要
• ngram における stopword の扱いが微妙……
Mroonga
• Tritonn の後継プロダクト
• ラッパーモードを利用すればほぼ現状と変わらないはず
• とはいえ、せっかく 脱Tritonn するのになーという思いも
• 検証時点では 8.0系には未対応(2019年6月現在も)
-> 今後のバージョンアップの枷になりそう
(ソースコードに手を入れて 8.0 対応の手助けできないか試したのだけど、
力及ばず。。。)
MySQL のみでの解決を断念
-> 他の Middle Ware導入を検討
• Groonga
– 速度は申し分ない
– HAのための冗長構成をすべて自前で組む必要がある
• Solr
– Lucene のフロントエンド
– Solr Cloud
• Elasticsearch
– Lucene をつかった全文検索サーバ
– 組み込みのクラスタ
採用
ngram 対応
形態素 vs ngram
• 転置インデックスに格納される語の単位
• 「東京都の水」
– 形態素:「東京」「都」「の」「水」
辞書依存, 一般的に新語や固有名詞に弱い
辞書のメンテが必要
– bigram:「東京」「京都」「都の」「の水」
インデックスサイズが肥大, 網羅的に検索できる
辞書不要
{"type": "kuromoji_tokenizer"}
{"type": "ngram", "min_gram": 2, "max_gram": 2}
USING NGRAM
• 弊社の Tritonn では ngram インデックスが利用されてた
– もともと 高速な LIKE がほしい的な理由からだった気がする
– 様々なジャンルの商品 -> 未知語が多い
– 適合性より網羅性重視
• Elasticsearch で ngram を使う事例は少ない?
– 日本語検索の多くは kuromoji 利用
• Elasticsearch 採用決定までに試行錯誤してる
「世界に一つだけの花」 に
「世界一」 がマッチする問題
趣旨とは関
係ないけど、
S は小文字
が正しいの
でこれは誤
り
ngram と match
• 「世界に一つだけの花」「世界一」
– index: 「世界」「界に」「に一」「一つ」「つだ」
「だけ」「けの」「の花」
– search: 「世界」「界一」
• match query は デフォルトでは OR
– 「界一」にはヒットしないが、「世界」 にヒットし
たことでマッチ
– ならば AND にしてやれば……
{"match": {"name": "世界一"}}
↓
{"match": {"name": {"query":"世界一", "operator":"and"}}
そんな甘くはない
• @johtani さんからの指摘
– https://twitter.com/johtani/status/10
58195319228268545
match_phrase
• 「世界に一つだけの花」にはマッチしなくなったが
「MySQL界一の地雷職人、世界のyokuさん」にはマッチ
• 解決するために match_phrase を利用
– match や multi_match を使わないという選択
{"match": {"name": "世界一"}}
↓
{"match_phrase": {"name": "世界一"}}
複数フィールドへの対応
• 例:「世界」 と 「花」 を含む name検索
• description フィールドも対象に
• match が使えないので bool query を駆使
↓ ↓ ↓
{"match" : {"name": "世界 花"}}
{"multi_match" :
{"query": "世界 花", "fields": ["name", "description"]}
}
bool query
{
"bool": {
"must": [
{
"bool": {
"should": [
{"match_phrase": {"name": {"世界"}}},
{"match_phrase": {"description": {"世界"}}},
]
}
},
{
"bool": {
"should": [
{"match_phrase": {"name": {"花"}}},
{"match_phrase": {"description": {"花"}}},
]
}
},
]
}
}
検索ワードの解析
• "(世界一 OR 日本一) 地雷職人 –yoku"
– Trironn では、そのまま渡せば意図通り
– 簡易パーサを作って対応
{"bool": {"must": [
{"bool": {"must": [
{"bool": {"should": [
{"match_phrase": {"name": {"query": "世界一"}}},
{"match_phrase": {"description": {"query": "世界一"}}
]}},
{"bool": {"should": [
{"match_phrase": {"name": {"query": "日本一"}}},
{"match_phrase": {"description": {"query": "日本一"}}}
]}}
]}},
{"bool": {"should": [
{"match_phrase": {"name": {"query": "地雷"}}},
{"match_phrase": {"description": {"query": "地雷"}}}
]}},
{"bool": {"must_not": [
{"bool": {"should": [
{"match_phrase": {"name": {"query": "yoku"}}},
{"match_phrase": {"description": {"query": "yoku"}}}
]}}
]}}
]}}
一文字にもマッチするように
• Tritonn では基本 bigram のはずなのだけどなぜか 1文字
でも検索可能 (ex:「壺」)
– 単体で検索することは稀かもしれないけど、複合語ではマッチ
させたい (ex: 「漬物」+「壺」)
• bigram だけでなく unigram も作成することで対応
– 単純に作成するだけだとインデックスサイズが肥大化
– stopword で一文字の 英字 数字 記号 平仮名 片仮名 を除外
スペースや # が除外されない
{"type": "stop", "stopwords_path": "stopwords.txt"}
{
"type": "stop",
"stopwords": [
"t", " ", "!", """, "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"[", "", "]", "^", "_", "`",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"{", "|", "}", "~", " ",
"ぁ", "あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お", "か", "が", "き", "ぎ", "く", "ぐ", "け", "げ", "こ", "ご",
"さ", "ざ", "し", "じ", "す", "ず", "せ", "ぜ", "そ", "ぞ", "た", "だ", "ち", "ぢ", "っ", "つ", "づ", "て", "で", "と", "ど",
"な", "に", "ぬ", "ね", "の", "は", "ば", "ぱ", "ひ", "び", "ぴ", "ふ", "ぶ", "ぷ", "へ", "べ", "ぺ", "ほ", "ぼ", "ぽ",
"ま", "み", "む", "め", "も", "ゃ", "や", "ゅ", "ゆ", "ょ", "よ", "ら", "り", "る", "れ", "ろ", "ゎ", "わ", "を", "ん",
"ゔ", "ゕ", "ゖ", " ゙", " ゚", "゛", "゜",
"ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ", "ゲ", "コ", "ゴ",
"サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ", "ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド",
"ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ", "ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ",
"マ", "ミ", "ム", "メ", "モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ヮ", "ワ", "ヲ", "ン", "ヴ",
"ヵ", "ヶ", "ー", "、", "。", "「", "」", "『", "』", "【", "】", "〔", "〕", "〖", "〗", "〘", "〙", "〚", "〛", "〜", "!", "?"
]
}
行単位で除外ワードを記述したファイルで stopword を指定
ファイルを読み込む処理で、# はコメント扱いの上、
スペース除去をしているため、 無視されていた
直接列挙することで対応
非正規化
category_id name code
1 cat1 1010
2 cat2 1011
3 cat3 2010
field化 (非正規化)
item_id name category_id ...
10 商品A 1 ...
20 商品B 2 ...
items table
categories table
{
"item_id": 10,
"name": "商品A",
"category_id": 1,
"categories::name": "cat1",
"categories::code": "1010",
},
{
"item_id": 20,
"name": "商品B",
"category_id": 2,
"categories::name": "cat2",
"categories::code": "1011",
}
items index
0…n
1
item_id plan provide
1 A 2019/06/01
1 B 2019/07/01
2 A 2019/06/07
nested field
item_id ...
10 ...
20 ...
items table
item_provisions table
{
"item_id": 10,
"item_provisions" : [
{
"plan": "A",
"provide": "2019-06-01"
},
{
"plan": "B",
"provide": "2019-07-01"
}
]
},
{
"item_id": 20,
"item_provisions" : [
{
"plan": "A",
"provide": "2019-06-07"
}
]
}
items index
1
0…n
user_id item_id user_price
1 10 1500
1 20 2500
2 10 3000
ユーザー設定価格
item_id default_price
10 1000
20 2000
items table
user_settings table
item_id price
10 1500
20 2500
item_id price
10 3000
20 2000
user_id:1 の場合
user_id:2 の場合
1
0…n
user_settings があれば
user_price を採用。なけれ
ば default_price を採用
user_id:2 が扱う 2000円 以上の
商品を抽出するSQL
SELECT item_id,
IFNULL(user_price, default_price) AS price
FROM items
LEFT OUTER JOIN user_settings USING(item_id)
WHERE user_id = 2
AND IFNULL(user_price, default_price) <= 2000
user_id item_id user_price
1 10 1500
1 20 2500
2 10 3000
非正規化+展開
item_id default_price
10 1000
20 2000
items table
user_settings table
1
0…n
{
"item_id": 10,
"user_settings" : [
{
"user_id": 1,
"price": "1500"
},
{
"user_id": 2,
"price": "3000"
},
]
},
{
"item_id": 20,
"user_settings" : [
{
"user_id": 1,
"price": "2500"
},
{
"user_id": 2,
"price": "2000"
},
]
}
items index
`number of documents in the
index cannot exceed
2147483519`
• 200万以上の商品と70万以上のユーザー
– 1兆4000億以上の nested object が必要
– user_settings は 2500万件程度
• 1シャードあたりの document 上限は 2147483519
– nested object は 1document として格納
– シャードを増やせば格納することは可能
• nested object が多すぎる document の操作でメモリ不足
– java.lang.OutOfMemoryError: Java heap space
user_id item_id user_price
1 10 1500
1 20 2500
2 10 3000
DB のデータを
そのまま非正規化
item_id default_price
10 1000
20 2000
items table
user_settings table
1
0…n
{
"item_id": 10,
"default_price": 1000,
"user_settings" : [
{
"user_id": 1,
"price": "1500"
},
{
"user_id": 2,
"price": "3000"
}
]
},
{
"item_id": 20,
"default_price": 2000,
"user_settings" : [
{
"user_id": 1,
"price": "2500"
}
/* user_id:2 は含めない */
]
}
items index
price による絞り込みをどう実現するか
• DB と同じデータをつかって実現
– SQL を参考に
• ES で柔軟に値を調整
– 検索スコア
– スコア調整用のクエリ
– painless (スクリプト)が利用可能
user_id:2 が扱う 2000円 以上の
商品を抽出するSQL (再掲)
SELECT item_id,
IFNULL(user_price, default_price) AS price
FROM items
LEFT OUTER JOIN user_settings USING(item_id)
WHERE user_id = 2
AND IFNULL(user_price, default_price) <= 2000
{
“function_score”: {
“query”: {
“function_score”: {
“query”: {
“bool”: {
"filter": [
{"match_all": {}}
],
“should”: [
{
“nested”: {
“path”: “user_settings",
"query": {
"function_score": {
"query": {
"term": {
"user_settings.user_id":{"value": 2, "boost": 0}
}
},
"field_value_factor" : {
"field":"user_settings.user_price", "missing": 0
},
"boost_mode": "replace"
}
}
}
}
]
}
},
"script_score": {
"script": {
"source": "0 < _score ? _score : doc['default_price'].value"
}
},
"boost_mode": "replace",
"min_score": 2000
}},
"boost_mode": "replace",
"weight": 1
}
}
1.全体を対象にして
2. user_id:2 の
user_settings があれば
3.user_price を _score に置き換える
(なければ0)
4. _score が 0 の場合は default_price を
_score に置き換え (_score が price になる)
5. _score (price) が 2000 以上のみに絞り込む
6. 重みづけを 1に戻すことで、金額がスコアに影響するのを抑制
現在
• Elasticsearch クラスタが稼働中
• 一部の処理については Elasticsearch
利用の検索に置き換え済み
• MySQL バージョンアップ
(5.0系 -> 8系) は隣の人が頑張ってる
まとめ
• Tritronn から Elasticsearch に移行
しています (現在進行形)
• データ同期の手法とか移行によるパ
フォーマンス改善とか話しきれなかった
ことも多い
• 俺たちの戦いはこれからだ
(_blank)

Weitere ähnliche Inhalte

Was ist angesagt?

いまさら始めてみたRxJS
いまさら始めてみたRxJSいまさら始めてみたRxJS
いまさら始めてみたRxJSlion-man
 
Ember.js Tokyo event 2014/09/22 (Japanese)
Ember.js Tokyo event  2014/09/22 (Japanese)Ember.js Tokyo event  2014/09/22 (Japanese)
Ember.js Tokyo event 2014/09/22 (Japanese)Yuki Shimada
 
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)Misaki Hisamura
 
Pythonで始めるDropboxAPI
Pythonで始めるDropboxAPIPythonで始めるDropboxAPI
Pythonで始めるDropboxAPIDaisuke Igarashi
 
20170923 excelユーザーのためのr入門
20170923 excelユーザーのためのr入門20170923 excelユーザーのためのr入門
20170923 excelユーザーのためのr入門Takashi Kitano
 
jQuery Mobile 1.2 最新情報 & Tips
jQuery Mobile 1.2 最新情報 & TipsjQuery Mobile 1.2 最新情報 & Tips
jQuery Mobile 1.2 最新情報 & Tipsyoshikawa_t
 
jQuery Mobile 1.3 最新情報
jQuery Mobile 1.3 最新情報jQuery Mobile 1.3 最新情報
jQuery Mobile 1.3 最新情報yoshikawa_t
 
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみました
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみましたEucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみました
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみましたEtsuji Nakai
 
Html5 Web Applications
Html5  Web ApplicationsHtml5  Web Applications
Html5 Web Applicationstotty jp
 
10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 Nobuaki Oshiro
 
LINQソースでGO!
LINQソースでGO!LINQソースでGO!
LINQソースでGO!Kouji Matsui
 
BigDataの集め方
BigDataの集め方BigDataの集め方
BigDataの集め方Mahito Ogura
 
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~Akabane Hiroyuki
 
脱コピペ!デザイナーにもわかるPHPとWP_Query
脱コピペ!デザイナーにもわかるPHPとWP_Query脱コピペ!デザイナーにもわかるPHPとWP_Query
脱コピペ!デザイナーにもわかるPHPとWP_QueryHidekazu Ishikawa
 
PHP Object Injection入門
PHP Object Injection入門PHP Object Injection入門
PHP Object Injection入門Yu Iwama
 

Was ist angesagt? (20)

Yahoo!ボックスAPI Hackathon向け資料
Yahoo!ボックスAPI Hackathon向け資料Yahoo!ボックスAPI Hackathon向け資料
Yahoo!ボックスAPI Hackathon向け資料
 
Yahoo!ボックスAPI Hackday資料
Yahoo!ボックスAPI Hackday資料Yahoo!ボックスAPI Hackday資料
Yahoo!ボックスAPI Hackday資料
 
いまさら始めてみたRxJS
いまさら始めてみたRxJSいまさら始めてみたRxJS
いまさら始めてみたRxJS
 
Ember.js Tokyo event 2014/09/22 (Japanese)
Ember.js Tokyo event  2014/09/22 (Japanese)Ember.js Tokyo event  2014/09/22 (Japanese)
Ember.js Tokyo event 2014/09/22 (Japanese)
 
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)
【GCPUG滋賀】BigQueryでさるでもわかるSQL(20190323)
 
Pythonで始めるDropboxAPI
Pythonで始めるDropboxAPIPythonで始めるDropboxAPI
Pythonで始めるDropboxAPI
 
Actor&stm
Actor&stmActor&stm
Actor&stm
 
20170923 excelユーザーのためのr入門
20170923 excelユーザーのためのr入門20170923 excelユーザーのためのr入門
20170923 excelユーザーのためのr入門
 
Scala with DDD
Scala with DDDScala with DDD
Scala with DDD
 
jQuery Mobile 1.2 最新情報 & Tips
jQuery Mobile 1.2 最新情報 & TipsjQuery Mobile 1.2 最新情報 & Tips
jQuery Mobile 1.2 最新情報 & Tips
 
jQuery Mobile 1.3 最新情報
jQuery Mobile 1.3 最新情報jQuery Mobile 1.3 最新情報
jQuery Mobile 1.3 最新情報
 
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみました
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみましたEucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみました
EucalyptusのHadoopクラスタとJaqlでBasket解析をしてHiveとの違いを味わってみました
 
Html5 Web Applications
Html5  Web ApplicationsHtml5  Web Applications
Html5 Web Applications
 
10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920 10分で分かるr言語入門ver2.9 14 0920
10分で分かるr言語入門ver2.9 14 0920
 
LINQソースでGO!
LINQソースでGO!LINQソースでGO!
LINQソースでGO!
 
Listを串刺し
Listを串刺しListを串刺し
Listを串刺し
 
BigDataの集め方
BigDataの集め方BigDataの集め方
BigDataの集め方
 
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
ビギナーだから使いたいO/Rマッパー ~Tengを使った開発~
 
脱コピペ!デザイナーにもわかるPHPとWP_Query
脱コピペ!デザイナーにもわかるPHPとWP_Query脱コピペ!デザイナーにもわかるPHPとWP_Query
脱コピペ!デザイナーにもわかるPHPとWP_Query
 
PHP Object Injection入門
PHP Object Injection入門PHP Object Injection入門
PHP Object Injection入門
 

Ähnlich wie Tritonn から Elasticsearch への移行話

速くなければスマフォじゃない - インターンバージョン-
速くなければスマフォじゃない - インターンバージョン-速くなければスマフォじゃない - インターンバージョン-
速くなければスマフォじゃない - インターンバージョン-Kazunari Hara
 
Objects Fields
Objects FieldsObjects Fields
Objects Fieldskmiyako
 
20150530 pgunconf-pgbench-semi-structured-benchmark
20150530 pgunconf-pgbench-semi-structured-benchmark20150530 pgunconf-pgbench-semi-structured-benchmark
20150530 pgunconf-pgbench-semi-structured-benchmarkToshi Harada
 
Ext.Directについて
Ext.DirectについてExt.Directについて
Ext.DirectについてYuki Naotori
 
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoGo言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoShoot Morii
 
Wb osaka 20120623
Wb osaka 20120623Wb osaka 20120623
Wb osaka 20120623Miho Ishida
 
拡張ライブラリ作成による高速化
拡張ライブラリ作成による高速化拡張ライブラリ作成による高速化
拡張ライブラリ作成による高速化Kazunori Jo
 
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6Shunsuke Nakamura
 
探検!SwiftyJSON
探検!SwiftyJSON探検!SwiftyJSON
探検!SwiftyJSONYuka Ezura
 
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと 12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと Haruka Ozaki
 
PGCon.jp 2014 jsonb-datatype-20141205
PGCon.jp 2014 jsonb-datatype-20141205PGCon.jp 2014 jsonb-datatype-20141205
PGCon.jp 2014 jsonb-datatype-20141205Toshi Harada
 
Guide for Swift and Viewer app
Guide for Swift and Viewer appGuide for Swift and Viewer app
Guide for Swift and Viewer appShintaro Kaneko
 
パフォーマンスの良いGASの書き方 Best Practice
パフォーマンスの良いGASの書き方 Best Practiceパフォーマンスの良いGASの書き方 Best Practice
パフォーマンスの良いGASの書き方 Best Practice啓介 大橋
 

Ähnlich wie Tritonn から Elasticsearch への移行話 (17)

d3sparql.js
d3sparql.js d3sparql.js
d3sparql.js
 
速くなければスマフォじゃない - インターンバージョン-
速くなければスマフォじゃない - インターンバージョン-速くなければスマフォじゃない - インターンバージョン-
速くなければスマフォじゃない - インターンバージョン-
 
Objects Fields
Objects FieldsObjects Fields
Objects Fields
 
20150530 pgunconf-pgbench-semi-structured-benchmark
20150530 pgunconf-pgbench-semi-structured-benchmark20150530 pgunconf-pgbench-semi-structured-benchmark
20150530 pgunconf-pgbench-semi-structured-benchmark
 
Cocoa勉強会201208
Cocoa勉強会201208Cocoa勉強会201208
Cocoa勉強会201208
 
Ext.Directについて
Ext.DirectについてExt.Directについて
Ext.Directについて
 
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoGo言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
 
AWS CLI Conference 2016
AWS CLI Conference 2016AWS CLI Conference 2016
AWS CLI Conference 2016
 
Wb osaka 20120623
Wb osaka 20120623Wb osaka 20120623
Wb osaka 20120623
 
拡張ライブラリ作成による高速化
拡張ライブラリ作成による高速化拡張ライブラリ作成による高速化
拡張ライブラリ作成による高速化
 
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6
データサイエンティストに聞く!今更聞けない機械学習の基礎から応用まで V6
 
探検!SwiftyJSON
探検!SwiftyJSON探検!SwiftyJSON
探検!SwiftyJSON
 
Rust samurai#01
Rust samurai#01Rust samurai#01
Rust samurai#01
 
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと 12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと
12-11-30 Kashiwa.R #5 初めてのR Rを始める前に知っておきたい10のこと
 
PGCon.jp 2014 jsonb-datatype-20141205
PGCon.jp 2014 jsonb-datatype-20141205PGCon.jp 2014 jsonb-datatype-20141205
PGCon.jp 2014 jsonb-datatype-20141205
 
Guide for Swift and Viewer app
Guide for Swift and Viewer appGuide for Swift and Viewer app
Guide for Swift and Viewer app
 
パフォーマンスの良いGASの書き方 Best Practice
パフォーマンスの良いGASの書き方 Best Practiceパフォーマンスの良いGASの書き方 Best Practice
パフォーマンスの良いGASの書き方 Best Practice
 

Mehr von do_aki

php-src の歩き方
php-src の歩き方php-src の歩き方
php-src の歩き方do_aki
 
PHP と SAPI と ZendEngine3 と
PHP と SAPI と ZendEngine3 とPHP と SAPI と ZendEngine3 と
PHP と SAPI と ZendEngine3 とdo_aki
 
PHPとシグナル、その裏側
PHPとシグナル、その裏側PHPとシグナル、その裏側
PHPとシグナル、その裏側do_aki
 
再考:列挙型
再考:列挙型再考:列挙型
再考:列挙型do_aki
 
signal の話 或いは Zend Signals とは何か
signal の話 或いは Zend Signals とは何かsignal の話 或いは Zend Signals とは何か
signal の話 或いは Zend Signals とは何かdo_aki
 
PHP AST 徹底解説(補遺)
PHP AST 徹底解説(補遺)PHP AST 徹底解説(補遺)
PHP AST 徹底解説(補遺)do_aki
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説do_aki
 
Writing php extensions in golang
Writing php extensions in golangWriting php extensions in golang
Writing php extensions in golangdo_aki
 
php7's ast
php7's astphp7's ast
php7's astdo_aki
 
N対1 レプリケーション + Optimizer Hint
N対1 レプリケーション + Optimizer HintN対1 レプリケーション + Optimizer Hint
N対1 レプリケーション + Optimizer Hintdo_aki
 
20150212 プレゼンテーションzen
20150212 プレゼンテーションzen20150212 プレゼンテーションzen
20150212 プレゼンテーションzendo_aki
 
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」do_aki
 
20141017 introduce razor
20141017 introduce razor20141017 introduce razor
20141017 introduce razordo_aki
 
20141011 mastering mysqlnd
20141011 mastering mysqlnd20141011 mastering mysqlnd
20141011 mastering mysqlnddo_aki
 
php in ruby
php in rubyphp in ruby
php in rubydo_aki
 
PHP から Groonga を使うにはこんなコードになるよ!
PHP から Groonga を使うにはこんなコードになるよ!PHP から Groonga を使うにはこんなコードになるよ!
PHP から Groonga を使うにはこんなコードになるよ!do_aki
 
N:1 Replication meets MHA
N:1 Replication meets MHAN:1 Replication meets MHA
N:1 Replication meets MHAdo_aki
 
Php radomize
Php radomizePhp radomize
Php radomizedo_aki
 
php and sapi and zendengine2 and...
php and sapi and zendengine2 and...php and sapi and zendengine2 and...
php and sapi and zendengine2 and...do_aki
 
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editorセキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editordo_aki
 

Mehr von do_aki (20)

php-src の歩き方
php-src の歩き方php-src の歩き方
php-src の歩き方
 
PHP と SAPI と ZendEngine3 と
PHP と SAPI と ZendEngine3 とPHP と SAPI と ZendEngine3 と
PHP と SAPI と ZendEngine3 と
 
PHPとシグナル、その裏側
PHPとシグナル、その裏側PHPとシグナル、その裏側
PHPとシグナル、その裏側
 
再考:列挙型
再考:列挙型再考:列挙型
再考:列挙型
 
signal の話 或いは Zend Signals とは何か
signal の話 或いは Zend Signals とは何かsignal の話 或いは Zend Signals とは何か
signal の話 或いは Zend Signals とは何か
 
PHP AST 徹底解説(補遺)
PHP AST 徹底解説(補遺)PHP AST 徹底解説(補遺)
PHP AST 徹底解説(補遺)
 
PHP AST 徹底解説
PHP AST 徹底解説PHP AST 徹底解説
PHP AST 徹底解説
 
Writing php extensions in golang
Writing php extensions in golangWriting php extensions in golang
Writing php extensions in golang
 
php7's ast
php7's astphp7's ast
php7's ast
 
N対1 レプリケーション + Optimizer Hint
N対1 レプリケーション + Optimizer HintN対1 レプリケーション + Optimizer Hint
N対1 レプリケーション + Optimizer Hint
 
20150212 プレゼンテーションzen
20150212 プレゼンテーションzen20150212 プレゼンテーションzen
20150212 プレゼンテーションzen
 
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」
MySQL Casual Talks 7 「N:1 レプリケーション ~進捗どうですか?~」
 
20141017 introduce razor
20141017 introduce razor20141017 introduce razor
20141017 introduce razor
 
20141011 mastering mysqlnd
20141011 mastering mysqlnd20141011 mastering mysqlnd
20141011 mastering mysqlnd
 
php in ruby
php in rubyphp in ruby
php in ruby
 
PHP から Groonga を使うにはこんなコードになるよ!
PHP から Groonga を使うにはこんなコードになるよ!PHP から Groonga を使うにはこんなコードになるよ!
PHP から Groonga を使うにはこんなコードになるよ!
 
N:1 Replication meets MHA
N:1 Replication meets MHAN:1 Replication meets MHA
N:1 Replication meets MHA
 
Php radomize
Php radomizePhp radomize
Php radomize
 
php and sapi and zendengine2 and...
php and sapi and zendengine2 and...php and sapi and zendengine2 and...
php and sapi and zendengine2 and...
 
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editorセキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor
セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editor
 

Kürzlich hochgeladen

新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
新人研修のまとめ       2024/04/12の勉強会で発表されたものです。新人研修のまとめ       2024/04/12の勉強会で発表されたものです。
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。iPride Co., Ltd.
 
PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000Shota Ito
 
UPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdfUPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdffurutsuka
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 
20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directoryosamut
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxIoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxAtomu Hidaka
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。iPride Co., Ltd.
 

Kürzlich hochgeladen (9)

新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
新人研修のまとめ       2024/04/12の勉強会で発表されたものです。新人研修のまとめ       2024/04/12の勉強会で発表されたものです。
新人研修のまとめ 2024/04/12の勉強会で発表されたものです。
 
PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000PHP-Conference-Odawara-2024-04-000000000
PHP-Conference-Odawara-2024-04-000000000
 
UPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdfUPWARD_share_company_information_20240415.pdf
UPWARD_share_company_information_20240415.pdf
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 
20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory20240412_HCCJP での Windows Server 2025 Active Directory
20240412_HCCJP での Windows Server 2025 Active Directory
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptxIoT in the era of generative AI, Thanks IoT ALGYAN.pptx
IoT in the era of generative AI, Thanks IoT ALGYAN.pptx
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
Amazon SES を勉強してみる その12024/04/12の勉強会で発表されたものです。
 

Tritonn から Elasticsearch への移行話

  • 4. Tritonn • Tritonn := MySQL + Senna • MyISAM の全文検索をフックするパッチで 実現 (ストレージエンジンではない) • MyISAM + Senna インデックス • 検索は MATCH…AGAINST 構文を利用
  • 5. CREATE TABLE items ( item_id INT, name TEXT NOT NULL, description TEXT NOT NULL, FULLTEXT KEY USING NGRAM, NORMALIZE, SECTIONALIZE (name,description) ) ENGINE=MyISAM SELECT item_id, name, description FROM items WHERE MATCH (name, description) AGAINST ('*E-1*D+*W1:10,2:3 検索語 –除外' IN BOOLEAN MODE) DDL SQL
  • 6. Tritonn の今 • 開発停止 • Groonga ストレージエンジン (現在の Moronga ストレージエンジン)に移行 • Trironn 最終リリースは 2009/11 (MySQL 5.0.87)
  • 7. Tritonn の今 • 開発停止 • Groonga ストレージエンジン (現在の Moronga ストレージエンジン)に移行 • Trironn 最終リリースは 2009/11 (MySQL 5.0.87) 現役で稼働中!!
  • 8. 10年前のMySQL • ハードウェア特性の相違 • Clientライブラリとの互換性 • OS (glibc) との互換性 / 脆弱性 さすがに MySQL の バージョンアップを しないとね! (脱 Tritonn)
  • 9. 移行にあたって重要視したこと • 従来の検索エクスペリエンスを大きくは変えない => 漏れより網羅性重視 / 正規化 / OR,AND,NOT • 検索速度が従来より極端に遅くならないようにする => 厳密な基準は設けなかったが、現状同等以下 • 運用コストはなるべく抑える => Tritonn 自体を運用するコストはほぼ0だった
  • 11. LIKE に置き換え • 複数語検索が必要な場合は LIKE を OR で結合していく感じ • 試しに200万件程度のデータに対して MATCH..AGAINST を 単純な LIKE に置き換えてみたところ ミリ秒単位で返ってきていたクエリ が数十秒単位に • データ量が少なく、今後も急には増えないところに対しての妥協案 としてあり -> 他の条件で十分に絞り込め、単純検索で問題ない ケースについての置き換え策として採用
  • 12. InnoDB Full Text Search • MySQL5.6 以降で利用可能な全文検索インデックス(ngram parser は 5.7 以降) • Senna 同様 MATCH..AGAINT を使うので、改修箇所が少なくて済 むかもという期待 ↓ ↓ ↓ • ngram parser (bigram)だと十分な速度が出なかった (LIKE同様 のデータで数秒程度) • 現状の挙動と合わせるためには前処理が必要 • ngram における stopword の扱いが微妙……
  • 13. Mroonga • Tritonn の後継プロダクト • ラッパーモードを利用すればほぼ現状と変わらないはず • とはいえ、せっかく 脱Tritonn するのになーという思いも • 検証時点では 8.0系には未対応(2019年6月現在も) -> 今後のバージョンアップの枷になりそう (ソースコードに手を入れて 8.0 対応の手助けできないか試したのだけど、 力及ばず。。。)
  • 14. MySQL のみでの解決を断念 -> 他の Middle Ware導入を検討 • Groonga – 速度は申し分ない – HAのための冗長構成をすべて自前で組む必要がある • Solr – Lucene のフロントエンド – Solr Cloud • Elasticsearch – Lucene をつかった全文検索サーバ – 組み込みのクラスタ 採用
  • 16. 形態素 vs ngram • 転置インデックスに格納される語の単位 • 「東京都の水」 – 形態素:「東京」「都」「の」「水」 辞書依存, 一般的に新語や固有名詞に弱い 辞書のメンテが必要 – bigram:「東京」「京都」「都の」「の水」 インデックスサイズが肥大, 網羅的に検索できる 辞書不要 {"type": "kuromoji_tokenizer"} {"type": "ngram", "min_gram": 2, "max_gram": 2}
  • 17. USING NGRAM • 弊社の Tritonn では ngram インデックスが利用されてた – もともと 高速な LIKE がほしい的な理由からだった気がする – 様々なジャンルの商品 -> 未知語が多い – 適合性より網羅性重視 • Elasticsearch で ngram を使う事例は少ない? – 日本語検索の多くは kuromoji 利用 • Elasticsearch 採用決定までに試行錯誤してる
  • 19. ngram と match • 「世界に一つだけの花」「世界一」 – index: 「世界」「界に」「に一」「一つ」「つだ」 「だけ」「けの」「の花」 – search: 「世界」「界一」 • match query は デフォルトでは OR – 「界一」にはヒットしないが、「世界」 にヒットし たことでマッチ – ならば AND にしてやれば…… {"match": {"name": "世界一"}} ↓ {"match": {"name": {"query":"世界一", "operator":"and"}}
  • 20. そんな甘くはない • @johtani さんからの指摘 – https://twitter.com/johtani/status/10 58195319228268545
  • 21. match_phrase • 「世界に一つだけの花」にはマッチしなくなったが 「MySQL界一の地雷職人、世界のyokuさん」にはマッチ • 解決するために match_phrase を利用 – match や multi_match を使わないという選択 {"match": {"name": "世界一"}} ↓ {"match_phrase": {"name": "世界一"}}
  • 22. 複数フィールドへの対応 • 例:「世界」 と 「花」 を含む name検索 • description フィールドも対象に • match が使えないので bool query を駆使 ↓ ↓ ↓ {"match" : {"name": "世界 花"}} {"multi_match" : {"query": "世界 花", "fields": ["name", "description"]} }
  • 23. bool query { "bool": { "must": [ { "bool": { "should": [ {"match_phrase": {"name": {"世界"}}}, {"match_phrase": {"description": {"世界"}}}, ] } }, { "bool": { "should": [ {"match_phrase": {"name": {"花"}}}, {"match_phrase": {"description": {"花"}}}, ] } }, ] } }
  • 24. 検索ワードの解析 • "(世界一 OR 日本一) 地雷職人 –yoku" – Trironn では、そのまま渡せば意図通り – 簡易パーサを作って対応 {"bool": {"must": [ {"bool": {"must": [ {"bool": {"should": [ {"match_phrase": {"name": {"query": "世界一"}}}, {"match_phrase": {"description": {"query": "世界一"}} ]}}, {"bool": {"should": [ {"match_phrase": {"name": {"query": "日本一"}}}, {"match_phrase": {"description": {"query": "日本一"}}} ]}} ]}}, {"bool": {"should": [ {"match_phrase": {"name": {"query": "地雷"}}}, {"match_phrase": {"description": {"query": "地雷"}}} ]}}, {"bool": {"must_not": [ {"bool": {"should": [ {"match_phrase": {"name": {"query": "yoku"}}}, {"match_phrase": {"description": {"query": "yoku"}}} ]}} ]}} ]}}
  • 25. 一文字にもマッチするように • Tritonn では基本 bigram のはずなのだけどなぜか 1文字 でも検索可能 (ex:「壺」) – 単体で検索することは稀かもしれないけど、複合語ではマッチ させたい (ex: 「漬物」+「壺」) • bigram だけでなく unigram も作成することで対応 – 単純に作成するだけだとインデックスサイズが肥大化 – stopword で一文字の 英字 数字 記号 平仮名 片仮名 を除外
  • 26. スペースや # が除外されない {"type": "stop", "stopwords_path": "stopwords.txt"} { "type": "stop", "stopwords": [ "t", " ", "!", """, "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", " ", "ぁ", "あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お", "か", "が", "き", "ぎ", "く", "ぐ", "け", "げ", "こ", "ご", "さ", "ざ", "し", "じ", "す", "ず", "せ", "ぜ", "そ", "ぞ", "た", "だ", "ち", "ぢ", "っ", "つ", "づ", "て", "で", "と", "ど", "な", "に", "ぬ", "ね", "の", "は", "ば", "ぱ", "ひ", "び", "ぴ", "ふ", "ぶ", "ぷ", "へ", "べ", "ぺ", "ほ", "ぼ", "ぽ", "ま", "み", "む", "め", "も", "ゃ", "や", "ゅ", "ゆ", "ょ", "よ", "ら", "り", "る", "れ", "ろ", "ゎ", "わ", "を", "ん", "ゔ", "ゕ", "ゖ", " ゙", " ゚", "゛", "゜", "ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ", "ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ", "ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ", "ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ", "モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ヮ", "ワ", "ヲ", "ン", "ヴ", "ヵ", "ヶ", "ー", "、", "。", "「", "」", "『", "』", "【", "】", "〔", "〕", "〖", "〗", "〘", "〙", "〚", "〛", "〜", "!", "?" ] } 行単位で除外ワードを記述したファイルで stopword を指定 ファイルを読み込む処理で、# はコメント扱いの上、 スペース除去をしているため、 無視されていた 直接列挙することで対応
  • 28. category_id name code 1 cat1 1010 2 cat2 1011 3 cat3 2010 field化 (非正規化) item_id name category_id ... 10 商品A 1 ... 20 商品B 2 ... items table categories table { "item_id": 10, "name": "商品A", "category_id": 1, "categories::name": "cat1", "categories::code": "1010", }, { "item_id": 20, "name": "商品B", "category_id": 2, "categories::name": "cat2", "categories::code": "1011", } items index 0…n 1
  • 29. item_id plan provide 1 A 2019/06/01 1 B 2019/07/01 2 A 2019/06/07 nested field item_id ... 10 ... 20 ... items table item_provisions table { "item_id": 10, "item_provisions" : [ { "plan": "A", "provide": "2019-06-01" }, { "plan": "B", "provide": "2019-07-01" } ] }, { "item_id": 20, "item_provisions" : [ { "plan": "A", "provide": "2019-06-07" } ] } items index 1 0…n
  • 30. user_id item_id user_price 1 10 1500 1 20 2500 2 10 3000 ユーザー設定価格 item_id default_price 10 1000 20 2000 items table user_settings table item_id price 10 1500 20 2500 item_id price 10 3000 20 2000 user_id:1 の場合 user_id:2 の場合 1 0…n user_settings があれば user_price を採用。なけれ ば default_price を採用
  • 31. user_id:2 が扱う 2000円 以上の 商品を抽出するSQL SELECT item_id, IFNULL(user_price, default_price) AS price FROM items LEFT OUTER JOIN user_settings USING(item_id) WHERE user_id = 2 AND IFNULL(user_price, default_price) <= 2000
  • 32. user_id item_id user_price 1 10 1500 1 20 2500 2 10 3000 非正規化+展開 item_id default_price 10 1000 20 2000 items table user_settings table 1 0…n { "item_id": 10, "user_settings" : [ { "user_id": 1, "price": "1500" }, { "user_id": 2, "price": "3000" }, ] }, { "item_id": 20, "user_settings" : [ { "user_id": 1, "price": "2500" }, { "user_id": 2, "price": "2000" }, ] } items index
  • 33. `number of documents in the index cannot exceed 2147483519` • 200万以上の商品と70万以上のユーザー – 1兆4000億以上の nested object が必要 – user_settings は 2500万件程度 • 1シャードあたりの document 上限は 2147483519 – nested object は 1document として格納 – シャードを増やせば格納することは可能 • nested object が多すぎる document の操作でメモリ不足 – java.lang.OutOfMemoryError: Java heap space
  • 34. user_id item_id user_price 1 10 1500 1 20 2500 2 10 3000 DB のデータを そのまま非正規化 item_id default_price 10 1000 20 2000 items table user_settings table 1 0…n { "item_id": 10, "default_price": 1000, "user_settings" : [ { "user_id": 1, "price": "1500" }, { "user_id": 2, "price": "3000" } ] }, { "item_id": 20, "default_price": 2000, "user_settings" : [ { "user_id": 1, "price": "2500" } /* user_id:2 は含めない */ ] } items index
  • 35. price による絞り込みをどう実現するか • DB と同じデータをつかって実現 – SQL を参考に • ES で柔軟に値を調整 – 検索スコア – スコア調整用のクエリ – painless (スクリプト)が利用可能
  • 36. user_id:2 が扱う 2000円 以上の 商品を抽出するSQL (再掲) SELECT item_id, IFNULL(user_price, default_price) AS price FROM items LEFT OUTER JOIN user_settings USING(item_id) WHERE user_id = 2 AND IFNULL(user_price, default_price) <= 2000
  • 37. { “function_score”: { “query”: { “function_score”: { “query”: { “bool”: { "filter": [ {"match_all": {}} ], “should”: [ { “nested”: { “path”: “user_settings", "query": { "function_score": { "query": { "term": { "user_settings.user_id":{"value": 2, "boost": 0} } }, "field_value_factor" : { "field":"user_settings.user_price", "missing": 0 }, "boost_mode": "replace" } } } } ] } }, "script_score": { "script": { "source": "0 < _score ? _score : doc['default_price'].value" } }, "boost_mode": "replace", "min_score": 2000 }}, "boost_mode": "replace", "weight": 1 } } 1.全体を対象にして 2. user_id:2 の user_settings があれば 3.user_price を _score に置き換える (なければ0) 4. _score が 0 の場合は default_price を _score に置き換え (_score が price になる) 5. _score (price) が 2000 以上のみに絞り込む 6. 重みづけを 1に戻すことで、金額がスコアに影響するのを抑制
  • 39. • Elasticsearch クラスタが稼働中 • 一部の処理については Elasticsearch 利用の検索に置き換え済み • MySQL バージョンアップ (5.0系 -> 8系) は隣の人が頑張ってる
  • 40. まとめ • Tritronn から Elasticsearch に移行 しています (現在進行形) • データ同期の手法とか移行によるパ フォーマンス改善とか話しきれなかった ことも多い • 俺たちの戦いはこれからだ

Hinweis der Redaktion

  1. ngram分解された単語の一部に stopword が含まれているとインデックスされない (ngram parser の場合)
  2. NEologd