ぺい

渋谷系アドテクエンジニアの落書き

インフラのボトルネックについて知る

インフラのボトルネックを理解する

コードはもちろん、リリースしてから安定して動かせるように面倒を見るまでが仕事というのが、弊社の開発スタイルなので、そこで最近学んだことについて、文献や自分の実体験からボトルネックに関する考え方をまとめてみた。

CPUボトルネック

CPU使用率に対する基本的な考え

CPU使用率が80%から90%をずっと推移している!と聞くと、自分のPCの感覚だと、「やばそう」という感覚に陥りますが、インフラにおいての使用率はそうとも限りません。

  • CPU使用率高い: うまくリソースを使い切っている
  • CPU使用率低い: オーバースペック

ただ、高いCPU使用率にも許容出来る度合いがあったりもするので、そこらへんの判断軸などを踏まえて、まとめてみる。

現実世界の例

CPU使用率が高い状態というのは、実世界に置き換えると、店員がみな忙しく働いているという状態です。利用者からすればオーダーさえ滞りなく通れば文句はなく、経営者もちょうどよい人件費でお店を回せているので、無駄がないと言えます。

システムの例

CPUコアが4つ(店員が4人みたいな)あったとします。それらが全てフル稼働状態だったとします。OSから見た時にCPUの使用率は100%と出ますが、これはネガティブな話ではなく、その逆で、費用対効果が高い状態であり、良い状態と言えます。なぜなら、この状態は他のレイヤーのスループットが非常に高いからです。もし、CPUがボトルネックであったとしても、利用者視点で満足出来る状態であれば問題にはなりません。

なので、CPU使用率が高すぎる!だから良くない!とはすぐに言えず、ユーザーへのレスポンスタイムやスループットに影響がないか?を確認して判断する必要があります。 もし、CPUがボトルネックだったとしても、なぜそうなっているのか?の根本原因を調べることが大事です。

100%は正義ではない

しかし、100%だから効率的だから良い!とも言えません。サービスは成長をします(調子良ければ)。その時に拡張性が担保されている必要はあります。なぜなら、ある日、突然のアクセス増があった時に死んでしまうからです。AWSにはASGという便利な機能があるため、閾値をうまく設定してやれば、突然死ぬことはありませんが、オートスケールは即時制のあるものではありません。(ホットスタンバイとかもありますが)スケールアウトにかかる時間などを加味した上で、上手にリソースを使えるとお金も心にも優しいインフラになると思います。

では、CPU使用率でもよくない高騰した状態ってなんだろう?というところをまとめていく。

待ち行列ボトルネック

ある店舗で、店員が全員忙しく働いているとします。しかし、それでも利用者があまりにも多すぎて、オーダーが通るまで何分も待ち続けるような状態の店があったとします。これはどうでしょう?店員というリソースは完全に使い切っていますが、利用者が明らかにストレスを感じる状況は健全な状態とは言えません。

CPU使用率でも同じことが言えます。処理の方式には待ち行列というものがあります。これはレジの順番待ちのようなものをイメージするとわかりやすいです。CPUの性能に対して、処理待ちのプロセスの質にもよりますが、数が多すぎるという状況になっとします。待ち行列は時間をかければいつか終わりますが、CPUのスループットよりもユーザーからのリクエスト数の方が多いと終わらなくなります。

実店舗などでは、「業務連絡します。3番レジお願いします」などをして、レジを新しくオープンするなどが似たような話です。

システムでは、CPUのコア数を増やして、スループットを高くする。または、プロセスの1処理あたりの時間を短くするなども考えられます。他にはサーバー台数を増やして、並列処理をするようにしたりもできます。

CPUのコア数を増やしたり、サーバー台数を増やしたりするのを、「スケールアウト」と呼んでいます。よく出てくるので覚えておくと話が分かるようになります。

レスポンスボトルネック

スループット問題を解決しても、レスポンス問題が解決するとは限りません。 例えば、ある店舗でのオーダーが、レジ以外にも、商品を作ったり、実際に届けるまでの工程がそもそも時間がかかっていると、店員がどれだけ居ても仕事が回りません。そこで、処理能力そのものをアップさせるという対応を取ることもあります。それが「スケールアップ」と呼ばれているもので、実際には難しいかもしれませんが、店員の処理速度が2倍になれば、処理時間は半分になるので、レスポンスまでの時間が半分になるかもしれません。

処理能力を上げる

CPUにおいては、これをクロック数が先程の店員の処理速度に相当します。CPUのクロック数の単位は「ヘルツ」であり、これは1秒あたりの処理命令数を示しています。 このクロック数を上げることで、一定の効果は得られますが、これには限界があります。どういうことかというと、例えば、店員の一人あたりの単価をアップするよりも(スケールアップする間のダウンタイムは?とかがあったり)、レジの台数を単純に増やすことの方が容易に出来るからです。(拡張性があれば増やすことは簡単なはず) 最近では、スケールアップもオンプレではなく、便利なクラウドがあるので、昔よりは容易なことですが、スケールアップをしたとしても、処理能力を何十倍にも上げるということは難しいでしょう。なによりも、最近のCPUはクロック数にはすごい差があるわけではないので、あまり効果は期待できないケースが多いかもしれません。

