20151028 第17回 solr勉強会 U-NEXTにおけるSolr活用事例

  • View
    7.085

  • Download
    3

  • Category

    Software

Preview:

Citation preview

U-NEXTにおけるSolr活用事例〜インクリメンタルサーチとレコメンドへの挑戦〜

株式会社U-NEXTシステム開発部

秋穂 賢

自己紹介

氏名 秋穂 賢(あきほ すぐる)

所属 株式会社U-NEXT システム開発部

リリース管理や開発用ミドルウェア管理DBFluteを使ったJava8でのAPI実装 など(前職ではChef / Zabbix / JobScheduler 等)

仕事

まずはじめに

Solr勉強会での登壇という貴重な機会を与えて

頂いた ロンウイット様、リクルートテクノロジーズ

様、誠にありがとうございます。

特にロンウイット様にはコンサルタントとしてSolrを活用する上で様々なアドバイスをいただきまし

た。

Solr歴半年程度の自分が今、ここに立てている

のもロンウイット様のご支援が大きいと感じており

ます。

そもそもU-NEXT?最新情報はWebで http://video.unext.jp/introduction

10/8に全面リニューアルを実施!

そもそもU-NEXT?最新情報はWebで http://video.unext.jp/introduction/device

そもそもU-NEXT?最新情報はWebで http://video.unext.jp/introduction/family

そもそもU-NEXT?最新情報はWebで http://video.unext.jp/introduction/fee

U-NEXTでのSolr利用箇所

U-NEXTではSolrを以下のシーンで活用

○ フリーワード検索(インクリメンタルサーチ)

○ ユーザ毎のレコメンドデータ取得

Solrを使ったバックエンドAPIの開発が主な役割

インクリメンタルサーチでの事例

インクリメンタルサーチでの事例

○ インクリメンタルサーチ?

□ 検索文字を全て入力してから検索を行うのではなく、

入力するたびに検索結果が返ってくる検索方法のこと

□ Googleも今はインクリメンタルサーチ

○ Why インクリメンタルサーチ?□ すべての文字を打つことなく結果に辿れる

□ 検索中に新しい気づきを得られる可能性

○ デモ

□ http://video.unext.jp

システム構成(全体)Webクライアント

(Knockout.js)

WebFrontAPI(PHP)

Portal API(PHP)

CMS API(Java)

SolrCloud

WebブラウザのJSクライアントとしてKnockout.jsを利用

Web特有の要件を満たすための専用 APIサーバ

各バックエンド系APIをまとめてクライアントへ返答するための APIサーバ

CMSDB

コンテンツ周りの情報を返却する APIサーバ

Solrは運用面を考慮してSolrCloudを利用

Varnish(Cache) 検索ワード毎にキャッシュを保持

システム構成(詳細)

CMS API(Java) Solr

CMSDB

CMS API(Java)CMS API

(Java)CMS API(Java)

SolrSolrCloud

LB

JavaのAPIクライアントからSolrCloudへのアクセスはLB経由でHttpSolrClientを利用=> 直前までmaster/slaveかSolrCloudのどちらにするか、決められなかったため

バッチ

Solrインポート用TSV

定期的にCMSDBからデータ抽出&TSVファイル生成SolrへTSVをインポート

インクリメンタルサーチ実現における課題

○ ユーザ体験として違和感のない応答速度を担

保する必要がある

スキーマ定義を工夫メタデータに作品名のカナがあるので、有効活用

○ 一文字打つたびに検索結果を返す必要がある□ 検索文字は変換前の英字(ローマ字)の可能性も考慮

レスポンスに必要なデータを全てSolrに入れ込みUI上、即時反映が必要な項目は含めないように(詳細は省きますが)本番同様の負荷テストの結果、Solrからのレスポンスは2,30msをマーク!!

schema定義

○ インクリメンタルサーチを実現するために大きく4つのパターンでanalyzerを定義□ ※ 全てautoGeneratePhraseQueriesはtrueを設定

