アジェンダと自己紹介

児玉悠氏(以下、児玉):みなさんお疲れさまです。自分は「ZOZOTOWNを支える検索パーソナライズ基盤」というタイトルで発表いたします。

本日のアジェンダは、まずZOZOTOWNの検索パーソナライズのプロジェクトの概要について説明します。そのあとにプロジェクトの組織体制について話して、アーキテクチャの話に入っていきます。そのアーキテクチャの設計の話で、リリース前の検証段階でいくつか変更ポイントがあったので、それについても説明します。最後に、今使っているElastic Cloud、Elasticsearchのマネージドサービスの話について話したいと思います。

改めまして自己紹介しますと、ZOZOテクノロジーズのMLOpsチームに所属しています、児玉と申します。Twitterは@dama_yuでやっているので気軽にフォローしてください。自分は2019年の11月入社で、つい最近、累計のリモート出社日数が累計のオフィス出社日数を上回ってしまうという、謎の現象が起きています。

今は検索パーソナライズとかレコメンドのSREをやっていて、前職はフリマアプリの会社で機械学習エンジニアをしていました。前職までは機械学習のモデルから基盤まで、けっこういろいろ広く浅くやっていた感じなんですけど、最近はその基盤部分とかSREを深掘りしてやっています。

検索パーソナライズのプロジェクト体制

ZOZOTOWNの中で検索というのはすごいコアな機能なので、今回の検索パーソナライズのプロジェクトというのは事業インパクトも大きく、重要なプロジェクトになっています。

今回はZOZOTOWNの検索パーソナライズのプロジェクトについてお話しします。ZOZOTOWNの検索結果を個人ごとにパーソナライズするプロジェクトです。画面上でここに出ている「おすすめ順」。もともと人気順がデフォルトであり、今回はパーソナライズしたおすすめ順というのを新規に追加しています。

これはユーザーの行動履歴とか属性を元にパーソナライズされたおすすめ順というのを新しく追加していますが、今回はそのインフラ寄りの話がメインになりますので、ロジックの話はほとんどしないと思います。ロジックに関してはあまり公開できない話も多く、たぶん質問いただいてもなかなか深掘りできないんじゃないかなと思います。これは今Webでリリース済みで、iOSやAndroidについてはまだです(2020/06/22当時。現在はiOS/Android もリリースされています)。

プロジェクト体制は幕張と青山の2拠点に跨っており、幕張オフィスからは主に3チーム関わっています。推薦基盤チームは検索パーソナライズ基盤のバックエンド全般やプロジェクトマネジメントを担当してくれています。分析本部は株式会社ZOZOテクノロジーズ所属ではなく株式会社ZOZO所属の部署なのですが、パーソナライズロジックの作成とかA/Bテストとか効果測定、分析まわりをやってくれています。

リプレイスチームは、このリプレイスというのはZOZOTOWNのもともとレガシーなアーキテクチャをモダン化してリプレイスするという意味でリプレイスチームという名前がついているんですけど、ZOZOTOWNの本体へのバックエンドのつなぎ込みというのをやってくれています。最後、青山オフィスからMLOpsチーム、これは自分も所属しているチームで、インフラの全般を担当しています。

アーキテクチャの全体と詳細

最初にざっくりとしたアーキテクチャ全体の図です。(スライド参照)このFront APIと書いてあるのはZOZOTOWN本体側の話で受け口になっていて、そこが今回新規に構築した検索パーソナライズの基盤を叩くかたちになっています。データの流れとしてはオンプレのDBからBigQueryにデータを持ってきて、そこからユーザー情報管理をしている部分と商品情報管理をしている部分にそれぞれユーザー情報と商品情報を流しています。

ユーザー情報管理をしている部分についてはBigQueryからユーザーステータスを生成してパーソナライズAPIに渡しています。ここの図には書いていませんが、ルール情報のパーソナライズAPIが持っていて返す感じです。

