Corriger les erreurs MySQL « server has gone away » et « Too Many Connections » dans WordPress

Cet article vous a aidé ?

Si votre site WordPress affiche de façon aléatoire « MySQL server has gone away » ou « Too many connections », ce n’est pas un problème de philosophie. C’est un problème de comptabilisation des ressources. Quelque chose tue des connexions, les épuise ou les retient trop longtemps—généralement lors d’un pic de trafic, d’une requête lente ou d’un mauvais plugin qui laisse la base de données « le robinet ouvert ».

Ce guide s’adresse aux personnes qui exploitent des systèmes en production et qui ont besoin d’une méthode calme et répétable pour passer de « c’est en panne » à « c’est stable » sans tâtonner. Vous aurez un diagnostic rapide, des commandes concrètes, la signification des sorties et les décisions à prendre ensuite.

Ce que signifient réellement ces erreurs (et ce qu’elles ne signifient pas)

« MySQL server has gone away »

Ce message signifie que l’application vous dit : « J’ai essayé d’utiliser une connexion MySQL, mais le côté serveur n’est plus là. » Cela peut arriver parce que :

  • Le serveur a fermé la connexion à cause d’un timeout d’inactivité (wait_timeout).
  • Le serveur a planté ou redémarré (OOM killer, redémarrage manuel, mise à jour, panic).
  • Un élément réseau a interrompu la connexion (NAT, pare-feu, timeout d’inactivité d’un load balancer).
  • Le client a envoyé un paquet plus gros que permis (max_allowed_packet).
  • Le serveur a atteint des limites de ressources et a commencé à refuser ou annuler des opérations (disque plein, corruption de table, fuite mémoire, épuisement de threads).

Ce n’est pas « WordPress est cassé. » WordPress n’est que le messager. Ne l’accusez pas ; interrogez-le.

« Too many connections »

Cela veut dire sans détours que le nombre de connexions clientes concurrentes a dépassé max_connections. Dans les architectures WordPress, cela arrive généralement parce que PHP ouvre des connexions plus vite que MySQL ne termine les requêtes.

Deux points subtils importent :

  • Augmenter max_connections n’est pas une solution en soi. Cela peut gagner du temps, ou transformer des « connexions refusées » en « tempête de swap » et faire tomber tout l’hôte.
  • Le nombre de connexions est un symptôme. La cause profonde est presque toujours la latence : requêtes lentes, verrous de table, I/O saturée, CPU à 100 %, ou PHP-FPM qui génère trop de workers.

Une idée paraphrasée souvent attribuée aux responsables ingénierie : « La fiabilité est une fonctionnalité que l’on construit, pas un vœu que l’on formule. » (idée paraphrasée, John Allspaw)

Petite blague #1 : MySQL ne « s’en va » pas vraiment. C’est plutôt qu’il abandonne la partie par colère parce qu’on lui a demandé cinq choses à la fois sur un disque de portable.

Playbook de diagnostic rapide (vérifications 1re/2e/3e)

Quand des pages renvoient des erreurs, vous avez une tâche : trouver le goulot d’étranglement rapidement, arrêter l’hémorragie, puis rendre la situation ennuyeuse.

Premier : identifier la classe de défaillance en 2 minutes

  1. MySQL est-il en service et accepte-t-il des connexions ? Si non, il s’agit d’un crash/redémarrage/OOM/disque.
  2. Les connexions sont-elles saturées ? Si oui, c’est « too many connections », souvent causé par des requêtes lentes ou une explosion des workers PHP.
  3. Les connexions sont-elles tuées en cours d’utilisation ? Si oui, pensez aux timeouts réseau, timeouts MySQL, taille maximale de paquet, comportement de proxy.

Second : décider où la file d’attente se forme

  • File côté PHP : PHP-FPM a une longue file d’écoute, de nombreux workers actifs, des requêtes lentes.
  • File côté MySQL : MySQL affiche de nombreux threads en cours, des requêtes longues, des attentes de verrous, des misses du buffer pool, des waits d’I/O.
  • File réseau/proxy : resets intermittents, timeouts NAT, « gone away » après des périodes d’inactivité.

Troisième : appliquer la mitigation immédiate la plus sûre

  • Si MySQL est down : corrigez le disque / l’OOM, redémarrez, et limitez la concurrence PHP avant de ré-accepter le trafic.
  • Si les connexions sont saturées : réduisez temporairement la concurrence PHP-FPM ; éventuellement augmentez légèrement max_connections si la mémoire le permet ; identifiez le pattern de requêtes lentes.
  • Si « gone away » survient après de l’inactivité : alignez les timeouts entre MySQL, PHP et les équipements réseau ; envisagez d’activer les keepalives ou d’éviter les connexions persistantes.

