ウェブサービス開発におけるイベントデータモデリングの実践ガイド
はじめに
こんにちは、UbieでBIエンジニアをしているyagです。
Ubieでは複数のチームが主体的にデータ分析パイプラインやデータマート整備を行い、データエンジニアやデータアナリストに閉じない形でデータ分析の民主化を達成しています。それを支えるのは共通して構築しているデータモデリングの技術です。具体的にはデータマートの論理的な分割であったり、サービスやビジネスに対応した形でのマートのオーナーシップの明確化、意図しないデータ毀損が発生しないような各種制約など、データから安定して価値を生み出す基盤が構築されています。
そのようなデータモデリングの中でも、特にウェブサイト上でのユーザの行動に着目し分析するための取り組みをイベントデータモデリング(Event Data Modeling)と呼びます。私が主に分析対象としているウェブアプリケーションの分析では、こうしたイベントデータをいかに分析可能にするかという部分に着目して日々データマートを整備し分析を行っています。
この記事では、イベントデータモデリングの基本概念から実践的なログ設計のポイントまで、実際の業務で私が気をつけている観点を中心に解説します。
イベントデータモデリングとは
イベントデータモデリングについて、Snowplow社のブログ記事では次のように定義しています。
Event data modeling is the process of using business logic to aggregate over event-level data to produce 'modeled' data that is simpler for querying.
https://snowplow.io/blog/introduction-to-event-data-modeling
つまり「イベントレベルの生データにビジネスロジックを適用して集約し、クエリしやすい形にモデル化されたデータを生成するプロセス」ということです。
イベントの基本概念
ここで扱うイベントとは、ユーザがウェブサイト上で行うひとつひとつの能動的または受動的な行動を指します。具体的には、
-
view
: 画面上の要素を見る、もしくは目に入る (ページの一部コンポーネント、ページ全体など) -
click
: 画面上の要素を押す (リンク、ボタン、トグルなど) -
input
: フォームに文字を入力する
といった操作が、それぞれ最小単位のイベントとしてログに記録されます。あくまでユーザが実際に行った行動自体が記録されるため、それらがどういった文脈や意思を持って行われたかはわかりません。
ユーザ行動の解釈は、一つ以上のイベントログをユーザごとに時系列で集約して初めて捉えることができます。例えば以下のようなシンプルなユーザのウェブサイト上の行動には、幾つもの単一イベントが複数組み合わさって成り立っています。
-
ユーザがECサイトで商品を商品カゴに入れて決済し購入する
- 商品を閲覧するイベント、商品を商品カゴにいれるイベント、決済ボタンをクリックするイベント、etc.
-
ユーザがSNSで記事やツイートを投稿する
- 入力ボタンをクリックするイベント、文字を入力するイベント、公開ボタンをクリックするイベント、etc.
-
ユーザが広告をクリックする
- ユーザの端末上に広告が表示されるイベント、ユーザが広告をクリックするイベント
イベントデータモデリングでは、この生データの集まりと実際のフロントエンドでユーザが体験するビジネスロジックを対応させます。例えば「このユーザは商品購入フローのユーザ情報入力画面まで到達したのち離脱した」といった文脈をイベント系列に付加し、最終的にセッション単位やユーザ単位、ファネル単位など分析に適したモデル(分析用のテーブル)を構築します。
イベントの構造
イベントの最小単位であるログは「Object(操作対象)」と「Action(操作内容)」で表現されます。
- Object: ページ、ボタン、入力フォームなど、ユーザが操作する対象
- Action: ページを読み込む、ボタンを押す、フォームに文字を入力するなど、具体的な操作内容
イベントログには、このObjectとActionが明確にわかるような情報を記録します。また、それに加えてクエリパラメータのようなObjectに紐づくメタ情報も同時に記録する必要があります。例えばフロントエンドから送られるイベントログを例示します。このそれぞれの行が一つのイベントであり、これらのイベントログだけを格納するテーブルがデータマートの起点となります。
user_id | object | action | context | datetime |
---|---|---|---|---|
user1 | search_button | click | search_query:"xxx sale" | 2025-06-01 10:00:00 |
user1 | search_page | view | sort:"relevant",num:10 | 2025-06-01 10:00:10 |
user1 | open_description | click | item_id:"xxx" | 2025-06-01 10:01:00 |
user1 | item_description | view | item_id:"xxx" | 2025-06-01 10:05:00 |
user1 | add_to_cart_button | click | item_id:"xxx" | 2025-06-01 10:05:30 |
user2 | search_button | click | search_query:"yyy" | 2025-06-01 11:00:00 |
user2 | search_page | view | sort:"price",num:10 | 2025-06-01 11:00:10 |
これをデータ分析しやすくするために集約処理すると、以下のような分析テーブルになります(カラム名は簡略化しています)。
user_id | funnel_search | view_item_list | funnel_add_to_cart | funnel_purchase |
---|---|---|---|---|
user1 | true | item_xxx | true | false |
user2 | true | NULL | false | false |
これを見れば商品購入ファネルにおいて、user1が商品カートまで到達したが購入までに至らなかったこと、user2が検索ページで離脱したことが一目瞭然となり、またカラム単位でのユーザ数の集計(例えばcount distinct)によってファネルの各レイヤーごとの到達数やCVRを算出することができます。
このようにイベントデータを適切に集約しモデリングすることで、ユーザ行動の傾向やコンバージョン経路の分析が可能になります。
分析のための実践的なアプローチ
効果的なイベントデータモデリングを実現するには、単にログを記録するだけでなく、後の分析を見据えた設計思想が必要です。ここでは、フロントエンド側でのログ出力や分析時の方針で特に重要となるポイントを解説します。
表示と描画を区別する
画面上で実際にユーザが目にしたかどうかを正確に把握することは、UX分析において極めて重要です。ページがブラウザ上で読み込まれてDOM上に要素が存在する状態と、ユーザの端末画面上に実際に表示されている状態とでは、集計において意味合いが異なります。
例えば、あるバナー広告が画面の下部にある場合は、ページ読み込み時点でHTMLとしては描画されていても、ユーザがスクロールするまでは実際には見られていません。この違いを意識せずにログを設計すると、バナーのCTRを正確に測ることができません。そのため、表示ログと描画ログは明確に分けておく必要があります。
また、命名規則も気を配る必要があります。イベント名にview/display/render/scroll_inなど似たような意味の言葉が乱立しがちなので、チーム内で統一した語彙を定めておくことが重要です(経験談)。
イベント系列の開始時点と終了時点を明確化する
イベントの系列として表現されるユーザ行動には、必ず開始と終了の2点が存在します。これらの時刻をイベントデータ集約時には明確に保持し、集計時にどの時点を起点として分析するかを意識しておくことが重要です。
例えば、サイト内回遊の分析であればサイト来訪時点の時刻を、購買行動の分析であれば商品が購入された時刻を基準とするなど、分析目的に応じて適切なイベント時刻を選択します。この判断を曖昧にしておくと、後から「この数値はいつ時点のイベントを基準に計算されたものか」がわからなくなり、混乱の原因となります。
実際に私が遭遇したケースを紹介します。ウェブサイトのあるサービスが利用されたかどうかを分析していたのですが、データマートには
- サービスの登録画面に到達するまでのページ遷移とそのユーザ情報が格納されたイベントテーブル
- そのサービスに登録したユーザのバックエンドのDBに格納されていたテーブル
の2つにまたがって分析する必要がありました。それぞれのテーブルを用いてファネル分析やコンバージョン分析用途で日次のコンバージョン数を出していたのですが、前者のテーブルではサービス到達画面に到達しないユーザも存在するため必然的にページ初回訪問の時刻を使うことになりますが、後者はサービス登録時点での時刻のみしか保持していません。すると、初回訪問から日をまたいで翌日にサービス登録画面に到達したユーザは、前者と後者で違う日付でコンバージョンしたこととなり、結果として細かな数値のズレに繋がりました。データの可視化の際には、こうしたデータが集計される背景や集計軸を明確にしておかなければいけないと実感しました。
冪等性を担保する
イベントログを集約する際には、ユーザの操作で同じアクションが複数回実行される可能性を常に考慮する必要があります。例えばボタンクリックでは、ブラウザバックやページ遷移のようなユーザ行動に起因するものもあれば、ネットワークの遅延などで意図せず同じボタンが複数回押されることもあります。これはページ表示も同様で、ユーザがウェブサイト内を回遊するときに何度も同じページを訪れることは頻繁に起こりえます。
イベントログからユーザ行動へと集約する過程で、どのイベントが意味を持つか、または持たないかを明確に意識する必要があります。例えば、商品をカートに入れてから購入までのファネル分析を行う場合、同じ商品を複数回カートに追加したイベントがあったときに、たまたまログが重複して複数回イベントが飛んだのか、同じ商品を2個買いたかったのか、売り切れの商品だと気づかずに何度もボタンを推していたのか、意味合いは異なるはずです。一方で、ファネル分析などでは入口から出口への到達という観点では、何度同じページを見ていたとしても最後に訪れたイベントだけ見ていれば良い場合もあります。
こうした重複を適切に処理するため、分析マート作成時にビジネスロジックの観点で重複排除の方針を決めておく必要があります。具体的には、以下のようにGROUP BY
句もしくはQUALIFY
句で集約することが多いです。
# 複数のイベントを許容する場合
SELECT
user_id,
LOGICAL_OR(url = "/search") AS has_view_search,
LOGICAL_OR(url LIKE "/item/%") AS has_view_item_description,
LOGICAL_OR(url = "/purchase") AS has_view_purchase,
MIN(event_time) AS first_event_time,
MAX(event_time) AS last_event_time,
FROM
page_view_event_table
GROUP BY
user_id
# 最初/最後のイベントを明示的に取得したい場合
SELECT
user_id,
CASE
WHEN context.type = "button" THEN "ボタン"
WHEN context.type = "text" THEN "テキスト"
END AS link_type
FROM
click_event_table
QUALIFY
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time DESC) = 1
ロジックを分析クエリ側でカバーしない。ユーザ行動すべてをイベントログとして取得しておく
フロントエンドの開発では「条件Aかつ条件Bの時にコンポーネントXを表示する」といったUI制御ロジックが実装されることがあります。こうした場合にログとしては条件Aと条件Bのログだけを送っておいて、分析クエリ側で condition_a AND condition_b AS view_component_X
といった形で表示されたことを集計することも可能です。しかしこうした方法を取るのではなく、イベントログとして「コンポーネントXが表示された」というログを送信しておくべきです。フロントエンド側の掲出条件は容易に変更される場合がありますし、変更された場合には「いついつまでの条件はこれで、いつからの条件はこれ」といったように分析クエリ側がより複雑になる可能性があるからです。
解決策としてはシンプルに、コンポーネントXが実際に表示された時点で、(Object:component_X, Action:viewed)
といったイベントをログとして出力する方法が確実です。これにより、UI制御ロジックがどのように変更されても、実際の表示状況を正確に把握し続けることができます。
こういったケースはフロントエンド実装時にログを出し忘れて応急処置的に行われることが多いですが、後々負債化することが確実なのでなるべく早期検知と対応が望ましいでしょう。
ログ欠損に適切に対応する
フロントエンドから送られるイベントログは、バックエンドのDBに保存されるトランザクションなどのログと比較して、欠損などが発生しがちでかつ修復が難しいです。そうしたケースを一定許容しながら分析すると同時に、なるべく欠損などを感知できるようにデータマートでのテストなどで対応すべきでしょう。
過去に私が遭遇したケースでは、イベントログに含めるコンテキストに画像ファイル(のBase64エンコードした文字列)が混入し、ログ自体のファイルサイズが巨大になってしまったことで確率的にログ送信がエラーとなるケースがありました。そうなると、集計時になぜかとあるイベントの件数が少ないということが発生します。このときは早めに気づくことができてすぐに対応できましたが、イベントログのバリデーションやテストの必要性を実感しました。
まとめ
イベントデータモデリングは、ウェブサイト上でのユーザ行動を定量的に測定し分析するために必要な基盤技術です。そのためには、ログを送る側のフロントエンドとログを受け取り加工するデータマートの両方にまたがって一貫した設計をする必要があります。その実現にはビジネス要件の理解とチーム間の密な連携が不可欠であり、常にアップデートし続ける画面のユーザ体験や実装に適用しなければいけません。データエンジニア/アナリティクスエンジニア側からはあるべきデータマートの姿から逆算して、フロントエンドエンジニアに適切な形でのログ仕様の策定と構築への協力を働きかけることが重要だと言えます。
Discussion