個人開発のアプリで起きた障害を３つ紹介します

# はじめに
<img width="150" alt="目隠し競技者（BLDer）" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376127/cfd7485e-a2de-0065-c71c-79c5ac940c7c.png">

こんにちは、shinhiroと申します。
私は**目隠しルービックキューブ**を練習している人に向けた、
**超ニッチ**なWebアプリを開発・運営しています。
[https://hcon-3bld.web.app/](https://hcon-3bld.web.app/)
<img width="600" alt="サイトの画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376127/c3a90547-da39-e652-0c71-02ef0a78e680.png">


毎日のタイムを記録付けして、弱点や成長を視覚化することでモチベを高めることを目的としています。
リリースしてから2年以上たちましたが、ありがたいことに登録ユーザは**100人**を超え、
毎日多くの目隠し競技者（**BLDer**）に使っていただいています。

今回はこのサイトを個人開発してきた2年間で起きた、
**一時的にサービスを停止**させてしまい胃がキリキリした３つの障害をご紹介します。

<img width="150" alt="腹痛" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376127/58a5bf7d-3c95-146d-9713-4b4e363e0ae4.png">

### 使用した技術

<img width="600" alt="仕様技術" src="https://user-images.githubusercontent.com/48212107/86787496-1ce08d00-c0a0-11ea-804e-e1bae2199ba9.png">

フロントエンドに**React + Redux**、
バックエンドに**Firebase**を使用しています。


## 事例①　Firebaseからのメールを無視した結果...

<dl>
  <dt>事象</dt>
    <dd>サイトの画面表示がローディングアニメーション（ぐるぐる）のまま先に進まない。</dd>
  <dt>発生日時</dt>
    <dd> 2020/06/25 06:11ごろ</dd>
  <dt>影響範囲</dt>
    <dd>サイトにアクセスしたユーザ</dd>
  <dt>経緯</dt>
    <dd>
      <table>
          <thead>
              <tr>
                  <th>日時</th>
                  <th>できごと</th>
              </tr>
          </thead>
          <tbody>
              <tr>
                  <td>06/25 07:57</td>
                  <td>ユーザから「画面が表示できない」との一報をが届く。</td>
              </tr>
              <tr>
                  <td>06/25 08:02</td>
                  <td>Firebaseのログで関数が実行できない旨のエラーメッセージを確認する。</td>
              </tr>
              <tr>
                  <td>06/25 08:10</td>
                  <td>Firebase functionsのランタイムバージョンが古い（Node.js 8）ことが原因と推測する。</td>
              </tr>
              <tr>
                  <td>06/25 08:20</td>
                  <td>ランタイムバージョンをNode.js 10に指定してデプロイする。</td>
              </tr>
              <tr>
                  <td>06/25 08:25</td>
                  <td>画面表示ができることを確認する。</td>
              </tr>
          </tbody>
      </table>
    </dd>
  <dt>原因</dt>
    <dd>Firebase functionsのランタイムバージョンが古く、すべての関数が実行されなかったことが原因でした。<br/>使用していたNode.js 8は 2020/6/5 に非推奨となっており、2020/5/22 にfirebaseから「Node.js 8 から Node.js 10にアップデートしておくように」とのメールが届いていましたが、内容を確認していませんでした。</dd>
  <dt>対応</dt>
    <dd>ランタイムバージョンをNode.js 10に指定してデプロイした。</dd>
  <dt>再発防止策</dt>
    <dd>Firebaseからの連絡はメインで使っているメアドにも送信するように設定し、確認漏れが無いようにする。</dd>
</dl>

## 事例②　非同期処理の書き方をミスって...

<dl>
  <dt>事象</dt>
    <dd>今日のスクランブルが表示されていない。<br>（※スクランブルがないとタイムが測れないので、本サービスとしては"死"を意味します。）</dd>
  <dt>発生日時</dt>
    <dd> 2021/12/04 00:00ごろ</dd>
  <dt>影響範囲</dt>
    <dd>サイトにアクセスし、タイムを測定しようとしたユーザ</dd>
  <dt>経緯</dt>
    <dd>
      <table>
          <thead>
              <tr>
                  <th>日時</th>
                  <th>できごと</th>
              </tr>
          </thead>
          <tbody>
              <tr>
                  <td>12/04 20:37</td>
                  <td>ユーザから「スクランブルが表示されない」との一報が届く。</td>
              </tr>
              <tr>
                  <td>12/04 21:39</td>
                  <td>一報メールを確認し、スクランブルが表示されていないことを確認する。</td>
              </tr>
              <tr>
                  <td>12/04 22:04</td>
                  <td>出先なので対応が遅れることをtwitterで周知する。</td>
              </tr>
              <tr>
                  <td>12/04 23:30</td>
                  <td>調査開始</td>
              </tr>
              <tr>
                  <td>12/05 00:00</td>
                  <td>12/05のスクランブルが投下される（スケジューラによる実行）</td>
              </tr>
              <tr>
                  <td>12/05 00:05</td>
                  <td>firebaseのログを確認。毎日00:00にスケジュールしているスクランブル投下用関数が12/04には実行されていなかったことを確認する。</td>
              </tr>
              <tr>
                  <td>12/05 01:00</td>
                  <td>関数が実行された原因がつかめないため、しばらくはスクランブルの投下を00:00後に目視する方針とした</td>
              </tr>
              <tr>
                  <td>2022/03/08</td>
                  <td>スケジュールされた関数で呼び出している非同期処理に誤りを発見。誤りを修正。</td>
              </tr>
          </tbody>
      </table>
    </dd>
  <dt>原因</dt>
    <dd>本来、非同期処理呼び出し→非同期処理完了→関数終了となるべきところ、非同期処理の中でPromiseを返さない分岐が存在し、非同期処理の完了を待たずに関数が終了してしまう場合があったのでした。JavascriptのPromiseの扱い方を間違えていたのが間接的な原因です。</dd>
  <dt>対応</dt>
    <dd>非同期処理で必ずPromiseを返すように修正した。</dd>
  <dt>再発防止策</dt>
    <dd>他の非同期処理でも必ずPromiseを返すよう見直した。</dd>
</dl>

## 事例③　NaNは何しにDBへ?

<dl>
  <dt>事象</dt>
    <dd>特定のページにアクセスすると、ページ全体の表示が消える</dd>
  <dt>発生日時</dt>
    <dd> 2022/06/16 23:23ごろ</dd>
  <dt>影響範囲</dt>
    <dd>特定のページにアクセスしたユーザ</dd>
  <dt>経緯</dt>
    <dd>
      <table>
          <thead>
              <tr>
                  <th>日時</th>
                  <th>できごと</th>
              </tr>
          </thead>
          <tbody>
              <tr>
                  <td>06/17 00:44</td>
                  <td>ユーザから「昨日の結果ページが変になっている」との一報が届く。</td>
              </tr>
              <tr>
                  <td>06/17 01:00</td>
                  <td>昨日の結果をfirestoreで確認し、特定のユーザのタイムがNaNになっているのを確認する。</td>
              </tr>
              <tr>
                  <td>06/17 01:10</td>
                  <td>同ユーザのタイムを修正し、影響を受けた箇所を正常に戻す</td>
              </tr>
              <tr>
                  <td>06/17 01:15</td>
                  <td>ページの表示が正常であることを確認する</td>
              </tr>
              <tr>
                  <td>06/18</td>
                  <td>・タイムがNaNで送られてこないように、・送られてきてもNaNで記録されないように、・NaNで記録されても読み込みが完了するように修正</td>
              </tr>
          </tbody>
      </table>
    </dd>
  <dt>原因</dt>
    <dd>NaNが代入された変数を演算処理しようとして、クライアント側でエラーが発生していました。変数にNaNが入ることを想定していなく、クライアント側とサーバー側どちらでもはじく処理をしていなかったのです。<br>ただ、なぜNaNが変数に入っていたかはまだ解明できていないです。</dd>
  <dt>対応</dt>
    <dd><ul>
<li>タイムがNaNで送られてこないようにサーバー側を修正<li>送られてきてもNaNで記録されないようにサーバー側を修正<li>NaNで記録されても読み込みが完了するようにクライアント側を修正</dd>
  <dt>再発防止策</dt>
    <dd>他の数値を使う箇所でNaNの対策をする</dd>
</dl>

# 3つの障害から学んだこと

#### 障害予防: Typescript導入
障害を0にするのは難しいですが、減らす工夫はできると思います。
事例②と③については型チェックをしていれば防げたはずです。
**Typescriptの偉大さ**に気づいきました。
今はすべてJavascriptなのですが、Typescriptに書き換えていこうと思います。

#### 障害対応: 早く気づく仕組みをつくる
３つの障害に共通していたのは、**ユーザからの一報**で障害に気づいたという点です。
これでは障害の発見が遅れ、サービスが利用できない時間が長引いてしまいます。
Firebaseのログから障害を**自動検知**し自分に通知する方法を考えています。

#### そして

サイト障害によってサービスを利用できないユーザがいる状態は申し訳ないし、**とてもツラい**です。
事例②のときはちょうど友人とお酒を飲んでいたのですが、
第一報を聞いたときに血の気が引いて酔いが完全に醒めましたね。
だけどこうしたツラさは脇に置いて、あせらず確実に対応する度量を持ちたいを思いました。
**心を痛めても障害は復旧しない**。

以上、個人開発のアプリで起きた障害を３つ紹介させていただきました。
私の失敗例を見て、運営されるサービスなどの参考にしていただければ幸いです。