###### tags: `TransactionalInformationSystems`
# 17章-3 ステートフルなアプリケーションの回復
## より一般のステートフルなアプリケーション
※ この節はふんわり書かれているのでこのノートもふんわりしている
余裕があれば見直したい
ここまで考えてきたアプリケーションは、メッセージキューに状態に関する情報を全て格納するという点で、実質的にはステートレス、もしくは準ステートフルとでもいうべきものだった。
ここでは、さらにステートフルな一般のアプリケーションに対するアプローチを考えていく。ただし、クライアントアプリケーションが直接データサーバにアクセスする2層アーキテクチャに限定する。
目標は、システムの障害による失敗をユーザに見えないように回復を行い、ほぼ全体のプロセスをただ一度だけ実行すること。カバーできない例外は、出力の重複表示と、失われた直前の入力の再要求のみにしたい。
メッセージの管理は、必ずしもキューでは行わない。代わりにメッセージログをとることで、回復時にもとのメッセージを復元してアプリケーションをリプレイする。
### サーバリプライロギング
メッセージログはサーバとクライアントのどちらで取ってもよいし、両方で取ってもよい。ただ、以下の理由からサーバでとるのが丸い。
- サーバは多くのクライアントと並行にやり取りするので、ロギングのためのディスクI/Oを効率よく使える。
- 複数のリクエストを柔軟な順番で実行できる(これは通常のメッセージングフレームワークでは不可能)。
- サーバはクライアントよりも信頼性が高い。
なお、ユーザの入出力に関するものはクライアント側がログを取る。
### データ構造
通常の回復機構に加え、サーバで以下のデータ構造を保持する。
- アクティブアプリケーションテーブル(AT):実行途中(失敗/再起動中のものも含む)のアプリケーションの状態に関する情報を格納する
- メッセージルックアップテーブル(MT):アクティブなアプリケーションのメッセージ、特にリプライメッセージをランダムアクセス可能な形で保存する
これらはメモリ上に置いて高速なアクセスを可能にし、ストレージには永続ログを書き出す。
各メッセージは以下のタグで修飾する。
- アプリケーション識別子(AppID):ホストクライアントを識別する値で、クライアントの中で一意に定まる
- message sequence number(MSN):各クライアントアプリケーションの中で単調増加かつ一意に定まる
```verilog
AT: array[AppID] of record
LastMSN: integer; // アプリケーションの最後のメッセージのMSN
StableMSN: integer; // これ以前のメッセージが全部クライアントの永続ログに記録されているMSN
RedoMSN: integer; // 不要になっていない最古のメッセージのMSN
RedoLSN: integer; // RedoMSNに対応するログエントリのLSN
MT: array[AppID,MSN] of record
MsgType: (request, reply, input, output);
MsgContents: array of char;
LF: persistent array[LSN] of record
LogRecType: (write, read, undo, request, reply, input, IP, start-IP, term-IP, CP);
LogRecContents: array of char;
AppID: integer;
MSN: integer;
```
アプリケーションの状態は定期的にクライアント側でも記録される。この点をインストレーションポイント(IP)と呼ぶ。IPもMSNを持つメッセージの形で記録し、開始と終了をそれぞれstart-IP、term-IPとする。
### 通常動作中のサーバロギング
サーバはデータベースへの書き込み、リクエスト/リプライのそれぞれについてログエントリを生成する。そのうちメッセージに関するものはMTにもランダムアクセスできる形で格納される。リプライログエントリは、クライアントに送り返されるまでにforceされる。このforceが完了したら、対応するリクエストに関するMTのエントリは削除してもよい。
リクエストの処理中にサーバが落ちた場合に特別な考慮が必要で、2つの選択肢がある。
- ロールバックリクエストと再実行:
終了していなかったリクエストを全てundoして実行しなおす。複数のリクエスト間の分離性が維持されたままundoされる必要がある。
- リクエスト処理の再開:
データベースからの読み込みも全てログを取る。再開時にはログをもとに状態を復元し、そのままリプライを完成させる。再開の間はデータベースの読み書きを遮断する。書き込みは恒等性のためにLSN検証を行う。読み込みに関して2つの方法がある。
1. 読み込んだデータを物理ログにとる。ログだけからデータを復元できるが、ログサイズが大きくなる。
2. 読み込み操作を論理ログに記録する。ログに基づいてデータベースから読み込んで復元する。flush順依存性を管理し、必要とされているバージョンがflushで上書きされないようにする。