【開催レポ】Tech Kitchen #29 Mobile App One Experience

レシピ事業部プロダクト開発グループの @miichan_ocha です。昨年の 12/18 (水) に「Tech Kitchen #29 Mobile App One Experience 〜サービス統合について語っちゃいます モバイルアプリ編〜」を弊社オフィスにて開催しました。

イベントではクックパッドレシピサービスの iOS/Android アプリ開発に携わる3名のモバイルアプリエンジニアから、昨年行った「One Experience」プロジェクトに関するお話をさせていただきました。この記事では、イベント当日の様子を発表資料とともに紹介させていただきます。

なお、「One Experience」プロジェクト自体の概要については下記の記事で紹介しているので、興味のある方はこちらもご覧ください。

日本版とグローバル版のモバイルアプリ統合の開発の裏側と今後の展望 by 三井田

最初の発表は私、三井田から「One Experience」プロジェクトで行った日本版とグローバル版のクックパッドモバイルアプリの統合や、現在のプロダクト開発グループの開発体制、これからやっていきたいことなどについてお話ししました。

speakerdeck.com

グローバル版の iOS/Android アプリを開発していたリポジトリから、日本向けとグローバル向け両方のアプリを配信するにあたってどのように開発を進めていったのか、統合が完了した今、これからどのようなことをやっていきたいのかなどをお話させていただきました。

週次リリースを実現するためのグローバルアプリ開発 by 山田

2番目の発表は、同じくレシピ事業部プロダクト開発グループに所属する山田 (@0x746572616e79) より、週次リリースを実現するためのグローバルアプリ開発についてお話ししました。

speakerdeck.com

プロダクト開発グループでは、1週間単位のスプリントでのスクラム開発を採用し、スプリントごとに機能のリリースを行っています。また、機能開発だけでなく、グローバル統合に伴う翻訳作業も同時に進める必要があります。この発表では、Feature Toggle を使ったスプリント(リリース)を跨いだ開発の進め方や、翻訳作業の流れについて紹介させていただきました。

複数リリースに跨った機能改善については、iOSアプリにおける複数リリースに跨った機能改善の開発事例紹介の記事にも詳しい記載があるので、興味のある方はこちらも合わせてご覧ください。

Androidアプリの One Experience リリース by こやまカニ大好き

最後の発表はレシピ事業部で Android アプリ開発を行っている、こやまカニ大好き (@kanidaisuki3) より、Android アプリを(コードベースが)別のアプリで上書きする具体的な手法についてお話ししました。

speakerdeck.com

この発表では、アプリのコードベースが完全に別物になっても、それまでのユーザーと同じ状態でログインしたままアプリ内のデータも引き継いだ状態で動かせるようにするために、どのように認証情報やローカルデータのマイグレーション、One Experience 版アプリのリリース作業を行なったのかなどについてお話させていただきました。

パネルディスカッション

発表後は、発表者3名にレシピ事業部部長の大石も加わり、参加者のみなさまからの質問への回答を中心としたパネルディスカッションを行いました。

グローバルのメンバーとのコミュニケーションはどうしているのか、日本版とグローバル版のUI統合はどのように決めたのかなどの多くの質問をいただき、とても充実した時間となりました。当日質問をくださったみなさまありがとうございました。

当時の質問や回答の一部は X の実況でご覧になれます。

懇親会

その後はシェフに作っていただいた料理を食べながら、参加者のみなさまと懇親会を行いました。美味しい料理を食べながら参加者のみなさまとたくさんお話・情報交換できてとても楽しかったです。

https://x.com/mimimi__ko/status/1869339039847092447 より

まとめ

今回のイベントはモバイルアプリ関連の Tech Kitchen としてはかなり久々のオフラインイベントで、私自身も久々のオフライン登壇で緊張していたのですが、参加者アンケートでもたくさんのフィードバックをいただき、開催して良かったなと思いました。今後もこのようなオフラインイベントを定期的に開催していけたら良いなと考えております。改めてこのイベントにお越しくださったみなさま、ありがとうございました。

トレンドワード機能を新システムに移行するときに考慮したこと

こんにちは。レシピ事業部検索チームの薄羽 (@usulity) です。

続々と関連記事が投稿されていますが、日本とグローバルのクックパッドを統合しました。

この統合に際して、日本のクックパッドの様々な機能がグローバル版へ移植されました。今回は、移植された機能の一つである「人気のキーワード」について、移植した際にどんな課題があってどう解決したのかの一部をご紹介できればと思います。

人気のキーワード

