Ошибка 2006: сервер MySQL ушел

8

Я запускаю приложение Python Pyramid на сервере CentOS, используя uWSGI и nginx. Я использую SQLAlchemy в качестве ORM, MySQLdb в качестве API и MySQL в качестве базы данных. Сайт еще не запущен, поэтому единственный трафик - это я и некоторые другие сотрудники компании. Мы приобрели некоторые данные для заполнения базы данных, поэтому самая большая (и наиболее часто запрашиваемая) таблица составляет ~ 150 000 строк.

Вчера я быстро открыл четыре новые вкладки сайта и получил пару ошибок 502 Bad Gateway. Я посмотрел в журнале uWSGI и обнаружил следующее:

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

Важное примечание: эта ошибка не связана с MySQL wait_timeout. Был там, сделал это.

Я задавался вопросом, была ли проблема вызвана одновременными запросами, обслуживаемыми одновременно. Я сделал себя тестером нагрузки бедного человека:

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

Конечно, в этих десяти запросах, по крайней мере, один вызовет ошибку 2006 года, а зачастую и больше. Иногда ошибки становятся еще более странными, например:

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

Когда столбец наиболее определенно существует и отлично работает на всех других идентичных запросах. Или этот:

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

Когда снова все работало нормально для всех остальных запросов.

Для дальнейшей проверки того, что проблема возникла из-за одновременных подключений к базе данных, я установил uWSGI для одного рабочего и отключил многопоточность, заставляя запросы обрабатываться по одному за раз. Конечно же, проблемы исчезли.

В попытке найти проблему я настроил журнал ошибок для MySQL. За исключением некоторых уведомлений при запуске MySQL, он остается пустым.

Вот мой конфиг 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

Тяжелое гугление по ошибке выявило мало, но предложило увеличить max_allowed_packet. Я увеличил его до 100M и перезапустил MySQL, но это совсем не помогло.

Подводя итог: параллельные соединения с MySQL вызывают 2006, 'MySQL server has gone away'и некоторые другие странные ошибки. В журнале ошибок MySQL нет ничего уместного.

Я работал над этим часами и не добился никакого прогресса. Кто-нибудь может мне помочь?

Терон Лун
источник
Когда вы имеете дело с параллельными запросами, каждый поток (или процесс или что-то еще) устанавливает свое собственное соединение с базой данных?
DerfK
Каждый процесс имеет пул соединений, управляемый SQLAlchemy, поэтому каждый запрос должен иметь свое собственное соединение.
Терон Лун
Еще одно замечание: нагрузочное тестирование не вызывает никаких проблем на моем локальном сервере разработки, а именно Waitress для сервера и MySQL для базы данных.
Терон Лун

Ответы:

18

Я также столкнулся с этим и нашел причину и исправить.

Причина этого заключается в том, что плагин Python UWSGI (или, скорее всего, все плагины UWSGI) fork () новых рабочих после загрузки приложения в родительский. В результате дочерние элементы наследуют все ресурсы (включая файловые дескрипторы, такие как соединение db) от родительского.

Вы можете кратко прочитать об этом в вики uwsgi :

uWSGI пытается злоупотреблять копией fork () при записи, когда это возможно. По умолчанию он будет работать после загрузки ваших приложений. Если вы не хотите такого поведения, используйте параметр --lazy. Включив его, вы получите указание uWSGI загружать приложения после каждого форка ().

И, как вы, возможно, знаете, соединения и курсоры Python для mysqldb не являются поточно-ориентированными, если вы явно не защитите их. Поэтому несколько процессов (например, рабочие uwsgi), использующие одно и то же соединение / курсор mysql, одновременно повредят его.

В моем случае (для Золотого API короля Артура ) это работало нормально, когда я создавал соединение MySQL для каждого запроса в области видимости другого модуля, но когда я хотел, чтобы постоянные соединения помогали с производительностью, я переместил соединение с базой данных и курсор в глобальную область видимости в родительский модуль. В результате мои связи наступали друг на друга, как ваши.

Чтобы исправить это, добавьте ключевое слово "lazy" (или параметр командной строки --lazy) в вашу конфигурацию uwsgi. В результате приложение будет разветвлено заново для каждого дочернего элемента вместо того, чтобы разветвляться от родителя и делиться соединением (и наступать на него в некоторый момент, так что сервер MySQL принудительно закрывает его из-за поврежденного запроса в некоторый момент).

Наконец, если вам нужен способ сделать это без изменения конфигурации uwsgi, вы можете использовать декоратор @postfork для правильного создания нового соединения с базой данных сразу после разрыва рабочего процесса. Вы можете прочитать об этом здесь .

Из вашего наблюдения я вижу, что вы уже переключились на pgsql, но вот ответ, чтобы вы могли лучше спать по ночам и для любого, как вы, и я пытаюсь найти ответ на этот вопрос!

PS Как только я понял проблему (курсор был поврежден из-за того, что работники наступали друг на друга), но не понял, что такое fork () и --lazy, я подумал о создании своего собственного пула, в котором работники могли бы " проверьте «соединение mysql из пула в глобальной области», а затем «вернитесь обратно» непосредственно перед выходом из application (), однако, вероятно, гораздо лучше использовать --lazy, если загрузка вашего веб-приложения / приложения меняется настолько, что вы постоянно создание новых работников. Даже тогда я мог бы предпочесть --lazy, потому что он значительно чище, чем реализация собственного пула соединений с БД.

редактировать: вот более подробное описание этой проблемы + решение, так как не хватает информации для других, кто столкнулся с ней: http://tns.u13.net/?p=190

FliesLikeABrick
источник
Это определенно приятно знать, что вызвало это. Спасибо!
Терон Лун
Просто добавлю, что этот пост был точно такой же проблемой, как и у меня, и ваше решение исправило это :) Спасибо!
MasterGberry
«Поэтому несколько процессов (например, рабочие uwsgi), использующие одно и то же соединение / курсор mysql, одновременно повредят его». Это было очень поучительно. У меня было два подключения к одной и той же базе данных в локальной сети (одно из оболочки, другое из моего приложения wsgi) и я получил эту ошибку. База данных сообщала о себе pingи других mysqladminзапросах. Вероятно, это потому, что я пытался удалить базу данных из оболочки ... но она продолжала выдавать ошибку "сервер ушел" для этой команды. В любом случае, спасибо!
Брайан Петерсон
Вы спасли мою жизнь.
пиявка