SREの現場から

LINEの「あけおめLINE」過負荷対策(2) ― 「発生可能性の低減」おけるボトルネックの設計

第1回では過負荷対策におけるリスクマネジメントの全体像、そして「発生可能性の低減」に関する全般的な説明をしました。今回はその「発生可能性の低減」のなかでもボトルネックの設計について、私たちが実際に行なっている方法をご紹介します。


少ないエンジニアリソースで複数のサービスのボトルネックを解析し、キャパシティプランニングを行うのは非常に難しいことです。そのため、私たちはボトルネックを解析する代わりに、ボトルネックを設計することにしました。具体的には、ほぼすべてのAPIサーバーにおいて、CPUリソースが最初に枯渇するようにチューニングしています。これにより、サーバーのキャパシティプランニングではCPUリソースの枯渇具合のみを気にすればよくなります。今回はこの具体的な手順をご紹介します。

そもそも、アクセス過多時のボトルネックを設計するためのもっとも単純で効果的な方法は、実際にストレステストを行い、その結果を細かく分析することです。しかしながら、ストレステスト環境を準備したり、実際のユーザーのリクエストを再現したシナリオを準備したりすることには大きな労力が必要です。特にキャッシュ機能があるサービスでは、同じようなリクエストを繰り返すと理想的すぎる試験結果になってしまいますし、ランダムにリクエストパラメータを分散させると、逆にキャッシュヒット率が悪くなりすぎて参考になるデータを取得できません。また、私たちは複数のマイクロサービスを管理しているため、すべてのマイクロサービスでストレステストと分析、そしてチューニングを行うことは限られたエンジニアリソースでは現実的にできません。

そのため、私たちはより少ない労力で致命的なボトルネックを見つけることを目標に、⁠カオスエンジニアリング」と簡易的な外れ値判定を利用することにしています。

カオスエンジニアリングによるボトルネックの検出

ユーザーアクセスを再現するシナリオをマイクロサービスごとに作成して実行するのは非常に手間がかかるため、実際のユーザーアクセスを特定のサーバーへ集中させて過負荷を発生させることにしました。このように本番環境で実際に障害を注入して行う実験は「カオスエンジニアリング」と呼ばれています。カオスエンジニアリングはNetflix社が提唱した概念で、複雑なシステムに向き合うための原則と手法を私たちに提示してくれます。より概念的な説明やカオスエンジニアリング発展の歴史については、⁠カオスエンジニアリング』[1]という書籍を参照してください。本項では、私たちが実際に行っている過負荷を再現するカオスエンジニアリングの手法や工夫を紹介します。

カオスエンジニアリングの3段階

カオスエンジニアリングにおいては、まず「定常状態による振舞いの仮説」を立てたうえで、そのシステムにおける異常事態、すなわち「何が発生したら?」を仮定します。最後に、それらにもとづいた実験と検証を行います。

「定常状態における振る舞いの仮説」とは、今回の事例でいえば「あるAPIサーバーは99%のリクエストを500ミリ秒以内に返す」といった仮説です。また、今回の高負荷時のボトルネック検出のために、「CPU使用率が70%までは定常の振る舞いを維持できる」という仮説も追加して立てました。

「何が発生したら?」の仮定はどうでしょうか。今回の事例では最終的にアクセス過多を再現できればよいため、「APIサーバーが次々とダウンしたら」を仮定しました。本来は「トータルアクセス数が増えること」を仮定できればよいのですが、前述の通りストレステストの準備が難しいためサーバーダウンを採用しています。

以上より、本番環境でサーバーダウンの障害を再現する実験を行い、障害発生時の挙動が事前に立てた仮説を採択するか棄却するかを検証することになります。

実験と検証の実施

実際にサーバーをいくつも落としていくと、トラフィックが生き残ったサーバーに集中し、CPU使用率が増えていきます。その際のエラー率やレイテンシを注視しつつ、⁠CPU使用率が70%になるまで定常状態を維持できるか」を確認します。

この実験をすべての主要なマイクロサービスで実施していきます。今回の事例では、1つのマイクロサービスあたりおよそ1〜2時間ほどの時間がかかりました。この際、なるべく他の障害と本実験のタイミングがぶつかることがないように、アプリケーションのリリースがないタイミングで実施しています。万が一実験の途中で予期せぬエラーやレイテンシ上昇が発生した場合は、すぐさま実験を中止し、ダウンしていたサーバーを復帰させます。

