インメモリデータベース　VoltDBのトランザクション処理を試してみる

# VoltDBのトランザクション処理

インメモリデータベースであるVoltDBはNoSQLなみに高速（というか超えている）でありながら、NoSQLでは実現が難しいトランザクション処理（ACID）を実現できます。

VoltDBではDML（insert/update/delete/select）をJavaで実装されたストアドプロシージャで実現します。ストアドプロシージャを開始すると、VoltDBは自動的にトランザクションを開始し、ストアドプロシージャが完了すると自動でコミットされます。ストアドプロシージャがエラーなどで完了しなかった場合は自動的にロールバックされます（アトミック）。
明示的にVoltAbortException例外をスローすることによって、強制的にロールバックすることもできます。

# プロシージャ作成

実際にトランザクション処理を確認してみます。
以下の処理を実行するプロシージャを作成します。

(1)トランザクションを開始する。（自動）
(2)Aテーブルにインサートする。
(3)Bテーブルにインサートする。
(4)コミットする。（自動）

エラー時にロールバックすることを確認するため、(2)と(3)の間で例外を発生させます。

まず、テスト用のテーブル（TEST_A、TEST_B）を作成します。

```
$ sqlcmd
CREATE TABLE TEST_A (
   ID varchar(50) NOT NULL,
   DATA smallint,
   PRIMARY KEY (ID)
);
PARTITION TABLE TEST_A ON COLUMN ID;

CREATE TABLE TEST_B (
   ID varchar(50) NOT NULL,
   DATA smallint,
   PRIMARY KEY (ID)
);
PARTITION TABLE TEST_B ON COLUMN ID;

```

次に以下の2つのストアドプロシージャを作成します。

・InsertDataToAAndB：　テーブルAとテーブルBにインサートする。
・InsertDataToAAndBFail：　InsertDataToAAndBを流用し例外を追加。

```InsertDataToAAndB.java
package test;

import org.voltdb.SQLStmt;
import org.voltdb.VoltProcedure;

public class InsertDataToAAndB extends VoltProcedure {

	public final SQLStmt insertTestA = new SQLStmt("insert into test_a values(?, ?)");
	public final SQLStmt insertTestB = new SQLStmt("insert into test_b values(?, ?)");

	public long run(String idA, int dataA, String idB, int dataB) throws VoltAbortException {

		voltQueueSQL(insertTestA, idA, dataA);
		voltExecuteSQL();
//InsertDataToAAndBFailクラスの場合はここで例外をスローしている。
		voltQueueSQL(insertTestB, idB, dataB);
		voltExecuteSQL();
		return 2L;
	}
}
```

作成したプロシージャはコンパイルし、catalog.jarを作成します。

VoltDBサーバに先ほど作成したcatalog.jarをアップロードし、以下のコマンドでプロシージャをロードします。

```
$ sqlcmd                                               3
1> load classes catalog.jar;
2> show classes;
--- Potential Procedure Classes ------------------------------
 test.InsertDataToAAndB    
 test.InsertDataToAAndBFail
```

「Potential Procedure Classes」でプロシージャがロードされていることが分かります。
次にプロシージャを作成（定義）するため、以下のコマンドを実行します。
2つのプロシージャが「Active Procedure Classes」になります。

```
$ sqlcmd
1> CREATE PROCEDURE FROM CLASS test.InsertDataToAAndB;
2> CREATE PROCEDURE FROM CLASS test.InsertDataToAAndBFail;
--- Active Procedure Classes ---------------------------------
 test.InsertDataToAAndB    
 test.InsertDataToAAndBFail
```

作成したプロシージャを実行し、トランザクション処理を確認します。

InsertDataToAAndBを呼び出し、TEST_AとTEST_Bテーブルに1レコードずつインサートします。

```
$ sqlcmd
> exec InsertDataToAAndB "1" 100 "1" 200;
(Returned 2 rows in 0.00s)
```

TEST_A, TEST_Bテーブルに1レコードずつ挿入されていることを確認します。

```
> select * from test_a;
ID   DATA 
---- -----
"1"    100

(Returned 1 rows in 0.01s)
> select * from test_b;
ID   DATA 
---- -----
"1"    200

(Returned 1 rows in 0.00s)
```

次にTEST_Aに挿入後、例外を発生させてロールバックさせた場合を確認します。

```
> exec InsertDataToAAndBFail "2" 100 "2" 200;
VOLTDB ERROR: USER ABORT
  test exception
    at test.InsertDataToAAndBFail.run(InsertDataToAAndBFail.java:16)
```

例外発生でロールバックされ、TEST_A, TEST_Bテーブルには元から存在した1レコードずつしかないことが確認できます。

```
> select * from test_a;
ID   DATA 
---- -----
"1"    100

(Returned 1 rows in 0.00s)

> select * from test_b;
ID   DATA 
---- -----
"1"    200

(Returned 1 rows in 0.00s)
```


# 終わりに

VoltDBでトランザクション処理ができることを確認しました。
トランザクション処理はできたのですが、複数テーブルを更新する際にパーティションキーを指定する方法が分からなかった。今回のやり方だとマルチパーティションになってしまい性能が落ちてしまう（と思われる）。それが仕様なのか設定・コーディングが悪いのかは調べてみる必要あり。

なお、ストアドプロシージャの実行計画は以下のようになっており、「RECEIVE FROM ALL PARTITIONS SEND PARTITION RESULTS TO COORDINATOR」から、マルチパーティションで処理されているので間違いないようです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179607/3c5f8a44-322a-ee99-17a5-2d09037dde89.png)