日向夏特殊応援部隊

俺様向けメモ

Iteration あれこれ

sharding やら partitioning とかし始めると段々とその key が特定のノートやパーティションに収まるように分類した上で処理とかしたくなる訳です。
最近、モバゲーのオープンプラットフォームのプロダクト全てで使っている、DBIx-DBHResolver にちょこちょこ新機能を入れたりしてるんですが、これに resolve_node_keys ってメソッドを最近つけたりしました。

#!/usr/bin/perl

use strict;
use warnings;
use feature qw(say);
use Data::Dump qw(dump);
use DBIx::DBHResolver;

my $resolver = DBIx::DBHResolver->new;
$resolver->config(
    +{
        clusters => +{
            TIMELINE =>
              +{ nodes => [qw/TIMELINE001 TIMELINE002/], strategy => 'Key' }
        },
        connect_info => +{
            TIMELINE001 => +{},
            TIMELINE002 => +{},
        }
    }
);

my %node_keys = $resolver->resolve_node_keys( TIMELINE => [ 1 .. 20 ] );
while ( my ( $node, $keys ) = each %node_keys ) {
    say $node;
    say dump $keys;
}

まぁこんな感じで使うんですが、

TIMELINE001
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
TIMELINE002
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

という出力となります。各ノードに分割アルゴリズムに則った形でノート名とキーの配列として返してくれます。余談ですがこの分割アルゴリズムは現在は Key, Range, List に対応してます。

このキーが今度はまた万単位であってそれらのキーを元に bulk insert するとかそんな処理が出てきたりする訳ですが、毎回のごとく

my @all_values = ( 1 .. 100000 );
my @values;
while ( ( @values = splice(@all_values, 0, 1000) ) > 0 ) {
  ### bulk insert by each @values
}

みたいな処理を書いて居た訳ですがどうにもスマートじゃないなぁと前から思ってました。

でこんな風にしてみるのはどうかなと思い立ったのがこちら。

#!/usr/bin/perl

use strict;
use warnings;
use feature qw(say);

use Array::AsHash;
use Data::Dump qw(dump);
use Data::Util qw(is_scalar_ref is_number);
use Iterator::Simple qw(iterator);
use SQL::Abstract;
use SQL::Abstract::Plugin::InsertMulti;

sub bulk_insert_iterator {
    my ( $values, $size ) = @_;
    $size ||= 100;
    iterator {
        my @next = splice( @$values, 0, $size );
        return unless ( @next > 0 );
        \@next;
    };
}

sub as_sql {
    my ( $stmt, @bind ) = @_;
    for my $v (@bind) {
        if ( is_scalar_ref $v ) {
            $stmt =~ s/\?/%s/;
        }
        elsif ( is_number $v ) {
            $stmt =~ s/\?/%d/;
        }
        else {
            $stmt =~ s/\?/'%s'/;
        }
    }
    sprintf( $stmt, @bind );
}

my $sql = SQL::Abstract->new;
my ( $stmt, @bind );
my %node_keys = (
    PEOPLE001_MASTER => [ map { $_ * 2 } ( 1 .. 25 ) ],
    PEOPLE002_MASTER => [ map { ( $_ * 2 ) - 1 } ( 1 .. 25 ) ]
);
my @cols = qw( id ref_id created_on );

my $ah = Array::AsHash->new( +{ array => [%node_keys] } );

while ( my ( $node, $keys ) = $ah->each ) {
    say $node;
    say '-' x 100;
    my $iter =
      bulk_insert_iterator( [ map { [ 1, $_, \'NOW()' ] } @$keys ], 10 );
    while ( my $values = $iter->next ) {
        ( $stmt, @bind ) = $sql->insert_multi( 'hidek', \@cols, $values );
        say as_sql( $stmt, @bind );
    }
    say '-' x 100;
}

で出力はこんな風になります。

PEOPLE001_MASTER
----------------------------------------------------------------------------------------------------
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 2, NOW() ), ( 1, 4, NOW() ), ( 1, 6, NOW() ), ( 1, 8, NOW() ), ( 1, 10, NOW() ), ( 1, 12, NOW() ), ( 1, 14, NOW() ), ( 1, 16, NOW() ), ( 1, 18, NOW() ), ( 1, 20, NOW() )
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 22, NOW() ), ( 1, 24, NOW() ), ( 1, 26, NOW() ), ( 1, 28, NOW() ), ( 1, 30, NOW() ), ( 1, 32, NOW() ), ( 1, 34, NOW() ), ( 1, 36, NOW() ), ( 1, 38, NOW() ), ( 1, 40, NOW() )
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 42, NOW() ), ( 1, 44, NOW() ), ( 1, 46, NOW() ), ( 1, 48, NOW() ), ( 1, 50, NOW() )
----------------------------------------------------------------------------------------------------
PEOPLE002_MASTER
----------------------------------------------------------------------------------------------------
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 1, NOW() ), ( 1, 3, NOW() ), ( 1, 5, NOW() ), ( 1, 7, NOW() ), ( 1, 9, NOW() ), ( 1, 11, NOW() ), ( 1, 13, NOW() ), ( 1, 15, NOW() ), ( 1, 17, NOW() ), ( 1, 19, NOW() )
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 21, NOW() ), ( 1, 23, NOW() ), ( 1, 25, NOW() ), ( 1, 27, NOW() ), ( 1, 29, NOW() ), ( 1, 31, NOW() ), ( 1, 33, NOW() ), ( 1, 35, NOW() ), ( 1, 37, NOW() ), ( 1, 39, NOW() )
INSERT INTO hidek ( id, ref_id, created_on ) VALUES ( 1, 41, NOW() ), ( 1, 43, NOW() ), ( 1, 45, NOW() ), ( 1, 47, NOW() ), ( 1, 49, NOW() )
----------------------------------------------------------------------------------------------------

まぁ最初の Array::AsHash は each でも一向に構わない訳ですが、何となく試しに使ってみました。

汎用的な Iterator を作りたいのであれば、Iterator::Simple が一番軽量で汎用的だなーという印象。