【Google Cloud】Cloud Spanner トランザクション排他制御

# 0. はじめに
最近、リレーショナル データベース構造 (Relational) を持ちつつ、水平方向のスケーラビリティ (Horizontal) および 分断耐性 (Partition tolerance) を備えた **NewSQL** という、リレーショナル データベース (RDB) と NoSQL の良い特性を備えたデータベース製品／サービスが商用データベースとして採用されてきています。
Google Cloud では、「**Cloud Spanner**」というマネージドの NewSQL サービスが公開されており、Google Cloud アカウントを持っている場合、すぐに利用することが可能です。
前回、[Cloud Spannerの基本 (トランザクション、ロックの種類)](https://qiita.com/nobtak/items/cfba10737398487a465f) についてまとめてみましたが、今回は、Cloud Spanner のトランザクション排他制御について検証した内容をまとめてみようと思います。



# 目次
[0. はじめに](#0-はじめに)
[1. トランザクション排他制御](#1-トランザクション排他制御)
　- [1.1 パターン 1](#11-パターン-1)
　- [1.2 パターン 2](#11-パターン-2)
　- [1.3 パターン 3](#11-パターン-3)
　- [1.4 パターン 4](#11-パターン-4)
　- [1.5 パターン 5](#11-パターン-5)
[2. まとめ](#2-まとめ)

# 1. トランザクション排他制御
今回、簡易に Google Cloud Spanner に対してトランザクションを指定したクエリを実行することが可能な「**spanner-cli**」コマンドラインツールを利用し、複数パターンのトランザクション排他制御を確認してみようと思います。
</br>

【参考URL】
[spanner-cli](https://github.com/cloudspannerecosystem/spanner-cli) 


# 1.1 パターン 1
異なるセッションから読み書きトランザクションを開始後、同じテーブルの同じセルを更新し、トランザクション Txn-1、Txn-2 の順でコミットを実施。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/00c78656-73ed-801d-cda4-5118aab6bca5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/4600deae-df01-7aa1-a14d-7114438151cb.png)

```結果
spanner> SELECT * FROM tab1;
+----+--------+--------+
| id | col1   | col2   |
+----+--------+--------+
| 1  | test2  | data01 |
| 2  | test02 | data02 |
| 3  | test03 | data03 |
| 4  | test04 | data04 |
+----+--------+--------+
4 rows in set (3.66 msecs)
```
</br>

**【結果】**
読み書きトランザクション Txn-1, Txn-2 内で同じテーブルの同じセルを更新したが、**書き込み共有ロック (WriterShared) はロックの互換性がある**ため、Txn-1, Txn-2 共に正常にトランザクションが完了することを確認。

# 1.2 パターン 2
異なるセッションから読み書きトランザクションを開始後、同じテーブルの同じセルを更新し、トランザクション Txn-2、Txn-1 の順でコミットを実施。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/af0a646f-1e7e-6472-83ea-579b6868febf.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/1a66f9fd-9c4e-f0f5-3a04-2f9147689ad5.png)
```結果
spanner> SELECT * FROM tab1;
+----+--------+--------+
| id | col1   | col2   |
+----+--------+--------+
| 1  | test3  | data01 |
| 2  | test02 | data02 |
| 3  | test03 | data03 |
| 4  | test04 | data04 |
+----+--------+--------+
4 rows in set (1.94 msecs)
```
**【結果】**
パターン 1 と異なり、Txn-2, Txn-1 の順でコミットを実施したが、パターン 1 と同様に**書き込み共有ロック (WriterShared) はロックの互換性がある**ため、コミットの順序を変更したとしても Txn-1, Txn-2 共に正常にトランザクションが完了したことを確認。
</br>

# 1.3 パターン 3
異なるセッションから読み書きトランザクションを開始後、同じテーブルの同じセルを参照、更新し、トランザクション Txn-1、Txn-2 の順でコミットを実施。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/724cb76d-9b5e-8bf0-c20c-ac19954b0d9c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/45723c52-8e2f-e5ba-4f4a-e2560e44a4ac.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/ba055ffe-2339-6f37-0adb-3b8f64e5819a.png)
```結果
spanner> SELECT * FROM tab1;
+----+--------+--------+
| id | col1   | col2   |
+----+--------+--------+
| 1  | test1  | data01 |
| 2  | test02 | data02 |
| 3  | test03 | data03 |
| 4  | test04 | data04 |
+----+--------+--------+
4 rows in set (1.51 msecs)
```
</br>

**【結果】**
読み書きトランザクション Txn-1 で該当のセルを参照時、**読み取り共有ロック (ReaderShared)** が獲得され、その後、該当セルを更新したタイミングで **排他ロック (Exclusive)** に変換される。
同様に Txn-2 でも該当のセルに対して **排他ロック (Exclusive)** が獲得されるが、Txn-2 でコミットされたタイミングが Txn-1 よりも遅い場合、**排他ロック (Exclusive)** はロックの互換性がないため、Txn-1 で実行されたトランザクションは正常に完了し、Txn-2 で実行されたトランザクションは アボート (Abort) され、更新した内容がロールバックされることを確認。

**【補足】**
Txn-1 を コミット ではなく ロールバック した後、Txn-2 を コミット すると、Txn-2 のトランザクションは正常に完了する。

# 1.4 パターン 4
異なるセッションから読み書きトランザクションを開始後、同じテーブルの同じセルを参照、更新し、トランザクション Txn-2、Txn-1 の順でコミットを実施。
</br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/38f6a620-3b48-d04f-33f9-2fe7e70d0dbe.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/c9ef216c-9a8b-2913-8498-08c222151639.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/981e54aa-d11a-c30e-991b-1f2b54d77cef.png)

```結果
spanner> SELECT * FROM tab1;
+----+--------+--------+
| id | col1   | col2   |
+----+--------+--------+
| 1  | test1  | data01 |
| 2  | test02 | data02 |
| 3  | test03 | data03 |
| 4  | test04 | data04 |
+----+--------+--------+
4 rows in set (1.51 msecs)
```
</br>

**【結果】**
読み書きトランザクション Txn-1 で該当のセルを参照時、**読み取り共有ロック (ReaderShared)** が獲得され、その後、該当セルを更新したタイミングで **排他ロック (Exclusive)** に変換される。
同様に Txn-2 でも該当のセルに対して **排他ロック (Exclusive)** が獲得されるが、Txn-2 でコミットされたタイミングが Txn-1 よりも早い場合、Txn-2 のコミットは Txn-1 トランザクションの完了待ち状態となり、その後、Txn-1 でコミットすると、Txn-1 で実行されたトランザクションは正常に完了し、Txn-2 で実行されたトランザクションは デッドロックとして アボート (Abort : Deadlock)  され、更新した内容がロールバックされることを確認。

**【補足】**
Txn-1 を コミット ではなく ロールバック した場合、Txn-2 のトランザクションは正常に完了する。

# 1.5 パターン 5
Txn-1 は 読み書きトランザクション を開始し、Txn-2 は 読み取り専用トランザクションで開始後、Txn-1 では 該当テーブルの該当セルを参照, 更新し、Txn-2 では 該当テーブルの該当セルを参照。その後、Txn-1 を コミット後、Txn-2 で 該当テーブルの該当セルを参照。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/bd9f8bc3-41dd-9f5a-ceb7-6751f613e57f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/e523bd09-9639-59a2-3a19-12b897fb1614.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/84f24c63-bb60-9b12-b37f-4661c8e9e452.png)

```結果
spanner> SELECT * FROM tab1;
+----+--------+--------+
| id | col1   | col2   |
+----+--------+--------+
| 1  | test1  | data01 |
| 2  | test02 | data02 |
| 3  | test03 | data03 |
| 4  | test04 | data04 |
+----+--------+--------+
4 rows in set (1.51 msecs)
```
</br>

**【結果】**
読み取り専用トランザクションではロックの獲得が行われないため、読み書きトランザクション Txn-1 は正常にトランザクションが完了し、読み取り専用トランザクション Txn-2 では、トランザクションを クローズ するまでの間、トランザクション開始時の状態のテーブルデータを参照することができたことを確認。
</br>

# 2. まとめ
今回、Cloud Spanner のトランザクション排他制御について検証した内容をまとめてみました。
Cloud Spanner は PostgreSQL などの リレーショナル データベースとは異なり、ロックの互換性の検証は コミット (Commit) のタイミングで行われているもようです。
検証結果を踏まえて、読み書きトランザクション内で参照、更新を実施する場合、コミット時にトランザクションがアボートする可能性があることを考慮したアプリケーションの実装が必要になると思いました。
また、読み書きトランザクションへの影響を最小限に抑えるため、一貫性のある読み取りのみが必要な処理の場合、読み取り専用トランザクションを利用してデータを参照するなど、アプリケーション側でトランザクションの使い分けが重要な設計要素になると思います。

しかしながら、アプリケーション側で上記に記載した点を考慮して実装すれば、ACID トランザクション、水平方向のスケーラビリティ (Horizontal) および 分断耐性 (Partition tolerance) を享受したデータベースを利用したシステムを構成することができるため、決済システム、ECサイトなど大量の書き込みトランザクションが行われ、かつ、ACID トランザクションを保証する必要があるシステム、複数のリージョンにデータベースを分散させ、マルチマスター (マルチライター) による大量の書き込みトランザクションを地理的に分散させる必要があるシステムなどで利用したいデータベースだと思いました。

</br>

**※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。**
**※ 2024年9月 現在**