「このレシピは何人分?」を機械学習で推定する

研究開発部の原島です。在宅勤務中は部のメンバーと 3 時にラジオ体操をしています。今日はラジオ体操の話はおいといてレシピの分量の話をします。

1 人分、2 個分、三枚分、約 4 皿、5 杯くらい、18 cm タルト型、...

クックパッドの一部のレシピは 1 人分のカロリーが計算されています。計算されたカロリーは検索結果の絞り込みや献立の作成などに使用されています。

ここでポイントとなるのは「1 人分」というところです。

レシピには、下図のように、その分量が記入されています。クックパッドの全レシピのうち、大体 50% のレシピの分量は「N 人分」という表記です。これらのレシピは、レシピ全体のカロリーを N で割ることで、1 人分のカロリーが計算できます。

f:id:jharashima:20200225174134p:plain
レシピの分量

一方、残りの 50% のレシピの分量は「N 人分」という表記ではありません。その半分(全体の 25%)はそもそも表記がありません。これは単に分量の記入が任意のためです。では、残りの 25% はどうなっているのでしょうか?

答えは様々です。「M 個分」や「M 枚分」、「M 皿分」のように「人」ではない助数詞による表記もあれば、「M cm タルト型」や「直径 M cm シフォン型」、「M cm 丸形 L 個」のように何らかの型の大きさとその個数による表記もあります。

こういった分量を「N 人分」に換算するにはどうすれば良いのでしょうか。同じ「M 個分」でもマフィンのレシピとケーキのレシピで N は違うでしょう。また、M は全角の時も半角の時も漢数字の時もあります。接頭辞や接尾辞も付いたり付かなかったりします。さて、どうすれば良いのでしょうか。

人手で頑張る

やっぱりまずはこれでしょう。人間の能力はすごいです。どんな表記でもちゃんとパースして、常識的な N に換算することができます。悩んだ時もレシピのタイトルや完成写真、各材料の分量などを参考にして、違和感のない N を選択することができます。

実際、1 日に十数レシピ(1 年で数千レシピ)の分量が人手で「N 人分」に換算されています。これは、配信記事で取り上げるレシピのカロリーを計算する過程などで換算されるものです。換算の際は、まず、一人のアノテーターが仮の N を決定します。そして、別のアノテーターがチェックして、最終的な N を決定します。

なお、チェック時のアノテーター間の一致率は大体 67% です。また、残りの 33% もそれほど大きな違いがないケース(e.g., 一人が N = 3、もう一人が N = 4)が多いです。これは、そもそも N の候補が少ないことが幸いしています。クックパッドのレシピは家庭用のものが多いので、大体 75% のレシピで N は 1 〜 4 です。

このように、できるのであれば、人手で換算するのが一番です。ただ、表記が「N 人分」でない(かつ、表記はある)レシピは数十万品あります。これらの分量を全て人手で換算するのはさすがに大変です。1 年で数千レシピを換算する今のペースでは大体 100 年くらいかかりそうです(その間に新しいレシピが投稿されるので、実際はもっとかかりそうです)。

機械学習を試す

そこで、機械学習の出番です。最近はライブラリやマネージドサービスが充実し、マイクロサービス化も促進されたので、機械学習をサービスで利用するハードルがぐっと下がりましたね。もちろんまだハードルはありますが、4 〜 5 年前と比較するとだいぶ楽になりました。機械学習は誰でも利用できる手段になりつつあります。

さて、今回開発したモデルは二つあります。一つ目は下図の single-source model です。このモデルはレシピの分量(もしくは、レシピのタイトル)を入力として N を出力します。より具体的な挙動は以下の通りです。

  1. 分量(もしくは、タイトル)をサブワードに分割
  2. 分割されたサブワードをエンコーダーに入力
  3. エンコーダーの結果を全結合層に入力
  4. softmax でいずれかの N(後述する実験では 1 〜 20)に分類

f:id:jharashima:20200225175932p:plain
single-source model

二つ目は下図の multi-source model です。このモデルは分量とタイトルの両方を入力として N を出力します。より具体的な挙動は以下の通りです。入力が複数になったことと、エンコーダーの出力を concat すること以外は single-source model と同じです。

  1. 分量とタイトルをそれぞれサブワードに分割
  2. 分割されたサブワードを各エンコーダーに入力
  3. エンコーダーの出力を concat して全結合層に入力
  4. softmax でいずれかの N に分類

f:id:jharashima:20200225174010p:plain
multi-source model

