Я запускаю приложение 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
Тяжелые поиски в Google об ошибке мало что показали, но предложили увеличить max_allowed_packet. Я увеличил его до 100 МБ и перезапустил MySQL, но это совершенно не помогло.
Подвести итоги: Параллельные подключения к MySQL вызывают 2006, 'MySQL server has gone away'
и еще несколько странных ошибок. В журнале ошибок MySQL нет ничего значимого.
Я работал над этим часами и не добился никакого прогресса. Кто-нибудь может мне помочь?
Я тоже столкнулся с этим, нашел причину и исправил.
Причина, по которой это происходит, заключается в том, что плагин python uwsgi (или, что более вероятно, все плагины uwsgi) fork () создает новые рабочие процессы после загрузки приложения в родительском. В результате дочерние элементы наследуют все ресурсы (включая дескрипторы файлов, такие как соединение с базой данных) от родителя.
Вы можете кратко прочитать об этом на uwsgi вики :
uWSGI пытается злоупотреблять копированием fork () при записи, когда это возможно. По умолчанию он разветвляется после загрузки ваших приложений. Если вы не хотите такого поведения, используйте параметр --lazy. При его включении uWSGI будет загружать приложения после fork () каждого воркера.
И, как вы, возможно, знаете, соединения и курсоры Python mysqldb не являются потокобезопасными, если вы явно не защищаете их. Следовательно, несколько процессов (например, uwsgi worker), использующих одно и то же соединение / курсор mysql одновременно, повредят его.
В моем случае (для Золото короля Артура API) это работало нормально, когда я создавал соединение MySQL для каждого запроса в области другого модуля, но когда мне требовались постоянные соединения для повышения производительности, я перемещал соединение с базой данных и курсор в глобальную область видимости родительского модуля. В результате мои связи наступали друг на друга, как и ваши.
Чтобы исправить это, добавьте ключевое слово "lazy" (или параметр командной строки --lazy) в конфигурацию uwsgi. В результате приложение будет разветвлено заново для каждого потомка, вместо того, чтобы разветвляться от родителя и разделять соединение (и наступать на него в какой-то момент, так что сервер MySQL принудительно закрывает его из-за поврежденного запроса в какой-то момент).
Наконец, если вам нужен способ сделать это без изменения конфигурации uwsgi, вы, вероятно, можете использовать декоратор @postfork, чтобы правильно создать новое соединение с базой данных сразу после разветвления рабочего процесса. Вы можете прочитать об этом Вот.
Из ваших последующих действий я вижу, что вы уже перешли на pgsql, но вот ответ, чтобы вы могли лучше спать по ночам и для всех, кто, как вы и я, пытаемся найти ответ на этот вопрос!
P.S. Как только я понял проблему (курсор был поврежден из-за того, что рабочие наступали друг на друга), но не осознал бит о fork () и --lazy, я задумал реализовать свой собственный пул, в котором рабочие будут «проверять» out "соединение mysql из пула в глобальной области видимости, затем" возвратиться "непосредственно перед выходом из application (), однако, вероятно, гораздо лучше использовать --lazy, если только нагрузка вашего веб-приложения / приложения не изменяется настолько, что вы постоянно создаете новые рабочие. Даже в этом случае я мог бы предпочесть --lazy, потому что он значительно чище, чем реализация собственного пула соединений db.
edit: вот более подробное описание этой проблемы + решения, так как для других, кто с ней столкнулся, не хватает информации: http://tns.u13.net/?p=190