Faits intéressants et contexte historique (pour comprendre le comportement)

  1. Les timeouts par défaut de MySQL étaient conçus pour des serveurs d’applications longuement connectés, pas pour les couches modernes de proxies, NAT et edges serverless qui aiment couper le TCP inactif.
  2. À l’époque classique du LAMP, les connexions persistantes étaient à la mode car ouvrir un TCP + authentification coûtait cher. Aujourd’hui, les connexions persistantes amplifient souvent les modes de défaillance lors de pics de trafic.
  3. « Too many connections » était autrefois un signe de fierté dans certaines organisations—jusqu’à ce qu’elles comprennent que chaque thread coûte de la mémoire et du temps de commutation de contexte.
  4. WordPress a historiquement favorisé un écosystème de plugins, excellent pour les fonctionnalités et catastrophique pour la discipline des requêtes. Un seul plugin peut ajouter 20 requêtes par page sans prévenir.
  5. InnoDB est devenu le moteur par défaut avec MySQL 5.5, ce qui a changé l’approche de tuning « correcte ». Les conseils de l’ère MyISAM hantent encore les forums.
  6. Les tempêtes de connexions sont une classe connue de défaillance en cascade : quand MySQL ralentit, PHP ouvre plus de connexions, ce qui ralentit MySQL davantage, ce qui déclenche des retries… et tout part en flammes.
  7. Historiquement, max_allowed_packet était petit par défaut car la mémoire comptait et les gros blobs étaient déconseillés. Les médias WordPress et les options sérialisées ignorent cette histoire.
  8. Beaucoup d’incidents « server has gone away » ne sont pas des bugs MySQL ; ce sont des timeouts d’infrastructure (load balancers, pare-feu) avec des valeurs par défaut comme 60 secondes que personne n’a vraiment configurées.
  9. Les connexions abortées ne sont pas toujours mauvaises : elles peuvent refléter des utilisateurs fermant le navigateur en plein milieu d’une requête. Mais un pic d’aborted connections accompagné d’erreurs est généralement un indice fort.

Modes de défaillance principaux derrière « gone away » et « too many connections »

1) MySQL redémarre (souvent OOM ou disque)

Si mysqld redémarre, chaque connexion active meurt. WordPress signale « server has gone away » parce que la socket qu’il utilisait est devenue fossile.

Déclencheurs courants :

  • Tuerie pour manque de mémoire : buffer pool trop grand, trop de buffers par thread, trop de connexions, ou fuite mémoire dans des services adjacents.
  • Disque plein : binlogs, tmpdir, ibtmp ou slow query logs qui grossissent ; MySQL commence à échouer sur les écritures ; puis tout se cascade.
  • Latence du système de fichiers : des stalls I/O entraînent des threads bloqués ; un watchdog redémarre ; les clients expirent.

2) Famine de connexions due à des requêtes lentes

WordPress est bavard. Ajoutez quelques requêtes meta non indexées, un plugin de rapports et un crawl agressif de bots, et vous pouvez occuper tous les threads. Les nouvelles connexions sont refusées : « too many connections ».

3) PHP-FPM ou la couche web est autorisée à créer trop de parallélisme

La plupart des pannes WordPress accusent MySQL, mais PHP-FPM tient souvent l’allumette. Si vous autorisez 200 workers PHP et que chacun peut ouvrir une connexion MySQL, vous avez effectivement configuré une inondation de connexions.

4) Timeouts mal alignés entre les couches

Imaginez wait_timeout à 60 secondes. Votre load balancer coupe le TCP inactif à 50 secondes. PHP essaie de réutiliser une connexion à 55 secondes. Résultat : « server has gone away », alors que MySQL n’a jamais fermé la connexion.

5) Paquets surdimensionnés et données WordPress bizarres

WordPress stocke des tableaux dans wp_options sous forme de blobs sérialisés. Certains plugins entassent des payloads énormes là-dedans (caches de page builders, dumps d’analytics). Si ce blob dépasse max_allowed_packet, MySQL coupe la connexion et vous obtenez « server has gone away ».

Tâches pratiques : commandes, sorties, décisions (12+)

Voici les actions que j’attends d’un on-call pendant un incident. Chaque tâche inclut une commande, une sortie d’exemple, ce que cela signifie et la décision à prendre.

Task 1: Confirm MySQL is alive and note uptime

cr0x@server:~$ systemctl status mysql --no-pager
● mysql.service - MySQL Community Server
     Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2025-12-27 10:11:03 UTC; 3min ago
       Docs: man:mysqld(8)
   Main PID: 1423 (mysqld)
     Status: "Server is operational"

