前のエントリで紹介した HTTP::Entity::Parser ですが、プロファイリングとるとPOSTデータの解析で半分弱の時間を使っていたのでそこをXSにしたら良いんじゃないかということで、新しく WWW::Form::UrlEncoded ってのを書いて出しました。

https://metacpan.org/release/WWW-Form-UrlEncoded
https://github.com/kazeburo/WWW-Form-UrlEncoded

似たモジュールに URL::EncodeText::QueryString がありますが、HTTP::Bodyのパーサと互換性あるこの仕様を満たしていないので、新しく書き起こしてみました。URL::Encode(::XS) がかなり速いのですが、それに近い速度がでています。

              Rate wwwform_pp    text_qs    wwwform  urlencode
wwwform_pp 11933/s         --       -77%       -87%       -88%
text_qs    52609/s       341%         --       -43%       -46%
wwwform    91995/s       671%        75%         --        -5%
urlencode  97303/s       715%        85%         6%         --

そして、これをHTTP::Entitiy::Parserに組み込んで0.03としてリリースしました

https://metacpan.org/release/HTTP-Entity-Parser
https://github.com/kazeburo/HTTP-Entity-Parser/

ベンチマークはPOSTデータのサイズを変えながら3つとってみました

## content length => 38

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 36201.85/s (n=39098)
http_entity:  1 wallclock secs ( 1.12 usr +  0.00 sys =  1.12 CPU) @ 76799.11/s (n=86015)
               Rate   http_body http_entity
http_body   36202/s          --        -53%
http_entity 76799/s        112%          --

## content length => 177

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 14901.80/s (n=16541)
http_entity:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 64474.07/s (n=69632)
               Rate   http_body http_entity
http_body   14902/s          --        -77%
http_entity 64474/s        333%          --

## content length => 1997

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.16 usr +  0.00 sys =  1.16 CPU) @ 1930.17/s (n=2239)
http_entity:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 29519.82/s (n=32767)
               Rate   http_body http_entity
http_body    1930/s          --        -93%
http_entity 29520/s       1429%          --

データが大きくなればなるほど差が大きくなります。

HTTP::Bodyと互換性のある HTTPのEntityをパースするモジュールをリリースしました。

https://metacpan.org/release/HTTP-Entity-Parser
https://github.com/kazeburo/HTTP-Entity-Parser/

HTTPのEntityってのは、こういう範囲を指します。

POST /foo HTTP/1.1          # Not part of the entity.
Content-Type: text/plain    # ┬ The entity is from this line down...
Content-Length: 1234        # │
                            # │
Hello, World! ...           # ┘

元ネタは「java - What exactly is an HTTP Entity? - Stack Overflow

HTTP::Entity::Parserの使い方はこんな感じ。

use HTTP::Entity::Parser;

my $parser = HTTP::Entity::Parser->new;
$parser->register('application/x-www-form-urlencoded','HTTP::Entity::Parser::UrlEncoded');
$parser->register('multipart/form-data','HTTP::Entity::Parser::MultiPart');
$parser->register('application/json','HTTP::Entity::Parser::JSON');

sub app {
    my $env = shift;
    my ( $params, $uploads) = $parser->parse($env);
}

このモジュール、元々は tokuhirom の Plackへのpullreqにあるものですが、「だれか別モジュールにして」とのことでしたので、テスト書いてHTTP::Bodyとの互換性を検証して、リリースしました。

このモジュールの開発動機はこのスライドにありますね。ただ、スライド中にでてくるURL::Encodeは互換性の問題で使ってません。

公平かどうか微妙かもですが、一応ベンチマーク。

#!/usr/bin/perl

use strict;
use warnings;
use HTTP::Entity::Parser;
use HTTP::Body;
use Benchmark qw/:all/;

my $content = 'xxx=hogehoge&yyy=aaaaaaaaaaaaaaaaaaaaa';

my $parser = HTTP::Entity::Parser->new;
$parser->register('application/x-www-form-urlencoded','HTTP::Entity::Parser::UrlEncoded');

cmpthese(timethese(-1, {
    'http_entity' => sub {
        open my $input, '<', \$content;
        my $env = {
            'psgi.input' => $input,
            'psgix.input.buffered' => 1,
            CONTENT_LENGTH => length($content),
            CONTENT_TYPE => 'application/x-www-form-urlencoded',
        };
        $parser->parse($env);
    },
    'http_body' => sub {
        open my $input, '<', \$content;
        my $body   = HTTP::Body->new( 'application/x-www-form-urlencoded', length($content) );
        $input->read( my $buffer, 8192);
        $body->add($buffer);
        $body->param;
    }
}));

