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

TheSchwartz を PostgreSQL / SQLite で使うと worker プロセスが太る(のは解決済)

MySQLだと問題ないみたい。あと、job の引数に何を渡すかで変わってくるらしい……

[2010-2-16 追記]
追記時点での DBD::Pg と DBD::SQLite の最新版 (DBD::Pg-2.16.1, DBD::SQLite-1.29) では、以下に記述されているメモリリークは解消されています。記事自体は記録の意味も兼ねているので消さずに残しますが、ご注意ください。


ちなみに SQLite 用のスキーマは TheSchwartz 自身に同梱されていて t/schema-sqlite.sql に、PostgreSQL 用のはリポジトリの trunk にあります。doc/schema-postgres.sql
検証用のスクリプトは最後に載せますが、単に client が job を突っ込んで、worker が job を取り出して $job->completed() するだけのものです。
Gtop を使って、Worker プロセスのメモリ使用量を表示するようにしてみました。
環境は以下。

1. $arg = { now => time() } の場合

jobs    mysql        Pg        SQLite
0      9957376    12713984    13180928
100   14295040    15949824    34889728
1000  14295040    15949824    35254272
2000  14295040    15949824    35717120

MySQL, PostgreSQL はメモリサイズが増えないが、SQLite は徐々に増えるし妙にでかい。

2. $arg = { now => DateTime->now() } の場合

jobs    mysql        Pg        SQLite
0     12640256    12312576    13574144
100   16977920    16945152    35401728
1000  16977920    22568960    35815424
2000  16977920    29491200    36208640

MySQL は一定だが、PostgreSQL は job を処理するごとにどんどんメモリサイズが増える。
SQLite も 1. の場合と同様に、徐々に増えている。

ということで、TheSchwartz の DB に MySQL 以外を使う場合は要注意かも。
TheSchwartz の worker を安全に停止する の対処をした上で、定期的に worker を再起動しています。

[2008-7-2 追記]
http://d.hatena.ne.jp/tokuhirom/20080629/1214748832 で DBD::SQLiteメモリリークが原因だと突き止めて頂きました。ありがとうございます。
とすると DBD::Pg もリークを疑うべきなんだろうけど、1. の場合には起きてないので、単にループするだけとかでは再現しないみたい。また後で追ってみよう。

[2010-2-16 追記]
DBD::Pg を現時点での最新版 2.16.1 にしたところ、PostgreSQL でもリークはなくなりました。
手元で検証できたバージョンについては、少なくとも 2.13.1 では解消されていたようです。

client.pl

use TheSchwartz;
use strict;
use DateTime;
$|=1;
my $count = 0;
my ( $dsn, $user, $pass ) = @ARGV;
my $client = TheSchwartz->new(
    databases => [{ dsn => $dsn, user => $user, pass => $pass }]
);
while (1) {
    for ( 1 .. 20 ) {
        $client->insert( MyWorker => { now => DateTime->now(), } );
        $count++;
    }
    sleep 1;
    print "$count jobs inserted.\n" if $count % 100 == 0;
}

worker.pl

package MyWorker;
use strict;
use warnings;
use base qw( TheSchwartz::Worker );
use GTop;
use DateTime;
my $gtop  = GTop->new;
my $count = 0;
printf "%d,%d\n", $count, $gtop->proc_mem($$)->size;

sub work {
    my ($class, $job) = @_;
    $job->completed();
    $count++;
    printf "%d,%d\n", $count, $gtop->proc_mem($$)->size
        if $count % 100 == 0;
}

package main;
use TheSchwartz;
my ( $dsn, $user, $pass ) = @ARGV;
my $client = TheSchwartz->new(
    databases => [{ dsn => $dsn, user => $user, pass => $pass }]
);
$client->can_do('MyWorker');
$client->work();