かなりシンプルなモデルではないでしょうか?単に、分量かタイトル(もしくは、分量とタイトル)の情報から N を推定するというだけです。表記のパースは最初から諦めて、サブワードに分割してニューラルネットワークに突っ込んでいます。

補足することがあれば、回帰問題でなく、分類問題としていることくらいでしょうか。これは、タルトやケーキなどのレシピにおける N が 8 や 6、4 のことが多かったからです。最初は回帰問題としていたのですが、N を連続値とするより離散値とするメリットの方が大きそうでした。

正解率は?

さて、このシンプルなモデルがどこまで通用するのでしょうか。同僚の @himkt にも手伝ってもらって、実験してみました。

実験には、分量表記が「N 人分」でない(かつ、表記がある)5,279 品のレシピを使用しました。これらを人手で「N 人分」に換算し、その 80% を訓練データに、10% を開発データに、10% をテストデータに使用しました。

モデルの設定は以下の通りです。

  • 埋め込み層: 20 次元
  • エンコーダー: 20 次元の LSTM(2 層)
  • 全結合層
    • single-source model: 20 次元(2 層)
    • multi-source model: 40 次元と 20 次元(それぞれ 1 層)

次元数などはいずれも開発データで調整しました。また、サブワードの分割には sentencepiece を、その学習にはクックパッドの 310 万品のレシピを使用しました。

タイトル  分量  正解率
RE 47%
ML (single) 28%
ML (single) 63%
ML (multi) 62%

結果は上表の通りです。RE(regular expression)はベースラインで、正規表現にもとづいて N を決定しました。具体的には、「M 個分」や「M 枚分」から M を抽出し、そのまま N としました。一方、ML(machine learning)は提案手法で、N は 1 〜 20 としました。また、正解率は初期値が異なる 5 回の平均値です。

表を見ると、分量の情報を使用したモデルの正解率は 62 〜 63% でした。アノテーター間の初見の一致率が大体 67% なので、なかなか良い正解率といえるのではないでしょうか。一方、タイトルの情報だけを使用したモデルの正解率は 28% でした。さすがに分量の情報を使用せずに推定するのは無理がありそうです。

意外だったのは、分量の情報のみを使用した single-source model の正解率が multi-source model の正解率より高かったことです。本質的には、タイトルの情報を使用せずに N を推定するのは不可能です。上でも言及したように、同じ「M 個分」でもタイトルが「マフィン」のレシピと「ケーキ」のレシピで N は違うでしょう。

single-source model が multi-source model に勝利(その差は 1% ですが)した理由はおそらく二つあります。一つ目は分量の情報のみでも、ある程度は、人数分が推定できたことです。ベースラインの正解率が 47% だったことから、47% のデータは「M 個分」などの M がそのまま正解の N だったことが分かります。こういった場合はタイトルの情報が必要なかったのかもしれません。

もう一つは、単に、multi-source model がタイトルの情報をうまく利用できなかった可能性があるということです。ベースラインで対応できなかった 53% のデータではタイトルの情報が有用に思われます。しかし、今回の実験では、二つのエンコーダーを学習させるには訓練データが少なかったのかもしれません。

以上を踏まえて、今は single-source model を試用しつつ、multi-source model を改善しているところです。訓練データを追加していけばどこかで multi-source model が勝利するのではないかなと予想しています。

タイトル 分量 正解 single multi
素朴なレーズンパン 1 斤 8 8 8
ツナポテトのミニコロッケ☆お弁当にも 8 個分 4 8 4
甘さ控えめのクッキー 鉄板 1 枚分 16 10 10

最後に成功例と失敗例を紹介します。一つ目の例は single-source model と multi-source model の両方が正解しました。「1 斤」を「8 人分」と換算したのはなかなか面白いです。二つ目の例は multi-source model だけが正解しました。「ミニコロッケ」は「2 個分」で「1 人分」と学習してくれたのでしょうか。三つ目の例は両方とも不正解でした。この場合、材料欄の情報(e.g., 各材料の分量)も利用しなければ正解するのは難しそうです。この辺りも今後の課題です。

おわりに

まとめると、クックパッドのレシピの約 25% は分量の表記が「N 人分」ではない(かつ、表記はある)ものの、その 60%(全体の 15%)は機械学習で N を推定できます。残りの 40%(全体の 10%)は訓練データを追加するなり、材料欄の情報を利用することで、N を推定できる可能性があります。

モデル自体を改善するのも一つの手です。Transformer や BERT(流行ってますね)を利用するのもありかもしれません。ただ、運用面を考慮するとモデルは軽量なものが良いので、この辺りは悩ましいところでもあります。やっぱり訓練データを追加するのが一番シンプルで、現実的な気もします。

