目次
はじめに
株式会社 WinTicket にて 27 卒で内定者アルバイトしている菊地友央(@kikuteaaa)です。
Flutter アプリチームに所属しております。
私が所属する株式会社 WinTicket では、Flutter モバイルアプリにおいて、より可用性を高めるための方策として各機能の SLO(Service Level Objective)を定義して、SLI(Service Level Indicators)を測定しています[1]。
SLI/SLO は、Google の SRE (Site Reliability Engineering) で提唱された、サービスの信頼性を定量的に管理するための仕組みです[2]。以下の表に一般的な SLI/SLO の説明をまとめます。
| 名前 | 説明 |
|---|---|
| SLI (Service Level Indicators) | サービスの信頼性を測る具体的な指標。例えば「ログイン成功率」「API レスポンス時間」など |
| SLO (Service Level Objectives) | SLI に対して設定する目標値。例えば「ログイン成功率 99.9% 以上」「レスポンス時間 500ms 以下」など |
SLI/SLO を適切に測定・設定し、監視基盤を構築していくことで、いわゆる「エラーが出ない実装ミス」を検出できます[3][4]。
現在、WINTICKET では、さらなるオブザービリティの向上と統合監視基盤の強化のために、Datadog の導入を進めています。Datadog は監視基盤サービスの 1 つで、Datadog を利用することによって、さまざまなイベントやログ・エラーなどが 1 つの Session という概念に紐づいて記録されるため、検索性が大幅に向上します。
今回、オブザーバビリティ基盤を Sentry から Datadog へ移行するプロジェクトの一環として、「ログイン完了までの時間」や「投票確定までの時間」のような、ユーザー操作の開始から完了までの期間を伴う操作を Datadog の RUM で計測する必要がありました。しかし、調査を進める中で標準機能にいくつかの制約があると判明したため、それを乗り越えるための実装をしました。
本記事では、その過程で直面した課題と解決方法について紹介します。
WINTICKET について
WINTICKET は、中央競馬、地方競馬などと並ぶ公営競技の「競輪」と「オートレース」がネットで楽しめるアプリです。
WINTICKET では、そのサービスの性質上多くのトランザクションが発生します。多くのユーザーに楽しんで頂くためには、サーバー・クライアント双方において可用性の確保が非常に重要です。これらの対策の一環として WINTICKET では、すでにアプリでも SLI/SLO を計測しています[1][5][6][7][8]。
やること
レース直前に投票できない、決済が完了しないといった問題が発生すると、大きな機会損失になります。そのため、ユーザーからの報告を待ってから対応するのでは遅く、問題を早期に検知して迅速に対応できる体制が必要です。
また、エラーとしてキャッチされないものは通常のアラートでは検出が困難です。例えば、投票フロー中のアニメーション実装に問題があり、特定の画面操作に時間がかかるようになった場合、エラーのアラートでは検出できません。
SLI/SLO では、平均時間や成功可否を見ることによって、そういった現象にいち早く気がつくことができます。
これらを有効活用するためには、SLI/SLO の値が正しく計測されることが第一条件です。また、私たち開発者の関心が SLI/SLO の状態へ向くように、能動的な行動に対してはダッシュボード、受動的な行動に対してはアラートを用意します[9][10]。ダッシュボードは、開発者がアプリの容態を気になった際に、「とりあえずここを一目みておけば安心だよね」という状態を目指します。
以上から、今回は以下の 3 つを実施して、Datadog へ移行します。
- SLI 計測基盤の改善
- ダッシュボードの作成
- アラートの作成
課題
今回、WINTICKET で実現したい SLI 測定は、ユーザーのアプリ操作に対して、任意の画面から任意の別の画面までをひとつの SLO として定義し、そのかかった時間と、操作の成功可否を集計するものです。

