2010/12/20

ZABBIX勉強会でDECOLOGのシステム監視運用のお話をしました。



こんにちは。落ちている点棒は拾う主義のhiroshiです。
今年最後のブログ更新になるかもしれないエントリーです。

去る2010年12月18日(土)18:30~に開催された「第3回ZABBIX勉強会」に登壇してみました!




きっかけは、TwitterでZABBIX-JP代表の寺島さんに「話してみませんか?」とご依頼いただいたことでした。やっててよかったTwitter!そして寺島さん!ありがとうございます!
今思えば「やりましょう。」っていうまたとないチャンスだったのに「DMでメールください」とか本当にダサかったなあって反省しています。

なんていうか、僕こういう勉強会とかセミナーとか行ったことなかったので、非常に緊張してて、当日が近づくにつれ、何度か「病気になったことにしようかなー」とか思わなかったかといったら嘘になるんですが、そういう誘惑に負けずにちゃんとやってよかったです。

どのへんがよかったかということを自分視点でざっくり書き連ねると・・・

  1. 会社、およびDECOLOGの知名度向上に役立った
  2. 思ったより自分とこのノウハウは需要があることがわかった
  3. 恥ずかしくない発表にするためにいろいろ調べたりするので、発表の資料制作の課程で新しいことがいろいろ知れた
  4. チームメンバーの勉強にもなった
  5. 「慣れ」ができた

といったところでしょうか。


1.会社、およびDECOLOGの知名度向上に役立った


これは、つまりリクルーティング力アップにつながりますね。なんせ5人ですから。今だけをみればなんとかなっているものの、今後のことを考えるともっと仲間が欲しいところです。
加えて、これくらいの規模になり、技術的にもそれなりに魅力的なことができる環境にあるという自負はあるものの、大阪ドームでイベントやったり雑誌出したりしても、まだまだエンジニア界隈での知名度は激低で、なかなか「ミツバチワークスで働きたいなあ」と考えてもらえるだけの状況にありません。

名も知れぬ何やってるかよくわからない会社に転職はなかなかしてもらえませんからね。。

そういった意味でのプラスになったことは間違いないと思っています。


2.思ったより自分とこのノウハウは需要があることがわかった


60億PVとか400台とかいっても、上を見れば上がある。だから、僕らのノウハウとかに興味持ってくれる人ってどれだけいるんだろう?と懐疑的だったんですが、そんなことはやってみないとわかりませんよね。実際やってみたところ、それなりに評価していただけたようで、非常に嬉しく思ったと同時に、自信につながりました。


3.恥ずかしくない発表にするために~いろいろ知れた


やっぱり人様になにかお見せしようと思ったら、そこそこ"勘"とか"流れ"でやってたことも、これを機会に裏を取ったりするわけです。その過程で、「あっ!できねーと思ったらできたのか!」とか、「こういうやり方もあったのか!」とか「やってたつもりだったけど、できてねーじゃん!」という部分が発見できたりするんです。

「周辺知識」みたいなものは、それに出会えるかどうかは、常にそのトピックスに対して自分アンテナが張れているかどうか?という基本的な部分はあるものの、最終的には「運」に作用されるところも大きいと思います。だから、取りこぼしはしばしば発生してしまいます。


「発表」というアクションはそういう「周辺知識」を取りこぼしにくくする作用がある気がします。



4.チームメンバーの勉強にもなった


まだ知識が浅いメンバーに対しては、教えたつもりでも伝わりきれていないことが、こういうアクションを通して伝えることができたりします。

また、そうではないメンバーに対しても、ズレがちになるノウハウや方針の基本コンセプトを再度互いに認識しあえる良いきっかけになります。


5.「慣れ」ができた


寄生獣の後藤も「何事も慣れだ」と言っていましたが、まさにそのとおり。0回の経験と1回の経験の差は、1回の経験と100回の経験の差より、差があると思っています。

上述のとおり、発表のメリットはたくさんあるので、今後も機会さえあればガンガンやっていきたいと思っています。

ガンガンやるぜ!となっても、基本的にシャイボーイなので今回の経験がなかったらビビって、すぐショボンとなってしまったかもしれません。そういう良い意味で「慣れ」ができたと思います。




という感じで、日曜日が終わり、月曜に入ったあたりの夜更けにガーっと書いたのでちゃんとまとまってないかもしれませんが、もしよければ、各所からのお誘いなどお待ちしております。そして、近い将来に自社で勉強会主催できたらいいなぁと思っています。

そして最後に、ご清聴・反応いただいたみなさん、本当にありがとうございました!







2010/12/14

ZABBIXでmemcachedを監視する



こんにちわ、stoneです。
話のマクラに、その時々の時事ネタとか入れたいのですが、記事の公開のタイミングはhiroshiに任せてあるので、自分ではわからないんですよねぇ。なので、今まで、マクラなしにすごく素っ気ない書き出しをしていたのですが、やっぱり気持ち悪いので、マクラを入れることにします。
いやぁ、熱かったですね、クラシコ。
まさに粉砕っ!!って感じで。CLの決勝ラウンドもこの調子で勝ち上がっていってほしいものです。


さて、マクラも無理矢理いれて、すっきりしたところで(笑)、以前もhiroshiがちょっと紹介しました、zabbixを利用したmemcachedの状態監視についてもうすこし掘り下げてご紹介しようかと思います。

以下、zabbixや、memcached自体については、
ZABBIX本家ページ
memcached本家ページ
をごらんください。


memcachedの稼働データを取り出す

まずなによりも、memcachedから稼働状況のデータを取り出さないことには、話が進みません。以前にも書いたことがあると思いますが、新規のソフトウェアを採用する際、「稼働状況のデータが取れる」というのを選定基準の一つにしています。

memcachedのソースパッケージのscriptディレクトリにmemcached-toolというプログラムがあります。DECOLOGでは、コレを利用してZABBIXに監視させています。memcachedのインストール時に同時に/usr/local/binにmemcached-toolをコピーして置くと後で面倒がなくていいです。

memcahed-toolで、こんな↓でデータがとれます。
$ memcached-tool localhost stats
#localhost:11211 Field       Value
                   bytes  1869063911
              bytes_read 1032067324963
           bytes_written 54746662119672
                 cmd_get 19593630588
                 cmd_set   581606051
   connection_structures        1995
        curr_connections         131
              curr_items     3809885
               evictions           0
                get_hits 18996360805
              get_misses   597269783
          limit_maxbytes  9663676416
                     pid       29872
            pointer_size          64
           rusage_system 2025915.129086
             rusage_user 344758.197787
                 threads           1
                    time  1291213602
       total_connections  3544421727
             total_items   581606051
                  uptime    33891235
                 version       1.2.5
バージョンが古いですが、ご容赦ください。

監視項目

DECOLOGでは、memcachedサーバーの監視項目は、
・ロードアベレージ
・ネットワークトラフィック
・memcachedへの接続数
・キャッシュヒット率
・ストアされているデータの中での最小のTTL
の5つを監視しています。

このうち、ロードアベレージ、ネットワークトラフィックは、ZABBIXのLinux_Templateのアイテムを利用しています。

memcachedへの接続数

上記のmemcached-toolの出力から、「curr_connections」の値をそのままZABBIXに監視させればOKです。
UserParameter=memcached.curr_conn,/usr/local/bin/memcached-tool entrylistcache01 stats | grep curr_conn | awk '{print $2}'

キャッシュヒット率

こちらは、出力されるデータを元にひと手間加えます。
まず、毎分、memcahed-toolsの出力をログに出力します。
/etc/zabbix/scripts/memcachedstat.sh
#! /bin/sh
LOG_DIR=/var/log/memcachedstat
file_name=${LOG_DIR}"/"`date +%M`

/usr/local/bin/memcached-tool localhost stats > $file_name
これを毎分起動して、1分ごとにログを残します。
/etc/cron.d/memcachedstat
* * * * * root /etc/zabbix/scripts/memcachedstat.sh

そして、ヒット率を計算するスクリプトも用意します。
/etc/zabbix/scripts/memcached_hit_ratio.sh
#! /bin/sh
LOG_DIR=/var/log/memcachedstat

range=$1
if [ "x"$range == "x" ]; then
        range=1
fi

curr_file=${LOG_DIR}/`date +%M`
comp_file=${LOG_DIR}/`date -d "$range min ago" +%M`

if [ ! -f "$comp_file" ]; then
        echo -1
        exit
fi

curr_hit=`grep get_hits $curr_file | awk '{print $2}' | sed -e 's/\r//'`
curr_miss=`grep get_misses $curr_file | awk '{print $2}' | sed -e 's/\r//'`

comp_hit=`grep get_hits $comp_file | awk '{print $2}' | sed -e 's/\r//'`
comp_miss=`grep get_misses $comp_file | awk '{print $2}' | sed -e 's/\r//'`

hit_delta=`expr $curr_hit - $comp_hit`
miss_delta=`expr $curr_miss - $comp_miss`

if [ $hit_delta == 0 ]; then
    echo -1
else
    echo "scale=2; 100 * $hit_delta / ($hit_delta + $miss_delta)" | bc
fi

まぁ、何をやってるか日本語に直すと、
・毎分ごとのログを残す
・ログのファイル名は、起動時の分(minute)にしておく(00〜59)
・memcached_hit_ratio.shでは、現在のデータと引数で指定された分(minute)までさかのぼったデータとの比較を行う(指定がなけければ、1分前)
・取り出す値は、get_hitsとget_misses
・各々の差分から指定された時間の間のヒット率を計算

これを、ZABBIXにのせます。
UserParameter=memcache.hit_ratio[*],/etc/zabbix/scripts/memcached_hit_ratio.sh $1

ストアされているデータの最小TTL

この項目を監視しようとおもった動機なんですが、ある機能を追加しょうとデバッグを行っていたのですが、キャッシュがすぐに消えてしまう、という現象にあたりました。感覚的には、1分も持たない感じでした。で、調べた結果、やっぱり、memcached-toolが教えてくれました。
「stats」を渡さないと、memcachedの中のメモリの使用状況がわかります。
$ memcached-tool localhost
  #  Item_Size   Max_age  1MB_pages Count   Full?
  1     104 B  33891469 s     195 1957267      no
  2     136 B  33891487 s      58  439789      no
  3     176 B  33891476 s      34  200526      no
  4     224 B  33891486 s      35  162846      no
  5     280 B  33891488 s      35  128192      no
  6     352 B  33891480 s      38  112709      no
  7     440 B  33891483 s      44  103514      no
  8     552 B  33891475 s      54  101027      no
---- snip ----
問題のキャッシュサーバーでは、こんな↓出力がありました。
#  Item_Size   Max_age  1MB_pages Count   Full?
  1     104 B  7698339 s       1   10080     yes
  6     184 B  7173349 s       1    3130      no
  7     208 B       39 s       3   15123     yes
  8     232 B   557835 s    1924 8694555     yes
  9     256 B       83 s      26  106496     yes
 10     288 B       37 s      29  105560     yes
 11     320 B       64 s      20   65520     yes
