tpcc-mysqlによるMySQLのベンチマーク

データベースに対する負荷ツール、ベンチマークツールは世の中にたくさんあるのですが、単一テーブルに対する主キー検索しかしないものなど、CPUクロック勝負の比較的単純なものが多くを占めているようです。そこで本日は、もう少し高度なベンチマーク仕様としてのTPC-Cと、MySQL向けの簡易実装であるtpcc-mysqlをご紹介します。
TPC-Cとは卸売業における注文・支払いなどの処理を擬似的に再現した業務モデルで、TPCという業界団体によって策定されたものです。9種類のテーブルに対する5種類のトランザクションがミックスされており、そのうち注文処理のスループットを測定結果として利用します。公式サイトで仕様書(PDF)が公開されています。
テーブル定義は以下のようになっています。ちなみにこのER図はMySQL Workbenchで出力したものです。

  • warehouse:倉庫です。倉庫表のレコード数は全体の規模を決めるスケールファクターとなっています。
  • district:配送区域です。倉庫あたり10の配送区域が存在します。
  • customer:顧客です。配送区域あたり3,000の顧客が存在します。
  • hisotory:支払い履歴です。支払い履歴表は測定中にレコードが増えていきます。
  • item:商品です。商品数は10万種類で固定されています。
  • stock:在庫です。warehouse×10万レコードあります。
  • orders:注文です。注文表は測定中にレコードが増えていきます。
  • order_line:注文明細です。一度の注文で平均10個の商品を発注します。
  • new_orders:未配送の新規注文です。注文を受けるとINSERTされ、商品を配送するとDELETEされます。

5種類のトランザクションは以下のようになっています。測定においてはこれら5種類のトランザクションをおよそ10:10:1:1:1でミックスします。

  • new order:注文です。主に注文データのINSERTを行います。
  • payment:支払いです。主に貸借のUPDATEと履歴データのINSERTを行います。
  • order status:注文状況の確認です。注文データのSELECTを行います。
  • delivery:配送です。新規注文のDELETEと注文データのフラグ更新を行います。
  • stock level:在庫確認です。在庫データのSELECTを行います。

TPC-Cの実装はあまり公開されているものがなく、私の知る限りDBT-2とtpcc-mysqlしかないようです。またこれらはいずれもTPC-Cの仕様をかいつまんで実装したものとなっており、全ての仕様を満たしてはいません。そのため測定結果はTPC-Cの公式スコアとは言えませんので、ご注意ください。
TPC-Cの概要が分かったところで、早速tpcc-mysqlのセットアップをしていきましょう。tpcc-mysqlMySQLのコンサル会社であるPercona Inc.によって開発されたもので、launchpadでソースが公開されています。launchpadからのソース取得にはBazaarを使います。CentOS 5.2においてBazaarすら入っていない状態からは、以下のようにしてインストールします。

# rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-2.noarch.rpm
# yum install bzr

$ mkdir bazaar
$ cd bazaar
$ bzr init
$ ls -la
合計 16
drwxrwxr-x  3 taira taira 4096  2月 11 20:20 .
drwxr-xr-x 39 taira taira 4096  2月 11 20:20 ..
drwxrwxr-x  6 taira taira 4096  2月 11 20:20 .bzr

ソースを取得します。

$ bzr branch lp:~percona-dev/perconatools/tpcc-mysql
Branched 2 revision(s).
$ ls
tpcc-mysql
$ cd tpcc-mysql
$ ls
add_fkey_idx.sql  count.sql  create_table.sql  drop_cons.sql  src

ビルドします。make all一発ですが、その前にmysql-develがインストールされている必要があります。

