2010年2月アーカイブ

偶数月なので、WEB+DB PRESSの季節です

WEB+DB PRESSで連載させて頂いている「大規模Webサービスの裏側」はVol.55にて第六回となり、最終回です。最終回は「監視」です。

「監視」の切り口を死活監視とリソース監視にわけて紹介し、後半はNagiosの設定や、Nagosの分散構成の解説です。Nagiosの分散構成はあまり資料がないので、(必要かどうかはおいておいて)面白いんじゃないかと思います。 同じVol. 55には宮川さんによるPSGI/Plackの記事もあるので、楽しみです。


昨日のデブサミ2010でRedHatの平さんとVmwareの各務さんの仮想化インフラのセッションでもでてきましたが、ハードウェアを知ることがその上で動くさまざまなソフトウェアを構築・運用していく上でも大事です。そのハードウェアを知るためにも監視を行い、リソースの利用のされかたを知るというのが重要だと思います。監視を楽に行えるよう様々なソフトウェアがあるので、どんどん活用して行きたいものです。

最終回となりますので、1年間のご愛読(まだ出てない)ありがとうございました! 1年間連載できたことがいい経験になり、記事を書くことでより深く技術を理解できたことも非常によい体験になりました。それから、執筆に協力いただいた、吉野君、加藤幹生さん、木村さん、藤沢さん、あと校正手伝ってくれたたんぽぽおよび、運用グループの方、奥さんありがとうございました。

また、何か記事の内容について詳しく聞きたいとか、「ここはどうやっているの?」とか質問がありましたら、メールなどで頂けたらと思います。ブログ等どこかで紹介していこうと思います。

PlackやAnyEventなどなど新しめのモジュールを使いたいんだけど、既存モジュールのrpmを作っていくのが面倒な場合に、それらのモジュール群をlocal::libを使ってまとめてどこかにディレクトリに入れるバンドルパッケージRPMができないかと考えてやってみた。

んで、できたので、specファイルをさらしてみる

Summary:        bundle package of Plack+AnyEvent
Name:           perl-bundle-plack
Version:        0.3
Release:        1%{?dist}
License:        Artistic
Group:          Development/Libraries
Source:         http://search.cpan.org/CPAN/authors/id/A/AP/APEIRON/local-lib-1.004009.tar.gz
Patch10:        local-lib-1.004009_destdir.patch 
BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildRequires:       perl(YAML)
BuildRequires:       perl(Config)
BuildRequires:       perl(File::Path)
BuildRequires:       perl(FindBin)
BuildRequires:       perl(ExtUtils::MakeMaker)
BuildRequires:       perl(ExtUtils::Install)
BuildRequires:       perl(ExtUtils::CBuilder)
BuildRequires:       perl(ExtUtils::ParseXS)
BuildRequires:       perl(Module::Build) >= 0.28
BuildRequires:       perl(CPAN)
AutoProv: no

%define buildhome %{_tmppath}/%{name}-%{version}-%{release}-home-%(%{__id_u} -n)
%define bundle_prefix /usr/local/bundle-plack
%define __perl_requires /usr/lib/rpm/perl.req --ignore_deps 'perl(Plack)'

%description
bundle package of Plack+AnyEvent

%prep
%setup -n local-lib-1.004009
%patch10 -p1

%build
[ "%buildroot" != "/" ] && rm -rf %buildroot
[ "%{buildhome}" != "/" ] && rm -rf %{buildhome}
export HOME=%{buildhome}
%{__mkdir_p} %{buildroot}%{bundle_prefix}
%{__mkdir_p} %{buildhome}/.cpan/CPAN

