Upgrade to Pro — share decks privately, control downloads, hide ads and more …

小さなバグが生んだ悲劇、そこから学ぶ耐障害性の高いアプリ設計

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Yosuke Imairi Yosuke Imairi
September 02, 2023

 小さなバグが生んだ悲劇、そこから学ぶ耐障害性の高いアプリ設計

iOSDC Japan 2023 にて登壇した内容となります。
https://fortee.jp/iosdc-japan-2023/proposal/eb9d4449-4ff8-421d-9ffb-691179245d14

登壇のアーカイブ
https://www.youtube.com/watch?v=9GbG13-jMVM

Avatar for Yosuke Imairi

Yosuke Imairi

September 02, 2023
Tweet

More Decks by Yosuke Imairi

Other Decks in Technology

Transcript

  1. © GO Inc. 自己紹介 2 プロフィール写真 GO株式会社 ユーザーシステム1グループ / 今入庸介

    タクシーアプリ『GO』のiOSアプリ開発 • 新機能開発 • アーキテクチャの選定・導入 • アプリ全体の設計、新機能開発 • CI 環境の構築・整備、自動化の促進 • チーミング、エンジニアの育成 @kamekiti
  2. © GO Inc. 〜 iOS アプリのリリースの翌日 〜 5 小さなバグが生んだ悲劇(タクシーアプリ『GO』での障害事例) iOS

    アプリ起動後に異常な数のリクエストが送られていることが判明 💥 (通常の数百倍のリクエスト…!ローディング状態が続き次に進めない) 社内で「一部のサービスへのリクエストが異常に減っている」との連絡 (どうやらタクシー配車依頼までユーザが辿り着けていない 🤔)
  3. © GO Inc. 8 障害時の対応がよくなかった 1. メンテナンス状態なのにアプリ側で適切な対応ができなかった ◦ メンテナンスモードに切り替わらなかった ▪

    アプリの初期段階からメンテナンスモード自体は実装されていた ◦ ユーザを混乱させてしまった ▪ これは障害?一時的に負荷が高い状態? ▪ ユーザ側の環境の問題?(電波が悪い etc..) 2. バグを含んだバージョンの利用を抑えることに時間を要した ◦ 修正版のアプリに更新してもらうことをただ祈るのみ…
  4. © GO Inc. • メンテナンス状態のときにユーザに現状の報告ができる場 ◦ アプリ内でユーザに現状を明確に伝える ▪ 障害なのか定期メンテナンスなのか ▪

    いつ復旧するのか • 復旧の目処が立たないという情報だけでも有益 ◦ WebサイトやSNSなどで障害情報を発信できるがあくまで補助的 • ユーザはアプリを使いたいのに使えない状態であることを肝に銘じる ◦ これはユーザにとって最悪の体験の1つとなる 11 メンテナンスモードで何を伝えられるとよいか
  5. © GO Inc. • 根本的な原因は、API に依存した設計になっていたこと ◦ メンテナンス状態かどうかを API 経由で取得していた

    12 なぜ『GO』はメンテナンスモードに切り替わらなかったのか iOSアプリから通常の数百倍のリクエストが送られる(バグ) GO のサーバや DB に負荷がかかり捌けなくなる アプリは API からメンテナンス状態を受け取りたいが、API のレスポンスが返ってこない アプリ上は通信中状態のまま(ユーザには何が起こっているか分からない)
  6. © GO Inc. • JSONファイルの取得に成功した場合のみ、その状態を適用させる • iOS / Android で別々のファイルで管理

    ◦ どちらか一方だけで障害が起きる可能性がある • サーバ・DB に依存しない場所でメンテナンス状態を管理する ◦ 『GO』の場合は GCS に JSONファイルを配置 ◦ メンテナンス状態かどうかを Bool 値で管理 13 メンテナンス状態かどうかの判断をどうやるか【障害時】 {"is_maintenance":false}
  7. © GO Inc. • 障害時と同様でもよいが、APIが相応の状態を返してもよい ◦ 定期メンテナンスを判断する API を用意 ◦

    機能の一部として利用している API の状態を利用 ▪ API ごとにハンドリングできるので、細かな制御がしやすい 14 メンテナンス状態かどうかの判断をどうやるか【定期メンテナンス】
  8. © GO Inc. • ユーザに対してアプリのバージョンアップを促す機能 ◦ ユーザはアプリを更新するまでアプリ内の機能が利用できない • 想定される利用シーン ◦

    アプリ内部にバグがあるバージョンの利用を抑制したい ◦ 古い機能のサポートを終了したい ◦ ユーザに届ける体験を統一したい ▪ ユーザによって利用できる機能に差がないようにする 16 強制アップデート機能とは?
  9. © GO Inc. ◦ needs_update_versions: 指定されたバージョンが対象 ◦ required_app_version: 指定されたバージョン未満すべてが対象 •

    『GO』の場合は、GCS に iOS / Android で個別の JSON ファイルを配置 ◦ メンテナンス状態の管理と同じ思想 • 強制アップデートを要求するアプリバージョンを記載する 18 強制アップデートが必要かどうかの判断をどうやるか { "needs_update_versions": ["6.7.0”, “6.7.1"] "required_app_version": "6.5.0" }
  10. © GO Inc. • アプリを起動した直後が理想 ◦ 可能な限り早い段階で判断したい ▪ アプリ内の機能が利用される前 ◦

    アプリ内部のバグはどこに潜んでいるか分からない 19 強制アップデートを要求するタイミング
  11. © GO Inc. • 今回の障害時のタイミングでは『GO』には強制アップデート機能がなかった ◦ 修正版をリリース後、アプリが更新されることを祈るしかなかった • Karte の接客テンプレートを使い、バグがあるバージョンに対して

    起動後にアプリのバージョンアップを促すようにした ◦ 告知は閉じられたので、やや強制力は弱め 20 余談:今回の事例で『GO』はどのようにアップデートを促進したのか?
  12. © GO Inc. • 同じ過ちを繰り返さないようにしたい ◦ なぜ今回の事象が発生したのかをチームメンバーと振り返る ◦ 根本的な原因は何だったのか? ◦

    どうすれば防ぐことができたのか? • 『GO』では今回の事例を受けて以下の対応をした ◦ メンテナンスモードの改善( サーバ / DB に依存しない設計) ◦ 強制アップデート機能の追加 ◦ ”目に見えないバグ” への対処(後述) 22 障害が落ち着いたら終わりではない
  13. © GO Inc. • 今回の障害の原因 ◦ コード誤り(ケアレスミス) • 今回の障害の詳細 ◦

    地図を動かしたり GPS の精度が変わったタイミングで、 意図しない通信処理が実行された ◦ GPS の精度はかなり頻繁に更新される ▪ 通常の数百倍のリクエストにつながった 23 『GO』の事例: ”目に見えないバグ” がリリースされた
  14. © GO Inc. 24 『GO』の事例: ”目に見えないバグ” がリリースされた • コードレビューで気づけなかったのか? ◦

    指摘がされていたが、修正されないまま Approve されマージされた • QA で気づけなかったのか? ◦ 現状の QA メンバーの規模ではサーバへの負荷にはならなかった ◦ 通信回数の確認までは行っていなかった • そもそも開発段階で気づけなかったのか? ◦ シミュレータでは再現しづらいバグで気づけなかった バグは様々な工程をすり抜けてリリースされてしまう…!
  15. © GO Inc. • 「目に見えない」ということが発見の障壁となった ◦ 目に見えるようにすれば気づきやすくなるのでは? • どれくらい通信されているかを可視化するデバッグモードを開発した ◦

    各 API がどれくらいリクエストされているかを一覧表示させる ◦ 前回のバージョンと比較して大幅な変化がないかを確認 25 『GO』の事例: ”目に見えないバグ” に対する再発防止策
  16. © GO Inc. 28 まとめ • 『GO』で起きた障害をもとに耐障害性の高いアプリ設計を検討した • メンテナンスモードの設計 ◦

    サーバ / DB に依存しない状態管理 ◦ 障害時と定期メンテナンス時では設計手法に差がある • 強制アップデート機能の設計 ◦ アップデートの要求は起動直後が理想 ◦ ユーザにアップデートを促すためにうまく誘導する • 根本的な原因の追究と対策 ◦ 同じ過ちを繰り返さぬようチームメンバーと対策を検討 & すぐ実行