実験の結果を見てみると、リクエスト数が増えるに従いCPU使用率が上昇し、最終的にはCPU使用率が70%に到達していることがわかります図1⁠。他のボトルネックが存在する場合、CPU使用率が上がりきる前にエラーやレイテンシが発生することもありますし、ヒープメモリが不足した場合は、ガベージコレクション(GC)によりCPU使用率が急激に上昇することもあります。

また、この結果からCPU使用率70%時のサーバー単位のクエリ/秒(1秒あたり処理できるクエリ数、QPS)も計測できます。このクエリ/秒を1サーバーあたりの上限として第3回で紹介するキャパシティプランニングで利用します。

図1 実験結果の一例
図1

そして、こうして得られた結果の中から、仮説が棄却された(CPU使用率が70%に届く前に問題が顕在化した)マイクロサービスに対して、プロファイラーなどを用いてさらに深くボトルネックを調査していきます。

なお、仮に仮説が採択された場合でも、より詳細な調査を必要とすることがあります。たとえば、CPUより先にヒープメモリが枯渇しているケースではGCの頻度が上がり、CPUリソースの消費が加速します。そういった場合は、CPU使用率が70%を超えるまで問題なくともチューニングすべきケースといえます。

平常時のアクセスが小さすぎるマイクロサービス

サーバーを落としていき、負荷を集中させる実験を行っていくと、サーバーが残り1台になってもCPU使用率が70%に届かないサービスがあることに気がつきます。

今回の事例においては、スタンプを購入する際の決済に関するマイクロサービスがそうでした。このマイクロサービスはキャンペーンのタイミングでアクセスが増える一方で、平常時はそこまで集中したアクセスがありません。そのためサーバー台数を1台にしてもCPU使用率が70%に届かなかったのです。

そういったサービスでは無理に70%を目指さずに終了することもあります。しかし、処理キャパシティの見積もりをより正確にしたいケースでは、積極的にサーバースペックを落としていくようにしています。たとえば、8vCPUのサーバー4台から4vCPUのサーバー8台へ変更するなどです。これにより、より精緻な見積もりが可能となり、過負荷の障害発生リスクを減らすことができます。

Zスコアによる外れ値判定を用いた問題のある可能性があるマイクロサービスの抽出

ここまでの実験でボトルネックがCPUでないサービスの検出は完了しています。しかし前述のとおり、GCによってCPU使用率が上昇するようなケースではCPU使用率70%までエラーなく到達することが多く、先ほどの実験だけではこれを検出できません。

担当するサービス数が少なければ、さまざまなグラフをすべてのサービスで確認し、GCの頻度やメモリプレッシャーについても総合的に判断して分析する価値があるでしょう。しかし、私たちは多くのサービスを管理しているため、すべてのサービスで多角的な分析を行うことはエンジニアリソースの観点から困難です。そのため、詳細な分析をするべきサービスを絞り込むために統計的な処理を用いています。

たとえば、クエリ/秒とCPU使用率の関係は、定数1で比例している状態が理想です。アクセス数が2倍になったとき、CPU使用率も2倍となっているのが理想ということです。前述のカオスエンジニアリングを通じて、すべてのマイクロサービスについてクエリ/秒とCPU使用率の最小値および最大値を取得できています。そこでこの数値を利用し、すべてのマイクロサービスに対して下記の計算を行い、比例定数を算出することにしました。

max(CPU使)/min(CPU使)max(QPS)/min(QPS)

今回は例として9つのサービスを対象に、詳細な分析をするべきサービスを絞り込んでみましょう表1⁠。ここで、⁠CPU変化率」は上記のmax(CPU使)/min(CPU使)を、⁠QPS変化率」は上記のmax(QPS)/min(QPS)を表しています。

表1 9つのサービスの実験結果
サービス min
(CPU
使用率)
max
(CPU
使用率)
CPU
変化率
min
(QPS)
max
(QPS)
QPS
変化率
CPU変化率/
QPS変化率
サービスA 8 16 2.0 1100 2900 2.6 0.77
サービスB 15 70 4.7 260 5660 21.8 0.22
サービスC 18 74 4.1 100 1540 15.4 0.27
サービスD 2 9 4.5 17 110 6.5 0.69
サービスE 15 82 5.5 500 1500 3.0 1.83
サービスF 19 80 4.2 1600 5050 3.2 1.31
サービスG 10 74 7.4 1500 2015 1.3 5.69
サービスH 10 64 6.4 130 2800 21.5 0.30
サービスI 13 71 5.5 450 2950 6.6 0.83