ところで、最近、複数の学生から「新卒採用ってもう始まっていますか」という質問をいただきました。始まっております。機械学習や自然言語処理、画像認識で毎日の料理を楽しみにすることに興味がある方は、是非、以下のページからご応募ください。お待ちしております。

info.cookpad.com

クックパッドの在宅勤務環境

コーポレートエンジニアリング担当 VP の @kani_b です。 昨今急速に拡大している新型コロナウイルス感染症の感染拡大リスクを鑑みて、従業員や関係者の皆さまの安全確保を目的に、クックパッドでは 2/18 (火) からまずは2週間ほど、国内拠点の全従業員(正社員、契約社員、パート・アルバイト、派遣社員、通常在席の業務委託)を対象に在宅勤務の原則化を実施することになりました。

クックパッド、新型コロナウイルスの拡大防止対策で、全従業員を対象に在宅勤務(Work from Home)を実施 | クックパッド株式会社

この記事では、現在クックパッドでどのような環境づくりのもと、在宅勤務が行われているかをご紹介します。 どの会社の方も同じような状況にあるかと思いますが、「他社ではどうやっているか」の一例として参考にしていただけると嬉しいです。

仕事に利用するシステム

クックパッドでは、業務にインターネットからアクセスできる各種クラウドサービスを多く利用しています。社内ツールもそのほとんどがインターネットからアクセスできるようになっています。 特に日常的に使われているのは以下のようなサービスです。

  • オフィススイート: G Suite
  • コミュニケーション: Slack, Zoom
  • コラボレーション: GitHub.com, GitHub Enterprise
  • Wiki, ドキュメンテーション: Groupad (内製ツール)
  • ワークフロー: ServiceNow
  • 人事・会計: Workday

日常業務の多くのシーンでこれらのシステムを活用しています。また、ここに挙げたツールは、雇用形態や勤務時間・国によらず原則ほぼすべての従業員が利用可能になっています。また業務の多くをこれらを活用する形で設計しているため、例えば特定部署でほとんど利用されていない、といった事象が起きにくくなっています。

環境の調査

在宅勤務の原則化を行うにあたり、オフィスとなる全従業員の自宅について、どういった環境があるかがわからなければ方針を決めることも難しいです。そのため全従業員を対象としたアンケートを Google Forms を使って行いました。具体的には以下のような項目をヒアリングしました。

  • 自宅に安定して接続できるインターネット環境はあるか
  • 遠隔会議のために利用しているマイクはあるか
  • その他、会社からの支援が必要な項目

在宅勤務開始にあたり、「あれもこれもサポートしなければ!」と先に考えてしまうと、実は需要のなかったことに時間を使ってしまうことになりかねません。準備を進めつつ、従業員の状況を把握しておくことが、より効果的な対応のためには重要です。

PC の持ち出し

クックパッドではもともと、個人に割り当てられた PC の持ち出しをほとんどの場合において許可しています 。持ち出しにあたっては、パスワードの設定などはもちろんのこと、内蔵記憶領域の暗号化など必要な設定が必ず行われるようになっています。 MDM (Mobile Device Management) として Microsoft Intune や Jamf Pro を利用し、管理を行っています。

PC 上のイベントは EDR (Endpoint Detection and Response) 製品を利用して監視やイベント対応を行っています。現在は主に CrowdStrike Falcon を利用しています。 また、特に機微な情報に触れるようなケースでは、VDI (仮想デスクトップ環境) として Amazon WorkSpaces を利用し、クライアント PC から接続して利用するようにしています。

備品の持ち出し

日頃から希望者にはマウスやキーボード、プライバシーフィルターなどの備品を (可能な限り希望に沿うものを) 貸し出しており、それらの社外持ち出しに特に制限はありませんでした。 備品の中でも特に大きいものとして、PC 用ディスプレイがあります。個人差はありますが、ディスプレイの有無によって作業効率が大きく変わることがあります。 今回は、「オフィスが主、自宅が副」の状態から、「自宅が主」に変わるということもあり、ディスプレイの持ち出しについても許可することにしました。

在宅勤務の原則化が決定されたのち、全従業員対象のアンケートで「ディスプレイの持ち出しを希望するか」を集計し、数を見積もりました。その後、破損防止のための梱包材を急遽用意し、各自で自宅にディスプレイを送付できるようにしました。

インターネット環境

スマートフォンでできることが増え、通信速度も年々向上している昨今、自宅にいわゆる固定のインターネット環境を持たない方もいます。最初に行った環境調査により人数の見積もりができたため、そういった方には会社からモバイルルーターの貸し出しを行いました。モバイルルーターの在庫も大量には持ち合わせていないため、テザリングの利用を前提に、会社で貸与できる携帯電話も活用しています。

