Serveur qui se rempli à vitesse grand V!

WRInaute impliqué
Bonjour,

Depuis hier soir 23h00 mon serveur dédié linux se rempli jusqu'à n'avoir plus d'espace disque... Voici le graphe munin:

muninw.png


J'ai également ça:

munin2.png


Comment puis-je savoir dans quel répertoire il se rempli??... :(
Merci
 
WRInaute impliqué
Merci spout.
Tout viens de revenir à la normale il y a quelques minutes... J'ai pu observer l'espace disque récupéré en direct avec un df -h!... Je comprends pas ce qui s'est passé...
 
WRInaute accro
une purge des logs peut être ... chez moi en local c'est les seuls trucs qui me font tilter la machine si tu fait pas attention.
 
WRInaute occasionnel
C'est assez impressionnant comme montée :oops:
Garde une commande top lancée ? Tu as sûrement une piste à trouver dans les requêtes.
J'ai vu ça sur un Spip, une table de commentaires spammée de plusieurs Go. Tu peux le vérifier rapidement.
N'hésite pas à couper un des sites par le webmin si tu as qq chose de suspect dans le top et vérifie les dernières modifications de fichiers ?
 
WRInaute impliqué
Oui, le problème c'est qu'après tout redescend à la normale... Donc ce n'est pas une table qui s'incrémente ou des données injectées... Vu les graphes c'est le cache de MySQL qui a été utilisé et qui est redescendu... Mais pourquoi et comment?...
 
WRInaute passionné
A mon avis tu t'es prit un gros scan ou crawl d'un bot...
Ca peut arriver, mais du coup tu as découvert une "faille" de sécu sur ton site : si je mets un robot et que je fais des boucles pour afficher des pages, je rempli ton disque et fait donc planter ton dédié.

Je te conseillerais de regarder si ça ne serait pas les logs BIN SQL qui serait activé, car ça correspond plutôt pas mal (ou alors un log d'erreur)
 
WRInaute impliqué
Malheureusement j'ai regardé tous les logs et déjà aucun n'avait une taille anormale... Et je n'ai rien trouvé dedans... Pour ce qui est de MySQL, je n'ai que les slow d'activés...
 
WRInaute impliqué
Bon ben c'est reparti... Mon serveur se rempli à nouveau. Mêmes symptomes... requetes MySQL + espace disque qui grossi. J'ai le process php5 qui est en haut du "top"...
 
WRInaute impliqué
J'ai réussi à m'en sortir comme ça... J'ai fait un kill du process php5 et les requêtes MySQL ont cessé. L'espace disque a été récupéré un peu plus tard, automatiquement...
 
WRInaute occasionnel
C'est déjà une piste. Maintenant il faut trouver le nid de ce script probablement malveillant à mon avis.
Edit : ton php.ini est-il optimisé pour la sécurité ? et le htaccess bloque-t-il les exploits communs ?
 
WRInaute impliqué
En fait j'ai trouvé des requêtes intempestives et je crois que j'ai repéré le script (executé par une tache CRON). Ce que je ne comprends pas c'est qu'il s'execute 4 fois par mois et que ce n'est que le 9 de chaque moi qu'il génère ce problème... C'est un script qui cherche un fichier texte et insère les lignes dans une table MySQL. Mais pourquoi fait il grimper l'espace disque à ce point?...
 
WRInaute occasionnel
Un vice dans ton code ? Une boucle ou un traitement qui ne s'arrête pas.
Peut-être que ta confi a changé et que le code n'est pus adapté ?
Il est vital ce script ?
 
WRInaute impliqué
Oui, j'en ai besoin. Ce que je ne comprends pas c'est pourquoi que le 9 du mois alors qu'il s'execute 4 fois par mois...

Le script:

Code:
$url = "http://www.siteweb.com/cron/products_fr.txt";
$file = fopen ("$url", "r");

while (!feof ($file)) {
	$line = fgets ($file);
	if (preg_match("@product_category@",$line)) { continue; }
	$listl2 = explode("	", $line);
	$famille_id = "";
	$marque_id = "";
	$description = addslashes($listl2[4]);
	$famarray = explode(">",$listl2[5]);
	$nbre_sep = count($famarray);
	$nbre_sep=$nbre_sep-1;
	$famille = addslashes($famarray[$nbre_sep]);

	$marque = addslashes($listl2[7]);
	$modele = addslashes($listl2[1]);
	$listl2[2] = trim(preg_replace("@\?utm_source=products\&utm_medium=merchant@i","",$listl2[2]));
	$listl2[2] = $listl2[2]."/349";

	$idarray = explode("/",$listl2[2]);
	for ($i=0; $i < sizeof($idarray); $i++) {
		if (preg_match("@^([0-9]+)$@",$idarray[$i])) {
			$id = $idarray[$i];
			break;
		}
	}
	$result3 = mysql_query("select id from ".$prefix."_boutique where id='$id'");
	$nbre = mysql_num_rows($result3);
	if ($nbre > 0) {
		mysql_query("UPDATE ".$prefix."_boutique set modele='$modele', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]' where id='$id'");
	}else if ($modele != "") {
		mysql_query("INSERT INTO ".$prefix."_boutique set modele='$modele', id='$id', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]'");
	}
}
$compteur=0;
$result1 = mysql_query("select famille from ".$prefix."_boutique group by famille order by famille");
while (list($famille) = mysql_fetch_row($result1)) {
	$compteur++;
	mysql_query("UPDATE ".$prefix."_boutique set famille_id='$compteur' where famille='$famille'");
}
$compteur=0;
$result1 = mysql_query("select marque from ".$prefix."_boutique group by marque order by marque");
while (list($marque) = mysql_fetch_row($result1)) {
	$compteur++;
	mysql_query("UPDATE ".$prefix."_boutique set marque_id='$compteur' where marque='$marque'");
}
 
WRInaute occasionnel
Ben tu poses le script quelque part où tu peux y accéder par une url et tu fais afficher les variables pour voir ce qu'il se passe (en commentant les requêtes. C'est du debug archaïque mais ça peut aider.
Par ex. tu affiches tes valeurs de compteur, les requêtes qui vont être exécutées etc ... petit à petit tu fais tourner le script à 100%. Si ça foire, tu le verras bien.
 
WRInaute impliqué
Ah oui, ça j'ai déjà fait... Tout parait correct :-(
Mais le fait que ça n'arrive qu'à une date précise me fait penser qu'il y a un élément externe au script qui provoque ça... J'ai désactivé le script et j'attends le 9 du mois prochain pour voir si ça ne le fait plus. Si non, je remets les trois autres dates sauf le 9 pour voir...
 
WRInaute accro
Quand on regarde le code de ton script vite fait c'est en effet assez anodin seulement tu as dès le départ deux boucles imbriquées (ligne 5 et 23) qui en plus d'exécuter des expressions régulières (c'est pas forcement léger de plus tu semble shooter un contenu fixe ce qui est pas le meilleur moyen), font des requêtes d'update ou d'insert.
A ce stade je tenterait une concaténation de la partie SQL pour ne la faire qu'en une fois a la sortie des boucles (je me connecte je passe ma requête concaténé je ferme je passe a la suite).

Ensuite tu repart dans deux itérations d'update qui sont loin d'être légère si la table boutique est un peu musclée. Là encore tu pourrait envisager de regrouper les update afin d'éviter de multiples accès a la base.
Toujours dans ces deux dernière boucles tu utilise un group by je pense qu'il est impossible de s'en passer mais sachant que tu traite tous les champs "famille" est il necessaire de demander une sortie ordonnée (order by famille) ? ce dernier traitement SQL si il peut être évité te fera peut être gagner des ressources.

Pour se faire une idée de l'impact SQL de ce script il faudrait connaitre le nombre de lignes du fichier product que tu parse.

Après je note une opération qui me semble "pas pertinente" ligne 29 :

Code:
$result3 = mysql_query("select id from ".$prefix."_boutique where id='$id'");
       $nbre = mysql_num_rows($result3);
       if ($nbre > 0) {
          mysql_query("UPDATE ".$prefix."_boutique set modele='$modele', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]' where id='$id'");
       }else if ($modele != "") {
          mysql_query("INSERT INTO ".$prefix."_boutique set modele='$modele', id='$id', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]'");
       }

select id from ".$prefix."_boutique where id='$id' semble vouloir dire donne moi l'id de boutique don l'id est X bref donne moi truc qui est truc :
1/ tu connais donc l'id as tu besoin de faire une requête ?
le test qui suit me laisse penser ($nbre > 0) que par cette requête tu détermine si l'id en question est présent ou pas. s'il ne l'est pas ($nbre = 0) alors tu fait une insertion sinon un update.

En imaginant que l'id est unique, perso je ferait un insert dans tous les cas (qui retournera une erreur si l'id existe déjà -> normal) et dans ce cas (erreur) je ferais un update ... A chaque itération tu gagne donc la requête de test et dans le cas ou l'id existe tu fait une seconde requête donc pour chaque itération c'est une ou deux requête au lieu de deux systématiquement avec l'utilisation de mysql_num_rows en moins ce qui n'est pas rien.
Note que tu peux aussi inverser la logique en forçant l'update et en faisant l'insert sur le cas en erreur (l'erreur se produira alors lors de l'update d'un record qui n'existe pas encore). C'est la structure des datas et la fréquence des nouveautés qui doit dicter le choix. Si tu as beaucoup de nouveaux id par rapport aux existant ti insert en premier si c'est l'inverse (ce qui serait plus logique) tu update en priorité et tu insert en cas d'erreur.

Pour finir je me demande si les deux dernière boucles de requêtes sont as un peut exagérées ou pour le moins mal pensées :

UPDATE ".$prefix."_boutique set famille_id='$compteur' where famille='$famille' sachant que $compteur++;

Je ne sais pas trop comment compteur se crée, ni sa logique, mais si il t'est possible de le gérer dans les boucles précédente tu pourrait peut être virer tout ça

Voila qque pistes qui ne règlent pas le souci du 9 de chaque mois mais c'est peut être tout simplement (si tu n'est pas sur un dédié) qu'un autre utilisateur exécute une tache aussi a cette date et heure (essaie de le décaler de 10 mn pour voir sans pour autant supprimer la tâche)
 
WRInaute impliqué
Bonjour Zeb,

zeb a dit:
Quand on regarde le code de ton script vite fait c'est en effet assez anodin seulement tu as dès le départ deux boucles imbriquées (ligne 5 et 23) qui en plus d'exécuter des expressions régulières (c'est pas forcement léger de plus tu semble shooter un contenu fixe ce qui est pas le meilleur moyen), font des requêtes d'update ou d'insert.
A ce stade je tenterait une concaténation de la partie SQL pour ne la faire qu'en une fois a la sortie des boucles (je me connecte je passe ma requête concaténé je ferme je passe a la suite).
[/code]
Ok, mais je ne suis pas un pro de la concaténation... :(

Ensuite tu repart dans deux itérations d'update qui sont loin d'être légère si la table boutique est un peu musclée. Là encore tu pourrait envisager de regrouper les update afin d'éviter de multiples accès a la base.
Toujours dans ces deux dernière boucles tu utilise un group by je pense qu'il est impossible de s'en passer mais sachant que tu traite tous les champs "famille" est il necessaire de demander une sortie ordonnée (order by famille) ? ce dernier traitement SQL si il peut être évité te fera peut être gagner des ressources.
La table fait 3287 lignes
Oui, en effet, je peux peut être me passer du tri sur cette requête...

Pour se faire une idée de l'impact SQL de ce script il faudrait connaitre le nombre de lignes du fichier product que tu parse.
Environ 10 000

Après je note une opération qui me semble "pas pertinente" ligne 29 :

Code:
$result3 = mysql_query("select id from ".$prefix."_boutique where id='$id'");
       $nbre = mysql_num_rows($result3);
       if ($nbre > 0) {
          mysql_query("UPDATE ".$prefix."_boutique set modele='$modele', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]' where id='$id'");
       }else if ($modele != "") {
          mysql_query("INSERT INTO ".$prefix."_boutique set modele='$modele', id='$id', description='$description', famille='$famille', famille_id='$famille_id', marque='$marque', marque_id='$marque_id', prix='$listl2[3]', adresse='$listl2[2]', photo='$listl2[9]'");
       }

select id from ".$prefix."_boutique where id='$id' semble vouloir dire donne moi l'id de boutique don l'id est X bref donne moi truc qui est truc :
1/ tu connais donc l'id as tu besoin de faire une requête ?
le test qui suit me laisse penser ($nbre > 0) que par cette requête tu détermine si l'id en question est présent ou pas. s'il ne l'est pas ($nbre = 0) alors tu fait une insertion sinon un update.
Oui, c'est exactement ça, je teste juste si il est présent.

En imaginant que l'id est unique, perso je ferait un insert dans tous les cas (qui retournera une erreur si l'id existe déjà -> normal) et dans ce cas (erreur) je ferais un update ... A chaque itération tu gagne donc la requête de test et dans le cas ou l'id existe tu fait une seconde requête donc pour chaque itération c'est une ou deux requête au lieu de deux systématiquement avec l'utilisation de mysql_num_rows en moins ce qui n'est pas rien.
Note que tu peux aussi inverser la logique en forçant l'update et en faisant l'insert sur le cas en erreur (l'erreur se produira alors lors de l'update d'un record qui n'existe pas encore). C'est la structure des datas et la fréquence des nouveautés qui doit dicter le choix. Si tu as beaucoup de nouveaux id par rapport aux existant ti insert en premier si c'est l'inverse (ce qui serait plus logique) tu update en priorité et tu insert en cas d'erreur.
C'est quoi la syntaxe "en cas d'erreur" pour la requête MySQL?

Voila qque pistes qui ne règlent pas le souci du 9 de chaque mois mais c'est peut être tout simplement (si tu n'est pas sur un dédié) qu'un autre utilisateur exécute une tache aussi a cette date et heure (essaie de le décaler de 10 mn pour voir sans pour autant supprimer la tâche)
Oui, en effet, c'est de l'optimisation, mais je ne pense pas que ce soit le problème d'origine. C'est un serveur dédié, donc personne d'autre... et au niveau processeur je suis à peine à 25% de CPU durant les pics...
 
WRInaute accro
je ne suis pas un pro de la concaténation
Code:
$requeteGlobal = '';
while(condition){
  (...)
  $marequete = 'truc machin; ';
  $requeteGlobal .= $marequete; // concaténation de chaque requête unitaire dans une requête globale
  (...)
}
mysql-query($requeteGlobal);

Environ 10 000
Mazette !!! ça fait un bilan carbone pas léger ... T'imagine que ton code fait pas moins de 20 000 requêtes dans la première série de boucle ! moi à sa place je te répond "t'as vue la vierge ?" (humour) même si php et mysql sont optimisés pour atténuer ce genre de comportement tu est limite je pense.

C'est quoi la syntaxe "en cas d'erreur" pour la requête MySQL?
Je ne l'ai pas en tête (mis a part le OR DIE) car j'évite ce genre de situation mais prend le temps de choper le code retourné vers php par mysql sur une table de test et tu sera vite fixé. et regarde ici

mais je ne pense pas que ce soit le problème d'origine
A l'origine tes table n'étaient pas si importantes et ton fichier surement pas aussi ... C'est de l'effet cumulatif a mon avis.

au niveau processeur je suis à peine à 25% de CPU durant les pics...
mouais je sais pas si c'est un détail significatif car c'est pas tant le "calcul" donc les spé intrinsèques du processeur qui sont sollicités que l'échange de données vers la base ce qui ne demande pas forcement de gros calculs mais en revanche de part le volume peut demander du temps et là l'effet pourrait être le même.
 
WRInaute impliqué
Code:
Mazette !!! ça fait un bilan carbone pas léger ... T'imagine que ton code fait pas moins de 20 000 requêtes dans la première série de boucle ! moi à sa place je te répond "t'as vue la vierge ?" (humour) même si php et mysql sont optimisés pour atténuer ce genre de comportement tu est limite je pense.
Ben pourtant il bronche pas, j'en ai 5 différents sur 5 bases/tables différentes de scripts comme celui ci et les fichiers texte sont un peu plus gros... Ils se lancent à 23h30 mais des jours différents :wink: Sur les graphes Munin on voit la charge mais elle est loin d'être au maxi (gros serveur).

Je ne l'ai pas en tête (mis a part le OR DIE) car j'évite ce genre de situation mais prend le temps de choper le code retourné vers php par mysql sur une table de test et tu sera vite fixé. et regarde ici
Ok, je vais regarder merci

A l'origine tes table n'étaient pas si importantes et ton fichier surement pas aussi ... C'est de l'effet cumulatif a mon avis.
Sincèrement je ne pense pas, il y a toujours eu énormément d'articles, et les mises à jour sont très légères (une 15aine d'article en plus tous les 2 mois)