Ce que cela signifie : Si l’uptime est de quelques minutes et que vous ne l’avez pas redémarré, vous avez une boucle de crash ou un redémarrage de l’hôte.

Décision : Si MySQL a redémarré récemment, vérifiez immédiatement les logs pour OOM/disque et limitez la concurrence PHP avant que le trafic ne le re-sature.

Task 2: Check MySQL error log for crash signatures

cr0x@server:~$ journalctl -u mysql --since "30 min ago" --no-pager | tail -n 40
Dec 27 10:10:58 server mysqld[1423]: 2025-12-27T10:10:58.221Z 0 [Warning] Aborted connection 891 to db: 'wpdb' user: 'wpuser' host: '10.0.2.15' (Got an error reading communication packets)
Dec 27 10:11:02 server systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
Dec 27 10:11:03 server systemd[1]: mysql.service: Scheduled restart job, restart counter is at 1.
Dec 27 10:11:03 server systemd[1]: Started MySQL Community Server.

Ce que cela signifie : Le status 9/KILL pointe souvent vers l’OOM killer ou une mise à mort administrative.

Décision : Validez les événements OOM et le dimensionnement mémoire ; ne faites pas des redémarrages répétés en espérant que la base retrouve la paix intérieure.

Task 3: Confirm OOM killer events

cr0x@server:~$ dmesg -T | egrep -i "oom|killed process|out of memory" | tail -n 20
[Sat Dec 27 10:11:01 2025] Out of memory: Killed process 1423 (mysqld) total-vm:4123988kB, anon-rss:2450180kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:6420kB oom_score_adj:0

Ce que cela signifie : MySQL a été tué pour cause de mémoire. « Server has gone away » est l’arrière-goût.

Décision : Réduisez l’empreinte mémoire (buffer pool, buffers par thread, nombre de connexions), ajoutez de la RAM ou déplacez MySQL hors de l’hôte congestionné.

Task 4: Check free disk and inode pressure (yes, inodes)

cr0x@server:~$ df -hT
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4   80G   76G  2.1G  98% /
tmpfs          tmpfs 3.9G   12M  3.9G   1% /run
cr0x@server:~$ df -ihT
Filesystem     Type  Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 ext4     5M   4.9M  120K   98% /

Ce que cela signifie : Un disque ou des inodes presque pleins peuvent casser les tables temporaires, les logs, les binlogs et même les écritures sur socket.

Décision : Libérez de l’espace immédiatement (rotation des logs, purge sécurisée des binlogs), puis corrigez la croissance (rétention des logs, alerting).

Task 5: Measure connection pressure and max_connections headroom

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 198   |
+-------------------+-------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 200   |
+-----------------+-------+

Ce que cela signifie : Vous êtes collé au plafond. MySQL passe du temps en commutation de contexte, et de nouvelles connexions vont échouer.

Décision : Ne montez pas directement à 1000 connexions. Réduisez d’abord la concurrence en amont (PHP-FPM), puis trouvez ce qui est lent.

Task 6: Identify who is consuming connections (by user/host)

cr0x@server:~$ mysql -e "SELECT user, host, COUNT(*) AS conns FROM information_schema.processlist GROUP BY user, host ORDER BY conns DESC LIMIT 10;"
+--------+------------+-------+
| user   | host       | conns |
+--------+------------+-------+
| wpuser | 10.0.2.15  | 184   |
| wpuser | 10.0.2.16  | 12    |
| root   | localhost  | 1     |
+--------+------------+-------+

Ce que cela signifie : Un nœud web submerge la base ou est coincé en train de réessayer.

Décision : Limitez le débit ou videz ce nœud ; vérifiez sa file PHP-FPM et ses logs d’erreur ; assurez-vous qu’il ne boucle pas sur des échecs DB.

Task 7: Spot long-running queries and lock waits

cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;" | head -n 30
Id	User	Host	db	Command	Time	State	Info
812	wpuser	10.0.2.15:54012	wpdb	Query	42	statistics	SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts ...
901	wpuser	10.0.2.15:54101	wpdb	Query	39	Waiting for table metadata lock	ALTER TABLE wp_postmeta ADD INDEX meta_key (meta_key)
...

Ce que cela signifie : Les requêtes longues et les verrous de métadonnées peuvent bloquer des threads. Cela alimente l’accumulation des connexions.

Décision : Tuez les requêtes ad hoc manifestement mauvaises, reportez les DDL et corrigez la requête/index. Si un DDL bloque, planifiez-le hors heures et utilisez des outils de changement de schéma en ligne.

