« MySQL server has gone away » est l’équivalent dans les bases de données d’un collègue qui disparaît en plein réunion : la salle devient silencieuse, tout le monde regarde son ordinateur, et pour une raison quelconque c’est désormais votre problème.
Sur Ubuntu 24.04, ce n’est généralement pas « un bug MySQL ». C’est un décalage de timeout, une limite de taille de paquet, un proxy qui se croit serviable, ou un redémarrage de mysqld que vous n’avez pas remarqué parce que l’application n’enregistre que « SQLSTATE[HY000] ». Nous allons corriger ça proprement : trouver rapidement le goulot d’étranglement, prouver la cause racine avec des commandes, et ajuster la bonne couche sans transformer votre base en pinata mémoire sans limites.
Ce que « server has gone away » signifie réellement (et ce que ça ne signifie pas)
La formule est trompeusement amicale. Elle implique que le serveur est parti prendre un café et reviendra bientôt. En pratique, cela signifie : le client a tenté d’utiliser une connexion MySQL et l’autre côté n’était plus là, ou le flux du protocole s’est brisé d’une manière dont le client ne peut pas se remettre.
Formes courantes de l’échec
- Erreur 2006 :
MySQL server has gone away(le client remarque que la socket est morte ou ne peut pas envoyer). - Erreur 2013 :
Lost connection to MySQL server during query(la requête a commencé, la connexion s’est rompue en cours d’exécution). - SQLSTATE[HY000] wrappers depuis les pilotes d’application (PDO, mysqli, JDBC) qui masquent le numéro d’erreur et le contexte.
Ce que ce n’est pas
Ce n’est pas automatiquement « la base est surchargée » et ce n’est pas résolu en « augmentant tous les timeouts ». Augmenter les timeouts à l’aveugle tend à masquer des fuites (fuites de connexions, transactions oubliées) jusqu’au jour où vous manquez de mémoire ou de threads.
Il n’y a que quelques catégories racines :
- Connexion idle tuée par MySQL (
wait_timeout) ou par un proxy/LB/NAT intermédiaire. - Paquet trop gros : MySQL le rejette (
max_allowed_packet) ou un proxy le rejette, ou le client atteint sa propre limite. - Redémarrage/crash du serveur : mysqld est mort, a été tué par OOM, ou systemd l’a redémarré.
- Réseau : resets TCP, problèmes de MTU, timeouts de conntrack, inadéquation des keepalive.
- Requête dépassant des timeouts (côté serveur
net_read_timeout/net_write_timeout, timeouts du proxy, timeouts côté client).
Une citation utile à garder en poche : L’espoir n’est pas une stratégie
— souvent entendue en ops ; une idée à appliquer au dépannage de base de données.
Blague #1 : Si votre solution est « régler les timeouts à 24 heures », vous n’avez pas résolu le problème, vous l’avez simplement programmé pour le prochain poste.
Faits et historique intéressants à utiliser
- Fait 1 : « MySQL server has gone away » existe depuis les premières bibliothèques clientes MySQL ; c’est un message côté client, pas une ligne de log serveur.
- Fait 2 : Le
wait_timeoutpar défaut classique était historiquement de 8 heures sur de nombreuses installations ; les environnements modernes avec proxies nécessitent souvent des timeouts plus courts et un pool correctement configuré. - Fait 3 : Le protocole est basé sur des paquets ; les statements volumineux et les lignes larges sollicitent à la fois
max_allowed_packetet les chemins d’allocation mémoire. - Fait 4 : Les passerelles NAT et pare-feu stateful ont fréquemment des timeouts TCP inactifs bien inférieurs aux valeurs par défaut de MySQL ; elles vont abandonner des sockets « en bonne santé » sans prévenir élégamment aucun des deux endpoints.
- Fait 5 : Les keepalives TCP Linux ont des valeurs conservatrices (heures). Dans les réseaux cloud, cela revient à « jamais », ce qui signifie que les connexions mortes peuvent persister jusqu’à la prochaine écriture qui échoue.
- Fait 6 : Beaucoup de drivers (surtout anciens) ne reconnectent pas automatiquement de manière sûre car reconnecter au milieu d’une transaction est un terrain miné pour l’intégrité.
- Fait 7 : Le
max_allowed_packetde MySQL existe côté serveur et côté client ; n’augmenter qu’un seul côté peut maintenir la panne. - Fait 8 : Un coupable moderne courant n’est pas MySQL du tout mais la pile couche‑7 : un pool de connexions côté application avec une durée de vie « infinie » qui garde des sockets que les proxies finissent par fermer.
- Fait 9 : L’erreur apparaît souvent après des déploiements parce que les déploiements changent le comportement des connexions : plus de parallélisme, payloads plus volumineux, logique de retry différente.
Méthode de diagnostic rapide (premier/deuxième/troisième)
Premier : déterminer si mysqld a redémarré ou planté
Si mysqld a redémarré autour du moment des erreurs, arrêtez de courir après les limites de paquets. Vos clients ont perdu la connexion parce que le serveur a disparu. Trouvez pourquoi il a redémarré.
- Vérifiez le journal systemd pour les redémarrages mysqld et les codes de sortie.
- Consultez le log d’erreurs MySQL pour des marqueurs de crash et la récupération InnoDB.
- Vérifiez les logs de l’OOM killer et la pression mémoire.
Deuxième : décider si c’est un timeout d’inactivité ou une coupure en cours de requête
« Gone away » juste après une période d’inactivité pointe vers wait_timeout ou des timeouts réseau/proxy. « Lost connection during query » pointe vers des requêtes longues, des timeouts de proxy, ou des timeouts côté serveur net_*.
- Comparez les timestamps d’erreur avec les « périodes d’inactivité » dans les logs applicatifs.
- Vérifiez les tendances de
Aborted_clientsetAborted_connectsdans MySQL. - Inspectez les réglages de timeout du proxy/LB si présent.
Troisième : tester les limites de paquets (seulement après avoir exclu les redémarrages)
Si les erreurs coïncident avec des inserts/updates volumineux ou du trafic BLOB, vérifiez max_allowed_packet côté serveur et client, et voyez si l’application envoie des statements de plusieurs mégaoctets.
- Vérifiez les valeurs actuelles de
max_allowed_packet(global et session). - Reproduisez avec un test contrôlé de payload volumineux.
- Corrigez en augmentant les limites de façon réfléchie ou en modifiant la manière d’envoyer les données (découpage, streaming, éviter les méga-queries).
Tâches pratiques : commandes, sorties, décisions (faire dans cet ordre)
Ce ne sont pas des « idées ». Ce sont les étapes à lancer sur Ubuntu 24.04 et ce que vous concluez de chacune. Exécutez-les en tant qu’utilisateur avec droits sudo sur l’hôte DB sauf mention contraire.
Tâche 1 : Confirmer quelle version de MySQL vous exécutez (MySQL vs MariaDB a de l’importance)
cr0x@server:~$ mysql --version
mysql Ver 8.0.39-0ubuntu0.24.04.1 for Linux on x86_64 ((Ubuntu))
Ce que cela signifie : Vous êtes sur des paquets Oracle MySQL 8.0. Les variables et chemins de logs correspondent aux conventions MySQL 8.0.
Décision : Utilisez la doc/variables MySQL 8.0 ; ne copiez pas les paramètres spécifiques à MariaDB.
Tâche 2 : Vérifier la santé du service et les redémarrages récents (systemd est le sérum de vérité)
cr0x@server:~$ systemctl status mysql --no-pager
● mysql.service - MySQL Community Server
Loaded: loaded (/usr/lib/systemd/system/mysql.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 08:22:10 UTC; 2h 11min ago
Main PID: 1234 (mysqld)
Status: "Server is operational"
Tasks: 44 (limit: 38316)
Memory: 1.3G
CPU: 18min 12.345s
CGroup: /system.slice/mysql.service
└─1234 /usr/sbin/mysqld
Ce que cela signifie : MySQL est actuellement en marche ; vous obtenez aussi le temps d’activité.
Décision : Si le temps d’activité est étonnamment court comparé au moment où les erreurs ont commencé, pivotez vers l’enquête sur crash/redémarrage avant d’ajuster les timeouts.
Tâche 3 : Récupérer la dernière heure de logs mysql.service (chercher sorties, OOM, redémarrages)
cr0x@server:~$ journalctl -u mysql --since "1 hour ago" --no-pager
Dec 30 09:54:01 server systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
Dec 30 09:54:01 server systemd[1]: mysql.service: Failed with result 'signal'.
Dec 30 09:54:01 server systemd[1]: mysql.service: Scheduled restart job, restart counter is at 1.
Dec 30 09:54:04 server systemd[1]: Started mysql.service - MySQL Community Server.
Ce que cela signifie : mysqld a reçu SIGKILL. Ce n’est pas gracieux. Raisons communes : OOM killer, kill administrateur, watchdog, ou éviction de conteneur.
Décision : Ne touchez pas encore à max_allowed_packet. Trouvez d’abord qui l’a tué.
Tâche 4 : Chercher des preuves de l’OOM killer (Ubuntu vous le dira)
cr0x@server:~$ journalctl -k --since "2 hours ago" | grep -E "Out of memory|Killed process|oom-kill" | tail -n 20
Dec 30 09:53:59 server kernel: Out of memory: Killed process 1234 (mysqld) total-vm:9123456kB, anon-rss:6212340kB, file-rss:0kB, shmem-rss:0kB, UID:112 pgtables:15432kB oom_score_adj:0
Dec 30 09:53:59 server kernel: oom_reaper: reaped process 1234 (mysqld), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Ce que cela signifie : MySQL a été tué pour cause d’épuisement mémoire. Votre appli voit « server has gone away » parce que c’était littéralement le cas.
Décision : Corrigez le dimensionnement mémoire, la concurrence ou les fuites ; augmenter les limites de paquets pourrait empirer la situation.
Tâche 5 : Localiser et suivre le log d’erreur MySQL (capturer la récupération après crash, les erreurs de paquet)
cr0x@server:~$ sudo ls -l /var/log/mysql/
total 120
-rw-r----- 1 mysql adm 118432 Dec 30 10:32 error.log
cr0x@server:~$ sudo tail -n 60 /var/log/mysql/error.log
2025-12-30T09:54:04.123456Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.39) starting as process 5678
2025-12-30T09:54:05.234567Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2025-12-30T09:54:06.345678Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.39' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
Ce que cela signifie : Vous voyez des marqueurs de redémarrage et de disponibilité ; si des erreurs de paquet existaient, vous verriez souvent « Got packet bigger than ‘max_allowed_packet’ bytes ».
Décision : Si les logs montrent une récupération après crash ou des redémarrages fréquents, corrigez la stabilité d’abord. Si les logs montrent des erreurs de taille de paquet, passez à la section paquets.
Tâche 6 : Vérifier les variables de timeout et de paquet actuelles (vérité globale)
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL VARIABLES WHERE Variable_name IN ('wait_timeout','interactive_timeout','net_read_timeout','net_write_timeout','max_allowed_packet','max_connections','connect_timeout');"
+---------------------+-----------+
| Variable_name | Value |
+---------------------+-----------+
| connect_timeout | 10 |
| interactive_timeout | 28800 |
| max_allowed_packet | 67108864 |
| max_connections | 500 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| wait_timeout | 28800 |
+---------------------+-----------+
Ce que cela signifie : Paquets de 64Mo, timeout idle de 8 heures, et timeouts net assez standards.
Décision : Si les erreurs surviennent après ~8 heures d’inactivité, c’est probablement wait_timeout. Si les erreurs surviennent autour de ~60 secondes pendant des jeux de résultats volumineux, suspectez net_write_timeout ou les timeouts du proxy.
Tâche 7 : Vérifier les variables de session du point de vue de l’appli (le pooling peut différer)
cr0x@server:~$ mysql -uroot -p -e "SHOW SESSION VARIABLES WHERE Variable_name IN ('wait_timeout','max_allowed_packet');"
+--------------------+----------+
| Variable_name | Value |
+--------------------+----------+
| max_allowed_packet | 67108864 |
| wait_timeout | 28800 |
+--------------------+----------+
Ce que cela signifie : La session courante hérite des valeurs globales. Les applis peuvent changer les valeurs de session au moment de la connexion.
Décision : Si l’appli utilise un compte différent avec des commandes d’initialisation, vérifiez cet utilisateur ou la configuration du pool de connexions spécifique.
Tâche 8 : Regarder les compteurs de connexions avortées (détecter timeouts et coupures réseau)
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Aborted_%';"
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Aborted_clients | 1249 |
| Aborted_connects | 12 |
+------------------+-------+
Ce que cela signifie : Aborted_clients compte les connexions avortées (client disparu, problème réseau, timeout). Ce n’est pas parfaitement diagnostique, mais les tendances comptent.
Décision : Si Aborted_clients augmente rapidement pendant les incidents, enquêtez sur les drops d’inactivité (proxy/NAT) ou sur une surcharge serveur causant des écritures bloquées.
Tâche 9 : Vérifier les connexions actuelles et ce qu’elles font (idle vs bloquées)
cr0x@server:~$ mysql -uroot -p -e "SHOW PROCESSLIST;"
+-----+------+-----------------+------+---------+------+------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------------+------+---------+------+------------------------+------------------+
| 101 | app | 10.10.0.21:5332 | prod | Sleep | 7200 | | NULL |
| 102 | app | 10.10.0.21:5333 | prod | Sleep | 7199 | | NULL |
| 201 | app | 10.10.0.22:6121 | prod | Query | 55 | Sending data | SELECT ... |
+-----+------+-----------------+------+---------+------+------------------------+------------------+
Ce que cela signifie : Vous avez des connexions pooled en sommeil depuis longtemps (2 heures). C’est acceptable si votre réseau le supporte. C’est problématique si un proxy tue les sockets inactifs après 60 minutes.
Décision : Si vous voyez beaucoup de connexions en Sleep très anciennes et que l’appli échoue après l’inactivité, alignez la durée de vie du pool et les timeouts du serveur/proxy.
Tâche 10 : Vérifier les resets TCP et retransmissions (le réseau ment‑il ?)
cr0x@server:~$ ss -tan sport = :3306 | head -n 20
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 10.0.0.10:3306 10.10.0.21:5332
ESTAB 0 0 10.0.0.10:3306 10.10.0.21:5333
ESTAB 0 0 10.0.0.10:3306 10.10.0.22:6121
Ce que cela signifie : Les connexions existent et ne semblent pas bloquées. Pour des signaux réseau plus profonds, utilisez nstat.
cr0x@server:~$ nstat -az | egrep "TcpRetransSegs|TcpExtTCPRcvCoalesce|TcpExtListenOverflows|TcpExtListenDrops" || true
TcpRetransSegs 124
TcpExtListenOverflows 0
TcpExtListenDrops 0
Décision : Si les retransmissions augmentent fortement pendant les erreurs, ce n’est pas « un réglage MySQL ». Enquêtez sur le chemin réseau, la MTU, la congestion, ou la saturation CPU hôte.
Tâche 11 : Valider les paramètres keepalive TCP Linux (souvent trop lents)
cr0x@server:~$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
Ce que cela signifie : Les keepalives commencent après 2 heures. Si votre pare‑feu coupe l’inactivité à 15 minutes, ça ne servira à rien.
Décision : Si vous devez maintenir des connexions longues et inactives, réduisez le keepalive time (ou mieux : fixez la durée de vie du pool en dessous du timeout réseau).
Tâche 12 : Chercher des erreurs de taille de paquet en greppant les logs
cr0x@server:~$ sudo grep -E "max_allowed_packet|packet bigger than" -n /var/log/mysql/error.log | tail -n 20
2025-12-30T10:01:12.123456Z 45 [Warning] [MY-000000] [Server] Got packet bigger than 'max_allowed_packet' bytes
Ce que cela signifie : Voilà votre preuve flagrante. Le serveur a rejeté un paquet entrant.
Décision : Augmentez max_allowed_packet à une valeur justifiée et corrigez le comportement applicatif qui a généré des paquets géants si possible.
Tâche 13 : Reproduire la limite de paquet en toute sécurité (test contrôlé, pas en période de production pico)
cr0x@server:~$ python3 - << 'PY'
import os
print(len(os.urandom(10*1024*1024)))
PY
10485760
Ce que cela signifie : Vous pouvez générer un blob de 10Mo. Testez ensuite un insert avec liaison de paramètres (préférable) en utilisant votre stack applicative, ou un test mysql local si acceptable.
Tâche 14 : Confirmer les sources de configuration sur Ubuntu (éviter d’éditer le mauvais fichier)
cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'pid_file'; SHOW VARIABLES LIKE 'socket';"
+---------------+------------------------------+
| Variable_name | Value |
+---------------+------------------------------+
| pid_file | /var/run/mysqld/mysqld.pid |
+---------------+------------------------------+
+---------------+------------------------------+
| Variable_name | Value |
+---------------+------------------------------+
| socket | /var/run/mysqld/mysqld.sock |
+---------------+------------------------------+
Décision : Sur le packaging Ubuntu, vous ajustez typiquement les paramètres MySQL dans /etc/mysql/mysql.conf.d/mysqld.cnf (ou des fichiers drop-in). Vérifiez avec mysqld --verbose --help si doute.
Tâche 15 : Tester la pression sur le max de connexions (épuisement de threads peut ressembler à « gone away »)
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 312 |
+-------------------+-------+
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| Max_used_connections| 498 |
+---------------------+-------+
Ce que cela signifie : Vous frôlez le max_connections (500). Quand vous atteignez le plafond, les applis échouent souvent à se connecter et rapportent mal l’erreur.
Décision : Si Max_used_connections approche la limite, corrigez le pooling et les fuites de connexions avant d’augmenter max_connections. L’augmenter peut transformer des « erreurs de connexion » en « OOM kill ».
Timeouts importants : wait_timeout, net_*_timeout, proxies et keepalives TCP
Commencez par la chronologie, pas par les noms de variables
La façon la plus rapide de résoudre « gone away » est de tracer le temps. Pas des tableaux de bord à quarante couleurs ; une simple chronologie :
- Quand l’application a-t-elle ouvert la connexion ?
- Combien de temps est‑elle restée inactive ?
- Quand l’erreur est‑elle survenue : à la première requête après l’inactivité, ou en plein milieu d’une requête ?
- Y a‑t‑il un proxy entre l’appli et MySQL ?
Si les erreurs surviennent à la première requête après une longue inactivité, vous regardez un décalage de timeout d’inactivité. Si les erreurs surviennent pendant une requête longue, vous regardez des timeouts lecture/écriture de requête, ou un crash/redémarrage du serveur.
wait_timeout et interactive_timeout : la guillotine d’inactivité de MySQL
wait_timeout est la durée pendant laquelle le serveur laisse une session non interactive rester inactive avant de la tuer. La plupart des connexions applicatives sont non interactives. interactive_timeout s’applique aux clients interactifs (comme un humain en terminal) quand le client définit le flag CLIENT_INTERACTIVE.
Ce qui va souvent de travers en production n’est pas « wait_timeout trop bas ». C’est généralement :
- Un pool applicatif qui garde des connexions pendant des heures.
- Un firewall/NAT/proxy en milieu de route qui coupe l’inactivité entre 5 et 60 minutes.
- L’appli conserve l’objet connexion et tente de le réutiliser plus tard.
- La requête suivante frappe une socket morte et vous obtenez « server has gone away ».
Ainsi, la solution n’est pas toujours « augmenter wait_timeout ». Dans de nombreux environnements, vous voudrez réduire le timeout server‑side et vous assurer que le pool n’accumule pas de zombies.
Conseils pratiques qui fonctionnent
- Fixez la durée de vie maximale du pool en dessous du plus court timeout inactif sur le chemin (NAT/proxy/firewall). Exemple : si le LB tue l’inactivité à 15 minutes, mettez la durée de vie du pool à 10 minutes.
- Privilégiez la validation à l’emprunt (requête de test) plutôt que les pings périodiques si votre charge est en rafales.
- Ne définissez pas wait_timeout sur plusieurs jours. Les connexions inactives longues ne sont pas « de la stabilité », ce sont des états obsolètes bien présentés.
net_read_timeout et net_write_timeout : les timeouts « en cours de requête »
Ceux‑ci sont des timeouts côté serveur pour lire depuis et écrire vers un client. Ils deviennent pertinents lorsque :
- Le client envoie un statement volumineux lentement (réseau lent, client surchargé), et le serveur considère qu’il a trop attendu.
- Le serveur envoie un gros jeu de résultats et le client ne lit pas (CPU client saturé, backpressure applicatif), donc le serveur se bloque puis se timeoute.
Si vous voyez Lost connection during query et que vous avez de gros résultats ou de grosses écritures, ces variables sont suspectes. Mais ne les augmentez pas aveuglément. Demandez pourquoi l’émetteur/récepteur est lent. En production, « client lent » est souvent un thread pool affamé par le GC, ou un worker PHP coincé faisant autre chose tout en gardant la socket.
Keepalives TCP Linux : plomberie de dernier recours
Les keepalives sont un moyen bas‑niveau pour maintenir l’état NAT/pare‑feu et détecter des pairs morts. Ils aident, mais ne remplacent pas un pooling correct.
Si vous contrôlez les serveurs et devez conserver des connexions longues via des middleboxes instables, régler les keepalives peut réduire les événements de sockets mortes surprises :
- Abaissez
tcp_keepalive_timeà quelque chose comme 300 secondes (5 minutes) si le chemin coupe l’inactivité autour de 10–15 minutes. - Gardez des intervalles raisonnables (
tcp_keepalive_intvl30–60 secondes) et un nombre modéré de probes.
Attention : les keepalives génèrent du trafic. Sur de très grandes flottes ce n’est pas gratuit. Mais c’est moins coûteux que de réveiller des humains.
Limites de paquets : max_allowed_packet, grosses lignes et requêtes volumineuses
Les erreurs de paquet sont simples à diagnostiquer quand on les capture réellement. La difficulté est que beaucoup de stacks n’enregistrent pas l’avertissement MySQL réel, donc vous ne voyez que « gone away ».
Ce que « paquet » signifie ici
Le protocole MySQL transmet des données en paquets, et max_allowed_packet limite le plus gros paquet que le serveur acceptera (et le plus gros qu’il enverra). Cela interagit avec :
- Immenses instructions
INSERT ... VALUES (...), (...), ... - Colonnes BLOB/TEXT volumineuses
- Gros jeux de résultats (serveur renvoyant beaucoup)
- Réplication et événements de binlog (oui, la taille des paquets compte aussi)
Comment choisir une valeur sans cargo cult
Valeurs courantes : 16Mo, 64Mo, 256Mo, 1Go. La tentation est de mettre 1Go « au cas où ». Ne le faites pas.
Pourquoi ? Parce que de plus gros paquets peuvent générer de plus gros buffers par connexion et des allocations mémoire importantes. Une seule requête gigantesque peut mettre la pression sur la mémoire et déclencher des OOM, qui se manifestent alors par… vous l’avez deviné… « server has gone away ».
Une approche raisonnable :
- Mesurez la taille des payloads : trouvez la plus grosse requête/ligne réaliste que votre application envoie.
- Ajoutez une marge : un facteur 2–4x suffit généralement, pas 100x.
- Privilégiez une modification applicative pour éviter les méga-statements : batcher plus petit, streamer les blobs, stocker les objets volumineux ailleurs, ou au moins compresser.
Les limites côté client comptent aussi
Beaucoup de clients ont leur propre max_allowed_packet ou équivalent. Si vous augmentez le serveur mais que le client refuse, vous échouerez toujours — parfois avec des erreurs spécifiques au driver qui embrouillent.
Le réparer correctement sur Ubuntu (MySQL 8.0)
Vous voulez un changement persistant dans la config, pas un SET GLOBAL qui disparaît au redémarrage.
Exemple de changement (illustratif) : définir max_allowed_packet=128M si vous avez la preuve que c’est nécessaire.
cr0x@server:~$ sudo grep -n "max_allowed_packet" /etc/mysql/mysql.conf.d/mysqld.cnf || true
cr0x@server:~$ sudo bash -lc 'printf "\n[mysqld]\nmax_allowed_packet=128M\n" >> /etc/mysql/mysql.conf.d/mysqld.cnf'
cr0x@server:~$ sudo systemctl restart mysql
Ce que cela signifie : Vous l’avez fixé au démarrage du serveur. Vérifiez ensuite.
cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL VARIABLES LIKE 'max_allowed_packet';"
+--------------------+-----------+
| Variable_name | Value |
+--------------------+-----------+
| max_allowed_packet | 134217728 |
+--------------------+-----------+
Décision : Si les erreurs cessent et que la mémoire reste stable, conservez-le. Si la mémoire monte ou que les événements OOM augmentent, votre « correction » est une grenade dont la goupille est juste scotchée.
Blague #2 : Un max_allowed_packet à 1Go, c’est comme acheter une plus grosse poubelle au lieu de sortir les déchets ; ça marche jusqu’à ce que ça ne marche plus, et alors c’est spectaculaire.
La cause sournoise : redémarrages, crashs, OOM et systemd
En production, « gone away » est très souvent littéral. mysqld est mort, a redémarré, ou a été remplacé pendant une opération de maintenance. Vos clients n’ont pas été consultés.
Pourquoi MySQL redémarre sur Ubuntu 24.04
- OOM killer dû à la pression mémoire (fréquent quand les buffers sont surdimensionnés ou que la charge a changé).
- Événements kernel ou hyperviseur (reboot de l’hôte, problèmes de live migration, stalls de stockage déclenchant des watchdog).
- Mises à jour de paquets redémarrant des services.
- Actions humaines (redémarrage pour changement de config) sans coordination avec les pools de connexion et la logique de retry.
- Crashs (des bugs existent ; mais considérez d’abord ressources et configuration).
Comment rendre les redémarrages moins douloureux
Deux angles : réduire la fréquence et réduire le rayon d’action.
- Réduire la fréquence : dimensionnez correctement la mémoire ; évitez les requêtes pathologiques ; assurez la santé du stockage ; ne faites pas tourner la machine à 99 % de RAM sans swap et sans plan.
- Réduire le rayon d’action : utilisez des retries applicatives idempotentes ; pools de connexion qui valident les connexions ; durées de vie de pool plus courtes ; envisagez MySQL Router/ProxySQL si approprié (et gérez alors leurs timeouts aussi).
Détecter rapidement les crash loops
Si vous voyez des redémarrages fréquents, vous avez un incident de stabilité, pas un exercice de tuning.
cr0x@server:~$ systemctl show mysql -p NRestarts -p ExecMainStatus -p ExecMainCode
NRestarts=3
ExecMainStatus=0
ExecMainCode=0
Ce que cela signifie : Le service a redémarré plusieurs fois depuis le boot.
Décision : Récupérez les journaux du journal sur la fenêtre des redémarrages ; vérifiez OOM et les logs d’erreur ; envisagez de réduire temporairement la concurrence côté appli pour stabiliser.
Proxies et load balancers : les intermédiaires de timeout
S’il y a un proxy entre votre appli et MySQL (HAProxy, ProxySQL, load balancer cloud, sidecar de service mesh), vous avez désormais au moins trois systèmes de timeout indépendants :
- Timeouts côté serveur MySQL
- Timeouts d’inactivité et de session du proxy
- Timeouts et réglages du client/driver et du pool
La plupart des cas « server has gone away après inactivité » viennent d’un décalage : le proxy tue la connexion en premier, mais le pool pense pouvoir la réutiliser indéfiniment.
À quoi ressemble le « correct »
- Le timeout d’inactivité le plus court gagne. La durée de vie max du pool doit être plus courte que celui‑ci.
- Keepalive seulement où nécessaire. Si le proxy supporte TCP keepalive, activez‑le là ; ne comptez pas uniquement sur les valeurs par défaut du kernel.
- Observer au niveau du proxy. Si le proxy journalise les raisons de déconnexion, cela vous fera gagner des heures.
Quand augmenter les timeouts est approprié
Parfois l’appli a vraiment besoin de requêtes longues (analytics, migrations, backfills). Dans ce cas :
- Augmentez les timeouts lecture/écriture du proxy/serveur au‑delà du temps de la plus longue requête légitime, plus une marge.
- Privilégiez le déplacement des travaux longs hors du chemin de requête.
- Utilisez des limites et des annulations correctes pour éviter des requêtes zombies qui ne finissent jamais.
Trois mini-histoires d’entreprise du terrain
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Ils avaient une hypothèse raisonnable : « Notre base et l’appli sont dans le même VPC, donc le réseau est stable. » Raisonnable n’est pas synonyme de correct.
L’application utilisait un pool avec une durée de vie généreuse. Les connexions restaient inactives des heures la nuit, puis étaient réutilisées le matin. Le taux d’erreur montait exactement au premier pic de trafic après 9h, puis retombait lentement.
La team a augmenté wait_timeout parce que ça ressemblait à des déconnexions inactives. Ça n’a rien résolu. Ils l’ont augmenté encore. Toujours rien. Pendant ce temps, les logs du proxy (que personne ne lisait) montraient que les sessions inactives étaient coupées à 60 minutes par une politique interne de load balancer.
Après avoir aligné la durée de vie du pool sur 45 minutes et activé une requête de validation à l’emprunt, l’erreur a disparu. La leçon n’était pas « augmenter les timeouts ». La leçon était « le plus court timeout sur le chemin décide ».
Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux
Une équipe voulait réduire les allers‑retours réseau. Ils ont modifié un import en masse pour créer d’énormes INSERT multi‑lignes. Le CPU a baissé, le débit a monté, tout le monde s’est félicité.
Puis un nouveau client a commencé à importer des enregistrements plus gros avec de longs blobs JSON. La première panne n’a pas été un « paquet trop grand » gracieux. Ce furent des « server has gone away » intermittents côté appli, suivis d’un redémarrage MySQL quelques minutes plus tard. L’on‑call a blâmé le réseau. Le réseau a blâmé l’appli. L’appli a blâmé MySQL.
La cause racine était simple : les statements gigantesques ont poussé l’utilisation mémoire (parsing serveur + buffers + overhead par connexion) et, sous forte concurrence, MySQL s’est fait OOM‑killer. L’optimisation a augmenté la taille des pires paquets et rendu les pics mémoire plus marqués. Ça marchait jusqu’à ce que ça ne marche plus.
La correction fut double : limiter la taille des batches pour maintenir les statements sous un maximum raisonnable, et définir max_allowed_packet selon les besoins réels. Ils ont conservé la plupart du gain de performance, et la base a cessé de jouer les mortes.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la situation
Une autre organisation avait l’habitude, terne sur le papier : après chaque changement significatif, ils capturaient les variables MySQL courantes, l’état systemd, et un petit ensemble de paramètres réseau OS dans un commentaire de ticket. Pas un dump gigantesque — juste ce qui explique le comportement.
Un après‑midi ils ont commencé à voir des « lost connection during query » sporadiques. L’instinct a été de blâmer une migration de schéma récente. Mais leurs « snapshots ennuyeux » montraient que net_write_timeout avait été abaissé des semaines plus tôt lors d’une tentative maladroite de « échouer vite », et le changement avait survécu dans un fichier drop‑in que personne ne se rappelait.
Ils ont rétabli la valeur, et les erreurs ont disparu. La migration était innocente. La valeur de la pratique n’était pas le snapshot en soi ; c’était la possibilité d’avoir rapidement un diff entre hypothèses et réalité.
C’est ce genre de rigueur ennuyeuse qui préserve vos week‑ends.
Erreurs courantes : symptôme → cause racine → correctif
1) Les erreurs surviennent juste après de longues périodes d’inactivité
Symptôme : La première requête après l’inactivité échoue avec « server has gone away », les retries suivants réussissent.
Cause racine : Les sessions TCP inactives sont dropées (proxy/NAT/pare‑feu) ou MySQL tue les sessions inactives (wait_timeout).
Correctif : Fixez la durée de vie max du pool en dessous du plus court timeout sur le chemin ; ajoutez une validation de connexion ; éventuellement ajustez les keepalives TCP. Si le serveur tue les inactifs et que vous avez vraiment besoin de longues inactivités, augmentez wait_timeout — mais seulement si le réseau peut les maintenir.
2) Les erreurs surviennent pendant de gros inserts/updates
Symptôme : Les pannes coïncident avec des écritures massives, du JSON volumineux, upload de BLOB, ou des statements multi‑lignes énormes.
Cause racine : Limite de paquet dépassée (max_allowed_packet) côté serveur ou client ; ou pression mémoire due aux statements gigantesques.
Correctif : Confirmer le message de log ; augmenter max_allowed_packet à une valeur justifiée ; réduire la taille des batches ; passer au streaming/découpage.
3) « Lost connection during query » sur des endpoints lents
Symptôme : Requête longue qui échoue après exécution. Ou gros jeux de résultats qui échouent quand le client est occupé.
Cause racine : Timeout lecture/écriture du proxy ou net_read_timeout/net_write_timeout MySQL trop bas ; client qui ne lit pas assez vite.
Correctif : Augmentez les timeouts pertinents pour dépasser les temps de requête réalistes ; corrigez l’appli pour streamer les résultats ; évitez de charger des jeux énormes en mémoire ; ajoutez de la pagination.
4) Les erreurs augmentent pendant les redémarrages MySQL
Symptôme : Les erreurs de connexion se concentrent autour des redémarrages de service ; l’uptime se remet à zéro.
Cause racine : Crash mysqld, OOM kill, redémarrage de paquet, reboot de l’hôte.
Correctif : Diagnostiquer la raison via journal + error log ; corriger le dimensionnement mémoire ; réduire la concurrence ; envisager le swap ; limiter les requêtes/paquets pires cas.
5) Les erreurs apparaissent après avoir augmenté max_connections
Symptôme : Moins de « too many connections », mais plus de « gone away » et de redémarrages.
Cause racine : Plus de connexions concurrentes augmentent l’utilisation mémoire et le context switching ; le serveur devient instable.
Correctif : Réduire les connexions avec un pooling adéquat ; ajouter un limiteur de connexions ; dimensionner correctement les buffers ; ne pas scaler par la force brute.
6) Les erreurs n’apparaissent que dans un environnement (prod, pas staging)
Symptôme : Staging est OK, prod échoue de façon intermittente.
Cause racine : Middleboxes/timeouts réseau différents, paramètres de pool différents, tailles de payload différentes, concurrence différente, ou config MySQL différente.
Correctif : Comparez le plus court timeout sur le chemin ; vérifiez les tailles réelles des payloads ; diff des variables MySQL ; reproduisez avec un volume de données proche de la prod.
Listes de contrôle / plan étape par étape
Plan étape par étape : arrêter l’hémorragie, puis corriger correctement
- Confirmer si MySQL a redémarré. Utilisez
systemctl status mysqletjournalctl -u mysql. Si oui, la stabilité passe avant tout. - Vérifier les OOM kills. Utilisez
journalctl -ken filtrant les messages OOM. Si OOM : réduire l’usage mémoire et la concurrence avant d’augmenter les limites. - Suivre le log d’erreur MySQL. Cherchez des avertissements de paquets et des marqueurs de récupération après crash.
- Classer le timing de l’erreur. Après inactivité vs pendant requête change entièrement la voie d’investigation.
- Inventorier les timeouts à travers les couches. MySQL (
wait_timeout,net_*), proxy, pool client, keepalive OS. - Corriger le décalage du plus court timeout. Réglez la durée de vie du pool et la validation ; ne tentez pas de « surpasser » un firewall par optimisme.
- Ce n’est qu’ensuite que vous ajustez les variables MySQL. Augmentez
max_allowed_packetsi vous avez des preuves ; ajusteznet_write_timeout/net_read_timeoutsi les coupures surviennent en cours de requête. - Retester avec une reproduction contrôlée. Utilisez une taille de payload connue, un temps de requête connu, et surveillez logs et compteurs.
- Verrouillez les changements dans la gestion de configuration. Évitez les fichiers drop‑in mystères et les SET GLOBAL « temporaires » qui disparaissent au redémarrage.
- Surveillez l’essentiel. Uptime, redémarrages, événements OOM, aborted clients, connexions utilisées, et distribution de latence.
Checklist : règles sûres pour le tuning des timeouts
- Ne définissez pas les timeouts idle du serveur plus longs que ce que le réseau peut maintenir.
- Durée de vie max du pool < plus court timeout réseau/proxy.
- Validation à l’emprunt vaut mieux que ping périodique pour des trafics en rafales.
- Augmentez read/write timeouts seulement après avoir confirmé des requêtes longues légitimes ou des clients lents.
Checklist : règles sûres pour le tuning des paquets
- Trouvez d’abord le payload légitime le plus volumineux.
- Augmentez par étapes (ex. 64M → 128M), pas des bonds jusqu’à 1G.
- Limitez la taille des batches ; évitez de construire des méga‑queries.
- Surveillez la mémoire après le changement ; taille de paquet et OOM sont de proches voisins.
FAQ
1) « MySQL server has gone away » est‑elle toujours un timeout ?
Non. Ça peut être un timeout, une limite de paquet, un redémarrage serveur, ou un reset réseau. Votre premier travail est de déterminer dans quelle catégorie vous êtes.
2) Dois‑je simplement augmenter wait_timeout pour arrêter les déconnexions inactives ?
Seulement si MySQL est la couche qui tue les inactifs et que vous avez vraiment besoin de connexions inactives longues. Dans de nombreux environnements, un proxy/NAT coupe la session TCP avant MySQL ; augmenter wait_timeout n’aide pas. Corrigez la durée de vie du pool et la validation d’abord.
3) Quelle valeur pour max_allowed_packet ?
Une valeur basée sur les tailles de payload mesurées plus une marge. 64Mo ou 128Mo conviennent souvent pour des applis qui déplacent occasionnellement des blobs moyens. Si vous avez besoin de centaines de Mo, repensez le design (chunking, object storage, streaming).
4) Pourquoi je ne le vois qu’après un déploiement ?
Les déploiements changent le comportement des connexions : plus de workers, différents defaults de pooling, requêtes plus grosses, logique de retry différente, ou un nouvel hop proxy. De plus, les upgrades de paquets peuvent redémarrer mysql.service.
5) Quelle est la différence entre erreur 2006 et 2013 ?
2006 indique souvent que la connexion était déjà morte quand on a tenté de l’utiliser. 2013 indique généralement que la connexion est morte pendant une requête ou un transfert de données. Ils pointent vers des suspects différents.
6) max_connections peut‑il causer « gone away » ?
Indirectement. Atteindre max_connections provoque des échecs de connexion ; selon le driver, l’appli peut mal rapporter l’erreur. Augmenter max_connections peut aussi accroître la pression mémoire et déclencher des crashs, qui eux génèrent de véritables « gone away ».
7) L’application doit‑elle auto‑reconnecter ?
L’auto‑reconnexion est dangereuse si vous êtes possiblement dans une transaction. L’approche plus sûre : utiliser un pool avec validation, gérer les retries à un niveau supérieur avec des opérations idempotentes, et échouer rapidement pour les écritures non idempotentes.
8) Les keepalives TCP résolvent‑ils cela de façon fiable ?
Ils aident avec les timeouts NAT/pare‑feu inactifs et la détection de pairs morts, mais ils ne remplacent pas des pools et timeouts correctement configurés. Utilisez‑les comme plomberie, pas comme architecture.
9) Et si le log d’erreur MySQL ne montre rien ?
Alors soit vous regardez au mauvais endroit, soit le logging est mal configuré, soit la déconnexion a lieu en dehors de MySQL (proxy/réseau). Confirmez les chemins de logs, vérifiez le journal systemd, et consultez les logs du proxy le cas échéant.
10) J’ai augmenté max_allowed_packet et ça échoue toujours. Et maintenant ?
Confirmez la limite côté client, confirmez que le réglage est appliqué (global vs session), et confirmez la taille du payload. Vérifiez aussi les limites du proxy et les événements OOM causés par des allocations plus grandes.
Conclusion : prochaines étapes à réaliser aujourd’hui
Si vous ne retenez qu’une habitude opérationnelle : cessez de traiter « server has gone away » comme un bug unique. C’est une classe de symptômes. Catégorisez vite.
- Vérifiez d’abord les redémarrages (
systemctl status,journalctl, logs OOM). Si mysqld est mort, corrigez la stabilité avant d’ajuster des boutons. - Décidez idle vs en cours de requête d’après le timing et le type d’erreur (2006 vs 2013). Cela vous dira si vous devez vous concentrer sur
wait_timeout/pooling ou sur read/write/query/proxy timeouts. - Prouvez les problèmes de paquet par des logs et une reproduction contrôlée. Augmentez
max_allowed_packetà une valeur justifiée, puis corrigez le batch pour éviter les falaises mémoire. - Alignez les timeouts à travers les couches (MySQL, proxy/LB, pool, keepalive TCP). Le plus court gagne ; agissez en conséquence.
Faites cela, et « server has gone away » cesse d’être un fantôme intermittent pour devenir un incident normal avec une cause racine et un postmortem court. Votre futur vous sera toujours fatigué, mais au moins il sera fatigué pour des raisons intéressantes.