現在のところ、モバイルルーターを利用している従業員の多くは問題なく業務できているようですが、高解像度の画像や動画を扱う業務が多いと厳しいという声も聞こえており、契約データ量については注意が必要です。クックパッドでも対応を検討しています。

VPN の利用

クックパッドでは以前より、社内ネットワークなどいわゆる境界を用いたセキュリティ対策の排除を進めていました。*1

ほぼすべての社内システムは Azure AD, もしくは G Suite アカウントを用いた SSO 環境下にあり、 HTTPS を必須化するなどインターネットから直接アクセスされることを前提に社内システムを構築しています。またほぼすべての認証 (エンジニアが利用する SSH なども含む) では 2FA が必須化されています。

現状では、利用するソフトウェアの制約により Google Drive などに移行されていないファイルサーバ、開発時に利用するデータベースなど、主に Web ではないトラフィックについて VPN を必要とする箇所が若干残っています。今回の在宅勤務の原則化に伴い、既存の VPN 環境のキャパシティ不足が懸念されたため、一次対応としてエンジニアには sshuttle を利用した VPN への切り替えをアナウンスしました。上記のような取り組みにより、キャパシティ不足に陥ることなく問題なく稼働しています。

遠隔会議

クックパッドでは遠隔会議のためのツールとして Zoomのライセンスを全従業員に発行し、利用しています。日常的に Zoom を使ったミーティングを行う従業員は多いため、利用そのものに大きな問題はありませんでした。 しかし、遠隔会議ではマイクを含めた音声品質が非常に重要です。オフィスではほぼすべての会議室に Zoom Rooms と専用ハードウェアを導入しているため、非常に快適な音声環境が得られますが、自宅ではそううまくいきません。

PC 内蔵のマイクでは、キータイプ音が混じったり音をうまく拾わなかったりなど、長時間の会議には不向きなことがわかっていました。特に、イヤホン・ヘッドホンなど声を聴くためのデバイスは分離しておいたほうが良いようです。 そのため、外付けマイクやヘッドホンをお持ちでない方には以下のような対応を推奨しています。

  • iPhone やスマートフォンに付属する純正のイヤホンマイクの利用
    • ミニピンジャックのものは Windows や MacBook でも動作する
  • スマートフォンやタブレットからの参加
    • PC は別途参加させ、画面共有などに利用する

Zoom ではバーチャル背景機能がよく使われています。これは、部屋の中などを映したくない際に背景を任意の画像に合成してくれる機能です。最近は動画を使えるようになっており、ミーティング開始時のウケを狙いたい人などによく使われています。

また、Zoom による会議がデフォルトになったことによって、会議のためのツールもスライドではなく Google Docs によるドキュメント共有が使われるなど、変化が起きています。これにより非同期に質問を書き込めるようになったり、Google Slides にふせんのようなテンプレートを用意したりするなど、会議の実施方法が進化しておりそれが便利、という声もありました。

社外の方への参加依頼

これまでオフィスで行っていたお客様との打ち合わせや、採用面接についても、Zoom を使った形への切り替えをお願いしています。 今回の目的を達成するためには、従業員だけでなく社外のお客様にご協力いただくことが必要不可欠です。特に遠隔会議においては、環境のセットアップなどでご負担をお願いすることも多いため、お客様向けの簡単なご案内を作成しお送りする試みをはじめています。

ログ収集と運用

在宅勤務を可能にする上で、ほとんどの会社がまず気にかけるのはシステムのセキュリティでしょう。クックパッドでは、これまで紹介したセキュリティに関する仕組みの多くについて、そのログ収集と監視をセキュリティ設計の上での重要事項としています。

現在、ログはすべてセキュリティログ検索基盤に集約され、疑わしいイベントなどが発生した場合に調査できるようになっています。詳しい実装については以下の記事をご覧ください。 Amazon Athena を使ったセキュリティログ検索基盤の構築 - クックパッド開発者ブログ

で、実際どう?

現在の体制となって4日が経過しました。現在のところ、ほぼ全員が在宅勤務をしています。 コーポレートエンジニアリング部では、システム利用で困った際のヘルプデスク(サービスデスク)も担当していますが、この業務のほとんどもリモート環境で行われています。

今回の措置が発表された直後から、社内では #remote-work_ideas というチャンネルが Slack に作られ、椅子やマイクなどの情報交換のほかに、様々な試みが社内で行われています。私が見かけたものをいくつか紹介します。

