日向夏特殊応援部隊

俺様向けメモ

Note of MogileFS #10 - Inside MogileFS architecture (3)

はじめに

前回のエントリ(d:id:ZIGOROu:20061018:1161159862)の続きです。
MogileFS::NewHTTPFile辺りからです。

MogileFS::NewHTTPFile

このモジュールはIOハンドルにtieする為のモジュールですけど、
WebDAVへの書き込みをIOハンドルにエミュレートしているだけみたいですね。

先のエントリでIO::WrapTieにてMogileFS::NewHTTPFileをtieする際に、

    return IO::WrapTie::wraptie('MogileFS::NewHTTPFile',
                                mg    => $self,
                                fid   => $res->{fid},
                                path  => $main_path,
                                devid => $main_devid,
                                backup_dests => $dests,
                                class => $class,
                                key   => $key,
                                content_length => $bytes+0,
                                );

とありましたがpathの所にある$main_pathって言うのは@destsをランダムに抽出した中のひとつでしかないです。


そしてMogileFS::NewHTTPFileのTIEHANDLEを見ると、

sub TIEHANDLE {
    my MogileFS::NewHTTPFile $self = shift;
    $self = fields::new($self) unless ref $self;

    my %args = @_;
    return undef unless $self->_parse_url($args{path});

    $self->{data} = '';
    $self->{length} = 0;
    $self->{backup_dests} = $args{backup_dests} || [];
    $self->{content_length} = $args{content_length} + 0;
    $self->{pos} = 0;
    $self->{$_} = $args{$_} foreach qw(mg fid devid class key);
    $self->{bytes_out} = 0;
    $self->{data_in} = '';

    return $self;
}
*new = *TIEHANDLE;

と言う訳で冒頭の_parse_urlにpathフィールドを投げてます。
従って_parse_urlを見ると、

sub _parse_url {
    my MogileFS::NewHTTPFile $self = shift;
    my $url = shift;
    return 0 unless $url =~ m!http://(.+?)(/.+)$!;
    $self->{host} = $1;
    $self->{uri} = $2;
    $self->{path} = $url;
    return 1;
}

PATHと言うかURLからhost, uri, pathを分解してフィールドに突っ込むと言う役になってます。
何故ここの記述を見るかと言えば、最終的に必ず接続しないとデータは突っ込めない訳でして、このハンドルへのIOは_connect_sockメソッドに依存するので、その部分の接続処理をある程度見ないと見通せないって訳です。


_connect_sockメソッドを見てみましょう。

sub _connect_sock {
    my MogileFS::NewHTTPFile $self = shift;
    return 1 if $self->{sock};

    my @down_hosts;

    while (!$self->{sock} && $self->{host}) {
        # attempt to connect
        return 1 if
            $self->{sock} = $self->_sock_to_host($self->{host});

        push @down_hosts, $self->{host};
        if (my $dest = shift @{$self->{backup_dests}}) {
            # dest is [$devid,$path]
            _debug("connecting to $self->{host} (dev $self->{devid}) failed; now trying $dest->[1] (dev $dest->[0])");
            $self->_parse_url($dest->[1]) or _fail("bogus URL");
            $self->{devid} = $dest->[0];
        } else {
            $self->{host} = undef;
        }
    }

    _fail("unable to open socket to storage node (tried: @down_hosts): $!");
}

接続前はsocketは開いておらず、尚且つ適切にhostが設定されてないとまずいのでwhileループが開始します。
_sock_to_hostは実際に接続する処理です。ソケットが開けない時はundefが返ってきます。


開けない場合は@down_hostsにそのhostを突っ込んで、予備で用意していたdestsから再度同じ事を試して別のhostへのsocketを開こうとします。


てっきりこの時にレプリカも作るんだと思い込んでたんですが、そうでは無いってのがこの部分から推測出来ますねぇ。


書き込みの際には最終的にハンドルをcloseしますから、CLOSEを見てみましょう。

MogileFS::NewHTTPFile->CLOSE

sub CLOSE {
    my MogileFS::NewHTTPFile $self = shift;

    # if we're closed and we have no sock...
    unless ($self->{sock}) {
        $self->_connect_sock;
        $self->_write("PUT $self->{uri} HTTP/1.0\r\nContent-length: $self->{length}\r\n\r\n");
        $self->_write($self->{data});
    }

多分、普通はここのunlessブロックに来ないと思うんですが、
要するにハンドルへの書き込み時には接続、リクエストヘッダ(PUT)送信、コンテンツ送信ってのをやるってのが良く分かりますね。

でレスポンスのパース等を経て、

    my MogileFS $mg = $self->{mg};
    my $domain = $mg->{domain};

    my $fid   = $self->{fid};
    my $devid = $self->{devid};
    my $path  = $self->{path};

    my $key = shift || $self->{key};

    my $rv = $mg->{backend}->do_request
        ("create_close", {
            fid    => $fid,
            devid  => $devid,
            domain => $domain,
            size   => $self->{content_length} ? $self->{content_length} : $self->{length},
            key    => $key,
            path   => $path,
        });
    unless ($rv) {
        # set $@, as our callers expect $@ to contain the error message that
        # failed during a close.  since we failed in the backend, we have to
        # do this manually.
        return $err->("$mg->{backend}->{lasterr}: $mg->{backend}->{lasterrstr}");
    }

    return 1;
}
*close = *CLOSE;

最終的にcreate_closeって言うコマンドをまたサーバーに投げる訳っすね。
この時点ではstorage nodeに既にファイルを突っ込んだ後です。

MogileFS::Worker::Query->cmd_create_close

ここ、データベース処理ばっかりなんで、特にソースを追いませんが
つまるところやってるのは、当初行ったテンポラリファイル登録から本番登録って言う流れを実行してるって感じですね。


但し重要な記述があります。

# mark it as needing replicating:
$dbh->do("INSERT IGNORE INTO file_to_replicate ".
         "SET fid=?, fromdevid=?, nexttry=0", undef, $fid, $devid);

replicationはquery workerが直接指示を出すんじゃなくて、データベースに一旦格納してreplication workerが見に行くだけみたいですね。