---- snip ----
「Max_age」の欄がそのメモリブロック(slabと言うらしい)の中で、一番古いデータの保持期間らしいのですが、軒並み1分程度でキャッシュが消えてしまう、という状況になっていました。
この状況を早めに検出するために、Max_ageの値を監視することにしました。

/etc/zabbix/scripts/check_memcached_max_age
#!/usr/bin/perl
my $memcached_tool = '/usr/local/bin/memcached-tool';
my $host = 'localhost';

open(IN, '-|', $memcached_tool, $host) or die "Can't start $memcached_tool: $!";

my $min = 1e100;

while () {
    my @line = split;
    my $max_age = $line[3];
    my $count = $line[6];
    next unless $count > 1;
    $min = $max_age if $max_age < $min;
}

print "$min\n";
ZABBIXでは、
UserParameter=memcache.check_max_age,/etc/zabbix/scripts/check_memcached_max_age
として、現状、86400秒(1日)以下だった場合に、Triggerが発動するようにしてあります。


こういう標準以外の仕込みを入れているサーバーは、他にApacheとか、MySQLとかあるんですが、それも次回以降にご紹介できればと思います。

では、また次回に♪

2010/12/06

daemontoolsの利用法をいくつか


こんにちわ、stoneです。

いままで、MySQL関連のご紹介をいくつかしてきましたが、今回は、少々切り口を変えて、daemontoolsについて、いくつかご紹介してみたいと思います。


daemontoolsのものすごく簡単な概要

daemontoolsとは、プロセス監視を行ってくれるデーモンプロセスです。
監視対象のプロセスが、何らかの理由で落ちた場合、daemontoolsが自動的に再起動を行ってくれます。DECOLOGでは、qmail、memcached、keepalived、gearmand等のプロセス監視で利用しています。
daemontoolsそのものの詳しいことは、本家サイトhttp://cr.yp.to/daemontools.html等を参照してください。


ディレクトリ構成

daemontoolsは、/service/xxxxxxというディレクトリを見つけると、その下にあるrunファイルを実行します。DECOLOGでは、/etc/supervice/xxxxxxというディレクトリを作成し、対象プロセスのすべての準備が整った後、
# cd /service
# ln -s /etc/supervice/xxxxxx
として、daemontoolsの監視下に置きます。
daemontoolsは、runに不備があっても、問答無用で起動しようとするので、この様な方法をとっています。


daemontools + memcacached

memcachedをdaemontoolsの監視下に置く際の、runスクリプトは以下のようになっています。
#! /bin/sh

PATH=/usr/local/bin:$PATH
BOOT_LOG=/var/log/memcached.log

USER=nobody
PORT=11211
CACHESIZE=2048
MAXCONN=2000
OPTIONS=""

exec memcached -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS
memcachedは、/usr/local/bin以下にインストールされていると想定しています。
ここでは、通常memcachedをデーモンとして立ち上げる際の「-d」オプションを指定しません。
また、CACHESIZEやMAXCONNは、アテの値なので、起動前に適宜変更しています。


daemontools + keepalived

keepalivedの場合のrunスクリプトは、こんな↓感じです。
#! /bin/sh

PATH=/usr/local/sbin:$PATH

exec 2>&1
exec keepalived -n -d -D -S6

ここでもkeepalived自体のデーモンプロセス化を避ける「-n」オプションを指定します。
また「-S6」オプションは、このオプション指定すると、syslogのlocal6にログが出力されます。なので、/etc/syslog.confに
*.info;mail.none;authpriv.none;cron.none;local6.none  /var/log/messages
local6.*  /var/log/keepalived.log
と記述しておけば、keepalivedのログは、/var/log/keepalived.logに出力されます。


また、keepalivedは、HUPシグナルを受け付けてくれるので、何らかの変更があった場合、
# svc -h /service/keepalived
で、再起動することができます。


daemontools + gearmand

gearmandでのrunスクリプトは、こんな↓感じです。
#! /bin/sh

PATH=/usr/local/sbin:$PATH
LOG=/var/log/gearmand.log

USER=root
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
exec gearmand -u $USER -l $LOG
しつこいですが、ここでもデーモンプロセス化しないように「-d」を指定しません。
そういえば、gearmandは、立ち上げた後、このサーバーで何かオペレーションした記憶がありません。gearmandのことを、忘れかけていました。(笑)


readproctitleのメッセージを消す

daemontoolsを使用していると困るのが、readproctitleというエラーメッセージ用のプログラムです。これは、runスクリプトを実行の際に、何かエラーが発生した場合、このreadproctitleにエラーメッセージが表示されます。
$ ps ax
----snip----
 5164 ?        S      0:00 readproctitle service errors: ...........................................
----snip----
何かエラーが起きると、この「....」の部分にエラーメッセージが表示されるのですが、これが自動的にクリアされません。なので、DECOLOGでは、以下のようになプログラムを用意しています。

/service/clearmsg/run
#! /bin/bash
yes '' | head -4000 | tr '\n' .
このままだと、このスクリプトが何度も実行されてしまうので、事前に
# touch /service/clearmsg/down
として、通常は、停止状態にしておきます。で、エラーを消したいときに、
# svc -o /service/clearmsg
として、一回だけ上記のrunスクリプトを実行させます。
この辺は、みなさん、困っているらしく検索すると似たような対処法がいくつも出てきます。


daemontoolsを完全に落とす

サーバーの役割の組み替えや、移動などで、daemontoolsが不要になるケースがあります。この場合、単純にdaemontools関連のプロセス(svscan、readproctitle、supervise等)をkillしても、すぐに同じプロセスが再起動されてしまいます。
daemontoolsは、インストールする際に/etc/inittabに
SV:123456:respawn:/command/svscanboot
という記述を追加しています。
daemontoolsを完全に落とす場合は、/etc/inittabのこの行を消すか、コメントアウトして、
# telinit q
とすると、完全にプロセスが落ちます。


次回は、うーん。。。。。。
まだ数台で運用していた頃の思い出話にしようか、サーバー監視にしようか、さっき思い出したgearmanの話にしようか、ちょっと、迷ってます。まぁ、書き始めるときの気分で決めます。(笑)

では、また次回に♪

2010/11/29

DECOLOGでのMySQL Archiveエンジンの使い方


こんにちわ、stoneです。

今回は、MySQLのストレージエンジンの中の1つ、ArchiveエンジンのDECOLOGでの使い方をご紹介したいと思います。
※「DECOLOGでのMySQL BlackHoleエンジンの使い方」も合わせてどうぞ

Archiveエンジンの概要

MySQLのマニュアルをご覧いただくのが正確なのですが、その特徴を簡単にまとめると。。。。
  • insert/selectは出来るが、update/deleteは出来ない
  • order byはサポートされない
  • blobもサポートされない
  • データは圧縮されてディスクに保存される

まぁ、最初にこのマニュアルを読んだときの、正直な感想は、
「どうやって使うんだ、これ?」
って感じでした。
deleteが出来ないので、データは溜まる一方だし、データは圧縮して保存されているので、selectもそんなに速くないことは容易に想像できます。


アクションログ

DECOLOGでは、ユーザーのアクティビティの統計を取るため、action_logというデータを取っています。
create table action_log (
  action_name varchar(16),
  blog_id     int unsigned,
  param       varchar(64),
  reg_date    datetime
) engine=archive;
action_nameには、例えば、"entry"(記事の投稿)、"comment"(記事へのコメント)等が入ります。このaction_logは、日次で集計を行っています。


テーブルのスワッピング

で、archiveエンジンの運用をどうするのかというと、上記のアクションログの場合、集計の前に、以下の様なSQLを発行します。
drop table if exists action_log_yesterday;
create table action_log_new like action_log;
alter table action_log rename to action_log_yesterday;
alter table action_log_new rename to action_log;
つまり、
  • action_log_yesterdayをドロップ
  • action_log_newテーブルを作成
  • 既存のaction_logをaction_log_yesterdayにリネーム
  • action_log_newをaction_logにリネーム
こうすることで、ほぼ瞬間的に(1秒以下)で、action_logの切り替えが完了します。ユーザーのアクティビティは、引き続き、新しく作成した空のaction_logに蓄積されます。


データの集計

集計したいデータは、action_log_yesterdayに保存されています。このテーブルは、ユーザーからのアクセスから解放されているので、ゆっくり集計することができます。
select action_name, blog_id, param from action_log_yesterday
と、action_log_yesterdayを全件selectして、結果をタブ区切りで、ファイルに出力します。ファイルの出力されたデータは、HDFSに乗せてHadoopで、集計をしています。
(Hadoopでの集計の一例は、以前の記事、HadoopによるApacheのログ解析の実際で紹介しています)

サーバーの運用

DECOLOGでArchiveエンジンを使用してるデータには、以下のような共通点があります。
  • ユーザーのアクセスで、selectが発生しない
  • かなり頻繁にinsertが発生する
  • 集計は、daily、もしくは、hourlyで行えば十分
  • 集計された値は、内部で使用する値


また、DECOLOGでは、Archiveエンジンで、4つのデータを運用していて、かなりの頻度でアクセス(insert)があるため、サーバーは7台用意して、LVS(keepalived)により負荷分散させています。


実は。。。。。。。。。。
ここまで、書いておいてなんなんですが、最近、hiroshiが中心となって、archiveエンジンを使用しているデータを順次redisに移行していってます。そのうち、彼がレポートを書いてくれるでしょう。(笑)


次回は、daemontoolsを利用した運用をいくつか紹介しようかと考えています。
では、また次回に♪

2010/11/22

mysqlのテーブルの「のれん分け」


二週連続でこんにちは。夜は炭水化物は控え、朝はカレーが食べたいhiroshiです。

DECOLOGもそうでしたが、サービス開始するときに、たいがいのWEBサービスは大規模になることを前提としたインフラ構成は採りません。
たとえば、1000人も会員がいない状態でDBにシャーディングが採り入れられてたら、それはちょっとした事件です。

なのでだいたいは1台のサーバにWEBサーバもDBサーバも兼任させるような状態から始まるわけです。

だんだん人気が出てきて負荷がキツイ!となれば、まずはWEBサーバとDBサーバを分けるあたりが定石なのでしょうか。
その状態でも1つのDBサーバには、サービスで利用する全テーブルが入っている状態も珍しくないかと思います。

そして、さらに人気が加速していけば、いずれはテーブルごとにDBを分ける、という局面にぶち当たるわけです。
この「DBを分ける」作業のことををうちでは「のれん分け」と呼んでいますが、この手順を紹介したいと思います。


のれん分けイメージ

大まかな手順は以下です。

  1. 三段構成にする。
  2. slaveを切り替える
  3. masterを切り替える
  4. 余分なテーブルを落とす