入退室自由な雑談部屋

一人で黙々と作業をしていると、人に会うこともなく寂しさや不安を感じる方も多いようです。そうした人向けに Zoom ミーティングを作成し、誰でも入れるようにしています。オフィスでもなんとなく起きる程度の雑談を Zoom 内に再現する試みです。

現在では色々な単位で部屋が存在し、部署内で実施されているものから部署関係なく「なんとなく」で人が集まっているものまで様々です。実際の様子はこんな感じ。

f:id:kani_b:20200221123137p:plain

会社の受付にいそうな CTO をはじめ、南国やアメリカにいそうな人たちはバーチャル背景を利用しているだけで、自宅から接続しています。 参加者からは、「自宅だと全く人の目がないのでだらけがちだが、Zoom に接続しておくことで一定の緊張感が保てる点が好き」といった声もあがっています。

リモート昼ごはん

クックパッドのオフィスには大きなキッチンがあり、各自で料理をすることができるようになっています。が、お昼時はどうしても混み合いますし、打ち合わせなどで移動もあり、作れないことも…

原則在宅勤務となったことで、それぞれの自宅キッチンで料理する機会が図らずも生まれています。この機会を活かして、自宅でクックパッドを使って料理をする社員が増えています。Slack に #リモート昼ごはん というチャンネルが生まれ、今は Instagram や Twitter に投稿する社員もいるようです。 #クックパッド社員のお昼ごはん などで覗いてみてください。

f:id:kani_b:20200221123232p:plain

ラジオ体操

在宅勤務では、打ち合わせなどで移動する必要のない反面、意識して体を動かさないといつのまにかつらい状態になっているという声がよく上がっています。 リングフィットアドベンチャーをやっている様子を配信している人や、チームで時間をとって Zoom で中継しながらラジオ体操をやる部署などが出てきているようです。


まだ4日ではありますが、各自・各チームが様々なアイデアをもって仕事を進めています。在宅勤務の実施にあたっては、生産性など様々な議論が行われていますが、我々も継続的な効果測定や改善を行っていきたいと考えています。 すでに実施している方、これから実施される方、検討中の方などにお役に立てば幸いです。

質問などありましたら @kani_b までお気軽に。

*1:昨今では、いわゆるゼロトラストネットワーク、BeyondCorp という形で浸透している概念に近いものです

広告配信サーバーにおける DynamoDB Accelerator (DAX) 活用事例の紹介

メディアプロダクト開発部マーケティングサービス開発グループの我妻謙樹です。クックパッドにおける広告開発システム全般の新規開発・保守・運用を担当しています。

マーケティング事業全般やチーム体制については、前回の記事でご紹介しました。こちらを読んで頂ければ、メディアプロダクト事業部をめぐる組織体制や、マーケティングサービス開発グループの技術スタックについて概要を掴んでいただけると思います。

今回は、その記事でも触れた広告配信サーバーの技術的な取り組みについてご紹介します。その中でも特に、Amazon DynamoDB Accelerator (DAX) の活用に焦点を絞ってお伝えします。

背景

従来、広告をアプリ側で表示させるためには、マーケティングサービス開発グループがオーナーとして開発している広告 SDK を、クックパッド本体アプリに組み込み、非同期に広告配信サーバーにリクエストを行うことで実現していました。

今回、iOS アプリにて大きな仕様変更が行われることになりました。その新しいバージョンでは、"モダンBFFを活用した既存APIサーバーの再構築" で紹介されている、Orcha と呼ばれる BFF Server を通じて、必要なレスポンスをクライアントに返すことが決まっていました。そこで、広告を表示させるにあたって、従来のように非同期で直接広告配信サーバーにリクエストするのではなく、BFF Server としての Orcha の立ち位置を利用し、Orcha から広告配信サーバーを問い合わせるようにできないか、という議論が要件定義フェーズで生じてきました。

f:id:itiskj:20200220122635j:plain
アーキテクチャ概要

広告配信システム概観についてまとめた前回の記事 の時点では、広告配信サーバは、iPhone/Android/Web からの HTTP リクエストを直接受け付けることを前提に書かれた Web サーバーでした。したがって、既存の広告配信サーバーに手を加え、gRPC によるサービス間通信を受け付けるような実装拡張を検討しました。

しかしながら、要件定義が進むにつれて、既存の広告配信サーバーの機能拡張がパフォーマンスの観点から実現が難しい、ということが明らかになっていきました。

