Db2リカバリ（物理バックアップ・リストア）

[Db2機能によるバックアップとリストア](https://qiita.com/ch7821/items/0af1bc61034d8570b7e0)についてまとめたので、今度はストレージ機能（いわゆる物理バックアップ）によるバックアップとリストアのシミュレーションをしてみる。
各領域の整合性を保つため、RDBMSの機能で静止化（書き込み抑止）する、DBを非活動化して停止する、またはOSシャットダウンした上で取得する必要があるが、今回はオンラインバックアップが要件である、つまりバックアップのためのサービス停止はできないと想定し、静止化状態でのバックアップ・リストアとする。

前提環境は以下の通り。

+ Db2 v11.1
+ OS RHEL 7.3
+ HyperVisor VMware Fusion

DBの構成は以下のようになっている。

| 項目 | 構成 |
|:-:|:-:|
| DB名  | HADRDB  |
| DBパス  |  /hadrdb/dbpath* |
| ストレージ  |  /hadrdb/tbsp1*,/hadrdb/tbsp2* |
| アクティブログ  |  /hadrdb/trnlog* |
| アーカイブログ  |  /hadrdb/arclog* |

表中の"*"をつけた箇所がマウントポイントになっていて、それぞれ/dev/sdd,/dev/sde,/dev/sdf,/dev/sdg,/dev/sdhにxfsでファイルシステムを作成している。
さらに、同じディスク構成でホストdb1とdb2とでHADRを構成している。適宜HADRの状態がどうなるかもみておく。
また、強制テイクオーバー前にバックアップを取得し、リストア後に元のPRIMARY/STANDBYの状態に復帰できるかも合わせて確認する。

なお、ディスククローンできるような高級なストレージ装置など持ち合わせていないので、~~VMwareの仮想ディスクをまるごとコピー・コピーバックすること~~ tarコマンドで、ストレージ機能[^1]の代替とする。

[^1]: AWSのEBSスナップショットなどでも同様

```text:fstab
/dev/sdd		/hadrdb/dbpath		xfs	defaults	0 0
/dev/sde		/hadrdb/tbsp1		xfs	defaults	0 0
/dev/sdf		/hadrdb/tbsp2		xfs	defaults	0 0
/dev/sdg		/hadrdb/trnlog		xfs	defaults	0 0
/dev/sdh		/hadrdb/arclog		xfs	defaults	0 0
```

想定シナリオは以下。

+ シナリオ１：オンライン中のとある時点でバックアップを取得し、さらにトランザクションが入った後に表スペース障害発生
+ シナリオ２：表スペースとログも逝ってしまった（多重障害）
+ シナリオ３：バックアップ取得後に被災してテイクオーバーしたものの、その後障害が発生
+ シナリオ４：被災訓練を想定して事前にバックアップ、STANDBYが強制テイクオーバーし、しばらくトランザクションが入ったのちにリストア（テイクオーバー後のアーカイブログがSTANDBYに残った状態）

それでは、状況開始！！！

# シナリオ１（オンライン中表スペース障害）
## オンライン中
空っぽの状態でバックアップ・リストアしても虚しい（というよりもテスト要件を満たさない）ので、オンラインの取引を想定してDBを更新しながらいろいろとやってみる。

```bash:PRIMARYでのトランザクション
db2 "CONNECT TO HADRDB"
db2 "CREATE TABLE Hogetbl (id INTEGER, desc VARCHAR(32))"
db2 "INSERT INTO Hogetbl VALUES (1, 'hoge1')"
db2 "TERMINATE"
```

## バックアップ取得

```bash:PRIMARY
db2 "CONNECT TO HADRDB"

# バックアップ前にバッファプールからディスクへ書き出し
db2 "FLUSH BUFFERPOOLS ALL"

# バックアップに備えて書き込み抑止
# INCLUDE LOGSでログ書き込みも停止（デフォルト動作）
db2 "SET WRITE SUSPEND FOR DB INCLUDE LOGS"

# この時点のログ状態確認
find /hadrdb -name "*.LOG"

# ログ出力も抑止されているため、更新が待たされることを確認
db2 "INSERT INTO Hogetbl VALUES (2, 'hoge2')"
```

バックアップ時点のログの状態は以下の通り。

| 項目 | 保持ファイル |
|:-:|:-:|
|PRIMARYアクティブログ   | S0000022.LOG〜S0000034.LOG  |
|PRIMARYアーカイブログ   | S0000019.LOG〜S0000022.LOG  |
|STANDBYアクティブログ   | S0000011.LOG〜S0000032.LOG  |
|STANDBYアーカイブログ   | S0000007.LOG,S0000008.LOG,S0000011.LOG  |

ここで物理バックアップ（tarで代替）。DBパス、表スペース、アクティブログ領域を対象に含め、PRIMARY/STANDBYそれぞれ実施。

```bash:PRIMARY/STANDBY
cd /; tar cvf /tmp/dbbkup.tar ./hadrdb
```

## バックアップ取得後
```bash:PRIMARY
db2 "CONNECT TO HADRDB"
# サスペンドを解除
db2 "SET WRITE RESUME FOR DB"
```

この時点でようやくバックアップ直前に実行したhoge2のSQLがリターンする（文末の参考資料にある通り、タイプ２接続はデフォルト無限待ち）。

## バックアップ完了後のオンライン再開中
```bash:PRIMARY
# さらに更新を続ける
for i in $(seq 3 10); do
db2 "INSERT INTO Hogetbl VALUES ( ${i}, 'hogehoge')"
done

# 強制アーカイブ
db2 "TERMINATE"
db2 "ARCHIVE LOG FOR DB HADRDB"

# 念の為HADR状態確認（peer）
db2pd -hadr -db hadrdb

# ログ状態確認
find /hadrdb -name "*.LOG"
```

| 項目 | 保持ファイル |
|:-:|:-:|
|PRIMARYアクティブログ   | S0000023.LOG〜S0000035.LOG  |
|PRIMARYアーカイブログ   | S0000019.LOG〜S0000023.LOG  |
|STANDBYアクティブログ   | S0000012.LOG〜S0000032.LOG  |
|STANDBYアーカイブログ   | S0000007.LOG,S0000008.LOG,S0000011.LOG  |

## 障害発生
表スペース領域のファイルシステムに障害が発生したと想定する（ログは生きている）。

```bash:擬似障害
rm -rf /hadrdb/tbsp[12]/*
```

## 復旧
ログは損傷していないため、DBパスと表スペース領域のみリストアする。

```bash:PRIMARY復旧
db2stop  # インスタンス停止

# 物理バックアップからリストア
cd /; rm -rf /hadrdb/dbpath/* /hadrdb/tbsp[12]/*
tar xvf /tmp/dbbkup.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2

db2start # インスタンス起動
```

DBをACTIVATEすると、

```bash:PRIMARY
db2 "ACTIVATE DB HADRDB"
```

```text
SQL1552N  The command failed because write operations for the database are 
suspended or are being suspended.
```

サスペンド状態で復旧してくるので、まずはこれを解除する必要がある。とはいえ、バックアップ完了後に実行した"SET WRITE RESUME FOR DB"はDBコネクションが前提となるが、この場合はCONNECTすらできない。
やり方が２通りあるのでそれぞれ見てみる。

## PRIMARY復旧後のサスペンド解除
### RESUME RESTART
```bash:PRIMARY
# WRITE RESUMEで再起動
db2 "RESTART DB HADRDB WRITE RESUME"
```

```text
SQL1273N  An operation reading the logs on database "HADRDB" cannot continue 
because of a missing log file "S0000022.LOG" on database partition "0" and log 
stream "0".
```

ログ#22がないといわれたのでアーカイブにある#22をアクティブログディレクトリにコピーしてもう一度リスタートする。

```bash:PRIMARY
cp /hadrdb/arclog/dbinst/HADRDB/NODE0000/LOGSTREAM0000/C0000004/S0000022.LOG  /hadrdb/trnlog/NODE0000/LOGSTREAM0000/
db2 "RESTART DB HADRDB WRITE RESUME"
```

```bash:PRIMARY
# HADRとログ状況確認
db2pd -hadr -db hadrdb
find /hadrdb -name "*.LOG"
```

```text:db2pd@PARIMARY
HADR_STATE = PEER
```

| 項目 | 保持ファイル |
|:-:|:-:|
|PRIMARYアクティブログ   | S0000024.LOG〜S0000036.LOG  |
|PRIMARYアーカイブログ   | S0000019.LOG〜S0000023.LOG  |
|STANDBYアクティブログ   | S0000012.LOG〜S0000032.LOG  |
|STANDBYアーカイブログ   | S0000007.LOG〜S0000008.LOG,S0000011.LOG  |

DBの内容を確認する。

```bash:PRIMARY
db2 "CONNECT TO HADRDB"
db2 "SELECT * FROM Hogetbl"
```

クラッシュリカバリにより障害発生直前のid=10までもどった。

### db2inidb
状況を巻き戻し、障害直前から状況再開。

```bash:擬似障害から復旧まで
### 擬似障害
rm -rf /hadrdb/tbsp[12]/*

### 復旧
db2stop  # インスタンス停止
cd /; rm -rf /hadrdb/dbpath/* /hadrdb/tbsp[12]/*
tar xvf /tmp/dbbkup.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2
db2start # インスタンス起動
```

```bash:PRIMARY
# ロールフォワードペンディング状態へ
db2inidb HADRDB as mirror

# 確認
db2 GET DB CFG FOR HADRDB | grep -i pend

# ロールフォワード実行
db2 "ROLLFORWARD DB HADRDB TO END OF LOGS"
db2 "ROLLFORWARD DB HADRDB STOP"

# 検索してみる
db2 "CONNECT TO HADRDB"
db2 "SELECT * FROM Hogetbl"  # id=10までロールフォワード
```

HADBの状態を確認する。

```bash:PRIMARY
db2pd -hadr -db hadrdb

# HADR is not active.ときたので再始動
db2 "START HADR ON DB HADRDB AS PRIMARY"

db2pd -hadr -db hadrdb
```

PEERになった。

# シナリオ２（オンライン中に表スペースとアクティブログ領域障害）
HADR含め、データは整合しているので、このままオンラインを継続する。

## オンラインバックアップ
```bash:PRIMARYオンライン
db2 "CONNECT TO HADRDB"
db2 "INSERT INTO Hogetbl VALUES (11, 'hoge11')"
```

```bash:バックアップ
db2 "FLUSH BUFFERPOOLS ALL"
db2 "SET WRITE SUSPEND FOR DB INCLUDE LOGS"
cd /; tar cvf /tmp/dbbkup2.tar ./hadrdb
find /hadrdb -name "*.LOG"
db2 "SET WRITE RESUME FOR DB"
```

ログの状態は以下の通り。

| 項目 | 保持ファイル |
|:-:|:-:|
|PRIMARYアクティブログ   | S0000025.LOG〜S0000037.LOG  |
|PRIMARYアーカイブログ   | S0000019.LOG〜S0000025.LOG  |
|STANDBYアクティブログ   | S0000015.LOG〜S0000035.LOG  |
|STANDBYアーカイブログ   | S0000007.LOG,S0000008.LOG,S0000011.LOG  |

## オンライン中
```bash:PRIMARY
# オンライン中（ログアーカイブも発生）
for i in $(seq 21 30); do
db2 "INSERT INTO Hogetbl VALUES (${i}, 'geho')"
done
db2 "TERMINATE"
db2 "ARCHIVE LOG FOR DB HADRDB"
db2 "CONNECT TO HADRDB"
for i in $(seq 31 40); do
db2 "INSERT INTO Hogetbl VALUES (${i}, 'geho')"
done
find /hadrdb -name "*.LOG"  # アーカイブには#19〜#26まで
```

## 障害発生（表スペースとログの多重）
```bash:PRIMARY
rm -rf /hadrdb/tbsp[12]/* /hadrdb/trnlog/*
```

## 復旧（表スペースとログ）
PRIMARYはログも壊れているので、バックアップ取得時点に（つまりid=11追加まで）戻る。STANDBY側が進んだ状態になってしまうので、STANDBY側も復旧が必要になる。

```bash:PRIMARY/STANDBY
db2stop force
cd /;
rm -rf /hadrdb/dbpath/* /hadrdb/tbsp[12]/* /hadrdb/trnlog/*
tar xvf /tmp/dbbkup2.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2 ./hadrdb/trnlog
db2start
```

## リカバリスタート
```bash:STANDBY
db2 "ACTIVATE DB HADRDB"
```

```bash:PRIMARY
db2 "RESTART DB HADRDB WRITE RESUME"
db2pd -hadr -db hadrdb
db2 "CONNECT TO HADRDB"
db2 "SELECT * FROM Hogetbl"
```

id=11まで戻り、HADRもpeer。

# シナリオ３（STANDBY強制TAKEOVER後の表スペース障害）
データはid=11まで入っている状態から状況開始。

```bash:PRIMARYオンライン
db2 "CONNECT TO HADRDB"
db2 "INSERT INTO Hogetbl VALUES(12, 'piyo')"
db2 "FLUSH BUFFERPOOLS ALL"
db2 "SET WRITE SUSPEND FOR DB INCLUDE LOGS"

cd /; tar cvf /tmp/dbbkup3.tar ./hadrdb  # PRIMARY/STANDBY

db2 "SET WRITE RESUME FOR DB"
for i in $(seq 13 20); do
  db2 "INSERT INTO Hogetbl VALUES(${i}, 'geho')"
done
db2 "TERMINATE"
db2 "ARCHIVE LOG FOR DB HADRDB"
db2 "CONNECT TO HADRDB"
db2 "INSERT INTO Hogetbl VALUES(21, 'gehhogehho')"
# --- 被災想定 ---
db2 "TERMINATE"
db2 "DEACTIVATE DB HADRDB"
db2stop
```

```bash:STANDBY→PRIMARY昇格
# 強制テイクオーバー
db2 "TAKEOVER HADR ON DB HADRDB BY FORCE"
db2 "CONNECT TO HADRDB"
db2 "SELECT * FROM Hogetbl"
# 旧PRIMARYで最後に追加したid=21まで存在

db2 "INSERT INTO Hogetbl VALUES(22, 'standbyhoge')" # さらに更新
```

泣きっ面に蜂でここで障害が発生（オフサイトの構成をケチるからこうなる？）。バックアップ断面（プライマリサスペンド中）から戻す。

```bash:STANDBY表スペース障害からの復旧
rm -rf /hadrdb/tbsp[12]/*  # 表スペース障害
db2stop force              # インスタンス停止
rm -rf /hadrdb/dbpath/*
cd /; tar xvf /tmp/dbbkup3.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2
db2start
```

```bash:STANDBY（ディスクから戻しているのでPRIMARY昇格取り消し状態）
db2 "ACTIVATE DB HADRDB" # これはうまくいく
db2 "CONNECT TO HADRDB"  # SQL1776N  The command cannot be issued on an HADR database. Reason code = "1".
db2 "TAKEOVER HADR ON DB HADRDB BY FORCE"
```

```text
SQL1273N  An operation reading the logs on database "" cannot continue because 
of a missing log file "" on database partition "" and log stream "".
```

ログがないので致命的に動けなくなってしまう。STANDBY時はPRIMARYの言いなりでログを管理し、テイクオーバーした時点でようやく自分でログの管理を始める。この場合、メタデータや表スペースがSTANDBY状態に戻るのに、ログだけはテイクオーバー（昇格）後のデータしか持っていないため、テイクオーバー時に必要なログがない。
状況を巻き戻して、再度ログ領域も含めてリストア実施する必要がある（つまりTAKEOVER直前まで戻る）。

```bash:STANDBY
db2stop force
rm -rf /hadrdb/dbpath/* /hadrdb/tbsp[12]/* /hadrdb/trnlog/*
cd /; tar xvf /tmp/dbbkup3.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2 ./hadrdb/trnlog
db2start

db2 "ACTIVATE DB HADRDB"
db2pd -hadr -db hadrdb  # STANDBYのまま
db2 "TAKEOVER HADR ON DB HADRDB BY FORCE"
db2pd -hadr -db hadrdb  # PRIMARY昇格
db2 "CONNECT TO HADRDB"
db2 "SELECT * From Hogetbl"  # id=12の時点（つまりバックアップ取得時点）まで戻った
```

というわけで、強制TAKEOVERしたら早めにバックアップを取っておいた方が安心できる。

# シナリオ４（テイクオーバーのフォールバック）
さっきの被災は実はフェイク（訓練）だった。よかった、誰も悲惨な目にあってはいなかった。
というわけでもともとの構成に戻さなくてはならない場合。こういうときはPRIMARY/STANDBY側で全て（アクティブログも含めて）を戻してしまえばよい。のだが、ここでひとつ注意しておかなければいけない点がある。
HADRのSTANDBYでは、通常時ログのアーカイブは行わず、テイクオーバーした時点で自律的にログのアーカイブを開始する。つまり、テイクオーバー直前までCURRENTだったログが、テイクオーバー後にアーカイブされ（仮にS0010.LOGとする）、DBパス、表スペースおよびアクティブログのすべてを戻した場合、STANDBYだったときのS0010.LOGがアクティブログディレクトリに復元される。アーカイブログとアクティブログの両方に同じシーケンスナンバーのログがあり、かつアーカイブログのほうが新しいという状態になる。
この状態でPRIMARYでクラッシュリカバリ（RESTART DB）したときに、アーカイブにあるログからアクティブログ領域へログファイルを持ってくるという動作をする。すると、PRIMARYとSTANDBYとでLSNが不整合（PRIMARY < STANDBY）となり、クラッシュリカバリに失敗する（STANDBYデータベースが落ちる）。
なお、RESTART DBする前にPRIMARY側でACTIVATEコマンドを実行するとエラーになるが、この後のRESTART DB時にSTANDBY側でこのようなログの更新は行われないように見える。またRESTART DBではなく`db2inidb`からのROLL FORWARDではこの現象は発生しない。
いずれにせよ、STANDBYにアーカイブログはいらないので、訓練やテストなどでテイクオーバーした後は、戻しのときにSTANDBY側のアーカイブログを削除しておくのが吉。


```bash:PRIMARY/STANDBY
db2stop force
# STANDBY
rm -rf /hadrdb/arclog/*

# PRIMARY/STANDBY
rm -rf /hadrdb/dbpath/* /hadrdb/tbsp[12]/* /hadrdb/trnlog/*
cd /; tar xvf /tmp/dbbkup3.tar ./hadrdb/dbpath ./hadrdb/tbsp1 ./hadrdb/tbsp2 ./hadrdb/trnlog
db2start

# STANDBY
db2 "ACTIVATE DB HADRDB"
# PRIMARY
db2 "RESTART DB HADRDB WRITE RESUME"
db2 "CONNECT TO HADRDB"
db2 "SELECT * FROM Hogetbl" # id=12まで
db2 "TERMINATE"
db2pd -hadr -db hadrdb      # peer
```

# ポイントを簡単にまとめ
## バックアップ・リカバリの留意点
物理バックアップでは、論理バックアップ（BACKUP DATABASE）でDb2が自動でやってくれるような各データの整合性確保を自分でやらなければならない。RPOがバックアップ取得時であれば、ログも含めてその時点まで戻してやるのがシンプルではある。
実際には、表スペースだけ障害になったのか、つまりログが生きているのかどうかすぐにはわからないケースもあるので、まずは表スペースとDBパスを戻してdb2inidbからのROLL FORWARD（またはRESTART DBでクラッシュリカバリ）を試み、ダメだったらログも戻す等、状況に応じてやれるところまでやるしかない。

## HADRでの留意点
STANDBY側ではPRIMARY側からログを制御され、TAKEOVER後にSTANDBY側で設定した内容で動く。TAKEOVER時にSTANDBY時代のログは切り捨てられて整理される。このため、TAKEOVER前に取った表スペース・DBパスを戻すとログと整合性がとれなくなる
どこまで復旧要件があるのか次第だが、被災したその日にリストアが必要になるようなケースを想定する、あるいは想定しなくてもよい理由が思い浮かばないのなら、TAKEOVER後にも同じようにバックアップをとってよくのが吉。

# 参考資料
[SET WRITEコマンド](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.1.0/com.ibm.db2.luw.admin.cmd.doc/doc/r0009503.html)
[RESTART DATABASEコマンド](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.1.0/com.ibm.db2.luw.admin.cmd.doc/doc/r0001974.html)
[db2inidbコマンド](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.1.0/com.ibm.db2.luw.admin.cmd.doc/doc/r0004473.html)
[JDBCタイムアウトパラメータ](https://www-01.ibm.com/support/docview.wss?uid=jpn1J1011445&aid=2)