3 ポイント 投稿者 GN⁺ 2023-12-08 | 1件のコメント | WhatsAppで共有
  • FLAME は、既存アプリケーションコードの一部を関数で包み、一時的なアプリケーションのコピー上で実行させることで、コードを別ランタイムへ移さずにきめ細かな弾力的スケーリングを狙う
  • 従来の FaaS 方式では、動画サムネイル生成のような作業でも、HTTP、S3、SQS、API Gateway、エンコーダー/デコーダー、ワークフローオーケストレーションが増えていく構成になり得る
  • Elixir の flame library は FLAME.call で関数実行をリモートランナーに委譲し、Fly.io の FLAME.FlyBackend は同じ Docker イメージで新しい Fly Machine を起動して、約3秒以内に親ノードへ接続する
  • 開発・テストでは LocalBackend で同じランタイム上で実行し、本番では minmaxmax_concurrencyidle_shutdown_after によって scale-to-zero と短時間の hot 状態維持を調整する
  • FLAME はジョブキューをなくすというより、耐久性保証と弾力的実行を分離し、キューには dispatch・commit・retry を担わせ、CPU 集約的な作業は FLAME 呼び出しの中で処理させる

FLAME パターンが狙う問題

  • 弾力的な自動スケーリングはサーバー管理の負担を減らし、利用量ベースのコストを約束するが、FaaS を使うと、別個のキュー、ストレージ、グルーコード、開発・テスト・CI の複雑さも一緒に増えることがある
  • FLAME は Fleeting Lambda Application for Modular Execution の略で、アプリケーション全体を lambda のように扱い、一部のモジュール作業だけを短時間起動するインフラ上で実行する方式である
  • 目標は次の3つに整理できる
    • fly deploygit push herokukubectl のような既存のデプロイフローでサーバー管理を減らす
    • アプリケーションコードの特定部分だけを オンデマンドできめ細かくスケールさせる
    • アプリケーションを書き直したり、コードの一部を独自ランタイムへ移したりしない

FLAME.call で既存関数を移す

  • 例は、Elixir アプリケーションでアップロードされた動画を ffmpeg でサムネイルへ変換する generate_thumbnails 関数である
  • 既存関数は一時ディレクトリを作成し、ffmpeg を実行した後、生成されたサムネイルを耐久性のあるストレージに保存し、URL を DB に Repo.insert_all で記録する
  • CPU 集約的な動画トランスコーディングは本番環境でサービス全体を停止させる可能性があるため、コード全体を FaaS やマイクロサービスへ移すのではなく、関数本体を FLAME.call(MyApp.FFMpegRunner, fn -> ... end) で包む
  • FLAME.callランナープール名と関数を受け取り、アプリケーション全体の新しいコピーを見つけるか起動したうえで、その関数だけを実行する
    • 関数がクロージャとして捕捉した %Video{} 構造体や interval のような変数は自動的に渡される
    • FLAME ランナーは起動後に親ノードへ接続し、実行する関数を受け取ってから結果を呼び出し元へ返す
    • 設定に応じて、ランナーは追加作業を待ってから idle 状態で停止するか、即時終了する
  • DB 接続を含むアプリケーション全体が実行されるため、元のコードと同じように Repo.insert_all をそのまま使える

FaaS の複雑さと FLAME の違い

  • FaaS は問題を解くための構成要素を提供するが、FLAME は別個の通信レイヤーそのものを減らすアプローチに近い
  • 動画サムネイルの例では、単純な AWS Lambda Function URL から始めても、すぐに複雑さが生じる
    • HTTP でサムネイルをアプリへストリーミングし直すには、両側にカスタムのエンコーダーとデコーダーを書く必要がある
    • 動画トランスコーディングやアップロードが15分を超えると、Lambda のハードタイムアウトのため、動画をチャンクに分け、さらに多くの Lambda を使う必要がある
    • ワークフローオーケストレーションと、SQS、S3 のような追加サービスが必要になる
  • FaaS ベースの実装は通常、次のコストポイントを生む
    • HTTP endpoint、S3、API Gateway による Lambda トリガー
    • 動画トランスコーディング用のカスタム Lambda 作成
    • サムネイル結果を SQS に保存
    • アプリ側の SQS consumer 作成
    • DB に保存し、別インスタンスに接続しているアクティブ購読者へイベントを送り返す方法の構成
  • FLAME はアプリケーション内部のコードと既存の DB、PubSub、プラットフォーム機能をそのまま使えるようにするため、別サービスや結果回収用ストレージを減らせる

