クックパッドマートのプロダクト開発チームに On-Call を導入した話

クックパッドマートでサーバーサイドエンジニアを担当している奥薗 基 ( @mokuzon ) です。

クックパッドマートのプロダクト開発チーム*1では半年ほど前からサービスの運用・障害・割り込み対応の当番として on-call を導入しています。直訳すると呼べばすぐ来る、待機しているという意味で、業務時間外も含めシステムを安定して運用するために待機するエンジニア、またはその制度そのものを指しています。SRE チーム*2には on-call は一般的ですし、プロダクト開発チームでも問題があれば直ぐに駆けつけることは一般的です。しかし、プロダクト開発チームで on-call を運用している事例は意外と世に出ていないので、モチベーションと運用方法、効果についてご紹介したいと思います。

クックパッドマートとは?

このブログでクックパッドマートに関するエントリーは久しぶりなので、改めてサービスの紹介をします。既にご存知の方はこのセクションは読み飛ばしても差し支えありません。

クックパッドマートは現在クックパッドが力を入れている新規事業の一つで、生鮮を中心とした EC プラットフォームです。

https://cookpad-mart.com/

以下がサービスの主な登場人物とその関係図です。

クックパッドマートの主な登場人物とその関係図

ユーザーが商品を購入し、販売者が商品を出荷し、ドライバーが各所に設置されたマートステーションに商品を届けます。 この流通を支えるために

  • ユーザー向け
    • EC モバイルアプリケーション
  • 販売者向け
    • 商品管理・出荷管理用 web アプリケーション
  • ドライバー向け
    • 作業用モバイルアプリケーション
    • 管理者向け web アプリケーション
  • マートステーション
    • 冷蔵庫そのもの
    • 遠隔監視・操作用のネットワークとアプリケーション

などを提供しています。

サービスローンチから2年経ち、最近では大手コンビニでマートステーション設置が始まるなど、鋭意拡大中のサービスです。 人員も急増しており、事業部の規模も社内最大になりつつあります。

なぜ On-Call を導入したか

クックパッドマートが on-call を導入することにした大きな理由は

  • 遅延が許されない流通に関わる処理が深夜に集中しているが、それが失敗することが多かった
  • 平日昼間の割り込みタスクが多い

の2つです。

深夜に起きる、遅延が許されない処理の失敗

これを説明するためにはクックパッドマートの特性について話しておく必要があります。

クックパッドマートの特性

1週間ごとに見直されるルーティング

理想はすべての販売者の商品をすべてのマートステーションで受け取り可能にすることですが、事業的・物理的制約でこれはまだ実現できていません。

そのため今は1週間ごとに販売者( 正確には販売者の共同出荷先 ) とマートステーションの組み合わせ、そしてそれを回るドライバーのルーティングを組み直しています。

購入可能かどうかが在庫だけではなく配送計画にも依存していること、そしてこの配送計画のデータ構造そのものが言葉にする以上に複雑です。

この複雑さにアプリケーションも人も対応しきれず、以前は入稿ミスやバグによるデータ不整合が多発していました。

山場が深夜から始まる

クックパッドマートの山場は販売者の商品の出荷から始まります。

市場や農家など、販売者の方々の朝はとても早いです。 早い販売者では深夜 02:00 から出荷が始まり、遅くても朝 10:00 までにほとんどの販売者が出荷を完了しています。 この出荷に合わせてシステムでは深夜から早朝にかけてユーザーの注文を締め切り集計し、出荷方法の指示をまとめて販売者に送信するという処理が動いています。

また、朝 08:00 にはドライバーの集荷・配送手順をまとめたデータ群が作成されます。

実際のドライバーへの指示
実際のドライバーへの指示

On-Call 導入へ

データ不整合があると販売者への出荷指示やドライバーへの集荷・配送指示の作成が止まり、比喩ではなく流通が止まります。流通はかなりギリギリのタイムラインで組まれているため、一刻も早く修正する必要があります。

これらを支える処理は販売者やドライバーが活動する前に処理が済んでいる必要があります。しかしまだアプリケーションが成熟していなかった頃は、この重要な処理を行うバッチが深夜に高頻度で落ち、夜な夜な対応に追われるということが続きました。

深夜に需要バッチが落ち悲鳴を上げるエンジニア達深夜に需要バッチが落ち悲鳴を上げるエンジニア達
深夜に需要バッチが落ち悲鳴を上げるエンジニア達

しかも原因が複雑さにあるために対応可能なエンジニアも少なく、特定メンバーの深夜労働時間が突出して伸びたり、毎晩深夜 02:00 のバッチが通るまでは心配で寝られない、という非常に不健全な状態に陥っていました。 そのため各日ごとに張り込むメンバーを決めて、それ以外のメンバーを解放することが急務でした。 この張り込みを当番化することが on-call 導入のきっかけになりました。