の4ステップです。



1.三段構成にする。


これはつまりこんな状態にすることをいいます。

三段構成イメージ

この作業は基本的には単純なslave増設と新規レプリケーション構成を組むのの組み合わせでできるので、前回のエントリ「replicationしてるMySQLのslave増設手順」を参考にしてください。

通常のレプリケーション構築との違う、ポイントとしては
  • テーブル構成は最初は丸ままコピーすること
  • 「New master」my.cnfの設定にlog-slave-updatesとreplicate-do-tableでノレン分けしたいテーブルを設定しておくこと
です。


テーブル構成を丸ままコピーするのは、そうしないとレプリケーションが失敗するからです。replicate-ignore-dbやreplicate-do-tableなどのレプリケーション設定は、「指定のDBまたはテーブルのデータだけを受け付ける(無視する)」、のではなく、「いったん全部のデータを受け付けるけどreplicate-do-table等の設定に合わないものは破棄する」という意味合いの設定です。

もう1つは特に説明は要らないですね。具体的な設定例としてはこんな感じです。

[mysqld]
log-slave-updates
replicate-do-table=db.entry

この構成が作れてNewも含めた全slaveの同期がとれたことが確認できれば次のステップです。同期がとれたかどうかの確認は件数をとるとかそういうので確認します。



2.スレーブを切り替える


これまでは、blog、user、entryというすべてのテーブルへの接続先は同じでしたが、これからはentryだけ別にしたいわけなので、そのための準備をします。
まず、LVSに別のバーチャルIPを割り当て、そこ宛てのパケットは「New slave」に割り振られるようにしておきます。

そして次にアプリケーション対応です。今まではアプリケーション中のDBの接続先の設定はMASTERかSLAVEか、というところだけの切り分けでした。今回の場合、entryテーブルのみ別のDBグループに切り分けることになるので、接続先の設定にentryのslave(New slave)に割り振るようにします。

イメージ的にはこんな感じです。

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
  $conn = DBConnection::get(MASTER);
 } else {
  $conn = DBConnection::get(SLAVE);
 }

 parent::__construct($conn);
}

だったのを・・・

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
  $conn = DBConnection::get(MASTER);
 } else {
  $conn = DBConnection::get(ENTRY_SLAVE);
 }

 parent::__construct($conn);
}
 

こんな感じに。

この時点ではまだentryのmaster(New master)への割り振りはしません。
WEBサーバが冗長構成をとっている場合は特にそうなのですが、アプリケーションをデプロイしている間にどうしてもmasterとnew Masterの両方をentryのマスターとして捉えられてしまうタイミングができてしまいます。(DECOLOGの場合)その結果、データの整合性が取れなくなってしまうため、まずはスレーブのみ切り替えを行います。これはidにauto incrementを使っている場面を想像するとわかりやすいと思います。



一方でスレーブの場合はslaveでもNew slaveでもどちらを参照されてもデータの整合性的に問題ないので、サービスを止めずに切り替えることができます。

そのため、DECOLOGでは、まずはslaveのみ切り替え、後にメンテナンス時にサービスを停止して、DBアクセスのない状態にしてからマスタを切り替えるという運用をしています。

slave切り替え後はこんな感じのイメージです。


blog,userテーブルのreadはslaveへ。entryテーブルのreadはNew slaveへ




3.マスタを切り替える


前述のとおり、サービスを停止しマスタへのアクセスがなくなった状態になってから、マスタのアクセス先を切り替えたプログラムをデプロイします。
※ちなみにマスタのアクセスがなくなった状態の確認は、show master statusでpositionが動かなくなるのを確認しています。

class My_DB_Entry extends My_DB {

function __construct($connect_master=false) {
 $conn = null;

 if ($connect_master) {
   $conn = DBConnection::get(MASTER); ←ココ
 } else {
   $conn = DBConnection::get(ENTRY_SLAVE);
 }

   parent::__construct($conn);
}

マスタの切り替えが上手く入ったのを確認したら、このステップは終了です。



4.余分なテーブルを落とす


これで、アプリケーションからのentryテーブルへのアクセスは、完全に新しい構成のみとなりましたので、最後の仕上げにかかります。

最初にmaster←new masterのレプリケーションを切ります。

mysql> stop slave;
mysql> change master to master_host='';



mysql> show slave status;
Empty set (0.02 sec)
となったらOKです。

次にmasterからentryテーブルを落とします。
mysql> drop table entry;
slaveのはレプリケーションしているので勝手にentry tableはドロップされます。

最後にnew masterからblogテーブルとuserテーブルを落とします。
mysql> drop table blog,user;
ここのnew slaveからもレプリケーションしているので勝手にドロップされます。

ということで、これでのれん分けの完了です。

これは例によって「DECOLOGのやり方」ということでもっと良いやり方があるかもしれません。
よくはてブのコメントとかで「こうしたらいい」というコメントをいただいたりしますが、まさしくそういう要素を多分に含んでおりますゆえ。。。

では、また!

次回は弊社シェフによる第1回クッキング講座を予定しています。








2010/11/15

replicationしてるMySQLのslave増設手順



こんにちは、hiroshiです。おひさしぶりですね。
stoneが書いたhadoopの記事が打ち合わせとかで「見ましたよ。評判ですよ。」とか言われてジェラシーいっぱいです。
僕もがんばります。目指せホッテントり!

といっても、僕だと書けることに限界があるので、今日は半定常作業のMySQLの増設作業について書こうと思います。

下図のように、master1台←slave2台がLVS+keepalivedで負荷分散構成されているDBがあるとします。

図1.master-slave構成


この構成の組み方にしようかと思ったのですが、これはググったらいっぱいあったのでホッテントリは狙えないと思ってやめました。

なので、今回のテーマは「このテーブルはwriteは余裕だけどreadがきつくなってきたからslaveを増設しなければ!」となった場合のslaveを増設する手順について書いてみます。
下図のslaveCを追加するぞ!の場合です。

図2. slave Cを追加するぞ


※今回はengine=innoDBの前提で書きます。
※各種userとかIPとかその辺はそこそこ適当にかいてます。

大まかな手順は以下です。

  1. コピー元となるslaveを1台LVSから切り離す。
  2. slave→masterのreplicationを止める。
  3. dumpを取る。
  4. dumpファイルを移動させる。
  5. dumpファイルをインポートする
  6. LVSに参加させる

こんな感じです。

1. コピー元となるslaveを1台LVSから切り離す。


忙しいDBでいきなり切り離すと、sleepのプロセスが大量発生するので、weightを1にしてから外すことにしています。

keepalived.conf
# slave A
real_server     192.168.0.101 3306 {
 weight  1 ←1にする。(通常10にしてます)
 inhibit_on_failure
  TCP_CHECK {
   connect_port    3306
   connect_timeout 3
  }
}

# slave B
real_server     192.168.0.100 3306 {
 weight  10
 inhibit_on_failure
 TCP_CHECK {
  connect_port    3306
  connect_timeout 3
 }
}


weight1にして
svc -h /servie/keepalive


# slave A
#real_server     192.168.0.101 3306 {
#    weight  1 ←1にする。(通常10にしてます)
#    inhibit_on_failure
#    TCP_CHECK {
#        connect_port    3306
#        connect_timeout 3
#    }
#}

# slave B
real_server     192.168.0.100 3306 {
 weight  10
 inhibit_on_failure
 TCP_CHECK {
  connect_port    3306
  connect_timeout 3
 }
}


コメントアウトして
svc -h /servie/keepalive


で、LVSからの切り離しは完了です。

2. slave→masterのreplicationを止める。


slave A上のmysqlでshow processlistするなどしてアクセスされなくなったのをやわらかく確認します。
小心者のぼくは最終的にはtcpdumpとかでmysqlのパケットが流れてこないかを確認するのですが、この時点ではまだreplicationしたままなのでバリバリmysqlのパケットが流れています。

mysql> show slave status\G
~中略~
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
1 row in set (0.00 sec)


mysql> stop slave;
~中略~
Master_Host: master
Master_User: hoge
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000049
Read_Master_Log_Pos: 760141636
~中略~
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: NULL
1 row in set (0.00 sec)

ということで、この時点でreplicationが止まりました。
Seconds_Behind_Master: NULL
がreplicationが止まった合図です。

で、
Master_Log_File: mysql-bin.000049
Read_Master_Log_Pos: 760141636
の部分をメモっておきます。

これからdumpをとるのですが、そのデータはmasterのbinログファイルが「mysql-bin.000049」でそのファイルの位置が「760141636」の状態のものだという意味です。]
部分的なコピペでもいいんですが、だいたいこんな感じshow slave status自体をファイルに落としておいておくといいかもです。

mysql> \T slave_status.txt
mysql> show slave status \G
mysql> \t

で、ここでtcpdump -i eth0 port 3306とかやってパケットが流れないのを確認すると精神衛生上いいので僕はそうしてます。

3. dumpを取る。


容量のでかいDBの場合、dumpに半日以上かかることもざらなので、screenを使います。screenじゃなくてもnohup使うでもなんでもいいんですが、不慮のネットワーク切断に備えた何かをやっておいたほうが賢明です。

$ screen

$ mysqldump -u root -p database_stage > dump_database_stage.sql
$ mysqldump -u root -p database_release > dump_database_release.sql
※テスト環境用のDBもあるので、一緒にダンプします。

これでひたすら待ちます。Zabbixのロードアベレージとかがグイーンとあがるので、それが下がったら「終わったかな」と思って見に行きます。dumpが終わったら通知メールを送るようなスクリプトを書いてもいいんですが、必要に思ったことがないので今はそういうのはないです。

ちなみに、だいたいの場合において、このdumpをとっているスキに増設先のmysqlの構築を済ませておきます。

dumpが終わったらreplicationを再開させます。

mysql> start slave;

replicationをとめてた時間などにもよりますが、

mysql> show slave status\G
~中略~
Seconds_Behind_Master: 1348615

と、こんな感じになっているので、0になるのを待ちます。これも見つめていてもなかなか終わらないので、Zabbixを利用します。Seconds_Behind_Masterの値は、常時監視項目としてZabbixで監視+閾値でアラートを設定しているので、これを利用してreplicationが追いつくタイミングを知ることもできます。

ここでちょっとワンポイントなのですが、replicationが追いついた後、

mysql> select count(*) from table;
をします。
innodb限定の手順なのですが、これでinnodb_buffer_poolにデータを乗せようとしています。これをやらないでいきなりユーザーからのアクセスにさらすと、処理がおっつかずにreplicationのディレイが発生しちゃったりします。

ところで、上述のとおり、slave増設はslave1台をサービスから切り離してdumpを取る、という作業手順になります。
ですが、増設のきっかけは「負荷が厳しいから」だけではなくて「slaveが1台故障したから」というケースもあります。
故障のケースでmaster1台←slave2台の構成だと、故障後はmaster1台←slave1台という構成と同義になり、そこからslaveを切り離してしまうとサービスに利用できるslaveがなくなってしまいます。最悪負荷の低い深夜帯にmasterにreadも担当させてやるか、サービスを止めるかなどするしかなくなってしまうので、そこそこ途方にくれます。

