こんにちは。 プロダクトアプリグループの太田です。 今回は実際に起きた障害事例から、いかに最速で検知できるか模索中のお話をします。
障害が起きた
アプリのリリースを行った翌日いつものKPIモニタリングをしている時でした。 前日から若干のサインアップ率の低下が確認できました。 この時は アプリリリース前からダウントレンド で何かしらの外的要因で低下したのではないかとアプリの障害を全く疑っていませんでした。
そしてさらに翌日…
もっと落ちてるーーー (;゚Д゚) ナイアガラ
何かが起きている… ようやくアプリの障害を疑い調査を始めます。 その結果アプリ側のメールサインアップ時の障害であることがわかり即対応版をリリースしました。
回復っ…!泣
AppStoreの審査に提出し通るまで気が気ではありませんでした。
起きていたこと
- Xcode10移行の際にUniversal Linksをハンドリングするメソッドの仕様変更があったが、その対応が漏れており会員登録アクティベーションが動作しなくなっていた
- 結果メール内のアクティベーションURLでアプリが開くがサインアップが完了しない
- たちの悪いことに (不幸中の幸いだけど) SNS経由の登録には問題ないため、すべての経路で登録できないわけではない
対応版リリースまでは一時的にUniversal Linksの設定を外し、メールアクティベーションはWebで完了するよう対策しています。 結果UXとして最悪な状態に… アプリで登録試みたのにWebに着地し、自らアプリに戻ってもログイン状態になっていない。泣 ユーザの皆様にご迷惑をかけてしまいました…
リリース直後に検知したい
この障害検知が遅れた原因は主に以下です。
- アプリリリース後の最新バージョンの伝搬が緩やかに進むため、障害の発生もゆるやか
- サインアップできる経路もあり全員ができないわけではないためユーザのお問い合わせもインシデントレベルで上がってこない
本来であれば障害起こすものをリリース前に防ぐのが正しいですが、今回のように万が一リリースしてしまった場合の検知方法を考えたいと思います。
サーバサイドの障害であれば、エラーのアラートが即座に上がりサーバ側で対処できることがほとんどです。クライアント側の障害である今回の場合は通信が発生していない (アプリが起動するだけだったため) ためエラーログも送られていません。 そもそもアプリは通信の発生しない動作が多くあるためエラーログでは検知できない。 各行動イベントに対して問題が起きてないか調べるのはキリがないので、KGIの直結する 重要なKPIをバージョンごとに分析 することで、いつもと違う動きをしている場合はその途中途中の行動に問題が起きていることがわかるのではないか? 問題が起きていることがわかれば、何の問題なのかそのバージョンの更新状態からすぐに障害が特定できるはず!
こうなった
みんな大好きfirebaseイベントをRedashで分析します。 各バージョンごとの購入転換率とサインアップ率を障害検知する指標としました。 数を調べるのは最新バージョンの伝搬がすぐには起こらないため率を調べます。
時たま跳ねているものは、該当バージョンのシェアが落ちて母数がかなり減っているため率として増減がかなり激しくなります。直近のバージョンの動きを見たいので特に影響はありません。
WITH dau AS ( SELECT e.event_date AS event_date , e.app_info.version AS version , count(distinct e.user_pseudo_id) AS cnt FROM `coconala-*******.events_*` AS e WHERE e.app_info.id = '**********' AND e._TABLE_SUFFIX BETWEEN FORMAT_DATE("%Y%m%d", DATE_ADD(CURRENT_DATE("Asia/Tokyo"),INTERVAL -15 day)) AND FORMAT_DATE("%Y%m%d", CURRENT_DATE("Asia/Tokyo")) GROUP BY event_date , version ), event AS ( SELECT e.event_date AS event_date , e.event_name AS event_name , e.app_info.version AS version , count(distinct e.user_pseudo_id) AS cnt FROM `coconala-*******.events_*` AS e WHERE e.event_name IN ('first_open', 'finish_loggedin', 'finish_registration', 'view_service', 'ecommerce_purchase') AND e.app_info.id = '**********' AND e._TABLE_SUFFIX BETWEEN FORMAT_DATE("%Y%m%d", DATE_ADD(CURRENT_DATE("Asia/Tokyo"),INTERVAL -15 day)) AND FORMAT_DATE("%Y%m%d", CURRENT_DATE("Asia/Tokyo")) GROUP BY event_date , event_name , version ), sum_event AS ( SELECT event_date , version , sum(if( event_name = 'first_open',cnt,0)) AS first_open , sum(if( event_name = 'finish_loggedin',cnt,0)) AS finish_loggedin , sum(if( event_name = 'finish_registration',cnt,0)) AS finish_registration , sum(if( event_name = 'view_service',cnt,0)) AS view_service , sum(if( event_name = 'ecommerce_purchase',cnt,0)) AS ecommerce_purchase FROM event GROUP BY event_date , version ), sum_all_event AS ( SELECT e.event_date AS event_date , e.version AS version , d.cnt AS dau , first_open , finish_loggedin , finish_registration , view_service , ecommerce_purchase FROM sum_event AS e INNER JOIN dau AS d ON d.event_date = e.event_date AND d.version = e.version WHERE e.version IN ('3.25.0', '3.26.0', '3.27.0', '3.28.0') ) SELECT * , SAFE_CAST((CASE WHEN dau != 0 THEN finish_loggedin/dau ELSE 0 END) as float64) as loggedin_rate , SAFE_CAST((CASE WHEN first_open != 0 THEN finish_registration/first_open ELSE 0 END) as float64) as signup_rate , SAFE_CAST((CASE WHEN view_service != 0 THEN ecommerce_purchase/view_service ELSE 0 END) as float64) as buy_rate FROM sum_all_event ORDER BY event_date DESC
そしてこれを当時のバージョンで見てみると…
明らかに初速から違う…! これでリリース直後に検知が可能そうです!
まとめ
その後モニタリング継続してますが、幸いにも大きな障害は発生していません。障害を発生させない努力をするのが最も大切ですが、発生した場合に検知する仕組みとリカバリ方法を常に考えておくのも重要です。 避難訓練ではないですが、毎回のリリース時に障害が発生した場合どうリカバリするか常に考え続けることで万が一の事態にも対応スピードが上がってくるかと思います。目指せ障害最速対応!(障害0!)