図の例では、Google でのログインボタンを押してから、その操作が完了するまでの時間と結果( Status )を計測しています。
これらの対象は、WINTICKET の CUJ (Critical User Journey) に基づいて設定されています。CUJ とはざっくりいうと、ユーザーが重要な目的を完遂するために行う特定の動作のことです。したがって、ログイン操作や WINTICKET であれば投票操作がそれに当たります。
実は、すでに Datadog での SLI 測定は実装されていましたが、Datadog 上で確認できる値が Sentry の SLI と乖離しており、正しくない状況でした。また、Datadog の RUM (Real User Monitoring) Explorer 上を確認すると、送られてくるデータの Status が空であったり、そもそも 10 秒以上の測定データが存在していないことが判明しました。
既存の SLI 測定の実装では Datadog の RUM Action における startAction/stopAction という API を利用しています。しかしながら、調査を進めると、Flutter の Datadog SDK では、これらの startAction/stopAction において 10 秒以上の計測が不可能であることが判明しました[11][12]。その後 Datadog へサポートチケットもオープンしながらさらに調査を進めたところ、WINTICKET で実現したい SLI 測定のための API は存在しないことがわかりました。
以下に実際に検討した API のリストをまとめます。
| 項目 | 説明 | 懸念点 | 使用可否 |
|---|---|---|---|
| RUM Actions (startAction/stopAction) | View を跨いだ測定が可能、任意のタイミングで測定の開始、終了ができる。 | dd-sdk-flutter の制約で10sec以上の測定ができないため、WTでのユースケースにそぐわない。 |
❌ |
| RUM Resources (startResource/stopResource) |
HTTP などの API におけるやり取りを記録するためのもの。任意のタイミングで測定の開始終了か可能。10sec以上も測定できる。 | HTTP の監視が目的なので、ダミーの URL を入れたりダミーの Method やステータスコードを入れる必要がある。APIの使用用途が異なるためそぐわない。[13] | ❌ |
| APM Traces | 任意のタイミングを記録でき、Span も設定できる。 | パフォーマンス測定用のものなので、趣旨が違う。 | ❌ |
| Feature Operation | 任意のタイミングと期間で記録できる、ユーザーの行動測定用のもの。まさに今回欲しいAPI | まだ実装されてない [14] | ❌ |
| RUM Actions (addAction) |
任意のタイミングで点として記録できる。 | 期間が測れないので、自分で時間を測ってカスタムAttributesに入れてあげる必要がある。 | ⭕️ |
上記の理由から現時点では、自前実装する道が良さそうだということで、RUM Actions の機能である addAction を駆使して、任意の時間計測が可能な仕組みを作る意思決定を行いました。

実装方法
SLIの計測
さて、RUM Action を利用して任意の期間を測定可能な仕組みを作ることが決まりました。ここで、必要な要件をまとめてみます。
- 操作の開始時と完了時に
addActionを呼ぶ必要がある。 - 操作が長い間されていない場合は自動的にタイムアウトにする必要がある。
また、Datadog の RUM Action の特性として、アプリが resumed 状態でない場合には、addActionしても一切記録されてないことが挙げられます。
そのため、上記に加えて以下の要件が必要になりました。
addActionするタイミングでアプリの状態を確認し、resumedでない場合にはresumedに戻ったタイミングで送信するようにする必要がある。- 上記の呼び出しは同時に行われる可能性があるため、排他制御をする必要がある。
アプリの状態に応じて addAction するかしないかの処理が必要であるため、
Flutter の WidgetsBindingObserver[15] を利用して AppLifecycleState(resumed / paused など)を監視しています。
また、今回の実装する SLI 測定部分の単体テストは可能ですが、動作するという確証を得るには実際にさまざま端末や OS バージョンで測定させてみる必要があります。
しかしながら、可用性の測定基盤という特性上、いきなり本番環境で切り替えることは避けるべきです。
そのため、本実装の動作確認には Sentry 側のデータと照合して検証する方針を立て、Sentry での実装と混在させることにしました。クラス責務を以下の 2 つへ分けて、Sentry で測定している部分にコード差分が出ないような構成をとっています。
- 1 回の
addActionを確実に送信するためのクラスDatadogAction - それらを区間として表現させるためのクラス
DatadogTransaction

