RUM サンプリングのすゝめ

## はじめに
こんにちは、Datadog Japan で Sales Engineer をしている [**AoTo**](https://qiita.com/AoTo0330) です。

この投稿は **[<font color="#6495ED">AoTo Advent Calendar 2023</font>](https://qiita.com/advent-calendar/2023/aoto) 16日目**の記事です。

**Real User Monitoring(RUM)** を利用されたことがある場合、コストの観点でサンプリングを余儀なくされたことがある方も多いのではないでしょうか？

今回は**統計学的観点**を交えて、一定の期間内で有効なデータを取得するために考慮すべき内容を考察していきます🐶

## サンプリングの理由
**Real User Monitoring(RUM)** は実際のユーザーから実際のデータを受け取り、ユーザーの行動やアプリケーションのパフォーマンスを分析をするソリューションです。そのため、ウェブアプリケーションやモバイルアプリケーションなどのクライアントサイドに SDK を組み込むことなどで実現され、すべてのアプリケーションユーザーのデータを収集します。

大規模なウェブやモバイルアプリケーション(例えば SNS やメディア・モバイルゲームなど)の分析を始める際、その全量のデータは膨大となることが予想されます。こうした膨大なデータをすべて保管し可視化・分析を行うことは、<INS>有償のツールを利用する上では**コストの懸念**</INS>が、<INS>無償のツールであっても**パフォーマンスの懸念**</INS>が挙げられます。

こうした背景から、コスト・パフォーマンス最適化を理由として、RUM によって取得されるデータを**サンプリング**し、予算内かつ効率的に実際のユーザーに則したデータを用いて分析を行うことが重要となってきます。しかし、サンプリングを行うことで収集されたデータが<INS>実際のユーザーに則した特性を表さなくなる</INS>可能性があります。

サンプリング方法とサンプリングレートを前提として、RUM データの収集・分析用途に応じて、どのようにサンプリングすることで統計的に信頼性があるデータとなるのかを考える必要があります。

## サンプリングの手法
市場行動調査をはじめとする統計学的な手法としては、大きく「**確率的サンプリング**」と「**非確率サンプリング**」に分けられます。

**確率的サンプリング**は、母集団から無作為に一定の確率で抽出する方法です。この時、すべての標本は平等にサンプルの一部となる機会がある必要があります。

**非確率サンプリング**は、母集団が現実の特性を表していないことを前提に、特定の条件で標本を抽出します。この時、抽出する際には現実の特性を踏まえて、母集団から標本を抽出する必要があります。

RUM データの場合は、一般的にはすべてのアプリケーションユーザーのデータを取得することができるため、確率的サンプリングによる平等な標本抽出が行われます。[^1]
[^1]: 近年では Cookie データの取得を容易に拒否できたり、クライアント側でユーザーデータを送信しないように設定ができるため、技術リテラシーが高いユーザーのデータほど欠損する可能性が高いとも考えられます。

ちなみに、現在のところ Datadog RUM ではサンプリングレートに応じて、確率的サンプリングを行います。Datadog RUM におけるサンプリングの考え方は「[Best Practices for RUM Sampling](https://docs.datadoghq.com/real_user_monitoring/guide/best-practices-for-rum-sampling/)」で一部伺えます。

## 統計的な指標
こうしてサンプリングされた標本データが母集団の特性を示しているか、つまり統計的に信頼性がある・有意であるかは、**標本誤差**という指標で表されます。統計誤差の算出方法の詳細については割愛しますが、慣習的に標本誤差は±5%以内であることが望ましいとされています。[^2]
[^2]: 近年、統計学的にはこの慣習的な指標を見直す動きが出てきていますが、今回は慣習に習って考察します。信頼区間は95%水準を想定しています。

この±5% 以内という標本誤差の指標は、母集団が大きくなっても400近くのサンプルサイズが取得できることによって担保されます。あくまでも端数を切り上げた参考値ですが、母集団とサンプルサイズによる標本誤差は以下の表のように推移します。[^3]
[^3]: SurvayMonkey「[統計的に有効な結果を導き出すために必要な回答数とは？サンプルサイズを決める方法](https://jp.surveymonkey.com/curiosity/how-many-people-do-i-need-to-take-my-survey/)」より引用

| 母集団 | ±3% | ±5% | ±10% |
| --- | --- | --- | --- |
| 500 | 345 | 220 | 80 |
| 1,000 | 525 | 285 | 90 |
| 3,000 | 810 | 350 | 100 |
| 5,000 | 910 | 370 | 100 |
| 10,000 | 1,000 | 385 | 100 |
| 100,000 | 1,100 | 400 | 100 |
| 1,000,000 | 1,100 | 400 | 100 |
| 10,000,000 | 1,100 | 400 | 100 |

この表からも、分析する時間軸や対象のサンプルサイズが400以上あることで、統計的に有意な分析結果を得られるということがわかります。つまり、母集団が500の場合は44%、1,000の場合は28.5%、5,000の場合は13.5%、10,000の場合は3.85%、100,000の場合は0.4%のようにサンプリングレート(確率的サンプリングを行う確率)を設定できます。

## サービスの特性
上記サンプリングレートに加えて、実際には母集団の大きさの他に、分析をする対象によってサンプリングレートを決定する必要があります。

月間で1,000,000セッションのアクセスがあるウェブアプリケーションでも、それを15分間あたりに統計的に有意な値を取りたいとなると母集団は15分あたりに350程度のため、サンプリングレートは50%程度が下限となります。
逆に、月間10,000セッションのアクセスがあるウェブアプリケーションに対して、月次のパフォーマンス分析ができれば良いということであればサンプリングレートは5%ほどまで下げられます。

このように、RUM を想定して分析をする時間軸と月間セッション数で表を作成すると、以下のようなサンプリングレートが与えられます。[^3]
[^3]: サンプリングレートの特性上、端数は切り捨て・切り上げをして元のデータを計算しやすい50%, 33%, 25% 10%, 5%, 1% のような値を利用しています。あくまでも参考値としてご参照ください。

| 月間セッション数 | リアルタイム(15分) | モニタリング(1時間) | 分析(1日) | レポート(1ヶ月) |
| --- | --- | --- | --- | --- |
| 500 | 100% | 100% | 100% | 50% |
| 1,000 | 100% | 100% | 100% | 33.3% |
| 3,000 | 100% | 100% | 100% | 10% |
| 5,000 | 100% | 100% | 100% | 16.7% |
| 10,000 | 100% | 100% | 50% | 4% |
| 100,000 | 100% | 75% | 10% | 1% |
| 1,000,000 | 50% | 25% | 1% | 1% |
| 10,000,000 | 10% | 2.5% | 1% | 1% |

## 何を取得したいか
これまで RUM でアプリケーションユーザー全体やアプリケーションパフォーマンスの分析に焦点を当ててお話ししてきました。

ですが、これらだけではなく RUM の役割のもう1つの側面として、エラートラッキングが挙げられます。エラートラッキングは全体の内数%しか現れない事象を、どのくらいの頻度で補足するかという観点で考える必要があります。

月当たりに最低でも特定の発生確率以上のものをほとんど確実に取得する、といったユースケースの場合、前日のユースケースに比べて極端にサンプリングレートを落とすことができます。


一連の計算は下記の Pyhon コードを利用して計算できます。
<details><summary>Python コード</summary>

```python:error_sampling.py
# 母集団のサイズ
population_size = <SIZE>

# 発生確率（%を小数に変換）
p = <RATE>

# 無作為抽出の割合（0%から100%まで0.5%刻みで変化させる）
sampling_ratios = [i/2000 for i in range(0, 2001)]

# 無作為抽出の割合ごとに成功確率を計算
success_probabilities = []

for sampling_ratio in sampling_ratios:
    # 無作為抽出の回数
    n = population_size * sampling_ratio
    
    # P(k = 0) の計算
    pk_0 = (1 - p) ** n
    
    # P(k ≥ 1) の計算
    pk_ge_1 = 1 - pk_0
    
    success_probabilities.append(pk_ge_1)

# 結果をテキストで表示
for i, ratio in enumerate(sampling_ratios):
    print(f"無作為抽出の割合: {ratio*100:.1f}% -> 1回でも成功する確率: {success_probabilities[i]*100:.2f}%")
```
</details>

一例として、月間1,000,000セッションあれば0.1%の事象で発生するエラーはサンプリングレート1%で確実に取得できます。

## 考慮事項
一連のサンプリングレートの考え方からもありますが、母集団のサイズを復元しづらい割合でサンプリングした際、母集団のサイズを直感的に把握しづらくなることが考えられます。

一般的に、RUM をサンプリングすると、RUM データを分析するバックエンドはサンプリングの有無を考慮せずユーザーの行動分析やアプリケーションのパフォーマンス分析を行います。これをレポートの際などは母集団のサイズに直して把握する必要がありますが、分数で表しづらい割合となる場合は注意が必要です。

例えばサンプリングレートが38%の場合、ある行動をするユーザーが1300人記録されたとすると、母集団のサイズは…計算したくないですね。これがサンプリングレート50%の場合は2倍に、33%の場合は3倍に、25%の場合は4倍にとしていけるので、これらの計算しやすい値に寄せることが重要となってきます。

## おわりに
今回は統計的な観点を元に、RUM サンプリングレートを決定する基準について考察していきました。

最後に、サンプリングを行わないで済む場合はサンプリングをしないことが最善であることを強調しておきます…！

RUM サンプリングを行う理由は様々ですが、RUM データのユースケースとサンプルサイズを考慮してサンプリングレートを決定することが重要となります🐶