人気のキーワードは、「クックパッドで最近よく検索されているキーワード」を集計して、ランキング形式で掲載する機能です。

日本版クックパッドの人気のキーワードページ
日本版人気のキーワード

このように、人気のキーワードは1時間おきに更新され、その時期・時間帯のトレンドを反映したようなキーワードのランキングになっています。

トップページの検索窓の下にも上位のキーワードが表示されており、人目につきやすい機能の一つです。

グローバル版人気のキーワード

グローバル版にも人気のキーワード機能があります。

基本的な機能は日本のものと同じで、そのときのよく検索されているキーワードを表現しています。

グローバル版人気のキーワードのスクリーンショット
グローバル版英語の人気のキーワード

今回取り組んだタスクは、このグローバル版の人気のキーワードとして、日本の人気のキーワードを日本版の計算方式で表示することがゴールになります。

課題

ということで、「グローバルにも人気のキーワードがあるから、日本語でも集計を実行するように修正すれば終わり!」としたいところなんですが、いくつか問題があります。

そもそも、グローバル版と日本版は別物のアプリケーションであるため違いは色々ありますが、「ログがない」ことと「更新頻度の違い」は移植する上で大きな問題でした。

ログがない

人気のキーワード機能を移植するにあたり、ユーザが移行してきたタイミングではもう人気のキーワードを表示しておきたいという要望がありました。

人気のキーワードは検索ログから計算するため、人気のキーワードを表示するにはグローバル版で検索ログが溜まっている必要があります。

しかし、日本のユーザをグローバル版の方に流すまでは、日本のユーザはグローバル版の方にはいないため検索ログはありません。

このままでは移行してきたタイミングで人気のキーワードを表示することができないので、なんとかグローバルの方にログがない状態でも人気のキーワードを計算できるようにしておきたいです。

また、キーワードのクオリティの面でも懸念があります。

この次でも説明しますが、人気のキーワードの計算は、過去の長い期間に渡ったログを使うことで、そのときの人気度を計算しています。

そのため、移行の初期段階ではログが十分に溜まっていないことで、人気のキーワードの質が不安定になってしまう恐れがあります。

更新頻度の違い

日本の方は毎時の更新なのに対して、グローバルの方は日次の更新になります。

計算頻度が日次か毎時かでは、単なる実行頻度の違い以上の差があり、人気度の計算方法が異なります。

人気度は長期的な目線と短期的な目線の両方で見た時に、目立って検索されている語を抽出しています。

簡略化しますが、例えばグローバル版の方では、人気度を「そのキーワードのその日の検索回数」/ 「過去に渡ったそのキーワードの1日の平均検索回数」で計算します。

こうすることで、あるキーワードが平均よりその日多く検索されていたら、そのキーワードの人気度は高くなります。

毎時の計算においても同じようなことをしますが、時間の区切り方が異なります。

「その時間帯」で目立って検索されたキーワードを調べたいので、毎時の計算では人気度を、「そのキーワードが1時間の間に検索された回数」-「そのキーワードがこの時間帯で検索される平均回数」として計算します。

このように、日次と毎時では人気度の計算方法が異なり、毎時では日次より細かい単位で平均を計算します。

単純にグローバル版の人気のキーワードの計算を毎時に動かしても再現できないことが分かったので、グローバル版の人気のキーワード計算の仕組みを変えるか、新しくバッチジョブを用意する必要があります。

解決

日本版とグローバル版両方のログから合算する

移行先にログがない問題を解決するために、移行先の人気のキーワードは日本とグローバル両方のログから計算します。

幸い、日本版でも人気のキーワードの計算に特別な情報は使っていなかったため、グローバル版のログで情報が足りないといったことはありませんでした。

これにより、グローバル版に移行初期のタイミングでも、良いクオリティの人気のキーワードが計算できるようになります。

また、ユーザの段階的な移行を考えた時にも、この方法は利点があります。

日本版とグローバル両方からログを取得しているため、仮にユーザの50%は日本、もう50%がグローバルにいるようなケースでも、両方のプラットフォームでのユーザ行動を加味した人気のキーワードを計算できます。

実際に、 One Experience ではユーザを日本版から少しずつグローバル版の方へ移行していきました。

それぞれからの検索ログを同じ価値と見なして良いかは議論の余地がありますが、それを検証するのは大変なので、1つの検索ログが1回の検索を示すことだけ確認して、移行途中で日本版と大きく結果が離れていなければよしとしました。

