読者です 読者をやめる 読者になる 読者になる

よしだのブログ

サブタイトルはありません。

【Lucene / Solr】G1GC か CMS か?

IT、プログラミング Solr 検索エンジン

こんばんは!宿題が遅れてすいませんw 今日は、ちょっとポエムみたいになるので余り役に立たないかもしれませんが、G1GC とOSSコミュニティのお話です。

お約束の、この記事は、Solr Advent Calendar 19日目の記事です。

qiita.com

きっかけ

ツイッター某所で G1GC がインデックスを壊すことがあるというようなつぶやきを見かけたのがそもそもの始まりでした。 以下が張られていたリンクで、ElasticSearch 2.X のユーザーマニュアルの一部でして、内容は変更してはいけない設定について記載されたものです。

www.elastic.co

その中にガベージコレクションの設定についてのセクションが有り、以下のような記載があります。

  • デフォルト GC を変更するな (CMS、G1GC はインデックスを壊すことがある)
  • G1GC は素晴らしいがいまだに定期的にバグが見つかっている

結構強い表現で、なかなか衝撃を受けたのですが、これって Lucene をコアに使うSolr もかんけいあるんじゃね?と思い調査をはじめました。

先に結論、G1GC 自己責任でどうぞ

結論から言えば、コミュニティとしての統一見解はないそうです。G1GC をもう使ってもいいかもしれないけど自己責任でね、ということです。色々調べてみた内容を書くと以下の通りです。

Lucene Wiki

まず、Lucene wiki には Java / JVM のバグに関してページがあり、以下のように記載があります。(2015/1/9 #31 RovertMuir) どのような状況下でも Lucene を G1GC で走らせるな、と結構強い口調で書かれています。ちょうど、最終更新時にこの一文が書き足されたようです。

Do not, under any circumstances, run Lucene with the G1 garbage collector. Lucene's test suite fails with the G1 garbage collector on a regular basis, including bugs that cause index corruption. https://wiki.apache.org/lucene-java/JavaBugs

開発者向けメーリングリスト

一方でメーリングリストでは、G1GC はだいぶこなれてきたので使ってもいいんじゃない?という投稿がされます。(2015/1/2 Shawn Heisey-2) 投稿者自身はG1GCを使っていて一度も問題に合ったことは無いと書いています。 一方で Mark Miller などは Aggerssive Option と呼んでおり、パフォーマンスとリスクを図りにかけ選択すべきと書いています。

I've been working with Oracle employees to find better GC tuning options.  The results are good enough to share with the community

Depending on your needs and risk tolerance, you might make a different choice. http://lucene.472066.n3.nabble.com/Garbage-Collection-tuning-G1-is-now-a-good-option-td4176927.html

某ニュースサイトの記事

また、全く別のJavaのニュースサイトの記事 (2015/1/26 jaxenter) では、G1GC と Lucene のこの辺の戦いの経緯が紹介されています。 (よく見ると、このエントリーを書いたのは lucene と solr の commiter である Uwe Schindler でした)

その中で、Oracle Java 8 update 40 のアナウンスで G1GC は production ready だと発表が合ったことや、最近の Lucene のビルドではこれらのエラーが見られなくなったと記載しています。 しかし一方で、かつて起こっていたエラーを理解している人が誰もおらず、ただエラーが出なくなっているだけであるとも記載しています。

When observing the Lucene builds during recent months, the Lucene team noticed that the errors initially seen no longer occurred. This is also consistent with the statement by Oracle that G1GC is “ready for production” in Java 8 Update 40.

However, one may still feeling bad when putting it into production, because some of the errors were never understood; they simply no longer occur – there is nothing more one knows about. https://jaxenter.com/java-9s-new-garbage-collector-whats-changing-whats-staying-118313.html

とどめに Lucene Solr Revolution 2016: Stump the Chump 2016 より

先日の Solr 勉強会に参加した際に Lucene Solr Revolution の Stump the Chump で喋っていたよー、という情報をキャッチし早速確認してみました。動画はご覧になれますので是非どうぞ。トップコミッター陣が経緯を含めてきちんと説明しております。

www.youtube.com

以下、喋っている内容のメモ書きです。

  • 31分ごろから39分ぐらいまで
  • Oracle が JDK 9 から G1GC をデフォルトにしようとしているがどう思いますか? Solr のデフォルトを G1GC にする予定は有りますか? solr を CMS / G1GC で使うメリットデメリットは有りますか?
    • bin/solr にはデフォルトの jdk も gc もコーディングしていないので、動かしている環境の設定が使われる
      • 正しくは、gc は CMS がデフォルトとしてコーディングされている (後に指摘が入る)
    • G1GC がダメかどうかも、pros / cons も分からないがテストはしっかりやるべき
    • G1GC でインデックスを壊すことが単体テストで確認されていた(今は直されている)
    • CMS でも同様に壊れるケースがあった
    • Mark Miller : 個人的な意見では 60GB 以上のような大きなヒープサイズでは g1gc は良いオプション、30GB 以下のヒープであれば CMS はよくテストされていて安定しているのでこれまでデフォルトにしてきたしおすすめしてきた。また、docValue の登場でそれほど大きなヒープサイズは必要としなくなったので、誰かが変えようとしない限り CMS のままだと思う。一方で、たくさん g1gc を使っているが特に問題は起きていないお客さんもたくさんいる。
    • java 8 でのパフォーマンス計測では g1 が良いという結果もでている
    • lucene の website (多分 lucene wiki のことだと思う) にあるコメントは g1 を使うなと書いてあるが、それはひどくクラッシュすることがあったので書かれたが、一人のコミッターが書いたのであってコミュニティの総意を示しているわけではない
    • 観客のコメント)5000台の solr を CMS から g1gc にスイッチしたが、リカバリーの原因になるような 15秒を超える gc の回数は確実に減った

