web-dev-qa-db-fra.com

Pourquoi dois-je obtenir "MySQL Server est parti" après avoir exécuté un bot de télégramme pendant quelques heures?

Je construis un Django (ver. 3.0.5) application qui utilise mysqlclient (ver. 2.0.3) en tant que backend dB. De plus, j'ai écrit un Django commande qui exécute un bot écrit à l'aide de l'API Python-Telegram-bot API, de sorte que la mission de ce bot est de fonctionner indéfiniment, car elle doit répondre aux commandes à toute heure.

Le problème est qu'environ 24h. Après avoir exécuté le bot (pas nécessairement d'inactivité tout le temps), je reçois une exception Django.db.utils.OperationalError: (2006, 'MySQL server has gone away') après avoir exécuté une commande.

Je suis absolument sûr que le serveur MySQL fonctionne tout le temps et est toujours en cours d'exécution à l'époque où je reçois cette exception. La version Server MySQL est 5.7.35.

Mon hypothèse est que certains threads MySQL sont âgés de vieillissement et sont fermés, alors après les avoir réutilisés, ils ne seront pas renouvelés.

Quelqu'un a-t-il heurté cette situation et sait comment résoudre?

Traceback (most recent call last):
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/telegram/ext/dispatcher.py", line 555, in process_update
    handler.handle_update(update, self, check, context)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/telegram/ext/handler.py", line 198, in handle_update
    return self.callback(update, context)
  File "/opt/Django/gip/gip/hospital/gipcrbot.py", line 114, in ayuda
    perfil = get_permiso_efectivo(update.message.from_user.id)
  File "/opt/Django/gip/gip/hospital/telegram/funciones.py", line 33, in get_permiso_efectivo
    u = Telegram.objects.get(idtelegram=userid)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 411, in get
    num = len(clone)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 258, in __len__
    self._fetch_all()
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 1261, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/query.py", line 57, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/models/sql/compiler.py", line 1151, in execute_sql
    cursor.execute(sql, params)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 100, in execute
    return super().execute(sql, params)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 68, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 77, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 86, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/utils.py", line 86, in _execute
    return self.cursor.execute(sql, params)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/Django/db/backends/mysql/base.py", line 74, in execute
    return self.cursor.execute(query, args)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 206, in execute
    res = self._query(query)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 319, in _query
    db.query(q)
  File "/opt/Django/gip/venv/lib/python3.6/site-packages/MySQLdb/connections.py", line 259, in query
    _mysql.connection.query(self, query)
Django.db.utils.OperationalError: (2006, 'MySQL server has gone away')

choses que j'ai essayées

J'ai déjà essayé de changer le Django settings.py fichier donc j'ai défini une valeur explicite pour CONN_MAX_AGE, et j'ai également défini une valeur pour le client MySQL wait_timeout Paramètre, être CONN_MAX_AGE plus bas que wait_timeout.

Paramètres.py :

DATABASES = {
    'default': {
        'ENGINE': 'Django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/opt/Django/gip/gip/gip/my.cnf',
        },
        'CONN_MAX_AGE': 3600,
    }
}

my.cnf :

[client]
...
wait_timeout = 28800

Malheureusement, le comportement est exactement le même: je reçois une exception environ 24h. après avoir exécuté le bot.


Réglage CONN_MAX_AGE à None ne fera aucune différence non plus.


J'ai installé le mysql-server-has-gone-away python package tel que proposé par @ r-marolahy, mais cela ne fera pas une différence non plus. Après près de 24 heures après l'avoir exécuté, le message "parti" montre à nouveau.


J'ai aussi essayé l'approche de la fermeture de vieilles connexions:

from Django.db import close_old_connections

try:
    #do your long running operation here
except Django.db.utils.OperationalError:
    close_old_connections()
    #do your long running operation here

Obtenir toujours le même résultat.

5
nKn

J'ai fini par programmer une requête DB toutes les x heures (dans ce cas, 6h) dans le bot. Le python-telegram-bot A une classe appelée JobQueue qui a une méthode appelée run_repeating. Cela dirigera une tâche chaque N secondes. Alors j'ai déclaré:

def check_db(context):
    # Do the code for running "SELECT 1" in the DB
    return

updater.job_queue.run_repeating(check_db, interval=21600, first=21600)

Après ce changement, je n'ai plus eu le même problème.


En outre, appelez la plupart des indocumés close_if_unusable_or_obsolete() Django méthode de temps à autre fonctionne aussi bien dans mon cas.

from Django.db import connection

connection.close_if_unusable_or_obsolete()
0
nKn

La connexion se brisera pour l'une des raisons pour des raisons, pas seulement des délais d'attente. Donc, il suffit de planifier la rupture.

Planifier une (solution la plus robuste):

Chaque fois que vous exécutez une requête, vérifiez pour les erreurs et avez du code à reconnecter et à relancer la requête (ou transaction).

Plan B (risqué pour les transactions):

Activer la reconnexion automatique. Ceci est mauvais Si vous utilisez des transactions multi-sorties. La reconnexion automatique au milieu d'une transaction peut conduire à un jeu de données corrompu (en violant la sémantique d'une "transaction".) C'est parce que la première partie de la transaction est ROLLBACK'd (en raison du déconnexion) et du repos est COMMITted.

Plan c (direct droit):

Se connecter lorsqu'un message arrive; Fais ton travail; puis déconnectez-vous. C'est-à-dire être déconnecté la plupart du temps. Cette approche est nécessaire si vous pouviez avoir beaucoup de clients qui se connectent (mais font rarement n'importe quoi).

Plan D (ne vaut pas la peine d'être envisagé):

Augmenter divers délai d'attente. Pourquoi la résolution de la résolution d'un problème (faible délai d'attente) lorsque d'autres problèmes sont non résolus (Network Hiccup).

Ma préférence? C est facile et presque toujours suffisant. A prend plus de code, mais est "meilleur". Les deux C et A sont peut-être encore mieux.

0
Rick James

La raison pour laquelle cela se produise est parce que le close_old_connection une fonction.

Ainsi, ce que vous pouvez essayer de faire est d'ajouter l'appel à la fermeture ancienne avant d'interagir avec DB:

Exemple de code :

from Django.db import close_old_connections
close_old_connections()
# do some db actions, it will reconnect db

S'il vous plaît laissez-moi savoir si cela ne résout pas votre problème.

0
Sabil