Orcha から広告を問い合わせる場合、広告配信サーバーの Latency が、ユーザリクエスト全体の Latency に影響します。具体的に言うと、Orhca の Latency が A ms, 広告配信サーバーの Latency が B ms あった場合、全体の Latency は (A+B) ms となります。ですから、広告によって本体のユーザ体験に悪影響を与えないために、Orcha に可能な限り早く広告を返すことが求められました。そこで、広告配信サーバーの SLO を「Latency」、SLI を「p95 で 15ms」を目標としました。

ところが、既存の広告配信サーバーの Latency は、p95 で 100ms, p99 で 120-140ms 前後でした。そして、当初のデータモデルの都合上、どう Scale-up/Scale-out したところで、目標とする SLI/SLO を達成できないことがわかりました。

というのも、既存の広告配信サーバーでは、AWS RDS (MySQL) + Elasticache (memcached) という構成でした。memcached はそれなりに早く、1 回の get command に対して 1-2 ms で返すことができていました。しかし、取り組むべき本質的課題は、既存のデータモデルのリレーションにありました。既存のビジネスロジックでは、広告抽選をするために、構造上 30-40 回、多いときには 50-60 回程度、 memcached に get command を送る必要がありました。すなわち、p95 で 100ms だったとすると、そのうちほぼ半数以上が memcached 部分で占めていることが観測できていました。

この時点で既存の広告配信サーバーでは、目標とする SLI/SLO を達成できないと判断せざるを得ませんでした。

解決策

そこで、以下の順番で広告配信サーバーをリプレースすることを決断しました。

  • refactoring
    • a-1. 過去の技術的負債を極限まで返済し、コード自体を削減
    • a-2. 仕様そのものを見直し、コード自体を削減
  • re-architecture
    • b-1. アクセスパターンを全て洗い出した上で、データモデルを根本から見直す
    • b-2. RDBMS (MySQL) -> NoSQL (DynamoDB) に移行

「a-1」および「a-2」は、調査的リファクタリング(exploratory refactoring)1の一環として、既存のビジネスロジックを理解すること、及び次の re-architecture のフェーズのリスクと作業コストを削減するための前準備として行いました。仕様そのものの要不要をディレクター陣と調整するといった、地道だが必要不可欠な取り組みも行いました。

「b-1」フェーズでは、既存の広告配信サーバーにおける全ての SQL 発行パターン、及び入稿から配信までの一連のデータフローの INPUT/OUTPUT を整理しました。そうして非正規化されたデータモデルを、「b-2」のフェーズに置いて DynamoDB に格納し、広告抽選を行うために 1 回の BatchGetItem を発行するだけで、既存のロジックを実現できるようになりました。

DynamoDB 自体は水平方向でのスケール性に優れ、数 ms でレスポンス結果を返してくれます。しかしながら、広告配信のターゲティング機能における DynamoDB の利用実績より、Latency に不安定性であることがわかっていました。具体的には、Max の Latency が 数百 ms 以上まで跳ねあがることが一日に数回の頻度で発生していました。それだけでなく、アプリケーションの性質上、読み込みの回数が多いため、DynamoDB への直接の操作回数を減らしてインフラコストを抑える必要がありました。更には、将来のサービスの成長に伴って、 容易に Read を scale-out できるようにしておく必要もありました。

以上の理由から、DAX に白羽の矢が立ちました。

DAX とは

DAX については、以下の特徴があげられます。

  • DynamoDB をバックエンドとすることに特化した in-memory cache store。
  • Single-leader 構成。Primary node がすべての Write を受け付ける。Replica nodes が Item を複製する。
  • DynamoDB への書き込みは Write-through, すなわち DynamoDB への書き込みリクエストの結果まで同期的に行う
  • キャッシュ戦略としては Least Recently Used (LRU) の他、Negative cache や TTL など必要最低限は実装されている

その他の詳細については、official developer guide の他、チームに展開した際の以下資料を参考にしてください

結果

結果からいうと、平常時で 5ms 前後でレスポンスを返すことができています。旧広告配信サーバーの平均 100~120ms と比較し、概ね 20x の改善を実現することができました。レイテンシが不安定でスパイクになっているのは、Cache の TTL が切れるタイミングだと判断しています。その場合でも DynamoDB にリクエストをしてせいぜい 10-15ms で返せています。

下記は負荷試験実行時の結果であり、まだ既存の全広告商品をリプレース後のサーバーが捌いているわけではないとはいえ、仕組み的には広告商品が増えても DAX/DynamoDB へのリクエスト数は増えません。したがって、ECS task の CPU/Memory や DAX instance type を、キャッシュに乗せるアイテム数(working set) に応じて正しく設定さえしていれば、この値からおおきくブレない想定です。