Lucene Solr Revolution 2016 の初日なので 2016/10/13 のコミッターの発言です。この Stump the Chump というセッション自体が、お酒を飲みながらフランクにコミッターが質問にこたえるよ!というないようなので、適当なことを言っているのかと思いきや、結構ちゃんとしたことをいっているw

Java 8 Update 40 以降、G1GC 絡みのバグは減っているのか?

一応見てみたけど、よくわかりませんね。。

  • Oracle Java のほうは、database の検索の絞込が使えずわからない。
  • 参考で Open JDK については、多くないが Java 8 にも影響する G1 の問題がまだたくさんあることがわかる。

https://bugs.openjdk.java.net/browse/JDK-7178365?jql=project%20%3D%20JDK%20AND%20issuetype%20%3D%20Bug%20AND%20status%20in%20(Reopened%2C%20Open%2C%20%22In%20Progress%22%2C%20New)%20AND%20text%20~%20G1

調査のまとめ

まとめると、以下のような状況であることがわかりました。

  • ElasticSearch では、G1GC は使わずに CMS を使うべきとしている
  • Lucene/Solr については、統一的な見解を見つけることはできなかった。コミッターにより温度感はまちまちの模様。G1GC 使ってもいいかもしれないけど、テストしてからがいいんじゃね?とのこと。
  • OpenJDK には G1 に関するバグがまだたくさんあるが、Lucene にどのぐらい影響があるかはわからない

個人的には G1GC が原因でインデックスがロストした、という事象を聞いたことはないので、まあ、使ってもいいのかなと思ったりするのですが、使い方や設定、バージョンや Oracle なのか Open なのかによりけりな部分もかなり大きいのでテストしましょ、というのがやはり正解だと思います。Oracle Java 9 からはデフォルトで使用する、という熱の入れようなようなので、Java 9 で安定することに期待しつつ。

【Solr】クエリのオペレータが無視される、仕様?!

IT、プログラミング Solr 検索エンジン

こんにちは!今日は Solr の小ネタを書こうかと思います。この記事は Solr Advent Calendar 2016 の16日目の記事です!

qiita.com

qf に存在しないフィールドを含めると、q に指定したオペレータが検索キーワードとして扱われる。

今日ご紹介するのは、罠というかバグのような Solr の仕様です。対象の Solr のバージョンは 5.x 〜 trunk まで全てです。

Solr で edismax を使う際に、よく検索対象としたいフィールドを指定する際に qf パラメータを使うと思います。この、qf でキーワード検索したいフィールド名を羅列しておくと、q パラメータでフィールド名を指定せず、キーワードだけ指定することで qf で指定したフィールドを横断的に検索してくれます。

この qf に shema 定義に存在しないフィールド名を指定するとどうなるかというと、なんと q で指定した AND や OR などのオペレータが単なる文字列として検索されます。。

例)hogefoobar というフィールドは存在しないケース

q : 日本 OR タバコ defType : edismax qf : title hogefoobar

/select?q=日本 OR タバコ&defType=edismax&qf=title hogefoobar

上記のようなパラメータで検索すると、title フィールドに「日本」「OR」「タバコ」のいずれかのキーワードが含まれるドキュメントがヒットします。。

原因を調べてみたところ、以下の箇所で クエリのパース時に Exception を受け取ると、q の中身をエスケープして再検索するようになっています。qf に適当なフィールド名を指定すると、FieldNotFoundException が投げられ、この箇所でキャッチしエスケープして再検索しているため、OR は文字列として扱われます。。

https://github.com/apache/lucene-solr/blob/master/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java#L310-L313

まとめ

というわけで、雑に qf に適当なフィールド名をつけて投げるとひどい目に合うよということで、きちんと qf には存在しているフィールドを指定しているか確認しましょう。 特に managed schema (スキーマレスモード) を使用していると、フィールドの管理が雑になりがちなので注意が必要だと思います。

一応

パッチを送っていたりしますが・・マージはされないかもしれません。

[SOLR-9677] edismax treat operator as a keyword when a query parameter 'qf' contains inexist field. - ASF JIRA

第19回Lucene/Solr勉強会 #SolrJP

IT、プログラミング Solr 検索エンジン

こんにちは!久しぶりの Lucene Solr 勉強会です。 メモを公開しますー。

NLP4Lを使ったランキング学習


株式会社シーマーク 山本 高志 様

www.slideshare.net

