Erro 2006: servidor MySQL foi embora

8

Estou executando um aplicativo Python Pyramid em um servidor CentOS usando uWSGI e nginx. Estou usando SQLAlchemy como um ORM, MySQLdb como API e MySQL como banco de dados. O site ainda não foi lançado, então o único tráfego sou eu e alguns outros funcionários da empresa. Como adquirimos alguns dados para preencher o banco de dados, a maior (e mais frequentemente consultada) tabela é de ~ 150.000 linhas.

Ontem, abri quatro novas guias do site em rápida sucessão e recuperei alguns erros 502 Bad Gateway. Procurei 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 se deve ao wait_timeout do MySQL. Estive lá, fiz isso.

Gostaria de saber se o problema foi causado por solicitações simultâneas sendo atendidas simultaneamente. Fiz para mim um testador de carga de pobre:

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

Certamente, dentre essas dez solicitações, pelo menos uma geraria um erro de 2006, muitas vezes mais. Às vezes, os erros ficavam 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 todos os outros pedidos idênticos. Ou este:

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 ainda mais se o problema surgiu de conexões simultâneas com o banco de dados, configurei o uWSGI para um único trabalhador e o multithread desativado, forçando as solicitações a serem processadas uma por vez. Com certeza, os problemas desapareceram.

Na tentativa de encontrar o problema, 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

Pesquisando profundamente no erro revelou pouco, mas sugeri que eu aumentasse max_allowed_packet. Aumentei para 100M e reiniciei o MySQL, mas isso não ajudou em nada.

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

Trabalho nisso há horas e não fiz nenhum progresso. Alguém pode me ajudar?

Theron Luhn
fonte
Quando você lida com solicitações simultâneas, cada thread (ou processo ou qualquer outra coisa) faz sua própria conexão com o banco de dados?
DerfK 13/07/12
Cada processo possui um conjunto de conexões gerenciado por SQLAlchemy, portanto, cada solicitação deve ter sua própria conexão.
Theron Luhn
Outra observação: o teste de carga não causa problemas no meu servidor de desenvolvimento local, que é o Waitress para o servidor e o MySQL para o banco de dados.
Theron Luhn

Respostas:

18

Encontrei isso também e encontrei o motivo e a correção.

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

Você pode ler sobre isso brevemente no wiki do uwsgi :

O uWSGI tenta abusar da cópia fork () na gravação sempre que possível. Por padrão, ele bifurca-se depois de carregar seus aplicativos. Se você não deseja esse comportamento, use a opção --lazy. Ativando, instruirá o uWSGI a carregar os aplicativos após a bifurcação de cada trabalhador ()

E como você deve saber, as conexões e cursores do mysqldb do Python não são seguros para threads, a menos que você os proteja explicitamente. Portanto, vários processos (como os trabalhadores do uwsgi) usando a mesma conexão / cursor do mysql simultaneamente o corrompem.

No meu caso (para a API Gold do King Arthur ), isso funcionou bem quando criei a conexão MySQL por solicitação no escopo de outro módulo, mas quando desejei conexões persistentes para ajudar no desempenho, movi a conexão e o cursor do banco de dados para o escopo global em o módulo pai. Como resultado, minhas conexões se interpuseram como as suas.

A correção para isso é adicionar a palavra-chave "preguiçosa" (ou a opção de linha de comando - preguiçosa) à sua configuração do uwsgi. Como resultado, o aplicativo será bifurcado novamente para cada filho, em vez de bifurcar-se dos pais e compartilhar a conexão (e pisar nela em algum momento, de modo que o servidor MySQL o force a fechar devido a uma solicitação corrompida em algum momento).

Por fim, se você quiser uma maneira de fazer isso sem modificar sua configuração do uwsgi, provavelmente poderá usar o decorador @postfork para criar corretamente uma nova conexão com o banco de dados imediatamente após a bifurcação de um processo de trabalho. Você pode ler sobre isso aqui .

Vejo pelo seu acompanhamento que você já mudou para o pgsql, mas aqui está a resposta para que você possa dormir melhor à noite e para alguém como você e eu tentando encontrar a resposta para isso!

PS: Depois de entender o problema (o cursor está sendo corrompido devido aos trabalhadores pisarem um no outro), mas não percebi o pouco sobre fork () e --lazy, estava pensando em implementar meu próprio pool onde os trabalhadores " verifique "uma conexão mysql de um pool no escopo global e, em seguida," faça check-in novamente antes de sair do aplicativo (), no entanto, provavelmente é muito melhor usar --lazy, a menos que a carga da sua aplicação / web varie o suficiente para que você esteja constantemente criando novos trabalhadores. Mesmo assim, eu poderia preferir --lazy porque é significativamente mais limpo do que implementar seu próprio pool de conexões db.

edit: aqui está uma descrição mais detalhada desse problema + solução, pois há uma escassez de informações sobre ele para outras pessoas que o encontraram: http://tns.u13.net/?p=190

MoscasLikeABrick
fonte
É definitivamente bom saber o que estava causando isso. Obrigado!
Theron Luhn
Apenas lançando por aí que este post era o mesmo problema exato que eu estava tendo também e sua solução o corrigiu :) Obrigado!
precisa saber é o seguinte
"Portanto, vários processos (como os trabalhadores do uwsgi) usando a mesma conexão / cursor do mysql simultaneamente o corrompem." Isso foi muito instrutivo. Eu tinha duas conexões abertas no mesmo banco de dados no meu local (uma de um shell, outra do meu aplicativo wsgi) e obtive esse erro. O banco de dados estava se reportando vivo a pinge para outros mysqladminpedidos. Provavelmente foi porque eu estava tentando soltar o banco de dados do shell ... mas continuava dando o erro "o servidor foi embora" a esse comando. De qualquer forma, obrigado!
Brian Peterson
você salvou minha vida.
sanguessuga