web-dev-qa-db-ja.com

エラー2006:MySQLサーバーが廃止されました

Python PyramidアプリをCentOSサーバーでuWSGIとnginxを使用して実行しています。SQLAlchemyをORMとして、MySQLdbをAPIとして、MySQLをデータベースとして使用しています。サイトにはまだ稼働しているため、トラフィックは私と会社の他の従業員だけです。データベースにデータを追加するためにデータを購入したため、最大の(最も頻繁に照会される)テーブルは最大150,000行です。

昨日、Webサイトの4つの新しいタブを連続して開いたところ、いくつかの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;

案の定、これらの10件のリクエスト内で、少なくとも1件は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をシングルワーカーに設定し、マルチスレッドを無効にして、リクエストを一度に1つずつ処理するように強制しました。案の定、問題は消えました。

問題を見つけるために、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_pa​​cketを増やすことを提案しました。それを100Mに増やしてMySQLを再起動しましたが、まったく役に立ちませんでした。

要約するには: MySQLへの同時接続により2006, 'MySQL server has gone away'およびその他の奇妙なエラー。 MySQLのエラーログには関連性はありません。

私は何時間もこれに取り組んできましたが、何も進歩していません。誰かが私を助けてくれますか?

8
Theron Luhn

私もこれに遭遇し、理由と修正を見つけました。

これが発生する理由は、アプリケーションが親にロードされた後、python uwsgiプラグイン(またはより可能性が高いすべてのuwsgiプラグイン)fork()新しいワーカーであるためです。その結果、子はすべてのリソースを継承します(db接続などのファイル記述子を含む)親から。

wsgi wiki でこれについて簡単に読むことができます:

uWSGIは、可能な限り、書き込み時にfork()コピーを乱用しようとします。デフォルトでは、アプリケーションをロードした後にフォークします。この動作を望まない場合は、-lazyオプションを使用してください。それを有効にすると、各ワーカーのfork()の後にアプリケーションをロードするようにuWSGIに指示します

そして、ご存じかもしれませんが、Pythonのmysqldb接続とカーソルは、明示的に保護しない限りスレッドセーフではありません。したがって、同じmysql接続/カーソルを同時に使用する複数のプロセス(uwsgiワーカーなど)は、それを破壊します。

私の場合( King Arthur's Gold APIの場合)、別のモジュールのスコープでリクエストごとにMySQL接続を作成したときにこれは正常に機能しましたが、永続的な接続でパフォーマンスを向上させるために、データベース接続を移動しましたカーソルを親モジュールのグローバルスコープに移動します。その結果、私のつながりはあなたのようにお互いを踏みつけていました。

これに対する修正は、uwsgi設定に "lazy"キーワード(または--lazyコマンドラインオプション)を追加することです。その結果、アプリケーションは、親からフォークして接続を共有するのではなく、子ごとに新たにフォークされます(そして、ある時点でそれを踏むと、MySQLサーバーは、ある時点での破損したリクエストにより強制的に閉じられます)。

最後に、uwsgi設定を変更せずにこれを行う方法が必要な場合は、@ postforkデコレータを使用して、ワーカープロセスがフォークされた直後に新しいデータベース接続を適切に作成できます。あなたはそれについて読むことができます ここ

あなたのフォローアップから、あなたはすでにpgsqlに切り替えたことがわかりますが、これが答えです。あなたが夜によく眠れるようにして、あなたのような人のために、私はこれに対する答えを見つけようとしています!

追伸私は問題を理解しましたが(ワーカーが互いに踏むためにカーソルが破損しています)、fork()と--lazyについて少し理解していなかったため、ワーカーが「チェックする」独自のプールを実装することを検討していましたグローバルスコープでプールからmysql接続を「アウト」し、次にapplication()を終了する直前に「チェックイン」します。ただし、Web /アプリケーションの負荷が絶えず作成されているほど変化しない限り、-lazyを使用する方がはるかに良いでしょう。新しい労働者。それでも、独自のdb接続プールを実装するよりもはるかにクリーンであるため、-lazyを選択する場合があります。

編集:これに遭遇した他の人のためにそれに関する情報が不足しているので、この問題と解決策のより完全な記述があります: http://tns.u13.net/?p=19

18
FliesLikeABrick