講演内容メモ

  • Apache Lucene のための自然言語処理ツール (OSS) github.com

  • 何ができるか?

    • 固有表現抽出
    • 文書分類
    • キーフレーズ抽出
    • ランキング学習 (今日のテーマ)
    • など・・
  • 標準提供のランキング学習モデル
    • PRank
    • RankingSVM
    • など・・
  • ランキング学習のフロー
    • クエリログ・アノテーションを教師データとして使用する (作成を支援するツールも含まれる)
    • Feature の抽出、トレーニング、モデル配置までできる
    • モデル評価の機能は未実装だが今後やっていきたい
  • Featrure は Lucecene で取得できる値を利用する
    • TF/IDF や BM25 など
  • リランキングは Solr の標準の機能を使用している
    • ランキングモジュール自体は NLP4L に含まれる PRank や RankingSVM をクエリで指定する
  • Bloomberg 版 LTR との比較

一言・感想

  • UI がついているので教師データの作成は非常にやりやすそうです。大規模なケースで複数人で手分けする場合にはどのようにできるかが気になった
  • Bloomberg 版とのもう少し詳細な比較が見たいです。

Lucene/Solr Revolution 2016参加レポート

楽天株式会社 中田 晋平 様


www.slideshare.net

講演内容メモ

  • 3点ピックアップして紹介

  • Working with Deeply Nested Documents in Apache Solr

    • 昔は Solr はいまいちだったが、5系ならイケる
    • 普通の入れ子の場合(2段階まで)
      • Lucene は flat な index しか持てないので、連続した docid に親子のデータを配置する、親子の区別用にもう一つのフィールドを利用する (boolean の isParent みたいな)
      • Block Join Query
    • Deeply Nested Document の場合
      • 基本的に Nested と同じ、どの階層でも独立した1つのドキュメント
      • path という考え方を導入、親子の構造を string のフィールドに含める、簡単に処理できるように PreProcessor を用意した
      • 親子孫含めて横断的に検索するには、v5.3 以降の ChildDocTransformerFactory を利用するとできる
  • Rebalancing API for SolrCloud

    • SolrCloud の運用を楽にする API を作成した
    • Rebalancing API を作成し、Solr に contribute (6系に含まれる?)
    • Re-sharding
      • 再インデキシング不要でシャードを増やす
      • 別の shard を用意して一旦マージした後に再度分割する
    • マイグレーション
    • 冗長化 (レプリカを増やす)
    • Reindexing なしなので速い、設定ファイルも一緒に配布する
  • The Evolution of Lucene Solr Numerics from Strings to Pints

    • 数値の文字列表現について
      • 最初は String で全て持っていた
      • 2009 年、trie numerics に置き換え
        • trie 木の一部にデータが集中するデータは非効率になっていた
      • 2016 年、dimentional point に置き換えを予定 (lucene にはすでに入っている)
        • k dimension tree を使用する
        • それぞれの次元に対して繰り返し分割する、分割した結果を元に tree を作成する、分割は中央値で分割する
        • ノードが一定数まで分割して終了する
        • データの密度に応じてバランスする

一言・感想

  • G1GC が OK というのがびっくりですね!私も参加していたのですが、知りませんでした。 (TODO: あとでしらべる)

Solrを活用した施設情報検索システムの取組み

株式会社NTTドコモ 榎園 健 様



講演内容メモ

  • 施設情報検索に特化した検索エンジン
    • 施設名称・所在地を考慮して検索 (横浜のコストコとか)
  • 課題1:地名解釈
    • 「横浜 ドコモ」で検索しても、ドコモショップ 戸塚店はヒットしない (戸塚店は、横浜市戸塚区にある)
    • edismax は、スコアが最大のフィールドのみを採用するので、地名でヒットしてもスコアは採用されない場合がある
    • プラグインを開発
      • クエリ内に地名が存在している場合、クエリを拡張して地名のフィールドに対して OR 検索してスコアを加算する
  • 課題2:施設人気度
    • 例:「赤坂サカス」で検索したときに、「スターバックスコーヒー赤坂サカス店」などもヒットするので、「赤坂サカス」自体が埋もれる
    • 施設に人気度や有名度が必要
    • アプローチ1:ツイートを使って人気度付与
      • 同名の施設は、共起語で判定 (北海道の円山公園か、京都の円山公園か)
      • 人名か地名か、説明を用意しておき共起語を用意しておく
    • アプローチ2:位置情報データ(モバイル空間統計)を活用した人気度付与
      • 人口分布の時間変動を把握できるので、人がたくさん集まっているところに高いスコアを付与する
  • 課題3:アプローチ3:パラメータ探索、フィールドごとに重みを設定してチューニングするが少し変えるだけで大きく変わるので難しい
    • 機械学習に寄るパラメータ探索
    • Hyperopt

一言・感想

  • モバイル空間統計 が利用できるのはドコモならではのチートですね!w

AtomicUpdateを50000倍速くした話(SOLR-9592)

ヤフー株式会社 石川 貴大 様