Task 8: Check InnoDB health and the real “why is it slow” clues

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | egrep -A3 -B2 "LATEST DETECTED DEADLOCK|History list length|buffer pool|I/O|semaphore" | head -n 80
History list length 124873
Pending reads 0
Pending writes: LRU 0, flush list 12, single page 0
Buffer pool size   131072
Free buffers       12
Database pages     130984
Modified db pages  9812

Ce que cela signifie : Une grande history list length peut indiquer un retard de purge ; beaucoup de pages modifiées peut indiquer une pression de flush ; peu de buffers libres montre un buffer pool chaud.

Décision : Si les flushs I/O sont engorgés, améliorez la latence du stockage, ajustez les paramètres de flush et réduisez les rafales d’écriture (caches, batchs). Si la purge est en retard, cherchez les transactions longues.

Task 9: Validate timeout settings that cause “gone away” after idle

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('wait_timeout','interactive_timeout','net_read_timeout','net_write_timeout','max_allowed_packet');"
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| interactive_timeout| 28800     |
| max_allowed_packet | 67108864  |
| net_read_timeout   | 30        |
| net_write_timeout  | 60        |
| wait_timeout       | 60        |
+--------------------+-----------+

Ce que cela signifie : Un wait_timeout de 60 secondes est agressif. C’est acceptable si votre appli ne réutilise pas les connexions inactives. Les stacks WordPress le font parfois (selon la librairie client et la persistance).

Décision : Alignez les timeouts : définissez wait_timeout sur une valeur réaliste (p.ex. 300–900) et assurez-vous que les proxys ne coupent pas avant MySQL ou inversement.

Task 10: Inspect aborted connections counters

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Aborted_%';"
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 1821  |
| Aborted_connects | 94    |
+------------------+-------+

Ce que cela signifie : Aborted_clients augmente quand les clients déconnectent de façon incorrecte ; Aborted_connects augmente quand l’authentification/handshake échoue ou quand les limites de ressources bloquent les connexions.

Décision : Si les deux augmentent durant l’incident, corrélez avec des drops réseau et la saturation de max_connections. Si seul Aborted_clients augmente, n’alertez pas tout de suite—vérifiez le taux d’erreur et le timing.

Task 11: Check PHP-FPM concurrency and queueing

cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Active: active (running) since Sat 2025-12-27 09:41:10 UTC; 33min ago
cr0x@server:~$ sudo ss -lntp | egrep "php-fpm|:9000"
LISTEN 0 511 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm8.2",pid=2201,fd=8))

Ce que cela signifie : Le backlog d’écoute (ici 511) est juste la capacité pour des requêtes en file d’attente. La vraie question : les workers sont-ils saturés ?

Décision : Vérifiez la page d’état FPM ou les logs ; si saturé, réduisez pm.max_children pour coller à la capacité de la DB, pas au « nombre qui rentre en RAM un bon jour ».

Task 12: Confirm Nginx/Apache is retrying upstream too aggressively

cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/27 10:12:01 [error] 3112#3112: *9821 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.55, server: example, request: "GET / HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000"
2025/12/27 10:12:03 [error] 3112#3112: *9838 FastCGI sent in stderr: "PHP message: WordPress database error Too many connections for query SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'"

Ce que cela signifie : Un upstream lent provoque des timeouts web ; les logs PHP montrent des erreurs DB. Ce sont des symptômes du même phénomène de file d’attente.

Décision : Réduisez la concurrence et corrigez la latence DB. Allonger les timeouts Nginx sans corriger la DB, c’est créer des pannes plus longues avec plus d’utilisateurs en colère.

Task 13: Find the worst WordPress queries (slow log on-demand)

cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.5; SHOW VARIABLES LIKE 'slow_query_log_file';"
+---------------------+----------------------------------+
| Variable_name       | Value                            |
+---------------------+----------------------------------+
| slow_query_log_file | /var/log/mysql/mysql-slow.log    |
+---------------------+----------------------------------+
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-27T10:12:22.114Z
# Query_time: 3.218  Lock_time: 0.004 Rows_sent: 10  Rows_examined: 184221
SET timestamp=1766830342;
SELECT p.ID FROM wp_posts p
JOIN wp_postmeta pm ON pm.post_id=p.ID
WHERE pm.meta_key='some_key' AND pm.meta_value LIKE '%needle%'
ORDER BY p.post_date DESC LIMIT 10;

Ce que cela signifie : Le nombre de lignes examinées est énorme. C’est votre tueur de connexions : chaque requête lente bloque un thread et alourdit la pile.

Décision : Ajoutez/ajustez des index, réduisez les requêtes meta ou remplacez le plugin responsable. Puis désactivez le log à seuil bas pour ne pas remplir le disque.

