コロナ全国調査のためのLINEのメッセージ配信の仕組み

小川拡氏:ここからは、システムの実装以外で調査当日までに行った準備についてお話します。まず調査ページや案内するメッセージの配信について検討しました。調査ページへの流入のほとんどはメッセージからとなるため、調査ページのトラフィックをコントロールする上でメッセージの配信について考えることが重要でした。

前半に紹介したように、本調査では約8,300万人に対して回答をお願いするメッセージを送信しました。メッセージを配信すると、内部的にはすべてのユーザーに同時に届くのではなく、受信するユーザー一人ひとりについて順番に配信や通知が行われます。

この配信ペースによって回答が行われるペースも変化します。(スライドを指して)こちらの図は同じユーザー数に対してメッセージを早く配信した場合と遅く配信した場合の回答数の増加ペースのイメージです。

同じメッセージ数を送信する場合でも、早く配信するとピーク時の回答数増加のスパイクは大きくなります。逆にゆっくり配信すると回答数の増加もゆっくりになり、ピーク時のトラフィックを低く抑えられます。

調査ページ自体も多数のリクエストを処理できるように準備を進めてきましたが、失敗の許されないプロジェクトであるため、万全を期すためにピーク時のトラフィックを抑えたいと考えました。

社内でメッセージを大量に配信する場合には、配信ペースをわざと抑える機能の導入を検討することがあります。しかし今回は特定アカウントの友だちだけでなく、全ユーザーにメッセージを届けるために普段のメッセージ配信システムとは異なる配信システムを利用する必要がありました。そのシステムは配信ペースを抑える機能を持っていませんでした。

パフォーマンステストの効果

メッセージの配信システムを改修することも検討しましたが、今回は配信を手動でコントロールすることにしました。配信対象となるユーザーのリストをいくつかに分割しておき、時間をあけて分割配信をすることで疑似的にゆっくり配信するのに近い状態を作りました。

これにより、一度にまとめて配信した場合と比べるとピーク時のスパイクを小さくできると考えられます。

なお、複数回に分割して配信することでオペレーションミスのリスクは高まります。しかし、今回はシステムを改修するには開発期間があまりにも短かったため、改修によって予期せぬ不具合が生じるほうがオペレーションミスのリスクよりも大きいと判断しました。

調査ページと回答の受付部分については事前にパフォーマンステストを行いました。ただし、時間が限られていたため、システム全体を再現してのテストではなく、1ホストに対してのみ簡易的なテストを実施しました。

その結果、少なくとも秒間1,000リクエスト程度は問題なく処理できることがわかりました。リクエストを処理する部分については単にnginxで静的なファイルを配信しているだけなので、インスタンスの追加に対して性能がスケールするはずです。これを踏まえて必要なサーバー台数を検討しました。しかし、過去に例のない取り組みなので、どの程度のアクセスがあるのかは未知数でした。

1つの目安として過去の大規模なキャンペーンで秒間25,000リクエストの実績があったため、この値の2倍の秒間5万リクエストを基準とし、50インスタンスを事前に用意しました。トラフィックについては先ほどご説明したように、メッセージの配信ペースにも大きく影響されます。

今回はメッセージの配信を手動で行うことにしたため、オペレーションを行うチームと開発チームが連携し、モニタリングを随時しながら配信ペースの調整やインフラの増強を行えるようにしました。

モニタリングにPrometheusを利用した理由

システムのモニタリングには、普段から利用しているPrometheusを利用しました。まず、各サーバーにはnode_exporterなど既存のエクスポーターを導入し、基本的なメトリクスをエクスポートするようにしました。

加えて、サービス固有のメトリクスをいくつか出力するようにしました。ログを検証するバッチの処理状況は、実際に処理を行った際にバッチのプログラム自身が出力するようにしました。MySQLに蓄積されたログの総数などは、MySQLのレプリカに対してクエリを発行することで、集計するようにしました。

これらのサービス固有のメトリクスはエクスポーターとして実装するよりもプッシュするほうが楽だったため、Pushgatewayを介して送るようにしました。Prometheusに集まったメトリクスを基にGrafanaでダッシュボードを構築したり、アラートマネージャでアラートを設定したりしました。