講演内容メモ

  • Atomic Update 部分更新などでよく使われる
  • 一度検索し直すので余り速くない (Realtime Get)
  • 実際に検証すると超遅い (0.1 dps ??)
  • Remote Debug や、コードリーティング、FlameGraph などで調査
  • SlowCompositeReaderWrapper
    • Lucene 的にはセグメントごとに LeafReader を使って個別に検索することが推奨されている
    • が Solr はそれに追いついておらず、SlowCompositeReaderWrapper ではこっそりこれをマージして横断して検索していた
      • SolrIndexSearcher から SlowCompositeReaderWrapper が呼ばれる・・
    • やったことは、LeafReader ごとに検索するように直した
    • v6.3 以降取り込まれる
  • 実は Lucene には一度取ってこなくても更新できる実装が進められている
    • posting が貼れないので検索には使えない

SolrCloud のリカバリー処理

IT、プログラミング Solr 検索エンジン

こんにちは!ご無沙汰しております。 この記事は Solr Advent Calendar 2016 の 1日目です!

qiita.com

一日目の出だしにしてはかなり渋め(アドバンスド)な内容かなと思いますが、SolrCloud のリカバリー処理についてコードを読んだり調べてみたので書いてみたいと思います。しかし、特に更新が非常に多い Solr を運用している場合は、最後だけでも是非読んで下さい。読まないと、全台ダウンしちゃいますよ!

今回は、特に文章だらけで申し訳ありませんが、よろしくお願いいたします。

前提

  • SolrCloud をざっと触ったことがあり、複数のレプリカやシャードの構成を組んだり使ったことがあることがある読者を前提に書きます。
  • Solr 5.5 のリカバリー処理について書きます。Solr 6 もそんなに違わないと思いますが。

リカバリー処理とは?

リカバリー処理とは、SolrCloud環境で同一シャード内のレプリカでインデックスデータの同期が失われた場合に、再度同期をとるために Solr によって行われる処理です。 例えば、1シャード3レプリカの3台の構成で、そのうちの1台がダウンしていた場合にインデックスの更新が行われた後、ダウンしていたサーバーが復旧すると、リカバリー処理が行われダウン中に実施されたインデックスの更新が反映されます。 ダウンしていたサーバーが復旧すると Solr Admin の画面で、Recovering となっているとこの処理が行われています。

その前にトランザクションログについて

SolrCloud の構成では、各 Solr のインスタンスごとにトランザクションログ(もしくはアップデートログ、移行 tlog と記載します) を保持しています。この tlog は SolrCloud の Near Realtime Search とデータの確実性を保つために導入されたファイルです。tlog には、データベースのトランザクションログと同様に更新リクエストのインデックス処理前のそのままのデータが記載されています。

SolrCloud ではあるインスタンスに更新データが届くと以下のような処理が行われます。

  1. tlog に書き込む、この際確実にディスクに書き込まれたことを確認する (fsynch)
  2. 書き込まれたことを確認したら、インデックスをメモリ(Java のヒープ上)だけに反映する (soft commit)
  3. (commitされると) メモリ上のインデックスデータを、インデックスのファイルに書き込む (hard commit)

hard commit は、/update?commit=true を送ったりすると行われます。

この時、2 と 3 の hard commit を実行する前にインスタンスが落ちると、インスタンスのヒープ上にしか無い更新データは消えてしまいます。 消えてしまうと困るので、インスタンスが復旧した際に tlog の有無を確認し、あった場合 tlog を再度適用し(リプレイといいます)、インデックスに反映します。ちなみにこの時行われるリプレイでは、いきなりインデックスのファイルに書き込まれます。

これがトランザクションログの役割で、インスタンス1つだけに注目した場合の処理です。以下の記事がこの辺のことを詳細に書いてあるのでご参考に。

lucidworks.com

では、レプリカ1にはインデックスがあって、レプリカ2がディスク障害でインデックスが飛んでしまった場合はどうでしょうか?

リカバリー処理の流れ

インスタンスをまたがったインデックスの障害のケースでも対応できるようにリカバリー処理は作られています。

まず、リカバリーはインスタンスがコレクションに参加した時に必ず行われます。この参加時とは、新規に追加した場合以外にも、ダウンしていたインスタンスが起動した場合も含まれます。この時、リカバリー対象のノードとリーダーノードのデータを比較します。 比較した結果トランザクションログとインデックスに差異がなければ処理は何も行われません。差異があった場合に、実際のリカバリーの処理が行われます。ちなみに、インスタンスを停止してすぐに起動しても一瞬リカバリーに入るのはこのチェックをしているためです。

また、自分自身がリーダーノードである場合も実際の処理は行われません。後述しますが、リーダーノードを正しいデータとしてリカバリーが行われるためです。

対象のノードがフォロワーで、リーダーノードのデータと差異がある場合にのみ、インデックスに対する処理は行われます。実際のリカバリー処理は以下の3ステップで行われます。

1. tlog のリプレイ

まず、tlog がある場合はリプレイします。ここまでは、1台のケースと同じです。

2. PeerSync

次に PeerSync と呼ばれる処理が行われます。

PeerSync とはインスタンスのダウン中に、インデックスの更新リクエストがされ、ダウンしていない同一シャード中の他のサーバーでインデックスが更新された場合に同期するための処理です。ダウンしていたインスタンスでは、PeerSync はリカバリー処理時には必ず実行が可能かチェックされます。具体的な実行が可能かどうかはどのぐらいインデックスに差異があるかを確認し、差異が少なければ PeerSync を行います。