ですので、slaveは3台が基本です。
masterがblackholeの場合を考慮すると「実体のあるDB3台が基本」という言い方もあります。

このことはid:naoyaさんの著書「大規模サービス技術入門」にも書いてあったのですが、僕たちは、実際に途方にくれた後にこの著書に出会い、「もっと早くこの本に出会えていれば」と悔やみました。

4. dumpファイルを移動させる。


sftpでもscpでもいいです。増設先のサーバに移動させるだけです。
ただ、うちではサーバ間のinとoutのトラフィックをZabbixで監視してます。で、特に何もしないとscpでも全力でファイル転送されるため、監視の閾値を超えてしまい、アラートメールが飛んできてしまいます。
そうすると、周りに冷たい目で見られるのでZabbixのトラフィック監視をOffにしておくことを忘れてはいけません。
この移動もファイルの容量次第ではscreenするなりしておきます。

5. dumpファイルをインポートする


$ mysql -u root -p database_stage < dump_database_stage.sql
$ mysql -u root -p database_release < dump_database_release.sql
これも相当時間がかかるケースがあるのでscreenなりをしておきましょう。 インポートが終わったら、replication設定をします。
mysql> change master to master_host='マスターホスト名', master_user='repl', master_password='repl', master_log_file='ログファイル名', master_log_pos=ポジション;
この「ログファイル名」と「ポジション」に2でメモったMaster_Log_FileとRead_Master_Log_Posを入れます。 その後は
mysql> start slave;
して、Seconds_Behind_Masterが0になるのを待ってselect count(*)やっての手順は先ほどまでと同じです。

6. LVSに参加させる


これで最後です。 keepalived.conf
# slave A
real_server     192.168.0.101 3306 {
 weight  10
 inhibit_on_failure
 TCP_CHECK {
  connect_port    3306
  connect_timeout 3
 }
}

# slave B
real_server     192.168.0.100 3306 {
 weight  10
 inhibit_on_failure
 TCP_CHECK {
  connect_port    3306
  connect_timeout 3
 }
}

# slave C
real_server     192.168.0.99 3306 {
 weight  1 ←1からはじめる
 inhibit_on_failure
 TCP_CHECK {
  connect_port    3306
  connect_timeout 3
 }
}
新しく追加したDBのweightは1からはじめます。何かしくじってたときのダメージを低くするためです。アプリのエラーログを監視しながらこの作業は行い、エラーがでれば即戻します。各種グラフを見ながらweightを10にまで持っていけたら作業完了です。 以上が単純なslaveの増設方法です。

これ、役に立つと思うのでブックマークよろしくおねがいします!

では、また会いましょう。

次回予告:「1つのDBに複数テーブルが入っている場合において、一部のテーブルだけ別DBに移動させるの術の巻」

2010/11/08

DECOLOGでのMySQL BlackHoleエンジンの使い方


こんにちわ、ミツバチワークス stoneです。

DECOLOGでは、データベースにMySQLを使用しています。
ストレージエンジンのメインはInnoDBなのですが、他にもMyISAM、BlackHole、Archiveエンジンを使っています。
今回は、その中でBlackHoleエンジンについて、DECOLOG内での利用方法をご紹介したいと思います。


BlackHoleエンジンについて


BlackHoleエンジンは、何もしません。
insert、update、deleteを行っても、データは全く変更されませんし、selectをしても、データは何も返ってきません。
実際のデータファイルを見てみても、テーブル定義ファイルの.frm以外のファイルは作成されません。
/dev/nullと似ているイメージです。

が、BlackHoleのテーブルに対して発行されたinsert、update、deleteは、binlogには残ります。
そのため、レプリケーションを行っている場合、slave側で何か実体のあるストレージエンジンでレプリケーションしていれば、その変更は、slave側に保存されることになります。

また、内部的には、何もしていないので、サーバーからの応答は、すごく速いです。


記事データの配置


以前は、DECOLOGでも、データベースもそれほど系統分けされていなくて、記事に関連するデータは、すべて、1台のサーバーがマスターとして稼働していました。



記事内の画像は、もちろん、本文データも大きくなってしまう傾向にあるので、
別テーブルとして運用しています。


ユーザーの投稿の滞留が発生


DECOLOGはデコメを専用アドレスに送ることによって記事投稿するブログです。受信メールサーバでは、受信したメールをPHPに渡すことによってDBへの登録を行っています。
ユーザーがどんどん増えてきて、1日あたりの記事投稿の件数が増えてくると、受信したメールをデータベースへ登録するシーケンスでメールの滞留が発生するようになりました。
よくよく状況を調べてみると、新規のテキストデータ・画像データをinsertする際、MyISAMでのテーブルロックが発生していました。
ご存知のように、MyISAMエンジンの場合、データに何かしら変更が入る場合、テーブル全体がロックされます。
このテーブルロックが、多発していて、それにより投稿されたメールの処理が遅れ、滞留するようになっていました。


対策1 マスターをBlackHole化


記事のテキストデータ・画像データはともに、マスターのサーバーへselectすることはなく、
また、insertされてから、投稿したユーザーがページへアクセスして、記事を表示(select)されるまで、少なくとも数秒程度のタイムラグがあります。
この特性を利用して、masterをBlackHole化することにしました。

手順としては。。。。。
masterのサーバーで
mysql> alter table (テキストテーブル) rename to (テキストテーブル)_saved;
mysql> create table (テキストテーブル) like (テキストテーブル)_saved;
mysql> alter table (テキストテーブル) engine=BlackHole;
として、テーブルをBlackHole化した後、 slaveのサーバー上で、
mysql> drop table (テキストテーブル);
mysql> alter table (テキストテーブル)_saved rename to (テキストテーブル);
として、テーブルを元に戻します。



対策2 memcachedを併用


「対策1」を導入した後に気づいたのですが、投稿したユーザー本人はその記事を見るまでに、数秒のタイムラグがあるのですが、人気があって頻繁にアクセスされているブログの場合、投稿されたそのタイミングで、他のユーザーからのアクセスがある場合があるんですね。
そうすると、テキストデータ・画像データのレプリケーションがまだ追いついていないケースがあって、その記事が正常に表示できないという問題が出てしまいます。

そこで、記事テキストと記事画像については、投稿のタイミングで、データベースよりも先に、memcachedにもデータをストアしておいて、レプリケーションのタイムラグを表面化しないようにしました。



次回は、DECOLOGでのArchiveエンジンの使い方について、ご紹介する予定でいます。

では、また次回に♪

2010/11/01

redis導入後にトラブル発生、そのレポート


こんにちわ、ミツバチワークス stoneです。

今回は、redisシリーズ第3弾、実際にredisをサービスの投入してみて、うまく行かなかった事例についてご紹介します。

redisの使用用途


今回、いくつかあるセッションデータのうち2つをMySQLからredisへ移行させました。
これらのセッションデータ、MySQL上では、セッションIDの他に複数のカラムから構成されているのですが、redis上では、この複数のカラムをserialize()して、
key(string) => value(string)
という形で格納するようにしました。

ちゃんとソースコードで確認はしていないのですが、memcachedでも、TTLが設定できますが、TTLを過ぎたデータを監視してクリアしていないですよね。
また、memcached内部のslabの構成次第では、TTLまでデータが保持されずに、データがクリアされてしまうケースにも遭遇したことがあります。
(まぁ、キャッシュだから当たり前の話なのですが)
反面、redisの場合、TTLまでデータが残っていて、かつ、TTLを過ぎたデータは、定期的にクリアされています。
(これは、ソースコードレベルでも、実際の状態監視でも確認できました。)

運用中の状態


サービスに投入後の、ピークタイムでのredisの状態です。

ロードアベレージ

redisのロードアベレージは、MySQLより低め、memcachedよりは高めで推移します。
こんな風にギザギザのロードアベレージは、初めて見る形です。
瞬間的にロードアベレージが上がるのは、バックグラウンドでのsnapshotの保存によるもののようです。

トラフィック

こちらも、初めて見る形のトラフィックグラフでした。
どうやら、バックグラウンドでsnapshotを保存するタイミングで、リクエストが滞留したり、滞留していたリクエストが一気に解消したり、というのを繰り返しているようです。
コレはこれで問題だったので、後ほど取り上げます

メモリの使用量

このグラフは、時間の尺が12時間なのですが、ピークタイムを過ぎて、TTLを過ぎたデータが、クリアされて行くのが、よくわかります。
最初にグラフを見たときには、とても感動しました。


問題1: リクエストが詰まる


現象

上記のredisのトラフィックが、サイト全体のパフォーマンスにまで、影響していました。
redisのトラフィックが詰まるタイミングで、サイト全体のトラフィックも低下する現象が見られるようになってきたのです。
下のリバースプロキシのグラフで、ざくっと切れ込みの様にトラフィックが下がっている部分は、redisの「詰まっている」タイミングと同じタイミングで起きていました。

対処

少し調べてみたのですが、なかなか原因はつかめません。
ひとまず、わかっているのは、
・snapshotの保存のタイミングで、トラフィックが低下する
・snapshotの保存のタイミングでは、CPUの使用率が上がる
の2点のみ。
そこで、保存の際に少しでもCPUの使用率を下げる為に、
rdbcompression no
(デフォルトはyes)
としてみました。

また、事前に別サーバーで、redisを立ち上げて、
1) "rdbcompression yes"で1万件程度のデータをストア
2) redis-serverをシャットダウン
3) redis.confを"rdbcompression no"に変更
4) redis-serverを起動
としても、データは保全されていることを確認しました。

結果(解決)






上記の問題があった時間帯の翌日のredisとリバースプロキシのトラフィックのグラフです。
ほぼ同じような形で見分けがつきにくいですが。。。。

redis

rev-proxy

問題だった詰まる→解消→詰まる→解消の現象が解消されています。ひとまず、ほっとしました。


問題2: 応答が極端に遅くなる


こちらは、残念ながら、解消できていない問題です。

現象

問題が起きた時間帯のredisのトラフィックのグラフです。
グラフを見ると一目瞭然なのですが、ある瞬間を境にして、トラフィックがかなり乱れているのがわかります。
同じ時間帯のロードアベレージを見てみると。。。。

これらのグラフから見て取るに、問題の現象は、
1) 内部的に「何か」が起きる
2) ロードアベレージがじわじわ上がる
3) その「何か」が分水嶺を超える
4) トラフィックが乱れる
というシナリオのようです。

この状態になると、redis-cliでも、応答が悪くなりました。
それにつれて、サイト全体のパフォーマンスが悪化して行きました。

対処