__END__
Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 36201.85/s (n=39098)
http_entity:  1 wallclock secs ( 1.10 usr +  0.01 sys =  1.11 CPU) @ 51661.26/s (n=57344)
               Rate   http_body http_entity
http_body   36202/s          --        -30%
http_entity 51661/s         43%          --

NYTProfでプロファイルをとると、application/x-www-form-urlencodedのパースがそれなりの時間を占めるので、互換性があるXSモジュールがあるともう少し速くなるんじゃないかなーと思ってる。

以前、「Q4Mを簡単に導入する方法 - MySQL Casual Advent Calendar 2011」で紹介したQ4M専用MySQLのセットアップスクリプトのMySQL5.6対応版を作りました。

kamipo先生によると、プラグインをあとからビルドしてMySQLに追加する事は推奨されていないとのことなので、Q4MのソースコードをMySQLのソースコードツリーにコピーしてから一緒にビルドします。これは同じくkamipo先生のmysql-buildを参考にさせて頂きました。

#!/bin/sh
set -e

MYVER=5.6.15
Q4MVER=0.9.11

CDIR=$(cd $(dirname $0) && pwd)
cd /usr/local/src
if [ -f $CDIR/mysql-$MYVER.tar.gz ]; then
    cp $CDIR/mysql-$MYVER.tar.gz ./
fi
if [ ! -f mysql-$MYVER.tar.gz ]; then
    wget http://ftp.jaist.ac.jp/pub/mysql/Downloads/MySQL-5.6/mysql-$MYVER.tar.gz
fi
tar zxf mysql-$MYVER.tar.gz

if [ -d q4m-$Q4MVER ]; then
    rm -rf q4m-$Q4MVER
fi
if [ ! -f q4m-$Q4MVER.tar.gz ]; then
    wget http://q4m.kazuhooku.com/dist/q4m-$Q4MVER.tar.gz
fi
tar zxf q4m-$Q4MVER.tar.gz
mv q4m-$Q4MVER mysql-$MYVER/storage/q4m
if [ ! -f mysql-$MYVER/storage/q4m/CMakeLists.txt ]; then
  curl -kL https://raw.github.com/q4m/q4m/$Q4MVER/CMakeLists.txt > mysql-$MYVER/storage/q4m/CMakeLists.txt
fi

yum install -y cmake ncurses-devel libaio-devel
cd mysql-$MYVER
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/q4m \
  -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci \
  -DWITH_EXTRA_CHARSETS=all \
  -DWITH_ZLIB=bundled -DWITH_SSL=bundled -DWITH_READLINE=1 \
  -DWITH_PIC=ON -DWITH_FAST_MUTEXES=ON \
  -DWITH_DEBUG=OFF \
  -DCOMPILATION_COMMENT="Q4M" -DMYSQL_SERVER_SUFFIX="-q4m" \
  -DMYSQL_USER=nobody -DMYSQL_UNIX_ADDR="/tmp/mysql_q4m.sock" \
  -DMYSQL_TCP_PORT=13306 \
  -DWITH_DEFAULT_FEATURE_SET=xsmall \
  -DWITH_PARTITION_STORAGE_ENGINE=1 \
  -DWITHOUT_DAEMON_EXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_FTEXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_ARCHIVE_STORAGE_ENGINE=1 \
  -DWITHOUT_BLACKHOLE_STORAGE_ENGINE=1 \
  -DWITHOUT_FEDERATED_STORAGE_ENGINE=1 \
  -DWITHOUT_INNOBASE_STORAGE_ENGINE=1 \
  -DWITHOUT_PERFSCHEMA_STORAGE_ENGINE=1 \
  -DWITHOUT_NDBCLUSTER_STORAGE_ENGINE=1 \
  -DWITH_INNODB_MEMCACHED=OFF \
  -DWITH_EMBEDDED_SERVER=OFF \
  -DWITH_UNIT_TESTS=OFF
make
make install

mkdir -p /usr/local/q4m/etc
cp $CDIR/my.cnf /usr/local/q4m/etc
cp $CDIR/q4m.init /etc/init.d/q4m
chmod 755 /etc/init.d/q4m
chkconfig --add q4m

/usr/local/q4m/scripts/mysql_install_db --skip-name-resolve \
  --basedir=/usr/local/q4m --defaults-file=/usr/local/q4m/etc/my.cnf
rm -f /usr/local/q4m/my.cnf
chmod 755 /usr/local/q4m/var
/etc/init.d/q4m start