PeerSync はリーダーのノードが保持している tlog と、自分の tlog を比較し大量の差異が無い場合は、行われていない tlog のみをコピーしリプレイを行いインデックスの同期を行います。tlog をコピーするため、tlog の同期も行われます。PeerSync の同期に成功した後はこの後のレプリケーション処理は行いません。

PeerSync が実行可能かどうかのしきい値は、トランザクションログ1ファイルに最大何件保持するかという設定 numRecordsToKeep に依存しています。numRecordsToKeep の件数を 100% とし、自分の新しいドキュメントの新しい方から 20% のインデックスのバージョンが、リーダーの新しい方から 80% のインデックスバージョンよりも古い場合は PeerSync しません。また、逆に自分のインデックスが新しい場合ももちろん PeerSync しません。わかりやすく書くと、単純な更新処理の場合、tlog 1ファイルに保持する最大件数(numRecordsToKeep)が 100 の場合、ダウン中に更新されたドキュメントが 60件未満なら PeerSync 、それ以上ならレプリケーションが行われます。

なぜ、全て PeerSync でリカバリーが行われないかというと、tlog はインデックスの情報ではなく処理前の更新リクエストがそのまま保持されているので、データのサイズが大きくなりがちで、さらに、リプレイ時にインデックス処理が行われるため比較的時間がかかること、tlog には全データを保持していないことなどの理由があると思われます。

3. レプリケーション

PeerSync で同期できない程度にインデックスに差異があった場合に行われるインデックスの同期処理が、レプリケーションです。

レプリケーションではインデックスのバイナリファイルごとリーダーからコピーし、同期をとる処理です。バイナリファイルはインデックスのセグメントごとに分割されており、差異があるセグメントのインデックスファイルのみコピーしインデックスの同期をおこないます。

レプリケーションでは、セグメントごとにわかれたインデックスファイル単位でコピーするため、Optimize などセグメント全体に作り変えが発生する処理を、サーバーのダウン中に行うと全インデックスのコピーが起きるため、レプリケーションに非常に時間がかかるので注意が必要です。ちなみに、レプリケーションではトランザクションログの同期は行われないので、ある程度の件数が新規に更新され、tlog の同期が取れるようになるまでは、ダウンしていたインスタンスを単純に起動・停止するだけでこのレプリケーションまで処理が進み、ファイルはコピーされずに終了します。

また、リカバリー中の更新リクエストはトランザクションログの buffered update という箇所に別途保存され、レプリケーション後にリプレイされます。リプレイ中にも受け取った更新リクエストは buffered update に追記されリプレイされ、全てのリプレイ処理が完了したのちに status が active になります。

何がリカバリー処理のトリガーとなるか?

リカバリー処理のトリガーとなる現象は、インデックスデータに差異が見つかること以外にもいくつかあります。例えば、更新処理時にリーダーからフォロワーにデータが転送されるのですが、この転送が失敗したり、フォロワーでのインデックス処理が失敗したと判断されると、リーダーからフォロワーにリカバリーを行うよう、リクエストが送信されます。

numRecordsToKeep の値に注意!

このリカバリー処理、よく出来ているのですが、更新リクエストが非常に多いユースケースについては注意が必要です。どういうことかというと、気にせずにデフォルト設定で使用すると、リカバリーがいつまでたっても終わらない、ということが起こりえます。

デフォルトの設定ではトランザクションログ1ファイルあたりに保持できるレコードの件数を示す、numRecordsToKeep の設定が 100 しかありません。更新処理が大量にある場合にリカバリーに入ると、ダウン中に発生する更新処理がしきい値を簡単にオーバーするので PeerSync は行われず、レプリケーションに入ります。レプリケーションでは、比較的時間のかかるインデックスのコピー処理が開始されますが、その間にもどんどん更新リクエストが入ってきます。入ってきた更新処理は、リプレイされインデックスに順次反映されますが、コピー中にたまった更新リクエストの数が多いと、リプレイ中にもどんどんトランザクションログに更新リクエストが貯まるので、終わらないという状態になります。

このリカバリー中のインスタンスは検索はできるのですが、検索結果が古く正しくないので、一般的なシステムではリカバリー中のサーバーには検索リクエストを飛ばさないようにすることが多いです。1台がずーっとリカバリー中だと、その分、外のサーバーに検索リクエストの負荷がかかるので、放おって置くと次々にサーバーがダウンし、全台ダウンすることがあります。

なので、numRecordsToKeep の値を大きめに設定することで、できるだけリカバリー処理を PeerSync で済ませることがベスト・プラクティスとなります。ただし、numRecordsToKeep を大きくするとディスクを大量に食うので注意が必要です。

まとめ

numRecordsToKeep の値を大きくするのはもちろんですが、適切な値を決めるには徹底的な負荷検証とチューニングをお忘れなきよう。

明日のアドカレは近藤さん (tkondo) による、更新処理のパフォーマンスに関するないようになりそうですー!乞うご期待。

qiita.com

embulk-output-solr を公開しました!

IT、プログラミング 検索エンジン Solr

こんにちは! fluentd の兄弟で、オープンソースのバルクローダー Embulk の Solr 用 Output プラグイン embulk-output-solr を公開しました。 ぜひご利用ください。Github のリポジトリは以下です。

