タスクの同時実行による Batch コンピューティング ノードの使用率の最大化

各ノードで複数のタスクを同時に実行することで、プール内の計算ノードの数を減らしつつ、リソースの使用率を最大限に高めることができます。

シナリオによっては、1 つのノードのリソースをすべて 1 つのタスクに割り当てた状態で最適に動作する場合がありますが、特定のワークロードでは、複数のタスクがそれらのリソースを共有するとジョブ時間が短縮され、コストが削減されることがあります。 次のシナリオで考えてみましょう。

  • データ転送を最小限に抑える (データを共有できるタスクの場合)。 共有データを少数のノードにコピーし、各ノードでタスクを並列実行することにより、データ転送の費用を大幅に削減できます。 この方策が特に当てはまるのは、各ノードにコピーするデータを、地理的リージョン間で転送する必要がある場合です。
  • メモリの使用率を最大化する (大量のメモリが必要だが、短期間のみで、実行中の発生回数が一定しないタスクの場合)。 より多くのメモリを使用する少数の大きなコンピューティング ノードを使用することで、このようなスパイクを効率的に処理できます。 これらのノードには、各ノードで並列に実行される複数のタスクが割り当てられていますが、各タスクは、ノードの豊富なメモリを異なるタイミングで利用できます。
  • ノード数の制限を緩和する (プール内でノード間の通信が必要な場合)。 現時点では、ノード間通信用に構成されるプールは 50 のコンピューティング ノードに制限されています。 このようなプール内の各ノードがタスクを並列に実行できると、より多くのタスクが同時に実行できるようになります。
  • オンプレミスのコンピューティング クラスターをレプリケートする (コンピューティング環境を初めて Azure に移行するときなど)。 現在のオンプレミスのソリューションがコンピューティング ノードごとに複数のタスクを実行している場合、ノード タスクの最大数を増やすことで、その構成をより厳密に反映できます。

サンプル シナリオ

例として、Standard_D1 ノードで十分な CPU とメモリの要件を持つタスク アプリケーションがあるとします。 ただし、必要な時間内にジョブを終えるには、そのようなノードが 1,000 個必要です。

1 個の CPU コアを持つ Standard_D1 ノードを使用する代わりに、それぞれが 16 個のコアを持つ Standard_D14 ノードを使用して、並列タスクの実行を有効にすることもできます。 1,000 ノードではなく、16 分の 1 の少ないノードで済む可能性があり、必要なノードは 63 個のみとなります。 大きなアプリケーション ファイルまたは参照データが各ノードに必要である場合、データのコピー先がわずか 63 個のノードで済むため、ジョブの実行時間と効率の面で改善が期待できます。

並列タスク実行を有効にする

並列タスク実行のためのコンピューティング ノードの構成は、プール レベルで行います。 Batch .NET ライブラリを使用する場合は、プールを作成するときに CloudPool.TaskSlotsPerNode プロパティを設定します。 Batch REST API を使用する場合は、プール作成時の要求本文に taskSlotsPerNode 要素を設定します。

Note

taskSlotsPerNode 要素および TaskSlotsPerNode プロパティは、プール作成時にのみ設定できます。 これらは、プールが既に作成された後で変更することはできません。

Azure Batch では、ノードあたりのタスク スロット数をノード コアの数の最大 4 倍まで設定できます。 たとえば、プールをノード サイズ "Large" (4 コア) で構成した場合、 taskSlotsPerNode は 16 に設定できます。 ただし、ノードのコア数には関係なく、ノードあたり 256 タスク スロットを超えることはできません。 各ノード サイズのコア数について詳しくは、「Cloud Services のサイズ (クラシック)」をご覧ください。 サービスの制限について詳しくは、「Batch サービスのクォータと制限」をご覧ください。

ヒント

プールの自動スケールの数式を作成するときは、taskSlotsPerNode の値を考慮してください。 たとえば、 $RunningTasks を評価する式は、ノードあたりのタスク数の増加によって大きな影響を受ける可能性があります。 詳細については、Batch プール内のコンピューティング ノードをスケーリングするための自動式の作成に関するページを参照してください。

タスク分散を指定する

同時実行タスクを有効にする場合、プール内のノード間でタスクをどのように分散させるかを指定することが重要です。

CloudPool.TaskSchedulingPolicy プロパティを使用することで、プール内のすべてのノードに対して均等にタスクを割り当てるように指定できます ("分散")。 または、1 つのノードにできる限り多くのタスクを割り当ててから、プール内の別のノードにタスクを割り当てるように指定することもできます ("圧縮")。

