Ejecución simultánea de tareas para maximizar el uso de los nodos de proceso de Batch

Mediante la ejecución simultánea de más de una tarea en cada nodo del grupo de Azure Batch, puede maximizar el uso de los recursos en un menor número de nodos de ejecución en el grupo.

Aunque algunos escenarios funcionan mejor con todos los recursos de un nodo dedicados a una sola tarea, en algunas cargas de trabajo se pueden observar tiempos de trabajo más cortos y menores costos si varias tareas comparten esos recursos. Considere los siguientes escenarios:

  • Reducción al mínimo de la transferencia de datos de las tareas que pueden compartir datos. Puede reducir considerablemente los gastos de transferencia de datos copiando los datos compartidos en un número menor de nodos y ejecutando tareas en paralelo en cada nodo. Esto es válido especialmente si los datos que se copian en cada nodo deben transferirse entre regiones geográficas.
  • Maximice el uso de la memoria para las tareas que requieran una gran cantidad de memoria, pero solo durante períodos breves y en momentos variables durante la ejecución. Puede emplear menos nodos de ejecución, pero de mayor tamaño y con más memoria, para controlar de forma eficaz dichos aumentos. Estos nodos tendrán varias tareas ejecutándose en paralelo en cada nodo, aunque cada tarea puede aprovechar la abundante memoria de los nodos en distintos momentos.
  • Mitigación de los límites de número de nodos cuando se requiere la comunicación entre nodos dentro de un grupo. Actualmente, los grupos configurados para la comunicación entre nodos están limitados a 50 nodos de ejecución. Si cada uno de los nodos de este tipo de grupo es capaz de ejecutar tareas en paralelo, el número de tareas que se podrán ejecutar simultáneamente será mayor.
  • Replicación en un clúster de proceso local, como cuando mueve por primera vez un entorno de procesos a Azure. Si la solución local existente ejecuta varias tareas en cada nodo de proceso, puede aumentar el número máximo de tareas de nodo para que refleje con mayor fidelidad esa configuración.

Escenario de ejemplo

Por ejemplo, imagine una aplicación de tarea con unos requisitos de CPU y memoria tales que unos nodos Standard_D1 sean suficientes. Sin embargo, para ejecutar el trabajo en el tiempo requerido, se necesitan 1000 nodos de ese tipo.

En lugar de utilizar nodos Standard_D1 con un núcleo de CPU, podría utilizar nodos Standard_D14 con 16 núcleos en cada nodo y habilitar la ejecución de tareas en paralelo. Podría usar 16 veces menos nodos; en lugar de 1000 nodos, solo se necesitarían 63. Si se necesitan datos de referencia o archivos de aplicación de gran tamaño para cada nodo, la eficiencia y la duración del trabajo se mejoran, ya que los datos se copian en solo 63 nodos.

Habilitación de la ejecución en paralelo de tareas

Los nodos de proceso para la ejecución en paralelo de tareas se configuran a nivel de grupo. Con la biblioteca .NET de Batch, establezca la propiedad CloudPool.TaskSlotsPerNode al crear un grupo. Si usa la API REST de Batch, establezca el elemento taskSlotsPerNode en el cuerpo de la solicitud durante la creación del grupo.

Nota

Solo puede establecer el elemento taskSlotsPerNode y la propiedad TaskSlotsPerNode en el momento de crear el grupo. No se pueden modificar después de haber creado el grupo.

Azure Batch permite una configuración de ranuras de tarea por nodo que cuadriplica el número de núcleos de nodo. Por ejemplo, si el grupo está configurado con nodos de tamaño "Grande" (cuatro núcleos), taskSlotsPerNode se puede establecer en 16. Sin embargo, independientemente de cuántos núcleos tiene el nodo, no puede tener más de 256 ranuras de tarea por nodo. Para más información sobre el número de núcleos de cada uno de los tamaños de nodo, consulte Tamaños de Cloud Services (clásico). Para más información sobre los límites del servicio, consulte Cuotas y límites del servicio Batch.

Sugerencia

Asegúrese de tener en cuenta el valor taskSlotsPerNode al construir una fórmula de escalado automático para el grupo. Por ejemplo, una fórmula que evalúe $RunningTasks podría verse afectada considerablemente por un aumento en las tareas por nodo. Para más información, consulte Creación de una fórmula automática para escalar nodos de ejecución en un grupo de Batch.

Especificación de la distribución de tareas

A la hora de habilitar tareas simultáneas, es importante especificar cómo desea que se distribuyan las tareas entre los nodos del grupo.

Mediante la propiedad CloudPool.TaskSchedulingPolicy, puede especificar que las tareas se deberían asignar de manera uniforme entre todos los nodos del grupo ("propagación"). O bien, puede especificar que se deberían asignar todas las tareas posibles a cada nodo antes de asignarlas a otro nodo del grupo ("empaquetado").

Como ejemplo, considere el grupo de nodos Standard_D14 (en el ejemplo anterior) configurado con un valor CloudPool.TaskSlotsPerNode de 16. Si CloudPool.TaskSchedulingPolicy se configura con un ComputeNodeFillType de Pack, se podría maximizar el uso de los 16 núcleos de cada nodo y permitir que un grupo con escalabilidad automática elimine los nodos sin usar del grupo (nodos sin tareas asignadas). El escalado automático minimiza el uso de recursos y puede ahorrar dinero.

Definición de ranuras variables por tarea