https://github.com/yahoojapan/embulk-output-solr

使い方

まず gem をインストールします。

embulk gem install embulk-output-solr

以下のような設定を embulk の config.yaml に記載します。以下の例では、localhost:8983 の Solr の mytest というコレクションに対してデータをフィードします。bulkSize は一度に送るドキュメント数で、idColumnName は id を示すフィールドの名前を記載します。

out:
  type: solr
  host: localhost
  port: 8983
  collection: mytest
  bulkSize: 500
  idColumnName: id

何がうれしいか?

他の output plugin と同様ですが、色々なリソースから取得したデータを Solr に一括でフィードすることが出来ます。例えば、embulk-input-jdbc と組み合わせることで、データベースに入っているレコードを Solr にフィードするバッチが簡単に用意することができます。embulk-input-jdbc にはWHERE句を指定する機能もあるので、例えば更新されたレコードだけフィードする、というような差分更新も簡単に実現できます。

embulk の素晴らしいところは、input と output をどのようにでも組み合わえることができるので、input プラグインさえあればどのようなデータソースでも Solr で検索できるようになることです。*1

一昔前のプロプライエタリな商用の検索エンジンでは、コネクターといっていかに多くのリソースに対応しているかが競争力の源泉の一つだったのです。なぜなら、そもそも検索したい対象のリソース(Oracle やら MySQL やら SharePoint やら ContentManager やら Documentum やら・・)につながらなければいけないし、SIer にコネクターを自作させるのもコスト的にも将来の保守的にも不安があったので、すでに製品に対応しているコネクターが用意されていれば当然そちらを購入していたわけです。もちろん、全てのリソースに対してプラグインがあるわけではないのですし、昔と今とでは OSS の受け入れ度も全く違うので一概には言えませんが、今やこんなに簡単に色々なリソースと繋がるなんて夢のようだなーと遠い目をしてしまうわけです。

embulk-parser-xml2 も公開しました

Solr と合わせて xml2 という parser プラグインも公開しました。これは、input で読み込んできた xml をパースしてフィールドと値を抽出して embulk で扱えるようにすることができるプラグインです。

https://github.com/yahoojapan/embulk-parser-xml2

embulk の parser にはすでに xml 用のプラグインが2つあるのですが、一方は DOM でパースしており大きな XML 文書は扱えず、もう一方は SAX パーサーですがサブエレメントを扱うことが出来ませんでした。例えば、以下のような XML があり、page タグを 1 文書として扱うとして設定したい場合、id タグと title タグは扱うことが出来たのですが、revision/timestamp や revison/text タグは扱うことが出来ませんでした。

<mediawiki>
  <page>
    <id>1</id>
    <title>title 1</title>
    <revision>
      <timestamp>2004-04-30T14:46:00Z</timestamp>
      <text>body text</text>
    </revision>
  </page>
  <page>
    <id>2</id>
    <title>title 2</title>
    <revision>
      <timestamp>2004-04-30T14:46:00Z</timestamp>
      <text>body text</text>
    </revision>
  </page>
</mediawiki>

このプラグインを利用することで embulk を使って、大きな XML 文書でかつ上記のようなサブエレメントが必要なドキュメントも、高速にパースして取り込むことができるようになります。例えば、Wikipedia のダンプを Solr に取り込ませたい場合、このプラグインを使うといい感じに読み込むことが出来ます!

ぜひこちらもご利用ください。

*1:inputプラグインの一覧は以下のブログにまとまっています。 http://qiita.com/hiroysato/items/da45e52fb79c39547f69#input%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3

Solr 6 新機能の紹介

IT、プログラミング Solr 検索エンジン

https://lucene.apache.org/solr/6_0_0/images/solr.svg

だいぶ時間が立ってしまいましたが、Solr 6 がリリースされました!というわけで、Solr 6 の新機能をドキュメントから調査してみました。調査する新機能は、yonik さんの以下のエントリを参考にしました。*1

http://yonik.com/solr-6/

Parallel SQL

https://cwiki.apache.org/confluence/display/solr/Parallel+SQL+Interface#ParallelSQLInterface-JDBCDriver

  • Solr で SQL が使える (select のみ、intsert / update / delete は対応しない)
    • 裏では Presto の SQL Parser を使って Streaming Expression (こちらも新機能、後述) に変換している
  • テーブル
    • テーブル名 = コレクション名
    • case insensitive
  • カラム
    • カラム名 = フィールド名
    • case sensitive
    • エイリアスをサポート、order by クラウスで参照できる
      • は利用不可
    • カラム名に score を指定することは LIMIT クラウスが含まれる場合のみ可

例1:techproducts コレクションのフィールド、manu と price を検索する

