背景
数万件のバッチ処理を回したいのだけど、シーケンシャルにしか処理されず時間がかかってしょうがないってことありますよね。
自分はこの前そうでした。
手っ取り早くマルチプロセスにするためのバッチを書いたら思いのほか使い勝手が良かったのでこちらにメモって置くことにします。
環境
Centos7
Perl5.x
やりたいこと
- 数万件のデータ処理
- 1件の処理自体はコマンド一発
- 入力データは1ファイルに纏めて、各プロセスが参照されるようにしたい
- 非エンジニアでも簡単にプロセス数を調整したい
- 個人的な趣味によりPerlで実装したけどなんでも良い
アプローチ
bash
でマルチプロセス処理をさせる簡単な方法は&
を使ってバックグラウンド処理させたり、xargs -P
を使う方法があるけど、今回は入力データは1ファイルにまとめておきたく、ロック処理が必要なため手組みで実装することにした。
処理としてはシンプルで
1. ロックフォルダを作成する
2. インプットファイルからデータを読み込む
3. 読み込んだデータをインプットファイルから削除する
4. ロックフォルダを削除する
5. 読み込んだデータを処理する
という流れ
で、作った雛形がこれ
Perlで書いてるけどなんでも良いと思う。
process.pl
#!/usr/bin/perl
use strict;
use Time::HiRes qw/sleep/;
use feature 'say';
my $lock="lock";
my $input="input.txt";
my $n=10;
my $waitTime=0.2;
while(1){
# create lock folder
if( system("mkdir $lock &> /dev/null") ){
say "wait...";
sleep $waitTime;
next;
}
# read the first $n rows.
my @list= split("\n", `head -n $n $input`);
# remove the read line
`sed -i -e '1,${n}d' $input`;
# unlock
`rmdir $lock`;
# program exit if the read rows is empty
if( int @list == 0 ){ last; }
# process the read rows
foreach my $line (@list){
unless( $line =~ /\S/ ){ next; }
chomp $line;
$line =~ s/(^")|("$)//g;
my @cmd_parts = ();
push( @cmd_parts, 'curl -s' );
push( @cmd_parts, '-X DELETE' );
push( @cmd_parts, "'http://xxx.xxx.xxx.xxx/$line'" );
my $cmd = join( " ", @cmd_parts);
my $result = `$cmd`;
say $cmd;
}
sleep 5;
}
使い方
input.txt
に処理するデータを改行区切りで格納しておく。
あとは perl process.pl >> app1.log &
って感じでバックグラウンド実行する。多重度を上げたかったらログ出力先をapp2.log
、app3.log
と変えて同じコマンドを実行していく。
ログ出力先が同じでも動作するけど、複数のプロセスが1つのファイルとIOするとIO競合が発生してログの欠損などがでるので別にするのが安全。