なお、現在は

  • 事前チェックスクリプトが整備され、深夜になる前に問題の修正ができるようになった
  • 万が一の場合も電話で通知されるようになった

ため、深夜のトラブルはほぼなくなり張り込みは完全になくなっています。

割り込み対応

もちろん理想は割り込みが起きないよう根本的な改善をしていくことですが、限られたリソースで新規開発を進めている以上、必ず割り込みは発生します。 そして悲しい現実として、割り込み対応をし続けると本来の業務へ使える時間は減っていき、人事評価に繋がる成果からは遠のきやすくなります。これでは負担が集中しているメンバーのメンタルは濁ります。

そこで、まずは最低限 on-call によりローテーションを決めて特定メンバーに割り込み対応が偏らないようにしています。 また、on-call の時とそうでない時で割り込み対応の有無を明確に分けることはエンジニアが集中しやすい環境としても重要です。

以下の3つがクックパッドマートの割り込み対応の大半を占めています。

データ修正

新規サービスに携わったことがある方々は分かると思いますが、サービス初期は素早くユーザーに価値を提供し事業を成立させることが最優先であるため、どうしても管理画面をはじめ運営用ツールの開発は後回しになりがちです。 しかし事業の規模や複雑さが増すにつれてデータ修正の件数は増えてきます。

すると何が起こるかと言うと、データ修正の依頼がエンジニアに殺到します。 管理画面や機能がない以上、非エンジニアはエンジニアにデータを直接書き換えてもらうかデータ更新のワンタイムスクリプトを書いてもらうしかないからです。

そしてこの割り込みは、関わるメンバーの多さや親切さなど理由はいくつかありますが、一部のメンバーに集中する傾向があります。今も個人への依頼を都度 on-call に誘導する努力を続けています。

非エンジニアからの質問を調査

非エンジニアの同僚から仕様を質問されたり、問題が起きた時に原因調査を依頼されたりすることはよくあります。 もちろんすぐに答えられたり責任範囲が明確だったりするものはそのメンバーが対応すればよいでしょう。しかし、エンジニアも自分が実装した内容を全て覚えてはいませんし、そもそも実装したエンジニアが今も在籍しているとは限りません。その場合結局エンジニアも都度コードを読んだり背景をたどったりして調べる必要があるのです。

特にユーザーからの問い合わせをまとめて受けている CS*3 からの質問が圧倒的に多いです。 またクックパッドマートのようなプラットフォーム型サービスでは、提供者側 ( クックパッドマートでは販売者 ) に対しても CS が存在します。こちらは売り上げに直結するため温度感が総じて高く、優先度最高の割り込みになりがちです。

実は on-call 導入前はまだユーザー向けの CS 専任メンバーがおらず、大きな負担にはなっていませんでした。しかし、経験上専任メンバーが生まれたら爆発的に増えることはわかっていたのであらかじめ負担が分散される on-call の仕組みに乗せてしまいました。実際に予想通りになった上、ユーザー数の増加にあわせて CS 案件も増えているので、先手を打ててよかったと思っています。

アクセススパイクへの事前準備

クックパッドマートは EC サービスなので、販促イベント等の

  • いい商品が出た
  • 期間限定品が出た
  • 安く買える商品が出た

という状況ではアクセスが如実に跳ねます。具体的には販促のプッシュ通知やタイムセールが該当します。特にタイムセール開始時のアクセス数は平時の15倍以上になります。このようなアクセススパイクは平時のインフラ構成では捌ききれないので、適切に対策をしておく必要があります。

タイムセール時の rps
実際のタイムセール時の rps、 18:30 過ぎに跳ねている

クックパッドマートのシステムのインフラは殆ど AWS*4 を利用して構成されています。さらに、プロダクト開発チームが自走出来るように SRE チームが権限の譲渡や基盤の整備などいわゆるセルフサービス化を進めているため、プロダクト開発チームでも容易にアプリケーションやデータベースのスケールアウトが出来る環境になっています。

販促イベントは不定期かつ内容も多彩なので、こうしたイベントがスケジュールされた際に過去の事例等からキャパシティプランニングをしてインフラの対策をし、該当時間に監視を行うのも on-call の重要な役目となっています。

このアクセススパイクとの奮闘はまた別の機会に詳細にご紹介できればと思います。

On-Call の運用方法

クックパッドマートでは2020年12月時点で 18 名のエンジニアで Primary と Secondary を12時間ないし24時間間隔でローテーションしています。 基本的には Primary が優先的に対応し、あふれた分を Secondary が拾うスタイルです。 現状では障害や割り込みといったタスクの種類と役割の紐付けは行っていません。

運用にあたっては以下のツールを使っています。

エンジニア依頼・質問専用の Slack チャンネル

弊社は全社的に Slack*5 を使っており、クックパッドマートも大量のチャンネルを使っています。その中で、エンジニア依頼・質問専用のチャンネルを作りました。