SELECT manu as mfr, price as retail FROM techproducts
  • Aggregation Model

    • Solr の SQL では、aggregation (結果のグルーピング) を2つの方法でサポートしている
      • Solr へのリクエスト時のパラメータで、どちらを利用するかを選択できる
    • map_reduct : ワーカーノードに送信する際にシャッフル (つまりソート)を行い、ワーカーノードで aggregation を行う。GROUP BY を行う対象のフィールドのカーディナリティ (値のバリエーション) に上限はないが、検索結果全体をネットワーク上に送信するコストがかかる点に注意が必要。
    • facet : JSON Facet API / StatsComponent を利用する方法。通常の検索と同様に Solr の検索で処理させる方法なので、ネットワークに送信されるのは集約された後の結果のみ。フィールドのカーディナリティが低い場合にはとても速い。
  • 設定

    • リクエストハンドラに、/sql と /stream 、/export を設定する (いずれもデフォルトでは暗黙的に設定されているので、solrconfig に書く必要はない)
    • SQL で使用するフィールドについて、docValues を設定する
  • JDBC Driver

    • SolrJ を JDBCDriver として利用できる
  • HTTP
    • /sql に対して、SQL を POST しても利用できる

例2:HTTP による POST

-bash-4.1$ curl --data-urlencode "stmt=SELECT id, price FROM techproducts WHERE popularity=1 LIMIT 100" http://ojt02.takumyos.ssk.ynwm.yahoo.co.jp:8983/solr/techproducts/sql?aggregationMode=facet
{"result-set":{"docs":[
{"price":19.95,"id":"F8V7067-APL-KIT"},
{"price":11.5,"id":"IW-02"},
{"EOF":true,"RESPONSE_TIME":6}]}}
  • 利用可能な表現
    • LIMIT
      • LIMIT が指定されていると score を得ることができる
      • LIMIT が指定されていると stored field なら field list に指定できる。unlimited の場合 docValues フィールドのみ利用できる
      • LIMIT が指定されていると indexed field なら order by に指定できる。unlimited の場合 docValues フィールドのみ利用できる
    • WHERE
      • = , () , OR , [ ] , NOT
    • ORDER BY
    • DISTINCT
    • Statistics
      • count / min / max / sum / avg
    • Group by
      • Having

メモ:まだ不安定な様子なので実際に利用するには程遠い

ざっと触ってみただけでも、以下の様な問題が有り、枯れていない印象です。(もしかしたら単にドキュメントが間違えているだけかもしれません・・w → Streaming Expression のドキュメントの間違いについては別のエントリにまとめます。)

  • WHERE id=apple とすると、正しくない結果が得られる (id は docvalues のフィールドではない)
  • WHERE price=[0 to 100] が利用できない (InvalidNumber エラーが出ている)
-bash-4.1$ curl --data-urlencode 'stmt=SELECT id, price FROM techproducts WHERE price="[0 to 100]" LIMIT 100' http://ojt02.takumyos.ssk.ynwm.yahoo.co.jp:8983/solr/techproducts/sql?aggregationMode=facet
{"result-set":{"docs":[
{"EXCEPTION":"java.util.concurrent.ExecutionException: java.io.IOException: --> http://172.21.234.168:7574/solr/techproducts_shard2_replica2/:Invalid Number: ","EOF":true,"RESPONSE_TIME":7}]}}

Streaming Expressions

https://cwiki.apache.org/confluence/display/solr/Streaming+Expressions

Streaming Expression は現状、実験的な API のため将来インターフェースが変更される場合があるそうですのでご注意ください

Streaming Expression では、並行に実行される異なるタスクの結果を結合することができる機能です。

例1:Stream Expression で検索している例。

-bash-4.1$ curl --data-urlencode 'expr=search(techproducts,q="*:*",fl="price", sort="price desc")' http://ojt02.takumyos.ssk.ynwm.yahoo.co.jp:8983/solr/techproducts/stream
  • search は export リクエストハンドラに依存しているので、sort と fl が指定されている必要がある。
  • sort に指定された項目は fl でも指定する必要がある。

指定可能な function

  • search : Solr で検索を行うファンクション、利用可能なパラメータに制限がある
  • jdbc : Stream で JDBC を使って Select を実行して結果を得る
  • facet : facet 検索を使って、バケットごとに集約する機能を提供する。
  • stats : 検索結果に対してシンプルな集約関数を提供する (sum/avg/min/max/count)
  • topics : pub/sub メッセージング。ユーザーはクエリを予め登録しておき、クエリに該当するする新規に登録されたか更新されたドキュメントを一度だけ返す。