このように、データソースを両方のプラットフォームから取得して計算することで、移行段階に左右されずに人気のキーワードを提供することができます。

日本用のバッチジョブを用意する

更新頻度の違いによる人気度の計算方法の違いが大きいため、今回は日本用のバッチジョブを用意することにしました。

グローバル版の方の仕組みを日本版に寄せる方向性も考慮しましたが、更新頻度以外に以下の理由で別のバッチジョブとして作ることにしました。

  1. グローバル版の方の人気のキーワードのクオリティをチェックしながら開発するコストが高い
  2. 日本とそれ以外の言語ではログの量に差があるため、そのためのパラメータ調整にコストがかかる

グローバル版では、多数の言語の人気のキーワードを計算しています。グローバル版の人気のキーワードの仕組みを変えた時、抽出されたキーワードのクオリティへの影響をチェックしたいわけですが、言語が違うのでチェックするのが大変です。

また、日本用にチューニングした人気度の計算方法が、他の言語でうまくいくとは限りません。日本向けに設定したパラメータが他の言語でも適切かどうかわかりせんし、また各言語向けに設定をするのもコストがかかります。

日本とその他の言語で別々のバッチジョブと言っても、将来的にはやはりなるべく1つにまとめられるようにしておきたいので、なるべくグローバル版の仕組みには乗るように注意しました。

結果とまとめ

結果として、ユーザの移行が始まる前に実装が完了し、無事移行初期段階でも人気のキーワードを表示することができました。

グローバル版日本の人気のキーワード
グローバル版日本の人気のキーワード

やはり、ユーザがどっちのプラットフォームに居ても人気のキーワードを計算できることは、One Experience ではとても有効な方法だったと思います。

また、バッチジョブを分けることで他の言語への影響をあまり気にする必要がなかったことも、開発していく上では重要だったと今改めて思います。一方で、人気のキーワードの計算が日本語だけ分かれていることでメンテナンスコストが増えてしまっているので、これを他言語と統合していくということが今後の課題として残っています。

個人的な感想としては、正しく人気のキーワードを計算できているのか最後まで不安がありましたが、結果としては日本版と同様のキーワードをグローバル版でも表示することができて良かったと思っています。

以上、One Experience における人気のキーワード機能の移行の話でした。

面白いと思ってくださった方は、ぜひチャンネル登録と高評価、また他にも色々な角度の One Experience 話が投稿されておりますので、ぜひそちらもご覧ください。

One experience 検索移行の話

こんにちは、レシピ事業部検索チームのオリギル(@orgil_)です。 先日、この開発者ブログで紹介されたOne experienceプロジェクトによって、クックパッドはプロダクト基盤をグローバル版のシステムに移行しました。私はこのプロジェクトにおいて検索の領域で移行の進行、開発を担当していました。このブログでは検索システムの移行について紹介します。

グローバルのシステムに寄せた理由

One experience によってクックパッドは検索基盤をグローバル側のシステムに移行しました。プロジェクト発足当初、日本とグローバルのどちらのシステムに寄せるかをいろんな観点から検討しました。日本ではSolr、Ruby、ECSなどで動くVoyagerというシステム、グローバルではElasticsearch、Python、k8s、Kafkaなどを用いたglobal-search-v2(通称GS2)という検索システムが動いています。検討の際、どちらのシステムも一長一短で、特段優劣をつけがたいものでした。

検索アルゴリズムに関しては両システムで考え方は一緒でした。辞書ベースでクエリ拡張をし、ドキュメントのタイトルや材料などにマッチスコアを付け、辞書の属性によって細かい調整をするようなスコアリングアルゴリズムは両方にあります。

またドキュメントの検索への反映時間はどちらも同等と呼べるものでした。GS2はKafkaを用いたイベント処理システムを用いて、ほぼ即時反映を実現しています。Voyagerでも定期的な同期をするようになっており、レシピの変更が最短5分で検索結果に反映されるようになっています。下記のブログで触れているように、プロダクトが求めるユーザー体験を実現するためには5分でドキュメントが反映されれば十分であるため、どちらのシステムでも同様な価値を届けられます。

VoyagerがGS2より一番優れていた点は、速度でした。議論時点ではGS2のp50, p95 レスポンスタイムはVoyagerより約4倍ほど遅いものでした。これは大きな懸念要素で、速度は検索システムにとってとても大事な指標です。GS2を採用する際に、日本の大きなトラフィック量が増えることでGS2が更に遅くならないかなどの懸念点はありました。