これにより

  • 各チャンネルにいる身近なエンジニアに負担が集中することを防げる
  • 各チャンネルに散らばっていて全体像が見えない割り込み対応の規模・状況が俯瞰できる
  • 記録が一箇所に蓄積される
  • 新メンバーがコミュニケーションする場所を迷わない

などのメリットがあります。

GitHub Issue

よくフロー型とストック型のツールを適切に使い分けましょうと言いますが、上記の Slack 専用チャンネルでは正に情報をストックしづらく参照しにくいという課題があったため、GitHub*6 の issue 上にて記録を残すことを意識したやり取りにシフトしつつあります。

こうすることで、初めて経験する問題に直面した際も、過去の同様の事例を元に対処しやすくなりました。

なお、この issue は一つ専用リポジトリを作って、issue open / close のみ先述の Slack チャンネルに通知を流すようにしています。

これにより通知で流れが阻害されることなく、各案件のアサイン割り振りや状況確認等を適切に Slack で行えるようになっています。

現在は以下の template を設けています。

## 概要

## 関係する URL

## 解決したい期限(あれば)

<!---
どなたかをこの issue にアサインして、誰がこの issue に取り組んでいるのか分かりやすい状態にしてください。

* エンジニア on-call で今 Primary の人をまずはアサインしてください。
    * その後、より適切な方がいればエンジニアの方で適宜アサインを変えていきます。
* よく分からなければ何もせずそのままにしておいてください。
    * 気づいたエンジニアの方、アサインの設定をお願いします。
    * もしちょっと待っても誰もコメントしていないようであれば #kaimono-dev-inquiry でエンジニアにご連絡ください。
-->

PagerDuty

当初は2ヶ月ほど Google スプレッドシート*7 でローテーション管理をしていました。しかしこの運用は管理者とon-call 対象者に少なくない負担を強いていました。 さらに、通知などツールによる支援を受けたい需要も大きかったため、部署横断の SRE チームで既に利用実績のある PagerDuty*8 を採用しました。

  • 夜間に重要な処理が多く走るため、電話をはじめ多くの通知方法を備えている点
  • 業務委託の方にも on-call をご対応いただく際に、業務時間外に割り振られないよう柔軟にローテーションのルールを決められる点
  • 各自のスケジュールを iCal 形式で出力できる点

が特に助かっています。

On-Call を導入して

これまで述べた役割を上記の方法で機械的に分担するようにした結果、

  • on-call 時以外の平和な夜
    • とはいえ今は on-call でもほとんど平和
  • on-call 時以外の割り込みが激減し、生産性が向上
  • on-call は予めスケジューリングされているため、それを見越した見積もりが出来る
  • 割り込み対応の偏りが軽減し、メンバーのメンタルに良い影響
  • 特定メンバーに集中していたアプリケーションの知識やノウハウの拡散

など、サービスの課題から組織の課題まで幅広く健全な状態に近づいたと考えています。

反面、参画して間もないメンバーにも在籍が長いメンバーと同様の負担を強いており、on-call に精神的負担を感じてしまう課題も出始めています。 この問題には、初期は経験豊富なメンバーとペアを組んだり、可能な限りドキュメント整備したりすることで改善を図っています。また、on-call のオンボーディングに関して SRE 本*9で非常に実践的な内容が紹介されており、SRE でなくともそのエッセンスは十分に活かせると考えています。

今後も、様々な問題を仕組みで解決するエンジニアらしさを武器にサービスも組織も更に成長させてゆきたいと思います。 そんなクックパッドマートは絶賛エンジニア大募集中なので、興味のある方はぜひ以下のリンクから詳細な採用情報をご覧ください。

https://cookpad-mart-careers.studio.site/

最後までお読みいただきありがとうございました。

*1:プロダクトの開発を行うチーム。イノベーションを生み出すことに責任があり、その速度も非常に重要視されている。チームには様々な職種のメンバーがいるが、本記事ではその中のエンジニアを指してこの用語を使っている。

*2:サイト・リライアビリティ・エンジニアリング ( Site Reliability Engineering ) を行うエンジニアチーム。Google が提唱していて、プロダクトの安定性に責任を持ち、そのためにソフトウェア・エンジニアリングのエッセンスを用いている。

*3:Customer Support、いわゆるお客様窓口。

*4:https://aws.amazon.com/

*5:https://slack.com/

*6:https://github.com/

*7:https://www.google.com/sheets/about/

*8:https://pagerduty.com/

*9:Betsy Beyer, Chris Jones, Jennifer Petoff, Niall Richard Murphy 編、澤田 武男、関根 達夫、細川 一茂、矢吹 大輔 監訳、Sky株式会社 玉川 竜司 訳『SRE サイトリライアビリティエンジニアリング―Googleの信頼性を支えるエンジニアリングチーム』 O'Reilly Japan, Inc. ( 2017 )