商品情報を管理しているところは、BigQueryから商品のメタデータ生成をして、インデクシングのバッチがElasticsearchにインデックスを作成して突っ込んでいます。そのパーソナライズAPIから返ってきたユーザー情報とロジックを元にElasticsearchを叩いて、そのあと必要な処理を行い、最終的にアイテムの順序を返しているという感じです。

アーキテクチャの詳細に入っていきます。これはさっきの図の上部分の話ですね。さっきも言ったようにZOZOTOWN本体のFront API側からパーソナライズのAPIを叩いています。

Front APIはAWS上に乗せていて、パーソナライズAPIはGCPのKubernetes Engine上に構築しています。ここにDedicated Interconnectと書いてありますが、AWSからGCPにつなぐところで専用線を使っていて、これはレイテンシーを小さくするために専用線を使っていて、そこでGCPのDedicated Interconnectを使っています。

パーソナライズAPIはユーザー情報を取ってくる部分とルール情報を取ってくる部分に分かれていて、ユーザー情報はBigQueryから毎日定期実行でDataflowを使ってBigtableにユーザー情報を流している感じです。キャッシュはCloud Memorystoreを使っています。ルール情報はCloud SQLで管理している感じです。

次に商品情報部分になります。ZOZOTOWNの商品情報を保存しているSQL Serverのレプリケーションから、Qlik Replicate を用いて、Kafka(マネージドサービスとしてConfluentを使用)-> Dataflow経由で、BigQueryに商品情報の更新差分を保存しています。

それとまた別の経路で分析部が管理しているBigQueryから商品特徴量のマスターデータを持ってきたものと、ここでジョインしています。ジョインするのにGoogle App EngineのCronジョブのバッチを叩いていて、App Engineの状態管理はCloud SQLで行っていて、最終的にこのバッチでElastic Cloudにインデックスを作成しています。

アーキテクチャ設計の検証

リリース前の設計段階でいろいろ検証して、アーキテクチャが変更になった部分があります。3つあってそれをそれぞれ説明していきます。

最初にAPIのインフラの話で、Cloud RunからGKEへの変更を行いました。Cloud RunはGCPのフルマネージドなサーバーレスコンテナプラットフォームです。今回のシステム要件として、プライベートIPでサービスを提供できることというのがあり、App EngineについてはパブリックIPになってしまうので採用しませんでした。

Cloud Runを検証した結果、Nリクエストに1回、リクエストに20秒かかるという現象が発生してしまいました。というのもCloud Runというのは、リクエストが来たらPodが起動してしばらくしたら停止してしまうんですね。ただ、このAPIはJavaで書かれているんですけど、Javaのコンテナの立ち上がりが遅くて20秒ほどかかってしまいました。

これによって今回のプロジェクトで採用するのを断念してGKEを採用しました。

ユーザー情報の更新バッチはDataflowなんですけど、これをCloud SchedulerとCloud Functionsで定期実行する予定でした。ただ、プロジェクトのCI/CDで用いていたGitHub Actionsの、YAMLの中ではon scheduleと書くのですが、それに変更しました。これによってGitHub ActionsのシンプルなYAMLファイルの定義のみで定期実行の設定ができるようになりました。

次がElasticsearchのインデクシングのバッチ。さっきのはApp Engineでやっているという話だったのですが、もともとこれはCloud Dataflowでやろうとしていました。ただ、Apache Beamが最新のElasticsearchのバージョンに対応していないということがわかって、結局App Engineで自前で書いたバッチをCronで動かしています。

Elasticsearch のいいところ・ツラいところ

次にElasticsearchの話をしようと思います。自分たちはElastic Cloudを使っていまして、Elastic Cloudのいいポイントとここはちょっとイマイチかなというポイントについて話して、そのあと監視とか運用についても話したいと思います。

Elastic Cloudとは、Elastic社が公式で提供しているElasticsearchのマネージドサービスです。

Elastic Cloudのいいポイントですけど、何よりもマネージドサービスなので運用が楽です。この前運用移管をうちのチームから別のチームにしたんですけど、そのときもアカウントの受け渡しだけで基本的にはOKだったので、それはすごく楽でした。他にいいポイントとしては選択可能なElastic stackのバージョンがAWSのElasticsearch Serviceより新しいというところですね。今はここに書いてある感じです。