表1の「CPU変化率/QPS変化率」の列からは、サービスGのみがアクセスの増加率に対するCPU使用率の増加率が明らかに高いことが見てとれます。したがって、サービスGをプロファイラーなどのツールを使って分析すれば、処理性能のキャパシティを改善できるでしょう。

ただしこのとき、⁠サービスGが外れ値である」という判断が主観にすぎないことに注意してください。分析するエンジニアによってはサービスBも外れ値であると判断するかもしれませんし、逆に忙しい時期にはついつい見なかったことにしてしまう可能性もあります。このように外れ値であることの明確な基準がない場合にはレビューでも指摘がしづらく、ひいては小さな議論のもととなり、不必要な議論はタスク進行を阻害します。

こういった問題を避けるため、私たちは簡易的な方法で外れ値を判定しています。この外れ値の判定はおおよそ人の直感と一致しますので、わざわざ行うべきか疑問に思う方もいるかもしれません。筆者の意見としては、外れ値の判定を明確な基準値として利用するかどうかはサービスやエンジニアの規模によって判断すればよく、特に必須の作業ではありません。

Zスコアを用いた外れ値判定の実施

ここで紹介する外れ値判定の方法は「Zスコア」または「標準得点」を用いた方法として知られています。この方法では、データが正規分布に従うと仮定したときに「計測された値が平均値からどれだけ離れているか」また「その値がどれくらいの確率で発生するのか」を確認します。正規分布を前提とした判定手法ですので、本来の分析での利用には非常に注意が必要です。今回は、限られたエンジニアリソースを有効に活用するために分析対象とすべきサービスを絞り込むことが目的であり、仮に分析対象を見逃しても他のリスク対応で十分カバーできるとして採用しています。

まずは、表1の「CPU変化率/QPS変化率」について、平均値μと標準偏差σを計算します。ここでは平均値μが1.32、標準偏差σが1.71となりました。

そして各値がどれだけ平均から離れているかを計算します。具体的には、μ±(×σ)の範囲内に含まれているかを確認します。正規分布において、μ±2σの区間に入る確率は約95%であるため、ここでは係数を「2」としました(たとえばこの係数を「3」にすると、μ±3σの区間に入る確率は約99.7%となります⁠⁠。この範囲の上限と下限はそれぞれ以下のとおりです。

1.32+21.71=4.741.3221.71=2.1

サービスBの「CPU変化率/QPS変化率」は0.22ですので、この範囲内に収まっています。一方でサービスGは5.68ですので、外れ値と言えそうです。

この方法によって、今回の例ではサービスGのみが外れ値であったことがわかりました。ここでこの方法を終了させてもよいですし、まだエンジニアリソースに余裕がある場合は、サービスGを取り除いたうえで改めて平均と標準偏差を計算しなおし、この方法で他に外れ値がないかを確認してみるのもよいでしょう。

分析とチューニングを行う

前述の方法によって、9サービスあるうち1サービスについてのみ、より詳細な分析とチューニングを行うべきということがわかりました。あとはサービスの特性によって、必要な分析やチューニング、負荷試験などを実施しましょう。

私たちも実際に今回の紹介した方法を用いて高負荷時にヒープメモリが不足するサービスを発見し、より大きなメモリのサーバーにリプレイスすることでサーバー台数を半減させられました。

今回のまとめ

今回は、過負荷による障害の発生可能性の低減のために、処理キャパシティの計測およびボトルネックの設計について紹介しました。

もちろん、すべてのサービスに対して十分なエンジニアリソースを投資して負荷試験を行い、パフォーマンス改善や性能上限の確認を行えることが理想でしょう。しかし現実的には、コストや時間の制約のなかでベターな結果を得る必要があります。今回紹介したのはそのために私たちが行なっている工夫であるとも言えるでしょう。

次回は、同じく「発生可能性の低減」を目的とした追加するサーバー台数の見積もり方法やマージンの考え方について、掘り下げて紹介していきます。

おすすめ記事

記事・ニュース一覧