TPC-Cは、TPCの管理組織(Transaction Processing Performance Council)が策定した一連の性能ベンチマークの1つであり、その登場以来、データベース業界における重要な性能評価基準として広く利用されています。世界中の主要なデータベースベンダーが、TPC-Cのランキングでより良い成績を収めることを目指して、TPC委員会にベンチマーク結果を提出しています。OceanBaseデータベースは、2019年と2020年の2回にわたって、TPC-Cのベンチマークで世界記録を更新し、同ランキングに初めてランクインした分散リレーショナルデータベースとなりました。
本記事では、OceanBaseデータベース上でTPC-Cテストを実行することにより、OceanBaseデータベースの OLTP性能を検証します。
TPC-Cについて
TPC-Cベンチマークのデータベースモデル
TPC-Cベンチマークは、卸売業の業務を模したデータベースの性能を測定します。TPC-Cベンチマークでは、データベースの初期状態が規定されています。まず、「ITEM」テーブルには固定された10万種類の商品データが格納されます。倉庫の数はテストの規模に応じて調整可能です。WAREHOUSEテーブルのレコード数をW(倉庫の数)とした場合、他のテーブルのデータ量は以下のように決まります。
STOCKテーブル:W × 10万件のレコード(各倉庫が10万種類の商品在庫を保持)。 -DISTRICTテーブル:W × 10件のレコード(各倉庫が10の地区にサービスを提供)。CUSTOMERテーブル:W × 10 × 3000件のレコード(各地区に3000人の顧客)。HISTORYテーブル:W × 10 × 3000件のレコード(顧客ごとに1件の取引履歴)。ORDERテーブル:W × 10 × 3000件のレコード(各地区で3000件の注文)。このうち最後に生成された 900件の注文はNEW-ORDERテーブルに追加されます。各注文には5~15件のORDER-LINEレコードがランダムに生成されます。
テストでは、各地域(DISTRICT)には対応する端末(Terminal)が1つずつあり、ユーザーへのサービス提供をシミュレートします。それぞれの端末は、周期的に繰り返し様々なトランザクション(取引処理)を実行します。
顧客が注文すると、複数の注文明細(ORDER-LINE)を含む注文(ORDER)が生成され、新規注文(NEW-ORDER)リストに追加されます。
顧客が注文に対する支払いを行うと、取引履歴(HISTORY)が生成されます。1つの注文(ORDER)には平均 10件の注文明細(ORDER-LINE)が含まれており、そのうち1%はリモート倉庫から取得する必要があります。これらが、TPC-Cモデルを構成する9つのデータテーブルとその関係性の概要です。
トランザクションのタイプ
このベンチマークには5種類のトランザクションが含まれています。
- NewOrder:新規注文リクエストでは、倉庫から5~15個のアイテムがランダムに選択され、新しい注文が作成されます。そのうち1%のトランザクションで、ロールバック(つまり
err)が必要になります。一般的には、新規注文リクエストが、全トランザクションリクエストの45%を超えることはありません。 - Payment:注文の支払いが完了すると、顧客アカウントの残高が更新され、支払い状況が反映されます。これは全トランザクションリクエストの43%を占めています。
- OrderStatus:直近注文クエリでは、いずれかのユーザーがランダムに選択され、そのユーザーの最新注文を照会した上で、注文内の各商品のステータスが表示されます。これは全トランザクションリクエストの4%を占めています。
- Delivery:配送シミュレーションのバッチ処理トランザクションにより、注文ユーザーの残高を更新し、NewOrderから出荷伝票を削除します。これは全トランザクションリクエストの 4% を占めています。
- StockLevel:在庫切れ状態の分析は、全トランザクションリクエストの4%を占めています。
環境の準備
OceanBaseクラスタ
デプロイされたOceanBaseクラスタのタイプによって、OceanBaseデータベースのパフォーマンスの評価方法は異なります。クイックスタートガイドに従って単一ノードでOceanBaseクラスタを構築した場合、本記事では、そのデータベースは単一のマシン上でどのように動作するかを確認できます。OceanBaseデータベースの分散アーキテクチャにおけるスケーラブルなOLTP性能を検証したい場合は、最低でも3つ以上のノードで構成されたクラスタでのテストが推奨されます。
この例で使用されているテナントモードはMySQLモードで、テナント名はtestです。独自のテナントを作成することができますが、詳細な手順については、マルチテナント機能の体験を参照してください。
BenchmarkSQLのインストール
TPCの管理組織は、TPC-Cについて厳密かつ詳細なテスト基準を定義しています。通常、開発者がTPC-Cのシナリオをシミュレートしてテストを行いたい場合は、現在よく使われているオープンソースのテストツールを利用できます。例えば、この記事で用いているBenchmarkSQLがそれに当たります。ダウンロードはBenchmarkSQLの公式サイトにアクセスしてください。
注意
テスト環境には、バージョン1.8.0以上のJava実行環境が必要です。
Benchmark SQL5への対応
Benchmark SQL5はOceanBaseデータベースのTPC-Cテストをサポートしていないため、このセクションでは、Benchmark SQL5の一部ソースコードを修正してOceanBaseデータベースに対応させる方法を詳しく説明します。
benchmarksql-5.0/src/client/jTPCC.javaファイルを修正し、OceanBaseデータベース関連の内容を追加します。if (iDB.equals("firebird")) dbType = DB_FIREBIRD; else if (iDB.equals("oracle")) dbType = DB_ORACLE; else if (iDB.equals("postgres")) dbType = DB_POSTGRES; else if (iDB.equals("oceanbase")) dbType = DB_OCEANBASE; else { log.error("unknown database type '" + iDB + "'"); return; }benchmarksql-5.0/src/client/jTPCCConfig.javaファイルを修正し、OceanBaseデータベースタイプを追加します。public final static int DB_UNKNOWN = 0, DB_FIREBIRD = 1, DB_ORACLE = 2, DB_POSTGRES = 3, DB_OCEANBASE = 4;benchmarksql-5.0/src/client/jTPCCConnection.javaファイルを修正し、SQLサブクエリにAS Lのエイリアスを追加します。default: stmtStockLevelSelectLow = dbConn.prepareStatement( "SELECT count(*) AS low_stock FROM (" + " SELECT s_w_id, s_i_id, s_quantity " + " FROM bmsql_stock " + " WHERE s_w_id = ? AND s_quantity < ? AND s_i_id IN (" + " SELECT ol_i_id " + " FROM bmsql_district " + " JOIN bmsql_order_line ON ol_w_id = d_w_id " + " AND ol_d_id = d_id " + " AND ol_o_id >= d_next_o_id - 20 " + " AND ol_o_id < d_next_o_id " + " WHERE d_w_id = ? AND d_id = ? " + " ) " + " )AS L"); break;変更後のソースコードを再コンパイルします。
[oceanbase@testdrier test]# cd benchmarksql-5.0 [oceanbase@testdrier benchmarksql-5.0]# antbenchmarksql-5.0/run/funcs.shファイルを修正し、OceanBaseデータベースタイプを追加します。function setCP() { case "$(getProp db)" in firebird) cp="../lib/firebird/*:../lib/*" ;; oracle) cp="../lib/oracle/*" if [ ! -z "${ORACLE_HOME}" -a -d ${ORACLE_HOME}/lib ] ; then cp="${cp}:${ORACLE_HOME}/lib/*" fi cp="${cp}:../lib/*" ;; postgres) cp="../lib/postgres/*:../lib/*" ;; oceanbase) cp="../lib/oceanbase/*:../lib/*" ;; esac myCP=".:${cp}:../dist/*" export myCP } ...省略 case "$(getProp db)" in firebird|oracle|postgres|oceanbase) ;; "") echo "ERROR: missing db= config option in ${PROPS}" >&2 exit 1 ;; *) echo "ERROR: unsupported database type 'db=$(getProp db)' in ${PROPS}" >&2 exit 1 ;; esacbenchmarksql-5.0/run/runDatabaseBuild.shファイルを修正します。AFTER_LOAD="indexCreates foreignKeys extraHistID buildFinish" # 変更後: AFTER_LOAD="indexCreates buildFinish"
設定ファイルの変更
設定ファイルprops.obは、BenchmarkSQLのrun/ディレクトリにあります:
db=oceanbase
driver=com.mysql.jdbc.Driver
conn=jdbc:mysql://127.0.0.1:2881/tpccdb?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&allowMultiQueries=true
user=root@test
password=****
warehouses=10
loadWorkers=2
//fileLocation=/data/temp/
terminals=10
//To run specified transactions per terminal- runMins must equal zero
runTxnsPerTerminal=0
//To run for specified minutes- runTxnsPerTerminal must equal zero
runMins=10
//Number of total transactions per minute
limitTxnsPerMin=0
//Set to true to run in 4.x compatible mode. Set to false to use the
//entire configured database evenly.
terminalWarehouseFixed=true
//The following five values must add up to 100
newOrderWeight=45
paymentWeight=43
orderStatusWeight=4
deliveryWeight=4
stockLevelWeight=4
// Directory name to create for collecting detailed result data.
// Comment this out to suppress.
resultDirectory=my_result_%tY-%tm-%td_%tH%tM%tS
osCollectorScript=./misc/os_collector_linux.py
osCollectorInterval=1
//osCollectorSSHAddr=user@dbhost
//osCollectorDevices=net_eth0 blk_sda
説明
db:データベースのタイプを指定します。ここではテンプレートと一致させてください。driver:ドライバーファイルとして、MySQLのJDBCドライバーを使用することを推奨します:mysql-connector-java-5.1.47、ドライバのダウンロードはこちら。conn:ここでは、IPアドレスにはOceanBase ServerのIPを入力し、ポートにはOceanBase Serverのデプロイメントポートを指定することを推奨します。その他の部分はテンプレートと一致させてください。user&password:環境で使用されているユーザー名、テナント名、パスワードに基づいて設定します。環境に複数のOceanBaseクラスタが存在する場合、userの形式は{user_name}@{tenant_name}#{cluster_name}を推奨します。warehouses:ウェアハウスの数を指定します。ウェアハウスの数によってパフォーマンステストの結果が決まります。複数ノードのOceanBaseクラスタをテストする場合は、1000以上のウェアハウスを選択することを推奨します。マシンのリソース設定が限られている場合、100個のウェアハウスを選択してテストを実行できます。loadWorkers:ウェアハウスデータの読み込み時の同時実行数を指定します。マシンのスペックが高い場合、この値を大きく設定できます(例:100)。マシンのスペックに限りがある場合は、この値を小さく設定する必要があります(例:10並列)。並列数が高すぎると、メモリ消費が速すぎてエラーが発生し、データロードをやり直す必要が生じる可能性があります。terminals:性能負荷テスト時の並列数を指定します。並列数は「倉庫数 × 10」を超えないようにすることを推奨します。超えると、不要なロック待ちが発生する可能性があります。本番環境では、このパラメータは最大1000に設定することを推奨します。テスト環境では、100から始めることを推奨します。runMins:パフォーマンステストの継続時間を指定します。時間が長いほど、データベースの性能と安定性をよりよく検証できます。10分以上を推奨し、本番環境のマシンでは1時間以上を推奨します。
データ準備
tpccdbデータベースの作成
テストテナントtestに、今回のテスト用データベースtpccdbを作成します:
CREATE DATABASE tpccdb;
テーブルの作成
テーブル作成スクリプトは通常、benchmarkSQLのrun/sql.commonディレクトリまたはその他の指定ディレクトリに配置されます。テーブル作成スクリプトは以下の通りで、パーティションテーブル方式で作成し、ほとんどのテーブルは倉庫IDによってHASHでパーティション分割されます。パーティション数は、テストするデータの規模とマシンの数によって決まります。 クラスタが1台または3台のマシンのみで構成されている場合、パーティション数は9で十分です。5000倉以上、またはクラスタのノード数が多い場合は、パーティション数を99に調整できます。
[root@obce-0000 run]# cat sql.common/tableCreates_parts.sql
CREATE TABLE bmsql_config (
cfg_name varchar(30) primary key,
cfg_value varchar(50)
);
-- drop tablegroup tpcc_group;
CREATE TABLEGROUP tpcc_group binding true partition by hash partitions 9;
CREATE TABLE bmsql_warehouse (
w_id integer not null,
w_ytd decimal(12,2),
w_tax decimal(4,4),
w_name varchar(10),
w_street_1 varchar(20),
w_street_2 varchar(20),
w_city varchar(20),
w_state char(2),
w_zip char(9),
primary key(w_id)
)tablegroup='tpcc_group' partition by hash(w_id) partitions 9;
CREATE TABLE bmsql_district (
d_w_id integer not null,
d_id integer not null,
d_ytd decimal(12,2),
d_tax decimal(4,4),
d_next_o_id integer,
d_name varchar(10),
d_street_1 varchar(20),
d_street_2 varchar(20),
d_city varchar(20),
d_state char(2),
d_zip char(9),
PRIMARY KEY (d_w_id, d_id)
)tablegroup='tpcc_group' partition by hash(d_w_id) partitions 9;
CREATE TABLE bmsql_customer (
c_w_id integer not null,
c_d_id integer not null,
c_id integer not null,
c_discount decimal(4,4),
c_credit char(2),
c_last varchar(16),
c_first varchar(16),
c_credit_lim decimal(12,2),
c_balance decimal(12,2),
c_ytd_payment decimal(12,2),
c_payment_cnt integer,
c_delivery_cnt integer,
c_street_1 varchar(20),
c_street_2 varchar(20),
c_city varchar(20),
c_state char(2),
c_zip char(9),
c_phone char(16),
c_since timestamp,
c_middle char(2),
c_data varchar(500),
PRIMARY KEY (c_w_id, c_d_id, c_id)
)tablegroup='tpcc_group' partition by hash(c_w_id) partitions 9;
CREATE TABLE bmsql_history (
hist_id integer,
h_c_id integer,
h_c_d_id integer,
h_c_w_id integer,
h_d_id integer,
h_w_id integer,
h_date timestamp,
h_amount decimal(6,2),
h_data varchar(24)
)tablegroup='tpcc_group' partition by hash(h_w_id) partitions 9;
CREATE TABLE bmsql_new_order (
no_w_id integer not null ,
no_d_id integer not null,
no_o_id integer not null,
PRIMARY KEY (no_w_id, no_d_id, no_o_id)
)tablegroup='tpcc_group' partition by hash(no_w_id) partitions 9;
CREATE TABLE bmsql_oorder (
o_w_id integer not null,
o_d_id integer not null,
o_id integer not null,
o_c_id integer,
o_carrier_id integer,
o_ol_cnt integer,
o_all_local integer,
o_entry_d timestamp,
PRIMARY KEY (o_w_id, o_d_id, o_id)
)tablegroup='tpcc_group' partition by hash(o_w_id) partitions 9;
CREATE TABLE bmsql_order_line (
ol_w_id integer not null,
ol_d_id integer not null,
ol_o_id integer not null,
ol_number integer not null,
ol_i_id integer not null,
ol_delivery_d timestamp,
ol_amount decimal(6,2),
ol_supply_w_id integer,
ol_quantity integer,
ol_dist_info char(24),
PRIMARY KEY (ol_w_id, ol_d_id, ol_o_id, ol_number)
)tablegroup='tpcc_group' partition by hash(ol_w_id) partitions 9;
CREATE TABLE bmsql_item (
i_id integer not null,
i_name varchar(24),
i_price decimal(5,2),
i_data varchar(50),
i_im_id integer,
PRIMARY KEY (i_id)
);
CREATE TABLE bmsql_stock (
s_w_id integer not null,
s_i_id integer not null,
s_quantity integer,
s_ytd integer,
s_order_cnt integer,
s_remote_cnt integer,
s_data varchar(50),
s_dist_01 char(24),
s_dist_02 char(24),
s_dist_03 char(24),
s_dist_04 char(24),
s_dist_05 char(24),
s_dist_06 char(24),
s_dist_07 char(24),
s_dist_08 char(24),
s_dist_09 char(24),
s_dist_10 char(24),
PRIMARY KEY (s_w_id, s_i_id)
)tablegroup='tpcc_group' use_bloom_filter=true partition by hash(s_w_id) partitions 9;
以下のコマンドを実行してテーブルを作成します。
./runSQL.sh props.ob sql.common/tableCreates_parts.sql
データのロード
データのロードはデータの初期化を意味します。その読ロード速度はマシンのスペックによって決まります。スペックが高いマシンほど、ロード速度は速くなります。
./runLoader.sh props.ob
データロードのINSERTSQLは、props.ob内のJDBC URLで指定されたBatch Insert機能を利用しています。この機能を有効にすると、書き込みパフォーマンスが大幅に向上します。
インデックスの作成
データが初期化したら、クラスタのtestテナントにログインし、tpccdbに以下の2つのインデックスを追加作成します。
[root@obce-0000 run]# cat sql.common/indexCreates.sql
CREATE INDEX bmsql_customer_idx1
on bmsql_customer (c_w_id, c_d_id, c_last, c_first) local;
CREATE INDEX bmsql_oorder_idx1
on bmsql_oorder (o_w_id, o_d_id, o_carrier_id, o_id) local;
テスト開始
パフォーマンステストを開始する前に、まず対応するテナントにログインしてクラスタのメジャーコンパクション(major freeze)を実行し、より良いテスト結果を得ることを推奨します。以下の方法で手動でメジャーコンパクションをトリガーできますが、このプロセスは必須ではありません。
obclient[oceanbase]> ALTER SYSTEM MAJOR FREEZE;
次のクエリでIDLEが返される場合は、メジャーコンパクションが完了したことを示します。
MySQL [oceanbase]> SELECT * FROM oceanbase.CDB_OB_ZONE_MAJOR_COMPACTION;
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
| TENANT_ID | ZONE | BROADCAST_SCN | LAST_SCN | LAST_FINISH_TIME | START_TIME | STATUS |
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
| 1 | zone1 | 1664503499339325817 | 1664503499339325817 | 2022-09-30 10:05:19.442115 | 2022-09-30 10:04:59.369976 | IDLE |
| 1002 | zone1 | 1 | 1 | 1970-01-01 08:00:00.000000 | 1970-01-01 08:00:00.000000 | IDLE |
+-----------+-------+---------------------+---------------------+----------------------------+----------------------------+--------+
2 rows in set
メジャーコンパクション完了後、テストを実行開始します:
./runBenchmark.sh props.ob
TPC-Cは、tpmC(Transactions per Minute)値で、システムの最大有効スループットを測定します。の際のトランザクションはNewOrderトランザクションを基準としており、最終的な測定単位は1分間あたりに処理される注文数となります。
OceanBaseデータベースのScalable OLTPを体験
上記のテストでは、クラスタが単一ノードでも複数ノードでも、デフォルト設定ではトランザクション処理を担当するのは、常に1つのゾーンの1つのレプリカ(=テナントのリーダー)だけです。これは、OceanBaseの初期設定では、テナントのリーダーがゾーンごとに優先順位に基づいて自動的に配置されるため、他のゾーンにあるレプリカは処理に参加しないからです。
OceanBaseは分散データベースとして、テナントの各パーティションのリーダーを複数のレプリカに分散(shuffle)させることができます。これにより、複数のマシンにわたる処理能力のリニアなスケーラビリティを実現できます。
お使いのクラスタ環境が3ノード以上の構成であれば、管理者として以下のコマンドを実行し、再度テストを起動するだけで、分散クラスタの拡張モードでの処理能力を体験できます。
obclient> ALTER TENANT test SET PRIMARY_ZONE='zone1';