mouais je sais pas si c'est un détail significatif car c'est pas tant le "calcul" donc les spé intrinsèques du processeur qui sont sollicités que l'échange de données vers la base ce qui ne demande pas forcement de gros calculs mais en revanche de part le volume peut demander du temps et là l'effet pourrait être le même.
Tous les autres graphes ne semblent pas montrer une souffrance quelconque du serveur. Mais je n'arrive pas à avoir les requêtes I/O sous munin, donc je ne ne sais pas ce que ça donne de ce côté là...
 
WRInaute accro
Recif a dit:
j'en ai 5 différents sur 5 bases/tables différentes
As tu regardé si ce script fautif était pas le seul a taper dans cette base .... auquel cas tu pourrait recentrer le souci sur cette base.
De plus cette base est sur la même machine physique ou sur une autre ?
 
WRInaute impliqué
Non, ce n'est pas le seul, mais le principal, les autres c'est en http en front office. Même machine physique.
Par contre en me replongeant à nouveau dans le code je viens de m’apercevoir que la structure du fichier txt avait changé! Et principalement les séparateurs, ce qui n'est pas rien! Don je subodore que les array se faisaient sur des caractères présents dans les descriptions, donc on devait avoir bien plus que 10 000 lignes!! J'ai corrigé tout ça et j'ai lancé le script. Les 10 000 lignes ont été avalées en moins de 10 secondes et la table a pris quelques centaines de lignes supplémentaires!
 
