Bien gérer un script PHP un peu long

emualliug

WRInaute impliqué
Bonjour à tous,

Un problème un peu inédit pour moi se présente. Je pense avoir la solution, mais j'aimerai votre avis sur la théorie avant de me lancer dans le codage.

La situation
Je vais avoir un script dont l'exécution pourrait se révéler un peu longue (de l'ordre de quelques minutes) ; il s'agit d'une boucle faisant un à deux appel SQL.

Les problèmes
  • Durée max du script PHP ; ce problème n'est pas essentiel puisque je suis sur un serveur Linux et que le temps du script est essentiellement lié à l'interrogation de la base qui n'est pas décomptée, mais ce serait le cas si le site venait à être exécuté sur un serveur Windows.
  • UX, il n'est pas bon que l'utilisateur soit confronté à une page lente.
  • Durée max HTTP, il pourrait être mis fin à la connexion si rien n'est renvoyé.

L'esquisse de solution
Voici ce que je pense faire :

L'utilisateur remplit le formulaire avec tous les paramètres et l'envoi en POST. Une première page vérifie que tout est OK (droits de l'utilisateur, cohérence du formulaire, etc.) ; si tout est bon, au lieux d'exécuter l'opération, elle enregistre les variables en y ajoutant un identifiant généré aléatoirement dans la SESSION de l'utilisateur courant et renvoi vers une deuxième page.

Cette deuxième page contient un script (jQuery) qui lance une requête POST en fournissant comme argument l'identifiant généré aléatoirement. Cela permet au serveur de savoir quel script exécuter, éviter qu'il le soit deux fois, et tout devrait se faire en arrière plan. Au fur et à mesure de l'avancement du script, une variable enregistrée en SESSION indique l'avancement, en outre un set_time_limit(30) sera inclus dans la boucle pour éviter tout problème de ce côté là.

La deuxième page contient un autre script qui émettrait une requête (toujours en jQuery), toutes les 2 secondes (moins ? plus ?) qui demande où en est le script (en utilisant encore l'identifiant généré aléatoirement). Quand cette requête lui indique que le script est fini, elle lance une redirection vers une troisième page qui affiche le résultat.

Naturellement, pour que ça marche, il faut pas laisser traîner les session ouvertes.

Les questions

Est-ce que ça vous paraît cohérent ? tenable et sûr ? est-ce que j'ai oublié quelque chose ?

Que se passe-t-il si l'utilisateur quitte la page ? pas grand chose je suppose, puisque le script principal est déjà lancé et qu'on attend pas de réponse de ce côté-ci.

Qu'est-ce qui se passerait en revanche si l'utilisateur se déconnecte ? Comment serait traitée session_start() et l'enregistrement dans $_SESSION lorsque l'utilisateur courant se sera déconnecté ?
 

rick38

WRInaute passionné
La 2e page n'a pas besoin d'exécuter le script en ajax car le 1er script PHP peut déjà le faire avec un exec(), par exemple exec('curl -X POST -d "param1=value" https://www.example.com/truc.php > /dev/null 2>&1 &');
Ensuite pour aller checker que l'opération est finie, oui l'ajax toutes les 2 secondes pourquoi pas.
Il y a une façon plus propre mais plus compliquée à implémenter qui est d'utiliser une notification push (en utilisant un service comme Pusher si on a pas envie d'installer un serveur pour ça), c'est le script php qui envoie l'info au client js, et non le client js qui va chercher l'info, donc une seul requête envoyée serveur->client au lieu que le client fasse toutes les 2 secondes client->serveur->client.

Sinon j'ai l'intuition que des requêtes qui mettent plusieurs minutes, c'est qu'il y a un problème de conception des requêtes ou de la base de données. Attendre plusieurs minutes (sans même savoir combien de temps il reste), ça n'est pas génial, des gens vont partir à ce moment-là.
 

emualliug

WRInaute impliqué
Merci pour ton retour.

L'usage de exec() est en effet une très bonne idée.

Pour le webpush, ça me paraît un peu compliqué et je ne vois pas de gain qui récompense l'effort dans mon cas.

Sinon j'ai l'intuition que des requêtes qui mettent plusieurs minutes, c'est qu'il y a un problème de conception des requêtes ou de la base de données.

Tu as tout à fait raison sur le principe, mais ici je ne pense pas que la structure de la base de donnée soit en cause, ou en tout cas je ne vois pas comment l'améliorer.

La requête est un peu complexe (rien d'incroyable, des jonctions avec des sous-requêtes), mais pas excessivement longue non plus à l'exécution (de l'ordre du centième de seconde sur mes tests, je table sur le dixième de seconde en production), rien d'affolant donc. Cependant, je suis obligé de la répéter plusieurs fois (avec une écriture à chaque fois), parce que chaque requête dépend de la modification qui a été induite par le résultat de la requête précédente, et cela peut être itéré quelques milliers de fois.

En l'espèce, l'utilisateur qui lance le script sait qu'il demande une opération "en masse", ça reste une action exceptionnelle, je pense qu'il est prêt à ce qu'elle prenne un peu de temps. Je veux surtout qu'il sache que c'est bien en cours.
 

rick38

WRInaute passionné
En fait mon idée du exec() n'est pas bonne, parce que ce script ne sera pas exécuté dans la même session, donc je ne vois pas comment il indiquerait quand il est terminé.

Ce principe devrait s'appliquer à tout ce qui (ou qui peut) prend du temps : appel d'une API, envoi d'un mail, ...

Mais même problème que mon exec(), ici la difficulté est que le visiteur doit être informé dans sa page que le script est en cours d'exécution, et quand il est terminé (ou quand il plante).
Ca demande à faire des trucs un peu tordus alors que finalement son idée d'utiliser la session pour stocker l'état du script était plus simple.
 
Dernière édition:

spout

WRInaute accro
Enregistrer l'état (running/done) dans une DB et interroger l'état en AJAX régulièrement (setInterval) (le plus simple) ou notification push en WebSocket.
 

emualliug

WRInaute impliqué
un worker qui tourne en tâche de fond (CLI)

Merci, j'irai jeter un œil voir ce que je peux en récupérer. Je ne suis pas complètement fermé à l'idée de stocker les valeurs intermédiaires ailleurs que dans le $_SESSION (BDD ou disque). Le $_SESSION avait toutefois l'avantage de la simplicité, et d'une certaine efficience aussi, puisque de toute façon il sera lu pour vérifier les droits de l'utilisateur courant, je m'évite donc une opération de plus.

mon idée du exec() n'est pas bonne, parce que ce script ne sera pas exécuté dans la même session

Arf, c'est dommage. Merci pour la précision en tout cas.

interroger l'état en AJAX régulièrement (setInterval)

Tiens d'ailleurs en y réfléchissant, je me dis que ce serait plus malin de faire une fonction récursive et de l'appeler après avoir eu le retour de la requête précédente, le cas échéant avec un setTimeout (comme proposé ici), pour éviter d'envoyer plusieurs requêtes à intervalle fixe sans avoir eu la réponse de la précédente.
 

emualliug

WRInaute impliqué
J'ai poursuivi ma réflexion.

Les solutions de queues sont sans doute assez intéressantes, mais je les trouve trop complexes.

L'idée d'un passage par exec(curl) me plaisait pas mal, parce que je ne compte ainsi pas sur une action lancée depuis le client pour réaliser l'opération.

Quitte à passer par curl, autant utiliser la bibliothèque de curl directement dans PHP, en précisant que je n'attends rien en retour et avec un CURLOPT_TIMEOUT de 1ms pour passer rapidement à autre chose (inspiré de cette réponse sur stackoverflow). Oui, mais l'identifiant de session ? et bien finalement il n'y a qu'à l'envoyer avec la requête avec un bête :
Code:
curl_setopt($curl, CURLOPT_COOKIE, session_name() . '=' . session_id());

hop ! problème réglé !

Sauf que réflexion faite, je ne trouve pas naturel de passer par une commande de réseau pour exécuter une commande locale. Alors, oui, j'étais prêt à le faire avec AJAX, mais c'était contraint par le procédé. Et puis curl_exec() induit une légère latence.

Finalement, je pense qu'appeler directement le script qui m'intéresse par un exec() est plus direct, plus propre, et plus adapté pour un script potentiellement un peu long. Bon, on en revient alors au problème de l'absence de $_SESSION. Je sais pas trop si y'a moyen de manipuler les variable de session lorsque PHP est exécuté depuis la ligne de commande.

De toute façon, je ne suis pas opposé à passer par une base de donnée pour manipuler les données relatives à l'avancement du script. J'y vois même quelques avantages en terme de journalisation et de suivi des opérations.
 

Discussions similaires

Haut