上記の図に SLO 測定におけるクラスのデータフローを示します。
ユーザーの操作開始時には開始時刻を記録し、同時にタイムアウト用の Timer を起動します。
操作が完了、もしくはタイムアウトしたタイミングで経過時間を算出し、
その結果を addAction の custom attributes として送信します。custom attributes を設定する際には、なるべく DatadogSDK の addAttributes は使用しないようにしました。
この API はグローバルな設定値のため、他の箇所で利用されると値が上書きされてしまう可能性があります。元々の実装で送られてくる custom attributes の値が一部不正だった問題はこれが原因でした。代わりに addAction の引数として custom attributes を設定することで正しく送信されるようになりました。
また、送信時にアプリが resumed 状態でない場合は即時送信せず、
start / finish の情報をスナップショットとして内部に保持します。
その後、AppLifecycleState.resumed を検知したタイミングで
保持していたスナップショットを送信することで、
バックグラウンド遷移を跨いだ操作でも計測結果が欠損しないようにしています。
また、Timer 発火のタイミング、ユーザーの完了タイミング、アプリのステート変化による呼び出しが同時に起こらないように、排他制御を行なっています。
ダッシュボードの作成
ここまでで、アプリ側の実装が完了したので Datadog 上にダッシュボードを作成していきます。
ただ、現時点では Datadog 上にあるデータは RUM Action としてのデータです。Datadog には RUM をどの程度保持するかの設定として Retention Filter (保持フィルター)が存在しています[16]。特に RUM の料金は、この保持量によって左右されるため、通常は低めに設定しているはずです。そのため、SLO の測定を使っている場合、直接 Action をカウントするだけでは正しい集計が行えません。
そこで今回は、アプリから送信している SLO の RUM Action でカスタムメトリクスを作成しました[17]。メトリクスにした場合、そのデータは Retention Filter の影響を受けずに集計できます。そして、RUM Action に custom attributes として付与している Action の Status 等でタグを設定し、別々に集計できるようにしてします。

さらに、Datadog 上に定義したカスタムメトリクスから SLO を定義しました。これによって Datadog の dashboard 上で容易に情報を見ることができるようになりました。
Dashboard に表示する情報としては以下の通りです。
- 任意の期間の成功率の変化グラフ
- 結果の割合
- Datadog に定義した SLO の状態
アラートの更新
WINTICKET では、元々 Sentry で取得していたアプリの SLO アラートを実現するために、自前の仕組みを用意していました。バーンレートを計算して閾値を超えている時に Slack へ通知するものです。これらは Google Cloud Functions で構築されていて、定期実行されています。今回の Datadog 移行にあたり、これらのアラート基盤を Datadog の SLO モニターへ移行することも検討しましたが、以下の理由から引き続き独自のアラート基盤を採用する判断をしました。
Datadog の SLO モニターを採用しなかった理由としては以下の二点が挙げられます。
- Datadog の SLO モニターは Multi-Window 方式のアラートのみをサポートしており、既存のアラート設計との互換性がない[9][18][19]
- アラートの閾値として設定されていた値はすでに長い間運用されており、ある程度適切な値に設定されているため、これまでのアラート頻度や閾値設定をそのまま活用したい [1]
通常のサーバーサイド SLI/SLO とは異なり、アプリの SLI にはユーザー起因の結果が含まれます。例えば、決済処理中にユーザーが途中でアプリを閉じた場合、これは技術的な「エラー」ではなく「キャンセル(中断)」として分類されます。WINTICKET ではこれらを区別するため、以下の 2 種類の SLI を計測しています。
| SLI種別 | 計算式 | 説明 |
|---|---|---|
| Available SLI | 成功数 / 全操作数 | 純粋な成功率。エラーもキャンセルも失敗として扱う |
| Cancel SLI | (全操作数 – キャンセル数) / 全操作数 | キャンセルを除いた完了率。ユーザー起因の離脱を考慮 |
この区別により、「システムの問題」と「ユーザー行動による離脱」を分離し、それぞれに適切な閾値でアラートを設定することが可能になっています。
また、今回アラートの視認性とわかりやすさ向上のため、グラフ機能を追加しました。
実は Datadog にもグラフを画像として出力してその url を返してくれる API が存在するのですが、グラフの自由度が低いことや複数のグラフを載せた時に視認性が下がることから
自前のグラフ描画機能として実装しています。