並列で処理をする

処理能力はそのままで、処理そのものを分割し、複数のCPUこあに同時処理させる。店員一人が全ての仕事をやるのではなく、それぞれの担当分野を分けるなどです。これで、同時に一人の利用者に対して、複数の店員がいろいろな処理を同時に進めることで、レスポンス時間を短縮するというアプローチです。

この並列で処理するというのは、「並列」、「マルチプロセス」、「マルチスレッド」を使って、一つの処理に対して、CPUコアを複数使うことで、レスポンスタイムの短縮が期待できます。これは、インフラの構成でどうこうというよりも、実際に動いているアプリケーションの改善なので、どういった処理があるのか?などは具体的に掘り下げて、並列化出来るか?をよく検討する必要があります。

CPUの使用率がうまく上がらない

大体のアプリケーションは、CPU使用率が100%に近い状態になることは少なく、ほとんどはCPUから離れたディスクIO、ネットワークIOなどで詰まるケースが多いです。

なぜ、CPU使用率が上がらないかと言うと、例えば、ディスクIOが多い処理があったとします。その場合、システムコールカーネルに命令が行きます。当然その間は、CPUは待ち状態となり、CPUが休んでいる状態なり、CPU使用率が上がりません。ケースにもよりますが、I/O待ちのキューが多くなり、待機するプロセス数が増加したりします。この状態はCPUはぼとるねっくではありません。これはI/O周りのボトルネックとなっている状態です。もし、ディスクのレスポンスに問題がなかったとしても、CPUやメモリ、I/Oなどのリソースがうまく活用されていないと、全体の処理が遅くなることがあります。 

こういった問題の解決には、2つのアプローチがあります。 一つは、同期I/O命令をスレッドごとに命令して、並列実行することで、CPU使用率もI/Oの負荷も上げる。もう一つは、I/Oを非同期化することで、I/O処理の完了待たずに次に進めていく。そうすることで、CPU処理とI/O処理を並行して進めることで、リソースをうまく使う。

メモリボトルネック

僕がやっていた仕事では、まだ遭遇したことがありませんが、いくつかよくあるボトルネックをまとめてみました。

ここでいうメモリとは、「補助記憶装置」ではなく、「主記憶装置」と呼ばれるRAMのことを指しています。 メモリは、CPUの計算結果などを記憶するものです。アクセス速度は補助記憶装置としてよく出てくるHDDやSSDなどに比べると段違いに速いです。全てを主記憶装置と呼ばれるメモリにしないのは、メモリは電気が通っていないと書き込まれていたデータが消えてしまう揮発性のメモリだからです。

スワップによるアクセス速度の低下

メモリに関連して起きる問題は、基本的にメモリ自身に問題が発生するというよりは、周りにしわ寄せが来る印象です。よくあるのがスワップの大量発生によるアクセス速度の低下による全体の処理性能が落ち込む減少です。

スワップとは、メモリの容量が足りなくなった時に、メモリの内容をハードディスクなどの補助記憶装置に移す機能のことです。これによってどういうことが起きるかというと、先にも書いた通り、HDDなどのアクセス速度はメモリと比べると、かなり遅いです。最近ではSSDなどの高速アクセス可能な補助記憶装置も出てきていますが、それでもメモリの方が速いです。 こうなると、発生する問題としては、アクセス時間が長くなり、結果的に処理完了時間が長くなります。

サーバーの状態を見ると、CPU使用率がかなり余裕で、メモリは100%近く使っているといった感じになります。なぜなら、CPUの待ち時間が長くなるので、使用効率が悪くなるからです。こうなると本来の力が発揮できなくなっていると言えます。 メモリがスワップされているかは、freeコマンドを使うと調査ができます。 もし、スワップが大量発生している場合は、メモリリークしているものがないか?とか、メモリに大きなサイズのものを載せすぎでは?などが考えられます。