$ cd src
$ ls
Makefile    load.c    ordstat.c  rthist.h    slev.c      support.c
delivery.c  main.c    payment.c  sequence.c  spt_proc.c  tpc.h
driver.c    neword.c  rthist.c   sequence.h  spt_proc.h  trans_if.h
$ make all
cc -w -O2 -g -I. `mysql_config --include`  -c load.c
(略)
$ cd ..
$ ls -l
合計 208
 -rw-rw-r-- 1 taira taira   1506  2月 11 20:22 add_fkey_idx.sql
 -rw-rw-r-- 1 taira taira    317  2月 11 20:22 count.sql
 -rw-rw-r-- 1 taira taira   3485  2月 11 20:22 create_table.sql
 -rw-rw-r-- 1 taira taira    763  2月 11 20:22 drop_cons.sql
 drwxrwxr-x 2 taira taira   4096  2月 11 20:24 src
 -rwxrwxr-x 1 taira taira  47381  2月 11 20:24 tpcc_load
 -rwxrwxr-x 1 taira taira 137049  2月 11 20:24 tpcc_start

ビルドが完了しました。tpcc_loadがデータ生成プログラム、tpcc_startが負荷生成プログラムです。
次に、データベース側のセットアップを行います。

mysql> grant all privileges on tpcc.* to tpcc@'%' identified by 'XXXX';
mysql> create database tpcc;

$ mysql -u tpcc -p -h vostro tpcc < create_table.sql
$ mysql -u tpcc -p -h vostro tpcc < add_fkey_idx.sql

実際にはデータベース名、データベースユーザ名は何でも構いません。create_table.sqlでテーブルを作成し、add_fkey_idx.sqlで外部キーと追加の索引を作成します。スクリプトを読めばすぐ分かりますが、ストレージエンジンにはInnoDBを利用しています。TPC-Cはその仕様上1%の確率で注文処理をロールバックしなければならないので、MyISAMは使えません。また、Falconを使いたい場合はスクリプトを修正する必要があります。
tpcc_loadを使って初期データをロードします。

$ ./tpcc_load
*************************************
*** ###easy### TPC-C Data Loader  ***
*************************************

 usage: tpcc_load [server] [DB] [user] [pass] [warehouse]
      OR
        tpcc_load [server] [DB] [user] [pass] [warehouse] [part] [min_wh] [max_wh]

           * [part]: 1=ITEMS 2=WAREHOUSE 3=CUSTOMER 4=ORDERS

$ ./tpcc_load vostro tpcc tpcc XXXX 1
*************************************
*** ###easy### TPC-C Data Loader  ***
*************************************
<Parameters>
     [server]: vostro
     [DBname]: tpcc
       [user]: tpcc
       [pass]: XXXX
  [warehouse]: 1
TPCC Data Load Started...
Loading Item
.................................................. 5000
(略)
Loading Orders for D=10, W= 1
.......... 1000
.......... 2000
.......... 3000
Orders Done.

...DATA LOADING COMPLETED SUCCESSFULLY.

tpcc_loadにはwarehouseというパラメータを与える必要があります。これはそのままwarehouse表のレコード数になります。warehouse表のレコード数によってその他のテーブルのレコード数が決まるので、この値がスケールファクターになります。1 warehouse(以下WH)あたりデータベース容量が約100MB増加します。クアッドコアで8GB程度のメモリを搭載したいまどきのPCサーバの場合、CPUバウンドの負荷をかけたいときは40〜100WH程度、I/Oバウンドの負荷をかけたいときは400〜1,000WH程度の値を指定します。40WH未満の規模だといくつかのトランザクションでロック競合が多発してしまうため、おすすめしません。上記の例は1WHですが、これはあくまでサンプルです。
ロードが終われば測定準備完了です。すぐに測定を始めても良いのですが、ここで一度データベースのバックアップを取得しておくことをおすすめします。TPC-Cは測定を行うとデータベースに不可逆な変更を加えてしまうためです。
バックアップが済んだら、tpcc_startプログラムで測定を開始します。

