Traiter de grandes quantités de données avec la batch api de Drupal et Drush

Traiter de grandes quantités de données avec la batch api de Drupal et Drush

17 Sep 2012 |  Drupal

Lorsque l’on est confronté aux joies de la production et de la maintenance de sites Internet il arrive souvent que l’on doive effectuer certaines manipulations sur des données existantes, comme l’ajout, la modification ou la suppression d’informations. Ces opérations sont en générale longues et coûteuses en mémoire car les quantités de données à traiter sont importantes et c’est pour cela que l’on utilise des batchs qui permettent de relâcher les processus une fois terminés évitant ainsi la saturation des ressources disponibles.

Quand la quantité de données à manipuler semble faible, le traitement avec un batch n’est pas nécessaire car il prendrait bien plus de temps qu’avec un simple script php. Même s’il est difficile de quantifier quand utiliser un batch ou un simple script, car tout dépend du travail à effectuer, des machines et du contexte, sachez que si vous devez réaliser un traitement lourd, l’utilisation d’un batch vous permettra de ne pas avoir peur que PHP s’arrête, simulant un malaise par manque de mémoire ou de temps.

La réalisation d’un batch avec Drupal c’est chose facile, puisqu’il existe une API pour cela, la Batch Api - http://drupal.org/node/180528 qui permet de créer de simplement des scripts de traitement.Il y a plein d’exemples sur le sujet, regardez le module example - http://drupal.org/project/examples - qui vous explique de A à Z comment coder et exécuter depuis l’interface web un batch.

Dans mon cas, je cherchais à réaliser un batch sans avoir besoin d’être connecté à l’interface d’administration de mon site et c’est naturellement que je me suis tourné vers Drush pour réaliser cette tâche.

Voici donc un petit snipet que vous pourrez réutiliser pour vos batch.

Déclaration de la commande Drush

Pour commencer nous allons créer notre commande drush. Dans un module, créez un fichier portant le nom de votre module avec l’extension drush.inc (dans mon cas sandbox.drush.inc)

function sandbox_drush_command() {
 $items  = array();
 $items['my-import'] = array(
    'callback'      => 'sandbox_setup_batch',
    'description' => dt('Import'),
 );
 return $items;
}

function sandbox_drush_help($section) {
 switch ($section) {
    case 'drush:myimport':
     return dt("Traitement des utilisateurs.");
 }
}

Déclaration du Batch

Une fois notre commande prête il nous faut déclarer notre batch, sandbox_setup_batch().

function sandbox_setup_batch() {

 // Ici nous créons un tableau nommé opérations, qui contiendra toutes les fonctions qui devront être invoquées lors de l'exécution du batch. Il est ainsi possible de réaliser plusieurs traitements différents avec un seul batch.
 $operations = array();
 $operations[] = array('sandbox_batch_process', array());

 // Déclaration des propriétés de notre batch.
 $batch = array(
   // Le tableau d’opérations à effectuer lors du traitement de notre batch.
   'operations' => $operations,
   'title' => t('Import batch'),
   'init_message' => t('Initializing'),
   'error_message' => t('An error occurred'),
   // Fonction qui sera appelée à la fin du batch.
   'finished' => 'sandbox_finished_method'
 );

 // Initialisation du batch.
 batch_set($batch);
 $batch =& batch_get();

 $batch['progressive'] = FALSE;

 // Execution du batch.
 drush_backend_batch_process();
}

Opération de batch à réaliser

A ce stade notre commande Drush est créée et notre batch déclaré, mais pour le moment il ne fait rien car nous n’avons pas encore implémenté la fonction principale, celle que notre batch devra exécuter. Ici, je récupère un par un tous les utilisateurs ayant le statut actif du site.

function sandbox_batch_process(&$context) {

 if (!isset($context['sandbox']['progress'])) {
   $context['sandbox']['progress'] = 0;
   $context['sandbox']['current_user'] = 0;
   // Récupération du nombre d’élément à traiter.
   $context['sandbox']['max'] = db_result(db_query('SELECT COUNT(DISTINCT uid) FROM {users} WHERE status = 1'));
 }

 // Histoire de ne pas avoir de problèmes nous récupérerons les utilisateurs 50 par 50
 $limit = 50;

 $sql = "SELECT uid FROM {users} WHERE status = 1 AND uid > %d ORDER BY uid ASC";
 $result = db_query_range($sql, $context['sandbox']['current_user'], 0, $limit);
 while ($row = db_fetch_array($result)) {
   
   // C’est ici que doit se faire le traitement de votre batch.
   //...

   // Une fois que nous en avons terminé avec les données en cours il nous suffit de mettre à jour les informations du batch et de passer à la suivante.
   $context['sandbox']['progress']++;
   $context['sandbox']['current_user'] = $account->uid;
 }

 // Tant que l’on n’a pas traité tout le jeu de données on continue à exécuter notre batch.
 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
   $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
 }
}

function sandbox_finished_method($success, $results, $operations) {
 drush_print('Finished importing!');
}

Et voilà, en quelques lignes nous avons un batch prêt à l’emploi pour traiter nos données. Notez que cette commande drush peut être ajoutée dans un CRON afin d’être exécutée régulièrement.

Julien Dubreuil

Vous avez une idée, un projet web à réaliser ?

Ensemble, mettons en oeuvre sa réussite. Je vous accompagne dans vos projets, depuis l'élaboration du cahier des charges jusqu'à la mise en production. Pour plus d'information n'hésitez pas à me contacter.

Contactez-moi