WRInaute impliqué
Pour l'optimisation je me suis servi de mysql_affected_rows car mysql_error retournait ok sur les updates... Mais j'ai lu l'avertissement qui disait qu'il ne falalit plus l'utiliser à partir de php 5.5! Je ne suis pas à cette version, mais pour le futur c'est aps top. D'ailleurs un paquet de scripts seraient obsolètes si je devais passer en 5.5. C'est vraiment pénible ces incompatibilités dans les différentes versions de php! :-((
 
WRInaute accro
Recif a dit:
plus l'utiliser à partir de php 5.5! Je ne suis pas à cette version, mais pour le futur c'est aps top. D'ailleurs un paquet de scripts seraient obsolètes si je devais passer en 5.5. C'est vraiment pénible ces incompatibilités dans les différentes versions de php! :-((
Oui j'ai vue aussi, ça me saoule d'autant que passer de fonctions natives bien rodées a une classe objet genre PDO de mémoire, ça ne garanti rien de mieux si ce n'est plus de conso mémoire et un code plus récent donc moins fiable...
Il semble de plus que ce soit la mode de faire changer (de force) les chose pour maintenir la politique de consommation bien conne qui gère le monde .... :cry:

bref quand le bon sens sera roi les hommes auront disparu.

Content que tu ai trouvé ton souci :wink:
 
Discussions similaires
Haut