f:id:itiskj:20200220122716j:plain
Grafana Dashboard 抜粋

また、完全な移行によってインフラコストも大きくコストダウンできることを見込んでいます。詳細な計算は伏せますが、旧配信サーバーで利用している AWS RDS + AWS ElastiCache (memcached) から DynamoDB + DAX に移行した結果、月間で 100,000 JPY 前後のコスト最適化が実現できます。更に、アプリケーションサーバーも Ruby (Rails) から Go への移行をしているので、ECS Service の Running Tasks 数の削減によるコスト低下も見込んでいます。

コスト最適化の取り組みは、"インフラのコスト最適化の重要性と RI (リザーブドインスタンス) の維持管理におけるクックパッドでの取り組み"にて紹介されているように、SRE チームが技術力を結集して、最適化に必要な基盤の整備や情報提供を行ってくれています。この取組のおかげで、アプリケーション開発者としても要件を達成しつつインフラコストを最適化するための土壌が養われつつあります。

また、DAX Cluster が再起動中であったり、万一疎通不可能であった場合を考慮し、DynamoDB へリクエストを Fallback する仕組みを実装しています。DAX への操作は DynamoDB と透過的であるため、必要以上にアプリケーションコードに複雑性を持ち込むこと無く実現できたのも、DAX を選択下からの特長でしょう。

制約条件

しかしながら、DAX はもちろん銀の弾丸ではありません。癖の強いミドルウェアですので、本番に導入する際は、以下の制約条件を十分に吟味してから検討してください。

  • DynamoDB 以外のデータストアをキャッシュすることは不可能
    • ただし、複数 DynamoDB Table を使うことはできる
  • DAX SDK の品質およびサポート状況の精査
    • 例えば、Ruby の SDK は存在しないため、自前実装が必要
    • 例えば、aws-dax-go には実装されていない API が多数存在する 2
  • Single-leader 構成のため、書き込みワークロードが求められるアプリケーションの場合は性能に注意
    • 許されるなら Write-Around と呼ばれる書き込み戦略をとることはできる
  • TTL はすべての Item に共通であり、個別に TTL を設定することはできない
    • 例えば、「この Item は TTL 1min, この Item は TTL 60min」といった柔軟な TTL の設定ができない
  • TTL の変更のために Parameter Group を更新する場合、実行中の instance に適用することができない
    • ダウンタイム無しに適用する場合、別 Cluster を立てた上で徐々にリクエストを切り替え、旧 Cluster を落とす、といった運用が必須

運用・保守

次に、DAX を用いたアプリケーションの可用性を中長期目線で担保するための取り組みについて、以下の観点から紹介します。

  • Monitoring
  • Alerting
  • Runbook
  • Maintenance

Monitoring

DAX の CloudWatch metrics 一覧については Developer Guide から確認できます。そのうち、アプリケーションの性質および事業の優先度から、以下の metrics を優先的に監視することにしています。

Metrics Description
CPUUtilization % of CPU utilization
CacheHitRatio ItemCacheHits / (ItemCacheHits + ItemCacheMisses)
ThrottlingRequestCount # of requests throttled by the node or cluster
FailedRequestCount # of requests that resulted in an error reported
EvictedSize Check whether the working set is increasing or not

Metrics 一覧については、Grafana Dashboard にまとめています。ここでは、DAX に限らずアプリケーションの状態を一覧できるような状態を作っています。デプロイ前後の監視体制時や、障害時の原因切り分けに利用することが目的です。

f:id:itiskj:20200220125257j:plain
Grafana Dashboard DAX 関連パネル

また、策定した SLI/SLO も Grafana Dashboard に表示させています。これによって、コンテキストがわからない新規メンバーでもアプリの正常状態を判断し、中長期的な改善の良し悪しの判断に利用できる状態の達成を目指しています。

その他、github.com/prometheus/client_golang 3 を利用したアプリケーションの状態も Monitoring しています。DAX 関連でいうと、DAX への問い合わせ時に Goroutine/Channel を利用した実装をしているため、Goroutine の挙動や GC の状態なども同じ Dashboard から閲覧できるようにしています。必要に応じて custom metrics も計測できるので、CloudWatch metrics だけでは測れない項目を監視フローに導入するのも容易です。

f:id:itiskj:20200220122811j:plain
Grafana Dashboard Goroutine 数パネル

Grafana に CloudWatch Metrics を表示させる場合、Dashboard TemplateGrafana Labs > Dashboard に公開したので、そちらを参考にしてください。なお、一点注意としては、Grafana Dashboard 作成時における DAX metrics の補完機能は v6.6.0 にて追加 されています。