cat <<EOF > %{buildhome}/.cpan/CPAN/MyConfig.pm
\$CPAN::Config = {
  'urllist' => [q[ftp://ftp.jaist.ac.jp/pub/CPAN/], q[ftp://ftp.riken.jp/lang/CPAN/]],
};
1;
__END__
EOF

perl -Ilib Makefile.PL  --bootstrap=%{buildroot}%{bundle_prefix}
make
make install
rm -rf %{buildroot}%{bundle_prefix}/.modulebuildrc

%install
export HOME=%{buildhome}
export ORIGINAL_PREFIX=%{bundle_prefix}
export DESTDIR=%{buildroot}
sed -i "s/'urllist' => \[\],/'urllist' => [q[ftp:\/\/ftp.jaist.ac.jp\/pub\/CPAN\/], q[ftp:\/\/ftp.nara.wide.ad.jp\/pub\/CPAN\/], q[ftp:\/\/ftp.riken.jp\/lang\/CPAN\/]],/" %{buildhome}/.cpan/CPAN/MyConfig.pm

perl -I%{buildroot}%{bundle_prefix}/lib/perl5 -Mlocal::lib=--self-contained,%{buildroot}%{bundle_prefix} -MCPAN -e '
$ENV{PERL_MM_OPT} .= " INSTALLMAN1DIR=none INSTALLMAN3DIR=none";
$ENV{PERL_MM_USE_DEFAULT} = 1;
for my $m (qw/EV IO::AIO AnyEvent AnyEvent::AIO Coro 
              HTTP::Parser::XS Plack Plack::Request Plack::Server::AnyEvent/){
    CPAN::Shell->rematein("notest", "install", $m);
}
'
#↑にいれるモジュールを書く

find %{buildroot}%{bundle_prefix} -name *.so -type f | xargs -I{} sed -i "s@%{buildroot}@@g"  {}
sed -i "s@%{buildroot}@@g" %{buildroot}%{bundle_prefix}/.modulebuildrc

find %{buildroot} -name perllocal.pod -or -name .packlist | xargs rm -f
find %{buildroot}%{bundle_prefix} -type f -print |
        sed "s@^$RPM_BUILD_ROOT@@g" |
        grep -v ^%{_mandir} |
        grep -v perllocal.pod |
        grep -v "\.packlist" > %{name}.files
if [ "$(cat %{name}.files)X" = "X" ] ; then
    echo "ERROR: EMPTY FILE LIST"
    exit -1
fi

%clean
rm -rf %{buildroot}
rm -rf %{buildhome}
%post

%preun

%postun

%files -f %{name}.files
%defattr(-, root, root, -)
%dir %{bundle_prefix}

%changelog

どこかからのコピペが混ざっていたりと、あまりきれいではないけど。このspecファイルによって、/usr/local/bundle-plack以下にlocal::libと依存モジュールがすべてインストールできる。

使用するときは、普通のlocal::libと同じようにshellだったら

eval $(perl -I/usr/local/bundle-plack/lib/perl5 -Mlocal::lib=/usr/local/bundle-plack)

とやればOK。

この方式を利用することで、サービスで用いている他のモジュールに影響することなく、新しいモジュールをインストールでき、一部の必要としているところだけに適用できるってのがいいですね。

以下はlocal::libにあてているpatch

diff -ur local-lib-1.004009.orig/lib/local/lib.pm local-lib-1.004009/lib/local/lib.pm
--- local-lib-1.004009.orig/lib/local/lib.pm    2009-11-08 10:27:23.000000000 +0900
+++ local-lib-1.004009/lib/local/lib.pm 2009-11-26 11:59:21.000000000 +0900
@@ -235,6 +235,20 @@
   File::Spec->catdir($class->install_base_perl_path($path), $Config{archname});
 }

+sub install_base_path {
+    my ($class, $path) = @_;
+    $ENV{ORIGINAL_PREFIX} ? $ENV{ORIGINAL_PREFIX} : $path;
+}
+
+sub destdir {
+    my ( $class ) = @_;
+    if ( $ENV{DESTDIR} && $ENV{DESTDIR} !~ m!/$! ) {
+        $ENV{DESTDIR} .= '/';
+    } 
+    $ENV{DESTDIR};
+}
+
+
 sub ensure_dir_structure_for {
   my ($class, $path) = @_;
   unless (-d $path) {
@@ -253,8 +267,11 @@
     warn "Attempting to create file ${modulebuildrc_path}\n";
     open MODULEBUILDRC, '>', $modulebuildrc_path
       || Carp::croak("Couldn't open ${modulebuildrc_path} for writing: $!");
-    print MODULEBUILDRC qq{install  --install_base  ${path}\n}
+    print MODULEBUILDRC qq{install  --install_base  } . $class->install_base_path(${path}) . qq{\n}
       || Carp::croak("Couldn't write line to ${modulebuildrc_path}: $!");
+    if ( my $destdir = $class->destdir ) {
+        print MODULEBUILDRC qq!         --destdir $destdir\n!;
+    }
     close MODULEBUILDRC
       || Carp::croak("Couldn't close file ${modulebuildrc_path}: $@");
   }
@@ -344,9 +361,10 @@

 sub build_environment_vars_for {
   my ($class, $path, $interpolate) = @_;
+  my $destdir = ( $class->destdir ) ? " DESTDIR=" . $class->destdir : "";
   return (
     MODULEBUILDRC => $class->modulebuildrc_path($path),
-    PERL_MM_OPT => "INSTALL_BASE=${path}",
+    PERL_MM_OPT => "INSTALL_BASE=" . $class->install_base_path(${path}) . $destdir,
     PERL5LIB => join($Config{path_sep},
                   $class->install_base_perl_path($path),
                   $class->install_base_arch_path($path),

もっとスマートな方法があればだれか教えてくださいませ(_ _)

memcachedに依存するシステムやコードを書く人は大嫌いな訳だけど、スケーラビリティを向上させてレスポンス時間の高速化には必須なmemcachedですが、最近のプロトコル変更には疑問を感じてしまう。

1.4.0では、こちらに書いた通り、いつの間にかdeleteのtimeoutがサポートされなくなった。なので、

delete key timeout noreply

というコマンドが無効になって困ることになった。それでも

delete key timeout

というコマンドは、timeoutにどんなも文字列が入っていてもエラーになることはなかった。timeoutは効かないけど。

ここから1.4.4ではさらに悪化。timeoutが0でないとエラーになるようになった。つまり

delete key 0 noreply
delete key 0

は有効なんだけど、

delete key 10

がエラーになるようになった。 この変更のコミットはこれ

実際試す。

% telnet localhost 11234
 Trying 127.0.0.1...
 Connected to localhost.
 Escape character is '^]'.
 version
 VERSION 1.4.4
 delete key 10
 CLIENT_ERROR bad command line format.  Usage: delete <key> [noreply]
 quit

プロトコルの変更は下位互換が基本中の基本だと思うんだ。また1つmemcachedが嫌いになった。

ひさびさにnginxなどいじっている。

nginxがnon-blockingで動いているので、組み込みのPerlでもblockingする処理をいれることはおすすめされていないのですが、sleepだけは機能が用意されていました。使い道がよくわからないけど、とりあえずレスポンスを遅延させるのだけやってみた。

まず、handlerとなるperlモジュール

package delay; 

use nginx; 

sub handler {
    my $r = shift; 
    my $args = $r->args;
    $args =~ m/sleep=([^&]+)/;
    my $sleep = $1 || 1;
    $r->variable("sleep", $sleep);
    if ( $sleep ne "no" ) {
        $r->sleep($sleep * 1000, \&next);
        return;
    }
    $r->send_http_header("text/html");
    $r->print("<html><head><title>title</title></head><body>sleep:$sleep</body></html>\n");
    return OK; 
}

sub next {
    my $r = shift;
    my $sleep = $r->variable("sleep");
    $r->send_http_header("text/html");
    $r->print("<html><head><title>title</title></head><body>sleep:$sleep</body></html>\n");
    return OK;
} 

1;

このコードの中の$r->sleepが遅延実行ようにするところ

$r->sleep( msec, func )

となるようです。

これを利用するnginx.conf

worker_processes  16;

events {
    worker_connections  32768;
}

http {
    include       /usr/local/nginx-server/conf/mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    tcp_nopush      on;
    keepalive_timeout  60;

    perl_modules /path/to/lib;
    perl_require  delay.pm;

    server {
        listen       80;
        server_name  localhost;
        access_log off;

        location / {
            perl  delay::handler;
        }
    }
}

ApacheBenchでベンチマークをとったところ、

 ab -c 1000 -n 100000 'http://localhost/?sleep=0.1'
 Concurrency Level:      1000
 Time taken for tests:   10.449 seconds
 Complete requests:      100000
 Failed requests:        0
 Write errors:           0
 Total transferred:      19100000 bytes
 HTML transferred:       6900000 bytes
 Requests per second:    9570.41 [#/sec] (mean)
 Time per request:       104.489 [ms] (mean)
 Time per request:       0.104 [ms] (mean, across all concurrent requests)
 Transfer rate:          1785.11 [Kbytes/sec] received

想定通り、1コネクションあたり10req/secできるみたい。

これだけだと使い道があまりわからないだけど、nginxのevent loopが組み込みperlから利用できるようになる最初の一歩だと期待すればwktkです。

あと、調べた感じ、組み込みperlを入れるオーバーヘッドは5%ぐらいだった。7万req/secが6万6千req/secぐらい