Db2:ロックウェイトが発生したら通知したい

# 背景
Db2 LUWでは**デッドロック**と**ロックタイムアウト**はdb2diag(テキストファイル)に出力されます。リアルタイムで発生を検知したい場合、スクリプトでdb2diagをチェックすることが可能です。ですが、**ロックウェイトはdb2diagには出力されません。** ロックタイムアウトになる前のロックウェイトも時間が長いのは通知してほしいです。

Db2のロックについては以下の記事もご覧ください。
[勉強会:Db2問題判別 ロック タイムアウトと長時間SQL](https://qiita.com/tsunenorikan/items/b46271eaa07e4726b392 "Qiita")

# やりたいこと
ロックウェイトが発生したら、リアルタイムに検知してメッセージングアプリで通知したい。
環境は IBM Db2 V11.5 Linux と Ubuntu 20.04 LTSです。

# MON_GET_APPL_LOCKWAIT 表関数

現時点の保持しているロックを表示するDb2の表関数です。SQLでselectして照会します。
**current timestamp**と**lock_wait_start_time**との差分をwhere条件に指定すると指定時間を超えてロックを保持している一覧を表示できます(例は30秒)。列が多いので必要な列のみをselectします。

```sql:lockwait.sql
SELECT lock_wait_start_time,
       lock_status,
       lock_mode,
       lock_current_mode
FROM   TABLE (Mon_get_appl_lockwait(NULL, -2))
WHERE  SECOND(CURRENT TIMESTAMP - lock_wait_start_time) > 30; 
```

列の詳細についてはIBMのウェブサイトをご覧ください。
[MON_GET_APPL_LOCKWAIT 表関数 - アプリケーションが待機しているロックについての情報の取得](https://www.ibm.com/docs/ja/db2/11.5?topic=mpf-mon-get-appl-lockwait-table-function-get-information-about-locks-which-application-is-waiting "IBM")

Db2コマンド・ウィンドウからSQLを実行します。
db2 -tf lockwait.sql

```log:lockwait.sqlの実行結果
$ db2 -tf lockwait.sql

LOCK_WAIT_START_TIME       LOCK_STATUS LOCK_MODE LOCK_CURRENT_MODE
-------------------------- ----------- --------- -----------------

  0 レコードが選択されました。


$ echo $?
1
$
```

Where条件に合致するものがない、つまり30秒を超えたロックがない場合、対象レコードはありません。**直後の戻り値はレコードがある場合は0になり、ない場合は1になります。** 例ではレコードがないため、echoで確認した戻り値は1になっています。

この表関数を**毎分select**して、レコードが存在するかを**戻り値**で判断します。

# 確認用スクリプトの作成

以下の処理を行うスクリプトを作成します。
1. データベース接続
1. 表関数の実行(SQLファイルを実行)
1. 戻り値の判断
1. メッセージングアプリの起動(例はSlack)
1. データベース切断

```shell:check_LOCKWAIT_slack.sh
#!/bin/bash

DB_NAME=MYAPP01
SQL_DIR=/home/db2inst1/task
SLACK_URI='https://hooks.slack.com/services/hogehoge'
ICON_URI='https://hogehoge'
TITLE="【要確認】ロックウェイトの発生"

PGM_NAME=$(basename $0)
ICON_URI=${ICON_URI:-$ICON_URI}
TITLE=${TITLE:-$TITLE}
CRLF=${CRLF:-"\n"}
HOST_NAME=$(hostname)
BOTNAME=${BOTNAME:-$HOST_NAME}

db2 connect to $DB_NAME

db2 -tf $SQL_DIR/lockwait.sql

if [[ $? == 0 ]]   # レコードがある場合
  then
    echo ERR:$?
    MESSAGE1=${MESSAGE1:-"ロックイベントモニターを確認してください。"}
    MESSAGE2=${MESSAGE2:-$PGM_NAME}
    MESSAGE3=${MESSAGE3:-$DB_NAME}
    payload="payload={
        \"username\": \"${BOTNAME}\",
        \"icon_url\": \"${ICON_URI}\",
        \"text\": \"${TITLE}${CRLF}${MESSAGE1}${CRLF}${MESSAGE2}${CRLF}${MESSAGE3}\"
    }"
    curl -s -S -X POST --data-urlencode "${payload}" ${SLACK_URI}
    echo -n -e "\n"
  else
    echo OK:$?
fi

echo 接続終了
db2 connect reset
```
スクリプトの大半はメッセージングアプリ(Slack)を起動するためのロジックです。メッセージングアプリの部分を変更することにより、メールなどにも対応可能です。

# スケジュールの設定

cronで毎分実行するように設定します。
crontab -e
```
* * * * * sleep 10; source /home/db2inst1/sqllib/db2profile; /home/db2inst1/task/check_LOCKWAIT_slack.sh
```

# スクリプトの実行結果

30秒を超えたロックがない場合の実行結果です。
```
   データベース接続情報

 データベース・サーバー = DB2/LINUXX8664 11.5.6.0
 SQL 許可 ID            = DB2INST1
 ローカル・データベース別名 = MYAPP01

LOCK_WAIT_START_TIME       LOCK_STATUS LOCK_MODE LOCK_CURRENT_MODE
-------------------------- ----------- --------- -----------------

  0 レコードが選択されました。

OK:1
接続終了
DB20000I  SQL コマンドが正常に完了しました。
```
30秒を超えたロックがある場合はレコードが存在するので戻り値は0になり、メッセージングアプリが起動するロジックに入ります。

# まとめ
1分に1回の収集なので全てをロックを補足できるわけではありませんが、ロックは発生すると立て続けに発生するので実際に運用すると通知が来て便利でした。また、スクリプトとSQLを別ファイルにすることで記述するSQLの自由度が増したり、SQLのテストが簡単に行えたりするメリットがありました。