cat storage/q4m/support-files/install.sql | /usr/local/q4m/bin/mysql -S /tmp/mysql_q4m.sock
echo "show plugins" | | /usr/local/q4m/bin/mysql -S /tmp/mysql_q4m.sock

my.cnfやinitスクリプトなどソースはすべてgithubで公開中。使う時は

$ git clone git://github.com/kazeburo/mysetup.git
$ cd mysetup/q4m_mysql56
$ sudo sh ./setup.sh

とするだけで動くと思われます。CentOS6で検証しています。

これをと使うと、 /usr/local/q4m 以下にMySQLとQ4Mを入れて、ポート13306にてmysqldが起動します。データも /usr/local/q4m 以下に入ります。Q4M専用MySQLという位置付けなのでInnoDBが使えないのが注意点。

initスクリプトもインストールするので

$ sudo service q4m start|stop|restart

とサービスの制御ができます。

これで5.1系とおさらばできますね〜

strftime(3)でいうところの「%z」にあたる、「+0900」のようなタイムゾーンのGMT/UTCへのオフセット文字列を出力するモジュールをリリースしました。XSです

https://metacpan.org/release/Time-TZOffset
https://github.com/kazeburo/Time-TZOffset

機能は1つ

my @localtime = localtime;
say tzoffset(@localtime); => +0900

Windowsでも動作します。

use Benchmark qw/:all/;
use POSIX qw//;
use Time::Local;
use Time::TZOffset;

cmpthese(timethese(-1, {
    'posix' => sub {
        POSIX::strftime('%z', @lt);
    },
    'time_local' => sub {
        my $sec = Time::Local::timegm(@lt) - Time::Local::timelocal(@lt);
        sprintf '%+03d%02u', $sec/60/60, $min/60%60;
    },
    'tzoffset' => sub {
        Time::TZOffset::tzoffset(@lt);
    },
}));
__END__
Benchmark: running posix, time_local, tzoffset for at least 1 CPU seconds...
     posix:  1 wallclock secs ( 0.66 usr +  0.39 sys =  1.05 CPU) @ 179442.86/s (n=188415)
time_local:  1 wallclock secs ( 1.12 usr +  0.16 sys =  1.28 CPU) @ 25846.09/s (n=33083)
  tzoffset:  1 wallclock secs ( 0.75 usr +  0.25 sys =  1.00 CPU) @ 286720.00/s (n=286720)
               Rate time_local      posix   tzoffset
time_local  25846/s         --       -86%       -91%
posix      179443/s       594%         --       -37%
tzoffset   286720/s      1009%        60%         --

POSIX::strftime('%z')より速く動作します。

このUTCへのオフセットはLinuxやBSD系のOSでは、t\m構造体に含まれており、mktime(3)を使うと簡単に得られます。

struct tm mytm;

memset(&mytm,0,sizeof(mytm));
mytm.tm_min = min;
mytm.tm_hour = hour;
mytm.tm_mday = mday;
mytm.tm_mon = mon;
mytm.tm_year = year;
mytm.tm_isdst = -1;
mktime(&mytm);

mytm.tm_gmtoff;

XS不慣れなのでツッコミ等お待ちしています。

1週間ぐらいtrialでしたが、Windowsでも動いたようなので0.10を出しました。すごーーーく、、、、ニッチです

https://metacpan.org/release/POSIX-strftime-Compiler
https://github.com/kazeburo/POSIX-strftime-Compiler

POSIX::strftime::CompilerはGNU互換で、ロケールの設定に影響を受けないstrftime(3)を提供します。WindowsでもGNU互換の文字が使えます。おそらくloggerとかloggerとかloggerに便利です

$ LC_ALL=ja_JP.UTF-8 perl -Ilib -MPOSIX::strftime::Compiler -E '
say POSIX::strftime::Compiler::strftime(q!%d/%b/%Y:%T %z!,localtime);
say POSIX::strftime(q!%d/%b/%Y:%T %z!,localtime)
'
21/Jan/2014:10:48:12 +0900
21/ 1月/2014:10:48:12 +0900

POSIX::strftime::CompilerのstrftimeはPOSIX::strftimeをwrapし、ロケールに影響をうけるフォーマット文字の部分を先に入れ替えています。

Apache::LogFormat::Compilerではstrftimeの前後にsetlocale(3)をしていますが、POSIX::strftime::Compilerを使えばそのコストを減らす事が出来ます。

use Benchmark qw/:all/;
use POSIX qw//;
use POSIX::strftime::Compiler;

