Comment faire évoluer php5 + MySQL au-dessus de 200 requêtes / seconde?

16

Je modifie ma page d'accueil pour les performances, actuellement elle gère environ 200 requêtes / seconde sur 3.14.by qui mange 6 requêtes SQL, et 20 req / seconde sur 3.14.by/forum qui est le forum phpBB.

Curieusement, les chiffres sont à peu près les mêmes sur certains VPS et sur un serveur Atom 330 dédié.

Le logiciel serveur est le suivant: Apache2 + mod_php prefork 4 enfants (essayé différents nombres ici), php5, APC, nginx, memcached pour le stockage des sessions PHP.

MySQL est configuré pour consommer environ 30% de la RAM disponible (~ 150 Mo sur VPS, 700 Mo sur serveur dédié)

On dirait qu'il y a un goulot d'étranglement quelque part qui ne me permet pas d'aller plus haut, des suggestions? (c'est-à-dire que je sais que faire moins de 6 SQL le rendrait plus rapide, mais cela ne ressemble pas à un facteur limitant, car sqld ne mange pas plus de quelques% en haut en raison des requêtes mises en cache)

Quelqu'un a-t-il testé que lancer Apache2 pré-forké et laisser juste nginx + php est beaucoup plus rapide?

Quelques repères supplémentaires

Small 40-byte static file: 1484 r/s via nginx+apache2, 2452 if we talk to apache2 directly. 
Small "Hello world" php script: 458 r/s via ngin+apache2.

Mise à jour: Il semble que le goulot d'étranglement concerne les performances de MySQL sur les données mises en cache. La page avec un seul SQL affiche 354 req / sec, avec 6 SQL - 180 req / sec. Que pensez-vous que je puisse modifier ici? (Je peux débourser 100-200 Mo pour MySQL)

[client]
port        = 3306
socket      = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket      = /var/run/mysqld/mysqld.sock
nice        = 0

[mysqld]
default-character-set=cp1251
collation-server=cp1251_general_cs

skip-character-set-client-handshake

user        = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
port        = 3306
basedir     = /usr
datadir     = /var/lib/mysql
tmpdir      = /tmp
skip-external-locking

bind-address        = 127.0.0.1

key_buffer      = 16M
max_allowed_packet  = 8M
thread_stack        = 64K
thread_cache_size   = 16
sort_buffer_size    = 8M
read_buffer_size    = 1M

myisam-recover      = BACKUP
max_connections        = 650
table_cache            = 256
thread_concurrency     = 10

query_cache_limit       = 1M
query_cache_size        = 16M

expire_logs_days    = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet  = 8M

[mysql]
[isamchk]
key_buffer      = 8M

!includedir /etc/mysql/conf.d/
BarsMonster
la source
Pourquoi utilisez-vous à la fois Apache et nginx?
jamieb
Il s'agit d'une configuration courante, d'Apache2 à PHP et de diverses applications nécessitant une infrastructure Apache, nginx pour réduire l'empreinte mémoire d'Apache2 à la charge.
BarsMonster
En fait, je ne comprends pas votre problème. Votre site est-il actuellement lent? Si oui, est-ce lent? Et combien souhaitez-vous l'accélérer? Avez-vous tenté de profiler des parties de votre site pour déterminer où se trouve le goulot d'étranglement?
jamieb
C'est dans la description: maintenant c'est sur 180-200 requêtes / seconde. Bien que cela soit bien plus que suffisant pour une page d'accueil, je souhaite modifier cette configuration pour que d'autres sites construits sur la même base de code fonctionnent plus rapidement. Idéalement, je veux saturer la connexion 100Mbit avec des pages dynamiques :-)
BarsMonster
2
"Demandes par seconde" n'est pas vraiment une mesure significative dans ce contexte. Mon netbook pouvait gérer "200 requêtes par seconde". Vous devez nous indiquer le temps de réponse que vous souhaitez atteindre avec ce type de débit de connexion.
jamieb

Réponses:

29

De toute évidence, il y a beaucoup de choses que vous pouvez essayer. Votre meilleur pari est de chasser vos journaux pour les requêtes qui n'utilisent pas d'index (activer les journaux pour ceux-ci) et d'autres requêtes non optimisées. J'ai compilé une énorme liste d'options liées aux performances au fil des ans, j'ai donc inclus un petit sous-ensemble ici pour votre information - j'espère que cela aide. Voici quelques notes générales sur les choses que vous pouvez essayer (si vous ne l'avez pas déjà fait):

