要約
- Apache ShardingSphere-JDBCを使用し、SQL Serverのシャーディングを行う設定ファイルを紹介します
- 本番運用を見据えて、シャーディング設定情報をZooKeeperやEtcdで管理する方法を紹介します
Apache ShardingSphereとは何か
Apache ShardingSphereはRDBのシャーディング(水平パーティショニング)を行うためのライブラリです。ShardingSphere-JDBCとShardingSphere-Proxyから構成されています。
ShardingSphere-JDBC
ShardingSphere-JDBCは、アプリケーションのJDBCドライバ層に組み込むライブラリで、既存のJDBC APIを拡張して水平パーティショニングやレプリケーションを透過的に実現します。依存ライブラリを追加するだけで、シャード間ルーティング、分散トランザクション、フェイルオーバーなどがサポートされ、Spring BootやMyBatisなどのフレームワークを意識せずに導入可能です。いくつかの制約はありますが、多数のRDBMSに対応しています。
ShardingSphere-Proxy
ShardingSphere-Proxyは、MySQL/PostgreSQLプロトコルをサポートする独立したミドルウェアで、アプリケーションとDBの間に配置することでシャーディング、読み書き分離、マスキングなどをプロキシレベルで透過的に提供します。複数言語やフレームワークから利用可能ですが、対応DBMSが限られている点に注意が必要です。
このほか、ShardingSphereの詳細は、公式サイトを参照してください
実施したこと
今回は、ShardingSphere-JDBCを使用して、簡単なシャーディングルールに基づきデータの格納、検索を行うSpringBootアプリを作成しました。
- 簡単な注文テーブル(注文ID, 顧客ID, 詳細)を顧客IDでシャーディングする
- データの格納先は、顧客ID(INT)の2で割った余りで決定する
- DBはローカルにdockerで2インスタンス用意する
構成
- JDK 21
- Spring Boot 3.4.5
- MyBatis Spring Boot Starter 3.0.4
- Apache ShardingSphere-JDBC 5.5.2
- SQL Server
ShardingSphere-JDBCの設定
シャーディングするデータソースとルールを定義します。
# ShardingSphere 設定ファイル
# データソース定義
# dockerで起動した2台のSQL Serverへの接続情報
dataSources:
ds0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
url: jdbc:sqlserver://localhost:14330;databaseName=db0;encrypt=false;trustServerCertificate=true;sendStringParametersAsUnicode=true
username: sa
password: xxxxxxxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
ds1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
url: jdbc:sqlserver://localhost:14331;databaseName=db1;encrypt=false;trustServerCertificate=true;sendStringParametersAsUnicode=true
username: sa
password: xxxxxxxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# シャーディングルール定義
rules:
- !SHARDING
tables:
# 注文テーブルのシャーディング設定
t_order:
actualDataNodes: ds${0..1}.t_order # ds0.t_order, ds1.t_order に展開される
databaseStrategy:
standard:
shardingColumn: customer_id # 顧客IDでシャーディングする
shardingAlgorithmName: db_mod_sharding_alg
# シャーディングアルゴリズム定義
shardingAlgorithms:
db_mod_sharding_alg:
type: INLINE
props:
# customer_id を 2 で割った余りで ds0 または ds1 に振り分ける INLINE 表現
algorithm-expression: ds${customer_id % 2}
application.properties
ShardingSphere-JDBCを使用するよう、application.propertiesを設定します。
# ShardingSphere Driver設定
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# ShardingSphere 設定ファイルの指定
spring.datasource.url=jdbc:shardingsphere:classpath:sharding-sphere.yaml
依存関係の設定
ShardingSphere-JDBCへの依存関係をpom.xmlに追加します
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.2</version>
</dependency>
データベースの準備
dockerで2台のSQL Serverを起動します。
# 2台のSQL Serverを起動する
services:
sqlserver-0:
image: mcr.microsoft.com/mssql/server:latest
container_name: sqlserver-0
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "xxxxxxxx"
# Map host port 14330 to container port 1433
ports:
- "14330:1433"
sqlserver-1:
image: mcr.microsoft.com/mssql/server:latest
container_name: sqlserver-1
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "xxxxxxxx"
# Map host port 14331 to container port 1433
ports:
- "14331:1433"
立ち上げた2つのSQL Serverインスタンスに同じDDLを使用してテーブルを作成しておきます。
sqlserver-0で以下を実行
CREATE DATABASE db0;
GO
USE db0;
GO
CREATE TABLE t_order (
order_id BIGINT PRIMARY KEY,
customer_id INT NOT NULL, -- シャーディングキー
order_details NVARCHAR(255)
-- 他に必要なカラム
);
GO
sqlserver-1では、db1に同じテーブルを作成しておきます。
SpringBootアプリケーションの開発
通常のSpringBootアプリケーションの開発と同様です。
MyBatisもシャーディングを意識せずに使えます。
今回は /api/ordersに対する注文の登録と{orderId}を指定して注文を検索するAPIを実装しました。
動作確認
APIから注文を登録後、sqlcmd等で確認します。偶数の顧客IDからの注文が1台目のSQLServer(ds0)に、奇数顧客IDからの注文が2台目のSQLServer(ds1)に登録されていたら、成功です。
# 偶数顧客IDの確認
sqlcmd -S localhost,14330 -U sa -P xxxxxxxx -d db0 -Q "SELECT * FROM t_order;"
# 奇数顧客IDの確認
sqlcmd -S localhost,14331 -U sa -P xxxxxxxx -d db1 -Q "SELECT * FROM t_order;"
SpringBootアプリ内でのMyBatisからの検索はデータの登録先を意識することは不要です。
設定情報のクラスタ管理
ShardingSphere-JDBCでは、データストアやシャーディングルールなどをZooKeeperやEtcdを使用して分散管理するClusterモードを指定することができます。クライアント間でのルールの不整合等を防ぐことができ、本番運用では、Clusterモードの使用が推奨されています。
# モード設定 (Cluster Mode with ZooKeeper)
mode:
type: Cluster
repository:
type: ZooKeeper
props:
namespace: shardingsphere
server-lists: localhost:2181,localhost:2182,localhost:2183 # ZooKeeperを3台構成で起動
connectionTimeoutMilliseconds: 5000 # e.g., 5 seconds connection timeout
retryIntervalMilliseconds: 500
maxRetries: 3
timeToLiveSeconds: 60
operationTimeoutMilliseconds: 5000 # e.g., 5 seconds operation timeout
ZooKeeperは以下のように構成しました
# ZooKeeper 3-node cluster for ShardingSphere Cluster Mode
version: '3.8'
services:
zookeeper1:
image: zookeeper:3.9
container_name: zookeeper1
hostname: zookeeper1
ports:
- "2181:2181" # Client port
environment:
ZOO_MY_ID: 1
# server.<myid>=<hostname>:<peer port>:<leader election port>
ZOO_SERVERS: server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888
ZOO_CFG_EXTRA: |
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true
clientPort=2181
clientPortAddress=0.0.0.0
volumes:
- zk-data1:/data # ZooKeeper data directory
- zk-datalog1:/datalog # ZooKeeper transaction log directory (optional, can improve performance)
networks:
- zookeeper-net
zookeeper2:
image: zookeeper:3.9
container_name: zookeeper2
hostname: zookeeper2
ports:
- "2182:2181" # Client port
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888
ZOO_CFG_EXTRA: |
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true
clientPort=2181
clientPortAddress=0.0.0.0
volumes:
- zk-data2:/data
- zk-datalog2:/datalog
networks:
- zookeeper-net
zookeeper3:
image: zookeeper:3.9
container_name: zookeeper3
hostname: zookeeper3
ports:
- "2183:2181" # Client port
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zookeeper1:2888:3888 server.2=zookeeper2:2888:3888 server.3=zookeeper3:2888:3888
ZOO_CFG_EXTRA: |
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true
clientPort=2181
clientPortAddress=0.0.0.0
volumes:
- zk-data3:/data
- zk-datalog3:/datalog
networks:
- zookeeper-net
volumes:
zk-data1:
driver: local
zk-datalog1:
driver: local
zk-data2:
driver: local
zk-datalog2:
driver: local
zk-data3:
driver: local
zk-datalog3:
driver: local
networks:
zookeeper-net:
driver: bridge
まとめ
ShardingSphere-JDBCを使用して、アプリケーションからは意識することなく、設定のみでシャーディングを実施することができました。