クックパッドの検索の裏側

初めまして、インフラストラクチャー部の加藤 (@EugeneK) です。

クックパッドでは現在178万ものレシピが公開されていますが、目的のレシピを探すために検索機能を提供しています。 今回は検索機能の裏側の仕組みについて、インフラストラクチャーの観点からお話ししようと思います。

全ての検索機能を支えるSolrと周辺のアーキテクチャ

クックパッドにはレシピの検索だけでなく様々な検索機能がありますが、その全てはSolrを活用して実装されています。 以前はMySQL Tritonnによる全文検索機能を使用していましたが、2011年頃からSolrに切り替わりました。

クックパッドではSolrをマスタ - スレーブ構成にすることで冗長性と負荷分散を実現しています。以下の構成図をご覧ください。

f:id:EugeneKato:20141021205109p:plain

マスタとスレーブの間には、リピータと呼ばれる検索インデックスを中継するためだけの役割のサーバがいます。このサーバを介することで、多数のスレーブが同時にマスタから検索インデックスを受け取ってもマスタが高負荷にならないようにしています。 また、クックパッドのサービスが稼働するアプリケーションサーバはスレーブのみを参照するので、間にバランサを挟むことで冗長性と負荷分散を同時に実現しています。

スレーブのグルーピング

クックパッドの検索インデックスは1つではなく、複数の検索インデックスがあります。 一番よく使われるものはレシピのインデックスですが、他にもつくれぽやユーザ等があり、変わったところでは検索窓に文字を入力したときに検索語の候補を表示するためのオートコンプリート用の検索インデックスがあります。

クックパッドではSolrのマルチコアという機能を利用して、一つのSolrサーバで複数のインデックスを運用しています。 複数の検索インデックスを使用することで、サービスの機能ごとに検索インデックスを使い分けることができるようになっています。

ところが、全ての検索インデックスの使われ方は均一ではありません。 均一ではないことから、インデックスごとに負荷の偏りが生じます。

検索を実行するときにかかる負荷は主に以下の三つの要素で決まります。

  • インデックス自体の大きさ (メモリの大きさが重要)
  • クエリ自体の複雑さ (高速なCPUが必要)
  • クエリが実行される回数 (多くの台数のサーバが必要)

クックパッドではこの三つの要素を踏まえ、スレーブをいくつかのグループに分けて、似たような特性のインデックスのみを持つスレーブ群を作成することで負荷の偏りに対応しています。 たとえば、レシピ検索のインデックスはデータサイズも大きく、実行されるクエリも複雑で、実行回数も多いため専用のグループにしています。 また、オートコンプリートのインデックスはデータサイズは小さく、実行されるクエリも簡単なのでユーザのインデックスと同居させたりしています。

検索クエリの特徴を捉える

クックパッドでは毎日多くの検索が行われています。「豚肉」や「カレー」といったよくある単語はもちろん、「老干媽」といった非常に珍しい単語で検索されることもあります。 もちろんどんな単語でも検索が行えるようになっていますが、よくある単語は非常に多くのユーザから何度もリクエストされます。 一方で珍しい単語は一日に数回検索されるかどうか、といった具合です。

検索回数の多い単語のバリエーションはある程度限られているため、検索結果のキャッシュを行うことでSolrの負荷を軽減しつつ高パフォーマンスを引き出すことが可能になります。

負荷の軽減と高パフォーマンスを出す仕組み

Solrには同じ検索クエリを受け付けたときの結果をキャッシュから返すクエリキャッシュの仕組みがありますが、クックパッドではSolrの前段に専用のキャッシュサーバを設置して、よりアグレッシブにクエリキャッシュを行っています。 クエリキャッシュを外部で行うことで、キャッシュ内容がサーバによって異なることで起きるフラグメント化(=非効率)を避けることが可能になります。 構成図でバランサと書いたサーバは、バランサであると同時にクエリキャッシュを行うサーバになっています。

さて、クエリキャッシュをSolrの外部で行うためにはどのような要件が必要でしょうか。 SolrはHTTPで通信を行うため、HTTPのリクエスト毎のレスポンスがキャッシュできれば、検索クエリ毎の検索結果をキャッシュするという要件を満たせそうです。

そこで、クックパッドではクエリキャッシュとバランシングを同時に行うサーバとしてVarnishを採用しました。 Varnishは高速なHTTPページキャッシュサーバとして知られていますし、同時にバランサの機能も内包しています。 このキャッシュサーバが検索クエリキャッシュとして機能することで、バックエンドとなるSolrへのリクエスト数の軽減と高速なレスポンスを実現しています。

また、前述のスレーブのグルーピングに関しても、検索リクエストのURLに含まれるインデックス名を元にして、バックエンドを切り替えています。

まとめ

クックパッドにおける検索の仕組みについて紹介しました。 検索で一番重要なことは、探しているものが見つかることですが、素早く見つけられることも重要です。 高速な検索システムを提供することで、ユーザが少しでも早く見つけられるように上記のような工夫をしています。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/