指定可能な decorator

  • complement : クエリAとクエリBを指定し、A にはあるが Bに含まれない結果を出力する
  • daemon : 指定した間隔で繰り返しファンクションを実行する (例:あるコレクションAに対する検索結果をコレクションBに継続的に登録する、
  • innterJoin : DB の inner join に相当、右のStreamに存在する Left のデータを結合して出力する。 (2つのストリームを連結するフィールドでソートしておく必要がある)
  • intersect : Left の Stream に存在する RIght のデータを出力する。Left のデータと結合せず、Right のデータのみを出力する。(2つのストリームを連結するフィールドでソートしておく必要がある)
  • hashJoin : DB の inner join に相当、右のStreamに存在する Left のデータを結合して出力する。(2つのストリームがソートされている必要が無いが、メモリを多く利用する)
  • Merge : 2つ以上の複数の Strem を結合する。あらかじめ指定したフィールドでソートしておく必要がある。 (DBのUnion)
  • leftOuterJoin : DB の left outer join と同様に、指定した2つの結果をくっつけることができる (2つのストリームを連結するフィールドでソートしておく必要がある)
  • outerHashJoin : DB の right outer join に相当 (2つのストリームがソートされている必要が無いが、メモリを多く利用する)
  • parallel : 複数の worker ノードに指定したストリームを並列して処理させることができる
  • reduce : 共通のフィールドでグルーピングする。
  • rollup : 指定したフィールドで Steream をグルーピングし、集計を行う。 (sum / min / avg / max / count ) グルーピングするフィールドで予めソートしておく必要がある
  • select : 元のストリームに含まれるフィールドのサブセットや変更されたフィールドを出力する。(出力するフィールドのチョイス、rename、値のリプレイス)
  • top : 指定した N 件の結果を出力する。
  • unique : 指定したフィールドの値を基準にユニークなストリームを生成する
  • update : 指定したストリームを元に、インデックスの更新処理を行う。

Graph Traversal Query

https://cwiki.apache.org/confluence/display/solr/Other+Parsers#OtherParsers-GraphQueryParser

  • ファンクションクエリの一つ
  • 幅優先探索、cyclic aware (閉路をサポート?)
  • クエリパラメータの to と from に指定したフィールドの値を元に、ドキュメント間のリンクを構築する

注意:上記の cwiki のドキュメントにある、Example のグラフの絵が正しくない

制限:この機能は1つの Solr インスタンス内でしか結果をたどることが出来ません! 6.1 では Streaming Expression で複数の分散ノードでのグラフ検索がサポートされる予定です。

パラメータ

  • to : 入力エッジを示すフィールド名を指定
  • from : 出力エッジを示すフィールド名を示す
  • traversalFilter : クエリー、マッチしたノード(ドキュメント)をフィルターする条件を指定する (あるノードがマッチしなかった場合、その先のノードは探索されない)
  • maxDepth : ノードに許容されるルートノードからの距離(最大エッジ数)
  • returnRoot : ルートノードを結果に含めるかどうか (boolean)
  • returnOnlyLeaf : リーフ(incoming edge だけのノード)だけを返すかどうか (boolean)
  • useAutn : 幅優先探索のイテレーションごとにオートマトンをコンパイルするかどうかを指定する (boolean)

メモ

BM25 Scoring

  • Okapi BM25 がデフォルトになります。
  • これまでの TF-IDF を使いたい場合は、ClassicSimilarity を使ってね。

Filters for Real-Time Get

https://cwiki.apache.org/confluence/display/solr/RealTime+Get

  • Real-Time Get (リクエストハンドラー /get ) で fq をサポート。取得するドキュメントをフィルターすることができる

Cross Data Center Replication (CDCR)

https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=62687462

  • 複数のデータセンター間での、データのレプリケーション
  • active-passive シナリオのみを本バージョンではサポート
    • データの更新があった場合に、ソースとなるデータセンターから、ターゲットのデータセンター(複数)にレプリケーションする
    • その逆はない、ターゲット側で更新してもソースには反映されない
    • レプリケーション中も検索はどちらでも可能
    • ターゲットのデータセンターではデータが反映されるまでの遅延が数秒程度生じる
  • インデックスデータが disk に書き込まれてからレプリケーションは開始される (commit されてから)
  • すぐにレプリケーションを開始するか、スケジュールするかは選択することができる
  • それぞれのシャードリーダーどうしがレプリケーションを行う
  • コネクションの切断や帯域の劣化などに耐えられるように設計されている、またコミュニケーションの最適化のためにバッチアップデートもサポートする
  • 更新分のデータの一貫性は保証するが、インデックス全体の一貫性は保証しない (気にしない)
    • つまり、CDCR を開始する前にすでに登録されていたインデックスはそのまま保持される
  • コレクション単位で設定する
  • ソースの Solr クラスタからターゲットの zookeeper アンサンブルが見えるようになっている必要がある
  • 高頻度のアップデートがある環境で builk-load する用途には向いていない場合がある (帯域による) 。その場合は、あらかじめ bulk-load して同期した後に、CDCR を有効にすると良い。
  • 使用可能な帯域の指定や制限などはできないが、batch-size やスケジュールである程度コントロールできそう

メモ

  • 単純なレプリケーションだけでなくインデックスのマイグレーションに使えるかも?
    • Solr の構成が異なっても問題ないか? (シャード数、レプリカ数)
    • on the fly でレプリケーション先を指定できるか? また、解除できるか?
    • 現在登録されているデータのレプリケーションが完了したことを簡単に検知できるか? (batch update モード?)

おすすめ書籍

[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン (Software Design plus)

[改訂新版] Apache Solr入門 ~オープンソース全文検索エンジン (Software Design plus)

  • 作者: 大谷純,阿部慎一朗,大須賀稔,北野太郎,鈴木教嗣,平賀一昭,株式会社リクルートテクノロジーズ,株式会社ロンウイット
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/11/29
  • メディア: 大型本
  • この商品を含むブログ (8件) を見る

検索エンジン自作入門?手を動かしながら見渡す検索の舞台裏

検索エンジン自作入門?手を動かしながら見渡す検索の舞台裏

*1:リリースノートを見ると、ほぼ全てカバーできているみたいでしたが