このエラーが出る原因は、サーバーがタイムアウトして接続が閉じられたことです。 そして、間違ったクエリーまたは長すぎるクエリーをサーバーに送信した場合にも、これらのエラーを受け取ることがあります。
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の効果が減っています。
件数が増えると、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::ForkManager
やParallel::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
というエラーを発見しました。しかし、プログラムのコビーを作るなので、メモリをたくさん食ってしまうことは確か!!スレッドの数とbulk insertの件数はまたトレードオフの問題があります。 メモリとCPUの限りがあるので、大体二分か三分くらいかかると思います。 上記の結果を考えると、每10000件のbulk insertは時間的に安定だと思います。 bulk insertの件数を10000件にします。 他のやり方はまた整理しています。
0 留言:
發佈留言