Alerting

Monitoring だけでは、ある日突然 working set が増加しアプリケーションが応答しづらくなったり、Goroutine を利用した実装不具合よる memory leak を発生させたり、といった事象に気づけません。そこで、以下の Alerting を導入しています。

CloudWatch Alarm

以下の 2 つの Metrics について CloudWatch Alarm を設定しています。

  • CPUUtilization
    • CloudWatch Metrics の値をそのまま利用
  • CacheHitRatio

通知については、よくあるパターンですが、以下の経路で開発メンバーの Slack channel に通知しています。

CloudWatch Alarm --> SNS Topic --> AWS Lambda --> Slack

社内では Terraform によって AWS リソースが管理されており、適用のみ一部の権限があるユーザーに絞っている、メンバーであれば誰でも閾値を変更したり、追加・削除したりできる状態を実現できています。

Runbook

チームでは Runbook を活用し、障害発生時でも、オーナーシップを持つ実装者以外でも Scale-up や Scale-out などができるような状態を目指しています。

Alerting 通知の際に Runbook URL を含めることによって、障害発生時において、ビジネス影響の確認から障害対応までの一連のフローを事前に分かる範囲でドキュメント化し、誰でも対応できる状態の達成を目指しています。

Alerting とは、通知の設定をすればいい、というものでは決してありません。アプリケーションの可用性を向上させ、事業の成長に貢献してこそ意味があります。Alerting が通知された後、適切なアクションやアプリケーションの修正を通じて初めて品質が向上するのであり、そこまでセットで考えてチームに導入しないと意味がありません。4

筆者にも、苦い思い出があります。以前、"cookpad storeTV の広告配信を支えるリアルタイムログ集計基盤" でご紹介したとおり、storeTV の広告配信ログ基盤において、ストリーム処理における Best Practices に則り、遅延ログの検出および Alerting の仕組みを構築しました。私がシステム構築後運用を他のメンバーに移譲した後、通知は来るものの、システムの設計思想や背景、アクションプランを適切に引き継ぎできていなかったため、割れ窓となってしまい、見るべきエラーを見落としてしまう、という状況を作ってしまっていました。

その時の反省を活かし、誰でも最低限の運用保守はできるようなチームの文化を築くことを目標に置きました。具体的な手順は勉強会で共有したり、その際のハマりどころを知見として展開すると行った取り組みも合わせて展開しています。更に、一度設定した閾値も、チームの状況、メンバーのスキル、アプリケーションの性質、サービスの成長に伴って柔軟に削除・チューニングできるよう、Metrics の意味の共有と目線合わせも、勉強会などの手段を通じて行っています。

Maintenance

DAX では、メンテナンス時のイベントを SNS Topic に通知させることができます。

When a maintenance event occurs, DAX can notify you using Amazon Simple Notification Service (Amazon SNS). To configure notifications, choose an option from the Topic for SNS notification selector. You can create a new Amazon SNS topic, or use an existing topic. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.cluster-management.html#DAX.cluster-management.custom-settings

Scale-up や Scale-out、TTL の変更のための Parameter Group 更新による再起動時など、DAX Cluster に何らかの構成変化がある場合、アプリケーションの監視体制に入る必要があります。こちらも CloudWatch Alarm と類似の以下の構成で開発メンバーの Slack channel に通知しています。

DAX Events --> SNS Topic --> AWS Lambda --> Slack

まとめ

以上、広告配信サーバーにおける DAX の活用事例について紹介してきました。

マーケティング領域は、技術的にチャレンジングな課題も多く、かつ事業の売上貢献に直結することが多い、非常にエキサイティングな領域です。また、アドネットワークではなく、自社の事業で専用の配信サーバーとユーザーデータを保持するからこその事業の面白さもあるため、事業開発に興味・関心が高い人にとっても活躍の可能性が大いにある場です。

メディアプロダクト開発部では、一緒に働いてくれるメンバーを募集しています。少しでも興味を持っていただけたら、以下からエントリーをしてください。



  1. 『レガシーソフトウェア改善ガイド』より

  2. d.unImpl() で確認できる関数は現在未実装

  3. https://grafana.com/grafana/dashboards/6671 に Prometheus client を利用して取得できる Metrics の Grafana Dashboard テンプレートが存在します

  4. 『入門 監視』3 章にも「監視とは、あるシステムやそのシステムのコンポーネントの振る舞いや出力を観察しチェックし続ける行為である。アラートは、この目的を達成するための1つの方法でしか無いのである」とあるとおり、Alerting という手段自体が目的とならないように意識したい

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://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('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/