Fly.io バックエンドとローカル実行

  • Elixir の flame library は FLAME パターンの実装であり、デフォルトで LocalBackendFlyBackend を提供する
  • FLAME.FlyBackend は Fly.io インフラ上で新しい Machine にアプリケーションのコピーを起動し、約3秒以内に親ノードへ接続して作業を受け取れる
  • Fly.io はアプリケーションをパッケージ化された Docker イメージとして実行するため、Fly API に現在のアプリと同じイメージを持つ新しい Machine の起動を要求する
  • Fly.io インフラでは、FLAME ランナーを親と同じリージョンで開始できるため、親とランナー間のレイテンシを減らせる
  • FLAME.FlyBackend はドキュメント込みで 200 LOC 未満 であり、ライブラリ依存は HTTP クライアント req ひとつだけである
  • 開発とテストでは LocalBackend により、ノート PC や CI サーバーの既存ランタイム上で該当コードを実行する

ファイル転送と開発・テストの単純化

  • FLAME はアプリケーションの外にコードを書かずに、ビジネスロジック、DB 設定、PubSub、プラットフォーム機能を再利用できる
  • Elixir では Erlang VM の分散機能により、親ノードのファイルストリームをリモート FLAME アプリケーションへ送れる
    • 親ノードで動画パスのファイルストリームを開く
    • FLAME 子側で一時ファイルストリームを開き、親ストリームをコピーする
    • その後 ffmpeg はリモートランナーの一時ファイルを入力としてサムネイルを生成する
  • この方式では、S3 や HTTP インターフェイスを別途設定しなくても、ファイルを FLAME サーバーへ渡せる
  • 別サービスのデプロイ、endpoint 管理、S3/SQS を通じた結果回収、開発・テスト・CI 依存関係の構成が減る

Elixir 以外での FLAME

  • Elixir はプロセス監督と分散メッセージングを提供するため FLAME モデルによく合うが、妥当な並行性プリミティブを持つ言語でもこのパターンを使える
  • JavaScript の概念実証例は、Fly Machine 上でアプリケーション関数を別のマシンで実行する形である: fly-run-this-function-on-another-machine
  • JavaScript ベースの FLAME 呼び出しの一般的な流れは、モジュール実行部分を新しいファイルへ移し、ランナープールで実行する方式である
  • 引数が JSON シリアライズ可能であれば、全体の流れは Elixir の例と同様に、アプリケーションコードが短時間起動するインスタンス上で実行される
  • 完全な FLAME ライブラリは次を処理する必要がある
    • 弾力的なプールの scale-up と scale-down ロジック
    • hot startup と cold startup を考慮したプール管理
    • orphaned resource 防止のためのリモートランナー監視
    • デプロイを最新状態に保つ方法

バックグラウンドジョブキューとの関係

  • FLAME はバックグラウンドジョブ処理器の中でも動作するが、ジョブキューと役割が重なる部分がある
  • ジョブキューは通常、耐久性保証が必要なときに使われ、キューが負荷変化に合わせてより多くのジョブを処理するよう調整できる
  • 耐久性のあるジョブと弾力的実行は別々の関心事である
    • キューを単なる offload 実行用途としてだけ使うと、ジョブにデータを入れ、結果を呼び出し元やユーザーデバイスへ戻すグルーコードが必要になる
    • 動画アップロード後のサムネイル生成成功を保証する必要があるなら、キューが dispatch、commit、retry メカニズムを担える
    • 実際のトランスコーディングはジョブ内部の FLAME 呼び出しで実行し、耐久性とスケール実行を分離できる
  • 保存前の動画プレビューや、ユーザーがすでにアプリを離れた後の ML モデル実行のように、耐久性が不要な作業は、耐久性ストレージにジョブを書き込む構成に合わないことがある

