NorikraでリアルタイムUU数表示を実現する
どーも、れもんです。みなさんNorikra使ってますか? えっ使ってない? まぁストリーム集計処理っていうのがなかなか使いどころ難しいですものねー。よっぽど速報値が必要な場面じゃないと出番がないし、ぼくもインストールしたけどほぼ使ってないという今日この頃です。
ということで! 今回はNorikraを使うとっかかりとして、最近リリースしたサービスのユニークユーザ数をリアルタイムに出したいなぁというお題をNorikraでやってみることにしました。
もともとそのサービスではネイティブアプリにGoogle Analytics SDKが入っててそれを使えば直近5分のリアルタイムユニークユーザ数は見えているのですが、サーバ側でもそれを計測したいということで、そこをNorikraでやってみることにしました。
理由は、よく出来たアプリだと幾つか画面を遷移していても通信しなくてよいように作られていて、クライアントサイドのリアルタイム計測とサーバサイドのリアルタイム計測はある程度差がでるのでは? ということでそこも可視化したいというところです。
ここからは実例を交えた説明に入ります。
まず、WebアプリからFluentd経由でNorikraへログを流すシステムがありますので、そこにクエリを仕掛けてリアルタイムなUU計測をやります。Fluent周りはざっくりとこんな感じになっています(実際のシステムとはタグ名等がちょっと異なります)。
- Webアプリから
app.activity
というtagでuser_id: 42
を含むようなレコードを流す - FluentからNorikraにactivityというターゲット名でレコードを流し込む
さて! あとはSliding Time Windowのクエリを書いてNorikraに設定すればOKです。こんな感じのクエリを書きました。どうやらGoogle AnalyticsのリアルタイムUUは過去5分で切っているらしいので、Sliding Time Windowの幅は5分にします。
`SELECT COUNT(DISTINCT user_id) AS active_user FROM activity.win:time(5min)` ```
で、これをin_norikraで取り込めばOKです。
実際にやってみると、おーすごいすごい、ちゃんとcountが上がっていくわー、Google Analyticsの数字と同じくらいになってるし大丈夫っぽいなーという感じです。
が! この方法には重大な問題があるのです…
それは何かというと、ドキュメントの `win:time` にも書かれているとおり、<q>Output events are generated for every input events.</q>ということで大量のユーザがガンガンくるサービスだと入力のあったイベント数と同じだけのイベント数でactive_userのレコードが吐き出されてしまうのです…。
このまま放置してディスク容量のアラートが上がってインフラチームに見つかったら椅子が飛ぶ可能性があるので、ここはどうしても間引きたいです。5minのwindowをslideさせつつ10秒に1回、1個だけレコードがでるようなクエリに出来ないかなぁといろいろ調べてみたのですが、どうもそれっぽい方法が見つかりません。と困ってTwitterでつぶやいてみたところ、モリスさんから的確な助言が!
<div class="tweet">
<blockquote class="twitter-tweet" lang="ja"><a href="https://twitter.com/acidlemon">@acidlemon</a> ぼーっとしてて読み違ってました。time_accum はダメですね。LOOPBACK にして time_batch + LIMIT 1 がいいのかな
— tagomoris (@tagomoris) <a href="https://twitter.com/tagomoris/status/519771805012078592">2014, 10月 8</a></blockquote>
</div>
なるほど! LOOPBACKって何に使うのかよくわからんので完全にスルーしていたのですが、そういう使い方があったんですね。
ということで、最終的に作ったのはこんなクエリの2本立てです。
<li>最新の5分のUUを出してactive_user_5minというターゲットに投げ込むクエリ
<ul>
- Query: `SELECT COUNT(DISTINCT user_id) AS active_user FROM activity.win:time(5min)`
- Group: `LOOPBACK(active_user_5min)`
- Name: `sliding_active_user.5min` (なんでもよい)
</li>
<li>active_user_5minから10秒おきに1回、値を取り出すクエリ (用途上、最大値を取ります)
- Query: `SELECT MAX(active_user) AS active_user FROM active_user_5min.win:time_batch(10sec)`
- Group: `app`
- Name: `suppress_active_user.5min` (fluentで取り込むタグの一部になる)
</li>
</ul>
後者のクエリが10秒に1回のペースで現在の5分間UUを出してくれるので、これをin_norikraで取り込みます。
<source>
type norikra
norikra my-norikra:26571
<fetch>
method sweep
target app
tag query_name
tag_prefix norikra.app
interval 5s
</fetch>
</source>```
この設定で、Fluentに norikra.app.suppress_active_user.5min
というタグで {"active_user": 1234}
というようなレコードが流れてきます。あとはこれを適当なところへ流してやればOKですね!
流し先はいろいろあると思いますが、RedisにsetするとかWebSocketに書き出すとかzabbixやnagiosやGrowthForecast or Forecuslightに投げるとかMackerelに投げつけるとかそんな感じでしょうか(まだそこ作ってないのでその辺の知見はありません)。
今回のまとめ: NorikraでSliding Time Windowを使うと「直近○分」という集計ができて便利だけど、レコード量が大変なことになります。そこで、Sliding Time Windowのクエリ結果をLOOPBACKで一度別ターゲットに流し、そのターゲットにtime_batchクエリを定期的に回してイベント数を間引くと環境に優しく集計できますね!