🐷

Multi Storage Transaction サンプルを試す その2 個別トランザクション単位でのScalarDBの動作を理解する

2024/08/02に公開

前回の記事ではScalar社が提供しているMulti Storage TransactionのサンプルをStep by Step で見ていくことで構築された環境の全体像を見ていきました。
https://scalardb.scalar-labs.com/docs/latest/scalardb-samples/multi-storage-transaction-sample/
https://zenn.dev/kamethemis/articles/5e2d84ccc96b3c

この記事はその続きです。チュートリアルを進めながら、引き続き内部のデータの動きを見ていくことで、ScalarDBのTransaction Managerがどのようにして複数のデータベースにまたがるデータを管理しているのか?を見ていきたいと思います。

前回までの環境のおさらい


CassandraおよびMySQLにいくつかのデータが入っています。(緑の数字がデータの数です)これらのデータはgradlewというツールにより単一のトランザクションで投入されているためTransaction Managerの中核であるCassandra上のcoordinator.stateテーブルには1件のデータが登録されています。このデータのtx_idカラムの値はorder.itemscutomer.customersのレコードにおけるtx_idカラムの値と一致しているため、どのトランザクションでどのデータが書き込まれたかがわかるようになっています

tx_state

tx_idと連携してtx_stateというカラムが存在しています。構築された環境では3が入力されています。
https://github.com/scalar-labs/scalardb/blob/562f38016336ae89e8d1ebb3682836f8f53c2db3/core/src/main/java/com/scalar/db/api/TransactionState.java#L5
このソースコードを見るとこのカラムは以下の値をとるようです。

  • 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という仕組みでステータスが解決されます。
詳細はこちらのブログに記載されています。
https://medium.com/scalar-engineering-ja/scalar-db-universal-transaction-manager-f2eb06a6e566

つづきをやってみる

ではこのチュートリアルを進めていきます。
https://scalardb.scalar-labs.com/docs/latest/scalardb-samples/multi-storage-transaction-sample/

./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