pixiv サイバーエージェント共同勉強会 solr導入記
TRANSCRIPT
pixiv Solr導入記
X
自己紹介 松宮 孝大(mattun)
プログラムエンジニア
◎pixivでかかわっているサービス ・pixivモバイル ・pixiv chat ・pixivの検索全般
を担当していますJavaが好きなんですがpixivにはJava好きは居ません・・・
目次
Tritonn使用時の検索の問題点
Solrの検証
pixivでのSolrの最適化
まとめ
Solrを導入する目的
Tritonnによる検索に限界があるため検索専用のアプリケーションを模索
検索だけで30台ある台数を減らせればいいなぁ
モバイルで人気順ソートを実装する
Tritonn時代のマシン構成アプリケーション Mysql-Tritton(senna)を使用
マシン構成( 自作機 B28) x 30台 AthlonX2 4850e 2.50GHz (2コア) Mem 8G SSD X25(80GB) or C300(64GB)
でのTritonnの問題点
Mysqlの全文検索には更新時ロックがかかる MyISAMのためReplicationで更新クエリーがくるとそこでロックがかかってしまう
CPUのコア数でスケールできない ロックがかかるためCPUが1コア分くらいしか使い切れていない
R-18など数値のある文字が重い たとえば6を検索したとき⑥や全角半角の6などもOR検索し条件が増える 揺らぎ補正のためNormalizeはOffにできない
MySQLのバージョンをあげることができない Tritonnが組み込まれたバージョンを使用しなければならないため Mysql5.1などにアップグレードできなかった
何かの検索文字 R-18
東方 ( はいてない OR 穿いてない OR はいてません OR 穿いてま せん OR ノーパン)
( 髪 OR かみ)( ほどき OR ほどく OR ほどけ OR ほどい OR ほ どいた OR ほどいて OR ほどこう OR ほどかせ OR ほどかれ
OR 解き OR 解く OR 解け OR 解い OR 解いた 解いて OR 解こう OR 解かせ OR 解かせ OR 解かれ OR おろす OR おろ
し OR おろした OR おろして OR おろそう OR おろさせ OR おろされ OR 下ろす OR 下ろし OR 下ろした OR 下ろして
OR 下ろそう OR 下ろさせ OR 下ろされ)
いろいろ重い検索条件を載せたかったのですがエロワードばかりなので自主規制・・・
これはやばい!
よし!Solrだ!
現在でも捌けないのに人気順ソートをモバイルに実装する必要がでてくる・・・
Solrの特徴
参照・更新ロックがかからない
レプリケーションが単純なファイル転送 Masterが持っているファイルをスレーブにコピーするだけ 内部でrsyncと同じようなことをしているだけ
スレーブの追加が簡単 設定ファイルを書くだけで起動すれば勝手に転送される Masterと同じインデックスファイルをもらうだけ
分散検索ができる
Normalizeや形態素解析やNGramなど目的に合わせたカスタマイズが可能
Commitが重い Solrはドキュメントを追加した後にCommitを行って初めてデータが反映される(トランザクションに近い?) そのCommitが結構な負荷のため1件ずつリアルタイムで更新せず溜め込んだデータをバッチで一気に処理する
pixivでは更新されたイラスト情報をMysqlのトリガーでログテーブルに保存しそのログを元にScalaで差分更新処理をしている
Commitのタイミングでキャッシュが消える 頻繁に更新するとクエリーキャッシュの意味を余り成さない
PHPやRubyにシリアライズされた結果を返してくれる 他にもXMLやJSONなどあり
Solr3.2.0を使用Solrの設定tokenizer : NGramTokenizer 指定された文字数で分割するfilter : LowerCaseFilter 大文字英字を小文字に変換charfilter : MappingCharFilterFactory 後ほど説明
データ構造uniqueKey: illust_idその他検索に必要なタグ、タイトル、コメント、作品がR18かどうかなどillust_id以外はインデックスのみでデータを持たない
Solr検証時の問題点
文字が長いと途中から検索できない・・・
NGramTokenizerというSolr標準のTokenizerを使用してたのですが
コードを読んでみると最大値がなぜか1024文字固定になっていたため可変に修正
solr-3.2.0/lucene/contrib/analyzers/common/src/java/org/apache/lucene/analysis/ngram/NGramTokenizer.java
修正前コード
char[] chars = new char[1024]; input.read(chars); inStr = new String(chars).trim();
修正後コード
CharArrayWriter writer = new CharArrayWriter(1024); int c; while((c = input.read()) != -1) {
writer.write(c); } inStr = new String(writer.toCharArray()).trim();
MultiValueを使うと検索結果がおかしい
unigramで2文字以上の検索ワードを投げるとタグがくっついた形ヒットする
例えばタグに[オリジナル] [パン]というタグがあったとして「ルパン」と検索すると・・・[オリジナル ] [パン] という様に分かれているタグが引っかかってしまう対処法が分からなかったため検索ワードの文字数によってunigramとbigramのインデックスを使い分けて対処した
オリジナル 猫の検索例 tag_bigram: オリジナル OR tag_unigram:猫
半角カタカナ文字にヒットしない半角カタカナを全角カタカナでヒットさせるには自前で設定をしなければならない(NGramTokenizerを使用する場合)
その場合MappingCharFilterFactoryというフィルターを使ってNormalizeすることができる設定例
"ガ"=>"ガ""ギ"=>"ギ"
pixivでは「へ」はどちらでもヒットするように設定"へ"=>"ヘ""べ"=>"ベ""ぺ"=>"ペ"
大量の件数がある場合ソートが重い全体で1200万件の内[東方]というタグには100万件以上ありソートだけで1.5秒くらいかかっている
1秒以上かかるものがある時点で検索を捌くことは不可能
データ更新は1分間隔でしていたのでキャッシュの恩威が少ない
ちなみにソートをしなければ一瞬で検索できる
じゃあ高速化しましょう!
高速化1SolrにはDistributedSearchという分散インデックス検索ができるのでインデックスを分散する
4分割(イラストID%4の余りが番号)して各マシンに1つずつインデックスを持てば検索速度が4倍早くなる
この機能は検索時にインデックスを持っているサーバーをパラメータで渡すだけでいいのでクライアント側の修正は簡単である
例q=東方&shareds=localhost:8983/solr/index0,localhost:8983/solr/index1
全体のインデックス
4分割された
イラストのイン
デックス
1つのイラストをイメージ
※補足 左に行くほど新しい
index1
index2
index3
index4
Solr1index1
Solr3index3
Solr2index2
Solr4index4
DistributedSearcher
すべてのインデックスから10件分(4分割なので40件分)の「東方」の結果を受け取る
クライアント
「東方」を検索する
その中で10件分の結果が返る
分散検索のイメージ図
4つのインデックスに投げる
高速化1の結果速度的には4分割するだけでかなりの高速化になった[東方]で1.5秒だったものが0.4秒くらいへ
それでも想定のマシン数では捌ききれない!
高速化2先ほどの4分割されたインデックスを古い日付のインデックスと見なし、新しい日付のイラスト(15万件)だけのインデックスを作り実質5分割にした
新しい日付イラストは1分間隔で差分更新する
古い日付のイラストは1時間隔で差分更新する
キャッシュの量を大量に設定
10万件以降のページングはキャッシュしない <queryResultMaxDocsCached>100000</queryResultMaxDocsCached> 一度の検索で2万件のキャッシュを保持しておく <queryResultWindowSize>20000</queryResultWindowSize>
全体のインデックス
古い日付の
イラストのイン
デックス
最新の日付のイラスト1分更新
index1
index2
index3
index4
Index_new
高速化2の結果超高速化された!
古い日付のイラストにはSolrにあるクエリーキャッシュで返せる(キャッシュヒット率79%)
新しいイラストは件数が15万件なのでまったく重くない
問題点として古い日付のイラストのインデックスと最新の日付のインデックスに同じデータが被ってしまうと件数に不整合が起こるのでデータが被らないように調整する
全体のインデックス
最新の日付のイラスト
最新の日付のインデックスと古い日付のインデックスが被らないようにする
Index_new
古い日付の
イラストのイン
デックス
index1
index2
index3
index4
最終的にどれだけ高速化されたか?実質常にクエリーキャッシュが効いているため件数の多い[東方]でも1.5 秒 → 0.04~0.1秒などになった
Tritonnでは30台で捌けなかったものが6台で余裕となった
QPS的に言うとSolrのステータスでは200 / qpsと出ている
まとめ普通に使うだけでもTritonnより早い
分散サーチ機能がついているためスケールが簡単にできる。
過去のデータや不変なデータを別のインデックスに持つようにし更新頻度を抑えればキャッシュのヒット率が大幅にあがる
常に大量の更新がある場合はバッチ処理が必須
ご清聴ありがとうございました