AWS の This is My Architecture(動画)でS3を中心としたセキュリティログ基盤の紹介をしました

技術部セキュリティグループの水谷です。

先日、Twitterに投稿もしたのですが、AWSのThis is My Architectureという事例紹介のシリーズでクックパッドが取り組んでいるセキュリティログ管理基盤の紹介ビデオが公開されました。この記事ではビデオの内容の補足、そして撮影の様子などを紹介したいと思います。

This is My Architecture とは?

AWSにおいて事例紹介となるようなアーキテクチャを構築・運用しているユーザが、自分たちのアーキテクチャを5分ほどのビデオで紹介するシリーズです。様々な分野で活躍しているユーザとAWSのアーキテクトの方と一緒にアーキテクチャを紹介する、という形式になっています。

aws.amazon.com

今回クックパッドでもオファーをいただき、社内でいくつかあった事例の中でセキュリティログ管理の話が紹介としては良さそうということで、私がAWSの桐山さんと共にビデオ撮影させていただきました。

動画の撮影

ここからは動画撮影に関するよもやま話をお伝えしようと思います。

撮影まで

この This is My Architecture の動画は毎年年末にラスベガスで開催されている1AWSのイベント re:Inventの会場で撮影されています2。そのため、この動画も撮影したのは実は去年のre:Invent(2019年12月)でした。

しかし、諸般の事情により私が現地に行って撮影する、というのが正式に決まったのが11月5日で、そこから超特急で渡航手配3や発表準備に取り掛かりました。10月にはre:Inventの参加するとは露ほども考えていなかったので、なんとか間に合ってよかったです。

原稿の準備

撮影準備で大変だったのはまず原稿の準備です。撮影をするうえで原稿を事前に用意しないといけないというルールがあったわけではないのですが、今回は英語で話すということと発表時間の制約が厳しい(原則5分以内)ことから、発表内容は全て原稿を用意しました。

このブログの後半に補足として書いていますが、今回のアーキテクチャとして伝えたいことはいろいろありました。なので、最初はもりもりの原稿になってしまい、かなり早口で説明しても2〜3分ほどオーバーしていた記憶があります。

そのため、このアーキテクチャの本質として伝えたいことに要点をしぼり、何度も推敲を重ねて実際に撮影に使用したバージョンまでブラッシュアップしました。特に私自身は英語が全く得意ではないので、わかりやすく丁寧に発音するためにはゆっくり喋れるような余裕が必要でした。そのため、さらに尺に余裕が出せるように原稿を短くしていきました。

発表練習

原稿の準備と並行して、発表練習も11月中にかなり時間をとって取り組みました。

f:id:mztnex:20201201143307p:plain

写真はAWSオフィスにて、実際の撮影時にある黒板をホワイトボードで想定しつつ、説明の手順を確認したり、発表原稿の読みあわせをしたときのものです。先に述べたように、私は英語がだいぶ苦手な部類だったため、流暢に話せるようにということも含めて練習していました。

発音に関しては(出来はさておき)かなり事前に確認しました。やはりどうしても日本人発音になってしまいがちなので、何度かネイティブスピーカーの人に発表を聞いてもらい、聞き取りづらいところなどをチェックして発音を修正する、というのを繰り返しました。また、自分が発音しづらかったりネイティブスピーカーの人に聞き取りづらいような単語は避け、別の単語に置き換えるような修正もしました。特に数字については結果を示すために非常に重要な要素にも関わらず、なかなか正確に聞き取ってもらうのが難しかったので、ボードに直接書く&特にはっきり話す、という作戦でいきました。

また、原稿準備のところでも触れましたが、今回は時間の制約が厳しかったことなどから発表原稿は一言一句丸暗記しました。英語でも日本語でも完全に丸暗記して発表するというのは実は初めての体験だったため、11月は暇さえあれば原稿をブツブツつぶやいていたなと記憶しています。

撮影本番

12月5日の朝(現地時間)から撮影でした。この前日は時差ボケ4と緊張で見事に一睡もできなかったのですが、意外と元気に撮影できました。

f:id:mztnex:20201201143414j:plain

撮影はre:Invent会場の一部が撮影専用のスタジオとなっており、そこで撮影してもらいました。This is My Architectureのページを見ていただくと分かる通り、かなりの数の事例紹介があるため、各チームが入れ替わり立ち替わりで撮影していました。

f:id:mztnex:20201201143434j:plain

ボードを用意してくれるスタッフの方にアーキテクチャ図の説明をしたりしつつ、その場でブラックボードに書き込みをしてもらうなど準備をして、撮影に突入しました。

f:id:mztnex:20201201143458j:plain