この現象が起きた場合、redis-serverを再起動すると、すんなりと問題が解消します。
が、TERMシグナルを発行しても、サさってシャットダウンできない場合もあり、実際に人のオペレーションが必要な状況でした。
問題が発覚してから、数日は、夜中まで監視を行い、問題が起きた場合はリスタート、という運用をしていたのですが、さすがに数人で運用してる現状では、無理がかかりすぎるため、MySQLに戻しました。



redis導入後、このトラブルが起きていないケースもあるので、原因の特定がまったくできていない状態です。
問題が起きていないケースでは、トラブルがあったケースに比べて、
・データのTTLが長いため、順次データがクリアされる状態になっていない
・データ件数が1/10程度
となっていて、今後、データのTTLが切れ始めるタイミングが要注意だと考えています。

今回は、トラブルに合いましたが、やはり、redisのスピード感は魅力です。
そのため、今後は、TTLが設定されない、永続的なデータについても、実験をしてみることにしています。

では、また次回に♪

2010/10/25

redisのサービスへの投入



こんにちわ、ミツバチワークス stoneです。

今回は、前回、ベンチマークをとったredisをサービスに投入した、その方法について、ご紹介します。

1 redisのセットアップ


1.1 redisをインストール

redisは、2.0.2を使用しました。
redisのインストールは、
# tar zxvf redis-2.0.2.tar.gz
# cd redis-2.0.2
# make install
ですんなり出来ました。
コンパイルされたバイナリは、/usr/local/bin/以下にコピーされています。

1.2 コンフィグ

DECOLOGでは、ApacheとMySQLを除いて、daemontoolsを利用して運用しています。
そのため、redisのコンフィグは、デフォルトのredis.confから、以下の項目を修正してあります。
(デフォルトのredis.confは、redis-2.0.2ディレクトリにあります。)
daemonize no
loglevel notice
dir /var/lib/redis/
maxmemory 8gb
これを、/etc/redis.confに保存しておきます。

1.3 ユーザーとディレクトリを設定

redis用のアカウント/グループとディレクトリを設定しておきます。
# groupadd redis
# useradd -g redis -d /var/lib/redis -s /sbin/nologin redis
アカウントとグループは、設定する必要はないのですが、なんか習慣的に作ってます。

# chmod 755 /var/lib/redis
# find /var/lib/redis/ -type f -exec rm {} \;
(useraddで作られるファイルを削除)

# mkdir -p /var/log/redis
# chmod 1777 /var/log/redis
(ログ用ディレクトリ)

1.4 daemontools用の設定

DECOLOGのサーバーでは、daemontoolsでプロセス管理する場合、/serviceに直接ディレクトリやファイルを作成しません。
/etc/superviseというディレクトリを用意して、その下にディレクトリ/ファイルの実体を作成します。
今回の場合なら、
/etc/supervise/redis/run
/etc/supervise/redis/log/run
です。
これらの確認が終わった後、
# ln -s /etc/supervise/redis /service/redis
として、daemontoolsのプロセス管理に制御を任せます。
以下では、話を簡単にするため、/serviceでのパス表記にしておきます。

file: /service/redis/run
#! /bin/sh
exec /usr/local/bin/redis-server /etc/redis.conf

file: /service/redis/log/run
#! /bin/sh
exec /usr/local/bin/setuidgid redis /usr/local/bin/multilog n20 s16777215 /var/log/redis
redisのログにもタイムスタンプが記述されるため、multilogのオプション"t"はつけませんでした。

2. redisの起動/シャットダウン


2.1 起動

daemontoolsが動いているので、/service/redis/runが作成された時点で、redisは起動されます。
手動オペレーションで、redisを落とした場合は、
# svc -u /service/redis
で、redisが起動されます。

2.2 シャットダウン

該当するドキュメントを見つけられなくて、結局、ソースコードを追っかけてわかったのですが、redisは、TERMシグナルを受け取ると、データのsnapshotを保存した後にプロセスを終了します。
なので、
# svc -t /service/redis
で、キレイにシャットダウンすることが出来ます。

2.3 再起動

単純に、
# svc -tu /service/redis
で、再起動をかけます。

再起動にかかる時間は、ほぼsnapshotを保存する時間です。
なので、事前にログを見て、所要時間を予測することが可能です。
いままでの実績で言うと、30Mbytes程度なら1秒以下、1〜2GBytesでは30秒程度かかります。

3. 監視項目


サービスに投入する前に、状態監視できるようにしておきます。
※DECOLOGでは監視にZABBIXを使っています→http://tech.dclog.jp/2010/09/decolog.html

数年前にZABBIXを導入してから、状態が監視できない状態で何かオペレーションするというのが、ものすごく怖く感じるようになりました。
なにか、目隠し状態で作業しているような感覚です。
何か新要素を投入を決めるときは、「稼働状態を取得できる」というのも大事な要素になります。

3.1 状態の取得

redisには、redis-cliという便利なクライアントツールが用意されています。
これを利用して、稼働中のredisの状態を監視します。

redisには、infoというコマンドが用意されていて、redis-serverの状態を出力してくれます。
$ /usr/local/bin/redis-cli info
redis_version:2.0.2
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
process_id:27077
uptime_in_seconds:786661
uptime_in_days:9
connected_clients:1
connected_slaves:0
blocked_clients:0
used_memory:34944584
used_memory_human:33.33M
changes_since_last_save:4903
bgsave_in_progress:0
last_save_time:1287647443
bgrewriteaof_in_progress:0
total_connections_received:12550855
total_commands_processed:37684666
expired_keys:6830
hash_max_zipmap_entries:64
hash_max_zipmap_value:512
pubsub_channels:0
pubsub_patterns:0
vm_enabled:0
role:master
db1:keys=59136,expires=59136

1分ごとにredisにinfoコマンドを発行して、状態を取得します。
file: /etc/cron.d/redisstat
* * * * * root /usr/local/bin/redis-cli info > /var/log/redisstat.log.new; mv /var/log/redisstat.log.new /var/log/redisstat.log
一度、redisstat.log.newに出力してから、redisstat.logにmvしているのは、
「redis-cli info」がredisサーバーからの応答を待ってる間に、ZABBIXが値を取りにきて、おかしな値が出力されるケースがある為です。
今のところ、redisでは、そういうケースはありませんが、ApacheやMySQLでは、そういう、いわば「滞空時間」にZABBIXが値を取得しにくるケースに遭遇したことがあります。


3.2 ZABBIXに項目を追加

infoから出力されるデータのうち、used_memoryとconnected_clientsを監視することにしました。
zabbixへの項目追加は、以下のようになります。
UserParameter=redis.connections,grep connected_clients /var/log/redisstat.log | cut -d':' -f2
UserParameter=redis.used_memory,grep 'used_memory:' /var/log/redisstat.log | cut -d':' -f2

これ、より汎用的に
UserParameter=redis.stat[*],grep $1: /var/log/redisstat.log | cut -d':' -f2
でも、いいかもしれませんね。
今、思いつきました(笑)

3.3 スクリーンを作成

ZABBIX上で、上記の2項目の他に、
・ロードアベレージ
・ネットワークのin/out
をまとめて、スクリーンを作って、監視体制の整備は、完成です。



新しい要素を加える場合、だいたい上記のような流れで、監視体制までを準備してから、サービスへ投入しています。
今回、redisをサービスに投入した後に出くわしたトラブルについても書こうと考えていたのですが、サービス投入までの記述が思った以上の分量だったので、トラブルについては、次回以降にしたいと思います。

では、また次回に♪

2010/10/20

NoSQL redisとMySQLのベンチマーク比較

こんにちは、ミツバチワークス stoneです。

「NoSQL」という言葉を、最近知りました。
確かに、データベースをテーブルごとに小分けして行くと、トランザクションとか、SQLのjoinとか使わなくなって、DECOLOGでも、MySQLを単純なKey-Valueストア的な使い方をしている箇所がいくつかあります。

Key-Valueストアと言えば、DECOLOGでもmemcachedを使用していますが、memcachedのスピード感でデータが揮発しないというのは非常に魅力的です。
DECOLOGのデータ群の中でも、簡単なKey-Valueストアで、話がすむデータがいくつかあり、
現状のMySQLよりも速い方法があるのなら、ぜひ、試してみたいところです。

そこで、今回、hiroshiが見つけてきたredisがよさそうなので、「そもそも本当に速いの?」というのを、簡単なベンチマークを取って、試してみました。

このベンチマークの環境は、DECOLOGで使用しているものがベースになっています。
そのため、ツッコミどころはたくさんあると思いますが、

「DECOLOGの環境では、こういう結果でした」


というご紹介だという前提で、読み進めてください。


1. ベンチマークの環境


サーバーは、4台用意しました。
サーバーのハードウェアは、すべて同じで、
CPUIntel Xeon L5410 × 2
Mem12GBytes
HDD685G(RAID1)
Net100Mbps
という構成です。

サーバーA

ベンチーマークを取るサーバー
このサーバー上で、abを走らせます

サーバーB

Apache + PHPのサーバー
DECOLOGで、実際にサービスしているWebサーバーと
同じセットアップをしてあります。
関係ありそうなソフトのバージョンを上げておくと。。。
Apache
2.2.x(MaxClients 200)
PHP
5.2.x(APC 3.0.x)
MySQLへの接続
PEAR::DB
Redisへの接続1
owlient-phpredis-2.0.4-5
Redisへの接続2
Rediska0.5.0
と、なっています。

サーバーC

MySQLサーバー
バージョン: 5.0.x
ストレージは、InnoDB、BlackHole、Archiveを使用しました。
InnoDBのパラメーターのうち、スピードに関係ありそうなモノのは、
こんな↓感じで設定しています。
innodb_buffer_pool_size=9216M
innodb_flush_log_at_trx_commit=2
innodb_file_per_table

サーバーD

Redisサーバー
バージョン: 2.0.1
パフォーマンスに関係しそうな設定は、こんな↓ところでしょうか?
save 900 1
save 300 10
save 60 10000
appendonly no
vm-enabled no

2.ベンチマークの方法


サーバーAから、abコマンドで、
$ ab -c 200 -n 100000 http://server-b/path/to/benchmark/script.php
として、abの出力のうち、
Requests per second
という項目に注目しました。

また、事前に、MySQL、Redisともに12万件程度の、id(int) => value(int)という形式の
データを事前にストアしてあります。

3. テスト項目

3.1 インクリメントのテスト

idをキーにして、valueをインクリメントする、というのをテストしました。

SQLにすると、こんな↓感じです。
update counter set count_value=count_value+1 where id=?
Redisなら、incrコマンドですね。

3.2 insertのテスト

現在、Archiveエンジンでロギング+バッチで集計、としている部分を、
redisのincrで置き換えられないか?というのも試してみたかったので、
一緒にベンチマークを取ってみました。

4. PHPのredisインターフェースについて


