サーバーの 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 つの価値が互いに対立し、異なる決定を示唆する傾向があることを納得させられたことを願っています。
残念ながら、この観察だけでは、特定のシステムに対して何をすべきかを教えてくれることはほとんどありません。あるシステムを見て、最初の分析が入力の非効率的な使用を示唆している場合、それが機能不全や悪い設計選択の巣窟であるのか、またはその表面的な非効率が膨大な冗長性や柔軟性、スラックを支えているのかを、より注意深く見ない限り判断できません。私たちはもっと近くを見なければならず、特定のチームや特定の問題におけるドメイン専門知識がほぼ常に必要です。
さらに、時には無料のランチがあります。いくつかの設計選択や決定は、パレートフロンティアを外側にシフトさせ、単にその上を移動するだけではありません。これは、最適化されたシステムでは稀かもしれませんが、その可能性を無視することはできません。そして、多くのシステムはまだそれほど最適化されていないのです!
さらに、設計空間における最適なポイントは、システムによって異なります。時には、信頼性や回復力が非常に重要であり、私たちは大規模な一次的非効率を許容するのが正しいです。しかし、時には、極端な効率が正しい目標であることもあります:おそらく私たちのマージンがそれを唯一の選択肢にするほど薄いか、または私たちのドメインの安定性とシステムへの要求に自信があるため、あらゆる種類の過度な変化に直面しないと感じるかもしれません。
したがって、主に私ができることは、エンジニアやデザイナー、システムの観察者として、このトレードオフとその影響を意識して作業するよう呼びかけることです。システムを無駄で非効率的だと非難するとき、その「無駄」が何を得ているのかを考えるために立ち止まる価値があります。システムを最適化するために出発するとき、現在のシステムの関節や柔軟性がどこにあるのか、どれが重要なのかを理解するために立ち止まり、それらをできるだけ保持するよう努めましょう。システムやチーム、組織のために効率を求める指標や目標を設定するとき、反対の圧力がない限り、私たちはおそらくシステムがより脆弱で壊れやすくなることを求めていることを意識しましょう。