Task 14: Verify table/index health quickly

cr0x@server:~$ mysql -e "CHECK TABLE wp_options, wp_posts, wp_postmeta QUICK;"
+-------------+-------+----------+----------+
| Table       | Op    | Msg_type | Msg_text |
+-------------+-------+----------+----------+
| wpdb.wp_options  | check | status   | OK       |
| wpdb.wp_posts    | check | status   | OK       |
| wpdb.wp_postmeta | check | status   | OK       |
+-------------+-------+----------+----------+

Ce que cela signifie : « OK » écarte certains scénarios de corruption. Ça ne prouve pas que la performance est satisfaisante.

Décision : Si vous voyez de la corruption, évitez les réparations ad hoc en période de pointe sauf si vous connaissez le périmètre d’impact. Stabilisez, faites un snapshot/sauvegarde, puis réparez de façon délibérée.

Task 15: Quantify memory per connection (rough sizing)

cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('read_buffer_size','read_rnd_buffer_size','sort_buffer_size','join_buffer_size','tmp_table_size','max_heap_table_size');"
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| join_buffer_size     | 262144   |
| max_heap_table_size  | 16777216 |
| read_buffer_size     | 131072   |
| read_rnd_buffer_size | 262144   |
| sort_buffer_size     | 262144   |
| tmp_table_size       | 16777216 |
+----------------------+----------+

Ce que cela signifie : Les buffers par connexion peuvent s’allouer à la demande. En haute concurrence, « à la demande » devient « tous en même temps ».

Décision : Gardez les buffers par thread modestes. Ne les montez pas pour résoudre une seule requête ; corrigez la requête.

Réglages qui comptent vraiment (et ceux qui font perdre du temps)

Commencez par la contrainte : les threads MySQL ne sont pas gratuits

Chaque connexion consomme de la mémoire et de l’attention du planificateur. Augmenter max_connections augmente la taille potentielle de la ruée.

Quand vous devez l’augmenter, faites-le comme un adulte :

  • Estimez d’abord la marge mémoire disponible (buffer pool + overhead + cache OS + autres démons).
  • Augmentez par paliers (p.ex. +25% ou +50%), pas par 10x.
  • Associez-le à une limitation amont (PHP-FPM max_children, limites du serveur web).

Buffer pool InnoDB : le gros réglage qui paie généralement

Pour des hôtes MySQL dédiés, un point de départ courant est 60–75% de la RAM pour innodb_buffer_pool_size. Sur des hôtes partagés, cela dépend de ce qui se bat pour la mémoire.

Si votre working set ne tient pas, vous utiliserez plus le disque. L’I/O disque ralentit les requêtes. Les requêtes lentes retiennent les connexions plus longtemps. Ça déclenche « too many connections ». C’est une boucle.

Timeouts : alignez-les, ne suivez pas le mythe

wait_timeout est la durée pendant laquelle MySQL garde une connexion inactif non interactive. S’il est trop bas et que votre appli réutilise des connexions inactives, vous obtenez « gone away ». S’il est trop élevé et que vous avez des milliers de connexions inactives, vous gaspillez de la mémoire. Choisissez une valeur réaliste et adaptez-la à votre environnement.

Pensez aussi aux tueurs invisibles : pare-feu et load balancers. Ils ont souvent des timeouts d’inactivité plus courts que MySQL. S’ils coupent des connexions silencieusement, la première lecture/écriture déclenche « gone away ».

max_allowed_packet : le réglage « pourquoi cet upload a tué ma base »

Les uploads WordPress ne passent pas directement par MySQL, mais les plugins stockent de gros blobs sérialisés, surtout dans options et postmeta. Si vous voyez des erreurs lors de grosses mises à jour, augmentez max_allowed_packet à quelque chose de raisonnable (64M ou 128M selon la charge) puis identifiez le plugin qui stocke des cochonneries.

N’y perdez pas de temps pendant un incident

  • Conseils aléatoires sur le query cache : Si quelqu’un vous dit d’activer l’ancien query cache MySQL, il voyage dans le temps. Dans MySQL moderne il a disparu.
  • Monter les buffers par thread : C’est la méthode pour provoquer des kills OOM avec confiance.
  • Désactiver la sécurité de flush d’InnoDB : Vous pouvez échanger durabilité contre vitesse, mais faites-le en connaissance de cause—pas parce qu’un commentaire de blog l’a recommandé.

Schémas de défaillance spécifiques à WordPress

Options autoloadées devenant une taxe sur la base

WordPress charge toutes les options avec autoload='yes' au démarrage. Si un plugin fourre de grosses données dans des options autoloadées, chaque requête paie le prix. Cela ralentit les requêtes, ce qui augmente l’utilisation concurrente de la BD.