かなり入念に準備したかいあって、撮影自体は非常にスムーズに終わりました。一応、スタッフの方からは一発OKをもらったのですが、ちょっと言いよどんでしまったところもあったので英語版をもう一度だけ撮影し直して完了できました。

さらに「時間余ったからせっかくなんで日本語版も撮ろうか?」みたいな話になり急遽日本語版も撮影することになりました。過去のThis is My Architectureをざっと見た限り二ヶ国語で別々に撮影されたものはほぼ無さそうで、おそらくかなりレアケースだったようです。おかげで日本語版も撮影させてもらったのですが、それまでずっと英語で話していた内容で全く日本語版を想定していなかったので、撮影中は必死に英語から日本語に翻訳して喋っていました。

完成動画

ということで完成した動画がこちらとなります。動画の編集や公開のタイミングなどの問題で、最終的に公開されたのが少し遅くなってしまいましたが、無事公開していただきました。

英語版

www.youtube.com

日本語版

www.youtube.com

(動画の補足)S3を中心としたセキュリティログ管理基盤

今回撮影した動画は5分以内に収める&使えるアーキテクチャ図も限られていたため、詳しい説明を大幅に削ってエッセンスだけを話させてもらいました。(原稿の推敲にはだいぶ苦労しました)なのでこのブログで少し内容を補足させてもらえればと思います。ちなみに、この話はかなり前から取り組んでおり何度か講演やブログでも紹介させて頂いたものなので、それらと重複する部分がかなり含まれる点はご容赦ください。

https://techlife.cookpad.com/entry/2018/05/31/080000

過去の記事でも紹介したとおり、クックパッドではセキュリティ監視に使うログを全てS3に保存してから利用する、というアーキテクチャを採用しています。

従来、セキュリティ関連のログ管理ではSIEM(Security Information & Event Manager)などのログ管理ソリューションが用いられて来ました。細部は製品やサービスによって違うものの、大まかな発想としてはログを直接取り込んでアラートを検出するフローとログを保管するフローを別々に扱うものが多かったと思います。このアーキテクチャの利点は、取り込んだログをなるべく早くアラート検知に利用することで発報までの遅延が短くなることです。しかし一方で以下の2つの課題を抱えています。

  1. 障害対応時の対応負荷:センサーからのログ送信経路やアラート検知・ログ保管側のシステムに障害があった場合には対応・復旧作業が必要になります。再度センサーなどからの取り込みをしないとログが欠損してしまいますが、ログの取り込み元の数が多くなるほど対応の工数が大きくなります。また、各センサーで大きくバッファを持たないとログが消失する可能性もあります
  2. ログスキーマ管理の難しさ:利用するログの種類が多くなるとログのフォーマット、スキーマ管理も大きな課題になってきます。アラート検知やログ保管をする際にはログのフォーマットやスキーマを解釈して処理をする必要がありますが、これを事前に設定しておかないと取り込みに失敗します。失敗後のリトライで再度ログを送信する必要がでてくると、これもトラブル対応と同様にログの欠損や消失を防ぐための負荷が大きくなってしまいます。

この問題を緩和するために、クックパッドのセキュリティログ管理基盤ではアラート検知やログ検索のフローを実行する前に「S3にログを保存する」というレイヤーを挟んでいます。S3はバケット上にオブジェクトを作成された際、即座にそのイベント情報をSNS (Simple Notification Service) などへ通知する機能があります。これを使ってS3へのログデータ到達とほぼ同時にアラート検知のためのプロセスを発火させたり、検索システムへログデータを転送させることができます。検索システムへのログデータ転送は、先日AWSのブログで紹介されていたAWS サービスのログの可視化やセキュリティ分析を実現する SIEM on Amazon Elasticsearch Serviceでも同様のアーキテクチャが採用されています。

f:id:mztnex:20201201144027p:plain

このアーキテクチャの利点

先の述べた課題である 障害対応時の対応負荷 および ログスキーマ管理の難しさ が軽減されるのが、まず1つ目の利点です。利点として一見地味なのですが、継続して運用をすることを考えると、これらの負担軽減は大きな意味を持ちます。

S3は高い可用性を持っており、多くの場合自分でストレージを運用するのに比べてトラブルが少なくてすみます。そのためセンサーからS3へ「とりあえず」ログを投げ込んでおくことで欠損のリスクを大幅に小さくすることができます。また、S3に保存された後の処理が失敗しても、再度S3のオブジェクト生成イベントを流すことで容易に後続の処理を再開することができます(ただしこれは後続の処理が冪等になるよう設計されている必要はあります)。

ログスキーマ管理の観点では「センサーがログを送ってきたタイミングでスキーマが完璧に定義できている必要がない」というのが大きな利点になります。センサーのログを受け取った時点でパースするようなシステムの場合、送信の前に完璧にパースできるようスキーマの把握をしてパーサーを用意しておく必要があります。もしパースに失敗した場合はセンサー側から再送が必要になってします。しかしS3に保存する際にはスキーマは全く関係なく、もしパースに失敗してもS3から処理を再開できます。到着するまでスキーマがはっきりしないようなログでも、S3のオブジェクト生成イベントを一時的にどこかへ退避させることで、到着してからパーサーの準備をするということも可能です。

