「データ指向アプリケーションデザイン」から学ぶ信頼性

# 概要

周りで名著だと言われているけど個人的には読んだことがなかった本の
[データ指向アプリケーションデザイン　―信頼性、拡張性、保守性の高い分散システム設計の原理](https://www.oreilly.co.jp/books/9784873118703/)
を長期休みの暇な時間に読んだので、忘れないうちにメモをしていた箇所を投稿しておく。

自分なりの理解を含めてメモを書いているので、誤っている部分がある場合にはご指摘いただけると喜びます。

**追記**
続きを書きました。
[「データ指向アプリケーションデザイン」から学ぶスケーラビリティ](https://qiita.com/KenUehara/private/1bb8da5c1698d8bfaba9)

# 信頼性

システムにおける信頼性とは、**何か問題が生じたとしても正しく動作し続けること**を指す。

システムの領域には大きく分けて
- ハードウェアの障害
- ソフトウェアの障害
- ヒューマンエラー

という信頼性を脅かす3つの要素があるが、これらは可能な限り対策をするべき。

なお、この本の中では障害を起こしうるものをフォールト、システムが全体として必要なサービスのユーザーへの提供を止めてしまった場合を障害としている。
このフォールトの存在を事前に見越して対策を取っているようなシステムのことを**耐障害性を持つ**（フォールトトレラント、もしくはレジリエントである）と言う。


## ハードウェアの障害

ハードディスクのクラッシュ、RAMの欠陥、電力網の停電などは常に起こりうるもの。

例えば、ハードディスクの平均故障時間（MTTF:Mean time to failure）はおよそ10年から50年。
10000台のハードディスクを運用している環境では大体1日に1台壊れるという概算になる。

### どのように対策をするのか？

まず最優先事項として、**個々のハードウェアに冗長性を持たせること**が挙げられる。

具体的には、
- ハードディスクはRAID構成にする
- サーバーは電源を二重化してホットスワップ（ここではサーバーの電源をつけたまま電力の供給元を切り替えること）可能にし、その上でバックアップ用の電源としてバッテリーとディーゼル発電機を用意する

といった冗長化を行うことでどこか一部分にフォールトが発生しても対応可能となる。

かつては冗長化を考えておくことがメインであったが、最近ではデータの量やアプリケーションに求められる処理量が増大するにつれて、大量のマシンを使用するアプリケーションが増加している。

大量のマシンを使用するということはすなわちハードウェアのフォールトが発生する確率も高まる、ということになる上、AWSなどのクラウドプラットフォームは単一のマシンの信頼性よりも柔軟性やエラスティック性を重要視して設計されているため、警告なしに仮想マシンのインスタンスが使用できなくなることが一般的。

:::note info
エラスティックとは、負荷の増大を検知して自動的にコンピューティングリソースを追加できるシステムのこと。
負荷の予想が難しい場合には自動で対応してくれるという特性から便利だが、一方で、運用時に予想外の出来事が発生するという注意点もある。
:::

それ故に、ハードウェアの冗長化に加えてソフトウェア側でも耐障害性の手法を使うことで、マシン全体が突如失われたとしても耐えられるようなシステムへの移行が進んでいる。

単一のマシンしか用意されていない環境ではマシンを再起動する時にはシステムが使用できなくなるダウンタイムを計画しなければならなくなるが、マシンの障害に耐えられるような設計をしているシステムの場合には各マシンに順番にパッチを当てることでシステム全体のダウンタイムを生じさせることなく運用が可能というメリットがある（これを**ローリングアップグレード**という）。

## ソフトウェアの障害

ハードウェアの障害はそれぞれ**相関性が薄く、独立しているもの**と考えられる。
例えば、あるハードディスクに障害が起こっても、それ以外のディスクに障害が発生する可能性は低い（サーバーラックの温度が極端に高いなどの要因があれば別）と考えられる。

一方で、ソフトウェアのフォールトは**相関性を持つ傾向**にある。

以下はその具体例。
- 特定の入力が与えられた場合にシステム全体がクラッシュする
- 一つのコンポーネントのフォールトが他のコンポーネントのフォールトを引き起こすカスケード障害を引き起こす[^1]
- 特定の処理を行う際のプロセスが暴走し、ハードウェアのリソースを使い切ってしまう
- 依存しているシステムが想定しているレスポンスを返さなくなる

など。

これらはシステムを開発するときに想定していた通常とは異なる状況が重なって、問題が表面化する時が来るまで潜伏している事がよくある。
そして月日が経つにつれ、想定していた通常通り、という状況はいつか通常通りではなくなる。

### どのように対策をするのか？

ハードウェアの冗長化のような手っ取り早い対策は存在せず、ちょっとした積み重ねが重要となる。
例えば、
- システムの前提について注意深く考察すること
- 徹底的なテストを行うこと
- プロセスを分離すること
- プロセスのクラッシュと再起動の許容、計測、モニタリングを行うこと
- プロダクション環境（本番環境）におけるシステムの挙動分析をすること

など。

## ヒューマンエラー

**人間は間違える、最大限努力したとしても信頼性はない。**

どんなに頑張っても間違えるときは間違える。むしろ誤りを起こさないように事前に考える。
そして誤りを起こしてしまった時にどのようにリカバリをするかに集中すべき。

### どのように対策をするのか？

- ヒューマンエラーの可能性が最小限となるようにシステムを設計すること。
上手く設計されている抽象化、API、管理インターフェースは「正しいこと」を行いやすく、「間違ったこと」を回避しやすくしてくれる。
**ここで注意すべきなのは、インターフェースの制限が強すぎると人間はそれを回避しようとするため、この方法のメリットが損なわれてしまうということ。**

- ヒューマンエラーが発生する可能性が高い部分障害に繋がりやすいところから分離し、サンドボックス環境に移すこと。

- ユニットテストから結合テスト、マニュアルテストまで**全てのレベルで徹底的にテストを行うこと**。自動化テストだと通常では滅多に発生しないケースを試すのに便利。

- ヒューマンエラーに起因する障害からのリカバリ案を作成すること。

- パフォーマンスメトリクスやエラーの発生率などのモニタリングの仕組みをセットアップすること。
モニタリングを行うことで警告シグナルを早期に受け取る事ができ、何らかの前提や制約に反していないかをチェックできる。
問題が生じた場合、その診断にはメトリクスが欠かせない。

- システムの管理方法とトレーニングの実践。

今日のアプリケーションにおいてはクリティカルでない障害でもユーザーに対する責任が存在する。
信頼性を犠牲にする事で開発コストを下げたり、運用コストを下げたりするケースもあるにはあるが、そうしたケースでは信頼性を犠牲にしていることを強く意識すること。




# 参考文献

[^1]:[Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region
](https://aws.amazon.com/jp/message/65648/)