Queue API: Ejecución de procesos por partes

June 18, 2021

La última versión de Drupal ha buscado dejar de ser solamente un sistema gestor de contenido para contar con más herramientas dignas de un framework. Para estos se desarrollaron diversas mejoras para la construcción de formularios, consultas a la base de datos, mejores sistemas de caching, mejoras a las librerías de theming, etc. Sin embargo, es muy poco lo que se ha mencionado sobre el queue API

¿En qué consiste?

El Queue API es una herramienta diseñada para la administración de instrucciones determinadas, algo que en otros entornos de desarrollo o sistemas operativos se conoce como cola de procesos. Consiste básicamente en un depósito de instrucciones esperados a ser ejecutados como si se tratara de una lista de tareas personal para el framework de Drupal, en la que este va agrupando las tareas pendientes y va descartando las que ya realizó, y lo hace de manera ordenada, sencilla y de un modo utilizable para todo programador. Usa los mismos conceptos de colas como estructuras de datos que se pueden observar en distintos lenguajes de programación.

¿Para qué sirve?

Veamos un ejemplo: cuento con un formulario en el cual se envía un mensaje a todos los usuarios que son de un rol determinado. La lógica que construye el correo a primera vista no parece tan complicada.

Alternativa 1: Enviar todos los correos ni bien se envíe el formulario

Sería cuestión de realizar una consulta a la base de datos (véase el API de base de datos de Drupal) para obtener los usuarios de un determinado rol, y por cada uno llamar a una función para enviar un correo con su respectiva información y sus datos personales.

Suena bastante simple. El problema radica en que si actualmente existen por ejemplo alrededor de 200 usuarios de cierto rol en el sitio, el proceso de enviar o de siquiera generar los correos, no podrá terminarse antes de que el servidor detenga bruscamente el proceso por exceso de tiempo o de memoria.

Alternativa 2: Guardar información en cache

Esta alternativa consiste en guardar lo que se pueda a la cache (sea del sistema o personalizada) y obtener la data guardada posteriormente usando un hook. El problema con este procedimiento es que aun se corre el riesgo de que el proceso de enviar información a cache sea igual de pesado para el servidor y PHP frene el proceso. Desafortunadamente para los desarrolladores de Drupal 6 o versiones inferiores, ésta era la única alternativa disponible para emular una cola de procesos.

Alternativa 3: Queue API

Esta alternativa esta basada en la anterior, con la diferencia que con este API, existen funciones disponibles para el desarrollo apropiado de este problema. Queue API provee funciones para guardar tareas en distintas colas que pueda crear el programador de acuerdo a sus necesidades, y Drupal se encarga de ejecutarlas posteriormente. Mientras todas las tareas estén correctamente organizadas para su posterior ejecución, de ésta forma al programador solamente le resta informar a los usuarios del formulario del ejemplo anterior que los correos están listos para ser enviados.

La forma en la que trabaja el Queue API se basa en cron. Cada tarea a ejecutar en vez de proceder con su ejecución, se usa una función que invoque este proceso y se usa una función para guardar este proceso a una cola nueva. Luego estos procesos son ejecutados cada cierto tiempo determinado por los parámetros de la cola, estos procesos los ejecuta Drupal en el servidor sin esperar una petición de usuario, sino que ésta cola de procesos para al cron del sistema donde se aloja el sitio.

¿Cómo funciona?

Veamos el ejemplo mencionado hace un momento sobre el envío de un formulario que envía un correo masivo.

Ésta función simula la funcionalidad que genera información para múltiples usuarios.


function mymodule_pending_users_info() {
  return array(
    array(
      'subject' => 'Asunto 1',
      'from' => 'mail1@example.com',
      'to' => 'mail2@example.com',
      'message' => '...'
    ),
    array(
      'subject' => 'Asunto 2',
      'from' => 'mail3@example.com',
      'to' => 'mail4@example.com',
      'message' => '...'
    ),
    array(
      'subject' => 'Asunto 3',
      'from' => 'mail5@example.com',
      'to' => 'mail6@example.com',
      'message' => '...'
    ),
    array(
      'subject' => 'Asunto 4',
      'from' => 'mail7@example.com',
      'to' => 'mail8@example.com',
      'message' => '...'
    ),
  );
}

Ésta función enviará un correo acorde a la información recibida.


function mymodule_send_mail_to_user($user_info) {
  drupal_mail(...);
} 

El código a continuación muestra como almacenar datos en las colas.


//Funcion para obtener la instancia de una cola. Como puede verse, se
//pueden generar cuantas se consideren necesarias usando esta función. Si la
//cola no existe, la funcion genera una nueva.
$queue = DrupalQueue::get('cola1');

//Por cada usuario obtenido por la funcion que obtiene los usuarios
//destinatarios, se agrega su información a la cola creada. Cabe destacar
//que estos items pueden ser de cualquier tipo que PHP soporte.
foreach(mymodule_pending_users_info() as $item) {
  $queue->createItem($item);
}
//Toma cada elemento de la cola y ejecuta una acción por cada uno. Posteriormente los remueve.
//claimItem sirve para buscar el ultimo elemento insertado en el queue.
//Éste código no se tomara en cuenta para el siguiente ejemplo.
while($item = $queue->claimItem()) {
  mymodule_send_mail_to_user($item->data);
  //Remueve el último item de la cola.
  $queue->deleteItem($item);
}

Si bien este código funciona, únicamente sirve para almacenar datos en colas, y hasta ahora no se esta aprovechando a plenitud las ventajas de usar colas, dado que aun seguimos procesando data en un solo llamado, y sigue siendo pesado para PHP llevar a cabo esta petición.

Para esto Drupal cuenta con un hook que permite llevar a cabo una tarea cuya información se la provee la data de una cola periódicamente. Esto se logra usando cron. Manteniendo el ejemplo anterior (sin el código que envía los correos), ya tenemos lo necesario para rellenar nuestro queue con la data pertinente. A continuación se usará hook_cron_queue_info para asignar una tarea a cada item cada vez que cron los reclama periódicamente.


/**
 * Implements hook_cron_queue_info().
 */
function mymodule_cron_queue_info() {
  $queues = array();
  $queues['my_cron_queue'] = array(
     //Por cada item, se llamara a la funcion señalada abajo.
    'worker callback' => 'mymodule_send_mail_to_user',
    //Los segundos que tiene cron para continuar ejecutando cada item.
    'time' => 60,
  );
  return $queues;
}

Usando éste hook, ya el programador no necesita hacer las respectivas operaciones para crear, obtener o liberar espacio de la cola, ni de usar hook_cron para el procesamiento de la data en la cola, dado que Drupal se encarga de todo eso.

Espero les sirva de utilidad este blog. ¡Mis más sinceros saludos, jóvenes drupaleros!