MySQL

  • query_cache_type = 1 - le cache des requêtes SQL est activé. S'il est défini sur 2, les requêtes ne sont mises en cache que si l'indication SQL_CACHE leur est transmise. De même avec le type 1, vous pouvez désactiver le cache pour une requête particulière avec l'indicateur SQL_NO_CACHE
  • key_buffer_size = 128M (par défaut: 8M) - mémoire tampon pour les index de table MyISAM. Sur les serveurs dédiés, visez à définir key_buffer_size sur au moins un quart, mais pas plus de la moitié, de la quantité totale de mémoire sur le serveur
  • query_cache_size = 64M (par défaut: 0) - taille du cache de requête
  • back_log = 100 (par défaut: 50, max: 65535) - La file d'attente des demandes de connexion en attente. N'importe que lorsqu'il y a beaucoup de connexions en peu de temps
  • join_buffer_size = 1M (par défaut: 131072) - un tampon utilisé lors de l'analyse complète des tables (pas d'index)
  • table_cache = 2048 (par défaut: 256) - devrait être max_user_connections multiplié par le nombre maximal de JOIN que contient votre requête SQL la plus lourde. Utilisez la variable "open_tables" aux heures de pointe comme guide. Regardez aussi la variable "open_tables" - elle devrait être proche de "open_tables"
  • query_prealloc_size = 32K (par défaut: 8K) - mémoire persistante pour l'analyse et l'exécution des instructions. Augmenter si vous avez des requêtes complexes
  • sort_buffer_size = 16M (par défaut: 2M) - aide au tri (opérations ORDER BY et GROUP BY)
  • read_buffer_size = 2M (par défaut: 128K) - Aide avec les analyses séquentielles. Augmentez s'il existe de nombreux scans séquentiels.
  • read_rnd_buffer_size = 4M - aide la table MyISAM à accélérer la lecture après le tri
  • max_length_for_sort_data - taille de ligne à stocker au lieu du pointeur de ligne dans le fichier de tri. Peut éviter les lectures de table aléatoires
  • key_cache_age_threshold = 3000 (par défaut: 300) - temps pour garder le cache des clés dans la zone chaude (avant qu'il ne soit rétrogradé pour se réchauffer)
  • key_cache_division_limit = 50 (par défaut: 100) - active un mécanisme d'éviction de cache plus sophistiqué (deux niveaux). Indique le pourcentage à conserver pour le niveau inférieur. delay_key_write = ALL - le tampon de clé n'est pas vidé pour la table à chaque mise à jour d'index, mais uniquement lorsque la table est fermée. Cela accélère beaucoup les écritures sur les clés, mais si vous utilisez cette fonctionnalité, vous devez ajouter une vérification automatique de toutes les tables MyISAM en démarrant le serveur avec l'option --myisam-recover = BACKUP, FORCE
  • memlock = 1 - processus de verrouillage en mémoire (pour réduire le swapping in / out)

Apache

  • changer la méthode de frai (en mpm par exemple)
  • désactiver les journaux si possible
  • AllowOverride None - dans la mesure du possible, désactivez .htaccess. Il arrête Apache pour rechercher des fichiers .htaccess s'ils ne sont pas utilisés afin d'enregistrer une demande de recherche de fichier
  • SendBufferSize - Défini sur le système d'exploitation par défaut. Sur les réseaux encombrés, vous devez définir ce paramètre près de la taille du plus gros fichier normalement téléchargé
  • KeepAlive Off (par défaut On) - et installez lingerd pour fermer correctement les connexions réseau et c'est plus rapide
  • DirectoryIndex index.php - Gardez la liste des fichiers aussi courte et absolue que possible.
  • Options FollowSymLinks - pour simplifier le processus d'accès aux fichiers dans Apache
  • Évitez d'utiliser mod_rewrite ou au moins des expressions rationnelles complexes
  • ServerToken = prod

PHP

  • variables_order = "GPCS" (si vous n'avez pas besoin de variables d'environnement)
  • register_globals = Off - en plus d'être un risque pour la sécurité, il a également un impact sur les performances
  • Gardez include_path aussi minimal que possible (évite les recherches de système de fichiers supplémentaires)
  • display_errors = Off - Désactive l'affichage des erreurs. Fortement recommandé pour tous les serveurs de production (n'affiche pas de vilains messages d'erreur en cas de problème).
  • magic_quotes_gpc = Off
  • magic_quotes _ * = Off
  • output_buffering = On
  • Si possible, désactivez la journalisation
  • expose_php = Off
  • register_argc_argv = Off
  • always_populate_raw_post_data = Désactivé
  • placez le fichier php.ini où php le recherchera en premier.
  • session.gc_divisor = 1000 ou 10000
  • session.save_path = "N; / path" - Pour les grands sites, envisagez de l'utiliser. Divise les fichiers de session en sous-répertoires

Tweaks OS

  • Montez les disques durs utilisés avec l'option -o noatime (pas de temps d'accès). Ajoutez également cette option au fichier / etc / fstab.
  • Ajustez / proc / sys / vm / swappiness (de 0 à 100) pour voir ce qui a les meilleurs résultats
  • Utiliser des disques RAM - mount --bind -ttmpfs / tmp / tmp