my $fmt = '%d/%b/%Y:%T';
cmpthese(timethese(-1, {
    'compiler_function' => sub {
        POSIX::strftime::Compiler::strftime($fmt, localtime($t));
    },
    'posix_and_locale' => sub {
        my $old_locale = POSIX::setlocale(&POSIX::LC_ALL);
        POSIX::setlocale(&POSIX::LC_ALL, 'C');
        POSIX::strftime($fmt,localtime($t));
        POSIX::setlocale(&POSIX::LC_ALL, $old_locale);
    },
   'posix' => sub {
       POSIX::strftime($fmt,localtime($t));
   },
}));

ベンチマーク結果

Benchmark: running compiler_function, posix, posix_and_locale for at least 1 CPU seconds...
compiler_function:  1 wallclock secs ( 1.10 usr +  0.00 sys =  1.10 CPU) @ 446836.36/s (n=491520)
     posix:  1 wallclock secs ( 1.01 usr +  0.00 sys =  1.01 CPU) @ 243326.73/s (n=245760)
posix_and_locale:  1 wallclock secs ( 1.10 usr +  0.00 sys =  1.10 CPU) @ 71087.27/s (n=78196)
                      Rate  posix_and_locale             posix compiler_function
posix_and_locale   71087/s                --              -71%              -84%
posix             243327/s              242%                --              -46%
compiler_function 446836/s              529%               84%                --

setlocaleする場合より6倍程度良い数値がでています。また、POSIX::strftimeの倍程度の速度がでています。これは全てのフォーマット文字が複雑な計算などをする必要が無い文字の場合にsprintfだけで結果を出してしまう為です。

Apache::LogFormat::Compilerで使おうかなと考えて作ったのですが、'%z'の計算が速くならないのであまり嬉しい事がなかったという今現在なう

わりと長い間悩んでいたんだけど、最近解決したのでメモ。

サービスで利用しているsmalllightの画像変換サーバが、Apacheが使っているメモリ以上のメモリを使用し、Swapしたりメモリ枯渇でサーバがダウンするなどのことが何度かありました。

dentry_cache1.png

↑メモリの動きはこんな感じ

いろいろ調べた結果「dentry cache」なるものがメモリ多くを占めていることがわかりました。dentry cacheはディレクトリやファイル名とinodeとを結びつけに使われるキャッシュです。smalllightでは画像を変換する際に一時ファイルを作成するので、その情報が残るようです。

手元で再現させる

本番で使っているサーバはCentOS5系ですが、手元のVagrant上のCentOS6(ファイルシステムはext4)で、再現させてみました。

use Parallel::Prefork;
use File::Temp qw/tempfile/;

my $pm = Parallel::Prefork->new({
  max_workers  => 50,
  trap_signals => {
    TERM => 'TERM',
    HUP  => 'TERM',
    USR1 => undef,
  }
}); 
while ($pm->signal_received ne 'TERM') {
  $pm->start(sub {
      srand();
      select undef, undef, undef, rand(1);
      while (1) {
          select undef, undef, undef, 0.01;
          my ($fh,$fn) = tempfile(UNLINK=>0);
          syswrite $fh, 'x'x128;
          close $fh;
          unlink $fn;
      }
  });
}
$pm->wait_all_children();

まず、上記のscriptを作成し、100並列で一時ファイルを作りまくります。そしてdstatでメモリ使用量を観察します。

