こんにちは、'16新卒入社で、Analyticsグループ所属の田中です。 仕事ではデータ分析基盤や機械学習システムの開発・運用を行っています。
今回はデータ分析基盤における「便利カラム」にまつわる問題と、それを解決するためのアーキテクチャについてご紹介します。
リブセンスのデータ分析基盤
みなさんの会社では、サービスのデータ分析をどのように行っていますか?
リブセンスにはデータ分析・活用の文化が根付いており、ディレクターや営業職の社員までもがSQLを用いてKPIのモニタリングや施策の評価を行っています。 分析活動を支えるために、社内では "Livesense Analytics" という全社横断のデータ分析基盤を構築・運用しています。
このような組織が形成されるまでのポイントについては次の資料で解説しています。
営業さんまで、社員全員がSQLを使う 「越境型組織」 ができるまでの3+1のポイント | リブセンス | PPT
Livesense Analyticsの全貌については次の資料で紹介しています。
リブセンスのデータ分析基盤の全貌 - Speaker Deck
少人数のチームで基盤を開発・運用するため、システムはAWS上に構築しています。 中核となる分析DBにはAmazon Redshiftを利用しており、数億件のレコードを集計するようなSQLを数秒から数分で実行することができます。
集計簡単化のための「便利カラム」
分析DBにはアプリケーションデータだけでなく、PVログのようなユーザの行動ログも保存しています。 これらのログにはURLや訪問日時などの生データに加えて、それらを加工して得られる二次データを付与しています。 この二次データ、いわゆる「便利カラム」には次のような値が含まれます。
- URLをサービスの機能ごとに分類したページ種別
- 連続する複数のPVをまとめるセッションID
これらのデータがあることで、ページ毎のPV数や複数PVにまたがるユーザ行動の持続時間の集計が簡単になります。
問題点1: 便利カラムの値の修正ができない
データ分析基盤を継続的に運用していると、しばしば便利カラムの値を修正したいことがあります。 例えば「これまでは前回PVから30分以上経過すればセッションが切れたとみなしていたが、その閾値を15分に短縮したい」といったケースです。
しかし、一旦DBに投入された過去データについては修正が難しい場合があります。 これは便利カラムの値がクライアントサイドやログ収集サーバで生成されているときに起こります。
以前のLivesense Analyticsでは、セッションIDはCookieに保存された前回PVのタイムスタンプをもとに生成されており、ページ種別はログ収集サーバで判定されていました。 このため、分析DBにログが投入されてからセッションの切れ目を変更するといったことは事実上不可能でした。
この問題の主な要因は、生データから二次データを生成するロジックの変更が想定されていないことです。
セッションIDなどの二次データは、URLやタイムスタンプなどの「事実」とは異なり、生データに対する「解釈」から生まれます。 セッション持続時間の閾値の例のが示すとおり、この解釈ルールは変更される可能性があります。 そのことが事前に想定されていなければ、あとからロジックの変更を適用することが難しくなってしまいます。
解決策1: バッチ層の導入
ロジックの変更を過去のデータに遡って適用するには、もとの生データから二次データを何度でも再生成できる仕組みが必要です。
そこで生データをAmazon S3などのストレージに保存し、バッチ処理で二次データを再生成する層を導入します。
通常の場合はバッチ処理を1日1回程度定期的に実行し、その都度に新たな生データから二次データを生成してDBに投入します。
二次データの生成ロジックに変更が生じた場合、バッチ処理の実装を変更し、過去の全ての生データから二次データを再生成してDBの中身を入替えます。
後者では蓄積された大量の生データを処理する必要がありますが、あらかじめHadoop MapReduceのような分散処理基盤を利用しておけば、処理の実装を切り替えずに済みます。 Amazon Elastic Mapreduce (EMR)などのオンデマンドサービスを使えば、必要に応じてクラスタを立ち上げられるため、コストを抑えることができます。
問題点2: バッチ処理の複雑化とパフォーマンス低下
バッチ層の導入によって「便利カラム問題」は解決できそうです。 しかしこの仕組みを長期的に運用しているといくつかの問題が生じると考えられます。
まず、生データが細切れになっているとバッチ処理のオーバーヘッドが増大してしまうことがあります。 ログが短い間隔でバッファリングされている場合、生データは数MB〜数十MB単位の細切れになったオブジェクトとして蓄積されます。 これらの大量のオブジェクトの読込にオーバーヘッドがかかると、全データのバッチ処理のパフォーマンスが低下してしまいます。
また、ログ収集時の都合により生データの構造がバッチ処理に適していないことがあります。 例えばユーザのマウスイベントを複数件束ねてログ収集サーバににPOSTしている場合、イベントを集計する処理を行う前にそれらを複数のレコードに分解する必要があります。
個々の問題はそれほど複雑には見えないかもしれませんが、長期間にわたって生データをログ収集時のまま保存していると、いざ全件処理を行いたいときに時間がかかったりエラーが発生といった問題が起こり得ます。
解決策2: マスタデータ層の導入
バッチ処理の複雑化やパフォーマンスの低下を招く主な要因は、バッチ層が次の2つの処理を同時に行っていることです。
- 生データをバッチ処理に適した形に整形する
- 整形されたデータから二次データを生成する
特に1.は二次データの生成ロジックとは独立しており、本来データ収集時に一度行うだけで済むはずです。 そこで、生データに対する一度きりの加工処理を行うためのマスタデータ層を導入します。
このマスタデータ層では、生データを次のような性質を満たすよう加工します。
- 解釈によって変わることのない一次データのみを保持している
- オブジェクトの粒度や形式がバッチ処理に適した状態になっている
二次データの生成処理とは異なり、マスタデータ層では定期的な差分処理のみを考えればよく、過去ログに対する全件処理は通常発生しません。 マスタデータ層を設けることで、二次データの生成処理をシンプルに保ち、パフォーマンスを向上させることができます。
ラムダアーキテクチャに向けて
実は上記のアーキテクチャは、ラムダアーキテクチャと呼ばれるデータ分析基盤の設計指針を参考にしたものです。
lambda-architecture.net - このウェブサイトは販売用です! - lambda architecture リソースおよび情報
本来のラムダアーキテクチャでは、データがマスタデータ層・バッチ層を通って利用可能となるのを待たずに速報値の分析ができるよう、スピード層と呼ばれる別のデータフローを構築します。 しかし今回の要件ではリアルタイム性は求められない(前日までのログが利用可能であればよい)ため、スピード層は用意していません。
この記事では説明しませんが、ラムダアーキテクチャについて詳しく知りたい方には次の書籍をおすすめします。
O'Reilly Japan - スケーラブルリアルタイムデータ分析入門
バッチ層・マスタデータ層に使われる技術
さて、ここまでバッチ層・マスタデータ層のアーキテクチャについて説明してきました。 最後にこれを実現した技術スタックを簡単にご紹介します。
もともとの分析基盤をAWS上に構築していたため、新しく開発したバッチ層・マスタデータ層でもAWSのサービスを活用しました。 生データ・マスタデータを保存するストレージにはS3、分散処理基盤にはEMRを採用しました。
分散処理フレームワークにはApache Spark (2系)を採用しました。 実装言語にはSpark本体の実装にも使われているScalaを採用し、パフォーマンスと型安全性を両立させるためDataset APIを用いて処理を記述しています。
開発で得られた知見は以下のような記事でも紹介しています。
今後機会があれば実装に関するノウハウなども共有していきたいと思います。
おわりに
長くなりましたが、最後までおつきあいいただきありがとうございました。 今後もこのブログでは分析基盤やデータ活用に関する内容を投稿していきますので、どうぞご期待下さい。