Ivan Peevski
la source
C'est une belle liste, j'en ai déjà eu la majorité, et quand j'ai ajouté des choses restantes, les performances n'ont pas augmenté. On dirait que le goulot d'étranglement est quelque part entre PHP et MySQL ne pouvant pas gérer plus de 800 requêtes par seconde à partir du cache de requêtes ...
BarsMonster
Ok, comment vous connectez-vous à la base de données (mysql_pconnect () au lieu de mysql_connect ())? Utilisez-vous des connexions persistantes? essayez dans les deux sens ...
Ivan Peevski
Je suis déjà sur pconnect et le pool de connexions est activé dans php.ini ...: -S
BarsMonster
Juste pour être complet, j'essaierais simplement de me connecter. J'ai vu des cas (en particulier dans les tests de charge) où cela fonctionne mieux.
Ivan Peevski
1

Si le goulot d'étranglement n'est pas le CPU, alors son IO - soit le réseau ou le disque. Donc .. vous devez voir combien d'E / S se passe. Je n'aurais pas pensé que c'était le réseau (sauf si vous êtes sur une liaison semi-duplex à 10 Mbps, mais cela vaut la peine de vérifier le commutateur au cas où la détection automatique ne ferait pas son travail correctement).

Cela laisse le disque IO, ce qui peut être un gros facteur en particulier sur les VPS. Utilisez sar ou iostat pour jeter un œil aux disques, puis google comment trouver plus de détails si votre disque est utilisé intensivement.

gbjbaanb
la source
Oui, le réseau n'est pas le problème - lors de l'exécution de ab à partir d'un serveur local, les performances sont les mêmes. J'ai vérifié le temps d'attente - il est inférieur à 0,01% -, fondamentalement, tout est dans le cache disque, et aucune écriture sur disque n'est impliquée dans le traitement de la demande (tous les journaux sont désactivés).
BarsMonster
1

J'examinerais la mise en cache avec Nginx ( memcached ) ou Varnish .

À tout le moins, vous devez serveur des fichiers statiques avec Nginx comme SaveTheRbtz dit.

Espennilsen
la source
Ce sont des pages dynamiques, donc je préférerais ne pas les mettre en cache.
BarsMonster
1
memcached n'est pas une application de mise en cache traditionnelle et peut faire des merveilles pour les pages dynamiques. Il se situe entre la base de données et votre application. Vous appliquez d'abord des requêtes memcached pour un objet, s'il n'est pas là, alors il est chargé à partir de la base de données. L'effet net est que vous utilisez la RAM pour servir vos demandes de base de données plutôt que le stockage persistant beaucoup plus lent sur la base de données.
jamieb
Memcache peut être utilisé avec nginx, qui est une fonctionnalité connue. Le stockage persistant plus lent n'est pas utilisé, tout est dans le cache de requêtes dans MySQL.
BarsMonster
Memcached et le cache de requêtes de MySQL ne sont pas vraiment comparables; ils ne font même pas la même chose. Vous êtes assez rapide pour abattre à peu près toutes les suggestions publiées ici sans prendre la peine de les comprendre. Je recommanderais que vous soyez un peu plus ouvert d'esprit.
jamieb
Je comprends clairement la différence entre le cache de requêtes memcached et MySQL. Mais en raison du fait que tout est dans le cache de requête avec un taux de réussite de 100%, je ne l'appellerais pas "stockage persistant lent". La réponse originale d'hier concernait l'utilisation de NginX + Memcached, ce qui est un scénario assez courant pour mettre en cache des pages entières. La mise en cache d'objets individuels est un autre scénario totalement différent. Bien que l'utilisation de memcached devant MySQL soit sur la table, je pense à obtenir plus de jus sans lui pour l'instant (car cela nécessiterait pas mal de changements de code).
BarsMonster
1

Comme le serveur ne semble pas poser de problème, le générateur de charge l'est peut-être. Essayez de l'exécuter sur plusieurs machines.

OliverS
la source
Les performances sont les mêmes, même si je l'exécute à partir du serveur lui-même. Peu importe la façon dont les connexions simultanées manu - 10 ou 50. Le test de charge se fait via ab -c 10 -t 10
BarsMonster
1

Il me semble que vous atteignez le nombre maximum de connexions qu'Apache permet. Jetez un œil à votre configuration Apache. L'augmentation de la limite du serveur et des clients max devrait aider si vous n'êtes pas déjà lié par une autre limite comme les E / S ou la mémoire. Regardez les valeurs présentes pour mpm_prefork_module ou mpm_worker_module et ajustez en conséquence pour répondre à vos besoins.

