Bonjour,
J'ai une fuite mémoire sur un script PHP, qui me provoque des plantages avec OOM-killer.
Ce script PHP effectue une requête SQL pour récupérer toutes les lignes d'une table PostgreSQL, et génère en RAM un fichier CSV que le navigateur WEB télécharge.
J'ai l'habitude de faire des requêtes PostgreSQL qui me retournent des quantités de données importantes sans avoir de problème de mémoire, donc je pense que c'est la génération du fichier CSV qui pose problème.
Une fois généré le fichier CSV a une taille de 24.2MB (il y'a environ 200000 lignes)
Voici quelques lignes du script :
connect_bdd($connexion_bdd);
$query = SELECT * FROM ma_table
$result = pg_query ($connexion_bdd, $query);
if ($result)
{
$nb = pg_num_rows($result);
echo "# nb = $nb\n\n";
while ($cells = pg_fetch_row($result))
{
echo $cells[0];
echo ";";
echo $cells[1];
echo ";";
echo $cells[2];
echo ";";
echo $cells[3];
echo ";";
...
}
pg_free_result($result);
}
}
else
{
error_log('ERREUR : Erreur pg_query_params');
error_log('pg_result_error : Erreur' + pg_result_error($result));
}
disconnect_bdd($connexion_bdd);
Voici la consommation mémoire lorsque je démarre mon serveur apache2 :
top - 10:53:40 up 2:09, 4 users, load average: 1,19, 1,51, 1,44
Tasks: 88 total, 1 running, 87 sleeping, 0 stopped, 0 zombie
%Cpu(s): 4,8 us, 0,0 sy, 0,0 ni, 95,2 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st
KiB Mem: 500780 total, 113632 used, 387148 free, 96 buffers
KiB Swap: 0 total, 0 used, 0 free, 39752 cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4812 www-data 20 0 23280 4140 552 S 0,0 0,8 0:00.00 apache2
4813 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4815 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4816 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4817 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
Une fois le fichier généré et téléchargé par le navigateur, la consommation mémoire est :
top - 10:58:36 up 2:13, 4 users, load average: 1,74, 1,68, 1,52
Tasks: 90 total, 3 running, 87 sleeping, 0 stopped, 0 zombie
%Cpu(s): 4,8 us, 4,8 sy, 0,0 ni, 90,5 id, 0,0 wa, 0,0 hi, 0,0 si, 0,0 st
KiB Mem: 500780 total, 277236 used, 223544 free, 1456 buffers
KiB Swap: 0 total, 0 used, 0 free, 99672 cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4812 www-data 20 0 117m 100m 2536 S 0,0 20,6 0:26.72 apache2
4813 www-data 20 0 23280 4140 552 S 0,0 0,8 0:00.00 apache2
4815 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4816 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4817 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
4825 www-data 20 0 23280 4132 544 S 0,0 0,8 0:00.00 apache2
Donc une fois le script PHP fini, le fichier CSV téléchargé, le processus apache2 de PID 4812 ne libère pas sa mémoire.
Et au bout de quelques fichiers générés apache2 sature la mémoire RAM.
Pourtant je ne vois pas d'erreur dans mon script.
Au niveau configuration du serveur apache2 pour vérifier que ce n'est pas un problème de cache j'ai modifié la configuration de /etc/defaults/apache2 :
## cache size
# HTCACHECLEAN_SIZE=300M
HTCACHECLEAN_SIZE=20M
## interval: if in daemon mode, clean cache every x minutes
# HTCACHECLEAN_DAEMON_INTERVAL=120
HTCACHECLEAN_DAEMON_INTERVAL=1
mais je ne pense pas que ce soit lié.
A noter que je suis sur une plateforme ARM Cortex-A8 DM3730, 512MB de RAM, Debian Wheezy, c'est peut être bug du serveur apache2 dans la nouvelle version de Debian mais ça m'étonnerai.
# marrant
Posté par fearan . Évalué à 1.
Quand je vois
j'ai tendance à penser a join, ^ un coup de google php join, et je tombe sur implode
echo implode(";", $cells);
ou
echo join(";", $cells);
ça ne devrait pas trop t'aider, mais ça rends le code plus concis .
Ensuite je ne sais pas si c'est du à un mauvais copier/coller, mais le pg_free_result($result); est indenté comme étant dans le while, et les {} n'ont pas l'air équilibré ;)
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: marrant
Posté par ilip . Évalué à 1.
Le code n'est pas complet, je n'ai mis que le code qui peut être utile à comprendre le problème.
Dans le vrai code, le pg_free_result n'est pas dans la boucle while.
J'ai fais un test en utilisant la fonction memory_get_usage en début et fin du script PHP, qui mesure la mémoire allouée par le script (http://php.net/manual/en/function.memory-get-usage.php).
J'obtiens 161984 bytes au début et 165152 bytes à la fin.
Donc apparement la mémoire RAM consommée par le processus apache ne vient pas de la mémoire allouée par le script PHP..
# comme ca, je dirais..
Posté par maboiteaspam . Évalué à 2.
… Qu'il te manque un coup de flush.
tu fais es echos pour écrire ta donnée sur la sortie standard, mais les 24mo reste dans le buffer d'apache tant que tu n'as pas intimé l'ordre à apache de se vider chez le client.
http://www.php.net/manual/en/function.flush.php
Il précise dans la doc qu'avec mod_gzip activé sur apache, cet appel de flush restera sans effet.
Les commentaires sur php.net, comme souvent, sont précieux de détails divers et variés à consulter obligatoirement.
Autrement, avec le bout de code fournit, je ne vois pas ce qui pourrait bugger. Et n'ayant pas d'expérience de pgsql, je ne saurais connaître ses subtilités.
Dans ta situation, j’essaierais de virer le bruit de mon script pour le focaliser sur un subset du code suffisamment restreint pour tenter d'identifier l'origine du problème.
Dans ton exemple je ferais une version qui ne fais que exécuter / boucler - traiter les résultats de requêtes, sans echo, ni sauvegarde en variable.
Si rien ne se dévoile, alors une autre version avec les echos ect.
Et ainsi de suite.
Finalement, étant donné un numéro de version de php (j'imagine qu'étant sous débian tu ne tournes pas sur la toute dernière version) tu pourrais aussi vérifier les changelogs pour voir si un bug ne semble pas te concerner de près ou de loin,
http://www.php.net/ChangeLog-5.php
il est assez régulier qu'un ou plusieurs leaks soit corrigés.
a+
# Re: Forum Programmation.php— fuite mémoirePHP
Posté par Juke (site web personnel) . Évalué à 1.
tu peux forcer le passage du garbage collector via gc_collect_cycles()
http://fr.php.net/manual/fr/function.gc-collect-cycles.php
et plutôt que de reinventer la roue, tu peux utiliser fputcsv()
http://fr.php.net/manual/fr/function.fputcsv.php
[^] # Re: Forum Programmation.php— fuite mémoirePHP
Posté par maboiteaspam . Évalué à 1.
http://php.net/manual/en/features.gc.collecting-cycles.php
Je trouvais ton idée séduisante, mais après lecture de la doc, un peu moins.
# ecrire ta sortie dans un fichier CSV
Posté par NeoX . Évalué à 3.
autant utiliser le code prevu pour, fputcsv
qui prend un handle de fichier, un tableau, et genere la ligne de csv qui va bien
http://lmgtfy.com/?q=php+csv
# Script simplifié
Posté par ilip . Évalué à 1.
J'ai simplifié au maximul mon script pour localiser l'erreur :
Donc là pas de requête PostgreSQL, pas d'allocation en RAM, juste du "echo".
Le fichier téléchargé fait 30MB
A chaque fois que je lance la page sur un navigateur WEB et télécharge le fichier généré j'ai un nouveau processus apache2 qui utilise de la mémoire et qui ne termine jamais :
top -d ,2 -U www-data :
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3388 www-data 20 0 23384 5880 1728 S 0,0 1,2 0:57.44 apache2
3389 www-data 20 0 23392 5884 1728 S 0,0 1,2 0:57.03 apache2
3390 www-data 20 0 23392 5880 1732 S 0,0 1,2 0:29.83 apache2
3392 www-data 20 0 23352 5840 1732 S 0,0 1,2 0:29.83 apache2
3393 www-data 20 0 23384 5876 1728 S 0,0 1,2 1:43.94 apache2
3396 www-data 20 0 23384 5872 1728 S 0,0 1,2 0:53.93 apache2
…
Pourquoi ces processus ne se terminent pas ou ne libèrent pas leur mémoire ?
D'après moi c'est une erreur du serveur apache
Je testerai demain sur une Debian Squeeze au lieu de Wheezy
[^] # Re: Script simplifié
Posté par GG (site web personnel) . Évalué à 2.
Tu peux aussi essayer Nginx à la place de Apache.
Il y aussi d'autres serveur web… Cherokee par exemple.
Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html
[^] # Re: Script simplifié
Posté par ilip . Évalué à 1.
J'ai testé le même site WEB sur le serveur Nginx, qui semble assez répandu et performant pour des fortes charges avec une consommation mémoire faible.
Contrairement a apache2, la mémoire est bien libèrée après que le fichier ai été transmis au navigateur WEB :
Avant execution script PHP :
Pendant execution script PHP :
Après execution script PHP :
[^] # Re: Script simplifié
Posté par netsurfeur . Évalué à 4.
La directive MaxRequestsPerChild définit combien de requêtes un process apache traite avant qu'il se termine et libère sa mémoire.
La valeur par défaut, 10000, est bien adaptée pour servir du contenu statique; une valeur beaucoup plus faible est plus adaptée pour du contenu dynamique.
[^] # Re: Script simplifié
Posté par ilip . Évalué à 0.
Le problème c'est que en une seule requête PHP le processus apache2 utilise 54MB de mémoire,
Donc en 10 requête, j'arrive à 100% de mémoire utilisée, ma plateforme ARM en a 512MB.
Le paramètre MaxMemFree (http://httpd.apache.org/docs/2.2/mod/mpm_common.html#maxmemfree) semble plus adapté, je l'ai reglé à 10000 (en KB), mais ça n'a pas résolu le problème.
Exemple de consommation mémoire après 5 requêtes PHP :
[^] # Re: Script simplifié
Posté par GG (site web personnel) . Évalué à 3.
Bonjour,
si avec Nginx la mémoire est libérée à la fin du script, alors tu peux essayer Apache avec le mod-cgi.
Cela dit, Nginx te fera quand même beaucoup économiser en ressources, CPU et RAM.
Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html
[^] # Re: Script simplifié
Posté par netsurfeur . Évalué à 5.
Avec un MaxRequestsPerChild à 1, la mémoire devrait être libérée après chaque requête.
[^] # Re: Script simplifié
Posté par ilip . Évalué à 1.
Ok j'ai essayé MaxRequestsPerChild à 1 et ça fonctionne,
Une fois la requête finie, le processus apache qui a géré la requête se ferme et donc la mémoire est libérée.
Je trouve ça étonnant que apache même si apache garde ses processus fils ouverts pour être plus réactif, ceux-ci ne libèrent pas leur mémoire automatiquement.
Cela m'entrainait des erreurs de type OOM :
Merci pour ton aide
[^] # Re: Script simplifié
Posté par Marotte ⛧ . Évalué à 2.
Je suis content de voir que tu as trouvé une solution à ton problème mais j'ai une question (de béotien) sur une de tes remarques.
Garder les processus fils pour être réactif n'est-il pas incompatible avec une libération de la mémoire ? Je veux dire, si on libère la mémoire il faut recharger (en mémoire), alors il n'y aurait plus aucun gain de performance ?
[^] # Re: Script simplifié
Posté par ilip . Évalué à -1.
je veux bien qu il garde des choses en mémoire, mais là garder le resultat d une requete sql qui fait 50MB, où est la limite ?
je crois vraiment qu il s agit d un bug
ou alors ce sont des buffers de communication entre postgresql et php ou entre php et apache qui ne sont pas libérés
[^] # Re: Script simplifié
Posté par maboiteaspam . Évalué à 1. Dernière modification le 06 mars 2013 à 11:48.
hello,
j'ai copié coller ton script sur mon apache local ici.
un ps avant
un ps après le premier dl
un ps après plusieurs dl (10aine)
Donc effectivement pas le même constat en fin de compte.
a+
[^] # Re: Script simplifié
Posté par ilip . Évalué à 1.
Bonjour,
C'est lorsque je rajoute la requête SQL que la mémoire utilisée passe à ~50MB.
Pourtant il n'y a pas de fuite mémoire dans mon script PHP, j'apelle bien "pg_free_result" sur le résultat, le problème est ailleur.
J'ai l'impression que PHP ne libère pas réellement les variables liées a postgresql.
Peut être un problème au niveau du garbage collector.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.