2014年1月アーカイブ

以前、「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を使うのが良い。そもそも速いので使わない手はないですね。