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って?」と言われ、
ジェネレーションギャップを感じました。
(え?知らない方が多数派ですかね、これって?)

では、また次回に♪