WebSphere Liberty でトランザクションのピア・リカバリーを試してみる(1/3)

WebSphere Liberty 21.0.0.9 からトランザクションのピア・リカバリーが利用できるようになったので、早速、動作を確認してみました。
尚、この機能が利用可能になったことは、下記の Open Liberty の blog で知りました。

- [MicroProfile 4.1, Transaction Peer Recovery, a new logging format and lots more in Open Liberty 21.0.0.9 - Transaction Peer Recovery](https://openliberty.io/blog/2021/09/03/microprofile-21009.html#transaction)


:::note warn
この記事は、Cloud やコンテナの環境で 2PC やピア・リカバリーの利用を積極的にお勧めするものではありません。
既存システムを単純に Cloud やコンテナに移行する場合や、システムの要件によっては、これらの機能が必要となることがあるかと思い、執筆しました。
:::

## トランザクションのピア・リカバリーってなに？
トランザクションのピア・リカバリー(以下、ピア・リカバリー)の話に入る前に、２フェーズ・コミット(2 phase commit, 以下、2pc)に関して、簡単に説明しておきます。2PC は、複数のリソース・マネージャー(Resource Manager, 以下、RM)に対して一貫性をもって更新を行うための仕組みです。RM と書きましたが、分かりやすく言うと、データベース(DB)やメッセージ指向ミドルウェア(MOM)などのことになります。WebSphere Liberty は、2PC において、トランザクション・マネージャー(Transaction Manager, 以下、TM)の役割を担い、トランザクションの一貫性を保つ役割を担います。例えば、WebSphere Liberty で稼働するアプリケーションが、DB にデータを insert し、MOM にメッセージを put する処理を１つのトランザクションとして実行する場合を図示すると、次のようになります。WebSphere Liberty の右側にあるディスクの絵は、TM が 2PC トランザクションの状況を記録するために使用するトランザクション・ログを示しています。
![2pc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555650/84fd9e9d-b211-445d-fab8-09d50ca16d4c.png)
アプリケーション、TM (WebSphere Liberty の一部)、トランザクション・ログ、２つの RM が出てきました。2PC トランザクションのフローを分かりやすくシーケンス図的に示すと次のようになります。2PC の詳細なフローはより複雑ですが、必要な部分だけを描いてあります。

![2pcフロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555650/db3edf25-02cb-d8eb-3fff-00b83e7ee1d0.png)

1. アプリケーションがトランザクションを開始します。
1. アプリケーションが DB にデータを insert します。
1. アプリケーションが MOM にメッセージを put します。 
1. アプリケーションがトランザクションの commit を要求します。
1. TM が MOM と DB に prepare を要求し、各々の prepare が完了します。
1. TM がトランザクション・ログにトランザクションの prepare  完了を記録します。
1. TM が DB と MOM に commit を要求し、各々の commit が完了します。
1. トランザクションが完了します。

:::note info
上記の図や説明では、RM にアクセスした順序と逆の順序で prepare が行われ、prepare の順序と逆の順序で commit が実行されるようになっています。仕様やマニュアルで確認したことはありませんが、WebSphere の TM はこのような順序で 2PC を処理するようです。
:::

上記のシーケンス図と説明にあるように、トランザクションの commit 処理が内部的に prepare と commit の２つのフェーズに分かれているので、２フェーズ・コミット(2 phase commit) と呼ばれています。
重要な点は、「TM がトランザクション・ログにトランザクションの prepare  完了を記録」した時点でトランザクションの commit が確定し、そのあとに TM や RM で障害が発生しても[^1]、リカバリー処理でトランザクションは必ず commit される点です。逆に、 prepare  完了を記録する前に TM で障害が発生すると、リカバリー処理でトランザクションは rollback されることになります。
[^1]: TM や RM で、ログが消失・破損する障害などを除き、正常にリカバリー処理が実行できた場合
RM 側は、自身の prepare が完了した後は、TM の指示があるまで prepare 状態を保持することになっています。TM の障害が長時間にわたりリカバリー処理がなかなか実行されないと、RM はトランザクションを完了させられませんので、追加したデータが確定しなかったり、データのロックが長時間にわたり他のトランザクションの処理に影響を与える可能性があります。トランザクションのリカバリー処理は、できるだけ早く速やかに実行される必要があります。

2PC の話が長くなりました。。。
トランザクションのピア・リカバリーとは、TM である WebSphere Liberty が複数稼働している環境において、ある WebSphere Liberty がダウンした場合に、別の WebSphere Liberty がトランザクション・ログを引き継ぎトランザクションのリカバリー処理を実行することです(下図参照)。尚、リカバリーが完了すると、引き継いだトランザクション・ログはリリースされます。

![ピア・リカバリー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555650/9f1ee230-53ab-d976-cfca-d376d5172ad2.png)

ピア・リカバリーが利用できない場合、ダウンした WebSphere Liberty を再起動してトランザクションのリカバリーを行うことになります。WebSphere Liberty の起動時間は短いので、再起動できる状態であれば比較的速やかにリカバリーできるかもしれませんが、サーバー(ノード)障害などで WebSphere Liberty が再起動できない場合は、代替の WebSphere Liberty にトランザクション・ログを処理させてリカバリーすることになるので、時間を要することになります。
ピア・リカバリーを使用すると、短時間でのトランザクションのリカバリーが可能になります。

## ピア・リカバリーの検証環境の準備
準備できる環境の都合で、kubernetes 環境で検証を行います。また、RM としては、Db2 と MQ を利用します。ピア・リカバリーの検証が目的なので、Db2 や MQ は可用性を考慮した構成にはしていません。
検証環境は、下図の通りです。WebSphere Liberty は kubernetes の StatefulSet で起動します。

![環境.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555650/d39a907a-9cff-c147-55f5-b2376d72b758.png)

kubernetes 環境などの場合、WebSphere Liberty のトランザクション・ログを DB に保管するように記載されていましたので、DB `tranlog` にトランザクション・ログを保存しています。
WebSphere Liberty は worker-0 と worker-1 で稼働させます。WebSphere Liberty には、ダミー RM を組み込み、検証用のアプリケーションを稼働させます。

検証用のアプリケーションは、以下のような処理を行います。

1. トランザクションを開始する。
1. ダミー RM(1) を操作する。
1. DB `sample` に１行 insert する。
1. ダミー RM(2) を操作する。
1. Queue `Q1` にメッセージを１つ put する。
1. ダミー RM(3) を操作する
1. トランザクションを commit する。

ダミー RM は、prepare/commit などの処理中に停止(スリープ)できるように実装された RM です。
今回のテストでは、ダミー RM(1) の commit 処理中に停止させ、障害を発生させます。つまり、「トランザクション・ログに prepare  完了を記録」した直後に障害を発生させることになり、Db2 と MQ は prepare が完了し、TM からの commit 指示を待つ状態となります。

WebSphere Liberty の server.xml ファイルでは、`transaction` タグ[^2]を使用してトランザクション・マネージャーを下記のように構成しました。

[^2]: 参照： [WebSphere Application Server Liberty - base - トランザクション・マネージャー (transaction)](https://www.ibm.com/docs/ja/was-liberty/base?topic=configuration-transaction)

```xml:serve.xml内のtransactionタグの内容
    <transaction recoverOnStartup="true"
                 transactionLogDBTableSuffix="_WLP1"
                 recoveryGroup="wlp1"
                 recoveryIdentity="${env.HOSTNAME}">
        <dataSource transactional="false" jdbcDriverRef="DB2Driver">
            <properties.db2.jcc databaseName="tranlog"
                                serverName="db2-db2u.db2.svc.cluster.local" portNumber="50000"
                                user="db2inst1" password="?????"/>
        </dataSource>
    </transaction>
```

server.xml ファイルの詳細は以下の通りです。
検証アプリ用DB `sample` のデータソース定義では、`type="javax.sql.XADataSource"` を指定し、XA 対応の JDBC ドライバーが使用されるように指定する必要があります。

<details><summary>server.xml ファイルの詳細（クリックで展開）</summary>
<div>

```xml:server.xml
<server>
    <featureManager>
        <feature>webProfile-7.0</feature>
        <feature>jms-2.0</feature>
        <feature>wmqJmsClient-2.0</feature>
        <feature>usr:dummy-resource-manager</feature>
    </featureManager>

    <jdbcDriver id="DB2Driver">
        <library>
            <fileset dir="/IBM/DB2Driver" includes="db2jcc4.jar"/>
        </library>
    </jdbcDriver>
    <dataSource id="SampleDS" jndiName="jdbc/sample" type="javax.sql.XADataSource" jdbcDriverRef="DB2Driver">
        <containerAuthData user="db2inst1" password="?????"/>
        <recoveryAuthData  user="db2inst1" password="?????"/>
        <properties.db2.jcc databaseName="sample" serverName="db2-db2u.db2.svc.cluster.local" portNumber="50000"/>
    </dataSource>

    <variable name="wmqJmsClient.rar.location" value="/IBM/MQRA/wmq/wmq.jmsra.rar"/>
    <jmsConnectionFactory id="jms_cf_qmtest" jndiName="jms/cf_qmtest">
        <containerAuthData user="admin" password="?????"/>
        <recoveryAuthData  user="admin" password="?????"/>
        <properties.wmqJms channel="DEV.ADMIN.SVRCONN" hostName="mq-ibm-mq.mq.svc.cluster.local" queueManager="QM_TEST"/>
    </jmsConnectionFactory>
    <jmsQueue id="jms_q_q1" jndiName="jms/q_q1">
        <properties.wmqJms baseQueueName="Q1"/>
    </jmsQueue>

    <transaction recoverOnStartup="true"
                 transactionLogDBTableSuffix="_WLP1"
                 recoveryGroup="wlp1"
                 recoveryIdentity="${env.HOSTNAME}">
        <dataSource transactional="false" jdbcDriverRef="DB2Driver">
            <properties.db2.jcc databaseName="tranlog"
                                serverName="db2-db2u.db2.svc.cluster.local" portNumber="50000"
                                user="db2inst1" password="?????"/>
        </dataSource>
    </transaction>

    <applicationManager autoExpand="true"/>
    <webApplication id="GenIndoubtTran" location="GenIndoubtTran.war" name="GenIndoubtTran"/>

</server>
```
</div>
</details>

さらに、jvm.options ファイルに下記の設定を追加してあります。これにより、トランザクションのリカバリー操作が messages.log に出力されるようになります。

```
-Dcom.ibm.tx.auditRecovery=true
```

# 次回へ
2PC とピア・リカバリーを簡単に説明し、検証用環境の説明をしました。
内容が長くなりましたので、実際の検証は次の記事といたしました。

- [WebSphere Liberty でトランザクションのピア・リカバリーを試してみる(2/3)](https://qiita.com/splendormy/items/e74dd478faba00c8c715)
- [WebSphere Liberty でトランザクションのピア・リカバリーを試してみる(3/3)](https://qiita.com/splendormy/items/ddf41b9cadd8f95359c9)