弾力的スケーリングのためのランナープール

  • Elixir FLAME 実装はランナーの 弾力的プールを定義し、scale-to-zero と同時実行数制限を併せてサポートする
  • 設定例では FLAME.Pool をアプリケーションの start/2 に追加する
    • min: 0 で scale-to-zero を許可
    • max: 10 で最大10個のランナーを起動
    • max_concurrency: 5 でランナーごとに5個の ffmpeg ジョブをサポート
    • idle_shutdown_after: 30_000 で呼び出しジョブが30秒なければ idle down
  • FLAME 親の有無に応じて Phoenix Web サーバーは条件付きで起動する
    • Web トラフィックを処理しない FLAME ランナーでは Web サーバーを起動する必要がない
    • DB のような MyApp.Repo は FLAME ランナー内部で使う必要があるため、そのまま残す
  • min: 1 を指定すると、アプリケーション起動時点から少なくとも1つの ffmpeg ランナーを hot 状態で維持できる

ステートフルなプロセス配置

  • Elixir アプリケーションのステートフルな部分は、メッセージ mailbox を持つ軽量プロセスプリミティブを中心に構成される
  • FLAME.callFLAME.cast は比較的 stateless なコードに適しており、FLAME.place_child は既存のプロセス仕様をローカルではなく FLAME ランナー上で開始させる
  • FLAME.place_childTask.Supervisor.start_childDynamicSupervisor.start_child のようなインターフェイスを使う箇所で利用できる
  • LiveView アップロード中のサムネイル生成例では、アップロード chunk を ThumbnailGenerator プロセスへ渡し、そのプロセスが ffmpeg と通信する
    • ffmpeg stdout から PNG delimiter を見つけると、LiveView プロセスへ画像メッセージを送る
    • LiveView は handle_info でメッセージを受け取り、UI に新しい画像を追加する
  • 既存の DynamicSupervisor.start_child(@sup, spec) 呼び出しを FLAME.place_child(Thumbs.FFMpegRunner, spec) に置き換えると、ThumbnailGenerator プロセスが FLAME ランナーで実行される
  • プロセスは場所に関係なくメッセージをやり取りできるため、アップロード終了やブラウザータブ終了でプロセスが終わると、FLAME サーバーは終了を検知し、他の作業がなければ idle down する

リモート監視と失敗処理

  • 短時間だけ起動するインフラには orphaned resource を防ぐための failsafe が必要である
  • 親がランナーを立ち上げると、ランナーは作業がないときに自ら idle down する必要があり、親ノードとそれ以上連絡できなくなった場合は failsafe shutdown を処理する必要がある
  • 新しいデプロイで親が置き換えられるときは、クラスター内で同じコードを実行していることを保証するため、ランナーも終了しなければならない
  • ランナーのジョブ結果を待つアクティブな呼び出し元は、ランナーが何らかの理由で落ちる状況を考慮する必要がある
  • Erlang VM が提供するプリミティブにより、この実装は単純になる
    • ローカル・リモートプロセスの監視と supervision
    • ノードの起動・停止を検知する node monitoring
    • アプリケーションの起動・終了順序を制御し、新しいデプロイ時にアクティブなランナーがジョブを終える時間を与える shutdown フロー
  • 内部実装の詳細は別記事で続く予定であり、現在は flame source を確認できる

現在の状態と次のステップ

  • Elixir FLAME ライブラリはまだ初期段階だが、今すぐ試すことができる
  • 今後、より高度なプール成長手法と Elixir での実装方式に関する詳細な分析が予定されている
  • 他の言語で FLAME パターンを実装する議論も可能である

