前回は ScalarDB のチュートリアルをこなした記事を書きました。
今回は ScalarDB Cluster のチュートリアルをこなしていきます。
ScalarDB Cluster とは
前回のチュートリアルでは ScalarDB を利用する際、「ScalarDB を起動する」のようなステップは存在しなかった。これはどういうことかというと、ScalarDB のデーモンプロセスのようなものは存在せず、ScalarDB の jar ファイルが Java で書かれたクライアントアプリケーションにリンクされ、APIコールの裏側でバックエンドDBにアクセスしていた、ということ。
一方、この記事でこれから試す ScalarDB Cluster では、Kubernetes クラスター内に ScalarDB のサーバープロセスが常駐し、クライアントアプリケーションはネットワーク経由で ScalarDB にアクセスすることになる。
なおこのクラスター構成はオープンソース版の ScalarDB では利用することができない。
今回はバイト先からライセンスを提供してもらってこの記事を書いている(バイト先公認)。
チュートリアルに挑戦
ScalarDB Cluster についても Getting Started というチュートリアルが用意されているので、今回もこれをひと通りなぞってみる。
チュートリアルの概要
Eコマースアプリケーションのサンプルを用いて、ユーザーの注文・支払いを ScalarDB で処理する。
全体アーキテクチャーはこんな感じ。
Kubernetes クラスターの中で ScalarDB Cluster と関連サービス、バックエンドDB(PostgreSQL)が動作していて、Eコマースアプリケーションはネットワーク経由でアクセスする。
DBスキーマの構築は前回と同様に Schema Loader (ただしCluster版) を使う。
+----------------+ +---------------+
| E-Commerce App | | Schema Loader |
+----------------+ +---------------+
| |
+---------------------+
|
+-------+-------------------------------------------------------------------------------------------------------------------------------+
| | [Kubernetes Cluster] |
| | |
| | [Pod] [Pod] [Pod] |
| | |
| | +-------+ |
| | +---> | Envoy | ---+ |
| | | +-------+ | |
| | | | |
| +---------+ | +-------+ | +--------------------+ |
| | Service | ---+---> | Envoy | ---+---------> | Service | ---+ |
| | (Envoy) | | +-------+ | | (ScalarDB Cluster) | | |
| +---------+ | | +--------------------+ | +-----------------------+ |
| | +-------+ | | +---> | ScalarDB Cluster Node | ---+ |
| +---> | Envoy | ---+ | | +-----------------------+ | |
| +-------+ | | | |
| | | +-----------------------+ | +------------+ |
| +---+---> | ScalarDB Cluster Node | ---+---> | PostgreSQL | |
| | | +-----------------------+ | +------------+ |
| | | | |
| | | +-----------------------+ | |
| | +---> | ScalarDB Cluster Node | ---+ |
| | +-----------------------+ |
| +----------------------------+ | |
| | Service | ---+ |
| | (ScalarDB Cluster GraphQL) | |
| +----------------------------+ |
| |
+---------------------------------------------------------------------------------------------------------------------------------------+
なお、今回も比較的きれいな Mac 環境で試していく。
前提条件
ScalarDB と同様に OpenJDK か Oracle JDK の 8, 11, 17 が必要。
そして Kubernetes クラスター上で動作する ScalarDB Cluster 環境。つまり上の図の [Kubernetes Cluster]
で囲まれているところすべて。
え? それってすごく大変なのでは…?
1. Kubernetes クラスターの作成
ScalarDB Cluster を動かす Kubernetes クラスターは、プロダクション環境向けには Amazon EKS などのサービスを利用することになるのだろうが、検証目的であればすべてローカルマシン内で完結させられる。
そしてその手順は次のページにガイドがあり、比較的簡単に構築することができる。
1-0. 前提ソフトウェアのインストール
ローカルで Kubernetes クラスターを動かすためのソフトウェアとして minikube、または kind を利用する。今回は minikube を使うことにする。
すべて Homebrew でインストールできる。
$ brew install minikube
$ brew install kubectl
$ brew install helm
終わったら minikube を起動しておく(ずいぶんポップなログだな)。
$ $ minikube start
😄 Darwin 13.3 (arm64) 上の minikube v1.33.1
✨ docker ドライバーが自動的に選択されました
📌 root 権限を持つ Docker Desktop ドライバーを使用
👍 Starting "minikube" primary control-plane node in "minikube" cluster
🚜 Pulling base image v0.0.44 ...
🔥 Creating docker container (CPUs=2, Memory=6100MB) ...
🐳 Docker 26.1.1 で Kubernetes v1.30.0 を準備しています...
▪ 証明書と鍵を作成しています...
▪ コントロールプレーンを起動しています...
▪ RBAC のルールを設定中です...
🔗 bridge CNI (コンテナーネットワークインターフェース) を設定中です...
🔎 Kubernetes コンポーネントを検証しています...
▪ gcr.io/k8s-minikube/storage-provisioner:v5 イメージを使用しています
🌟 有効なアドオン: storage-provisioner, default-storageclass
🏄 終了しました!kubectl がデフォルトで「minikube」クラスターと「default」ネームスペースを使用するよう設定されました
1-1. PostgreSQL コンテナを起動
まずは Bitnami の Helm リポジトリを追加(Bitnamiって何だ?)。
$ helm repo add bitnami https://charts.bitnami.com/bitnami
そして PostgreSQL をデプロイ。
$ helm install postgresql-scalardb-cluster bitnami/postgresql --set auth.postgresPassword=postgres --set primary.persistence.enabled=false
...
長いメッセージが出てくるのだが、基本的には起動された DB に psql コマンドでアクセスする方法について説明しているだけだ。
ここではその説明よりもシンプルに psql を実行する方法を紹介しておく。
$ kubectl exec -it postgresql-scalardb-cluster-0 -- bash -c 'PGPASSWORD=postgres psql -U postgres'
これは起動した PostgreSQL コンテナの Pod の中で psql コマンドを実行している。
パスワードが直書きだが、このパスワードはそもそも先ほど helm install
したときにコマンドラインで指定したものなので、今さら隠す意味はない。
いちおう DB の中身を確認しておこう。
postgres=# \dn
List of schemas
Name | Owner
--------+-------------------
public | pg_database_owner
(1 row)
postgres=# \dt public.*
Did not find any relation named "public.*".
唯一存在する public
スキーマにはテーブルはひとつも定義されていない。
1-2. ScalarDB Cluster をデプロイ
ここから先は ScalarDB の有償ライセンスが必要になる。そこらへんの説明は割愛。
まずは Helm に ScalarDB のチャートリポジトリを追加。
$ helm repo add scalar-labs https://scalar-labs.github.io/helm-charts
ScalarDB Cluster のコンテナイメージを GitHub Packages からプルするために必要な k8s シークレットリソースを作成する。
$ kubectl create secret docker-registry reg-docker-secrets --docker-server=ghcr.io --docker-username=<YOUR_GITHUB_USERNAME> --docker-password=<YOUR_PERSONAL_ACCESS_TOKEN>
<YOUR_GITHUB_USERNAME>
は、自分の GitHub アカウント名に置き換える。
<YOUR_PERSONAL_ACCESS_TOKEN>
には GitHub で作成した Personal Access Token (PAT) 文字列をセット。
PAT を作成するときのスコープには read:packages
を含める必要がある。
以下の長〜い複数行コマンドを実行して、scalardb-cluster-custom-values.yaml
というファイルを作成。
$ cat << 'EOF' > scalardb-cluster-custom-values.yaml
envoy:
enabled: true
service:
type: "LoadBalancer"
scalardbCluster:
scalardbClusterNodeProperties: |
# ScalarDB Cluster configurations
scalar.db.cluster.membership.type=KUBERNETES
scalar.db.cluster.membership.kubernetes.endpoint.namespace_name=${env:SCALAR_DB_CLUSTER_MEMBERSHIP_KUBERNETES_ENDPOINT_NAMESPACE_NAME}
scalar.db.cluster.membership.kubernetes.endpoint.name=${env:SCALAR_DB_CLUSTER_MEMBERSHIP_KUBERNETES_ENDPOINT_NAME}
# Storage configurations
scalar.db.storage=jdbc
scalar.db.contact_points=jdbc:postgresql://postgresql-scalardb-cluster.default.svc.cluster.local:5432/postgres
scalar.db.username=postgres
scalar.db.password=postgres
# For ScalarDB Cluster GraphQL tutorial.
scalar.db.graphql.enabled=true
scalar.db.graphql.namespaces=emoney
# For ScalarDB Cluster SQL tutorial.
scalar.db.sql.enabled=true
graphql:
enabled: true
service:
type: "LoadBalancer"
EOF
そして ScalarDB Cluster をデプロイ。
$ helm install scalardb-cluster scalar-labs/scalardb-cluster -f ./scalardb-cluster-custom-values.yaml
ポッドの状況を確認。しばらく経つと以下のような構成となり、全てのポッドのステータスが Running となるはず。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
postgresql-scalardb-cluster-0 1/1 Running 0 26m
psql 1/1 Running 0 10m
scalardb-cluster-envoy-547dbbbf4b-cv4hx 1/1 Running 0 9m46s
scalardb-cluster-envoy-547dbbbf4b-hqq2b 1/1 Running 0 9m46s
scalardb-cluster-envoy-547dbbbf4b-wcrft 1/1 Running 0 9m46s
scalardb-cluster-node-6896885945-2rnzb 1/1 Running 0 9m46s
scalardb-cluster-node-6896885945-md2t7 1/1 Running 0 9m46s
scalardb-cluster-node-6896885945-qfcpx 1/1 Running 0 9m46s
ここでデプロイされた ScalarDB Cluster のバージョンを確認しておこう。
3つある scalardb-cluster-node-*
という名前のポッドのうち、任意のひとつの名前を引数に次のコマンドを実行する。
$ kubectl get pod scalardb-cluster-node-6896885945-qfcpx -o jsonpath='{.spec.containers[*].image}'
ghcr.io/scalar-labs/scalardb-cluster-node:3.13.0
バージョンは 3.13.0
だ。この情報は後の手順で参照するので覚えておく。
ロードバランサーサービス向けに別ターミナルを開いて minikube tunnel
コマンドを実行。
minikube を利用したローカル k8s クラスターでは、これをやらないと k8s クラスター内のロードバランサーにアクセスできない。
$ minikube tunnel
✅ トンネルが無事開始しました
📌 注意: トンネルにアクセスするにはこのプロセスが存続しなければならないため、このターミナルはクローズしないでください ...
🏃 scalardb-cluster-envoy サービス用のトンネルを起動しています。
🏃 scalardb-cluster-graphql サービス用のトンネルを起動しています。
このコマンドは Ctrl-C するまで実行中のままとなる。
元のターミナルに戻ってサービスの状況を確認。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16h
postgresql-scalardb-cluster ClusterIP 10.107.134.66 <none> 5432/TCP 29m
postgresql-scalardb-cluster-hl ClusterIP None <none> 5432/TCP 29m
scalardb-cluster-envoy LoadBalancer 10.108.134.40 127.0.0.1 60053:31087/TCP 12m
scalardb-cluster-envoy-metrics ClusterIP 10.110.174.15 <none> 9001/TCP 12m
scalardb-cluster-graphql LoadBalancer 10.100.115.226 127.0.0.1 8080:32195/TCP 12m
scalardb-cluster-headless ClusterIP None <none> 60053/TCP 12m
scalardb-cluster-metrics ClusterIP 10.102.96.82 <none> 9080/TCP 12m
チュートリアルの例では LoadBalancer の EXTERNAL-IP が localhost
になっているが、自分の環境では 127.0.0.1
となった。まあでも大きな違いはなさそうなので、細かいことは気にせず先へ進むことにする。
ここまででようやく ScalarDB Cluster チュートリアルの前提条件を満たすことができたので、チュートリアル本編に戻ってシナリオを続行する。
2. ScalarDB サンプルリポジトリをクローン
サンプルのEコマースアプリケーションと関連ファイルが詰まったリポジトリをローカルにクローンする。
$ git clone https://github.com/scalar-labs/scalardb-samples
リポジトリ内には *-sample
というディレクトリがたくさんあるが、このチュートリアルではそのうちの scalardb-sample
を利用するので、そこに移動する。
$ cd scalardb-samples/scalardb-sample
3. build.gradle ファイルを修正
サンプルリポジトリは元々、非クラスター構成の ScalarDB で動かす設定になっているので、これを ScalarDB Cluster で使えるようビルド設定を変更する。
変更内容は以下の diff 参照。
$ git diff build.gradle
diff --git a/scalardb-sample/build.gradle b/scalardb-sample/build.gradle
index e9af1fb..34da5ed 100644
--- a/scalardb-sample/build.gradle
+++ b/scalardb-sample/build.gradle
@@ -11,7 +11,7 @@ repositories {
}
dependencies {
- implementation 'com.scalar-labs:scalardb:3.9.1'
+ implementation 'com.scalar-labs:scalardb-cluster-java-client-sdk:3.13.0'
implementation 'info.picocli:picocli:4.7.1'
}
com.scalar-labs:scalardb-cluster-java-client-sdk:3.13.0
の中の 3.13.0
の部分は、先ほど確認した ScalarDB Cluster のバージョンに合わせて置き換える。
4. database.properties ファイルを修正
database.properties
というのは、クライアントアプリケーションから ScalarDB への接続情報を記述する設定ファイル。これも ScalarDB Cluster 用に変更する。
まずはアプリケーションの直接の通信先となる Envoy サービス(先の構成図参照)のアドレスを、次のようにして取得する。
$ kubectl get svc scalardb-cluster-envoy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
scalardb-cluster-envoy LoadBalancer 10.107.213.118 127.0.0.1 60053:32378/TCP 12h
EXTERNAL-IP
で示される 127.0.0.1
がそのアドレス。
そして database.properties
を以下のように書き換える。
$ git diff database.properties
diff --git a/scalardb-sample/database.properties b/scalardb-sample/database.properties
index a44993a..5cf4abb 100644
--- a/scalardb-sample/database.properties
+++ b/scalardb-sample/database.properties
@@ -1,4 +1,2 @@
-scalar.db.storage=cassandra
-scalar.db.contact_points=localhost
-scalar.db.username=cassandra
-scalar.db.password=cassandra
+scalar.db.transaction_manager=cluster
+scalar.db.contact_points=indirect:127.0.0.1
先ほど確認したアドレスは scalar.db.contact_points
に indirect:127.0.0.1
のような形式で指定する。
ここで indirect
というのはクライアントモードを示している。
クライアントモードには indirect
と direct-kubernetes
の2種類がある。ざっくり説明すると、indirect
モードでは k8s クラスターの外に配置されたアプリケーションが Envoy サービス経由で ScalarDB Cluster にアクセスする。direct-kubernetes
モードではアプリケーションと ScalarDB Cluster を同一の k8s クラスター内に配置し、より効率的なアクセスが可能になる。これ以上の細かい話は別のページを参照されたし。
で、このチュートリアルでは indirect
モードを利用する。
5. データベーススキーマの作成
DBスキーマは schema.json
ファイルに定義されている。その内容については後述。
このスキーマ定義を元に ScalarDB のデータベースとテーブルを作成するには、ScalarDB Cluster Schema Loader というツールを利用する。
前回の非クラスター構成の ScalarDB で利用した ScalarDB Schema Loader は名前がちょっと違う別のツールなので注意。
ScalarDB Cluster Schema Loader は以下のページからダウンロードできる。
ダウンロードするのは scalardb-cluster-schema-loader-<version>-all.jar
。
<version>
の部分は、先ほど確認した ScalarDB Cluster のバージョンに従う。
これを database.properties
や schema.json
と同じ scalardb-samples/scalardb-sample
ディレクトリに保存する。
そして database.properties
と schema.json
を引数に ScalarDB Cluster Schema Loader を実行。
$ java -jar scalardb-cluster-schema-loader-3.13.0-all.jar --config database.properties -f schema.json --coordinator
[main] INFO com.scalar.db.schemaloader.command.SchemaLoaderCommand - Config path: database.properties
[main] INFO com.scalar.db.schemaloader.command.SchemaLoaderCommand - Schema path: schema.json
[main] INFO com.scalar.db.cluster.common.ClusterNodeManager - Local IP addresses: [fe80:0:0:0:ce81:b1c:bd2c:69e%utun2, fe80:0:0:0:ce3f:5330:f35a:e6b5%utun1, fe80:0:0:0:d6cb:351c:550e:46e3%utun0, fe80:0:0:0:9863:97ff:feda:a21e%llw0, fe80:0:0:0:9863:97ff:feda:a21e%awdl0, fe80:0:0:0:4008:edff:feae:5644%anpi0, fe80:0:0:0:4008:edff:feae:5645%anpi1, fe80:0:0:0:8f0:9204:d36:e2dd%en5, 124.34.222.219, fdfe:1cff:ac75:c24d:1460:b321:c393:d620%en0, fe80:0:0:0:4bb:c15b:3025:b301%en0, 192.168.0.25, fe80:0:0:0:0:0:0:1%lo0, 0:0:0:0:0:0:0:1%lo0, 127.0.0.1]
[main] INFO com.scalar.db.cluster.common.ConsistentHashingDistribution - Refreshed the ring for [127.0.0.1]
[main] INFO com.scalar.db.schemaloader.SchemaOperator - Creating the table customers in the namespace sample succeeded
[main] INFO com.scalar.db.schemaloader.SchemaOperator - Creating the table orders in the namespace sample succeeded
[main] INFO com.scalar.db.schemaloader.SchemaOperator - Creating the table statements in the namespace sample succeeded
[main] INFO com.scalar.db.schemaloader.SchemaOperator - Creating the table items in the namespace sample succeeded
[main] INFO com.scalar.db.schemaloader.SchemaOperator - Creating the coordinator tables succeeded
成功すると PostgreSQL DB に以下のようなテーブルが作成されるらしい。注文と信用枠を管理する感じですかね。
データベースを確認してみよう。
$ kubectl run psql --rm -it --image docker.io/bitnami/postgresql:16.3.0-debian-12-r14 --env="PGPASSWORD=postgres" --command -- psql --host postgresql-scalardb-cluster -U postgres -d postgres -p 5432
postgres=# \dn
List of schemas
Name | Owner
-------------+-------------------
coordinator | postgres
public | pg_database_owner
sample | postgres
scalardb | postgres
(4 rows)
postgres=# \dt sample.*
List of relations
Schema | Name | Type | Owner
--------+------------+-------+----------
sample | customers | table | postgres
sample | items | table | postgres
sample | orders | table | postgres
sample | statements | table | postgres
(4 rows)
coordinator
, sample
, scalardb
の3つのスキーマができた。coordinator
と scalardb
は前回確認したものと一緒だろう。
そして sample
スキーマにはER図どおりの4つのテーブルが作成されている。
6. 初期データのロード
まだテーブルを作成しただけで全てのテーブルは空っぽなので、マスター系のテーブル(customers
と items
)に初期データをロードしていく。
$ ./gradlew run --args="LoadInitialData"
Starting a Gradle Daemon (subsequent builds will be faster)
...
BUILD SUCCESSFUL in 10s
2 actionable tasks: 2 executed
データを確認。
postgres=# select * from sample.customers ;
customer_id | name | credit_limit | credit_total | tx_id | tx_state | tx_version | tx_prepared_at | tx_committed_at | before_tx_id | before_tx_state | before_tx_version | before_tx_prepared_at | before_tx_committed_at | before_name | before_credit_limit | before_credit_total
-------------+---------------+--------------+--------------+--------------------------------------+----------+------------+----------------+-----------------+--------------+-----------------+-------------------+-----------------------+------------------------+-------------+---------------------+---------------------
1 | Yamada Taro | 10000 | 0 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | | |
3 | Suzuki Ichiro | 10000 | 0 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | | |
2 | Yamada Hanako | 10000 | 0 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | | |
(3 rows)
postgres=# select * from sample.items ;
item_id | name | price | tx_id | tx_state | tx_version | tx_prepared_at | tx_committed_at | before_tx_id | before_tx_state | before_tx_version | before_tx_prepared_at | before_tx_committed_at | before_price | before_name
---------+--------+-------+--------------------------------------+----------+------------+----------------+-----------------+--------------+-----------------+-------------------+-----------------------+------------------------+--------------+-------------
2 | Orange | 2000 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | |
1 | Apple | 1000 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | |
4 | Mango | 5000 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | |
3 | Grape | 2500 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | |
5 | Melon | 3000 | 38681f6a-ecda-4320-9c56-fd97b5559af5 | 3 | 1 | 1720760130050 | 1720760130497 | | | | | | |
(5 rows)
これでようやくサンプルアプリケーションを実行する環境が整った。
7. サンプルアプリケーションでトランザクション実行
7-1. 顧客情報の参照
ID=1 の顧客の情報。
$ ./gradlew run --args="GetCustomerInfo 1"
...
{"id": 1, "name": "Yamada Taro", "credit_limit": 10000, "credit_total": 0}
...
名前は Yamada Taro さん。
信用枠は 10000 円で、現在はまだ枠を消費していない。
7-2. 注文登録
ID=1 の Yamada さんがリンゴを3つ、オレンジを2つ注文。
リンゴは 1個 1000円、オレンジは 1個 2000円なので、合計7000円。
items
テーブルによると、リンゴのIDは 1 で、オレンジは 2、呪文のようなコマンドだがサンプルアプリケーションなのでぜいたくは言わない。
$ ./gradlew run --args="PlaceOrder 1 1:3,2:2"
...
{"order_id": "da92b8db-3c73-4236-a3d2-33b03d4be371"}
...
注文は da92b8db-3c73-4236-a3d2-33b03d4be371
という ID で登録された。
7-3. 注文確認
ID を利用して注文詳細を確認してみる。
$ ./gradlew run --args="GetOrder da92b8db-3c73-4236-a3d2-33b03d4be371"
...
{"order": {"order_id": "da92b8db-3c73-4236-a3d2-33b03d4be371","timestamp": 1720760994700,"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}}
...
分からん。JSONを整形してみよう。
{
"order": {
"order_id": "da92b8db-3c73-4236-a3d2-33b03d4be371",
"timestamp": 1720760994700,
"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
}
}
7-4. 注文登録 その2
Yamada さんが追加でメロンを1つ注文してくれた。メロンは 1個 3000 円だ。
$ ./gradlew run --args="PlaceOrder 1 5:1"
...
{"order_id": "22f327b7-9bc5-465e-95ae-331cd9edfc0e"}
...
7-5. 注文履歴の確認
Yamada さんの注文履歴を確認してみる。
$ ./gradlew run --args="GetOrders 1"
...
{"order": [{"order_id": "da92b8db-3c73-4236-a3d2-33b03d4be371","timestamp": 1720760994700,"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},{"order_id": "22f327b7-9bc5-465e-95ae-331cd9edfc0e","timestamp": 1720761558116,"customer_id": 1,"customer_name": "Yamada Taro","statement": [{"item_id": 5,"item_name": "Melon","price": 3000,"count": 1,"total": 3000}],"total": 3000}]}
...
整形。
{
"order": [
{
"order_id": "da92b8db-3c73-4236-a3d2-33b03d4be371",
"timestamp": 1720760994700,
"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
},
{
"order_id": "22f327b7-9bc5-465e-95ae-331cd9edfc0e",
"timestamp": 1720761558116,
"customer_id": 1,
"customer_name": "Yamada Taro",
"statement": [
{
"item_id": 5,
"item_name": "Melon",
"price": 3000,
"count": 1,
"total": 3000
}
],
"total": 3000
}
]
}
7-6. 売掛金額の確認
Yamada さんはこれまでに10000円分の注文をしたが、まだお金を支払っていない。
現在の売掛金合計を確認する。
$ ./gradlew run --args="GetCustomerInfo 1"
...
{"id": 1, "name": "Yamada Taro", "credit_limit": 10000, "credit_total": 10000}
...
信用枠10000円を使い切ってしまっている。
この状態でさらにブドウとマンゴーを1つずつ、計7500円の注文をしてみる。
$ ./gradlew run --args="PlaceOrder 1 3:1,4:1"
...
java.lang.RuntimeException: Credit limit exceeded
at sample.Sample.placeOrder(Sample.java:204)
at sample.command.PlaceOrderCommand.call(PlaceOrderCommand.java:33)
at sample.command.PlaceOrderCommand.call(PlaceOrderCommand.java:8)
at picocli.CommandLine.executeUserObject(CommandLine.java:2041)
at picocli.CommandLine.access$1500(CommandLine.java:148)
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2453)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2415)
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273)
at picocli.CommandLine$RunLast.execute(CommandLine.java:2417)
at picocli.CommandLine.execute(CommandLine.java:2170)
at sample.command.SampleCommand.main(SampleCommand.java:35)
...
Credit limit exceeded
(信用枠超過)というエラーになってしまった。
7-7. 支払い
Yamada さんは買掛残 10000円のうち、8000円を支払ってくれた。
$ ./gradlew run --args="Repayment 1 8000"
...
枠を確認。
$ ./gradlew run --args="GetCustomerInfo 1"
...
{"id": 1, "name": "Yamada Taro", "credit_limit": 10000, "credit_total": 2000}
...
買掛残が2000円に減った。
では改めてブドウとマンゴーの注文を入れてみよう。
$ ./gradlew run --args="PlaceOrder 1 3:1,4:1"
...
{"order_id": "0d84e309-fc2c-43e3-ba3f-f85f74df0119"}
...
今度は成功!
チュートリアルのシナリオはここまで!
8. お片付け
起動した ScalarDB Cluster と PostgreSQL を停止。
$ helm uninstall scalardb-cluster postgresql-scalardb-cluster
minikube tunnel
を実行しっぱなしになっているターミナルは Ctrl-C。
最後に k8s クラスターを削除。
$ minikube delete
🔥 docker の「minikube」を削除しています...
🔥 コンテナー「minikube」を削除しています...
🔥 /Users/pc-0056_ebihara/.minikube/machines/minikube を削除しています...
💀 クラスター「minikube」の全てのトレースを削除しました。
まとめ
ScalarDB Cluster を Kubernetes クラスターの中で動作させることができました。
実のところ自分は k8s に関しては、他の人が構築したクラスターをちょこちょこ触る程度の経験しかなかったのですが、一連の作業を通じて k8s リソースの種類や役割、kubectl コマンドの基本的な使い方について学べたのは想定外に大きな収穫でした。ラッキー!
ただ helm についてはまだちょっとよく分からないですね…。
非クラスターな ScalarDB 用のサンプルアプリケーションが、ビルド設定と接続設定を変更するだけで、コード修正なしに ScalarDB Cluster でも動作しました。つまり、ScalarDB の構成の違いは、クライアントアプリケーションコードに対して透過的ということです。
なお、今回のチュートリアルの内容だけだと、素の ScalarDB に対する ScalarDB Cluster の有用性をあまり感じられないかもしれません。
別途勉強したところによると、ScalarDB Cluster では gRPC や GraphQL、SQL といった API が利用でき、Java や JVM 言語以外のプログラミング言語による開発が可能なようです。
また認証・認可など、ScalarDB Cluster でのみ提供されるエンタープライズ向け機能もあるということです。
おまけ: ホストマシンから PostgreSQL への便利な接続方法
k8s で起動した PostgreSQL にアクセスするのにいちいち psql 用の Pod を起動するのはタルいので、自分はホストマシンで起動した psql から k8s クラスター内の PostgreSQL DB に接続できるようにしておきたい。
まずは PostgreSQL クライアントをインストール。
$ brew install libpq
.zshrc にパスとデフォルトの接続情報を追加。
export PATH=/opt/homebrew/opt/libpq/bin:$PATH
export PGHOST=127.0.0.1
export PGPORT=5432
export PGDATABASE=postgres
export PGUSER=postgres
~/.pgpass
ファイルを作成(パーミッションは 600 でないと psql がエラーになる)。
$ echo "127.0.0.1:5432:postgres:postgres:postgres" > ~/.pgpass
$ chmod 600 ~/.pgpass
k8s のポートフォワードを設定。ホストマシンの5432番ポートへのアクセスが、PostgreSQLが起動するPodの5432番ポートにフォワードされるようにする。
$ kubectl port-forward --namespace default svc/postgresql-scalardb-cluster 5432:5432
kubectl port-forward
を単純に実行すると、ポートフォワードしている間はずっとコマンドが実行中になってしまい制御が返ってこない。
このターミナルはポートフォワード専用にして、psql の実行自体は別のターミナルから行うか、kubectl port-forward
コマンドラインの最後に &
を付けてバックグラウンド実行してしまえばよい。
これでホストのターミナルから psql
とタイプするだけで、クラスター内の PostgreSQL にアクセスできるようになる。
$ psql
psql (16.3)
Type "help" for help.
postgres=#