DB:「Server has gone away」エラー解決策について

このエラーが出る原因は、サーバーがタイムアウトして接続が閉じられたことです。 そして、間違ったクエリーまたは長すぎるクエリーをサーバーに送信した場合にも、これらのエラーを受け取ることがあります。

1.サーバータイムアウト

サーバがタイムアウトしたから、接続がクローズします。 デフォルトでは、何も起きない状態が 8 時間続くと、サーバは自動的に接続をクローズします。 この時間は、mysqld 開始時に wait_timeout 変数を設定することで変更できます。

対策:

1) タイムアウトの設定を変更する (以下のクエリは8時間のタイムアウトです)
set global wait_timeout = 28800;
wait_timeoutを設定するのは、MySQLは1つの接続で1つのスレッドを占有するので多くの接続があることでそれだけリソースが消費されてしまうことです。また競合をさけるため多くのlockが必要になり性能劣化の可能性もあります。また、idleが貯まることでMySQLに設定した同時接続数(max_connections)に達し、新しい接続ができないことがでてくると思います。そこを考えると、wait_timeoutはどうやって決めるのはまだわからないです。

2.接続をcloseしている

MySQLとのコネクション上で close を発行し、クローズしたコネクションでクエリを実行しようとしたことが考えられます。

対策:

1) 全ての処理が終わるまでcloseしないようにする 2) クローズした後に再コネクトする 3) {mysql_auto_reconnect=>1} を指定する 4) Scope::Containerを使う
my $dbh = DBI->connect( $dsn, $user, 
  $pass, {mysql_auto_reconnect=>1}) 
    or croak 'connection failed:';=
Model::Baseのやり方と同じく、データベースへの接続を細かく管理するため、Scope::Containerに接続情報をキャッシュして、同じDSNやユーザ名で接続の場合、キャッシュからdbhを返します。そして、任意の範囲(スコープ)だけで接続維持を行い、スコープから外れたら自動で接続を切ることになります。 in_scope_container()Scope::Conitanerが有効になっているかどうか確認し、scope_container()dbhを取得します。
 use Scope::Container;

  sub getdb {
      if ( my $dbh = scope_container('db') ) {
          return $dbh;
      } else {
          my $dbh = DBI->connect(...);
          scope_container('db', $dbh)
          return $dbh;
      }
  }

  for (1..2) {
    my $contaier = start_scope_container();
    getdb(); # do connect
    getdb(); # from container
    # $container scope out and disconnect from db
  }

  getdb(); # do connect

3.クエリが大きすぎる

大きすぎるクエリをサーバに送信した場合も、このエラーが発生する可能性があります。 大きなクエリが必要な場合(たとえば、大きな BLOB カラムを使用している場合)、mysqlのmax_allowed_packetの設定を変更して、クエリ制限を引き上げることができます。max_allowed_packetはデフォルト1MBですが、サーバーおよびクライアント間で転送可能なパケットの最大サイズは1GBです。そして、拡張メモリは要求に応じて割り当てられます。そのため、mysql はユーザが大きなクエリを発行するときやmysqlが大きな結果レコードを返す必要のあるときだけメモリを増やして割り当てます。 1GB以内の数値に変更しればいいんですが、max_allowed_packetのデフォルト値が小さいのはクライアントとサーバーの間の不正なパケットを捕捉するための予防措置であり、誤って大きなパケットが使用されてメモリー不足にならないようにするためでもあります。
それで、ここの記事(what is a reasonable value for maxallowedpacket for Drupal 7 with moderate traffic?)はこう書いていますが、データベースで最大なBLOBカラムの11倍はmax_allowed_packetの値となるってどうやって理解するのはわからないです。

対策:

1) maxallowedpacket の設定を変更する (以下のクエリは500MBです)
set global max_allowed_packet = 52428800;

bulk_insertの件数について

できればinsertのループを減たいという目的は変わらないです。何本のinsertは何回Databaseにqueryを短時間で上げることで、特にinsertは何回テプールをロックすることもあります。それで、大量のinsertはDatabaseに大きな負担をかけます。それ以外には、短時間で大量のqueryを上げるのはnetwork trafficになることも可能です。そういう考えると、maxallowedpacketを最大化にしたほうがいいですが、また問題が出ました。
     
      件数が増えると、bulk insertの効果が減っています。
     それで、時間を改善するため、並列処理にします。 またデータベースへのコネクションについて、Forkを使う時にはForkした子供が親のコネクションをそのまま使ってバグになることがあります。 恐らく原因は、DBIはDESTROY時に自動的にdisconnectするようになっているので子供が勝手に親の接続を切ってしまい、その後親がその接続を使おうとすると使えないということになります。なので、ここは作成の部分をたたいてqueryを集約して返したいです。 つまり、queryの作成を並列処理しつつ、結果をまとめて返してから、DBIのINSERTを実行します。やはり時間かかるのはquery作成の部分です。 ちなみに、Bulk Insertについて、前に言ったbulk_insert_buffer_sizeという変数に関係があるそうですが、また調べでみると、これは MyISAMのみのCacheでした。
       Perlで並列処理を実装するとなると、方法はたくさんあると思います。
  • Fork (Parallel::ForkManager、Parallel::Prefork)
  • Coro
  • AnyEvent
  • Job Queue ....など
      まずはForkについて、これはPerl組み込みのfork関数で実現できるって便利だと思いますが、多くの子プロセスを作る時にfork()を呼びまくるのは面倒くて、Parallel::ForkManagerParallel::Preforkなどのモデルで管理します。(ここはParallel::Preforkのことはまだ見てないですが、概念としてParallel::ForkManagerは並列クライアントを作るもので、Parallel::Preforkは並列できるサーバを作るものです...具体的にはまだ研究中です) そして、Parallel::ForkManager では、run_on_finish()というメソッドで子プロセスからデータを受けることができます。 子プロセスのfinish()のタイミングでpid毎にファイルにStorable::store()して、親プロセスではStorable::retrieve()しています。ですが、run_on_finish()で受けれるデータはHashしかなさそうです?(ちなみにtmp fileは.txtとなります)ここは確定してないですが、前はcannot allocate memoryというエラーを発見しました。しかし、プログラムのコビーを作るなので、メモリをたくさん食ってしまうことは確か!!
     下記はParallel::ForkManagerの実装結果です。


スレッドの数とbulk insertの件数はまたトレードオフの問題があります。 メモリとCPUの限りがあるので、大体二分か三分くらいかかると思います。 上記の結果を考えると、每10000件のbulk insertは時間的に安定だと思います。 bulk insertの件数を10000件にします。 他のやり方はまた整理しています。

0 留言:

發佈留言