状態データの分散管理の方法について
はじめに
状態マネージャーとは?
状態マネージャーとはflowのチェックポイントやP2Pセッション、flowステータスなどCorda clusterの状態データの一貫性や完全性を保持するために、RDB上の管理テーブルに挿入, 更新, 削除をするための制御機構です。
本記事の目的
デフォルトでは状態データは共通のRDBに保存されますが、データの保存先が集中することでRDBのI/Oに対するボトルネックになりえます。そのためCorda clusterでは状態データをタイプごとに分散して、別々のRDBに保存できます。本記事ではその設定手順を紹介します。
状態データはCordaの英語版ではStateと表記されていますが、Cordaにはブロックチェーン上の価値(債権、トークン、法定通貨)という意味を表す言葉としてもStateが用いられています。本記事では混乱をさけるためStateという言葉を使わず状態データと表現します。併せて状態データの管理機構を状態マネージャー(英語ではState Manager)と表現します。
想定読者
- Corda cluster構築に携わるインフラエンジニア
前提
- 任意のKubernetes clusterが起動していること。本記事ではバージョン1.30を使用しています。
- 操作端末にcorda-cli.shがインストールされていること。本記事では、corda 5.2.1バージョンを使用しています。インストール方法およびバージョン確認方法についてはこちらをご覧ください。
- 操作端末にpsqlがインストールされていること。本記事では14.12バージョンを使用しています。
- 状態データ専用のRDBが用意されていること。本記事では各状態データ専用のRDBを6つ用意します。本番環境では、独立したRDBインスタンスを用意する必要がありますが、学習目的の場合、Cordaと同じKubernetes clusterにPostgreSQL Podを代用できます。詳しい手順は「付録」の「bitnami postgresを用いた状態データ専用DB設定手順」をご覧ください。
手順
状態マネージャー専用RDBの設定
状態マネージャー専用RDBへの接続はCorda cluster起動時に行われますが、以下のRDBの設定はCorda cluster起動前に完了させる必要があります。
- 任意のスキーマに状態データテーブルの作成 状態データテーブルは、corda-cli.shのdatabaseコマンドを使って出力したSQLを適用して作成します。以下はスキーマ「sm_flow_checkpoint」に状態データ関連のテーブルを作成する手順例です。
- 状態データテーブル操作用のロール作成
# PostgreSQLの接続情報を定義。
export PGHOST=localhost
export PGPORT=5433
# テーブルが属するスキーマを定義
export SCHEMA_NAME=sm_flow_checkpoint
# PostgreSQLのadminユーザーを定義
export PGUSER=postgres
export PGPASSWORD=postgrespassword
# 操作ディレクトリ配下にSQL出力用ディレクトリを作成。
mkdir ./${SCHEMA_NAME}
# corda-cli.sh databaseコマンドでスキーマおよびテーブル作成用のSQLを出力。
# 正常に終了した場合、特に返り値はなく指定したディレクトリ(この例の場合./sm_flow_checkpointディレクトリ)配下に、statemanager.sqlというファイルが出力する。
# DBの状態を事前チェックするための--jdbc-urlオプションをつけているので、PostgreSQLに到達しない環境の場合は失敗する。
# PostgreSQLに到達しない環境で実行したい場合は、--jdbc-url, -u, -pオプションを除外して実行すること。
corda-cli.sh database spec -s "statemanager" -g "statemanager:${SCHEMA_NAME}" --jdbc-url "jdbc:postgresql://${PGHOST}:${PGPORT}/${PGDATABASE}" -u ${PGUSER} -p ${PGPASSWORD} -c -l ./${SCHEMA_NAME}
# ./sm_flow_checkpoint/statemanager.sqlが作成されていることを確認。
cat ./sm_flow_checkpoint/statemanager.sql
CREATE SCHEMA IF NOT EXISTS sm_flow_checkpoint;
-- *********************************************************************
-- Update Database Script
-- *********************************************************************
-- Change Log: net/corda/db/schema/statemanager/db.changelog-master.xml
-- Ran at: 2024/10/22 15:56
-- Against: postgres@jdbc:postgresql://localhost:5433/cordacluster
-- Liquibase version: 4.19.0
-- *********************************************************************
SET SEARCH_PATH TO sm_flow_checkpoint, "$user","public";
.
.
.
UPDATE sm_flow_checkpoint.databasechangeloglock SET LOCKED = FALSE, LOCKEDBY = NULL, LOCKGRANTED = NULL WHERE ID = 1;
SET SEARCH_PATH TO sm_flow_checkpoint, "$user","public";
# 数十行のSQLファイルが出力されればOK
# SQLを適用
psql -f ./${SCHEMA_NAME}/statemanager.sql
CREATE SCHEMA
SET
.
.
.
SET
# エラーが出なければOK
PostgreSQLロールを作成して、「1」で作成したテーブルへの操作権限を付与します。 以下はロール「sm_user_5433」を作成して、「1」で作成したスキーマ「sm_flow_checkpoint」に対する操作権限を付与する手順例です。
# 環境変数の一部は「1」の手順例から流用
# テーブル操作用のロールおよびパスワードを定義
export STATEMANAGER_USER=sm_user_${PGPORT}
export STATEMANAGER_PASSWORD=sm_password_${PGPORT}
# テーブルを操作するPostgreSQLロールの作成とスキーマへの権限付与を実行するためのSQL出力
echo "CREATE USER ${STATEMANAGER_USER} WITH ENCRYPTED PASSWORD '${STATEMANAGER_PASSWORD}'; \
GRANT USAGE ON SCHEMA ${SCHEMA_NAME} TO ${STATEMANAGER_USER};
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ${SCHEMA_NAME} TO ${STATEMANAGER_USER};
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${SCHEMA_NAME} TO ${STATEMANAGER_USER};
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${SCHEMA_NAME} TO ${STATEMANAGER_USER};
ALTER ROLE ${STATEMANAGER_USER} SET search_path TO ${SCHEMA_NAME};" > ./${SCHEMA_NAME}/setting_user_schema.sql
# SQLを適用
psql -f ./${SCHEMA_NAME}/setting_user_schema.sql
CREATE ROLE
GRANT
GRANT
GRANT
GRANT
ALTER ROLE
# エラーが出なければOK
Corda clusterの設定ファイル編集
状態マネージャーが分離させたテーブルに状態データを記録できるようにするためには、helm chartにわたすvalues.yaml中に状態データテーブルへの接続情報を定義してCorda clusterを起動します。冗長になるため、以下では、flowCheckpointの設定のみを記載していますが、workerと状態データの対応関係は、「付録」の「状態データ一覧」をご覧ください。
# values.yaml
image:
registry: "任意のdocker repository"
tag: "5.2.1.0"
# databasesセクションに接続するDBの基本情報を定義
# id: values.yaml内でのエイリアス。同ファイルの別箇所でDBに関するidを要求された場合に設定。
# name: database名
# host: 接続DBのホスト名
# port: 接続DBのポート番号
databases:
- id: default
name: "cordacluster"
host: "postgres-postgresql"
port: 5432
type: "postgresql"
- id: "flow-checkpoint-state-manager"
name: "cordacluster"
host: "postgres-5433-postgresql"
port: 5433
type: "postgresql"
- id: "flow-mapping-state-manager"
name: "cordacluster"
host: "postgres-5434-postgresql"
port: 5434
type: "postgresql"
- id: "flow-status-state-manager"
name: "cordacluster"
host: "postgres-5435-postgresql"
port: 5435
type: "postgresql"
- id: "key-rotation-state-manager"
name: "cordacluster"
host: "postgres-5436-postgresql"
port: 5436
type: "postgresql"
- id: "p2p-session-state-manager"
name: "cordacluster"
host: "postgres-5437-postgresql"
port: 5437
type: "postgresql"
- id: "token-pool-cache-state-manager"
name: "cordacluster"
host: "postgres-5438-postgresql"
port: 5438
type: "postgresql"
.
.
.
# 状態マネージャーが各タイプの状態データを保存するDBおよびスキーマ名を設定
stateManager:
# flowCheckpointという状態データの保存先設定
flowCheckpoint:
type: Database
# databasesで定義したidを設定
storageId: "flow-checkpoint-state-manager"
# 接続先DBに作成済のスキーマ名を設定
partition: "sm_flow_checkpoint"
flowMapping:
type: Database
storageId: "flow-mapping-state-manager"
partition: "sm_flow_mapping"
flowStatus:
type: Database
storageId: "flow-status-state-manager"
partition: "sm_flow_status"
keyRotation:
type: Database
storageId: "key-rotation-state-manager"
partition: "sm_key_rotation"
p2pSession:
type: Database
storageId: "p2p-session-state-manager"
partition: "sm_p2p_session"
tokenPoolCache:
type: Database
storageId: "token-pool-cache-state-manager"
partition: "sm_token_pool_cache"
.
.
.
# 各workerが状態データテーブルを操作するために、各DBに接続するためのusernameとpasswordを定義する。
workers:
flow:
image:
repository: "corda-ent-flow-worker"
replicaCount: 1
stateManager:
# flowCheckpointに紐づいたDB(postgres-5433-postgresql)に接続するためのロールおよびパスワード設定
# 下記例では、シークレット「my-secret」のキー「sm_user_5433」および「sm_password_5433」を参照しているが、直接定義することもできる。
flowCheckpoint:
username:
valueFrom:
secretKeyRef:
name: "my-secret"
key: "sm_user_5433"
password:
valueFrom:
secretKeyRef:
name: "my-secret"
key: "sm_password_5433"
.
.
.
# 他のworkerでも同様の定義をする。
Corda cluster起動
helm installでCorda clusterを起動します。
helm install corda ~/Corda/Corda5/5.2.1/corda-enterprise-5.2.1.tar -f ./values_with_statemanager.yaml
NAME: corda
LAST DEPLOYED: Tue Oct 15 15:59:34 2024
NAMESPACE: corda-cluster1
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Extract the username and password for the REST API admin created during bootstrap:
kubectl get secret my-secret --namespace corda-cluster1 -o go-template='{{ .data.restapiusername | base64decode }}'
kubectl get secret my-secret --namespace corda-cluster1 -o go-template='{{ .data.restapipassword | base64decode }}'
2. Expose the API endpoint on localhost by running this command:
kubectl port-forward --namespace corda-cluster1 deployment/corda-corda-enterprise-rest-worker 8888 &
3. The API endpoint definition can then be accessed via: https://localhost:8888/api/v1/swagger
For more information, see the Corda 5 documentation at docs.r3.com.
kubectl get podsで全てのpodがrunningになっていることを確認します。
SR19-032:files_for_writing_article ktateyam$ kubectl get pods
NAME READY STATUS RESTARTS AGE
corda-corda-enterprise-create-topics-zc45h 0/1 Completed 0 4m1s
corda-corda-enterprise-crypto-worker-67d98d478c-7zzhq 1/1 Running 0 3m15s
corda-corda-enterprise-db-worker-5f88d44744-5t8dv 1/1 Running 0 3m15s
corda-corda-enterprise-flow-mapper-worker-d445ffcbd-6tqvx 1/1 Running 0 3m15s
corda-corda-enterprise-flow-worker-5b7dc488f-9qwnj 1/1 Running 0 3m15s
corda-corda-enterprise-membership-worker-7f4d4b495d-bqsxl 1/1 Running 0 3m15s
corda-corda-enterprise-p2p-gateway-worker-5597f5f69d-q5mzz 1/1 Running 0 3m15s
corda-corda-enterprise-p2p-link-manager-worker-86d4644546-9c466 1/1 Running 0 3m15s
corda-corda-enterprise-persistence-worker-7486767876-4dvs4 1/1 Running 0 3m15s
corda-corda-enterprise-preinstall-checks-g25cf 0/1 Completed 0 4m25s
corda-corda-enterprise-rest-worker-6d94844597-t4prs 1/1 Running 0 3m15s
corda-corda-enterprise-setup-db-mr4vs 0/1 Completed 0 3m48s
corda-corda-enterprise-setup-rbac-7cmlq 0/1 Completed 0 3m15s
corda-corda-enterprise-token-selection-worker-6c5bc6cfb6-n49ct 1/1 Running 0 3m15s
corda-corda-enterprise-uniqueness-worker-6fcf566cf6-wmqc7 1/1 Running 0 3m15s
corda-corda-enterprise-verification-worker-6885cf7b-2jwb4 1/1 Running 0 3m15s
kafka-0 1/1 Running 1 (167m ago) 167m
kafka-1 1/1 Running 1 (167m ago) 167m
kafka-2 1/1 Running 0 167m
kafka-zookeeper-0 1/1 Running 0 167m
postgres-5432-postgresql-0 1/1 Running 0 158m
postgres-5433-postgresql-0 1/1 Running 0 154m
postgres-5434-postgresql-0 1/1 Running 0 153m
postgres-5435-postgresql-0 1/1 Running 0 153m
postgres-5436-postgresql-0 1/1 Running 0 153m
postgres-5437-postgresql-0 1/1 Running 0 153m
postgres-5438-postgresql-0 1/1 Running 0 153m
起動後の確認方法とエラーシューティング
「Corda cluster起動」においてすべてのPodが起動した場合でも、RDBへの接続設定が誤っている場合、任意の操作で失敗することがあります。たとえば、Corda clusterを起動後にflow statusの状態を確認した場合、以下のエラーが返ってくることがあります。
”ERROR: relation \”state\” does not exist\n Partition: 69
”というエラーは一般的にはPostgreSQLでstateというテーブルが存在しない場合に出力するエラーです。こちらは2つの観点で、原因切り分けができます。
- 接続先のRDBにテーブルが生成されていない場合
- SQL発行コマンドの実行結果を確認してエラーが出ていないことをご確認ください。
- RDBにログインしてテーブルの存在をご確認ください。
- values.yamlに設定した
username
やpassword
が間違っている場合 - 「状態データテーブル操作用のロール作成」で作成したロールおよびパスワードがそれぞれのRDBへの
username
やpassword
として設定されているかご確認ください。 - 特にRDBのadminロールとパスワードを誤って設定しているケースが散見されます。この場合、RDBには接続はできるためCorda clusterのPodは起動します。しかし、adminロールはテーブルが属するスキーマをsearch_pathとして設定していないため、テーブルを見つけることができません。そのため、状態データの書き込みができずに、Cordaの処理が失敗します。 以下誤った設定例です。
# PostgreSQLにadminロール「postgres」でログインして、roleとsearch_pathの対応関係を出力
cordacluster=# SELECT rolname, rolconfig
FROM pg_roles
WHERE rolconfig IS NOT NULL;
rolname | rolconfig
--------------+----------------------------------
sm_user_5433 | {search_path=sm_flow_checkpoint}
(1 row)
# adminロールでテーブル一覧を表示。search_pathが設定されていないのでテーブルが見つからない。
cordacluster=# \dt
Did not find any relations.
# sm_user_5433ロールで再ログインして、テーブル一覧を表示。stateが表示される。
cordacluster=> \dt
List of relations
Schema | Name | Type | Owner
--------------------+-----------------------+-------+----------
sm_flow_checkpoint | databasechangelog | table | postgres
sm_flow_checkpoint | databasechangeloglock | table | postgres
sm_flow_checkpoint | state | table | postgres
(3 rows)
# values.yamlを確認。usernameとして、adminロール「postgres」が設定されている。
# この場合、Corda clusterがDBにアクセスしてもテーブルを見つけられずにエラーになる。
workers:
flow:
image:
repository: "corda-ent-flow-worker"
replicaCount: 1
stateManager:
flowCheckpoint:
username:
value: "postgres" # -> sm_user_5433が正しい。
password:
value: "postgrespassword5433" # -> sm_user_5433のパスワードが正しい。
また関連workerが以下のWARNを繰り返し出力している場合、Corda clusterからRDBの接続に失敗している可能性が高いです。kubectl logs 各workerのpod名
を実行して確認してください。
# 可読性のために加工していますが、本来は1行のメッセージとして出力されます。messageフィールドを確認してください。
{
"instant": {
"epochSecond": 1729582296,
"nanoOfSecond": 257000000
},
"thread": "flow-event-mediator-long-running-thread-75889d88-a714-4151-90e5-4610fc18934b",
"level": "WARN",
"loggerName": "net.corda.messaging.mediator.processor.ConsumerProcessor-FlowEventMediator",
# stateテーブルが見つからないWARN
"message": "Retrying processing: ERROR: relation \"state\" does not exist\n Position: 68.",
"endOfBatch": false,
"loggerFqcn": "org.apache.logging.slf4j.Log4jLogger",
"threadId": 100,
"threadPriority": 5
}
おわりに
DBを分散した状態データの管理は、パフォーマンスの向上に有効であると言われています。理想的にはすべての状態データを分散させることですが、コスト的に難しい場合は以下の状態データだけでも分散することをご検討ください。
flowMapping
flowCheckpoint
tokenPoolCache
(Tokenを使う場合)
また、以下に付録の設定例も載せてありますので、ご興味があればご確認ください。
<ご質問・ご要望の例>
- Corda Portalの記事について質問したい
- ブロックチェーンを活用した新規事業を相談したい
- 企業でのブロックチェーン活用方法を教えて欲しい 等々
SBI R3 Japan エンジニアリング部所属
ソリューションアーキテクト/PoC支援
This is the way. みんなでキャズムを超えていきましょう!