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を流用し例外を追加。
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」から、マルチパーティションで処理されているので間違いないようです。