ServerLimit 512
MaxClients 512
Erik Giberti
la source
Eh bien, ai-je vraiment besoin de cela à condition d'avoir nginx devant apache2, donc je crois qu'il n'y a pas beaucoup de sens d'avoir plus que des cœurs physiques * 2 processus Apache2 ....
BarsMonster
Je viens de le vérifier. L'augmentation du nombre de processus Apache2 de 4 à 16 n'a pas amélioré les performances du tout (il a même chuté de 0,5%). L'augmentation du nombre de travailleurs nginx à 2 ou 4 n'a rien amélioré.
BarsMonster
1
Si vos données sont assez statiques, c'est-à-dire qu'elles ne mettent pas à jour tous les autres chargements de page, vous pouvez augmenter votre query_cache. MySQL conservera le jeu de résultats de cette façon et tirera de la mémoire. Cependant, si la table qui est mise en cache reçoit des écritures pendant ce temps, elle invalide le cache (même si les données ne sont pas affectées), ce qui en fait un gaspillage de mémoire.
Erik Giberti
En ce moment, je vois un taux de réussite de 100% dans le cache de requêtes, et MySQL semble toujours lent ...
BarsMonster
1
Ajoutez skip-name-resolver à votre fichier de configuration MySQL. Cela permettra d'économiser une recherche DNS sur chaque connexion au serveur. L'inconvénient ici est que toutes les connexions devront être verrouillées par IP (en supposant que vous n'utilisez pas '%'). Si le SQL se trouve sur le même serveur et n'a pas besoin d'être accédé ailleurs que sur localhost, vous pouvez également ajouter un saut de réseau pour tuer toute la pile TCP / IP. Cependant, je pense que le goulot d'étranglement est Apache.
Erik Giberti
0

Cette charge est-elle générée par un outil ou des charges réelles?

Vous voudrez peut-être vérifier memcached. J'ai vu des problèmes à des taux de connexion élevés provoquant une latence dans l'application.

Si vous utilisez un générateur de charge, qu'obtenez-vous en frappant une petite page statique?

Pendant les chargements, vous souhaiterez peut-être vérifier la pile réseau pour les conditions TIME_WAIT. Vous remplissez peut-être votre file d'attente de connexion.

Il y a environ 100 autres raisons et éléments que vous pouvez consulter, mais sans plus d'informations, je lance juste des suppositions à ce stade.

jeffatrackaid
la source
Il est testé via ab-c 10 -t 10 URL Je compare le serveur lui-même, donc le réseau ne devrait pas être le problème. J'ai publié plus de repères selon votre demande.
BarsMonster
Je ne dépenserais pas trop d'efforts pour régler avec ab. Vous trouverez peut-être que cela ne se traduit pas bien en performances réelles. Ce que vous pouvez faire, c'est disséquer votre application et tester chaque composant. Par exemple, frappez le serveur apache directement avec juste une très petite page statique. Cela vous donnera une idée de votre demande max / s sur le backend. Mettez nginx devant, testez à nouveau le même fichier backend. Puis testez avec une simple page php de type "hello world" Parfois, toutes les couches peuvent masquer quelque chose de simple. Surveillez également les connexions pendant le test. Assurez-vous que votre pile réseau ne se remplit pas.
jeffatrackaid
J'ai fait ces repères hier, et ils se trouvent dans la description de la question d'origine mise à jour. De plus, les tests sont effectués sur localhost, donc le réseau n'est pas un problème.
BarsMonster
Le réseau peut être un problème même lorsqu'il est effectué sur un hôte local. Peu probable dans votre cas, mais cela peut entraîner des problèmes. Au moins maintenant, vous avez une limite supérieure de ~ 450 req / sec avec votre configuration PHP actuelle. L'étape suivante consiste à déposer un appel de base de données et à voir comment cela change. J'aime séparer cela lorsque vous effectuez un réglage de haut niveau, car cela peut vraiment vous aider à identifier la couche à l'origine du plus de problèmes.
jeffatrackaid
-1

99% des problèmes de temps comme celui-ci remontent à la base de données. Assurez-vous que vos indices de frappe d'abord. Si cela ne fonctionne pas, commencez à mettre en cache tout ce que vous pouvez.


la source
Ce sont tous des index et comme je le disais, il atteint même le cache de requêtes MySQL dans 100% des cas
BarsMonster
-1

Je vous recommande d'utiliser (si possible) un pool de connexion pour garder la base de données connectée à vos applications Web (pas besoin de se reconnecter à chaque demande). Cela peut faire une énorme différence de vitesse.

Essayez également d'analyser toutes vos requêtes avec EXPLAIN (et pourquoi ne pas profiler vos requêtes avec SHOW PROFILE?).

Kedare
la source
Toutes les requêtes utilisent des index. Le pool de connexions MySQL est utilisé.
BarsMonster