1. 日本語タイトル向けの設定(単純検索)○ (charFilter)solr.MappingCharFilterFactory

□ 記号などの不要な文字を削除

○ (charFilter)solr.ICUNormalizer2CharFilterFactory

□ 日本語の正規化

○ (tokenizer)solr.JapaneseTokenizerFactory

□ 形態素解析による日本語の文字分割

○ (filter)solr.LowerCaseFilterFactory□ 英字の大文字小文字を区別しない

2. 日本語タイトル向けの設定(インクリメンタル)

○ トークナイザまでは 1 と同じ

○ (filter)solr.JapaneseReadingFormFilterFactory□ 漢字を読みに変換

○ (filter)solr.ICUTransformFilterFactory(Hiragana-Katakana)

□ ひらがなをカタカナに変換

○ (filter)solr.ICUTransformFilterFactory(Katakana-Latin)

□ カタカナをローマ字に変換

○ (filter)solr.LowerCaseFilterFactory□ 小文字に統一

○ (filter)solr.EdgeNGramFilterFactory□ 形態素解析結果毎に1gram

○ ※ クエリサイドにはEdgeNGramを含まないように設定

□ => 結果的にはインデックスサイドのEdgeNGramにヒットする

schema定義

3. カナタイトル向けの設定(インクリメンタル)

○ charFilterまでは 1 と同じ

○ (tokenizer)solr.KeywordTokenizerFactory□ カタカナはフィールド全体を1つのトークンとみなす

○ 残りは 2 と同じ

※ クエリサイドは 2 と同じ。ローマ字に直して前方からの部分一致で検索する

例) 「アノヒ」というタイトルの場合、indexは a / an / ano / anoh / anohi が生

「あのh」で検索すると「あのh」で形態素解析されて「anoh」でフレーズクエリとして

検索される

schema定義

4. 1〜3までで検索にかからなかった向けの2gram設定

○ solr.NGramTokenizerFactoryで2gramの設定

○ 1〜4までをdismaxでの横断検索を実行

○ 横断検索する際には別途、設定してあるシノニムにも同時に検索

を実行□ 例) 「あの日見た花の名前を僕達はまだ知らない」を「あのはな」と略す

○ 日本語タイトル向けの設定(単純検索)□ => このプライオリティを一番高く設定(400)

○ 日本語タイトル向けの設定(インクリメンタル)

○ カナタイトル向けの設定(インクリメンタル)□ => これらプライオリティを2番目に(200)

○ 2gram設定

□ => 最低のプライオリティに(50)○ ※ シノニムについては通常ワードの半分にプライオリティを設定

schema定義

残課題

○ 検索精度が悪い部分がある□ 作品は存在しているのに検索にひっかからない

□ おそらく、漢字かな変換や形態素解析あたりが原因と

想像

□ こういった対象を地道に潰していく必要がある

○ 同着スコア時の並び順の最適化をしたい(短

い単語で検索された時など)

□ 現状はscore順となるため、検索する人の傾向によっ

て、アニメを優先させたり、ドラマを優先させたりなど、

検索のパーソナライズ化も目指す

レコメンドでの事例

○ レコメンド要件として、↓がありました

□ トップ画面で表示される特集をユーザ毎のオススメを表示したい

□ 各ジャンルトップではジャンルに絞り込んだレコメンドを表示した

□ モバイルアプリの場合、最初に好きなジャンルや映画を聞いて、

好きなものを優先して出したい

○ レコメンド自体の計算は別環境で行われていて、計算結果のキャッ

シュを何かしらでもたせたかった

レコメンドでの事例

インクリメンタルサーチでの高速レスポンスの実績+RDBに似た絞り込み検索が行える= Solr※ SolrはRedis等のキャッシュとRDBの間のようなイメージで使える

システム構成

レコメンド計算サーバ

Solrインポート用TSV

レコメンドSolrCloud

レコメンド計算結果を計算後、レコメンドキャッシュ用SolrCloudにデータインポートを実行

レコメンドSolrCloudレコメンド

SolrCloud

