こんにちは、Wantedly エンジニアの竹野(@Altech_2015)です。突然ですが、去年から断続的に取り組んでいた Wantedly のデータ分析基盤について紹介しようと思います。
やったことは以下の3つです。それぞれ他のエンジニアを一人くらい巻き込んで1-3日で取り組みました。
- BigQuery の導入
- 分析結果をチームで共有する仕組み
- クエリをリポジトリで管理してスケジューリングする
BigQuery の導入
もともと Wantedly ではサービス開始当初からデータウェアハウスにログデータを貯め続けていました。Wantedly はエンジニアが数字にも責任を持つ会社なので、そういったデータをエンジニアがクエリを書いて分析しながら、デザイナーと一緒にサービスを開発しています。
また、クエリをスケジューリングして結果を分析用 DB に書き出し、アプリケーション・サーバーから利用するといったことも同時に行っています。
ただ、当時は Hive(Hadoop 上で実行される)でクエリを書いていたので、クエリの結果が返ってくるまでの時間がどうしても長くなっていました。その結果、チームでは以下のような問題を抱えていました。
- 新しく学習する人のコストが高い
- 仕様を詰めて開発もしなければいけないエンジニアの時間が分析だけに大きく取られてしまう
どうにかしなければいけないなと思い、Google が提供する BigQuery を試したところ既にあるユースケースでかなり高速に結果が返ることが分かりました。
ただ全てのデータを一気に移行するのは大仕事なので、まず指定したデータセットだけを日時で同期する exporter を作成し、利用価値の高いデータセット・利用価値の高い最新のデータから同期されるようにしました。
# このリストに追加すると BigQuery で利用できるようになる
EXPORT_TABLES = %w[
prod.ios_action_log
...
]
導入してみると、速度感の違いから、アドホックな分析は BigQuery で行われるようになっていきました。
あとでより詳しく調査したところ、もともと Hadoop の元になった MapReduce と言う技術はバッチ用途に設計されたもので、数秒でクエリが返ってくるような分析には不向きであり、そうった背景から Dremel と言う BigQuery の元になったクエリサービスが Google 社内で立ち上がった、ということが分かりました。
An Inside Look at Google BigQuery
分析結果をチームで共有する仕組み
BigQuery のバックエンドは非常に素晴らしいですが、用意されているクエリ・フロントエンドは社内で利用するにはいくつか足りない機能がありました。
特に、大本の設計の部分で、クエリやジョブの実行がそれぞれのアカウントに閉じていて、他人のクエリが見れないのは問題でした。今まで同じ部屋で作業していたのが、いきなり全員密室で作業をするようになったような感じです。
更に、Wantedly では GitHub Issue を使って分析や問題定義を進めることが多いのですが、そういったときに分析のクエリと結果を共有したり、必要ならクエリを少し書き換えて再度実行できる、といったことは重要でした。こういったものがないと、そのとき必要な数字は出せても、一ヶ月後に同じ数字を出して比較する、と言ったことができなくなりますし、新しく入ってきた人の学習が困難にもなります。
そこで、GitHub 認証を使ってアクセスできる BigQuery フロントエンドを社内ハッカソンで作成しました。
これにより、ジョブを URL でシェアしたり、他の人のクエリをコピーして利用できるようになりました。
また、副産物ですが、自分たちでフロントエンドを作成することで細かなコントロールもできるようになりました。
例えば、BigQuery のクエリ言語は Legacy SQL と Standard SQL の二つがあり、用意されているフロントエンドは Legacy SQL が標準だったのですが、そのときは出たばかりであった Standard SQL をデフォルトにすることができました。これにより、二つのクエリ言語が社内で使われるという状況を防ぐことができたと思います。
少し脇道にそれますが、アドホックな分析でもなくアプリケーション・サーバーからも使わないもの、つまり意思決定のために継続的に見ていきたい数字もあります。元々これには DOMO というサービスを利用していたのですが、こちらも BigQuery につなぐことが多くなりました。
クエリをリポジトリで管理してスケジューリングする
ここまででアドホックな分析はほぼ不便なく出来るようになったのですが、そうなってくると、同じ BigQuery でデータを集計し、アプリケーション・サーバーから利用したいと思うようになりました。
別に BigQuery を叩くAPI・ライブラリはあるので日時で書き出す等のタスクはいくらでもやりようはあるのですが、いくつかの観点からそれではまずいと思うようになりました。
- アプリケーションで使われるクエリは、サーバーのコード同様にレビューしてリポジトリで管理したい
- クエリの中で `CURRENT_TIMESTAMP()` のような副作用は使わず、引数として注入したい
- 結果の書き出しは APPEND ではなく UPDATE を使って可能な限りべき等にしたい
2 や 3 は、何かしら障害が起きてジョブが正常に実行できなかったときに再実行を容易にするためのものです。1 については逆で、元々 Web ブラウザー上でクエリを作成していたのですが、「どういう目的で作られたのか」「今も利用されているのか」「その後変更はされたのか」といったことが分からず、何かある度に無駄なコストがかかっていました。
こうったことは Github でコードを管理していれば分かるようなことなので、リポジトリで管理する判断にしました。
具体的には、以下のような DSL で記述したファイルを作成して Pull Request を出します。
export do
table :user_daily_metrics
columns %w[user_id date page view]
mode :update, %w[user_id date]
end
schedule do
frequency :daily
end
run :bq, <<QUERY
SELECT
user_id, DATE(time, '+9') date, COUNT(*) AS page_view,
FROM prod.request_log*
WHERE
_TABLE_SUFFIX = FORMAT_TIMESTAMP("%Y%m%d", TIMESTAMP_SUB(_WT_SCHEDULED_TIME, INTERVAL 1 day), '+9')
GROUP BY user_id
QUERY
_WT_SCHEDULED_TIME
は実際には BigQuery には存在しない変数で、ジョブがスケジューリングされた時刻がクエリ組み立て時に注入されます。
この Ruby ファイルが実際にどうスケジューリングされるかですが、ちょうどこのときは Wantedly で全面的にインフラとして利用している Kubernetes が 1.4 になってスケジューリングの仕組みが入ったため、そこに乗せることにしました。
上記の Ruby ファイルはマージすると以下のような Kubernetes のリソースを表現するYAML ファイルに CI 上で変換され、ジョブとして登録されます。
apiVersion: batch/v2alpha1
kind: ScheduledJob
metadata:
name: user-daily-metrics
labels:
namespace: general
basename: user-daily-metrics
role: job
namespace: analytics
spec:
schedule: "10 19 *"
startingDeadlineSeconds: 30
concurrencyPolicy: "Replace"
suspend: false
jobTemplate:
...
Kubernetes に乗せたことは当初自分が思っていたよりもメリットが大きく、インフラに感謝しました。
- kubectl コマンドでジョブのスケジューリングが確認できるので動作が理解しやすい
- ジョブの成功・失敗といったログを残したり確認することを自前でやる必要がない
- クラスタリングされた複数のマシン上で実行されるので、効率化・冗長化の恩恵を受けられる
ここまで主に BigQuery に絞って紹介しましたが、Ruby で書かれているので通常の ActiveRecord モデルを使って DB のデータを集計するといった用途にも使われていたりします。
run :proc, -> (scheduled_time) {
counts = VisitDb::Project.where(...)
..
# Return array of array or array of hash
}
今後の課題
ここまで作成したシステムは割とシンプルな形のものでしたが、多くのユースケースに対応できました。
ただ利用が広がるにつれて汎用化したい箇所も出てきています。例えば、ジョブの書き出し先をより柔軟に選べるようにする(e.g. ElasticSearch に書き出したい)と言ったことで、Ruby の DSL を拡張して上手く既存のツールにつなぐことが考えられます。
また、僕自身は最近は機械学習などより数学的な手法も取り入れた募集の推薦に取り組んでいるのですが、そうしたケースではまた違った視点でのデータの受け渡しを考える必要があるなと感じています。
今後も適宜このあたりは改善していきたいと思っています。こういった基盤の作成が好きなエンジニアも Wantedly では募集していますので興味ある方は是非話を聞きに来てください。