Multi Storage Transaction サンプルを試す その2 個別トランザクション単位でのScalarDBの動作を理解する
前回の記事ではScalar社が提供しているMulti Storage TransactionのサンプルをStep by Step で見ていくことで構築された環境の全体像を見ていきました。
この記事はその続きです。チュートリアルを進めながら、引き続き内部のデータの動きを見ていくことで、ScalarDBのTransaction Managerがどのようにして複数のデータベースにまたがるデータを管理しているのか?を見ていきたいと思います。
前回までの環境のおさらい
CassandraおよびMySQLにいくつかのデータが入っています。(緑の数字がデータの数です)これらのデータはgradlew
というツールにより単一のトランザクションで投入されているためTransaction Managerの中核であるCassandra上のcoordinator.stateテーブルには1件のデータが登録されています。このデータのtx_id
カラムの値はorder.items
とcutomer.customers
のレコードにおけるtx_id
カラムの値と一致しているため、どのトランザクションでどのデータが書き込まれたかがわかるようになっています
tx_state
tx_id
と連携してtx_state
というカラムが存在しています。構築された環境では3
が入力されています。
このソースコードを見るとこのカラムは以下の値をとるようです。
- PREPARED(1)
- DELETED(2)
- COMMITTED(3)
- ABORTED(4)
- UNKNOWN(5)
通常のTransaction Managerの機能としては3
ないしは4
が用いられ、3
トランザクションが正しくコミットされた、ということを意味しますので今の環境に問題はなさそうです。これらの情報はロールバック時のフラグとして用いられるようです。またSCalarDBは仕組み上2フェーズコミットを用いている関係上、例えば各DBへのデータ記載後coordinator.stateへのアクセスが出来ない、もしくはトランザクション自体がクラッシュする事などが考えられます。その場合、各DBのレコードが保持するtx_id
の値とcoordinator.stateが保持するtx_id
の値がずれることが想定されます。これはLazy Recoveryという仕組みでステータスが解決されます。
詳細はこちらのブログに記載されています。
つづきをやってみる
ではこのチュートリアルを進めていきます。
./gradlew run --args="GetCustomerInfo 1"
を実行するといろいろ出力されますが、大事なのは以下の部分です。
{"id": 1, "name": "Yamada Taro", "credit_limit": 10000, "credit_total": 0}
つまりMySQLに対して
select * from customer.customers where `customer_id` = 1;
を実行しています。ScalarDBが面白いのはここからです。Cassandraで以下を実行します。
select * from coordinator.state;
tx_id | tx_created_at | tx_state
--------------------------------------+---------------+----------
6e796405-fb32-4d78-a647-85f2b0f4e4cb | 1722584888849 | 3
d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002785 | 3
このようにデータが増えています。Transaction Manager はSelectに対してもトランザクションを管理していることがわかります。
次にこのカスタマーが注文を入れるトランザクションを発生させます。
その前にわかりやすいようにitems
テーブルの中身を以下に記載しておきます。
SELECT * FROM "order".items;
item_id | before_name | before_price | before_tx_committed_at | before_tx_id | before_tx_prepared_at | before_tx_state | before_tx_version | name | price | tx_committed_at | tx_id | tx_prepared_at | tx_state | tx_version
---------+-------------+--------------+------------------------+--------------+-----------------------+-----------------+-------------------+--------+-------+-----------------+--------------------------------------+----------------+----------+------------
5 | null | null | null | null | null | null | null | Melon | 3000 | 1722582002806 | d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002626 | 3 | 1
1 | null | null | null | null | null | null | null | Apple | 1000 | 1722582002806 | d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002626 | 3 | 1
2 | null | null | null | null | null | null | null | Orange | 2000 | 1722582002806 | d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002626 | 3 | 1
4 | null | null | null | null | null | null | null | Mango | 5000 | 1722582002806 | d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002626 | 3 | 1
3 | null | null | null | null | null | null | null | Grape | 2500 | 1722582002806 | d733f2f3-b2fb-40e9-b586-cc3fa8374590 | 1722582002626 | 3 | 1
注文コマンドを実行します。
./gradlew run --args="PlaceOrder 1 1:3,2:2"
これはitem_idの1
(つまりApple)を3個、2
(つまりOrange)を2個、という意味です。
{"order_id": "40f0281f-586f-48a1-b2a0-40ab711d07e6"}
今回の注文IDが振り出されました。当然ですがcoordinator.state
にはさらにレコードが追加されています。orders
テーブルには以下の通り1つの注文がレコードとして新しく増えています。
SELECT * FROM "order".orders;
customer_id | timestamp | before_order_id | before_tx_committed_at | before_tx_id | before_tx_prepared_at | before_tx_state | before_tx_version | order_id | tx_committed_at | tx_id | tx_prepared_at | tx_state | tx_version
-------------+---------------+-----------------+------------------------+--------------+-----------------------+-----------------+-------------------+--------------------------------------+-----------------+--------------------------------------+----------------+----------+------------
1 | 1722585372116 | null | null | null | null | null | null | 40f0281f-586f-48a1-b2a0-40ab711d07e6 | 1722585372787 | c24d1af1-d0f4-4107-89d2-4bfdd17d2242 | 1722585372719 | 3 | 1
先ほど発生させたOrderには2個の注文が含まれていました。つまりAppleを3個、と、Orangeを2個です。order.statements
にはこのため2個のレコードが追加されています。
SELECT * FROM "order".statements;
order_id | item_id | before_count | before_tx_committed_at | before_tx_id | before_tx_prepared_at | before_tx_state | before_tx_version | count | tx_committed_at | tx_id | tx_prepared_at | tx_state | tx_version
--------------------------------------+---------+--------------+------------------------+--------------+-----------------------+-----------------+-------------------+-------+-----------------+--------------------------------------+----------------+----------+------------
40f0281f-586f-48a1-b2a0-40ab711d07e6 | 1 | null | null | null | null | null | null | 3 | 1722585372787 | c24d1af1-d0f4-4107-89d2-4bfdd17d2242 | 1722585372719 | 3 | 1
40f0281f-586f-48a1-b2a0-40ab711d07e6 | 2 | null | null | null | null | null | null | 2 | 1722585372787 | c24d1af1-d0f4-4107-89d2-4bfdd17d2242 | 1722585372719 | 3 | 1
ただしこれらの2個の注文は1度のトランザクションで行われているため、coordinator.state
には一つのレコードが記載されているのみです。
次に先ほどの注文の詳細を検索するコマンドを実行します。
./gradlew run --args="GetOrder 40f0281f-586f-48a1-b2a0-40ab711d07e6"
{
"order":{
"order_id":"40f0281f-586f-48a1-b2a0-40ab711d07e6",
"timestamp":1722585372116,
"customer_id":1,
"customer_name":"Yamada Taro",
"statement":[
{
"item_id":1,
"item_name":"Apple",
"price":1000,
"count":3,
"total":3000
},
{
"item_id":2,
"item_name":"Orange",
"price":2000,
"count":2,
"total":4000
}
],
"total":7000
}
}
このようにNestされたJSONが2つのトランザクション情報を含んで出力されます。当然このタイミングでもcoordinator.state
のレコードは増えています。
チュートリアルはまだつづきます。ScalarDBとしての新しい機能の使い方があるわけではないのでこの記事はここで一旦終わりとしますが、で興味がある方はやってみて下さい。いかがでしょうか?ScalarDBの全体感とcoordinator.state
が果たす役割を感じていただけましたでしょうか?
Discussion