CMS API(Java)CMS API

(Java)CMS API(Java)CMS API

(Java)

LB

CMSDB

最終的にクライアントへ返す・返さないという制御はDBで実施並び順はSolrに格納されたレコメンド計算結果順になる

レコメンド用Solrには計算結果の各ID情報とスコア情報を持つ

○ シンプルな絞り込み検索だけ(ファセットは使わない)

○ データとしては以下のような形式(一部略)

schema定義

uid ユーザID ジャンルID カテゴリID 特集ID 特集スコア 作品ID

1 user1 anime SF feature1 0.998 s1,s2,s3...

2 user1 houga SF feature10 0.95 s10,s11,s12..

3 user1 anime Drama feature11 0.93 s20,s21,s22..

4 user1 youga Action feature18 0.89 s30,s31,s32..

5 coldstart anime Drama feature4 0.98 s4,s5,s6...

6 coldstart houga SF feature5 0.96 s14,s15,s16..

7 coldstart anime SF feature15 0.94 s24,s25,s26..

8 coldstart youga Action feature22 0.92 s34,s35,s36..

ジャンル単位でのレコメンド表示の際にジャンルで絞り込む

初期のカテゴリ選択のフィルタリングに利用

ユーザ毎のオススメ順にスコアが並んでいる

特集の中の作品の並び順もユーザ毎のオススメ順になっている

○ user1が好きなカテゴリにDramaとActionを選択した場合

□ => レコメンド結果はフィルタしない

□ => coldstart向けの結果はフィルタする

schema定義

uid ユーザID ジャンルID カテゴリID 特集ID 特集スコア 作品ID

1 user1 anime SF feature1 0.998 s1,s2,s3...

2 user1 houga SF feature10 0.95 s10,s11,s12..

3 user1 anime Drama feature11 0.93 s20,s21,s22..

4 user1 youga Action feature18 0.89 s30,s31,s32..

5 coldstart anime Drama feature4 0.98 s4,s5,s6...

6 coldstart houga SF feature5 0.96 s14,s15,s16..

7 coldstart anime SF feature15 0.94 s24,s25,s26..

8 coldstart youga Action feature22 0.92 s34,s35,s36..

選択していない対象は下においやられる

レコメンドでの課題

○ やること自体はただのfq(score関係ない)

○ データ件数が膨大

□ アクティブユーザ数 × 特集数 が最大のレコメンド結果のデー

タ数

□○ さすがに多すぎる... & 特集も見る人はそうそういない

□ => 1人あたり最大100特集をレコメンド

□□ ※ フリーワード検索は数十万件程度

Solrからはせいぜい100ms程度で応答して欲しい => 性能検証を実施

mask

mask

mask

性能検証

○ AWS上に本番相当スペックのSolrCloudを構築してテスト

○ Solrのシャード数:2

□ 2台のSolrに2シャード作ったので、1Solrあたり、1シャード

○ jmeterで実際の疑似クエリを生成し、ランダムにユーザを選択

し、Solrへの検索を行う

○ 10スレッド * 1000リクエストを実行

○ 10万ユーザの中からランダムで1人のユーザを選択してレコメンド

データをリクエスト

○ 事前に1000リクエスト投げてウォームアップを行う

○ 平均、90パーセンタイル値、最小、最大、エラー数を比較

性能検証結果

件数(万件) 平均(ms) 90%(ms) 最小(ms) 最大(ms) エラー件数

60 22 30 8 503 0

180 20 24 8 598 0

300 23 30 8 513 0

420 24 32 9 755 0

540 25 32 8 509 0

660 25 33 9 497 0

780 24 32 8 499 0

900 27 31 8 715 0

1020 26 35 8 520 0

性能検証結果

件数(万件) 平均(ms) 90%(ms) 最小(ms) 最大(ms) エラー件数

60 22 30 8 503 0

180 20 24 8 598 0

300 23 30 8 513 0

420 24 32 9 755 0

540 25 32 8 509 0

660 25 33 9 497 0