Correctif : auditez wp_options, réduisez le contenu autoloadé, et déplacez les caches volumineux vers un cache d’objet (Redis/Memcached) ou vers des fichiers.

Requêtes wp_postmeta sans index appropriés

Les requêtes meta sont flexibles mais coûteuses. Beaucoup de plugins construisent des fonctions « type recherche » en utilisant LIKE '%...%' sur meta_value. Ce n’est pas une requête ; c’est un appel à l’aide.

Correctif : limitez les requêtes meta, ajoutez des index ciblés (avec précaution) ou déplacez cette fonctionnalité vers un moteur de recherche.

WP-Cron et tempêtes admin-ajax

Un trafic élevé peut déclencher WP-Cron fréquemment si celui-ci dépend du trafic. Par ailleurs, admin-ajax.php peut être abusé par thèmes/plugins pour un polling constant.

Correctif : déplacez cron vers un cron système réel ; rate-limitez les endpoints admin-ajax ; mettez en cache agressivement pour les utilisateurs anonymes.

Petite blague #2 : « Too many connections » est la façon dont MySQL dit qu’il a besoin de moins de réunions dans son agenda.

Trois mini-récits du terrain en entreprise

Mini-récit 1 : La panne causée par une mauvaise hypothèse

L’équipe avait un parc WordPress derrière un load balancer : deux nœuds web, un nœud MySQL. Tout était stable depuis des mois. Puis des « server has gone away » intermittents ont commencé—seulement sur certaines pages, surtout après que des utilisateurs aient navigué et cliqué quelque chose.

La première hypothèse fut prévisible : « MySQL time out ». Quelqu’un proposa d’augmenter wait_timeout à plusieurs heures. Une autre personne suggéra d’activer les connexions persistantes pour « éviter le coût de reconnexion ». Ça semblait plausible, et c’était faux.

Le problème réel se trouvait dans la couche réseau. Un pare-feu entre le subnet web et le subnet DB avait un timeout TCP inactif plus court que celui de MySQL. Il coupait silencieusement les sessions inactives. PHP réutilisait ces connexions plus tard et recevait des resets. Les logs MySQL affichaient des messages « aborted connection », mais MySQL n’était pas celui qui abrogeait.

La correction fut triste : aligner les timeouts et activer les keepalives TCP sur les nœuds web pour que les connexions inactives restent en vie, ou mieux, désactiver la réutilisation persistante là où elle n’était pas nécessaire. La leçon est restée : quand vous voyez « gone away », vérifiez qui a raccroché en premier.

Mini-récit 2 : L’optimisation qui a tourné mal

Une autre société courait après la performance. Ils réduisirent le TTFB en ajoutant un scaling agressif de PHP-FPM : un pm.max_children élevé pour que le site « gère les pics ». Il a géré les pics—en les créant.

Pendant une campagne marketing, le trafic monta. PHP-FPM spawnait des workers. Chaque worker ouvrait une connexion MySQL et exécutait quelques requêtes. MySQL commença à ralentir, donc les requêtes prenaient plus de temps. Des requêtes plus longues signifiaient des workers occupés plus longtemps, donc le pool en créait davantage. Les connexions atteignirent le plafond. « Too many connections » explosa. Puis des retries apparurent depuis l’appli et quelques proxies en amont.

Ils avaient optimisé le mauvais élément : le débit côté web, pas la capacité de bout en bout. La bonne correction n’était pas « monter max_connections à 2000 ». C’était de limiter la concurrence PHP à ce que la base pouvait servir avec une latence acceptable, et de mettre en cache le trafic anonyme pour que la majorité des requêtes n’ait pas besoin de MySQL.

Après réglage, le système géra la même campagne avec moins de workers, moins de connexions DB, et plus de hits dans le cache. Dans un benchmark synthétique « combien de workers puis-je spawn », ça semblait plus lent. Pour les utilisateurs réels, c’était plus rapide. C’est le seul indicateur qui compte.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Un déploiement WordPress d’entreprise tournait sur MySQL avec des réplicas. Rien d’extraordinaire. La sauce secrète était la discipline : chaque changement de schéma était planifié, chaque mise à jour de plugin avait un soak en staging, et le slow query logging était activé pendant les heures ouvrées avec des seuils raisonnables.

Un après-midi, une mise à jour de plugin introduisit un nouveau pattern de requête sur wp_postmeta. Pas catastrophique. Juste plus lent. Mais assez pour que le nombre de connexions commence à grimper sous le trafic normal. L’on-call le remarqua grâce aux alertes sur « Threads_connected en pourcentage de max_connections » et sur le temps de requête 95e percentile dans leurs métriques DB.