いろいろと議論しましたが、最終的にはGS2の方に統合することになりました。一番の決め手は多言語対応がVoyagerでは難しかったからです。GS2はすでに約30言語をサポートしていてElasticsearchのAnalyzerを各言語で細かく設定、運用しており、辞書も言語、地域ごとに別れています。この運用をVoyagerでやるのが難しそうでした。

また辞書の扱い方が大きく違いました。日本の検索は長年運用されて、辞書は成熟しており、変更が頻繁に行われないため、辞書の更新は日次バッチで適用されます。しかし、GS2では辞書の変更も即時反映で、すぐに検索結果が変わります。これは辞書の管理を各地域のCM(コミュニティマネージャー)にお願いしていて、彼ら彼女らが辞書を変更しながら日々検索改善を行っているからです。まだ辞書を一から作っている新興マーケットが多かったため、辞書の変更による検索結果の変化を実際に見ながら辞書の作成をしているためです。 GS2採用にあたって速度面での懸念はありましたが、VoyagerがGS2より速い理由はわかっていて、いくつかGS2でも実現できる仕組みがあったため、なんとかできそうだということで進めました。

検索移行の進め方

検索移行のゴールとしては、日本の検索と完全に同じ結果を返すようにすることを目指しました。GS2の検索ロジックの考え方は日本と同じだと言いましたが、実際の計算式は違っていて、検索結果にいろんな差異があります。通常ならABテストなどで、GS2ベースのスコアリングに置き換えても大丈夫か、検証しながら切り替えることも考えられます。しかしOne experience プロジェクトでは検索のみならず、メインのレシピサービスやデータベース、基盤をまるごと移行しているため、一部をユーザーに出して検証することができませんでした。また日本の人気順検索はプレミアムサービスの大事な機能であるため、慎重になる必要があり、システム移行作業とアルゴリズムの大きな改変の検証を同時にやるリソースがありませんでした。GS2で日本専用の検索クエリビルダーを作ることは容易で、大きな技術的な負債にもならないので、まずは同じ結果を再現することに従事しました。

RBO指標

検索結果が同じになっているかを確かめるために、日本とグローバルの結果を比較する必要がありました。今回の移行で、ふたつの検索結果の一致度を測る指標として、Rank Biased Overlap(RBO)という指標を用いました。RBOは2つの順位付きリストの類似性を測定する指標で、特に順位の上位に重点を置きながら、リスト全体の比較を行える特長があります。

RBOを計算する際の上位100件の結果を減衰パラメータ=0.978に設定して測りました。このパラメータは上位60件の一致度でRBO指標の9割の重みが決まるように設定しました。これはレシピ検索においてほとんどのアクセスが最初の3ページの閲覧に収まっているためです。実装のデフォルトである減衰パラメータ=0.9では上位約10レシピだけでRBOスコアが決められてしまい、上位数件の結果が合ってるかどうかに重きが置かれる指標になってしまうからです。

この比較をTop20000キーワードに対して、新着順と人気順の両方で毎日自動で計算するようにしました。日々のRBOの変化、2万キーワードのRBOの分布で進捗を測り、チーム内で RBOが低かったキーワードを分析しながら、移植機能の優先度をつけたり、仕様の見落としを発見しながら進められました。

最初のRBO計測

RBO値とそれに対するキーワードのヒストグラムで移行の進捗が可視化することができました。上の図は一番最初のRBO分布のグラフです。このときはもちろん何もできていないのでほとんどのキーワードがRBO=0.0という結果でした。

初期の開発過程の変化
最初は一番基礎的な検索サポートをしていきました。Elasticsearchで正しいAnalyzerを設定したり、Voyagerのベースの計算式をGS2に実装したり、検索時のクエリを正しく形態素解析するようにしたり、基礎的な部分を作っていきました。この時点でRBO=0.0結果がほとんどなくなり、大部分が右側に寄っている形になりました。
同日の新着順と人気順のRBO分布
またこれは同日の新着順と人気順の分布です。人気順のみの機能などもあることから、両方ともモニターしていく必要がありました。プロジェクト通して人気順のほうがRBO成績は良い傾向にありました。これは新着のレシピドキュメントが日本とグローバルで同期がズレていたり、人気順は人気順スコアの比重が大きいため似やすいことに起因しています。

基礎的な機能の移植が一段落し、プロジェクトの中盤ではRBO値が低いクエリ(0.4以下)を並べて、漏れている機能の発見や、未実装機能の優先度付けを行いながら進めました。難しかったのはRBOが0.6-0.8あたりのキーワードの解析でした。RBOが高いキーワードはもうすでにある程度似ている結果で、差分を探すのが難しいものとなっていました。この辺になってくると単純にアルゴリズムの違いだけではなく、ドキュメント作成時の形態素解析の微妙な差異なども影響してくるため、地道に細かく調査しながら進めました。