780 24 32 8 499 0

900 27 31 8 715 0

1020 26 35 8 520 0

件数が60万件〜1020万件までは平均・90パーセンタイル値共に大きな変化はなく、良好な性能

性能検証結果

件数(万件) 平均(ms) 90%(ms) 最小(ms) 最大(ms) エラー件数

60 22 30 8 503 0

180 20 24 8 598 0

300 23 30 8 513 0

420 24 32 9 755 0

540 25 32 8 509 0

660 25 33 9 497 0

780 24 32 8 499 0

900 27 31 8 715 0

1020 26 35 8 520 0

最小は10ms以下と驚異の性能を発揮最大はいずれも500ms以上のパターンがある=> ハズレを引く人がたまにいる可能性あり

エラー件数はいずれも0となっており発生していない状況

性能検証結果

○ 1 Solrあたり1シャードで 1 シャードあたり 1020 / 2 = 510万件ま

では性能的にはほとんど問題ない結果

○ 本番では6台のSolrCloudで6シャード2レプリカとした

□ 1Solrあたり2シャード

現状は特に問題がないため、暫くこの構成

mask

その他の工夫点

○ 本事例ではSolrのクライアントにJava(solrj)を利用

○ solrjをそのまま使うとタイプセーフではない...

タイプセーフにクエリを実行

try (HttpSolrClient httpSolrClient = new HttpSolrClient(“http://10.105.21.28:8983/solr/general”)) { httpSolrClient.setParser(new XMLResponseParser()); ModifiableSolrParams params = new ModifiableSolrParams(); params.add("q", "あの日"); params.add("defType", "dismax"); params.add("qf", "name kana^10 name_general^20 synonym synonym_general synonym_kana"); QueryResponse response = httpSolrClient.query(params); SolrDocumentList results = response.getResults(); LOG.debug("count={}", results.size()); LOG.debug("list={}", results); } catch (IOException | SolrServerException e) { throw new SystemException("error", e); }

タイプセーフにクエリを実行

○ ORMにDBFluteを採用

○ DBFluteの自動生成機能を使ってschema.xmlから各種クラス

を自動生成

SolrPagingResultBean<General> list = generalBhv.selectPage(cb -> { cb.query().dismax("あの日", queryField -> { queryField.put(GeneralMeta.Name, null); queryField.put(GeneralMeta.Kana, 10); queryField.put(GeneralMeta.NameGeneral, 20); queryField.put(GeneralMeta.Synonym, null); }); cb.specify().fieldUid(); cb.paging(10, 2); }); LOG.debug("list={}", list);

DBFluteの機能でページング検索が出来る

dismax検索出来る対象のフィールドがタイプセーフに指定可能

specifyでflをタイプセーフに指定可能

タイプセーフにクエリを実行

○ タイプセーフにすることでミスがあるとコンパイルエラー

○ 単純なミスがなくなる

□ なぜか動かなくて色々とエラー原因を探し回った結果、結

局ただのtypoだった...なんてことを防げる

○ 変更に強くなる

□ スキーマ定義を変えたけど、クエリの指定を変えるのを忘れ

た...なんてことを防げる

タイプセーフにした結果、本当に必要なロジックの実装に集中できる

まとめ

○ Solrの機能を有効活用すれば日本語でのイ

ンクリメンタルサーチも実現できる□ ただし、一部精度が悪い部分もあり

○ Solrを使えばレコメンド結果のような大量デー

タを高速に検索可能

□ RedisなどのKVSキャッシュでは出来ない絞り込み検

索が高速に可能

□ 特に単純な検索であれば高速なレスポンスが期待で

きる

○ タイプセーフは大事□ 無駄な時間をかけずに済む

まとめ

最後に

U-NEXTでは人材を募集しています

映画やアニメが好きなひとエンターテイメントが好きなひと

配信プラットフォームに興味のあるひと技術的なことが好きなひと

一緒に仕事をしましょう

http://unext.co.jp/recruit/

ご静聴ありがとうございました

Recommended