Erro 2006: o servidor MySQL foi embora

7

Estou executando um aplicativo Python Pyramid em um servidor CentOS usando uWSGI e nginx. Estou usando SQLAlchemy como ORM, MySQLdb como API e MySQL como banco de dados. O site ainda não foi publicado, então o único tráfego é de mim e de alguns outros funcionários da empresa. Nós compramos alguns dados para preencher o banco de dados, então a maior (e mais freqüentemente consultada) tabela é ~ 150.000 linhas.

Ontem eu abri quatro novas abas do website em rápida sucessão, e recebi de volta um par de 502 erros de Gateway Incorreto. Eu olhei no log do uWSGI e encontrei o seguinte:

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

Nota importante: Este erro não é devido ao wait_timeout do MySQL. Estive lá, feito isso.

Gostaria de saber se o problema foi causado por solicitações simultâneas sendo exibidas simultaneamente. Eu me fiz um testador de carga para um homem pobre:

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

Com certeza, dentro desses dez pedidos pelo menos um lançaria um erro de 2006, muitas vezes mais. Às vezes, os erros ficariam ainda mais estranhos, por exemplo:

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

Quando a coluna definitivamente existe e funcionou bem em todas as outras solicitações idênticas. Ou esta:

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

Quando, mais uma vez, funcionou bem para todos os outros pedidos.

Para verificar melhor se o problema resultou de conexões de banco de dados simultâneas, configurei o uWSGI como um único trabalhador e o multiencadeamento desativado, forçando os pedidos a serem processados um de cada vez. Com certeza, os problemas desapareceram.

Em uma tentativa de encontrar o problema, eu configurei um log de erros para o MySQL. Com exceção de alguns avisos durante a inicialização do MySQL, ele permanece vazio.

Aqui está minha configuração do 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

O Google pesquisado no erro revelou pouco, mas sugeriu que eu aumentasse max_allowed_packet. Eu aumentei para 100M e reiniciei o MySQL, mas isso não ajudou em nada.

Para resumir: Conexões simultâneas com o MySQL causam 2006, 'MySQL server has gone away' e alguns outros erros estranhos. Não há nada de relevância no log de erros do MySQL.

Eu tenho trabalhado nisso por horas e não fiz nenhum progresso. Alguém pode me ajudar?

    
por Theron Luhn 13.07.2012 / 20:27

1 resposta

15

Também encontrei isso e descobri o motivo e corrijo.

A razão pela qual isso acontece é que o plugin python uwsgi (ou mais provavelmente todos os plugins uwsgi) fork () new workers depois que o aplicativo é carregado no pai. Como resultado, os filhos herdam todos os recursos (incluindo descritores de arquivos como a conexão db) do pai.

Você pode ler sobre isso brevemente no wiki uwsgi :

uWSGI tries to abuse fork() copy on write whenever possible. By default it will fork after having loaded your applications. If you do not want that behaviour use the --lazy option. Enabling it, will instruct uWSGI to load the applications after each worker's fork()

E, como você deve saber, as conexões e os cursores do mysqldb do Python não são thread-safe, a menos que você os proteja explicitamente. Portanto, vários processos (como os operadores uwsgi) usando a mesma conexão / cursor mysql concorrentemente o corromperão.

No meu caso (para a API King Arthur's Gold ), isso funcionou bem quando criei a conexão MySQL por solicitação no escopo de outro módulo, mas quando eu queria conexões persistentes para ajudar com o desempenho, movi a conexão com o banco de dados e o cursor para o escopo global no módulo pai. Como resultado, minhas conexões estavam pisando uma na outra como a sua.

A correção para isso é adicionar a palavra-chave "lazy" (ou opção de linha de comando --lazy) à sua configuração do uwsgi. Como resultado, o aplicativo será bifurcado novamente para cada filho em vez de bifurcar-se do pai e compartilhar a conexão (e avançar em algum ponto, de forma que o servidor MySQL force o fechamento devido a um pedido corrompido em algum momento).

Finalmente, se você quiser uma maneira de fazer isso sem modificar sua configuração do uwsgi, você provavelmente poderá usar o @postfork decorator para criar adequadamente uma nova conexão de banco de dados imediatamente após um processo de trabalho ser bifurcado. Você pode ler sobre isso aqui .

Eu vejo do seu follow-up que você já mudou para pgsql, mas aqui está a resposta para que você possa dormir melhor à noite e para qualquer pessoa como você e eu tentando encontrar a resposta para isso!

P.S. Uma vez que eu tive uma compreensão do problema (o cursor sendo corrompido devido a que os funcionários pisavam um no outro), mas não percebi o pouco sobre fork () e --lazy, eu estava pensando em implementar meu próprio pool onde os trabalhadores "verificariam" out "uma conexão mysql de um pool no escopo global, então" volte a verificar "logo antes de sair do application (), entretanto é muito melhor usar --lazy a menos que a carga da sua aplicação varie o suficiente para que você esteja constantemente criando novos trabalhadores. Mesmo assim, talvez prefira --lazy, porque é significativamente mais limpo do que implementar seu próprio pool de conexões de banco de dados.

edit: aqui está um artigo mais completo sobre este problema + solução, pois há uma escassez de informações sobre ele para outros que o encontraram: link

    
por 07.08.2012 / 00:05