Ils n’attendirent pas une panne complète. Ils rollbackèrent le plugin, purgèrent une option autoload bloat venue avec, et ajoutèrent un index ciblé en fenêtre de maintenance. Les utilisateurs n’eurent presque rien remarqué. La direction n’apprit rien. Voilà à quoi ressemble le succès : la prévention si ennuyeuse qu’elle ne fait pas de slides.

Erreurs courantes : symptôme → cause profonde → correctif

1) « Server has gone away » après exactement N secondes d’inactivité

  • Symptôme : Les erreurs apparaissent quand les utilisateurs reviennent sur le site après une période d’inactivité ; ça se reproduit avec un chronomètre.
  • Cause profonde : Désalignement des timeouts (wait_timeout vs timeout du firewall/LB vs réutilisation côté client).
  • Correctif : Alignez les timeouts ; augmentez modérément wait_timeout ; désactivez les connexions persistantes si présentes ; configurez les keepalives TCP si approprié.

2) « Too many connections » lors de pics de trafic, avec iowait CPU élevé

  • Symptôme : La BD devient lente, le nombre de connexions augmente, l’iowait monte en flèche.
  • Cause profonde : Miss du buffer pool + stockage lent ; les requêtes attendent le disque.
  • Correctif : Augmentez le buffer pool InnoDB (dans les limites RAM), améliorez le stockage, réduisez le coût des requêtes, ajoutez du caching pour le trafic anonyme.

3) « Too many connections » juste après l’augmentation de PHP-FPM max_children

  • Symptôme : Plus de workers « améliorent » le débit pendant un jour, puis les incidents commencent.
  • Cause profonde : La concurrence amont dépasse la capacité DB ; tempêtes de connexions.
  • Correctif : Limitez PHP-FPM à la capacité DB ; ajoutez mise en file et backpressure ; envisagez une réplique en lecture pour le trafic en lecture si l’appli le supporte.

4) « Server has gone away » lors de sauvegarde d’articles ou mise à jour de plugins

  • Symptôme : Actions admin échouent ; les logs montrent des erreurs de paquet ou des connexions abortées.
  • Cause profonde : max_allowed_packet trop petit, ou écritures longues atteignant des timeouts.
  • Correctif : Augmentez max_allowed_packet ; inspectez ce qui est stocké ; empêchez les plugins de stocker de gros blobs dans les options.

5) « Error establishing a database connection » de façon intermittente

  • Symptôme : Erreur générique de connexion DB WordPress, pas toujours « too many connections ».
  • Cause profonde : Redémarrages MySQL, problèmes DNS, épuisement de sockets, ou throttling d’authentification.
  • Correctif : Vérifiez l’uptime MySQL et les logs de crash ; pointez l’hôte DB par IP si le DNS est instable ; vérifiez les limites de descripteurs de fichiers ; auditez les échecs d’authent.

6) Augmenter max_connections fait tomber l’hôte

  • Symptôme : Moins d’erreurs « too many connections », mais la latence empire ; mysqld est ensuite tué par l’OOM.
  • Cause profonde : Mémoire par connexion + overhead du scheduler ; swap ou OOM.
  • Correctif : Revenez à un max_connections plus sûr ; limitez PHP ; réduisez les buffers par thread ; ajoutez du caching ; scalez verticalement ou migrez la BD sur un hôte dédié.

Listes de vérification / plan étape par étape

Checklist réponse incident (15–30 minutes)

  1. Confirmer la santé DB : vérifiez systemctl status mysql et l’uptime.
  2. Vérifier OOM/disque : lignes OOM dans dmesg ; df -h et df -ih.
  3. Mesurer la saturation : Threads_connected vs max_connections.
  4. Trouver les coupables : processlist ; principaux users/hosts ; requêtes longues.
  5. Appliquer du backpressure : réduire la concurrence PHP-FPM (ou vider temporairement un nœud web) avant de toucher aux réglages DB.
  6. Activer brièvement le slow log : capturer les patterns de requêtes ; ne le laissez pas en permanence à des seuils trop bas.
  7. Stabiliser : confirmer que le taux d’erreur baisse ; surveiller la latence ; s’assurer que MySQL ne redémarre plus.

Plan de stabilisation (le lendemain)

  1. Corriger les requêtes lentes : index, modifications de plugins, réécriture des requêtes.
  2. Auditer wp_options autoload : supprimer le bloat, désactiver les coupables, déplacer le caching hors de la BD.
  3. Aligner les timeouts : MySQL, PHP, serveur web, load balancer, pare-feu.
  4. Redimensionner correctement le buffer pool : en fonction de la RAM et de la charge, pas des croyances.
  5. Ajouter du monitoring : utilisation des connexions, latence des requêtes, métriques InnoDB, croissance disque, événements OOM.