redisのプロジェクトホームへ行くと、
Predis、Rediska、redis.php、phpredisの順で、PHPのインターフェースが記載されています。
Rediskaは、PHPのみで記述されていること、phpredisは、PECLのmemcacheに使用感が似ていたこと、
からこの2つを取り上げました。

5. ベンチマークの結果


さて、前置きはコレぐらいにして、ベンチマークの結果です。
それぞれ、5回試行して、最大/最小値をカット、中間値の3つの値の平均を取っています。

で、結果は、こんな↓感じでした。
MySQL
コマンドストレージreq/sec
updateBlackHole3650.94
updateInnoDB3204.03
insertBlackHole3400.94
insertArchive3435.64

Redis
コマンドクライアントreq/sec
incrphpredis6553.42
incrRediska2955.51


ついでに、select/getも各々1パターンだけとってみました。
エンジンreq/sec
MySQL(InnoDB)3699.17
redis(phpredis)6317.66

それぞれをグラフにするとこんな感じです。
図.コマンド/ストレージ


図.コマンド/クライアント




図.エンジン

6.感想


このベンチマークの結果、DECOLOGの環境では(←ここ、大事です)、redis+phpredisが頭抜けて速いことがわかりました。

また、ベンチマークを取っていたときは、
「すげーっ!!redis、速ぇーーーっ!!」と興奮していたのですが、
少し間を置いてから、冷静になって考えてみると、
・phpredisとRediskaの結果の対比
・RediskaとMySQLがほぼ変わらない
を考えると、PHPのコードが多分に影響してるように思えます。
MySQLの接続は、DECOLOGの歴史的な要因で、PEAR::DBですし。。。

が、「現状のDECOLOGの環境」をベースに考えれば、
やっぱり、redis+phpredisが断然速いので、redisをサービスへの投入することを決めました。
新しい要素の投入は、結構、この程度のノリでやってます。
実際にサービス投入/運用してみて、初めて見えてくる問題とかありますし。。。

7. サービスに投入してみたけれど。。。


この記事を書いてる時点で、redisをサービスに投入して、
うまく行った事例が1つと、うまく行かなかった事例が1つあります。

うまく行かなかった事例では、問題が2つ発生しました。
1つはパラメーターの調整で解決できたのですが、
もう1つの問題は解決できずに、サイトへのアクセスに影響が出るトラブルになってしまったので、
MySQLに戻しました。

うまくいった事例では、本当にスムースに、期待通りに稼働してくれています。
サーバーのロードアベレージもMySQLに比べて低めで推移してますね。

redisのサービスへの投入方法や、うまくいかなかった事例については、
次回以降に、ご紹介しようと考えています。

では、また次回に♪

2010/10/13

HadoopによるApacheのログ解析の実際


こんにちは、ミツバチワークス stoneです。

今日は、DECOLOGで行われている、Apacheのログ解析について、
ご紹介してみようかと思います。

現在、DECOLOGでは、リバースプロキシが8台あって、
その8台の1日のApacheのログは、全部で、200Gバイト以上になっています。
これを、13台のHadoopのスレーブノードで解析を行っています。

全体の流れとしては、
1) リバースプロキシからHDFSにログを転送
2) 解析用のサーバーで、HDFSにログの転送が終わるのを監視
3) ログの転送が終わったら、Hadoopを起動、解析
4) Hadoopの解析結果をデータベースに保存

以下では、各々のステップを個別に見て行くことにしますね。

1. リバースプロキシからHDFSにログを転送


当初、Hadoopのプロセスが立ち上がっていないと、HDFSにはアクセスできない、
と思い込んでいたので、解析用のサーバーが、
1) 各リバースプロキシからのログをscpで収集
2) 収集したログをHDFSへ
というステップを踏んでいたのですが、
このステップだけで、昼過ぎまでかかるようになったため、
現在では、リバースプロキシから直接HDFSへ展開するように変更しました。

Apacheのログは、cron.dailyで起動されるlogroateでローテーションがかかるので、
そのcron.dailyの最後にHDFSへの展開するスクリプトが起動するようになっています。

/etc/cron.daily/zz_log_to_hdfs.sh
#! /bin/sh

HOST=`hostname -s`
TODAY=`date +%Y%m%d`

HADOOP='/path/to/hadoop/bin/hadoop dfs'

HDFS_BASE='hdfs://hadoop-master:9000/accesslog'
LOG_FILE="$HDFS_BASE/$TODAY/$HOST"
SIGNUP_FILE="$HDFS_BASE/signup/$HOST"

LOCAL_SRC="/path/to/log/access_log.1"

$HADOOP -copyFromLocal $LOCAL_SRC $LOG_FILE
$HADOOP -chmod 777 $LOG_FILE
$HADOOP -touchz $SIGNUP_FILE
$HADOOP -chmod 777 $SIGNUP_FILE


2. 解析用サーバーで、HDFSにログの転送が終わるのを監視


各リバースプロキシから直接HDFSへ展開することで、時間は大幅に短縮できたのですが、
1点、問題がありました。
それは、
リバースプロキシからのログの転送がいつ終わったのか、わからない
という問題です。

そのため、ちょっと、工夫をして、HDFS上にsignupディレクトリというモノを作りました。
各リバースプロキシは、ログの転送が終わったタイミングで、
このsignupディレクトリにホスト名のファイルをtouchします。
前出のスクリプトの下から2行目でそのtouchをしています。
$HADOOP -touchz $SIGNUP_FILE
解析用のサーバーは、適当な時間に起動した後、このsignupディレクトリを監視して、
すべてのサーバーが出そろった段階で、次のHadoopでの解析のステップに進みます。
(signupディレクトリは、転送が始まる前に事前に空にしています。)


3. Hadoopでの解析


3.1 Key-Valueのマッピング


ご存知のように、Hadoopでは、mapのステップで、
あるデータを「key => value」の形にマッピングして、
reduceのステップで、同一のkeyの値を取りまとめてくれます。

DECOLOGでは、以下のような切り口でログ解析をしています
・1時間ごとのHit数、PV数
・ページグループ毎のPV数
・携帯キャリアごとのHit数

具体的なキーは、こんな感じです。
YYMMDD-hourly_hit-HH (時間ごとのヒット数)
YYMMDD-hourly_page-HH (時間ごとのPV数)
YYMMDD-page_group-PAGE (ページグループごとのHit数)
YYMMDD-page_group_pv-PAGE (ページグループごとのPV数)
YYMMDD-user_agent-CARRIER (携帯キャリアごとのHit数)

アクセスされたURLから、
・ページビューなのか画像なのか?
・ページビューの場合、HTTPステータスが200か?
・どのページグループに属するURLか?
を判別しています。

なので、例えば、記事ページへのアクセスがあった場合、
aaa.bbb.ccc.ddd - - [13/Oct/2010:11:05:09 +0900] "GET /en/00000/00000 HTTP/1.1" 200 999 "-" "DoCoMo/2.0 D905i(c100;TB;W24H17)" "-"
という感じのログになるのですが、これをmapperに通すと、
20101013-hourly_hit-11    1
20101013-hourly_page-11    1
20101013-page_group-Entry    1
20101013-page_group_pv-Entry    1
20101013-user_agent-docomo    1
という結果が出力されることになります。

3.2 mapper/reducerの記述


実際のmapperとreducerは、Javaではなく、Perlで記述しています。
Perlで記述したスクリプトをhadoop-streamingを利用して、
mapper/reducerとして、稼働させています。

hadoop-streamingは、データの入出力に標準入力/標準出力を使用するため、
動作の検証は、実際のログデータをmapperに標準入力から流し込んだときに、
想定通りの出力が得られるか?で、行うことが出来ます。
(reducerは、そのmapperの出力を流しこめばOKです。)


4.解析結果をDBに保存


Hadoopの解析結果は、「part-0001」みたいなファイル名でHDFS上に出力されているのですが、
これをうまい具合に取り出す方法がよくわかっていなくて、
現状、以下のようにして、ローカルに取り出しています。
$HADOOP dfs -cat "$OUTPUT_DIR/part-*" | grep $YESTERDAY > $TMP_FILE

保存するデータベースは、以下のようなテーブル定義になってます。
(データベースは、MySQLです)
カラム名データタイプ
log_datechar(8)
sectionenum("hourly_hit", "houly_page" ...)
section_keyvarchar(255)
log_countint unsigned

解析結果は、上記のmapperの出力に準じた形になっているので、
20101013-hourly_hit-11    999
と出力されていて、これを、
log_date: 20101013
section: houry_hit
section_key: 11
log_count: 999
と保存しています。


ごく初期の段階では、webalizerを使って、サーバー1台で解析をしていたですが、
解析が夜までかかるようになったため、Hadoopによる解析に切り替えました。
当時(2009年4月ごろ)は、サーバーの負荷対策で、ほぼ毎日のようにサーバーの増設や、役割の変更、
それに伴うプログラムの修正を行っていました。
その際、その対策の根拠となるのが、前日のZABBIXのグラフとアクセス解析の結果でした。
なので、アクセス解析が夜までかかると、負荷対策の立案にとても困った記憶があります。
参考までに、切り替えを行ったタイミングでは、1日あたり、だいたい4700万PV/2億6000万Hitでした。
(ログのサイズは、データが残っていませんでした。)
現在は、1日あたり、だいたい2億PV/15億Hitなのですが、お昼までには解析が終了しています。


蛇足ですが。。。。。。。。。。。。。
ウチでは、このシステムを「HAL」と読んでます。
「Hadoop for Apache Log」の頭文字をとって「HAL」としていますが、
本当は、HAL9000からパクって、「Hadoop for なんちゃら」は、
後からこじつけました。
が、他の年下のエンジニアからは、「何スか?HAL9000って?」と言われ、
ジェネレーションギャップを感じました。
(え?知らない方が多数派ですかね、これって?)

では、また次回に♪

2010/10/06

LogWatchが多すぎる!!

こんにちは。
ミツバチワークスで技術責任者をしています、stoneと申します。
先駆けてブログを開設・投稿してくれていたhiroshiが、
ハンドルネームで名乗っているので、自分もハンドルネームで名乗ってみることにしました。
なかなかに気恥ずかしいですね、これ。(笑)



さて、とても幸運なことに、ここ数年というもの、DECOLOGというサービスは、
ユーザー数/トラフィックともに順調に伸びて行っている状態で、
その要求に応える為に、サーバーの増設も毎月のようにおこなっています。

当初、DECOLOGは、たった1台のサーバーで運営されていました。
現在では、300台以上のサーバーで構成されています。
もちろん、それ以前に、その規模のサーバー群を扱ったこともないですし、
まさか、自分が100台以上のサーバーで構成されるサービスに関わるなんて、
夢にも思ってませんでした。

台数が増えるにつれ、数台での運用していた頃には想像できなかったことが、
いろいろ出てくるのですが、今回は、そんな「想像できなかったこと」のうちの
1つを紹介したいと思います。



