読者です 読者をやめる 読者になる 読者になる

oranie's blog

旧:iをgに変えると・・・なんだっけ・・・

【Perl】Parallel::ForkManagerモジュールで並列処理が便利過ぎて生きているのが辛い

とあるDBを利用したバッチ処理をやらせようとした時に

#スクリプトの一部分

my @list1 = (適当なリストその1);
my @list2 = (適当なリストその2);

foreach my $value1 (@list1){
    foreach my $value2(@list2){
            #DBに接続して、あるテーブルのレコード件数countする→
            #その結果を別テーブルにUPDATEするSQL実行処理;
    }
}

というまあ、foreachでグルグルグルグル回すスクリプトを書いたんですね。
で、まぁこれがクソ重かったんですね。テーブルのデータは全部メモリ上に載っているんですが、
10GB(1億レコードぐらい)ぐらいのテーブルで順番に$value1,$value2の変数を条件に使ってCOUNT()を使用した
SELECT文を投げるので、一つのSQL投げたらDB側のCPUコアが100%になってしまって処理完了までに数十秒掛かると。


で、ループの回数が全部で10回とかなら良いんですが、5000回は回るんですよね。
そうすると、一発動かしてから完了させるのに4〜5時間とか掛かってしまう。


で、同じような事をさらに今後は数種類やりたかったんですよ。でも、この処理時間見ると
「いまどき無いわー。やっぱHadoop使えないと生き残れないの?俺死ぬの?」ってなって凹むわけですよ。


ただ、DBサーバの状況を見るとそこそこ良いサーバなので論理コア16コアあるんですが、残りの15コアは使用率低いんですよ。
ん?(・ω・)という事は
・一つのSQLはクソ重い。(一発で数十秒掛かる。ただ、ちゃんとしたテーブルのチューニング出来たら早いかもw)
・でも同時に並列実行出来ればその分処理時間短縮されるから、少しは現実的になるよね。
・それぞれのSELECT→UPDATEは範囲がかぶらないので同時実行しても問題無し。
という条件なので残りのコアも有効利用したら、データはメモリに載っかっているんだから
DiskI/O発生しないので、同時実行分単純に早くなるんじゃね?
という事で、SQL処理部分を同時実行するように書き換えてみようとしました。


で、ググって見た所普通にfork()やろうとするとなんか良く解らんのですよ、ぼくバカだから(^p^)
ここらへんとか見ても、同時にforkさせる数をサーバによって変えたかったんだけど、使い方良く解らん。

fork プロセスを分岐する
http://d.hatena.ne.jp/perlcodesample/20090413/1240326405


「こんな基本的な事も良く分からないの?バカなの?死ぬの?」ってまた勝手に凹むわけですね。
で、数分見て面倒くさくなって、もっと楽な方法ねーかなーって調べたら
Parallel::ForkManager 使って並行ダウンローダ作った
というサンプル付きで解説頂いてますが、素晴らしく楽にfork出来るCPANモジュールがあるんじゃないですか。
で、詳しい内容はリファレンス見てもらうとして、以下の様に書き換えました。

use Parallel::ForkManager;

#並列実行をとりあえず8で
my $pm = Parallel::ForkManager->new(8);

my @list1 = (適当なリストその1);
my @list2 = (適当なリストその2);

foreach my $value (@list1){
    foreach my $value2(@list2){
        $pm->start and next;
        #DBに接続して、あるテーブルのレコード件数countする→
        #その結果を別テーブルにUPDATEするSQL実行処理;
        $pm->finish;
    }
    $pm->wait_all_children;
}


という感じでスクリプト書き換え実行して見た所、MySQLサーバの様子を見たら予想通り
・さっきまで1コアしか頑張っていなかったけど、8コアたっぷり100%に頑張っている。
・一つ一つの処理時間は前と変わらない。(多分、DiskI/O発生したら余計遅くなるとは思う)
・なので単純に全体の計算時間は1/8程度に短縮。
という現実的な処理時間に収める事が出来ました。


子沢山は素敵ですね!!