RBO分布の推移を可視化してみました。移行が進むごとに分布が右側に寄っていく様子が伺えます。RBOは最終的に新着順で RBO>=0.8 の割合が 97.2%、人気順で RBO >=0.8: 95.1% という結果になりました。いくつかの機能は違う形でもってきたり、Voyagerから持ってきたくない仕様などもありましたが、最終的にはとても高い一致率になりました。ユーザーリリース後もKPIが大きく変化することなく移行できました。

速度

検索移行において日本のシステムと同等な検索結果を出すことと同時に、同等な速度を用意することに注力しました。前述したようにGS2はVoyagerの4倍ほど遅いレスポンスタイムでした。

VoyagerがGS2と比べ速い理由はいくつかわかっていました。一つはクエリ拡張の方式です。辞書を使ったクエリ展開は主に同義語展開と、語の親子関係による拡張があります。日本では synonym token filter を使用して同義語展開を行い、親子関係の単語はインデックス時に事前計算して入れています。そのため検索時は元キーワードのみの検索で同義語展開と親子マッチが可能になっています。その変わりに辞書の変更を反映させるには、日次バッチを待つしかないです。対して、GS2では辞書反映をリアルタイムで実現したいため、クエリ拡張をElasticsearchクエリビルド時に行っているため、ESクエリが肥大化し、検索が重い処理になります。

もう一つの速い理由はSolrを用いた検索基盤が最適化されていたことです。Voyagerでは様々な工夫を凝らしてインデックスサイズを極限まで減らしています。またSolr-hakoを用いたシステムがとても高コスパで高パフォーマンスでした。GS2のElasticsearch構成と比較してクラスタを組んでいないし、Solrノードが1インデックスしか持たないため、クエリキャッシュが効率的に使えます。

GS2移行において、実際の日本のトラフィックに近い負荷試験シナリオを用意してパフォーマンスを計測していきました。前述したSynonym token filterや親子関係の事前計算をGS2にも移行して持ってきたので、GS2の他の言語と比較してすでにある程度速くなってました。その上で、API側、Elasticsearchの構成、インデックス設定などいろいろ模索しながらパフォーマンス改善していきました。GS2にはPrometheus, Grafana を用いたAPM機能が備わっていたので、検索の一連の流れ、クエリ解析、Elasticsearchへのリクエストなどのどこがボトルネックになっているのか明確で、注力する領域を明確にできていました。

またユーザーから見えるWebやアプリの検索ページのパフォーマンスも測定しながら、別のチームの方々が、検索基盤より前にあるシステムの最適化にも積極的に取り組んでいました。

最終的にはGS2はp95ではVoyagerより高速なパフォーマンスになりました。p50のベースのレスポンスタイムは上がりましたが、p95レベルでは日本より安定したレスポンスタイムを実現できました。

  • Voyager -> GS2
  • p50: ~22ms -> ~33ms
  • p95: ~80ms -> ~50ms

さいごに

One experienceはUI、体験が大きく変わり、ユーザーに大きな負担を強いる挑戦でした。ユーザーの方には、検索結果とその応答性は同等なものを提供するように検索チームとして注力して来ました。結果、ほとんどのキーワードでは同等な検索結果を完全にGS2のシステムで用意することができ、レスポンスタイムも損なうことなく検索移行を実現することができました。

検索移行が終わったいま、真のGlobalな検索開発が始まりました。一つのチームで約30言語の検索改善に取り組んでいきます。また日本とGlobalの検索アルゴリズムをなるべく揃えていこうとしています。実際のユーザートラフィックがあるなか、改めてGS2に備わっているABテスト基盤を活用しながら、Global、日本両方のアルゴリズムのいいとこ取りをし、同じアルゴリズムに揃えていきたいと思っています。もちろん言語ごとの差異は残ると思っていて、完全に一緒にするとは思っていませんが、お互いから学べるところは多いはずです。

レシピ検索の詳細な話や検索以外の機能など、ここで書ききれない移行の話はたくさんあるのですが、またいずれどこかの機会に話せたらと思います。また、One experienceプロジェクトについて弊社のTechlifeブログでこれまでに、これからもいろんな記事が投稿されていきます。Techlifeのツイッターで随時更新しています、ぜひチェックしてください。