Plan de durcissement (le sprint suivant)

  1. Séparer les rôles : mettez MySQL sur un hôte dédié ou un service managé si possible.
  2. Mettre en cache le trafic anonyme : full-page cache + object cache pour réduire la charge DB.
  3. Limiter la concurrence intentionnellement : définissez PHP-FPM pm.max_children pour protéger MySQL.
  4. Hygiène opérationnelle : changements de schéma planifiés, politique de mise à jour des plugins et plan de rollback qui ne repose pas sur la prière.

FAQ

1) Dois-je simplement augmenter max_connections pour réparer « Too many connections » ?

Seulement comme soupape temporaire, et seulement après avoir vérifié la marge mémoire. La solution durable est de réduire le temps de requête et la concurrence en amont.

2) Quelle est la manière la plus rapide d’arrêter l’incident maintenant ?

Limitez la concurrence au niveau web (PHP-FPM max_children ou videz un nœud web) pour que MySQL puisse récupérer. Ensuite chassez les requêtes lentes.

3) Pourquoi je vois « server has gone away » alors que MySQL semble sain ?

Parce que la connexion peut être tuée par des pare-feu, load balancers, passerelles NAT ou timeouts client. Vérifiez les timeouts et les resets réseau, pas seulement l’uptime de mysqld.

4) Est-ce causé par WordPress lui-même ou par un plugin ?

Le cœur WordPress n’est généralement pas directement en cause. Les plugins créent souvent des requêtes pathologiques (surtout les requêtes meta) ou gonflent les options autoloadées.

5) Les connexions MySQL persistantes aident-elles les performances WordPress ?

Parfois, mais elles peuvent aussi amplifier les erreurs « gone away » et l’accaparement de connexions. Dans la plupart des setups modernes, corriger le coût des requêtes et mettre en cache rapporte plus.

6) Quelles valeurs de timeout devrais-je utiliser ?

Il n’y a pas de réponse universelle. Mais wait_timeout=60 est souvent trop bas pour des réseaux à plusieurs couches. Commencez vers 300–900 secondes et alignez avec les timeouts des équipements réseau.

7) Pourquoi cela arrive-t-il surtout lors d’actions admin ?

Les actions admin déclenchent des requêtes plus lourdes, des écritures et parfois des changements de schéma. Elles exposent aussi les limites de max_allowed_packet lorsque de gros blobs sont mis à jour.

8) Une réplique peut-elle résoudre « too many connections » ?

Seulement si votre application distribue réellement les lectures vers la réplique. WordPress classique ne sépare pas automatiquement lectures/écritures sans outils supplémentaires. Le caching est habituellement le premier gain.

9) Comment savoir si l’I/O disque est le vrai goulot ?

Cherchez un iowait élevé, des temps de requête lents corrélés à une activité disque, des flushs InnoDB en attente et un faible taux de hit du buffer pool. Si le stockage est lent, tout le reste est du théâtre.

10) MariaDB est-elle différente ici ?

Les modes de défaillance sont similaires : plafonds de connexions, timeouts, coût mémoire/threads et requêtes lentes. Les variables et valeurs par défaut peuvent différer selon la version, vérifiez votre version.

Conclusion : prochaines étapes pratiques

Ces erreurs MySQL ne sont pas des mystères. Ce sont des constats comptables : vous avez manqué de connexions, de temps, de mémoire ou de patience quelque part dans la pile.

  1. Aujourd’hui : exécutez le playbook de diagnostic rapide, limitez la concurrence PHP, confirmez que MySQL ne redémarre pas et capturez les requêtes lentes.
  2. Cette semaine : éliminez ou corrigez les patterns de requêtes les plus nuisibles (souvent causés par des plugins), réduisez le bloat autoloadé des options et alignez les timeouts entre MySQL et les couches réseau.
  3. Ce sprint : ajoutez du caching, définissez des limites de capacité explicites et placez du monitoring/alerting sur l’utilisation des connexions et la latence des requêtes pour voir la falaise avant d’y tomber.

Si vous changez une chose culturellement : arrêtez de traiter max_connections comme un réglage de performance. C’est un disjoncteur. Dimensionnez-le prudemment, puis concevez votre stack pour qu’il ait rarement de l’importance.

← Précédent
Échecs temporaires SMTP 4xx : causes principales et correctifs qui fonctionnent
Suivant →
Génération d’images intermédiaires : frames gratuites ou piège de latence ?

Laisser un commentaire