サーバーの CPU 使用率の「適切な」レベルは何ですか? よく設計され、適切に運用されているサービスの監視ダッシュボードを見ると、1 日または 2 日間の平均でどのくらいの CPU 使用率を期待すべきでしょうか?
これは非常に一般的な質問であり、単一の答えがあるべきかどうかは明確ではありません。とはいえ、長い間、私は一般的に「高い方が常に良い」と信じていました:私たちはできるだけ 100% の使用率に近づくことを目指すべきです。なぜでしょうか? それは、100% 未満は未使用のハードウェア容量を示し、リソースを無駄にしていることを意味するからです。サービスが CPU を最大限に活用していない場合、より小さなインスタンスに移動するか、そのノードで追加の作業を実行することができます。
この単純な直感は、実際にはほとんど正しくありません。
理想的な状態に達し、サービスが 100% の使用率に近い状態で動作しているとしましょう。もし予期せずバイラルになり、予期しないトラフィックの急増を受けた場合、どうなりますか? または、各リクエストで少し余分な CPU を必要とする新機能を展開したい場合はどうでしょうか?
100% の使用率に達している場合、何かが発生して負荷が増加すると、私たちは困難に直面します! 100% の使用率で運用していると、増分の負荷を吸収する余地がありません。私たちは何らかの形で劣化するか、緊急のキャパシティを追加するために慌てるか、またはその両方をしなければなりません。
この単純な例は、非常に一般的な現象の一例です:効率の改善はしばしばレジリエンスとトレードオフの関係にあり、システムを最適化すればするほど、このトレードオフは悪化する傾向があります。
あるポイントを超えると、システムをより効率的にすることは、レジリエンスを低下させることを意味し、逆に、堅牢性を構築することはシステムを効率的でなくする傾向があります(少なくとも短期的には)。これは、ウィン / ウィンがないわけではありません;時には、パレートフロンティアを外側に移動させることが可能です。「愚かな」パフォーマンスバグを修正することが時にはこの効果を持つことがあります。しかし、あるレベルの努力を超えると、トレードオフを強いられることになります。
ここでの「レジリエンス」とは、「信頼性」や「稼働し続ける能力」だけでなく、より広い意味での「変化に対応する能力」を指します。これは、バグや障害の変化だけでなく、製品のニーズの変化、市場の変化、組織やチームの構成の変化など、あらゆる種類の変化を含みます。
このトレードオフの例#
このトレードオフは、キャパシティプランニングを超えた領域で発生します。ほぼすべての技術システムや組織のほぼすべてのレベルに適用されます。私が観察した他の場所をいくつか挙げます:
冗長性#
サービスの複数のインスタンスを実行し、いずれかのインスタンスが失敗した場合に負荷が他のインスタンスに透過的にルーティングされるようにロードバランサーを設定することは非常に一般的です。洗練された組織では、このパターンがデータセンター全体のレベルで適用され、データセンター全体が失敗してもその負荷が他のデータセンターにルーティングされるアーキテクチャが存在します。
これが機能するためには、各インスタンスがフェイルオーバーしたインスタンスからの増分負荷を吸収するのに十分な余剰容量を持っている必要があります。定常状態では、障害がない場合、その容量はアイドル状態であるか、せいぜい、瞬時にドロップできる低優先度の作業を処理している必要があります。冗長性を多く求めるほど、定常状態でアイドルにしておかなければならない容量が増えます。
最適化#
洗練されたパフォーマンス最適化は、問題領域の特定の特性や構造を利用することで機能することがよくあります。不変条件をデータ構造やコードの組織に組み込むことで、膨大なパフォーマンスを得ることができます。しかし、特定の仮定に深く依存すればするほど、それらを変更するのが難しくなり、重度に最適化されたコードは進化させたり機能を追加したりするのが非常に難しくなります。
具体的な例として、Sorbet についての私の考察では、型推論をローカル専用かつ単一パスにすることを決定し、その仮定を私たちのコードとデータ構造に組み込みました。この選択は大幅な効率向上をもたらしましたが、ある意味でシステムをより壊れやすくしました:競合する型システムが持つ多くの機能は、これらの仮定のために Sorbet のコードベースでは実装が不可能または非常に困難です。私はこのプロジェクトにとって正しい選択だったと確信していますが、トレードオフを認識することは重要です。
Hillel Wayne は、同様の特性を「賢いコード」と呼び、次のように定義しています。
問題に関する知識を利用するコード。
彼もまた、この意味での賢いコードは効率的であるが、時には壊れやすい傾向があると述べています。
シリアル化フォーマット#
「メモリ内のstructs
を直接ディスクにコピーする」よりも効率的なシリアル化フォーマットを見つけるのは難しいです;ほとんどコードは必要なく、データを保存または読み込むためのシリアル化コストはほぼゼロです。
しかし、これにより壊れやすいシステムが生じます – 新しいフィールドを追加するだけでもデータを書き換えたり、特別な処理が必要になります。そして、異なるエンディアンやワードサイズを持つマシン間でデータを共有することは課題となります。
逆に、すべてのデータを JSON や類似の一般的なコンテナとして書き込むことで、新しいフィールドを追加したり、新しいモジュールが互いに通信したりするための無限の柔軟性が生まれますが、既存のコードに影響を与えずに行うには、ワイヤ上での大幅なオーバーヘッドと CPU の作業が必要になります。
分散システム#
私のお気に入りのシステム論文の一つは、COSTという論文で、いくつかのビッグデータプラットフォームを調査し、それらの多くが利用可能なハードウェアに対して(ほぼ)線形にスケールする望ましい特性を持っていることを観察していますが、調整された単一スレッドの実装よりも_ばかげた_ほど効率が悪いというコストを伴っています。
私はこれが一般的なトレードオフであることを発見しました。分散計算フレームワークは、スケールアップによってほぼ任意のワークロードを処理できる柔軟性とレジリエンスを持っています。誰かが非効率的なコードをデプロイしても、スケールアウトによって対応でき、ハードウェアの障害も透過的に処理できます。もっとデータを処理する必要がありますか? ただハードウェアを追加するだけです(しばしば透過的に、何らかのオートスケーリングを使用して)。
逆に、慎重にコーディングされた単一ノードのソリューションは、より速くなる傾向があります(時には 10〜100 倍速く!)、しかしはるかに壊れやすいです:データセットがもはや 1 つのノードに収まらない場合、または 10 倍高価な分析を実行する必要がある場合、またはチームの新しいエンジニアが知らずに厳しい内部ループ内に遅いコードをコミットした場合、システム全体が崩壊するか、仕事を遂行できなくなる可能性があります。
小規模チームと大規模組織#
小規模チーム – 一人の人の「チーム」を含む – は非常に生産的で効率的です。チームが小さいほど、コミュニケーションのオーバーヘッドが少なく、各エンジニアの頭の中に豊かな共有コンテキストを保持するのが容易になります。ドキュメントを書く必要が少なく、変更についてコミュニケーションする必要が少なく、新しいメンバーのオンボーディングやトレーニングにかかる時間も少なくなります。小規模チームは、「慎重に考え、努力する」という戦略を使用して、大規模チームよりもはるかに遠くまで進むことができ、リンターや慎重な防御的抽象設計のようなツールに依存する必要が少なくなります。
適切な状況下では、慎重な設計と経験豊富なエンジニアを持つ小規模チームが、サイズが 10 倍のチームの生の出力にほぼ匹敵することが可能です – 効率の大幅な改善です!
しかし、小規模チームは、組織、技術の状況、またはプロジェクトのビジネスニーズの変化に対してはるかに壊れやすく、レジリエンスが低いです。4 人のチームから 1 人が離脱すると、バンド幅の 25% が失われます – さらに悪いことに、チームは新しいメンバーを雇い、オンボーディングする練習がほとんどなく、膨大な知識やドキュメントが残りのメンバーの頭の中にしか存在しません。
同様に、ビジネスの焦点の変化や新製品の立ち上げには、チームのシステムから多くの機能や他の開発が必要となるため、チームがそれをサポートする能力を超えるのは比較的容易であり、チームを迅速に成長させることは同じ理由で課題となります。
自動化と人間のプロセス#
一般的に、機械がタスクを実行する方が、人間が手動で実行するよりも効率的です – より安価で、迅速で、しばしばより信頼性があります。
しかし、人間は無限に適応可能であり、機械(物理的な機械とソフトウェアシステムの両方)ははるかに壊れやすく、固定されています。人間がループにいるシステムは、状況の変化や予期しないイベントに即座に対応するための選択肢がはるかに多くなります。
同じ人間をすべて保持しながら、彼らの仕事の一部を加速するために自動化を追加しても、私たちは自動化依存のリスクを抱えます。これは、人間が自動化に過度に依存し、不適切に信頼するようになったり、自動化なしで機能する能力が萎縮して、必要なときに「手動」で適切に介入できなくなることを意味します。
スラック#
これらの特定の観察の多くは、実際にはスラック(ソフトウェア製品ではなく)に関する観察です。
健全なスラックを持つシステムは – 少なくとも短期的には、単純な分析において – 定義上非効率的です:そのスラックは「アイドル」になっている時間やリソースであり、代わりに出力を生み出すことができたはずです。
より広い視点で見ると、そのスラックはレジリエンスの鍵です:システムに「余裕」があることで、小さな混乱や不具合に対処できます。開発者やオペレーターは、そのスラックを利用して予期しない負荷を処理したり、根本的な問題を解決したりすることができます。
スラックのないシステムは、機能している限り効率的ですが、壊れやすく、通常の運用モードに何らかの変更が加わるとすぐに崩壊します。
結論#
私は、効率と信頼性が互いに対立し、トレードオフまたは少なくとも対立する方向に圧力を生み出す具体的な例に触れようとしました。これが広範な現象であり、厳密なトレードオフが存在しない場合でも、これらの 2 つの価値は互いに対立し、異なる決定を示唆する傾向があることを納得させられたことを願っています。
残念ながら、この観察だけでは特定のシステムに対して何をすべきかを教えてくれることはほとんどありません。あるシステムを見て、最初の分析が入力の非効率的な使用を示唆している場合、それが機能不全や悪い設計選択の巣窟であるのか、それともその表面的な非効率が膨大な冗長性、柔軟性、スラックを支えており、将来の変化に耐えることができるのかを詳しく見ない限り判断できません。私たちはもっと詳しく見る必要があり、特定のチームや特定の問題に関するドメインの専門知識がほぼ常に必要です。
さらに、時には無料のランチがあります。一部の設計選択や決定は、パレートフロンティアを外側にシフトさせ、単にその上を移動するだけではありません。これらは、最適化されたシステムでは稀かもしれませんが、その可能性を無視することはできません。そして、多くのシステムはまだそれほど最適化されていないのです!
さらに、設計空間における最適なポイントはシステムによって異なります。時には、信頼性やレジリエンスが非常に重要であり、私たちは大規模な一次的非効率を容認するのが正しいです。しかし、時には、極端な効率が正しい目標です:私たちのマージンがそれを唯一の選択肢にするほど薄い場合や、私たちのドメインの安定性やシステムに対する要求に自信を持っている場合、過度に急激な変化に直面しないと感じる場合です。
したがって、主に私ができることは、エンジニア、デザイナー、システムの観察者として、このトレードオフとその影響を意識して作業するよう呼びかけることです。システムが無駄で非効率的であると非難するとき、その「無駄」が何をもたらしているのかを考えることが重要です。システムを最適化しようとするとき、現在のシステムの関節や柔軟性がどこにあるのか、どれが重要であるのかを理解するために一時停止し、それらをできるだけ保護するよう努めましょう。システムやチーム、組織の効率を求める指標や目標を設定するとき、反対の圧力がない限り、私たちはおそらくシステムがより壊れやすく、脆弱になることを求めていることを意識しましょう。