[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
  epoch   |usr sys idl wai hiq siq| used  buff  cach  free| used  free
1389191041| 44  38  18   1   0   0|48.5M 2424k 10.1M  430M|   0   992M
1389191071| 38  49  13   0   0   0| 132M 3956k 12.5M  342M|   0   992M
1389191101| 46  54   0   0   0   0| 167M 4004k 12.5M  307M|   0   992M
1389191131| 48  52   0   0   0   0| 200M 4052k 12.5M  274M|   0   992M
1389191161| 44  56   0   0   0   0| 236M 4108k 12.5M  238M|   0   992M
1389191191| 45  54   0   0   0   0| 270M 4156k 12.5M  204M|   0   992M
1389191221| 48  52   0   0   0   0| 303M 4204k 12.5M  170M|   0   992M
1389191251| 49  50   0   0   0   0| 337M 4252k 12.5M  137M|   0   992M
1389191281| 49  50   0   0   0   0| 369M 4428k 12.5M  105M|   0   992M
1389191311| 47  53   0   0   0   0| 402M 4476k 12.5M 71.9M|   0   992M
1389191341| 49  51   0   0   0   0| 435M 4524k 12.5M 38.9M|   0   992M
1389191371| 52  48   0   0   0   0| 467M 4572k 12.5M 6348k|   0   992M
1389191401| 44  56   0   0   0   0| 389M 4356k 11.8M 85.4M|   0   992M <- 一回解放されてる?
1389191431| 42  58   0   0   0   0| 425M 4820k 12.6M 48.2M|   0   992M
1389191461| 45  55   0   0   0   0| 460M 4868k 12.6M 12.7M|   0   992M
1389191491| 43  57   0   0   0   0| 467M 4916k 12.6M 6104k|   0   992M
1389191521| 46  54   0   0   0   0| 467M 4964k 12.6M 6600k|   0   992M
1389191551| 44  56   0   0   0   0| 463M 5012k 12.6M 9700k|   0   992M
1389191553| 44  56   0   0   0   0| 466M 5020k 12.6M 7344k|   0   992M

30秒ごとに30MB強、メモリ使用量が増えて行き、物理メモリいっぱいいっぱいまで使います。その後はキャッシュを順次解放するのかメモリ使用量に大きな変化がなくなります。

dentry cacheがどの程度あるのかは、slabtopコマンドを使うと確認できます。

[vagrant@localhost ~]$ slabtop -o
 Active / Total Objects (% used)    : 1943159 / 1945730 (99.9%)
 Active / Total Slabs (% used)      : 97116 / 97129 (100.0%)
 Active / Total Caches (% used)     : 90 / 183 (49.2%)
     Active / Total Size (% used)       : 369732.02K / 370130.38K (99.9%)
 Minimum / Average / Maximum Object : 0.02K / 0.19K / 4096.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
1875580 1875580 100%    0.19K  93779       20    375116K dentry
 15680  15600  99%    0.03K    140      112       560K size-32
  8732   7813  89%    0.06K    148       59       592K size-64
  7102   7064  99%    0.07K    134       53       536K selinux_inode_security
  5859   5833  99%    0.14K    217       27       868K sysfs_dir_cache
  5776   5757  99%    0.20K    304       19      1216K vm_area_struct
  5390   5349  99%    0.05K     70       77       280K anon_vma_chain
  4212   4161  98%    0.58K    702        6      2808K inode_cache
  3036   2935  96%    0.04K     33       92       132K anon_vma

dentry cacheはメモリが必要になれば解放されるらしいので、その検証のため、もう一つメモリを大量に使うプロセスを起動します。

perl -e 'my $x="x"x200_000_000;sleep(3600);

これは300MBぐらいメモリを確保します。

そしてまた、dstatで観察すると、なんと、swapしてしまいました。

[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
  epoch   |usr sys idl wai hiq siq| used  buff  cach  free| used  free
1389191593| 45  46   9   0   0   0| 464M 5076k 12.6M 9196k|   0   992M
1389191623| 48  52   0   0   0   0| 467M 5124k 12.6M 6476k|   0   992M
1389191653| 55  45   0   0   0   0| 464M 5172k 12.6M 8936k|   0   992M
1389191683| 55  45   0   0   0   0| 459M 5220k 12.6M 13.6M|   0   992M
1389191713| 53  47   0   0   0   0| 437M 4824k 12.5M 35.9M|  76k  992M <- ここで起動
1389191743| 46  54   0   0   0   0| 482M  104k 2868k 6072k|  27M  965M
1389191773| 46  53   0   0   0   0| 481M  112k 2432k 6792k|  29M  963M
1389191803| 45  55   0   0   0   0| 483M  152k 1956k 5924k|  36M  956M
1389191833| 45  54   0   0   0   0| 482M  204k 2120k 6676k|  48M  944M
...
1389192371| 48  52   0   0   0   0| 475M 1972k 8184k 6156k| 141M  851M
1389192401| 48  52   0   0   0   0| 477M 1544k 6844k 5756k| 142M  850M
1389192431| 48  52   0   0   0   0| 478M  932k 6044k 5760k| 144M  848M

dentry cacheを解放するスピードが間に合わないのか、一部のメモリがswapに追い出され、さらに徐々にswapが増えて行くる結果となりました。

画像変換ではたまに大きなサイズの画像などがあり、メモリを多く必要とする場合があります。その際にswapしてしまい性能がダウンしてしまうことが予想されます。またこのままswapが増え続ければメモリ枯渇の可能性もあります。

dentry cacheの解放方法

swapやメモリ枯渇を防ぐめには、dentry cacheを早めに解放してメモリを空けておくのが良さそうです。その方法は2つあります。

一つ目は vm.drop_caches を使う方法

$ sudo /sbin/sysctl -w vm.drop_caches=2 または 3
# echo 2 > /proc/sys/vm/drop_caches

ディスクキャッシュをクリアする方法と同じです。2をいれるとdentry cacheのクリア。3をいれるとdentry cacheとディスクキャッシュをクリアします。

もう一つが、vm.vfs_cache_pressure を設定する方法

$ sudo /sbin/sysctl -w vm.vfs_cache_pressure=10000

デフォルトは100で、数字を大きくすると早めにキャッシュを解放するようです。しかし、かなり大きな数字にしてもサービスの本番環境では目立った変化はありませんでした。

tmpfsを使う方法

最後に発見したのがtmpfsを使う方法。tmpfsだと、dentry cacheは溜まらないようです。

環境変数TMPDIRにtmpfsを指定して先ほどのscriptを実行します。

[vagrant@localhost vagrant]$ TMPDIR=/dev/shm perl mktmp.pl

またdstatで観察すると、メモリが増えて行かないのが分かります。

[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
  epoch   |usr sys idl wai hiq siq| used  buff  cach  free| used  free
1389168490| 47  35  17   0   0   0|44.3M 2428k 8804k  435M|8444k  984M
1389168520| 49  37  14   0   0   0| 146M 3924k 11.1M  330M|8444k  984M
1389168550| 60  40   0   0   0   0| 146M 3932k 11.1M  330M|8444k  984M
1389168580| 55  44   0   0   0   0| 146M 3932k 11.1M  330M|8444k  984M
1389168610| 56  44   0   0   0   0| 146M 4228k 12.4M  327M|8444k  984M
1389168640| 57  43   0   0   0   0| 144M 4388k 12.4M  330M|8444k  984M
1389168642| 57  43   0   0   0   0| 144M 4388k 12.4M  330M|8444k  984M^C

これは、tmpfsがLinuxのVFSを使わないからでしょうか。

ここまで分かったので、Apacheの起動スクリプトに

export TMPDIR=/dev/shm

を追加して再起動したところ、サーバが安定して動くようになりました。

dentry_cache2.png

↑今のメモリの動き

まとめ

  • dentry cacheによりメモリが圧迫される可能性がある
  • tmpfsを使うとdentry cacheは溜まらない
  • TMPDIR環境変数で一時ファイルの場所は変更出来る

大量に一時ファイルを作成するようなアプリケーションはtmpfsを使うのが良い。そもそも速いので使わない手はないですね。

ちょっとずつ試してる。環境は以下。

  • MacOS X 10.8
  • VirtualBox 4.3.2
  • Vagrant 1.3.5
  • Packer v0.4.0

packer でCentOS6.5のイメージを作る

まず、packerを使ってCentOS 6.5のイメージを作るところから。テンプレートはgithubにあげてます。

$ git clone https://github.com/kazeburo/my_packer.git
$ cd my_packer
$ packer build centos6.5.json

テンプレートは https://github.com/hnakamur/my-packer-template-files を参考にさせて頂きました。
このあとの作業が楽になるようにCentOS 6.5をインストールしたあとに、yum groupinstall "Development Tools" し、不必要そうなサービスを止めています。

ビルドが完了すると、「packer_virtualbox_virtualbox.box」ができるので、vagrantに登録

$ vagrant box add centos6.5dev packer_virtualbox_virtualbox.box

ここでは「centos6.5dev」という名前にしています。

vagrant plugin の導入

vagrant-vbguest

VirtualBox Guest Additions を自動でアップデートしてくれるpluginを導入します。あると便利

$ vagrant plugin install vagrant-vbguest

vagrant-reload

provisionの中でゲストマシンの再起動できます。kernelのアップデートがある時には必須

$ vagrant plugin install vagrant-reload

Vagrantを使って起動する

つくったCentOSのイメージを起動する

$ mkdir learning_docker
$ cd learning_docker
$ vagrant init centos6.5dev

そしてdockerをすぐに使えるようにprovisionerを使っていくつか設定します

$ cat Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "centos6.5dev"
  config.vm.network "forwarded_port", guest: 5125, host: 15125
  config.vm.provision :shell, :inline => <<-EOT
curl -fsSkL -o /etc/yum.repos.d/hop5.repo http://www.hop5.in/yum/el6/hop5.repo
yum remove -y kernel-headers
yum install -y kernel-ml-aufs kernel-ml-aufs-devel kernel-ml-aufs-headers
yum groupinstall -y 'Development Tools'
sed -i "s/default=1/default=0/" /boot/grub/grub.conf
yum install -y docker-io
gpasswd -a vagrant docker
chkconfig docker on
service docker start
EOT
  config.vm.provision :reload
end

動作に必ず要るわけではないですが、aufs-backed docker host on centos6 | blog.hansode.org を参考にaufs対応カーネルをいれています。また、仮想マシンの中で起動したgrowthforecastにアクセスするためのport forwardの設定や、vagrantユーザでdockerを使えるようにグループの設定もしています。

これで仮想マシンを起動します。初回起動時にprovisionerが動いて、kernelを入れ替えて一度再起動します。

$ vagrant up

上がってくると vagrant-vbguest がVirtualBox Guest Additionsのアップデートを行い、正常に起動するはず。

$ vagrant ssh

これで dockerを使う準備ができました。

$ docker info
Containers: 0
Images: 0
Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 0
WARNING: No swap limit support

DockerでGrowthForecastを起動する

上の準備でvagrantユーザでdockerが使えるようになっているので、適当なディレクトリを作り、Dockerfileを作ります。

$ mkdir growthforecast
$ cd growthforeacst
$ cat Dockerfile
FROM centos
RUN yum -y groupinstall "Development Tools"
RUN yum -y install pkgconfig glib2-devel gettext libxml2-devel pango-devel cairo-devel git ipa-gothic-fonts
RUN git clone https://github.com/tagomoris/xbuild.git
RUN xbuild/perl-install 5.18.1 /opt/perl-5.18
RUN echo 'export $PATH=/opt/perl-5.18/bin:$PATH' > /etc/profile.d/xbuild-perl.sh
RUN /opt/perl-5.18/bin/cpanm -n GrowthForecast
RUN mkdir -p /var/lib/growthforecast
EXPOSE 5125
CMD ["/opt/perl-5.18/bin/growthforecast.pl","--data-dir","/var/lib/growthforecast"]

そして、docker buildでイメージを作ります

$ docker build -t user/growthforecast .

perlのビルドも行うのですごく、、時間かかります。

これができたら、コンテナの起動です。

$ docker run -p 5125:5125 kazeburo/growthforecast

5125は、vagrantでport forwardの設定がしてあるはずなので、Mac上のブラウザから、「http://localhost:1525/」 にアクセスできるはずです。

gf-docker.png

停止は、Ctrl-C です。これだとコンテナの起動のたびにデータが消えてしまうので、適当なディレクトリをマウントします。

$ mkdir -p /home/vagrant/growthforecast/data
$ docker run -p 5125:5125 -v /home/vagrant/growthforecast/data:/var/lib/growthforecast kazeburo/growthforecast

これで再起動してもデータが初期化されることが無くなりました。

まだまだやってみたレベル。。

GrowthForecastのバージョン0.80をリリースしました。

gf_0_80.png

インストールはCPANもしくはgithubから

主な変更点

変更点は以下の通りです。

  • bootstrap 3 にアップグレーとして見た目が変わった (kazeburo)
  • グラフリストの折り畳み (kazeburo, dalyfolowerさん)
  • mysqlを使った場合のdefault valueの扱い (oranieさん,sonotsさん)
  • exportへのstepとcf (統合巻数) オプションの追加 (anoworlさん)
    参考 -> http://qiita.com/anoworl/items/63a2d8d33cab077e729b
  • unix domain socket対応 (kazeburo)
  • faviconつけた (kazeburo)

みなさんありがとうございます!

ドキュメントページの英語化

あと、hkmurakamiさんに翻訳をして頂き、http://kazeburo.github.io/GrowthForecast/ のページが英語になりました。こちらもありがとうございます!

gf-site.png

ちなみに、日本語のページは右上のメニューで表示できます。

12/21 発売の WEB+DB PRESS Vol.78 のPerlリレー連載にて「PSGI/Plack実践入門」という記事を書きました。

内容はYAPC::Asiaで発表した内容 + plackconで藤原さんが発表したことが中心で、てんこ盛りとなっております。

  • PSGIの仕様の復習
  • PSGIサーバの役割
  • よく使われるPSGIサーバとその特徴的な機能の紹介(Starlet,Starman,Twiggyなど)
  • PSGIサーバの選び方
  • Plack::Middleware
  • Server::Starterを使ったホットデプロイ

PerlでWebアプリケーションを作り、動かすのであれば必ず押さえておくべき事柄をまとめてあるのでぜひ読んで頂けたらと思います。

WEB+DB PRESS Vol.78
WEB+DB PRESS Vol.78
posted with amazlet at 13.12.18
WEB+DB PRESS編集部 編 栗林 健太郎 安詮院 康広 山口 良平 尾上 忠輔 大川 高志 坂本 寛樹 青木 峰郎 増井 雄一郎 中島 聡 江島 健太郎 中島 拓 柴田 博志 伊藤 直也 登尾 徳誠 片桐 崇 後藤 秀宣 佐藤 鉄平 近藤 宇智朗 長野 雅広 奥野 幹也 渡邊 恵太 A-Listers 家永 英治 はまちや2 川添 貴生 原田 勝信 和島 史典 城倉 和孝 安達 俊雄 Akira 川嶋 賢一
技術評論社
売り上げランキング: 852

DMMの特集で冒頭にモリスさんの名前があるのがポイントです。

ここを書き直して転載

memcachedに関する記事は「第1回 memcachedの基本:memcachedを知り尽くす|gihyo.jp … 技術評論社」など何回か書いていますが、最近のmemcachedでの起動オプションのおすすめをまとめてみようと思います。なおこの記事はMemcached Advent Calendarではありません。

まとめるとこんな感じです。

$ memcached -v -p 11211 -U 0 -u memcached -m 1024 \
                       -c 100000  -t 4 -C -B ascii

ひとつずつ簡単に紹介します。

-v ログ出力

ログを verbose モードで起動します。エラーや警告が表示されます。弊社ではmemachedをdaemontools経由で起動し、ログを記録しています。

-v

-vオプションは -vv-vvv と v の数を増やす事で様々なメッセージを出力させることができますが、プロダクション環境では -v だけで十分でしょう。

-p TCPポート

ListenするTCPポートです。デフォルトは11211ですね

-U UDPポート

memcachedはUDP経由でも使えます。使わない場合は

-U 0

としてUDPをListenしないようにできます。

-u ユーザ名

memcachedをrootユーザで動作させることはできません。-u オプションの指定が必要となります。

-u memcached

memcachedをsetuidgid等を用いてrootユーザ以外で起動することもできますが、その際は後述する -c オプションで大きな数値を指定できなくなります。rootで起動して-uにてユーザを指定するのがシンプルです。

-m メモリ容量

最大のメモリーサイズ。デフォルトは64MBですね。単位はメガバイトなので、1GB使いたい場合は

-m 1024

と指定します。

memcachedは起動時に指定したメモリを確保しません。-k オプションを追加すると起動時にメモリを確保しますが、あまり使いません

-c 同時接続数

memcachedが扱える最大同時接続数です。デフォルトは1024。

実際には1プロセスで開く事ができるファイルディスクリプタ数の設定(rlimit)に使われるだけなので、かなり大きな数字にしても余計なリソースを使うなどの心配はありません。

-c 100000

などと大きな数字にしてしまいましょう。

-t スレッド数

memcachedはマルチスレッドで動作します。-t オプションでそのスレッド数を指定します。デフォルトは 4 です。

-t 4

変更しても目に見える差は出てきません

-C CASを無効化

CASを使うと値が変化していない時だけsetを行うというトランザクションのような操作ができるようになります。しかし、1キャッシュアイテムにつき8Byteの容量が必要になるので、全く使わない場合にはそれなり大きな無駄になります。

-C

-C オプションを使うとCASが無効になり、メモリの節約もできます。エコ!

-B プロトコル

memcachedにはおなじみのASCIIプロトコルとBINARYプロトコルがあり、バイナリの方が少し速いみたいです。memcachedはコマンドがサーバに届いた時に最初のデータをみてASCIIかBINARYかを判断しますが、どちらかしか使わない場合、-B オプションで指定してその処理を飛ばすことができます。

-B ascii|binary

デフォルトはautoで、自動的に判断します。

その他のオプション

-d デーモン化

memcachedをデーモンとして起動する場合に指定します。前述の通り弊社ではdaemontools経由での起動なので使っていません

-I 最大のキャッシュアイテムサイズ

デフォルトのmemcachedは1MB以上の大きさのキャッシュを保存することができませんが、-I オプションで変更ができます。

-I 3m

これで3MBとなります

-A shutdownコマンド

12/9にリリースされたばかりのmemcached-1.4.16で追加されたオプションです。shutdownコマンドが有効になります。(すでにflush_allがあるけど) injectionあったらコワイですね

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

アイテム

  • dentry_cache2.png
  • dentry_cache1.png
  • gf-docker.png
  • gf-site.png
  • gf_0_80.png
  • DSC01197.jpg
  • ehon.jpg
  • a48358be.jpg
  • tumblr_mw15kt3hum1qiph61o1_500.jpg
  • Untitled.png

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 4.27-ja