あとはElasticsearchを作っているElastic社の公式のテクニカルサポートが受けられるというメリットもあります。英語で公式のサポートに投げたらわりとその詳細に丁寧に回答してもらえるという印象です。

次にElastic Cloudのツラみポイントですね。まず、細かくインスタンスタイプを選べないということがあるかなと思います。これでスペックの調整をしようとしたときにもうちょっとノードのスペックを上げたいみたいなことがあったときに、細かく調整できないみたいな場面がありましたね。

これで一番苦労しているんじゃないかと思うんですけど、ノード数の増減をしようとしたときにけっこう失敗することがあって、例えば2台から4台に増やすときに失敗して、2台、3台、4台と1台ずつ増やしていったら成功するみたいな謎の癖があって、ここら辺はもうちょっと安定してほしいかなという気持ちです。

次は同じノードに複数のコンテナが乗って、I/Oを持っていかれるというのがあると思います。次はElastic Cloudの操作APIというのが、このプロジェクトをリリースしたときというのはなくてIaC化できなかったという問題がありました。ただ、最近Elastic Cloud Control、ecctlコマンドというのが公開されたので、これを使用していく予定です。

最後に問い合わせのコンソール画面が本体と別ログインになっていて、アカウントの人数に制限があって、これはちょっとUX的によくないなと思っています。

Elasticsearchの監視と運用

次が監視の話で、今はElastic Cloudの監視にDatadogを使用しています。メトリクスはDatadog Agentから送っているものと自分たちで構築したカスタムのバッチで送っているものがあります。Datadog Dashboardは各メトリクスを一覧で見るのに使用しています。主に見ているのはquery/secとかCPU使用率です。

次がDatadog monitorですね。これは閾値のアラートに使用しています。レスポンスタイムが遅かったりquery/secが多すぎたりみたいな監視に使っています。

次がElasticsearchの運用の話ですね。運用上の作業でもっとも多いのはElasticsearchのノード数変更です。これはセールとかのキャンペーンごとに過去の負荷傾向から毎回ノード数を見積もって、基本的にキャンペーン前日までにElasticsearchの運用担当がノード数の変更を行うようにしています。

ここで注意なのがElastic CloudのElasticsearch本体だけでなくて、関連するインデクシングバッチのConfigファイルとかも変更する必要があります。

最近、検索まわりのインフラを一貫して見れるように、うちのチームからリプレイスチームに運用を移管しました。これは「MLOpsチームでElasticsearchの運用まで全部見るのはどうなんだ」みたいな問題があって、検索APIとかのインフラを基本的に見ているリプレイスチームに運用を移管したという経緯があります。

内容としては以上です。検索基盤とか推薦基盤、MLOpsチーム、各部署で絶賛エンジニア採用中なので、今回の発表を見て興味を持ってくれた方は調べていただけるといいかなと思います。

ノード数を増やしたときのバッチの設定変更

では質問を見ていこうかなと思います。なかなかTwitterで質問を受け付けるというのに慣れていないんですが、見ていきますね。質問がある方はどんどん質問を投げてくれればいいかなと思います。

「バッチの設定変更をしているのはなんでだろう?」。これはどこの部分の話ですかね?

なるほど。Ohtaniさん(@johtani)がTwitter上で質問してくれているやつで、ノード数を増やしたときのバッチの設定変更の話ですね。スライド的にはここですね。

これは今回の図に出てきたインデクシングのバッチ以外にもいくつかインデックスを作成するバッチがあるんですけど、ノード数とかレプリカ数をElasticsearchからも現在のレプリカ数を取ってきてインデックスを作成するとはなっていなくて、今はConfigファイルの中でレプリカ数とかを設定するようになっているので、そこも変える必要がある感じですね。

ここはElasticsearch側からレプリカ数とかを取得して、というかたちにしたいんですけど、今は現状できていない感じです。

時間も少なくなってきたので、ちょっとこの辺りにしたいかなと思います。自分からは以上になります。

ありがとうございました。