他にも、安価にログを保存できるS3でログ保管ができる、という利点があります。SIEMなどログ保管・検索を同じアーキテクチャに持つものだと、ログを保持するための料金が比較的高くなってしまいます。保管と検索を同時にしているため、保持しているログなら容易に検索できるメリットがある反面、ストレージ本体やストレージを動かすためのインスタンスないし筐体のコストが高価になりがちという問題があります。S3はデータの保存料金が非常に安価に抑えられておりコストメリットが大きいだけでなく、トータルの保存容量の制限もないためにストレージの残り容量を夜な夜な気にする必要もありません。ライフサイクルマネジメント機能を使って自動的に長期的に保存するのにも向いています。

このアーキテクチャの欠点

従来のアーキテクチャと比べたときに、S3を中心としたアーキテクチャの欠点として挙げられるのは、センサーからアラート検知やログ検索のシステムへログが到達するまでの遅延が発生することです。これはS3がオブジェクトストレージであるため、ログをなるべく細切れにせずにある程度の数を1つのオブジェクトにまとめたほうがリクエスト数が減ってコストメリットが出るためです。また、あまりにリクエスト数が多いとAPIの呼び出し制限にひっかかる恐れがあります(参考)。こうしたことから、センサー側で少しログをためた後にS3へアップロードするため、ログを貯める時間がそのまま遅延になります。この遅延がどのくらいになるかはログの流量やセンサーの能力や性質などに依存するため一概には言えませんが、筆者の経験則からするとおおよそ1〜2分、最大でも5分程度になるように各種設定するのが良いと思われます。

ここで問題になるのが、1〜2分ほどの遅延がセキュリティログの利用にどのくらいの影響を及ぼすのか? という点です。例えばManaged SOCのようなビジネスをしていたり専属の24/365体制なSOCを持つような組織の場合は、アラートが上がった場合に即応する体制が整っており、1〜2分の遅延を削ることに意味があるかもしれません。しかしそうでない場合は必ずしもアラート検出から1〜2分で対応できるとは限らず、遅延が致命的になるとは想像しにくいです。また、Managed SOCでもアラート発生から対応するまでのSLA(Service Level Agreement)が設定されている場合がありますが、最短でも15分程度です。その中の1〜2分であれば、多くの組織の場合許容できるのではないかと考えられます。クックパッド内でもこのような基準をもとに考え、多少の遅延は許容できると判断しました。

分析パートについて

この動画を撮影したのが実に1年前なので、分析パート(ボードのANALYSISの部分)について少し補足です。

アラートの検知については引き続きLambdaを使い、ログの中からアラートとして扱うべき事象の抽出をしています。アラート発報後の対応のフェイズについては サーバーレスで作るセキュリティアラート自動対応フレームワーク で紹介しております。

また、ログ検索については動画中でgraylogを使っていると話していますが、その後Athenaをベースとしたセキュリティログ向け全文検索システムminerva に移行しており、graylogは退役しています。こちらについてはAmazon Athena を使ったセキュリティログ検索基盤の構築 でも紹介しておりますので、よろしければあわせて御覧ください。

まとめ

最初、この撮影の話を聞いたときは「まあ5分喋るくらい大したことないでしょ」とか軽く考えていたのですが、正直な感想として想像の100倍くらい大変でした。しかし、撮影準備の過程で自分の作ったアーキテクチャについていろいろな人と議論できたり、その本質について考えさせられるなど良い経験もさせてもらいました。なにより、動画という良い形で成果を残せたことは、とても良かったと思います。この機会を提供してくれた&協力してくれたAWSの方々、そして桐山さんに改めて御礼を申し上げたいです。

少し話が変わりますが、クックパッドのセキュリティエンジニアはこのような自分たちが必要とする仕組みを自分たちで考え、組み立て、現場に活かしていくというのが役割の一つになっています。先日、CODE BLUE 2020でもこのような話をさせていただいたのですが、情報セキュリティの課題を知識や経験だけでなくエンジニアリングで解決していく、というのはとても刺激的で、私自身はとても楽しんで仕事をしています。しかし現状、一緒にチャレンジしてくれるメンバーが足りていないこともありセキュリティエンジニアのポジションは引き続き募集しています。もし興味のある方がいらっしゃいましたら、まずはzoomなどでカジュアル面談して実際どうよ?という話もできるかと思いますので、ぜひお気軽にお声がけください!


  1. (既に開催されていますが)今年のre:inventはバーチャルで11/30から3週間続くそうです https://reinvent.awsevents.com/

  2. なぜわざわざre:Inventの開催期間中なのかというと、re:Inventなら世界各地から関係者が集まるから一気に撮影できてよい、ということらしいです

  3. 直前に参加しようとした場合、最大の問題はホテルなのですが、これはre:Inventに参加する同僚の部屋が奇跡的に1人分余っている状態で、そこに転がり込ませてもらいました

  4. re:Inventの会期は12/2からで他の同僚はさらに前に現地入りしていたのですが、自分は12/1に引っ越しがあって、現地着が12/3 夜という突貫出張でした。おかげで当日までに時差ボケ治らず。

