読者です 読者をやめる 読者になる 読者になる

Treasure Dataの解析プラットフォームを使ってみた

ログの解析は日時でscpでかき集めてバッチ集計してるんだけど

  • リアルタイムで集計したい
  • もっと柔軟に集計したい

という人は多いんじゃないでしょうか。


リアルタイム収集はFluentdを使えばいけそうですが、集計部分を柔軟にというとどうだろう。
CookpadやAmebaはHiveを使ってるとの情報がある。
『Hive on AWS @ COOKPAD』
『Amebaのログ解析基盤』
(どっちも古い。HiveはHadoop上でSQL(っぽく)ログ解析するためのプロダクトです)
「面白そうだなー。でもHadoopよくわからん、というかサーバいっぱいいりそうだから承認通すのめんどくさい(´・ω・`)」
とか思ってたらSoftwareDesignの最新号にこんな記事が。

Cookpadの人
「Treasure Dataは...ログ解析用の商用プラットフォームを提供しています。
Fluentd経由でTreasure Dataのストレージへと保存することでメンテナンスフリーで高速な解析プラットフォームとして利用できます。
...
クックパッド規模のPVのログでも、現状では1日分のログ解析を1~2分で終えることができます」

えっ、なにそれ楽しそう!!!

というわけで使ってみた

登録やtdコマンドのインストール、認証設定

アカウント登録するだけでトライアル版が利用できます。


そしてtdというコマンドで保存先のデータベース作ったりクエリ発行したりできるっぽいです。
導入手順はここ見てください!
http://docs.treasure-data.com/articles/quickstart

インストール自体はtd-agent入れてからgemでOK

# /usr/lib64/fluent/ruby/bin/gem  install td

追記)tdコマンドは最新版のtd-agentに同梱されたので、gem installは不要になりました。

そして恒例のApacheアクセスログ突っ込んでみた

といってもここに全部書いてるんですが。
http://docs.treasure-data.com/articles/apache
いつもどおりtd-agent入れてログの集約&転送

Apacheサーバ上でtail設定

<source>
  type tail
  format apache
  path /var/log/httpd/access.log
  tag td.apache.access
</source>

<match *.**>
  type forward
  send_timeout 60s
  recover_wait 10s
  heartbeat_interval 1s
  phi_threshold 8
  hard_timeout 60s

  <server>
    name aggrigation01
    host 192.168.1.100
    port 24224
    weight 60
  </server>

  flush_interval 10s
</match>

集約サーバでtdlogプラグイン使ってTreasureDataにデータ送信

<source>
  type forward
  port 24224
  bind 0.0.0.0
</source>

<match td.apache.access>
  type copy
  <store>
    type tdlog
    apikey XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    auto_create_table
    buffer_type file
    buffer_path /var/log/td-agent/buffer/td
  </store>

  <store>
    type file
    path /var/log/fluent/access.log
  </store>
</match>

指定がなければタグの後ろから2番目がデータベース名、後ろの端がテーブル名になります。アタマはtd.じゃなくてもOK.
設定によるかと思いますが、データの格納遅延は数分レベルでした。

で、とりあえず4000万件くらい突っ込んでみました

[root@test01 ~]# /usr/lib64/fluent/ruby/bin/td tables
+----------+-------------+------+----------+--------+
| Database | Table       | Type | Count    | Schema |
+----------+-------------+------+----------+--------+
| apache   | access      | log  | 39167813 |        |
| testdb   | testtbl     | log  | 1005     |        |
+----------+-------------+------+----------+--------+
Hiveクエリ例

ここにいろいろサンプルのってます
http://docs.treasure-data.com/articles/apache#sample-queries
time以外はJSON形式で格納されているっぽいです。v['key']で参照します。

[mikeda@test01 ~]$ /usr/lib64/fluent/ruby/bin/td query -w -d apache  "SELECT * FROM access WHERE unix_timestamp()-60*60 < time ORDER BY
time DESC LIMIT 10"
...
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
| v                                                                                                                                                                    | time       |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
| {"user":"-","method":"GET","code":"200","size":"3532","host":"126.213.174.65","agent":""DoCoMo/2.0 SH03B(c500;TB;W30H18)","referer":"http://masudaK.jp/chocolate"}   | 1338858502 |
...

timeでパーティション切ってるらしいので、できる限りそこで絞り込みましょう。


それでは簡単な集計の例

例1)1時間ごとの200 OKなアクセス数を24時間ぶん出してみる

[mikeda@test01 ~]$ /usr/lib64/fluent/ruby/bin/td query -w -d apache \
"SELECT from_unixtime(CAST(time/(60*60) AS INT)*60*60) AS day, COUNT(*)
  FROM access
  WHERE
    v['code'] = 200
    AND unix_timestamp()-60*60*24 <= time
    AND time < unix_timestamp()
  GROUP BY CAST(time/(60*60) AS INT) ORDER BY day"

...

+---------------------+---------+
| day                 | _c1     |
+---------------------+---------+
| 2012-06-03 03:00:00 | 495761  |
| 2012-06-03 04:00:00 | 1460441 |
| 2012-06-03 05:00:00 | 1540227 |
| 2012-06-03 06:00:00 | 1645884 |
| 2012-06-03 07:00:00 | 1763153 |
| 2012-06-03 08:00:00 | 1872281 |
| 2012-06-03 09:00:00 | 1969544 |
| 2012-06-03 10:00:00 | 1951634 |
| 2012-06-03 11:00:00 | 1906095 |
| 2012-06-03 12:00:00 | 2123442 |
| 2012-06-03 13:00:00 | 2276931 |
| 2012-06-03 14:00:00 | 2190546 |
| 2012-06-03 15:00:00 | 1979878 |
| 2012-06-03 16:00:00 | 1539153 |
| 2012-06-03 17:00:00 | 1113297 |
| 2012-06-03 18:00:00 | 842665  |
| 2012-06-03 19:00:00 | 675836  |
| 2012-06-03 20:00:00 | 674376  |
| 2012-06-03 21:00:00 | 884574  |
| 2012-06-03 22:00:00 | 1126021 |
| 2012-06-03 23:00:00 | 1113593 |
| 2012-06-04 00:00:00 | 1159783 |
| 2012-06-04 01:00:00 | 1201051 |
| 2012-06-04 02:00:00 | 1231317 |
| 2012-06-04 03:00:00 | 930149  |
+---------------------+---------+


例2)今日のリファラ数ランキング

[mikeda@test01 ~]$ /usr/lib64/fluent/ruby/bin/td query -w -d apache  \
"SELECT v['referer'], COUNT(1) as numreq 
  FROM access
  WHERE
     unix_timestamp()-60*60*24 <= time
     AND time < unix_timestamp()
  GROUP BY v['referer']
  SORT BY numreq DESC LIMIT 10"

...

+----------------------------------+----------+
| _c0                              | numreq   |
+----------------------------------+----------+
| -                                | 16170940 |
| http://mikeda.jp/wiki            | 670475   |
| http://d.haneta.ne.jp/mikeda     | 569030   |
| http://varnish.jp/               | 485617   |
| http://mikeda.biz/               | 446594   |
| http://oranie.jp/                | 392752   |
| http://masudaK.jp/chocolate      | 381886   |
| http://blog.xcir.net/            | 301802   |
| http://d.hatena.ne.jp/akuwano/   | 169590   |
| http://blog.mikeda.jp/           | 162940   |
+----------------------------------+----------+


こういう簡単なものなら1-3分で終わる感じでした。



REST APIといくつかの言語のライブラリ紹介もあります
http://docs.treasure-data.com/articles/rest-api
Rubyだとこんな感じで使えました。(ライブラリはtd-agentに入ってます)

#!/usr/lib64/fluent/ruby/bin/ruby
require 'td'
require 'td-client'

apikey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
db     = "apache"
table  = "access"
query  = "SELECT COUNT(1) FROM #{table}"

cln = TreasureData::Client.new(apikey)
job = cln.query(db, query)
#p job
until job.finished?
  sleep 2
  job.update_status!
end
if job.success?
  job.result_each { |row| p row }
end

すんごく簡単!トライアル版でもいろいろ使い道ありそう!!!
というわけでもうちょっと突っ込んでみます。