現在、DECOLOGを構成するサーバー群のログの監視には、LogWatchを利用しています。
ご存知のようにLogWatchは、dailyでレポートメールを送ってきてくれるのですが、
10数台ぐらいまでは、毎朝、異常がないか、目視でレポートメールを確認をしていました。

が、これが、台数がふえるにつれ、LogWatchのチェックだけで、
かなりの時間が必要になってきてしまうんですね。
そして、ついには、もう、目視では、チェックしきれないほどの量になってしまいました。
連休明けや、夏休み/冬休み明けともなると、LogWatchだけで、
1000通を超えることもありました。
問題に直面してみれば、至極当然の結果なのですが、自分には、想像の範囲外の問題でした。

もう、チェックし切れないなら、レポートを切ってしまえばいいのですが、
「もし何か見落としがあったら。。。」と思うと、バチンと切ってしまう勇気も持てませんでした。

そこで、考えたのが、今、ウチで使っている「LogWatch Watch」(笑)です。
だいたい、こんな↓流れで動いてます。
  • LogWatchのレポートメールをLogWatch Watchが動いているサーバーにも送信
  • メールを受けると、フィルタリングプログラムが起動
  • フィルタリングプログラムには、あらかじめ、無視していい記述を登録しておき、
    それから漏れた記述をデータベースに保存
  • Webインターフェースを用意して、登録外の記述をチェック
実際のフィルタリングは。。。。
  • Cron: run-parts /etc/cron.(hourly|weekly|daily)を無視
  • Diskspace: 90%以上になったら、データベースに登録
  • sshd: "SFTP subsystem requests"を無視
とった感じで、登録する/無視するの判断をしています。

これを作って、実際に使ってみると、思った以上に便利でした。
1日数百行のチェックから、1日50項目以下のチェックですみます。

実際の画面は、こんな↓感じです。


現在は、時間の合間を見つけて、改良を進めて、キーボードショートカットで動くようになっています。
j: 下へ
k: 上へ
x: 項目を削除
m: 項目を保護(削除できないように)
とviライクな操作感で、サクサクとチェック・削除をしています。

DECOLOG本体での機能追加や修正のプログラミングは、
バグを仕込んでしまうと影響がとても大きいから、
一定以上の緊張感をもって臨んでいるのですが、
こういう「まかない」的なプログラムを書いてるときは、
その緊張感から解放されて、本当にプログラミングが楽しく感じられます。
自分勝手にイメージして、自分勝手に実装できますしね。(笑)


他にも、問題に直面するまで想像できなかったことに、
  • クラスCのIPアドレスが足りなくなる
  • L2/L3のスイッチの限界値に行き当たる
などあるのですが、これらは、また、機会があったら、 後日、ご紹介することにしますね。 

では、また次回に

仲間募集のお知らせ

2011/03/02 追記)募集要項を公開しました。
2012/02/15 追記)本ページの最後に「募集しているのってエンジニアだけなの?」を追加しました。

こんにちは!hiroshiです。

先に、今週はZABBIXの続きじゃなくなりました。すみません。。。

今回は仲間本格募集のお知らせです。

本格募集といっても「募集要項はこれです。気に入った方は面接に応募してください。」というノリではないです。

「まずはお話してみませんか?」というノリです。

「もっと違うことやりたいなー」とか「ああいうことやりたいんだよなー。でも今の会社じゃ難しいよなー」という人はたくさんいると思うんです。

でも、どこか興味のある会社を見つけたとしても、本当にその会社ことを知ることができるのは面接の場か、もしかしたら就業してから、ということもめずらしくないと思うんです。

興味を持った会社に自分の知り合いの人でもいれば、その人からいろいろ聞けたりするかもしれません。でも、うちのような小さな会社ではそういうこともないでしょう。特にぼくたちは「中の人」ですから(笑)。

なので、まずぼくたちと知り合って、外側からは見えない「どういう会社なの?どういうことができるの?どういう人たちが働いているの?」といった情報をみなさんが収集できる機会があったらいいかな、と思ったんです。

だから、最初は履歴書も職務経歴書も要りません。

現在就業中の方は、転職の意思が明確じゃなくても全然OKです。「ちょっと気になるなー」と思ったらお話してみましょう。

まずはTwitterからDMをいただければと思います。※メールアドレスも下に追記しました。
単発質問ならDMでやりとりをしてもいいですし、Twitterでおさまらないことならメールでやりとりしてもいいですし、ご希望者の数にもよりますが直接お会いすることもアリだと思っています。
お会いするときはさすがにプロフィールのご提示をお願いするかもですが。。

期限は決めてないので、この記事を見たかたはいつでも待ってますので遠慮なくお声掛けください。


2011/01/27の追記)
Twitterを使ってない方やDMに抵抗がある方など、メール連絡をご希望の方向けに連絡先メールアドレスを用意しました。こちらからお願いいたします
→→
tech-recruit[ AT ]328w.co.jp
※[AT]はアットマークに変えてください。


2011/02/02の追記)
お問い合わせいただく内容で多いものをFAQとして以下にまとめます。

FAQ

求める人材像は?


  • 「技術がスキ!」という人。
  • 「DECOLOGというサービスをスキになれそう」と自分で思えそうな人
  • 「DECOLOGを使ってくれているユーザーの立場でモノゴトを考えられそう」と自分で思えそうな人
です。


求めるスキルは?

一言でいうなら、どんな小さいサービス(ただの掲示板とか)でもよいので、一人でwebサービスを公開できるような知識があることが望ましいです。

プラスして、プログラミングだとかインフラとか、1つ深く興味を持っているモノがある、というのが良いイメージでしょうか。

特に

  • スマートフォンアプリケーションエンジニア
  • サーバサイドアプリケーションエンジニア
  • フロントエンドエンジニア

を絶賛募集してます。
AWSもかなりの高確率で触れることになります。



毎日どんなことやってんの?

・新規機能開発
・パフォーマンスチェック/改善対応
・不具合対応
など、60億PVのサービスを支えるために必要になるさまざまなタスクに取り組んでいます。

非常に多岐にわたり、かつ、優先順位がめまぐるしく変化するところが、特色だと思います。

特に恒常的に時間的に遅くまで働くことはないのですが、それ相応の知識がつくまでは、アサインされるタスクの範囲やレベルが上がっていかないので、未知のことを吸収する意欲=「技術がスキ」でないと、つまんなくてキツイと感じるかもしれません。

追記@2012-01-21)
昨年から新サービスの開発が始まっています。エンジニア担当は当時入社したばかりのtakaを中心として進行中しています。DECOLOGの運用開発のみでなく、新しいサービスの開発に関わる、というケースもあります。


社風ってどんな感じ?

わきあいあいとやっています。
ベンチャーではありますが、「とにかく気合で勝負!」みたいな体育会系的なノリはなく、どちらかというとフワっとした感じかもしれません。
ただ、これは個々人によって感じ方は違うと思うので、あまり参考にせず、ご自身の目で確認されたほうがよいです。

他チームとの交流的な話をすると、たとえばディレクター陣とかの他チームとかとも普通に飲みに行ったりする感じです。



あと、「社風」というより「TECHチーム風」になりますが、マンパワー使ったら負けだと思っています。
TECHチームが担う業務において、マンパワーで解決する話ってあまりないと思うんです。もし「けっこうマンパワーで解決してるよ!」ということがあったとすると、たぶんそれは、
・「サーバセットアップ手順はワードにまとめてあるからそれに従えば誰でもできるよ。いっぱいセットアップしなきゃいけないからマンパワーで解決だね!」

とか

・「こんな不具合起きたけど、この2つのコマンド打てばとりあえずしのげるからそれでいいか」みたいなものの積み重ねで、それが大きなものになり「2つのコマンド打つ要員が足りなくなったから増員!」

とか、そんな感じのコトが多いようなきがします。

このへんのことは
・「誰でもできることはシステムにやらせる」こと心がける
・一度起きた不具合や問題は二度起きないように、必ず一定の結果がでるまで優先的に原因追及/対応を行う
ことでカバーできることだと思います。

ワードにまとまるセットアップ手順だったら、それはプログラムになると思います。
「大きな障害」というのはあまり単体で存在はせず、小さな不具合が積み重なって起こるものだと思います。

なので、僕らはこの2点のことをいつも心がけています。
これが数百台のサーバを管理し、数十億PVを5人で支えられている大きな要員だと思っています。

マンパワー使ったら負けだから絶対に使いません!というわけじゃないんです。時にはそうすることがベストなこともあります。でも、負けは負けなので、それは後で必ずリベンジすべくゴニョゴニョやってます。

募集してるのってエンジニアだけなの? 

いえいえ、デザイナーやディレクターなど、ほぼ全職種に渡ってます。
とりわけ、以下の職種については超ウルトラ絶賛大募集中です。

1.ディレクター兼マークアップエンジニア
2.フロントエンドエンジニア

それぞれ必要な能力をステレオタイプに切り分けると

1.ディレクター兼マークアップエンジニア
ディレクション・デザイン > HTML/CSS/JSの知識があり、できればある程度構築できること

2.フロントエンドエンジニア
昔で言えばコーダーと呼ばれてた人たちですが、HTMLだけでなく、CSS・JSの高度な知識を必要とします。
※HTML/CSS/JSの知識があり構築できること


こちらもぜひご応募ください!







2010/09/29

DECOLOGのZABBIXを使ったシステム監視

こんにちは、hiroshiです。今は土曜の昼下がり、週1連載なんて言わなきゃよかったなーと思いながら男が一度言ったことは1ヶ月はやらねばだめだよね!ということで記事を書いてます。

今日はDECOLOGのシステムの監視についてです。
「便りがないのは良い知らせ」と言いますが、その便りが「システム落ちました」だとシビれますよね。

そうならないためには、システムを常時監視し、なるべく事前に異常に気づける仕組みづくりが欠かせません。

DECOLOGではシステムの監視にオープンソースツール「ZABBIX」を使っています。

ZABBIXはServerとAgentからなるクラサバ形式のツールで、CPU・メモリ・HDDの使用率などのおなじみなものはもちろん、標準出力に出せるものならなんでも取れてしまう優れものです。と書きましたが、他のツールのことは知らないのでこれはZABBIXに限ったことじゃないかもしれないです。

ちなみに例えばmemcachedのコネクション数を取りたい時なんかは、zabbix_agentd.confに↓のような感じにかけばよいわけです。
UserParameter=memcached.curr_conn,/usr/local/bin/memcached-tool localhost stats | grep curr_conn | awk '{print $2}'
grep curr_connのところはawkでNR==8とかでもいいかもですね。まあ、取れればなんでもいいワケです。

そして取得したデータをお好みのグラフにまとめたり、そのグラフをスクリーンという画面単位に整理できたりするのです!まあ、これも他のツールでもできることかもしれないですけど。

