Db2のHADR構成構築メモ

[前回](https://qiita.com/ch7821/items/7f53340032a4d639a146)Db2のネイティブ暗号化の構築覚書に続いて、暗号化したDBをHADR（High Availability Disaster Recovery）構成にするところまでの覚書。
HADRはいわゆるRDBMSのレプリケーション機能で、OracleならData Guard、MySQLならそのまんまレプリケーション（名前ないのかな？）、HiRDBならData Replicatorに相当する機能。

完成後はこのような構成になる。PRIMARYのDB01はDb2ネイティブ暗号化による暗号化済み。
<img width="513" alt="スクリーンショット 2018-10-28 14.39.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/574f4b8b-91f6-110a-553f-1250c11a7515.png">

| 項目 | PRIMARY | STANDBY |
|:-:|:-:|:-:|
|ホスト名   | db1  | db2  |
|インスタンス名   | dbinst  | dbinst  |
|DB名   | DB01  | DB01  |
|HADRポート[^1]|db2_hadr（55555/tcp）|db2_hadr（55555/tcp）|

PRIMARY、STANDBYのホスト名はそれぞれ"db1"と"db2"で、それぞれインスタンス名（インスタンスユーザ名）は"dbinst"。
HADR構成の要件としては、インスタンス名は必ずしも同じでなくてよいものの、DB名は同じでなければならない。
物理パスは同じでなくても動作するが、初期構築時にPRIMARYからSTANDBYへDBをリストアするので、同じ方が簡単（物理パスが異なるとリストアが面倒になる）。つまり一旦、同じ構成のDBがSTANDBY側にもできて、そこからSTANDBY側固有の設定をすることになる。
また、ログはアーカイブモードが必須（循環ログはNG）。
同様に、プライマリで暗号化している場合は、同じ暗号化キー（DBの暗号化鍵を暗号化するマスターキーを含むキーストア）をスタンバイにも登録する必要がある。

[^1]: マニュアルには「個別の」ポートが必要と記載があるが、同じポート番号が不可とは明記されていない。

# 前提環境
| 項目 | 値 | 
|:-:|:-:|
|OS   | RHEL7.3  |
|Db2   | v11.1.3.3  |
|DB PATH| /db/db01/database |
|Storage| /db/db01/tbsp1,/db/db01/tbsp2|
|Log| /db/db01/log1|
|Mirror Log| /db/db01/log2|
|Log ArchMeth1| DISK:/db/db01/logarch |

※VMware Fusion上の仮想マシンとして構築

# DB作成

## DBディレクトリ作成（PRIMARY/STANDBYそれぞれ）
```bash
mkdir /db/db01
cd /db/db01; pwd
mkdir database tbsp1 tbsp2 log1 log2 logarch
```

## DB作成（PRIMARYのみ）
```bash
db2 "CREATE DB DB01 
AUTOMATIC STORAGE YES
ON /db/db01/tbsp1,/db/db01/tbsp2
DBPATH ON /db/db01/database
USING CODESET UTF-8 TERRITORY JP
COLLATE USING IDENTITY
PAGESIZE 4 K
ENCRYPT CIPHER AES KEY LENGTH 256 MASTER KEY LABEL MasterKey"
```

## DB設定更新（PRIMARYのみ）
```bash
db2 "UPDATE DB CFG FOR DB01 USING NEWLOGPATH /db/db01/log1"
db2 "UPDATE DB CFG FOR DB01 USING MIRRORLOGPATH /db/db01/log2"
db2 "UPDATE DB CFG FOR DB01 USING LOGARCHMETH1 DISK:/db/db01/logarch"
db2 "UPDATE DB CFG FOR DB01 USING LOGINDEXBUILD ON"
```

# HADR構成
## PRIMARYでバックアップ取得
NFSでPRIMARY、STANDBYからアクセス可能な領域に出力。NFSが使えなければSCPなりHULFTなりでSTANDBYへ転送。

```bash
db2 "BACKUP DB DB01 TO /nfs/db01"
```

## STANDBYにリストア
```bash
db2 "RESTORE DB DB01 FROM /nfs/db01 ENCRYPT"
```
暗号化しているので`ENCRYPT`オプション必須。
ただし、先にSTANDBYでもCREATE DATABASEをENCRYPTつきで実行済みならENCRYPT不要（つけるとエラー）。BACKUP DBのバックアップデータはデフォルト暗号化されない点には一応注意。

## PRIMARY・STANDBYでHADRポート番号追加
```bash
echo "db2_hadr    55555/tcp" >>/etc/services
```

## ローカルHADR設定（PRIMARY）
```bash
db2 "UPDATE DB CFG FOR DB01 USING
    HADR_LOCAL_HOST   db1
    HADR_LOCAL_SVC    db2_hadr
    HADR_SYNCMODE     ASYNC
"
```

## ローカルHADR設定（STANDBY）
```bash
db2 "UPDATE DB CFG FOR DB01 USING
    HADR_LOCAL_HOST   db2
    HADR_LOCAL_SVC    db2_hadr
    HADR_SYNCMODE     ASYNC
"
```

## リモートHADR設定（PRIMARY）
```bash
db2 "UPDATE DB CFG FOR DB01 USING
    HADR_REMOTE_HOST db2
    HADR_REMOTE_SVC  db2_hadr
    HADR_REMOTE_INST dbinst
"
```

## リモートHADR設定（STANDBY）
```bash
db2 "UPDATE DB CFG FOR DB01 USING
    HADR_REMOTE_HOST db1
    HADR_REMOTE_SVC  db2_hadr
    HADR_REMOTE_INST dbinst
"
```

## スタンバイでHADR開始
```bash
db2 "START HADR ON DB DB01 AS STANDBY"
db2pd -hadr -db DB01
```

## プライマリでHADR開始
```bash
db2 "START HADR ON DB DB01 AS PRIMARY"
db2pd -hadr -db DB01
```

## 状態確認
```bash
db2pd -hadr -db DB01
```

```text:抜粋
                            HADR_ROLE = STANDBY
                           HADR_STATE = PEER
                  HADR_CONNECT_STATUS = CONNECTED
```
以下を確認する。

* HADR_ROLEがそれぞれPRIMARY、STANDBYになっていること
* HADR_STATEがPEERになっていること
* HADR_CONNECT_STATUSがCONNECTEDになっていること

# 同期確認

## PRIMARYでテーブル作成
```bash
db2 "CONNECT TO DB01"
db2 "CREATE TABLE Hogetbl(hoge INTEGER PRIMARY KEY NOT NULL, geho VARCHAR(16))"
db2 "INSERT INTO Hogetbl VALUES (1,'hogehoge')"
```

## STANDBYで確認
### STANDBYをPRIMARYに昇格（PRIMARYはSTANDBYへ降格）
```bash
db2 "TAKEOVER HADR ON DB DB01"
db2pd -hadr -db DB01
```
通常、STANDBYであるうちはCONNECTできないが、[レジストリ変数（DB2_HADR_ROS）を設定することでアクセス可能](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.1.0/com.ibm.db2.luw.admin.ha.doc/doc/t0054260.html)。

### SELECTしてみる
```bash
db2 "CONNECT TO DB01"
db2 "SELECT * FROM Hogetbl"
```
無事に旧PRIMARYのデータが引継がれた。

### 元に戻す
```bash:PRIMARYで実行
db2 "TAKEOVER HADR ON DB DB01"
db2pd -hadr -db DB01
```

# 起動と停止
## 停止
停止するときは、PRIMARYからSTANDBYの順。

```bash:PRIMARY
db2 "DEACTIVATE DB DB01"
db2stop
```

```bash:STANDBY
db2 "DEACTIVATE DB DB01"
db2stop
```

## 起動
起動は逆にSTANDBYからPRIMARYの順。

```bash:STANDBY
db2start
db2 "ACTIVATE DB DB01"
```

```bash:PRIMARY
db2start
db2 "ACTIVATE DB DB01"
```

## HADR構成の破棄（PRIMARY、STANDBYそれぞれ実施）
```bash
db2 "DEACTIVATE DB DB01"
db2 "STOP HADR ON DB DB01"
```

これをやるとHADR環境は作り直しが必要になる可能性がある（トランザクションがPRIMARY側で大量に発生して、PRIMARY/STANDBY間の整合性が取れなくなるなど）。
なにも更新がなければ再度`START HADR ON DB DB01 AS PRIMARY/STANDBY`で再開始可能。

# いろいろ実験
## バックアップとリストア
バックアップはPRIMARY側で実行する（STANDBYでは取得不可）。オフライン・オンラインいずれも可能。

### リストアして障害発生直前までロールフォワード

```bash
db2 "DEACTIVATE DB DB01"
db2 "BACKUP DB DB01 TO /db/db01/backup"
```

ここでDBを更新してみる。

```bash
db2 "ACTIVATE DB DB01"
db2 "CONNECT TO DB01"
db2 "INSERT INTO Hogetbl VALUES(2, 'gehogeho')"
db2 "TERMINATE"
```

ここで障害が発生したと想定し、リストア＆ロールフォワードしてから、HADRのPRIMARYとして再始動する。

```bash
db2 "DEACTIVATE DB DB01"
db2 "RESTORE DB DB01 FROM /db/db01/backup TAKEN AT 20181028114425"
db2 "ROLLFORWARD DB DB01 TO END OF LOGS AND STOP"
db2 "ACTIVATE DB DB01"
db2 "CONNECT TO DB01"
db2 "SELECT * FROM Hogetbl"
db2 "START HADR ON DB DB01 AS PRIMARY"
db2 "db2pd -hadr -db DB01"
```
しばらくHADR_STATE = REMOTE_CATCHUP_PENDINGになっていたがPEERに戻った。


## 壊してみるその１（中途半端にリストア）
ログから最後までロールフォワードせず、バックアップ時点までしかもどさなかった（戻らなかった）場合。
まずは先ほどと同様にバックアップ取得。

```bash
db2 "DEACTIVATE DB DB01"
db2 "BACKUP DB DB01 TO /db/db01/backup"
```

DBを更新する。

```bash
db2 "ACTIVATE DB DB01"
db2 "CONNECT TO DB01"
db2 "INSERT INTO Hogetbl VALUES(3, 'hogehogehoge')"
db2 "TERMINATE"
```

障害発生！リストアする。

```bash
db2 "DEACTIVATE DB DB01"
db2 "RESTORE DB DB01 FROM /db/db01/backup TAKEN AT 20181028115805"
db2 "ROLLFORWARD DB DB01 TO END OF BACKUP AND STOP"
db2 "ACTIVATE DB DB01"
db2 "CONNECT TO DB01"
db2 "SELECT * FROM Hogetbl"
```

当然、3個目のレコードはない。ここでHADRを再開しようとすると、、、

```bash
db2 "START HADR ON DB DB01"
SQL1768N  Unable to start HADR. Reason code = "7".
```

`db2 "? SQL1768N"`で調べると、Reason code 7はSTANDBYとの通信タイムアウト。DB構成パラメタ上もSTANDARDになっている。

```bash:出力抜粋
db2 "GET DB CFG FOR DB01" | grep -i hadr
 HADR database role                                      = STANDARD
```

STANDBY側を調査する。

```bash
db2pd -hadr -db DB01
Database DB01 not activated on database member 0 or this database name cannot be found in the local database directory.
```

HADRが落ちているのでdb2diagで調べると。

```bash:出力抜粋
db2diag -l Severe
MESSAGE : ZRC=0x8610000D=-2045771763=SQLP_BADLOG "Log File cannot be used"
          DIA8414C Logging can not continue due to an error.
SQL1034C  The database was damaged, so all applications processing 
          the database were stopped.
```

あたりまえだが、PRIMARYとSTANDBYとでログの状態が変わったので、これ以上HADRはできないということで、STANDBY側のHADRが異常終了した。
これを修復するには再度PRIMARYでバックアップを取得し、これをSTANDBYへリストアするしかない（HADR初期構成）。
Directoryサーバのレプリケーションではオンライン初期化ができるのに、、、

### 復旧
```bash:PRIMARY側
db2 "BACKUP DB DB01 TO /nfs/db01"
```

```bash:STANDY側
db2 "STOP HADR ON DB DB01"
db2 "DROP DB DB01"                            # すでに存在するDBをドロップ
db2 "RESTORE DB DB01 FROM /nfs/db01 ENCRYPT"  # 最新バックアップからリストア
# HADR関連パラメタ変更
db2 "UPDATE DB CFG FOR DB01 USING
  HADR_LOCAL_HOST db2
  HADR_LOCAL_SVC db2_hadr
  HADR_REMOTE_HOST db1
  HADR_REMOTE_SVC db2_hadr
"
db2 "START HADR ON DB DB01 AS STANDBY"
```

```bash:PRIMARY側
db2 "START HADR ON DB DB01 AS PRIMARY"
db2pd -hadr -db DB01
```

といわけで超面倒。ログの不整合が起きると大変なので気をつけましょう。

## 壊してみるその２（STANDBYが計画停止）
STANDBYが計画停止している間にPRIMARYのログが全部切り替わってしまった状況を想定（PRIMARYのアーカイブログを使ってSTANDBYをPEERまで持っていってくれるかどうかの検証）。

```bash:STANDBY側計画停止
db2 "DEACTIVATE DB DB01"
db2stop
```

```bash:PRIMARY側（データ更新しながらログをアーカイブへ逃がす）
# 現在のアクティブログを確認
db2 "GET DB CFG FOR DB01" | grep "First active"
# ついでにログ設定も確認
db2 "GET DB CFG FOR DB01" | grep -e "LOGPRIMARY" -e "LOGSECOND"

# 更新とログアーカイブの繰り返し
for i in $(seq 5 50); do
db2 "CONNECT TO DB01"
db2 "INSERT INTO Hogetbl VALUES(${i}, 'hogehoge')"
db2 "TERMINATE"
db2 "ARCHIVE LOG FOR DB DB01"
done

# ログとHADR状況確認
find /db/db01/log1/NODE0000 -name "*.LOG"

db2pd -hadr -db DB01 | grep _LOG_FILE
            PRIMARY_LOG_FILE,PAGE,POS = S0000160.LOG, 0, 741864654
            STANDBY_LOG_FILE,PAGE,POS = S0000110.LOG, 2, 533182206
     STANDBY_REPLAY_LOG_FILE,PAGE,POS = S0000110.LOG, 2, 533182206

```

この時点でのログの状態は以下の通りだった。

| 項目 | 状況 |
|:-:|:-:|
| PRIMARYのFirst active log  | S0000160.LOG  |
| PRIMARYのアクティブログ保持状況  | S0000159.LOG〜S0000171.LOG（LOGPRIMARY=13）  |
| STANDBYのFirst active log相当| S0000110.LOG|

つまり、STANDBY側はPRIMARYのアクティブログからは追いつけない状況になっている。

```bash:STANDBY側復旧
db2start
db2 "ACTIVATE DB DB01"
db2pd -hadr -db DB01
```

"HADR_STATE"は"REMOTE_CATCHUP"からしばらくして"PEER"になった。
このことから、PRIMARYのアーカイブログも使って、STANDBY側のDBに追いつきをかけてくれると想定される。