Erreur 2006: le serveur MySQL est parti

8

J'exécute une application Python Pyramid sur un serveur CentOS en utilisant uWSGI et nginx. J'utilise SQLAlchemy comme ORM, MySQLdb comme API et MySQL comme base de données. Le site n'est pas encore en ligne, donc le seul trafic est moi et quelques autres employés de l'entreprise. Nous avons acheté des données pour remplir la base de données, de sorte que la table la plus grande (et la plus fréquemment interrogée) est d'environ 150 000 lignes.

Hier, j'ai ouvert quatre nouveaux onglets du site Web en succession rapide et j'ai récupéré quelques erreurs 502 Bad Gateway. J'ai regardé dans le journal uWSGI et j'ai trouvé ce qui suit:

sqlalchemy.exc.OperationalError: (OperationalError) (2006, 'MySQL server has gone away') 'SELECT ge...

Remarque importante: cette erreur n'est pas due au délai d'attente de MySQL. J'y suis allé, j'ai fait ça.

Je me demandais si le problème était dû à des demandes simultanées traitées simultanément. Je me suis fait un testeur de charge de pauvre:

for i in {1..10}; do (curl -o /dev/null http://domain.com &); done;

Effectivement, dans ces dix demandes, au moins une entraînerait une erreur de 2006, souvent plus. Parfois, les erreurs devenaient encore plus étranges, par exemple:

sqlalchemy.exc.NoSuchColumnError: "Could not locate column in row for column 'table.id'"

Quand la colonne existe très certainement et a bien fonctionné sur toutes les autres demandes identiques. Ou, celui-ci:

sqlalchemy.exc.ResourceClosedError: This result object does not return rows. It has been closed automatically.

Quand, encore une fois, cela a bien fonctionné pour toutes les autres demandes.

Pour vérifier davantage que le problème provient de connexions de base de données simultanées, j'ai défini uWSGI sur un seul travailleur et le multithreading désactivé, forçant les demandes à être traitées une par une. Effectivement, les problèmes ont disparu.

Pour tenter de trouver le problème, j'ai configuré un journal des erreurs pour MySQL. À l'exception de certains avis lors du démarrage de MySQL, il reste vide.

Voici ma configuration MySQL:

[mysqld]
default-storage-engine = myisam
key_buffer = 1M
query_cache_size = 1M
query_cache_limit = 128k
max_connections=25
thread_cache=1
skip-innodb
query_cache_min_res_unit=0
tmp_table_size = 1M
max_heap_table_size = 1M
table_cache=256
concurrent_insert=2
max_allowed_packet = 1M
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 64K
innodb_file_per_table=1
log-error=/var/log/mysql/error.log

Une recherche intensive sur l'erreur a révélé peu, mais a suggéré que j'augmente max_allowed_packet. Je l'ai augmenté à 100M et redémarré MySQL, mais cela n'a pas aidé du tout.

Pour résumer: les connexions simultanées à la cause MySQL 2006, 'MySQL server has gone away'et quelques autres erreurs étranges. Il n'y a rien de pertinent dans le journal des erreurs de MySQL.

J'y travaille depuis des heures et je n'ai fait aucun progrès. Quelqu'un peut-il m'aider?

Theron Luhn
la source
Lorsque vous traitez des demandes simultanées, chaque thread (ou processus ou autre) établit-il sa propre connexion à la base de données?
DerfK
Chaque processus possède un pool de connexions géré par SQLAlchemy, donc chaque demande doit avoir sa propre connexion.
Theron Luhn
Autre remarque: le test de charge ne pose aucun problème sur mon serveur de développement local, qui est Serveuse pour le serveur et MySQL pour la base de données.
Theron Luhn

Réponses:

18

J'ai également rencontré cela et trouvé la raison et le correctif.

La raison pour laquelle cela se produit est que le plugin python uwsgi (ou plus probablement tous les plugins uwsgi) fork () les nouveaux travailleurs après le chargement de l'application dans le parent. Par conséquent, les enfants héritent de toutes les ressources (y compris les descripteurs de fichiers comme la connexion db) du parent.

Vous pouvez en lire brièvement sur le wiki uwsgi :

uWSGI essaie d'abuser de la copie de fork () en écriture autant que possible. Par défaut, il se bifurquera après avoir chargé vos applications. Si vous ne souhaitez pas ce comportement, utilisez l'option --lazy. L'activer, demandera à uWSGI de charger les applications après le fork de chaque travailleur ()

Et comme vous le savez peut-être, les connexions et les curseurs mysqldb de Python ne sont pas threadsafe sauf si vous les protégez explicitement. Par conséquent, plusieurs processus (tels que les travailleurs uwsgi) utilisant la même connexion / curseur mysql le corrompent.

Dans mon cas (pour l' API Gold du roi Arthur ), cela a bien fonctionné lorsque j'ai créé la connexion MySQL par demande dans la portée d'un autre module, mais lorsque je voulais des connexions persistantes pour améliorer les performances, j'ai déplacé la connexion à la base de données et le curseur vers la portée globale dans le module parent. En conséquence, mes relations se chevauchaient comme les vôtres.

Le correctif consiste à ajouter le mot clé "lazy" (ou l'option de ligne de commande --lazy) à votre configuration uwsgi. En conséquence, l'application sera à nouveau fourchue pour chaque enfant au lieu de bifurquer du parent et de partager la connexion (et de marcher dessus à un moment donné, de sorte que le serveur MySQL la force à fermer en raison d'une demande corrompue à un moment donné).

Enfin, si vous vouliez un moyen de le faire sans modifier votre configuration uwsgi, vous pouvez probablement utiliser le décorateur @postfork pour créer correctement une nouvelle connexion à la base de données immédiatement après le fork d'un processus de travail. Vous pouvez en lire plus ici .

Je constate d'après votre suivi que vous êtes déjà passé à pgsql, mais voici la réponse pour que vous puissiez mieux dormir la nuit et pour quelqu'un comme vous et j'essaie de trouver la réponse à cela!

PS Une fois que j'ai eu une compréhension du problème (le curseur était corrompu à cause des travailleurs qui marchaient les uns sur les autres) mais que je ne me suis pas rendu compte de la fourchette () et --lazy, j'envisageais d'implémenter mon propre pool où les travailleurs " consultez "une connexion mysql à partir d'un pool à portée globale, puis" réinscrivez-vous "juste avant de quitter application (), mais il est probablement préférable d'utiliser --lazy à moins que votre charge web / application varie suffisamment pour que vous soyez constamment créer de nouveaux travailleurs. Même dans ce cas, je préférerais --lazy car il est beaucoup plus propre que l'implémentation de votre propre pool de connexions db.

modifier: voici une description plus approfondie de ce problème + solution car il y a une pénurie d'informations à ce sujet pour ceux qui l'ont rencontré: http://tns.u13.net/?p=190

FliesLikeABrick
la source
C'est vraiment bon de savoir ce qui a causé cela. Je vous remercie!
Theron Luhn
Je viens de dire que ce message était exactement le même problème que moi aussi et que votre solution l'a corrigé :) Merci!
MasterGberry
"Par conséquent, plusieurs processus (tels que les travailleurs uwsgi) utilisant la même connexion / curseur mysql le corrompront." C'était très instructif. J'avais deux connexions ouvertes à la même base de données sur ma section locale (une à partir d'un shell, une autre à partir de mon application wsgi) et j'ai eu cette erreur. La base de données se rapportait à lui-même pinget à d'autres mysqladmindemandes. C'était probablement parce que j'essayais de supprimer la base de données du shell ... mais cela continuait de donner l'erreur "le serveur est parti" à cette commande. Quoi qu'il en soit, merci!
Brian Peterson
tu m'as sauvé la vie.
sangsue du