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

mixi engineer blog

ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

mixi大規模障害について

memcached mixi


こんにちは。システム本部技術部たんぽぽGの森本です

先日のmixi大規模障害についてのブログです。
はじめにお断りしておきますが、弊社CTOがtwitterで公開した以上の情報はまだ得られておりません。
twitterでは書ききれなかった細部を補足してみたいと思います

現状判明しているのは以下の点です
  • memcachedに大量の接続・切断を行うとmemcachedプロセスが突然終了することがある
  • memcachedには異常時に終了するフローもあるが、同時に出力されるはずのエラーログは出ていなかった
  • coreも出力されていなかった
テスト環境にて追試を行ったところ、なんどか再現させることができましたが、確実に発生する条件は未だ不明です。 障害時の memcachedのバージョンは1.4.4, libeventのバージョンは1.3bです memcached の起動オプションは以下のとおり
./memcached -vv -p 11222 -u nobody -m 16000 -c 40720 -C -U 0 -t 4 -b 4096
クライアントマシンは 3 台 各クライアントでは 5000 個 fork、1 プロセスでは最大 50 個コネクション生成。最大に達すると 1 つコネクションを再生成し get 命令を 1 つ発行。専用クライアントライブラリでは無く、ソケットを直接操作 クライアントコードは以下
#!/usr/local/bin/activeperl

use strict;
use warnings;

use IO::Socket::INET;

my $host = shift;
my $port = shift;

foreach my $sig (keys %SIG){
    next if($sig eq '__WARN__');
    next if($sig eq '__DIE__');
    next if($sig eq 'INT');
    next if($sig eq 'TERM');
    $SIG{$sig} = sub {
        my $sig = shift;
        print "Cought a sig[$sig]\n";
    };
}

for(1 .. 50*100){
    foo();
}
sleep 1 while(1);

sub foo {
    fork && return;

    my @sock;
    for (;;) {
        if(50 < @sock){
            my $nth = int(rand(@sock));
            my $s = splice(@sock, $nth, 1);
            print $s "get foo\n";
            my $ret = <$s>;
            close($s);
            next;
        }
        my $s = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => 'tcp');
        if ($s) {
            print $s "get foo\n";
            my $ret = <$s>;
            print STDERR ".";
            push @sock, $s;
        } else {
            warn "XX [$$]Can't open port:[$!]\n";
            if (@sock) {
                my $nth = int(rand(@sock));
                my $s = splice(@sock, $nth, 1);
                print STDERR "close socket\n";
                print $s "get foo\n";
                my $ret = <$s>;
                close($s);
            } else {
                sleep 1;
            }
        }
    }
}
memcachedにいくつかdebug文を仕込んだところ、以下のことがわかりました。
  • memcached.c:main()にある event_base_loop から抜けていた 通常はここで無限ループするはず
  • event_base_loop()中の event_haveevents(base) が false を返していた
  • event_haveevents()で base->event_count が0になったため return (base->event_count > 0); で false を返していた
コネクション最大状態で、open/close を激しく繰り返すと発生しやすい印象です。

5000プロセスとかなり負荷を掛けているつもりなのですが再現度はかなり低いです
もしかすると1台のクライアントで同時に行えるopen/closeはコア数が上限になってしまうなどの制限があるのでしょうか
引き続き確実な再現方法と原因究明に向けて調査を行っていきます