レシピサービスのフロントエンドを Next.js と GraphQL のシステムに置き換えている話

技術部の外村(@hokaccha)です。今回はクックパッドのウェブサイトのフロントエンドを Next.js などを使って作り直している話を書きます。

この記事で紹介する新システムは、スマートフォン向けのレシピページで確認することができます。もし興味があるかたはレシピページをスマートフォンのユーザーエージェントで開いて DevTools などで確認してみてください。 Next.js と GraphQL で動いているのがわかると思います。

f:id:hokaccha:20201130205054p:plain

ご存じの方も多いかもしれませんが、クックパッドのウェブサイトはモノリシックな Rails で作られていて、10年以上 Rails で開発を続けてきました。10 年以上同じシステムで開発を重ねれば当然レガシーな部分が大量に生まれてきますが、特にフロントエンドはその影響が顕著でした。

どこから使われているかわからない CSS が大量にある、JS のコードは昔ながらの CoffeeScript*1 と jQuery で書かれており、JS のライブラリの管理も vendor/assets にファイルを入れてリポジトリにコミットするという運用。app/assets 以下のファイルやディレクトリ名に規則や規約もなく、どこにファイルを置いていいかすらよくわからない、という状態です。

こういった状態なので当然開発効率は悪くなりますし、開発のモチベーションも低くなります。その結果ユーザーに価値を届けるまでのスピードが遅くなってしまうのが最大の問題です。この問題を根本的に解決するため、今回 Next.js でフロントエンドを作り直すという決断をしました。

一度にすべての画面を置き換えるのは無理*2なのでまずは最も活発に開発されておりユーザーからの利用も多いところから始めることにして、スマートフォン版のレシピページをターゲットに決め、今年の2月ぐらいから開発をはじめて先月リリースすることができました。比較的うまくいっているので今後も適用する画面を広げていく予定です。

以降では今回のシステムの技術要素やパフォーマンスへの影響について説明します。

技術要素

今回のシステムにおける重要な技術要素は以下です。

  • TypeScript(言語)
  • Next.js(フロントエンドフレームワーク)
  • GraphQL(API)

技術選択する上でまず最初に考えたのは、TypeScript を中心に据えることです。型チェックによる整合性の検査、補完やリファクタリングを中心としたエディタの支援など、TypeScript を導入することによる生産性の向上は非常に大きいものがあります。

次にフロントエンドの描画ライブラリはシェアの大きさやTypeScript との相性、使いやすさなどを考慮したうえで React を採用することにし、サイトの性質上 Server Side Rendering(以下 SSR)は必要になると思っていたので、React.js で SSR がいい感じに動くフレームワークということで Next.js を採用しました。Next.js は技術選定をした少し前に TypeScript 対応を強化したり、動的なルーティングをサポートしたりと、いい感じのアップデートがあったのも決め手の一つでした。

次に API です。クックパッドにはモバイルアプリなどで使われている、社内では Pantry と呼ばれている REST API のシステムがあります。最初は Pantry を直接 Next.js から利用する方向で考えていましたが、認証の問題や、リクエスト・レスポンスへの TypeScript の型付けの問題、将来的に Pantry 以外のサービスにもリクエストが必要になる可能性を考えると、BFF レイヤーに GraphQL を導入するのがよさそうという結論になり、GraphQL を導入することにしました。

この目論見はうまくいき、BFF のレイヤーとして GraphQL がいい感じに動いています。リクエスト・レスポンスの型定義には graphql-codegen を利用することで GraphQL のスキーマから TypeScript の型定義ファイルを自動生成しています 。また、GraphQL のサーバー実装も TypeScript (素朴な express-graphql を使った実装)を採用し、単一の言語でフロントエンドと API を開発できるので言語のコンテキストスイッチが少なくなるようにしています*3

現状の GraphQL サーバーの仕事のほとんどは Pantry へのリクエストですが、Pantry へのリクエストのところを少し工夫して楽することに成功したので紹介します。

Pantry は Garage というフレームワークを用いて実装されている API サーバーです。Garage には fields というクエリストリングで取得するリソースのフィールドを絞り込めるという機能があります。

/v1/recipes/:id?fields=id,title,user[id,name]