前述した例の Standard_D14 ノードのプールで、CloudPool.TaskSlotsPerNode の値を 16 で構成した場合を例として検討します。 ComputeNodeFillTypePack を指定して CloudPool.TaskSchedulingPolicy を構成した場合、各ノードの 16 コアすべての使用率が最大になり、自動スケーリング プールが未使用ノード (タスクが割り当てられていないノード) をプールから削除できます。 自動スケーリングにより、リソース使用量を最小限に抑え、コストを削減できます。

タスクごとの可変スロットを定義する

タスクは、計算ノードでの実行に必要なスロット数を CloudTask.RequiredSlots プロパティに指定して定義できます。 既定値は 1 です。 計算ノードでのリソース使用量に関連した比重がタスクごとに異なる場合は、可変タスク スロットを設定できます。 可変タスク スロットにより、各計算ノードで、CPU やメモリなどのシステム リソースを圧迫しない妥当な数の同時実行タスクが実行されるようにすることができます。

たとえば、プロパティ taskSlotsPerNode = 8 が指定されたプールの場合、マルチコアを必要とする CPU 負荷の高いタスクを requiredSlots = 8 として送信できる一方で、その他のタスクは requiredSlots = 1 に設定することができます。 この混合ワークロードがスケジュールされた場合、CPU 負荷の高いタスクはその計算ノード上で排他的に実行されますが、他のタスクは他のノードで同時に (一度に最大 8 タスクまで) 実行できます。 混合ワークロードにより、計算ノード間でワークロードのバランスを調整し、リソース使用効率を向上できます。

タスクの requiredSlots にプールの taskSlotsPerNode よりも大きい値を指定しないでください。タスクが実行されなくなります。 現在、Batch サービスでは、タスク送信時にこの競合は検証されません。 競合が検証されない理由としては、ジョブの送信時にプールがバインドされていない場合がある、または無効化/再有効化によって別のプールに変更される場合があるためです。

ヒント

可変タスク スロットを使用する場合、多くのスロットを必要とする大きなタスクを一時的にスケジュールできない可能性があります。これは、一部のノードにアイドル スロットが残っていたとしても、計算ノードで利用可能なスロットが不足しているためです。 これらのタスクに対するジョブの優先度を上げることで、ノードで利用可能なスロットを勝ち取る可能性を高めることができます。

タスク実行のスケジュールが失敗した場合は、Batch サービスによって TaskScheduleFailEvent が出力され、必要なスロットが使用できるようになるまでスケジュールの再試行が継続されます。 そのイベントをリッスンして、潜在的なタスクのスケジュールの問題を検出し、それに応じて軽減策を実行できます。

Batch .NET の例

次の Batch .NET API コード スニペットでは、ノードごとに複数のタスク スロットを持つプールを作成し、必要なスロットを指定してタスクを送信する方法を示します。

ノードごとに複数のタスク スロットを持つプールを作成する

このコード スニペットは、ノードあたり 4 つのタスク スロットが許可されている 4 つのノードを含むプールの作成要求を示しています。 ここでは、各ノードがタスクでいっぱいになってからプール内の別のノードにタスクを割り当てるタスク スケジュール ポリシーが指定されています。

Batch .NET API を使用したプールの追加方法の詳細については、「BatchClient.PoolOperations.CreatePool」を参照してください。