$ ./tpcc_start
***************************************
*** ###easy### TPC-C Load Generator ***
***************************************

 usage: tpcc_start [server] [DB] [user] [pass] [warehouse] [connection] [rampup] [measure]
  • warehouse:ロード時に指定したwarehouse表のレコード数を指定します。
  • connection:データベースに対する同時接続数を指定します。これはクライアント側で処理を行うスレッド数でもあります。tpcc-mysqlは他の負荷ツールと違って思考遅延時間、キー入力時間といったものをわざと実装しておらず、例えばconnectionに10を指定すると10スレッドが休みなくSQLを投げ続けるという、データベースに対してかなり厳しい仕様になっています。実際の測定にあたってはconnectionを5、10、15、…と少しずつ増やしながらプログラムを何度も実行し、スループットのピーク値を探すというのが基本的な流れになります。
  • rampup:測定を開始するまでの助走時間を秒単位で指定します。ほとんどのデータベースはテーブルのデータをメモリ上にキャッシュする仕組みを持っており、負荷をかけていくとキャッシュにデータが溜まっていきだんだん速くなります。rampupを設定することで最初の遅い状態での測定値を除外します。いまどきのPCサーバで100WHの場合、300〜900秒あたりで設定します。
  • measure:測定時間を秒単位で指定します。データベースは内部でいろいろな処理をしているので測定中速くなったり遅くなったりしますが、だいたい7,200秒(2時間)あれば安定した値がとれます。面倒な場合は600秒(10分)程度にすることもあります。
$ ./tpcc_start vostro tpcc tpcc XXXX 1 4 60 180
***************************************
*** ###easy### TPC-C Load Generator ***
***************************************
<Parameters>
     [server]: vostro
     [DBname]: tpcc
       [user]: tpcc
       [pass]: XXXX
  [warehouse]: 1
 [connection]: 4
     [rampup]: 60 (sec.)
    [measure]: 180 (sec.)

RAMP-UP TIME.(60 sec.)

MEASURING START.

  10, 61(0):0.8, 58(0):0.4, 5(0):1.0, 6(0):1.2, 5(0):3.8
  20, 66(0):0.8, 70(0):0.4, 7(0):1.0, 7(0):1.2, 7(0):2.0
(略)

測定中は、10秒おきにその10秒間のスループットが出力されます。一番左がnew orderトランザクションスループット、括弧内はTPC-Cの仕様に定められているレスポンスタイムを満たせなかった回数、コロンの後は90%タイルのレスポンスタイムです。
測定終了後、各トランザクションのレスポンスタイムや、トランザクションの比率がTPC-Cの仕様を満たしていたかどうかなどが出力されます。

<RT Histogram>

1.New-Order

0.20,    536
0.40,    484
0.60,    166
0.80,    113
1.00,     66
(略)
<Constraint Check> (all must be [OK])
 [transaction percentage]
        Payment: 43.54% (>=43.0%) [OK]
   Order-Status: 4.33% (>= 4.0%) [OK]
       Delivery: 4.33% (>= 4.0%) [OK]
    Stock-Level: 4.37% (>= 4.0%) [OK]
(略)
<TpmC>
                 371.000 TpmC

最後に出力されるのが1分間あたりのnew orderトランザクション数で、これがいわゆるTPC-Cのスコアになります。繰り返しますが公式スコアではありませんので注意してください。
これまでの経験では、クアッドコアのCPUを使い切って10,000tx/mを少し超えるかどうか、一方I/Oバウンドの設定にした場合は、RAID 1+0で物理ディスクを10本束ねたストレージを用いて1,500tx/mいくかどうかといったところです。公式サイトのランキングには400万tx/mとか600万tx/mといった桁外れのスコアが並んでいますが、私たちのような一般のエンジニアにはまず縁のない構成ですので参考にしない方が良いと思われます(^^;
この手の並列性の高い更新ワークロードに対しては現在のMySQLInnoDBは正直課題だらけで、Perconaの人がこのツールを利用していろいろと模索している様子が伺えます(例1例2例3例4)。ちなみに上の結果はinnodb_buffer_pool_sizeをわざと32MBにしたもので、これを512MBにすると…

<TpmC>
                 3243.333 TpmC

パフォーマンスチューニングは本当に難しいです。tpcc-mysqlはパフォーマンスチューニングの教材としても結構面白いと思います。個人的には、I/Oバウンドの設定にした上で最近流行の高性能SSDを使うとどうなるか、という点に興味があります。