Rails4 + Fluentd + MySQL でランキング機能作ってみた
アクセスランキングを作ってみよう!
今、Rails4 を使ってアクセスログからランキング作ってみたいなぁーと思ってたんですが、みんなどうやってんだろうって聞いてみたところ、 Fluentd ってのを組み込むだけで簡単に集計とかできるよーって話だったので、使ってみました。
Fluentd とは
まず読み方は、 「ふるーえんとでぃー」であって、「ふるーえんど」とかじゃないっぽいです。d はたぶんデーモンの d 。
色々説明面倒なんで省略しますが、ログ収集元とか出力先が簡単に設定できて、自分の欲しい形でログ保持できますよーって感じっぽい。なので、 「シェルスクリプトで処理した tail の処理結果をファイルに」「nginx から出力されたアクセスログを mongoDB に」とかそういう処理を自前で書く必要なく、簡単な設定だけでいけちゃう。
詳しくは → 柔軟なログ収集を可能にする「fluentd」入門 - さくらのナレッジ
今回の Fluentd
今回は Rails である処理が実行された時に、アクセスログを Apache だけじゃなく、MySQL に保持してランキング作りたい!ってのが要望。
調べてみると、 Rails + Fluentd + MySQL の設定で書かれた記事がなかったので、設定した備忘録としてログを残してみます。
前提
- rails のアプリケーションを作ってて、実行環境がもうできてる
- gem とか mysql あたりの設定ももちろん終わってる
- zsh 使ってる(そうでなければ適宜 .zshrc とかを .bashrc に置き換えてください)
- vim で編集できる(よね…)
おおまかな流れ
- 置き場となるデータベース/テーブルを作成する
- fluentd をインストールする (本体)
- fluent-plugin-mysql をインストール (出力の準備)
- fluent-logger を Rails に組み込む (入力の準備)
- 実行
1. 解析用のデータベース/テーブルを作成する
まずは、どんな情報が欲しいか考えるところから始まりますが、今回はページアクセスランキングを作るとしましょう。 ページID、アクセスした時間 の情報があれば、ミニマムなランキングが出来そうです。
# mysql に接続してデータベース/テーブルを作っていきます mysql -u user_name -p # データベース作る > create database access_log; # テーブルを作る > CREATE TABLE `page` ( `id` int(10) NOT NULL AUTO_INCREMENT, `page_id` int(10) NOT NULL, `access_time` int(10) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
これで準備完了。
2. fluentd のインストール
これもあっさり終わると思います。
$ gem install fluentd $ source ~/.zshrc
これだけで本体はインストールできて、パスが通ってるはずです。
3. fluent-plugin-mysql をインストール (出力の準備)
fluent-plugin-mysql を入れる
gem install fluent-plugin-mysql
これだけで終わりです。楽ちん。
Rails の DB 情報に設定した情報を追記
1 で設定したデータベースを設定する
$ cd /home/user/app/pageapp $ vi ./config/database.yml
database.yml にデータベースの情報を追記(username,password,socketあたりは変更してください)
accesslog: adapter: mysql2 encoding: utf8 database: access_log pool: 5 username: mysqlname password: mysqlpass socket: /var/lib/mysql/mysql.sock
fluent 用の conf ファイルを作成。
$ cd /home/user/app/pageapp $ mkdir ./config/fluent # fluent の config ファイルを作成する $ fluentd --setup ./config/fluent
conf の修正
$ vi ./config/fluent/fluent.conf
fluent.conf の最下部に 以下の情報を追記します(database, keynames, sql, username, password あたりは変更してください)
<match page.access> type mysql host localhost database access_log key_names page_id,access_time sql INSERT INTO page (page_id,access_time) VALUES (?,?) username mysqlname password mysqlpass flush_interval 10s </match>
これで Mysql へのアクセス準備が出来ました。
4. fluent-logger を Rails に組み込む (入力の準備)
インストール
fluent-logger は Rails のアプリの該当の箇所にアクセスされた場合に 「 fluentd にログを送って」、っていう指示を出すために必要になります。
rails の アプリケーションの場所まで飛んでください
$ cd /home/user/app/pageapp $ vi Gemfile
vim 等で以下の 2行を Gemfile
の適当な位置に追記します
# for access log by fluentd gem 'fluent-logger'
インストールする
$ bundle install
これで fluent-logger が Rails アプリケーション内で使えるはずです。
具体的に コントローラーから呼び出して使ってみる
モデルを以下のように作成
$ cd /home/user/app/pageapp $ vi app/models/access.rb
class Access < ActiveRecord::Base establish_connection(:accesslog) self.table_name = "page" self.primary_key = "id" end
これで Rails から該当の DB を呼び出す準備が完了
一例としてページの情報を処理するコントローラーから 呼び出してみます
$ vi app/controllers/pages_controller.rb
例えば、 pages#view が呼ばれた時だけ fluentpost するようにすれば、id と時間が db に格納されます。
class PagesController < ApplicationController Fluent::Logger::FluentLogger.open(nil, :host=>'localhost', :port=>24224) before_action -> { fluentpost(params[:id]) }, only: [:view] # routes.rb で params[:id] 受け取るようにしてます def fluentpost(id) Fluent::Logger.post("page.access",{ "page_id" => id, "access_time" => Time.current.to_i }) end
実行
$ cd /home/user/app/pageapp $ fluentd -c ./config/fluent/fluent.conf -vv &
問題なければ、これで実行されます。この状態で 各ページにアクセスしてみてください。
ページアクセス後、 MySQL から確認してみます。
$ mysql -u mysqlname -p -D access_log mysql> select * from page; +-----+---------+-------------+ | id | page_id | access_time | +-----+---------+-------------+ | 343 | 1 | 1413172076 | | 344 | 5 | 1413172429 | | 345 | 1 | 1413172438 | | 346 | 1 | 1413172483 | | 347 | 1 | 1413172735 | | 348 | 3 | 1413172800 | | 349 | 5 | 1413172803 | | 350 | 5 | 1413173082 | | 351 | 1 | 1413173177 | | 352 | 3 | 1413173216 | | 353 | 7 | 1413177045 | | 354 | 1 | 1413177811 | +-----+---------+-------------+ 12 rows in set (0.00 sec)
問題があるときは、ログが挿入されていない / ログに赤字でエラーが出ると思います。
色々設定して間違ったのは、
- fluent.conf ファイルの keynames がきちんと設定されていない
- fluent.conf の sql 文が間違っている
- fluent.conf のソケットが間違っている
- mysql の username, password が間違っている
- コントローラー内のパラメーターが存在していないので nil になっていて insert できない
などでした。
アクセスログから、ランキングを作る
ランキングを呼び出したいコントローラーから以下のような ActiveRecord を書けば呼び出せます。
リストだけ取り出すこともできるんですが、ランキングの順番に取り出すためのコードは以下の様に field(id, 1,5,3,7,....)
のようにしてあげる必要があります。
# select page_id, count from page order by count(page.page_id) desc # 1,5,3,7 ranked_id_list = Access.group(:page_id).order('count(page.page_id) desc').count.keys sorted_rank = ActiveRecord::Base.send(:sanitize_sql_array, ["field(id ,?)", ranked_id_list]) # select * from page order by field(id, 1, 5, 3, 7) @popular_pages = Page.order(sorted_rank)
これで終わり!
実際に該当の アクションにアクセスしてみて確認してみてください。
その他
本来、こういう処理をする場合、 MySQL をランキングとして使うのは負荷がかかりあまりよくないですが、アクセス数がそこまであるわけじゃないし、MySQL じゃないものの学習コストをそこまで取っていられないっていう場合だったので今回これを選択しました。
Fluentd を使うことによって、こういった選択も MySQL ではない DB への移行だとか、ログ収集用のサーバーの変更がしやすくなるはずです。
前日分のアクセス数は不要なのでDBから削除してテーブル肥大化を防ぎましょう。また集計の方法も、 アクセスした時間で where 句追記するといったこともすれば、時間ごとのランキング等も作れそうですね。
さらには、テーブルを日毎にシャーディングしたり等も今後のサイトの発展次第では設定しなくてはならない気がしますが、今はミニマムな実装だけで終わることにします。
参考
Fluentd | Open Source Data Collector
tagomoris/fluent-plugin-mysql · GitHub
fluent/fluent-logger-ruby · GitHub
Fluentdで、RailsのログをS3に保存する方法 - Qiita
【Ruby】gemでinstallできたはずが読み込んでいない - うみやま亭
special thanks
@hase1031 hase1031のブログ