CloudPool pool =
    batchClient.PoolOperations.CreatePool(
        poolId: "mypool",
        targetDedicatedComputeNodes: 4
        virtualMachineSize: "standard_d1_v2",
        VirtualMachineConfiguration: new VirtualMachineConfiguration(
            imageReference: new ImageReference(
                                publisher: "MicrosoftWindowsServer",
                                offer: "WindowsServer",
                                sku: "2019-datacenter-core",
                                version: "latest"),
            nodeAgentSkuId: "batch.node.windows amd64");

pool.TaskSlotsPerNode = 4;
pool.TaskSchedulingPolicy = new TaskSchedulingPolicy(ComputeNodeFillType.Pack);
pool.Commit();

必要なスロットを指定したタスクを作成する

このコード スニペットでは、既定以外の requiredSlots を指定してタスクを作成します。 このタスクは、計算ノード上に使用できる空きスロットが十分にある場合に実行されます。

CloudTask task = new CloudTask(taskId, taskCommandLine)
{
    RequiredSlots = 2
};

コンピューティング ノードを、実行中のタスクとスロットの数とともに一覧表示する

このコード スニペットは、プール内のすべての計算ノードを一覧表示し、実行中のタスク数とノードあたりのタスク スロット数を出力します。

ODATADetailLevel nodeDetail = new ODATADetailLevel(selectClause: "id,runningTasksCount,runningTaskSlotsCount");
IPagedEnumerable<ComputeNode> nodes = batchClient.PoolOperations.ListComputeNodes(poolId, nodeDetail);

await nodes.ForEachAsync(node =>
{
    Console.WriteLine(node.Id + " :");
    Console.WriteLine($"RunningTasks = {node.RunningTasksCount}, RunningTaskSlots = {node.RunningTaskSlotsCount}");

}).ConfigureAwait(continueOnCapturedContext: false);

ジョブのタスク数を一覧表示する

このコード スニペットは、ジョブのタスク数を取得します。その中には、タスク状態ごとのタスクとタスク スロットの両方が含まれます。

TaskCountsResult result = await batchClient.JobOperations.GetJobTaskCountsAsync(jobId);

Console.WriteLine("\t\tActive\tRunning\tCompleted");
Console.WriteLine($"TaskCounts:\t{result.TaskCounts.Active}\t{result.TaskCounts.Running}\t{result.TaskCounts.Completed}");
Console.WriteLine($"TaskSlotCounts:\t{result.TaskSlotCounts.Active}\t{result.TaskSlotCounts.Running}\t{result.TaskSlotCounts.Completed}");

Batch REST の例

次の Batch REST API コード スニペットは、ノードごとに複数のタスク スロットを持つプールを作成し、必要なスロットを指定してタスクを送信する方法を示します。

ノードごとに複数のタスク スロットを持つプールを作成する

このスニペットは、ノードごとの最大タスク数が 4 である 2 個の大きいノードを含むプールの作成要求を示します。

REST API を使用したプールの追加方法の詳細については、「Add a pool to an account (アカウントにプールを追加する)」を参照してください。

{
  "odata.metadata":"https://myaccount.myregion.batch.azure.com/$metadata#pools/@Element",
  "id":"mypool",
  "vmSize":"large",
  "virtualMachineConfiguration": {
    "imageReference": {
      "publisher": "canonical",
      "offer": "ubuntuserver",
      "sku": "20.04-lts"
    },
    "nodeAgentSKUId": "batch.node.ubuntu 20.04"
  },
  "targetDedicatedComputeNodes":2,
  "taskSlotsPerNode":4,
  "enableInterNodeCommunication":true,
}

必要なスロットを指定したタスクを作成する

このスニペットは、既定以外の requiredSlots を指定してタスクを追加する要求を示しています。 このタスクは、計算ノード上に使用できる空きスロットが十分にある場合にのみ実行されます。

{
  "id": "taskId",
  "commandLine": "bash -c 'echo hello'",
  "userIdentity": {
    "autoUser": {
      "scope": "task",
      "elevationLevel": "nonadmin"
    }
  },
  "requiredSLots": 2
}

GitHub 上のサンプル コード

CloudPool.TaskSlotsPerNode プロパティの使用方法は、GitHub の ParallelTasks プロジェクトで紹介されています。

この C# コンソール アプリケーションでは、Batch .NET ライブラリを使用して、1 つ以上のコンピューティング ノードを含むプールを作成します。 さらに、これらのノードで構成可能な数のタスクを実行して、可変負荷をシミュレートします。 アプリケーションの出力には、各タスクを実行したノードが示されます。 また、ジョブのパラメーターと実行時間の概要も出力されます。

次の例は、ParallelTasks サンプル アプリケーションの 2 つの異なる実行からの出力の概要部分を示しています。 ここに示されているジョブ期間にはプールの作成時間が含まれていません。これは、送信時に "アイドル" 状態だった計算ノードを持つ以前に作成されたプールに対して各ジョブが送信されたためです。

サンプル アプリケーションの 1 回目の実行では、プール内の 1 つのノードと、ノードごとに 1 タスクという既定の設定が使用され、ジョブの実行時間は 30 分を超えていることがわかります。

Nodes: 1
Node size: large
Task slots per node: 1
Max slots per task: 1
Tasks: 32
Duration: 00:30:01.4638023

2 回目の実行では、ジョブの実行時間が大幅に短縮されています。 この削減は、ノードごとのタスク数を 4 にしてプールが構成されているためであり、並列タスク実行によってほぼ 4 分の 1 の時間でジョブを完了できます。

Nodes: 1
Node size: large
Task slots per node: 4
Max slots per task: 1
Tasks: 32
Duration: 00:08:48.2423500

次のステップ