Una tarea se puede definir con la propiedad CloudTask.RequiredSlots especificando el número de ranuras que requiere para ejecutarse en un nodo de ejecución. El valor predeterminado es 1. Puede establecer ranuras de tareas variables si las tareas tienen pesos diferentes asociados con el uso de recursos en el nodo de ejecución. Esto permite que cada nodo de ejecución tenga un número razonable de tareas en ejecución simultáneas que no llegue a saturar los recursos del sistema, como la CPU o la memoria.

Por ejemplo, en el caso de un grupo con la propiedad taskSlotsPerNode = 8, puede enviar tareas de uso intensivo de CPU que requieren varios núcleos con requiredSlots = 8, y otras tareas con requiredSlots = 1. Cuando esta carga de trabajo mixta está programada, las tareas de uso intensivo de CPU se ejecutarán exclusivamente en sus nodos de ejecución, mientras que otras tareas se podrán ejecutar simultáneamente (hasta ocho tareas a la vez) en otros nodos. Esto le ayuda a equilibrar la carga de trabajo entre los nodos de ejecución y a mejorar la eficacia del uso de los recursos.

Asegúrese de no especificar un valor de requiredSlots para la tarea que sea mayor que el valor de taskSlotsPerNode para el grupo, de lo contrario la tarea no se ejecutará. El servicio Batch no valida actualmente este conflicto al enviar tareas. Esto se debe a que es posible que el trabajo no tenga ningún grupo enlazado al momento del envío o a que puede cambiar a un grupo diferente si se deshabilita o se vuelve a habilitar.

Sugerencia

Cuando se usan ranuras de tareas variables, es posible que las tareas grandes con más ranuras necesarias no se puedan programar temporalmente debido a que no hay suficientes ranuras disponibles en ningún nodo de ejecución, incluso cuando todavía hay ranuras inactivas en algunos nodos. Puede subir la prioridad del trabajo para estas tareas con el fin de aumentar su oportunidad de competir por las ranuras disponibles en los nodos.

El servicio Batch emite TaskScheduleFailEvent cuando no puede programar una tarea para que se ejecute y seguirá intentando la programación hasta que estén disponibles las ranuras necesarias. Puede escuchar ese evento para detectar posibles problemas de programación de tareas y llevar a cabo su mitigación en consecuencia.

Ejemplo de Batch .NET

En los siguientes fragmentos de código de la API de .NET para Batch se muestra cómo crear un grupo con varias ranuras de tareas por nodo y cómo enviar una tarea con las ranuras necesarias.

Creación de un grupo con varias ranuras de tareas por nodo

En este fragmento de código se muestra una solicitud para crear un grupo que contiene cuatro nodos con cuatro ranuras de tarea por nodo. Se especifica una directiva de programación de tareas que llenará cada nodo de tareas antes de asignarlas a otro nodo del grupo.

Para obtener más información sobre cómo agregar grupos mediante la API de .NET para Batch, consulte 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();

Creación de una tarea con las ranuras necesarias

Este fragmento de código crea una tarea con requiredSlots no predeterminado. Esta tarea solo se ejecutará cuando haya suficientes ranuras libres disponibles en un nodo de ejecución.

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

Enumeración de los nodos de ejecución con recuentos para ejecutar tareas y ranuras

En este fragmento de código se enumeran todos los nodos de ejecución del grupo y se imprimen los recuentos para ejecutar tareas y ranuras de tarea por nodo.

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);

Enumeración de recuentos de tarea para el trabajo

Este fragmento de código obtiene los recuentos de tareas para el trabajo, lo que incluye los recuentos de tareas y de ranuras de tarea por estado de tarea.

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}");

Ejemplo de REST Batch

En los siguientes fragmentos de código de la API REST para Batch se muestra cómo crear un grupo con varias ranuras de tareas por nodo y cómo enviar una tarea con las ranuras necesarias.

Creación de un grupo con varias ranuras de tareas por nodo

En este fragmento de código, se muestra una solicitud para crear un grupo que contiene dos nodos de gran tamaño con un máximo de cuatro tareas por nodo.

Para más información sobre cómo agregar grupos mediante la API REST, consulte Agregar un grupo a una cuenta.

{
  "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,
}

Creación de una tarea con las ranuras necesarias

En este fragmento de código se muestra una solicitud para agregar una tarea con requiredSlots no predeterminado. Esta tarea solo se ejecutará cuando haya suficientes ranuras libres disponibles en el nodo de ejecución.

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

Código de ejemplo en GitHub

El proyecto ParallelTasks en GitHub muestra el uso de la propiedad CloudPool.TaskSlotsPerNode.

Esta aplicación de consola de C# utiliza la biblioteca de .NET de Batch para crear un grupo con uno o más nodos de proceso. Ejecuta un número configurable de tareas en esos nodos para simular una carga variable. La salida de la aplicación especifica qué nodos han ejecutado cada tarea. La aplicación también proporciona un resumen de los parámetros de trabajo y la duración.

En el siguiente ejemplo, se muestra la parte de resumen de la salida de dos ejecuciones diferentes de la aplicación de ejemplo ParallelTasks. Las duraciones del trabajo que se muestran aquí no incluyen la hora de creación del grupo, ya que cada trabajo se envió a un grupo creado anteriormente cuyos nodos de ejecución tenían el estado Inactivo en el momento del envío.

La primera ejecución de la aplicación de ejemplo muestra que, con un solo nodo en el grupo y la configuración predeterminada de una tarea por nodo, la duración del trabajo es superior a 30 minutos.

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

La segunda ejecución del ejemplo muestra una disminución notable en la duración del trabajo. Esta reducción se debe a que el grupo se configuró con cuatro tareas por nodo, lo que permite la ejecución de tareas en paralelo de forma que el trabajo se completa en casi una cuarta parte del tiempo.

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

Pasos siguientes