1件のコメント

 
GN⁺ 2023-12-08
Hacker Newsのコメント
  • この4年間、100個を超えるLambda関数で構成されたアプリの苦痛と複雑さを経験してきた立場からすると、この記事はFaaSサーバーレスアーキテクチャの欠点を的確に突いていると思う。
    始めたばかりの頃は、こうした欠点はなかなか見えない。むしろ利用量が少なければほぼ無料で、保守もほとんど不要という利点のほうが目立つ。
    その後、Lambdaのワークフローが絡み合い、相互依存のせいで次第に硬直化してくると、いっそモノリスにして数百ドル余計に払ってでも自前で運用すべきだったと後悔することになる。最近なら fly.io のようなところを使えば、むしろ安く済むこともある。
    Elixirを使わない場合にどうなるのか気になる。

    • 意欲のある開発者なら、ほぼどんなプロセスやアーキテクチャでも18か月くらいは動かし続けられる、というパターンを何度も見てきたし、今では一種の法則のように思える。
      状況が悪化する頃には、新しい仕事を探すタイミングでもあったりする。特に入社から1年ほど経って、自分が導入したプロセスを今になって後悔しているような場合はなおさらだ。ひどい上司、めちゃくちゃな「単体テスト」、スクラムなどで何度も見てきた。
      ただ、人が職場で感じる不快感の原因を明確に認識しているのか、それとも単に「もう出ていく時期だ」と受け取っているだけなのかは分からない。不快なものに名前を付けようとすると強い反発を受けることが多かったし、Good to Great を読んでからは、そのことに対して感情を消耗することがずっと減った。誰も「ああ、自分の行動の結果なんだ」とは言いたがらない。
      私が保守しているルーブ・ゴールドバーグ的なシステムを実質的に作った人たちは、真っ先に去っていった。その船の船長が同僚に、うちのエンジンをオープンソース化したらどうかと尋ねたところ、同僚は、すでに存在していてもっと優れた車輪をわざわざ再発明したようなシステムを誰も使いたがらないだろうと答えた。その人は3〜4か月以内に自発的に辞めた。
    • どの言語でもこの問題を解けそうなものを作っている。現時点ではTypeScript SDKがあり、Java版も開発中だ。
      Lambda同士が相互に呼び出せる一般的なサービス指向コードを書けるなら、アプリケーションを何百もの短時間実行の断片に分割する必要はない。ただし、待ち時間まで課金されるならそれは不可能だ。Lambdaが入出力待ちの間に実行を一時停止できれば、この問題は解決できる。だから永続実行(durable execution) が答えになり得ると思う。
      ここ数週間、そのことを示す記事を書いていた: https://restate.dev/blog/suspendable-functions-make-lambda-t...
    • 記事の一節では、Elixir以外でのFLAMEについても触れた。要するに、妥当な並行性モデルを持つ言語であれば、一般に適用可能なパターンだ。
      キャプチャした変数をシリアライズできる関数のような、Elixirで無償で手に入る使い勝手のすべてを得るのは難しいだろうが、JavaScriptのような言語でも、クロージャで包む代わりにモジュールの実行部を新しいファイルへ移すようなやり方で、9割程度まではいける気がする。
      FLAMEライブラリを実装する人は、プーリング、監視、リモート通信の部分も書かなければならない。Elixirは分散メッセージングと監視の面で多くを無償で得られる。プロセス配置に関する機能も、事実上Elixir特有のものだ。
    • Lambdaが12個あるだけでも耐え難かった。元のアプリは、保守やデプロイをあまり考えていない人が作ったもので、コードがあちこちにコピー&ペーストされていた。
      結局、複数のエンドポイントを単一のLambdaで処理する太ったLambdaモノリスへ移行した。
    • コールドスタートをあまり気にしない、あるいは管理できるなら、Lambda上でもモノリスは作れる。
      言い換えれば、モノリスにしつつAWSコストも最小化できる。どちらか一方しか選べないという話ではない。
      最近は asp.net を使っているが、最適化済みのEFモデルを含めて ready-to-run でデプロイしたかなり大きなアプリでも、比較的すばやく起動する。
  • 筆者です。ようやく公開できてうれしいし、質問があれば答えます。何人かがFLAMEパターンをJavaScriptやGo、ほかの言語で実装してみようと思うくらいには刺激になればうれしいです。

    • 良さそう。Microsoftが注目してくれるといいのだが。Azure Functionsはセキュリティ設定とデプロイが複雑すぎるし、どんなコードを実行したいのかについて妙な前提を置いている。
    • 最も自然な実装は、JVM上のvert.xのような形になる気がする。
      すでにイベントバス上でリアクティブ拡張、Future、コルーチンによって非同期実行を処理する仕組みがあり、クラスター全体でのデータのシリアライズや分散もサポートしている。複数の人気言語向けのイベントバスクライアントもあるので、複数言語を混在させてアプリケーションを作れるはずだ。
    • 誇張は少し控えたほうがいいと思う。
      問題を「…死よりもひどい運命」のように提示し、その解決策はあまりにも簡単で苦痛がなく、別のアプローチを捨てないと愚かに見えるようにすると、こういう記事は純粋な営業記事として片付けられやすい。それは万病に効く薬を売る人のやり方だ。
      記事の前半で問題自体はうまく押さえていたのだから、もっと感情を抑えた客観的な比較があれば、より良いアプローチに興味を持つ人にも拒否感を与えにくかったと思う。
      実質的な内容は興味深かったが、表現の仕方のせいで敬遠してしまった。建設的なフィードバックとして受け取ってほしい。
    • 記事も動画も良くて、概念もとても興味深い。JavaScript実装を期待しているが、簡単ではなさそうだ。
      それと、ffmpeg.fly.dev を先に取ってしまったことがほんの少し申し訳なくなった。
    • これが、Sidekiqがバックグラウンドジョブを実行するためにRailsコードベースをインスタンス化するやり方と、根本的にどう違うのか気になる。
  • 「既存のアプリコードのどの部分でも関数で包むだけで自動スケールし、そのコードブロックがアプリの一時的なコピーの中で実行されると想像してみてほしい」という部分が興味深い。
    fork がやることをサーバーレス向けにしたように聞こえる。すばらしい仕事だ。

  • 数年前、実質的にこういうことをやってくれるサービスを使ったことがある。PiCloud は残念ながら Dropbox に吸収されたが、その前はジョブを透過的にワーカーへ分散する、まさにこのモデルを持っていた。コードをまとめてワーカーで実行する方式だった
    例はここにある。まったく同じモデルだとわかる: https://github.com/picloud/basic-examples/blob/master/exampl...
    Elixir は使ったことがないが、何十年も前に Erlang は使ったことがあり、BEAM は根本的にはあまり変わっていないように見える。設計の中核にある要素なので、この種の作業にはずっと向いていそうだ。それでも完全なフリーランチではなく、待っている間に メインプロセスが死ぬ可能性 はあるのではと思う

  • 全体の主張に同意する。私たちは https://www.windmill.dev で別のアプローチを取っていて、抽象化の単位をコンテナレベルではなく ソースコードレベル と見ている
    main 関数と import をパースして引数と依存関係を抽出し、望みのランタイム(TypeScript、Python、Go、Bash)でコードをそのまま実行する。肝はキャッシュを効率的に管理して、import に関係なくワーカーを常にホットな状態に保つことだ
    この方法はコードベースへの統合という点では FLAME ほどではないが、対象ユーザーが異なる。私たちのユーザーは、複雑なワークフロー、cron ジョブ、あるいは自動生成 UI 付きの使い捨てスクリプトを最初から作る
    FLAME ではコンテキスト全体がスナップショットされ、対象 VM で復元されるように見える。別のアプローチは、必要なコンテキストと不要なコンテキストを指定する構文を導入し、最小限だけをロードするというものだ。既存コードベースと Windmill をよりうまく統合し、HTTP 呼び出しに依存しないようにするため、現在模索している

    • FLAME で実際に起きていることは、正確にはそうではない。FLAME は BEAM の組み込み クラスタリング機能 によってリモートノード上で関数を呼び出しているだけだ
      その過程で、必要なコンテキストだけを送る処理が暗黙的に行われる。記事でもこう説明されている: “FLAME.call はランナープール名と関数を受け取る。次にアプリケーション全体の新しいコピーを見つけるか起動し、そこで関数を実行する。関数がクロージャとしてキャプチャした変数、たとえば %Video{} 構造体や interval は自動的に一緒に渡される”
    • w が 1 文字多かった。探している人のために URL はここ: https://www.windmill.dev/
      プロジェクトの目標が気に入った。Windmill がより良い オープンソースの Retool/Airtable 代替 になることを本当に期待している
  • 「FLAME では開発およびテストランナーがローカルバックエンドでそのまま実行される」のが良い。ローカル開発体験 がまともなサーバーレスなのはうれしい

    • その通り。サーバーレスが好きになれない理由のひとつは、モノリスを動かすのに比べて ローカル開発体験 がずっと悪いことだからだ
  • 「その後、アプリケーション全体の新しいコピーを見つけるか起動し、その関数がそこで実行される」のなら、Flame.call ごとにアプリの全プロセスを新しく起動して 実行コンテキスト をコピーしているのか?
    スケーラビリティの観点では非常に単純な解法だが、欠点もありそうだ
    アプリの起動時間が 10ms 増えれば、アプリケーション内のすべての Flame.call 箇所に 10ms が追加されることになるし、メモリも同様だろう
    このシステムを使うなら、こうした懸念を考慮する必要がありそうだ

    • 記事後半の FLAME.Pool がこの点を扱っている。ランナーはプールされ、指定した時間だけホットな状態に保たれた後、アイドル状態へ落ちる
      負荷がある状況ではプールはすでにホットになっているため、コールドスタートのコストはほとんど発生しない。さらに Elixir ライブラリには、満杯のランナーに当たって新たにコールドスタートする状況を避けるための、より洗練されたプール拡張手法も追加中だ
      ホットなランナーでは、オーバーヘッドは親子間のレイテンシだけだ。同じデータセンター内である必要があるので、1ms 以下になるはずだ
  • 素晴らしい。私たちが https://www.inngest.com/ で作っているものの、とても軽量な Elixir 専用版 のように感じる
    どちらも既存コードを何かでラップしてサーバーレス関数で使えるようにし、本質的にはリモート RPC として呼び出せるようにする点が似ている
    こうしたコードはしばしば命令的なステップの連なりとして実行される。各ステップは追加の Lambda として直列または並列に実行できる。しかしステップ間には、変数にキャプチャされた暗黙の状態がある。このため関数はワークフローになる。Inngest モデルでは、この状態をキャプチャして再び関数に注入することで耐久性を持たせている
    耐久性の観点では、このようなプロセスは キュー ベースであるべきだ。このモデルの良い点は、キューが安価なことだ。キューを 1 行のコードのように安くできれば、すべてが簡単になる。どんな開発者でもインフラを気にせず信頼性のあるコードを書けるようになる
    モニタリングと可観測性も重要だ。デッドレターキューは本当にひどく、失敗した関数やステップを管理して再実行できなければならない
    FLAME と Inngest の違いもある。Inngest はキューベースでイベント駆動型であり、どの言語からでも HTTP 経由で提供できる。Inngest は状態を外部保存するので、Elixir でワークフローを書いてから TypeScript に書き換えて再デプロイしても、実行中の関数は CRIU に似た形でバックエンド言語をまたいでライブマイグレーションできる
    イベント駆動方式ならフロー制御も可能だ。デバウンス、バッチ、スロットリング、ファンアウトまで、どんなランタイムや言語でも扱える。たとえば Fly 上の Elixir アプリがイベントを送り、TypeScript + Lambda で関数を実行させることができる
    FLAME がこれからどう進むのか楽しみだ。目標が似ている部分があると思う

    • Inngest は素晴らしいサービスに見える。記事ではジョブハンドラ、耐久性、リトライについても触れていた
      Elixir で耐久性、リトライ、ワークフローが必要なときは、普通は Oban を使うし、ここでも引き続きそうするつもりだ。Oban ジョブが FLAME を呼び出して弾力的な実行を処理することになる
  • HNが強制するアメリカ式のタイトル大文字表記を本当に嫌う理由の一つ。Serverless が、大文字の S の会社 serverless.com を再考するという意味には見えても、小文字の s の原則を再考するという意味には見えない
    ついでに言うと、誰かに Serverless を再考してほしい

    • 大文字小文字の表記は HN が強制しているのではなく、投稿した人が決めているのではないかと思う
      https://sst.dev/を見たかは分からないけれど、彼らはまさにそれをやったように見える。たとえば Live Lambda Development があるので、コードをクラウドに上げてデプロイを待つ必要がなく、フィードバックループを大幅に短縮できて、ローカル開発が本当に簡単になる
  • アイデアはかなり素晴らしいし、API も優れている
    「動画トランスコーディングのような CPU バウンドな処理は、本番環境でサービス全体をすぐに停止させかねない」という部分は、単純に CPU 基準のオートスケーリングをすればいいのでは?

    • その考えは記事の前半で扱おうとしていた。そうしたアプローチの問題は、間違った動作単位でスケールしてしまうことだ
      特定のホットなジョブを処理するために、Web サーバーを含むアプリケーション全体をスケールすることになる。私たちが望んでいること、そして FaaS を求める理由は、きめ細かな弾力的スケーリングだ。ここでのアイデアは、Web サーバーやワーカーのスケールボタンをやみくもに連打してうまくいくことを願う代わりに、既存のアプリコードに対してこうしたきめ細かなスケーリングを可能にすることだ
    • そうでもあり、そうでもない。残りのワークロードは CPU をそれほど必要としないかもしれない。こうした性能が必要なのは 1 つか 2 つのワークロードだけで、その処理がほかの仕事に押しのけられないようにしたい場合もある
      あるいは GPU が必要かもしれない
      中核サービスは 1〜2 台のサーバーで十分でも、1 日に 1 回くらいしか発生しないジョブのために、必要なときだけ数十・数百・数千台にスケールしなければならないこともある