さらにこれは1回目の調査の時点では存在しておらず、あとから実装したものなのですが、PrometheusからAPIで回答数を取得し、LINE Notifyで通知する仕組みも構築しました。

(スライドを指して)こちらがGrafanaのダッシュボードとLINE Notifyでの通知の様子です。Grafanaは主に開発者が確認することを想定しています。ファーストビューで回答数や回答数の増加を表示することで調査が問題なく進んでいるのかを一目でわかるようにしています。気になる点があればスクロールしたりダッシュボードを切り替えたりすることで、詳細を確認できるようにしています。

LINE Notifyは関係者全員のいるグループ内に定期的に通知するようにして、能動的に確認しなくても大まかな状況を把握できるようにしています。簡易的な作りですが、チーム全体で数字を共有でき、またそこからコミュニケーションも発生しました。これらの準備をして調査当日を迎えました。

全国調査当日、大規模なトラフィックをどう処理したか

ここからは調査時のメトリクスを見ながら、当日の様子を紹介します。配信当日の回答状況は(スライドを指して)こちらのようになりました。こちらのグラフは回答数、最初の設問の回答と最終回答を合わせた合計イベント数、そして秒間のイベント数の推移を示しています。

3月31日の午前10時頃に小規模なメッセージ配信を行い、その後、モニタリングや最終確認を経て午後から本格的なメッセージ配信を実施しました。

秒間のイベント数は1つの大きな山のようになっていますが、ところどころで秒間のイベント数が下がっている部分があるように、分割してメッセージの配信が行われています。ピーク時の秒間イベント数は毎秒5,000件近くに達しました。もし分割せずに配信していたら、瞬間的なスパイクはさらに大きくなっていたと思います。

メッセージ配信が終わったあとは急速にイベント数は減少していき、2日間を通じてとくにトラブルなく調査を終わることができました。最終的な回答数はおよそ2,500万件、イベント数は6,600万件でした。

しかし実は裏側では一部の処理で遅延が発生していました。(スライドを指して)こちらは調査1日目におけるIDトークンの検証処理で発生した遅延の推移です。一緒に表示されている秒間イベント数は、先ほどお見せしたものと同じものです。

配信が進むに連れて、秒間イベント数が増加していき、秒間2,000件に達したあたりから遅延が生じ始め、一時的に350万件近くのイベントが処理待ちの状態になりました。

その後イベント数が減少していくに連れて遅延は解消していきました。なお、遅延の起きない程度に配信のペースを落とすことも考えられました。しかし、夜遅い時間にユーザーに通知が飛ぶのを避けたかったことと、LINE自体のトラフィックのピークに重なる可能性もあったことから、遅延については許容して早期に配信を終えることを優先しました。

もし、IDトークンの検証まで含めてユーザーからのリクエスト内で同期的に行っていたとすると、レスポンスが遅延したり、最悪の場合はリクエストを正常に受け付けられなかったかもしれません。今回は非同期処理を導入したことで、準備に時間が避けない中でもユーザーに影響が出ることは回避できました。

シンプルな設計がもたらしたもの

今回の調査を最初に立てた方針から振り返りたいと思います。まず最小限の仕様でシンプルなシステムを作るという点については、機能を絞り込むことでnginxだけでリクエストを処理する実装に落とし込むことができ、高いパフォーマンスと安定性を担保できました。

段階的に開発を進めるという方針の基、最初に目に見える部分を完成させた上で裏側の仕組みを段階的に整備していきました。これにより不確定要素が多い中でも確実にシステムを構築できました。システムトラブルを回避する設計という点では、非同期処理を導入して一部のコンポーネントが意図した通りに機能しなかったとしても全体に影響が出ないように配慮しました。

当日はIDトークンの検証処理が遅延する結果となりましたが、ユーザーに見えるかたちで影響が出ることは避けられました。

まとめです。我々は新型コロナ対策のための全国調査を開発期間3日、仕様策定やQAを含めた準備期間6日で実施しました。

本調査は8,300万人に対してメッセージを送信し、約2,500万件の回答を集める非常に大規模なものでしたが、そのシステムはシンプルなものでした。シンプルな設計にすることで、短期間での開発と多数のトラフィックを安定して処理することを両立することができました。

以上で発表を終わります。ご清聴いただき、ありがとうございました。