redisでキューイング

redisにはリスト型というものがあってkeyごとにリストを格納できる。left, rightで方向を表し、leftが先頭、rightが末尾。これを利用してredisでキューイングを実装する。

use strict;
use warnings;
use Redis::Fast;
use 5.10.0;
use Data::Dumper;

sub p { warn Dumper \@_ }

my $redis = Redis::Fast->new;
$redis->flushall;

my $key = "keyhoge";

{ # enqueue
    for my $i (1..5) {
        my $value = "data_$i";
        $redis->rpush($key, $value);
    }

    # 結果確認
    p "enqueue done. data", $redis->lrange($key, 0, -1);
}

{ # dequeue
    my $get_row_per = 2;

    my $loop = 0;
    while (1) {
        say "--------------- loop $loop";

        my @result;
        $redis->multi;

        eval {
            my $part = $redis->lrange($key, 0, ($get_row_per - 1));
            $redis->ltrim($key, $get_row_per, -1);

            if ($loop == 1) {
                die "some error";
            }
        };

        if ($@) {
            say "error. '$@' rollback";
            $redis->discard;
        } else {
            @result = $redis->exec;
            my $part_result = $result[0];
            if ($part_result and @$part_result) {
                p $part_result;
            } else {
                say "end";
                last;
            }
        }
        $loop++;
    }
}
% perl queue.pl
$VAR1 = [
          'enqueue done. data',
          'data_1',
          'data_2',
          'data_3',
          'data_4',
          'data_5'
        ];
--------------- loop 0
$VAR1 = [
          [
            'data_1',
            'data_2'
          ]
        ];
--------------- loop 1
error. 'some error at queue.pl line 39.
' rollback
--------------- loop 2
$VAR1 = [
          [
            'data_3',
            'data_4'
          ]
        ];
--------------- loop 3
$VAR1 = [
          [
            'data_5'
          ]
        ];
--------------- loop 4
end
  • rightからpushし、leftから取り出すことでFIFOにする
  • 逆は不可。pushにはlpush, rpushはあるが、rangeはlrangeしかなく、leftからしか取り出せない
  • データを取り出すコマンドにはpopがあるが、これもrpopしかなくrightからしか取り出せない。1個ずつ取り出すのはパフォーマンスが悪いため、複数取り出す
  • それにはrangeでデータを先頭から参照し、rangeは参照するだけでデータは変更しないため、取り出したのちにtrimでリストを切り詰める
  • rangeとtrimの間にエラーが起きたら(redis自体が落ちた、ネットワーク的に到達できなかったなど)整合性が取れなくなるため、トランザクションを貼る
  • トランザクションはexecでコミットする。execはトランザクション中に発行したコマンドの結果を配列で返す
  • redisはmysql等のDBMSより圧倒的に早いので大規模なキューイングを処理するときはTheSchwartzより効率がいい(信頼性などを考慮しケースバイケース)