このような感じです。カンマ区切りで取得したいフィールドを指定し、user[id,name] のようにネストしたリソースのフィールドも絞り込めます。これは GraphQL のクエリに非常によく似ています。GraphQL で表現すると次のようになるでしょう。

query {
  recipe(id: $id) {
    id
    title
    user {
      id
      name
    }
  }
}

Garage の fields は Facebook の Graph API を参考にされて作られており、Graph API が GraphQL の原型であるという経緯により、このような類似したインターフェースになっているようです。今回のシステムではこの性質を利用し、GraphQL のクエリを Garage の fields に自動で変換し、Pantry へリクエストするという機能を実装しました。これはうまく動いており、 GraphQL のサーバー実装が大幅に簡略化されました。

システム構成

今回はすべての画面をリプレイスするわけではなく、一部の画面だけ新システムに向けるので、その制御を前段のリバースプロキシ(Nginx)で振り分けています。

f:id:hokaccha:20201130093752p:plain

/recipe/:idのスマートフォンからのリクエストを Next.js へ、/graphql を GraphQL のサーバーへルーティングし、残りはこれまで通りの Rails のアプリケーションへルーティングします。Next.js のサーバーでは SSR をおこない、HTML を作ってユーザーに返します。GraphQL へのリクエストについては、Next.js が SSR 時に GraphQL の API を呼び出す場合と、クライアントがブラウザから直接 GraphQL の API を呼び出す場合があります。

SSR の是非については色々と議論があるでしょうが、パフォーマンス(特に LCP(Largest Contentful Paint))の最適化、OGP 対応などを考慮して SSR を採用しています。

社内では Node.js でサーバーを運用した知見がほとんどなかったので、性能や運用の面で不安がありましたが、社内には ECS によるコンテナのデプロイ基盤が整っており、Docker で動きさえすればマルチプロセス化などは考えずに 1vCPU でタスクを横に並べるだけでいいので思っていたよりも楽に運用が可能でした。性能面でも、Next.js の SSR サーバーは 200rps 強を 1vCPU のタスク 7 つ前後で捌けているのでまずまずといったところです*4

パフォーマンス

パフォーマンスの変化についても少し触れておきます。フロントエンドのパフォーマンス計測には Calibre というサービスを利用しており、以下が Calibre での before/after です。

before

f:id:hokaccha:20201127165227p:plain

after

f:id:hokaccha:20201127165251p:plain

これを見てもらうとわかるように、システムを刷新したことで大幅にパフォーマンスが向上しました。特に First Contentful Paint (以下 FCP) が圧倒的に速くなっているのがわかると思います。なお、これは低速回線(上記の数値は 3G 回線相当で計測)で特に顕著で、LTE 相当だともう少し差は小さくなります。

f:id:hokaccha:20201201101339p:plain
きれいな崖ができた様子

元の実装の FCP が遅かったのはシステムをリニューアルする前からわかっていた問題点のひとつで、巨大な CSS や defer できない JS が head で読まれていて、クリティカルレンダリングパスの最適化ができていないのが原因でした。なんとかしようにもどこで読まれているかわからない CSS が大量にあって消すのが難しい、haml(Rails の View)に埋めこまれた JS が head で読まれる JS に依存していて defer できない、などの理由で FCP の最適化が難しい状態でした。

ですので Rails が遅い、Next.js だと速い、というフレームワークの差ではありません。Rails でもスクラッチで書き直してチューニングすれば同程度のパフォーマンスはでます。ただ、Next.js はそういったパフォーマンス最適化をある程度自動でやってくる点においては非常に楽でした。また、Next.js に組み込まれた Web Vitals の計測機能を使って Web Vitals の数値を記録するようにしたのでこれを使って今後も改善を進めていく予定です。特に LCP、TTI(Time To Interactive) あたりはもう少しどうにかしたいですね。

まとめ

Next.js や GraphQL を使ってウェブサイトのフロントエンドのシステムを刷新している話を書きました。今後も適用範囲を広げていき、開発の生産性をあげることでユーザーに届ける価値を最大化していきます。また、今回書いた以外にも、認証やロギング、エラートラッキング、CSS in JS、描画のパフォーマンス最適化、A/B テストなど色々と面白い知見が溜まっているのでまた別の機会に共有したいと思います。

最後に、クックパッドではモダンなフロントエンドの基盤を作っていく仕事や、この基盤を使ってサービス開発する仕事が大量にあります(切実)。もし少しでも興味があればお気軽にお問い合わせください!

*1:CoffeeScript は今回の話とは別のプロジェクトでなくすことに成功しましたがこの話はまた別の機会に。

*2:調べたら現時点で1600以上のルーティングがありました

*3:Pantry を始めとするバックエンドの API サーバーは Ruby なのでそこを変更する必要があれば当然コンテキストスイッチは発生しますが。

*4:GraphQL のサーバーは別。

/* */ @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;*/ /*}*/