Après avoir bien optimisé toute la partie frontend de mon site au sens HTML, CSS, JavaScript, etc, je me suis penché sur les aspects plus techniques et technologiques. Je parlerai donc de PHP, de MySQL, de serveur Apache2, de Memcached, d’Opcode mais aussi d’architecture applicative.
Pour pouvoir travailler dans de bonnes conditions j’ai loué un serveur dédié virtuel (VPS) chez Gandi. L’offre actuelle est composée de parts, une part représentant environ 1/60ème d’un gros serveur. J’ai choisi ce type de serveur car je n’ai pas réellement besoin d’un full dédié mais aussi pour l’architecture scalable fourni. En effet, il est possible de rajouter des parts à la volée, ponctuellement ou de manière programmée. On peut ainsi pallier une grosse surcharge automatiquement même si le temps de déclenchement est trop élevé (dépassement du seuil défini pendant 20 minutes au moins).
Installation et configuration
J’ai donc pris une part, vierge de toute installation à l’exception de la distribution Linux, Debian 5 dans mon cas. J’ai commencé par installer les services web utiles : Apache2, MySQL, PHP 5.3 et PhpMyAdmin. Ensuite, j’ai installé APC via la commande PECL (pour PHP 5.3 on veillera à bien prendre apc-beta). J’ai également installé Memcached et l’extension qui va bien pour l’utiliser avec PHP. Je passe les aspects configurations de sécurité du serveur en lui-même mais voilà ce que j’ai fait pour chaque application :
Apache2
Niveau configuration, rien n’a été modifié mis à part les vhosts et un paramètre dont je donnerai les détails plus bas. Du côté des modules, j’ai installé ceux qui vont bien : deflate,expires, headers, mem_cache, rewrite et php5.
MySQL
Aucune modification, sauf l’activation des logs lors des phases de test. Pour cela, on modifie le fichier my.cnf dans /etc/mysql/.
APC
Il faut l’activer dans le fichier php.ini :
Puis le configurer :
apc.enabled=1
apc.stat=0
apc.mmap_file_mask = /media/ramdisk1/apc.XXXXXX
Permet d’éviter les appels disque.
Définit l’endroit où sont stockés les fichiers de cache. Ici j’utilise un Ramdisk de 32Mo même si ce n’est pas forcément utile car APC utilise de base un segment de mémoire partagé. A voir plus bas une note à ce sujet.
Pour créer un Ramdisk, il faut utiliser le système de fichier tmpfs. Un exemple de ligne à insérer dans /etc/fstab :
Memcached
On ne touche à rien excepté la taille alloué que j’ai passé de 32Mo à 16Mo : suffisant et avec 256Mo de base, le mieux est de se restreindre.
Phases de tests
Avec tout ceci, j’ai pu faire des tests et voir ce que permettait un tel serveur. J’ai fait des Apache Bench (ab) de plusieurs manières. Les premiers n’ont pas été glorieux, un simple ab 200/5 donnait moins de 2 requêtes par seconde. Très mauvais du coup. De ce fait, trois pistes :
- Problème matériel : CPU trop faible, RAM insuffisante, lenteurs des accès disques.
- Problème logiciel : Apache2 mal configuré, MySQL saturé.
- Problème sur l’application en elle-même.
Point numéro 1 : Via @mrboo, je savais à quoi m’attendre et comme je n’ai pas le choix, je dois me contenter de ce que j’ai et l’optimiser. (Je fais le choix de tester une part unique). Au niveau des accès disques, ce type de serveur obtient de bons scores. Puis un simple htop me montrait bien qu’il se passait quelque chose ailleurs.
Point numéro 2 : MySQL est en quelques sortes protégé par Memcached puisque ce dernier stocke les résultats des requêtes SQL pendant un certain temps t. Ce n’est pas là que ce situe le problème.
Au niveau d’Apache2, j’ai modifié le paramètre KeepAlive en le positionnant à Off. Ce changement m’a permis de gagner énormément et d’obtenir des résultats convenables.
Point numéro 3 : Au niveau de mon application j’ai repensé la couche d’accès aux données. En effet, j’avais dans un premier temps ajouté le cache Memcached à PDO. C’est un tord puisque les connexions se faisaient toujours (même si derrière, aucune requête n’était exécutée). C’est pourquoi j’ai modifié ce petit point en testant d’abord l’existance de résultats dans le cache et cas échéant en ouvrant ensuite une connexion à la base de données et en intérrogeant MySQL.
Memcached fonctionne sur un système clé/valeur : je stocke le hash md5 de la requête SQL et son résultat correspondant, j’ai également pris soin d’utiliser la compressionMemcached disponible.
De plus, j’ai optimisé mes requêtes SQL ainsi que mes tables de base de données.
Après de tels changements voici les résultats obtenus :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38360 bytes
Concurrency Level: 70
Time taken for tests: 136.806 seconds
Complete requests: 500
Failed requests: 0
Write errors: 0
Total transferred: 19376000 bytes
HTML transferred: 19180000 bytes
Requests per second: 3.65 [#/sec] (mean)
Time per request: 19152.871 [ms] (mean)
Time per request: 273.612 [ms] (mean, across all concurrent requests)
Transfer rate: 138.31 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 5.7 0 90
Processing: 98 17651 29364.9 310 127048
Waiting: 97 16805 28446.2 284 126923
Total: 99 17651 29364.9 310 127048
Percentage of the requests served within a certain time (ms)
50% 310
66% 7322
75% 25825
80% 41547
90% 64293
95% 89797
98% 101118
99% 101982
100% 127048 (longest request)
will-serv:~#
200 requêtes, 1 en concurence : 6,97 requêtes par seconde.
200 requêtes, 5 en concurence : 6,65 requêtes par seconde.
A ce niveau là, le serveur encaisse bien (du moins à mon échelle).
Ramdisk or not Ramdisk avec APC
Au niveau d’APC j’ai testé l’utilisation d’un Ramdisk :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38360 bytes
Concurrency Level: 70
Time taken for tests: 93.212 seconds
Complete requests: 500
Failed requests: 1
(Connect: 0, Receive: 0, Length: 1, Exceptions: 0)
Write errors: 0
Total transferred: 19376074 bytes
HTML transferred: 19180074 bytes
Requests per second: 5.36 [#/sec] (mean)
Time per request: 13049.644 [ms] (mean)
Time per request: 186.423 [ms] (mean, across all concurrent requests)
Transfer rate: 203.00 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 372 957.7 0 2886
Processing: 121 12005 19024.4 3701 90325
Waiting: 96 11847 18792.4 3581 90296
Total: 121 12376 19328.5 4002 93211
Percentage of the requests served within a certain time (ms)
50% 4002
66% 8782
75% 14842
80% 17808
90% 38836
95% 57641
98% 81943
99% 92179
100% 93211 (longest request)
will-serv:~#
Puis l’utilisation sans Ramdisk par APC. Le résultat est meilleur :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38364 bytes
Concurrency Level: 1
Time taken for tests: 28.705 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 7751200 bytes
HTML transferred: 7672800 bytes
Requests per second: 6.97 [#/sec] (mean)
Time per request: 143.523 [ms] (mean)
Time per request: 143.523 [ms] (mean, across all concurrent requests)
Transfer rate: 263.70 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 6.4 0 90
Processing: 121 143 33.6 124 275
Waiting: 29 136 29.7 121 273
Total: 121 143 34.3 124 275
Percentage of the requests served within a certain time (ms)
50% 124
66% 151
75% 151
80% 152
90% 181
95% 241
98% 249
99% 271
100% 275 (longest request)
will-serv:~#
On utilisera donc un Ramdisk avec les Opcodes type eAccelerator mais pas avec APC. Après réglage de ces détails, le serveur passe la barre de 7 requêtes par seconde avec des temps de réponse assez plaisants :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38360 bytes
Concurrency Level: 1
Time taken for tests: 28.513 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 7750400 bytes
HTML transferred: 7672000 bytes
Requests per second: 7.01 [#/sec] (mean)
Time per request: 142.563 [ms] (mean)
Time per request: 142.563 [ms] (mean, across all concurrent requests)
Transfer rate: 265.45 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 33 142 36.4 124 279
Waiting: 28 133 25.0 122 278
Total: 33 143 36.4 124 279
Percentage of the requests served within a certain time (ms)
50% 124
66% 152
75% 152
80% 153
90% 184
95% 243
98% 246
99% 274
100% 279 (longest request)
will-serv:~#
Toujours mieux avec 5 requêtes concurrentes :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38360 bytes
Concurrency Level: 5
Time taken for tests: 29.581 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 7750400 bytes
HTML transferred: 7672000 bytes
Requests per second: 6.76 [#/sec] (mean)
Time per request: 739.524 [ms] (mean)
Time per request: 147.905 [ms] (mean, across all concurrent requests)
Transfer rate: 255.87 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 8.6 0 120
Processing: 121 736 614.0 659 7590
Waiting: 120 731 609.4 658 7498
Total: 121 737 613.9 659 7590
Percentage of the requests served within a certain time (ms)
50% 659
66% 766
75% 789
80% 829
90% 959
95% 1064
98% 1849
99% 4688
100% 7590 (longest request)
will-serv:~#
Ensuite dans les benchs plus importants :
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software: Apache/2.2.9
Server Hostname: localhost
Server Port: 80
Document Path: /willdurand/
Document Length: 38360 bytes
Concurrency Level: 50
Time taken for tests: 84.838 seconds
Complete requests: 500
Failed requests: 28
(Connect: 0, Receive: 0, Length: 28, Exceptions: 0)
Write errors: 0
Non-2xx responses: 5
Total transferred: 19215376 bytes
HTML transferred: 19019345 bytes
Requests per second: 5.89 [#/sec] (mean)
Time per request: 8483.797 [ms] (mean)
Time per request: 169.676 [ms] (mean, across all concurrent requests)
Transfer rate: 221.19 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 9 29.4 0 147
Processing: 788 8372 11975.1 3746 84661
Waiting: 786 8295 11930.9 3696 84261
Total: 788 8382 11977.4 3753 84764
Percentage of the requests served within a certain time (ms)
50% 3753
66% 6488
75% 9115
80% 10478
90% 15884
95% 34825
98% 51623
99% 75095
100% 84764 (longest request)
will-serv:~#
Moins satisfaisant même si le serveur répond bien, il met plus de temps et perds quelques requêtes…
Un ab 1000/100 me donne les résultats suivants :
- 4,42 requests/second
- 14% d’échec
- 66% des requêtes en moins de 6s
Plus loin dans les optimisations
Dernière option, mettre le repertoire de cache en Ramdisk, cette fois-ci c’est très bénéfique et un ab 200/1 passe à 7,21 requêtes par seconde. Et un ab 200/5 passe à 6,97 requêtes par seconde en ayant 98% des requêtes traitées en moins de 3s.
Webwait obtenu : Average load time after 10 runs : 0,41s.
Après cela, j’ai réfléchi à ma gestion des ressources. Aidé par @madmatah, je me suis intéressé à la configuration Apache2 et au MPM Prefork. Après une série d’Apache Bench, j’ai configuré ce module comme suit :
MinSpareServers 1
MaxSpareServers 1
MaxClients 3
MaxRequestsPerChild 0
J’ai également passé le timeout à 2 secondes. Le load average devient ainsi inférieur à 1. Pour surveiller le load average ainsi que les utilisations de la RAM et du SWAP je me suis servi de la commande
mais aussi de
Au final, j’ai modifié mes premiers essais :
- Le Ramdisk est passé de 32Mo à 4Mo. Il stocke désormais le cache HTML de mon site.
- Memcached est lancé avec 8Mo alloués.
Par contre le KeepAlive est toujours sur Off. J’ai de gros soucis dès que je le passe à On…
Dernière optimisation faite : l’ajout des options de montage noatime et nodiratime sur mes volumes dans /etc/fstab.
Conclusion
Le manque de puissance CPU se fait énormément ressentir. Au niveau RAM, on s’en sort très bien car il est rare qu’elle soit saturée. Mais le CPU est très vite à 100% d’utilisation et peine à revenir. Il faudrait voir avec des ressources supplémentaires.
Une telle part reste un bon début pour se faire la main sur un serveur dédié, pas cher, souple et scalable il ne démérite pas.
Cependant, avec l’utilisation de Memcached, on limite les accès base de données (et donc disque). Un serveur RPS chez OVH ne fait pas le poids au niveau de la rapidité d’accès disque face à une part Gandi, il serait intéressant de reprendre ma configuration et de la porter sur un serveur RPS afin d’établir un comparatif. Je pense faire ça le mois prochain.
Pour terminer, j’ai toujours des soucis avec le KeepAlive à On, les temps minimums de traitement d’une requête sont de l’ordre du KeepAliveTimeout…
Voici également deux screens des interfaces de gestion Memcached et APC que j’utilise :
Merci à www.mrboo.fr pour ses tests et retours d’expérience.
Merci à @madmatah pour ses nombreux conseils et son temps.
Le mot de la fin : le site sur lequel vous vous trouvez n’a pas subi les modifications précédentes…




















2 trackbacks
[...] temporaire. L’explication pour la création d’un Ramdisk est expliqué ici : http://www.willdurand.fr/optimiser-son-application-web-en-jouant-sur-php-mysql-et-apache2/. MySQL est également protégé par une couche Memcached, ce qui permet de faire tenir une charge [...]
[...] Ce billet était mentionné sur Twitter par jicéb, onion_papa. onion_papa a dit: http://tinyurl.com/29jpesp Optimiser son application web en jouant sur PHP, MySQL et Apache2 | William DURAND – Développeur web indépenda [...]