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

Perl - 大きいログファイルを処理する

1ファイル数ギガとかあるログファイルを一行ずつ処理したいとき、いくつか fork して、worker1 は 0MB-500MBを処理、worker2 は 500MB-1000MBを処理, .. みたいにやると意外に簡単にできた。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Fcntl qw(:seek);
use File::Basename qw(basename);

sub process_line {
    my $line = shift;
    # do something
    $line;
}

my $file = shift;
if (!$file || !-f $file) {
    help();
}

my $size     = -s $file;
my $worker   = 5; # number of workers
my $per_size = int($size / $worker);
my @position = map { $per_size * $_ } 0..($worker - 1);
push @position, $size;

my %worker;
for my $i (0..($worker - 1)) {
    my $pid = fork;
    die "fork failed" unless defined $pid;
    if ($pid) {
        $worker{$pid}++;
        next;
    }

    my $out_file = join '_', "out", basename($file), $i;
    open my $fh,  "<", $file      or die "$file: $!";
    open my $out, ">", $out_file  or die "$out_file: $!";

    my $start = $position[$i];
    my $end   = $position[$i + 1];
    seek $fh, $start, SEEK_SET or die "seek failed: $i";
    scalar(<$fh>) if $i != 0; # drop first line
    while ( (tell($fh) <= $end) && (my $line = <$fh>) ) {
        my $result = process_line $line;
        print {$out} $result;
    }
    exit;
}

while (%worker) {
    if (my $pid = wait) {
        if (exists $worker{$pid}) {
            delete $worker{$pid};
        }
    }
}

sub help {
    print STDERR <<"HELP";
Usage:
    % $0 big.log
HELP
    exit 1;
}