Linux のメモリー管理(メモリ−が足りない?,メモリーリークの検出/防止)(Kodama's tips page)

その他

  • GC大量発生によるCPU高騰
  • キャッシュメモリアクセス競合によるアクセス速度の低下(まあ、あんま気にすることはない)

書いてて疲れたので、次に行きます。

ディスクI/Oボトルネック

ディスクI/Oは、今までのメモリやCPUに比べると非常に遅いものなります。最近では、SSDなどの高速なディスクもありますが、それでも遅いです。 ディスク周りがボトルネックになるケースは、CPUの使い方を工夫するだけでは、効果があまり上がりません。I/Oの効率が上がるようにするか、I/Oをへらす工夫が必要です。

DBサーバー

ディスクI/Oが発生する先として、以下のような種類のものが、よく出てきます。

  • ローカルストレージ: サーバー内部のディスク
  • SAN(Storage Area Network)
  • NAS(Network Attached Storage)

そして、これらを活用することがあるDBサーバーのボトルネックを見ていきます。

まずは、それぞれを使う場合の通信経路の違いについて

DBサーバー <-> SANスイッチ <-> SANストレージ DBサーバー <-> ネットワークスイッチ <-> NASストレージ DBサーバー <-> 内部ディスク

アプリケーションから見ると、その先で何が使われているかなどを気にする必要はありません。OSカーネル側でリクエストを必要なプロトコルに変換し、ディスクやストレージにリクエストを送ります。確かに動作させる側としては、特に違いを意識する必要はありませんが、インフラを見る側の人間だとこの違いを知る必要があります。

まず、性能の違いについて、これはある程度、前提条件を設定させてもらいます。例えば、ローカルディスクが3~4のディスクでRAID構成を組んでいて、キャッシュにはサーバー上のOSのめもりが利用されているとします。 それに対して、ストレージは何十という単位でディスクを配置してあり、更にキャッシュ専用のメモリ領域が容易されています。ディスクは数によってスループットが向上しますので、外部ストレージのがスループットの面では高い能力を発揮すると言えます。(多くの場合は)

スループットは、同じ領域え利用しているユーザーが多ければ多いほどに低下します。しかし、逆にローカルディスク側はディスクを専有できている点では良いのですが、他のアプリケーション領域と共有している可能性があります。OSのストレージに対しる設定に依存するので、気をつけるべきです。

レスポンスという点では、基本的には近い方が速いので、単体のレスポンスはローカルディスクが最速になります。なので、この差を埋めるために、外部ストレージでは、メモリ領域の活用やデータのキャッシュを効率よく使うなどをして、レスポンス改善に努めています。

今回はスループットやレスポンスという点だけで、ストレージを見ましたけど、実際は対象外性なども入れて考えると、もっと考えるべきポイントが増えてきます。また、さらっと書きましたけど、導入コストの問題を考えたりすると、また面倒だったり・・・(最近はクラウドが便利なので考えること減りましたがw)

HDDやSSDの違いについて知ると、また面白いので良い記事を紹介しておきます。

HDD と SSD の性能とは

ネットワークI/Oボトルネック

ネットワークI/Oのボトルネックはレスポンスタイムのオーバーヘッドに大きく影響します。レスポンス時間の改善をすることは難しいため、そもそも、I/Oが発生する回数を減らすアプローチが有効です。

通信プロセスボトルネック

ネットワーク回線において、帯域というのが重視されることが多いです。帯域が大きいと高速であるわけでないです。 まず、前提として、1つのプロセスで処理をしている場合、高スループットを実現することは難しいです。それは、「データ転送」「通信結果の確認」と言ったやり取りが発生するからです。もし、帯域を使い切ったような通信を行いたい場合、処理を多重化し、並列化する必要があります。そうすることで、通信量が増え、より上限に近いスループットが実現できます。また、そのやり取りに使っているデータを圧縮して、転送量をへらすというのも良いアプローチにはなりますが、圧縮や解答はCPUオーバーヘッドとトレードオフになります。

ネットワーク経由のボトルネック

ネットワークで起きているボトルネックは目には見えにくいです。実際に処理をしているAPサーバーやDBサーバーなどは、レスポンスタイムが長くなった原因が読み取れません。逆に言うと、特に問題が見えない場合は、ネットワーク系で問題が起きているとも考えられます。

例えば、デフォルトゲートウェイがさばけるトラフィックの限界を迎えていた場合、ここがボトルネックとなり、全体の処理効率がある一定のラインで止まってしまうでしょう。

アプリケーションボトルネック

これまでのインフラの「スケールアップ」「スケールアウト」を使うことで、増強や分散が可能とは書きましたが、そもそもアプリケーション側がそれに対応していないとボトルネックになることがあります。

データ更新ボトルネック

特定のデータに依存した処理に対するボトルネック。 どういうことかというと、あるAというリソースにデータ更新をしにいってるプロセスがあるとします。同じAに対するデータ更新したいプロセスが複数あったとしても、同時に更新することは出来ないので、処理待ちが発生します。そのため、どれだけ処理スピードが上がったとしても、データ更新部分でボトルネックとなります。

解決策はいくつかあって、一つは値をキャッシュ化して、DBサーバーへ問い合わせをせずに内部である程度完結させることで、処理効率を上げる。しかし、これだと根本的な解決にはなりません。 一つは毎度DBに問い合わせることをやめて、RDB以外の場所にRDBに適用するべき内容をためておき、それらを精査して、一定タイミングで適用をしていく(結果整合性)。これではデータのリアルタイム性は失われる可能性があります。これが許容出来ない場合は、データベースの機能などを使って行を分割して同時処理を可能にすることや、そもそも表そのものを分けたりなどをしたりすることも考えられます。サービスの質的に何が担保されている必要があるかによってアプローチは変わってきますので、ここはいろいろある。(雑なまとめ)

他にもいくつかありますが、とりあえず、こんなところにする。