Migrer un catalogue de huit mille fiches produits vers des descriptions reformulées par IA ne se fait pas en une boucle foreach lancée depuis une tâche cron unique : au-delà de quelques centaines d’appels, on se heurte aux limites de débit du fournisseur, aux timeouts d’exécution PHP, et à l’absence totale de visibilité sur l’avancement réel du traitement. Cet article documente l’architecture que nous avons mise en place pour ce type de volume, avec Action Scheduler comme colonne vertébrale.
Cet article ne traite pas la question du coût de ces appels en volume, abordée séparément : il se concentre sur l’architecture de traitement en file d’attente elle-même.
Pourquoi une tâche cron classique ne suffit pas
Une tâche déclenchée par wp_schedule_event() s’exécute dans le cycle de vie classique d’une requête WordPress, avec une limite de temps d’exécution PHP qui rend impossible le traitement de plusieurs milliers d’appels API en une seule fois. Action Scheduler, la bibliothèque utilisée notamment par WooCommerce, résout ce problème en planifiant chaque unité de travail comme une action indépendante, traitée par lots successifs sans dépendre d’une seule exécution longue.
Mettre en file d’attente une action par élément à traiter
Plutôt que de planifier une seule grosse tâche, on planifie une action distincte par fiche produit à traiter, avec un léger décalage entre chacune pour respecter les limites de débit imposées par le fournisseur d’API.
function wpm_mettre_en_file_reformulation( array $ids_produits ) {
$delai = 0;
foreach ( $ids_produits as $id_produit ) {
as_schedule_single_action(
time() + $delai,
'wpm_reformuler_description',
array( 'produit_id' => $id_produit ),
'wpm-reformulation-ia'
);
$delai += 3; // Trois secondes entre chaque appel, pour respecter le débit autorisé.
}
}

Le traitement individuel, avec gestion des erreurs
Le callback exécuté pour chaque action gère l’appel à l’API, mais aussi le cas d’échec, en distinguant les erreurs qui justifient une nouvelle tentative de celles qui n’en justifient pas.
add_action( 'wpm_reformuler_description', function ( $produit_id ) {
$description = get_post_field( 'post_content', $produit_id );
$reponse = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
'timeout' => 20,
'headers' => array(
'Authorization' => 'Bearer ' . WPM_LLM_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'gpt-4o-mini',
'messages' => array(
array( 'role' => 'user', 'content' => 'Reformule cette description produit : ' . $description ),
),
) ),
) );
$code_statut = wp_remote_retrieve_response_code( $reponse );
if ( is_wp_error( $reponse ) || 429 === $code_statut || $code_statut >= 500 ) {
wpm_replanifier_avec_retry( $produit_id );
return;
}
if ( 200 !== $code_statut ) {
update_post_meta( $produit_id, '_wpm_reformulation_statut', 'echec_definitif' );
return;
}
$corps = json_decode( wp_remote_retrieve_body( $reponse ), true );
update_post_meta( $produit_id, '_wpm_description_reformulee', $corps['choices'][0]['message']['content'] ?? '' );
update_post_meta( $produit_id, '_wpm_reformulation_statut', 'terminee' );
} );
Le retry exponentiel, pour ne pas aggraver une limite de débit
En cas d’erreur 429, qui signale un dépassement de la limite de débit, réessayer immédiatement ne ferait qu’aggraver le problème. Le délai avant nouvelle tentative augmente donc à chaque échec, jusqu’à un nombre maximal de tentatives au-delà duquel l’action est marquée en échec définitif plutôt que retentée indéfiniment.
function wpm_replanifier_avec_retry( $produit_id ) {
$tentatives = (int) get_post_meta( $produit_id, '_wpm_tentatives', true );
if ( $tentatives >= 3 ) {
update_post_meta( $produit_id, '_wpm_reformulation_statut', 'echec_definitif' );
return;
}
update_post_meta( $produit_id, '_wpm_tentatives', $tentatives + 1 );
$delai = pow( 2, $tentatives ) * 60; // 60s, puis 120s, puis 240s.
as_schedule_single_action(
time() + $delai,
'wpm_reformuler_description',
array( 'produit_id' => $produit_id ),
'wpm-reformulation-ia'
);
}
Suivre l’avancement sans deviner
Sur un lot de huit mille fiches, l’équipe cliente avait besoin de savoir où en était le traitement sans consulter les logs serveur. Une page d’administration simple interroge les métadonnées de statut pour afficher un décompte par état : en attente, terminé, échec définitif, avec un bouton pour relancer manuellement les échecs.
Ce que cette architecture a permis de tenir
Le lot complet de huit mille fiches s’est traité en un peu moins de deux jours, sans intervention manuelle, avec un taux d’échec définitif inférieur à 1 %, principalement des fiches au contenu source vide ou corrompu, détectées et signalées pour un traitement manuel séparé.
Une file d’attente qui ne sait pas dire où elle en est n’est pas beaucoup plus rassurante qu’une boucle qui tourne en silence. Le suivi d’avancement n’est pas un confort, c’est une condition de confiance de l’équipe cliente.
Notre verdict
Pour tout traitement en volume, Action Scheduler apporte exactement ce qu’une boucle simple ne peut pas offrir : un traitement par unité indépendante, un espacement contrôlé des appels, et une reprise sur erreur qui ne demande aucune surveillance humaine constante pendant l’exécution.