Datadog Runbookの作成
アラート発生時にすぐ対応できるよう、Datadog 上の Notebook を用いて Runbook を作成しました。
各アラートには該当する Runbook へのリンクを設定し、対応方針や確認すべき指標をすぐに参照できるようにしています。
また、Notebook では template variable を利用し、アラートのリンクをクリックするだけで
対象のアプリバージョン・SLO・OS が自動的に選択されるようにしました。
この仕組みで、Runbook 内のグラフもアラート内容に応じて自動的に切り替わります。
まとめ
今回は、Datadog RUM を用いてモバイルアプリ上のユーザー操作を SLI として計測する仕組みを実装し、ダッシュボード作成やアラート更新まで行いました。
実は内定者アルバイト期間中、この他にも新機能を実装したり、その途中で見つけた Flutter の不具合を修正してコントリビュート[20]してみたり、はたまた FlutterKaigi への参加したり、他の Datadog の機能導入をしてみたり、、、など、自身にとって非常に実りのある時間となりました。
特に、普段普通にアプリの学生エンジニアとして活動していると、なかなか意識がしにくい可用性の部分について、
実際に多くのユーザーのいるアプリで経験させていただけたのは良い経験になったと確信しています。
実は私、ほぼ iOS とサウンド周り (C/C++) しかやったことがなく、Flutter 未経験でどこまでできるか不安がありました。しかしメンターの方をはじめアプリチームの社員の方々のサポートのおかげで、非常に良い時間を過ごせました。
この記事を読んで少しでも興味を持っていただけたのであれば、ぜひ WINTICKET でのインターンシップに参加してみてください。
多くのユーザーが利用するプロダクトならではの課題に向き合える、やりがいのある環境です。
参考文献
[1]: WINTICKETアプリで実現した高可用性と高速リリースを支えるエコシステム
[2]: SRE fundamentals: SLIs vs SLOs vs SLAs, Google Cloud Blog
[3]: Service Level Objectives, Google SRE Book
[4]: Implementing SLOs, Google SRE Workbook
[5]: 大規模投票サービス「WINTICKET」だからこそ SRE の重要性を改めて感じた話
[6]: WINTICKET Flutter アプリの SRE for Mobile の取り組み
[7]: SLI/SLOを用いてFlutterアプリでユーザー体験の品質を監視する
[8]: WINTICKETを支えるインシデントマネジメントの取り組み
[9]: Alerting On SLOs, Google SRE Workbook
[10]: Monitoring Distributed Systems, Google SRE Book
[11]: Step 2 – Instrument your application, Flutter Monitoring Setup, RUM
[12]: dd-flutter-sdk, startAction
[13]: Support for custom trace, DataDog dd-sdk-flutter
[14]: Support for custom trace, DataDog dd-sdk-flutter
[15]: WidgetsBindingObserver, Flutter Dev
[16]: Datadog Retention Filters
[17]: Datadog Custom Metrics
[18]: Datadog Service Level Objectives, Monitor
[19]: Datadog Service Level Objectives, Burn Rate
[20]: Flutter, Add DeviceOrientationBuilder widget by MediaQuery orientation
