wp eval-file migration-traduction.php –batch-size=500 –offset=0. C’est par cette commande, lancée depuis un terminal SSH un mardi à cinq heures du matin, que commence la migration de cent mille fiches produit d’un catalogue e-commerce vers une structure multilingue complète, opération distincte d’une traduction de dix mille produits déjà traitée pour un catalogue plus modeste où le volume ne posait pas les mêmes contraintes de temps d’exécution.
À cette échelle, une boucle PHP unique qui traiterait les cent mille fiches d’un seul tenant se heurte à deux limites concrètes : le temps d’exécution maximal du script (souvent 30 à 300 secondes selon la configuration serveur) et la consommation mémoire qui augmente au fil du traitement si les objets WordPress chargés ne sont pas correctement libérés. La seule méthode robuste consiste à découper le travail en lots, avec un mécanisme de reprise capable de repartir exactement là où le traitement s’est arrêté.
Étape 1 : dimensionner le lot
La taille de lot idéale dépend directement de la mémoire allouée à PHP en ligne de commande (souvent plus généreuse qu’en mode web, mais pas illimitée) et de la complexité du traitement par fiche. Sur un catalogue avec des champs ACF, des variations WooCommerce et une création de traduction via l’API de Polylang, un lot de cinq cents fiches par exécution constitue un bon point de départ, à ajuster après une première exécution de test en mesurant la mémoire réellement consommée avec memory_get_peak_usage().
Étape 2 : écrire le script de traitement avec reprise

Le script doit lire un fichier de progression avant chaque lot, traiter le nombre de fiches prévu, puis mettre à jour ce fichier avant de se terminer, que le lot suivant soit déclenché manuellement ou automatiquement :
<?php
$progress_file = WP_CONTENT_DIR . '/migration-progress.json';
$progress = file_exists( $progress_file )
? json_decode( file_get_contents( $progress_file ), true )
: array( 'offset' => 0, 'done' => 0, 'errors' => array() );
$batch_size = 500;
$products = wc_get_products( array(
'limit' => $batch_size,
'offset' => $progress['offset'],
'orderby' => 'ID',
'order' => 'ASC',
) );
foreach ( $products as $product ) {
try {
pll_set_post_language( $product->get_id(), 'fr' );
// création ou mise à jour de la traduction cible ici
$progress['done']++;
} catch ( Exception $e ) {
$progress['errors'][] = array(
'id' => $product->get_id(),
'message' => $e->getMessage(),
);
}
// libère la mémoire des objets WordPress accumulés
wp_cache_flush();
}
$progress['offset'] += $batch_size;
file_put_contents( $progress_file, wp_json_encode( $progress ) );
echo "Lot terminé. Total traité : {$progress['done']}\n";
L’appel à wp_cache_flush() à chaque fiche, bien que coûteux en performance pure, évite l’accumulation en mémoire du cache d’objets WordPress qui provoque, sur des traitements longs, un épuisement mémoire progressif difficile à diagnostiquer autrement.
Étape 3 : orchestrer les lots via une tâche cron ou WP-CLI
Deux options s’offrent pour enchaîner les lots. La première, la plus simple à surveiller, consiste à lancer manuellement chaque lot via WP-CLI (wp eval-file) dans une boucle shell avec une pause entre chaque appel, en observant les logs en direct. La seconde, adaptée à une fenêtre de maintenance nocturne sans supervision humaine, enchaîne les lots via une tâche planifiée système (cron) qui relance le script toutes les deux minutes jusqu’à ce que le fichier de progression indique que l’offset dépasse le nombre total de fiches à traiter.
Étape 4 : calculer la fenêtre de maintenance avant, pas pendant
La question à trancher avant le lancement, jamais pendant, est le temps total estimé. Une exécution de test sur mille fiches donne un temps moyen par fiche, à multiplier par cent pour obtenir une estimation réaliste du temps total, en ajoutant une marge de trente pour cent pour absorber les lots plus lents (fiches avec beaucoup de variations, par exemple). Cette estimation permet de communiquer une fenêtre de maintenance réaliste aux équipes métier, plutôt que de découvrir en cours de nuit que le traitement dépassera largement l’horaire annoncé.
Étape 5 : traiter les erreurs après coup, sans bloquer le lot suivant
Le fichier de progression conserve la liste des fiches en erreur plutôt que d’interrompre le traitement du lot en cours. Une fois la migration terminée, une passe de reprise ciblée ne traite que les identifiants listés dans le tableau errors, ce qui évite de rejouer inutilement les quatre-vingt-dix-neuf mille fiches qui se sont bien déroulées pour corriger les quelques dizaines qui ont échoué.
- Mesurer le temps et la mémoire réels sur un échantillon avant de dimensionner le lot final
- Écrire un mécanisme de reprise basé sur un fichier de progression persistant, jamais en mémoire seule
- Libérer explicitement le cache d’objets à chaque fiche traitée pour éviter l’épuisement mémoire
- Isoler les erreurs dans une liste séparée pour une reprise ciblée après la migration principale
- Communiquer une fenêtre de maintenance calculée sur un échantillon réel, jamais sur une estimation optimiste
En résumé
À l’échelle de cent mille fiches, la différence entre une migration réussie et une nuit blanche tient presque entièrement à la discipline du traitement par lots : dimensionner correctement, journaliser la progression, libérer la mémoire, isoler les erreurs. Rien de spectaculaire techniquement, mais chaque étape négligée se paie cash au milieu de la nuit, précisément au moment où personne n’a envie de déboguer un script figé.