しかもしかもですよ、それぞれの監視項目に閾値を設定してそれを超えたらメール送信したりもできちゃうのです。・・・きっとこれも他のツールでもできるんだろうなー。

なぜZABBIXかというと「これ」っているのはなくて、DECOLOGの場合はstoneが導入当時、やりたいことができるツールを探していくつか試した結果、導入もすんなりいってしっくりきた最初のツールがZABBIXで、今も特に困ったことがないのでZABBIXを使い続けています。

困ってなきゃ、なんでもいいんです。今Cacti使ってて困ってることがなければCactiでいいと思います。ツールの変更とかは困ってきたら、もしくは困ることが見え始めてきたら考えるというノリでやってます。これは利用するプログラム言語もIDEもなんでもそうなんですが、あまり先のことまで考えすぎてはいけないと思ってます。

さて、そのZABBIXを使って何を監視しているか?ですが、基本的な死活監視およびCPU,メモリ,HDDの使用率、トラフィックなどは全サーバ視てます。というかZABBIXのAgentセットアップしてServerと疎通とると同時に勝手にみてくれてます。あとは用途ごとに定番のものがあります。だいたい以下です。

プロキシサーバー(バランサー)
DECOLOGのトラフィックはすべて数台のリバースプロキシサーバーを通って複数台のWEBサーバなりsquidサーバなりに振られるんですが、まずここのトラフィックが最重要監視項目です。何か問題がある場合は、ここに高確率で表れます。プログラムの更新とかネットワーク関係の設定変更とかをやる際は、タスクごとにいろいろ監視しながらやりますが、ここの監視は絶対に含まれます。
図1.とある日のとある時間のトラフィック
ここに変な動きがあったら何かがおかしい可能性が高い!

WWW/AP

CPUのロードアベレージとapacheのBusyWorkerを見てます。

DB
masterはロードアベレージとコネクション数で、slaveはさらにreplication delayを見てます。

memcached
コネクション数とキャッシュHit率を見てます。

squid
キャッシュHit率とファイルディスクリプタ数を見てます。

メールサーバ
キューの数とか送信状態を見てます。


と、こんな感じの定番項目以外は、必要に応じてグラフを起こして監視します。

グラフは、同じ役割のものを全部束ねて作ります。こんなイメージです。
こうすると、サーバに異常があったり、設定におかしなところがあったりすると、1本だけおかしな動きになるのでわかりやすいです。
たとえば
こんな感じです。これはとあるDBのロードアベレージのグラフですが、バックアップ用のDBを使って負荷のかかるSQLを流しているのでバックアップ用のDBの線(赤いの)だけロードアベレージが跳ね上がってます。

異常監視だけじゃなくて、上記のような例だと、「この線が下降してきたら、SQL処理が終わったんだな」みたいな用途にも使えます。

あとは、とにかく気になったものはグラフを起こしてスクリーンにまとめます。スクリーンには名前が付けられるので分かりやすい名前をつけます。たとえば「Squid Overview」とかです。しばらく、そのまま運用してみて、「やっぱこれいらねえな」ということになれば、なくなりますし、「これいいね!」ということになると、「04 Squid Overview」というように名前の手前に数字が付くというAKIRAのナンバーズのような制度を設けています。

今回はここまでにします。

各監視項目には必要に応じて閾値が設けられており、その閾値を超えるとアラートメールを飛ばすように設定していますが、次回はそのアラートメールについて書く予定です。違ったらごめんなさい。

次回が1ヶ月目のはずなので後1回はかならず週刊を守るべくがんばります。

では、また次回に会いましょう。

2010/09/22

画像配信システムその2

こんにちは、hiroshiです。
自分のことをhiroshiとかアルファベットで表現すると軽く有名人っぽくて気分がいいです。みなさんも是非やってみてください。

さて、前回の記事の最後に紹介させていただいたはITメディアさんの記事では、記事画像配信の仕組みについて触れられています。
あの仕組みは「デコメで投稿される記事に使われている画像」の特性から現状のあの構成になりました。

一方でDECOLOGで扱われる画像はもちろん記事画像だけではありません。
代表的なものに、ブログTOP画像というのがあります。

ブログTOP画像は、その名の通りブロガーのみなさんの顔ともいえるブログのTOPページを飾る画像です。

図1.ブログトップページサンプル

上図の通りページ上部に配置される画像であるため、携帯画面でのファーストビューはこのブログTOP画像で占めらる割合が大きいです。
つまり、「ブログTOP画像=ブロガーのみなさんの顔」といっても過言ではなく、少しでも早く表示させたい画像なわけです。

さて、ITメディアさんの記事で、記事画像配信の仕組み変遷の歴史画像が掲載されていますが、あれの3代目まではこのブログTOP画像も同じパケットのフローにのっていました。違っていたは一番下のDBだけです。

しかしながらブログTOP画像と、記事画像では大きな違いが2点あります。
  1. アクセスの頻度が違う。記事画像は時間が経てばが落ちるが、ブログTOP画像は一定。
  2. データ量が違う。記事画像は際限なく増加するが、ブログTOP画像は仕様上、ブログ開設数×2が上限

1から、アクセス頻度がロングテール化する記事画像のようなアーカイブ方式はそぐわないことがわかります。
この大きな違いを持つ2種類の画像が共存していたため、squidのキャッシュ効率も悪く(たぶん)、ブログTOP画像の表示がもっさりする、登録しても反映が遅い(=replication delayの発生)ことがしばしばありました。


なので、「まずブログTOP画像をなんとかしよう!」ということで対策会議を開きました。といっても技術責任者のstoneと2人でベランダでタバコすいながら「なんかいい方法ないっすかねー」みたいなノリでしたけど。
で、いろいろ議論を重ねた末に、「とりあえずこれでやってみっか!」となったのが下図の構成です。

図2.ブログトップ画像構成

Before、Afterでの大きな違いは、画像の保持方法を動的(DB管理)から静的(FS管理)に変えたことです。
2の「データ量が違う」ということから、ITメディアさんの記事で触れられているinodeの枯渇の心配もないことがわかり、だったらDBにぶち込まないでそのまま置いといたほうが早くね?ということでそうしてみました。

画像サーバは読み取りと書き込み用にポートを分けておき、書き込み用には画像を受け取る用のPHPスクリプトを置いておきます。

書き込み処理の流れを追ってみます。ブログトップ画像の登録はメール送信で行われます。
  1. qmailで受け取ったメールはパイプ処理でphpプログラムに渡す
  2. phpでメールの中身を解析し、画像ファイルの取り出し・加工処理などを行った後、
  3. DBにぶち込み、
  4. HTTPでブログIDをキーに決められた格納先画像サーバとバックアップ用画像サーバにPUTする。

PUTと言っても、単純にrequestに画像データをぶち込んで画像サーバの81に向けて特定のURLを呼び出すだけです。

METHODとしてのPUTを使ってみる方法(WEBDAVとかを使うんだったけかなあ?)も試行錯誤してみたんですが、作業を担当したのが2流の僕なのですぐ音を上げました。「stoneさん、これムリっす!」「じゃあ、あと1時間だけ挑戦してダメだったらやめよっか」やっぱりだめでした。。

言い訳っぽいのですが、あのまま試し続けていればカッコいい方法が実現できたかもしれません。ただ、慣れてない方法でやるよりもシンプルかつ自分が完全に理解できている方法でやったほうが、トラブル時などの対策案も多々浮かんできます。それになにより、人がいないので方針を決めたら作業完了までの工数が読めない方式は取りたくない、というのもありました。

一方で、どこかで新しいことやモノに挑戦していかないと手元のカードは増えません。状況に余裕があるときであれば、新しい手法・試したことのない手法はどんどん突っ込んでいくべきだと思います。

ということでまあ当時は余裕がなかったんです。

さて、話を元に戻します。
ユーザーから送られてきた画像は、3箇所に書き込まれます。もちろん理由があります。
内訳はDB1箇所に画像サーバ2箇所。

画像サーバ2箇所に書き込んである理由はすぐわかります。画像サーバがぶっ壊れたらすぐ代替と交換できるようにそうしてます。なので、バックアップ用サーバは全ての画像を保持しています。

DBに書き込んでいる理由もやっぱり画像サーバがぶっ壊れたときのためです。
どこかの画像サーバがぶっ壊れても、バックアップサーバを突っ込めばサービスとしては問題ない状態になりますが、その間に復旧作業をしなければなりません。

バックアップ用サーバからrsyncで、とかもちょっと考えましたが、普及中もユーザーによる追加/更新が行われ続けるため、完全に同期がとれるのかがすごく疑問でした。また、その検証方法も思いつきませんでした。

一方、DBで持っておけば、その辺の取り回しはいくらでも融通が効くので、復旧用のスクリプトさえ用意しておけば数時間で普及できる計算が成り立ちました。ということでDBにも書き込んでいるわけです。


読み込み処理の流れはシンプルです。URLにその画像がどの画像サーバにあるかがわかるようになってるのでmod_proxyで振り分けるだけです。

この仕組は想定通りの結果が得られ、今でも非常に安定して稼働しています。

本当は、既存の仕組みから新しい仕組みに切り替えるときに発生する「移行」についてもいろいろ工夫してきた話があるのですが、これはまた後日にさせてください。

では、また次回に会いましょう。

2010/09/16

DECOLOG TECH BLOG始めました。

はじめまして。hiroshiです。

Twitterでは、前からやるやるといってやってなかった「DECOLOGの中の人ブログ」、ようやく始めました。

気がつけば「DECOLOG」もそれなりの規模のサービスになってきて、技術的にもそれなりのことをできるようになりました。それは、同じ業界の方々が生み出した様々なプロダクトや、公開していただいた経験・ノウハウに助けられて成り立っていると思っています。そこでセンエツではあるものの、ぼくたちの経験・ノウハウを公開することによって、ぼくたちが助けられたように、誰かを助けられたらいいな!ということで始めました。

DECOLOG」って何?という方も多いと思いますので、初回は「DECOLOG」の紹介を簡単にしたいと思います。
まず、どんなサイトなの?というところは、この記事で弊社の光山がお話させてもらってるのでどうぞ。

次に数字で見るDECOLOGです。

  • ブログ開設数:190万
  • PV数:2億/日
  • HIT数:14億/日
  • メール送信数:1,000万/日
  • apacheのログ:220GB/日
  • サーバ台数:約330台
※数字はすべて「約」で、2010年9月中旬のものです。

などなど。
これをハードウェアを除くすべての運用・開発を5人の技術チーム(最近、当社比で倍近くの人数になりました!)で回してます。

ヒストリーも含めて、このシステムがどのように支えられているか?支えてられてきたか?をぽつぽつ紹介していきたいと思ってます。

最後に、9月1日にITメディアさんに掲載していただいた「月間57億PV、300台のサーバを運用するミツバチワークスが編み出したインフラ技術」を紹介して終わります。

ではまた次回に会いましょう。