メインコンテンツへスキップ
見出し画像

【小売業必見!実用例】バッチとオンラインの並列実行〜TsurugiとPostgreSQLでの比較〜【次世代高速RDB 劔"Tsurugi"】

    1. はじめに

    劔本(*)の帯に「バッチとオンラインを同時に高速処理」「メニーコア環境の性能をフルに引き出せる」とあります。
    実際どうなのでしょうか。

    ここでは、トランザクションの分離レベルをSERIALIZABLE(ダーティーリードなどの問題が起きない、最も厳密なレベル)で比較しています。

    結果として、PostgreSQLではバッチ処理が全く成功しませんでしたが、Tsurugiはすべて成功しました。
    また、論理96コア環境でオンライン処理を1並列から192並列まで増やして実行したところ、1秒あたりの処理数がPostgreSQLでは約32倍までしか伸びませんでしたが、Tsurugiは約150倍まで伸びました。

    (*)劔本:『次世代高速オープンソースRDB Tsurugi』、日経BP

    2. シミュレーションの概要

    バッチとオンラインを並行実行するサンプルとして、買い物に使われるポイントカードの仕組みのシミュレーションアプリを作りました。

    画像

    2.1. ポイント利用

    顧客はカードを持っており、そこに現金をチャージし、買い物での代金支払いに利用します。
    いつ、どの店で、どのカードを利用したかは、ポイント利用履歴として記録します。
    この処理は、比較的短時間で終わる処理であり、多くの顧客が並列に各自のカードを利用します。

    これをオンライン処理とします。

    2.2. ポイント付与

    企業側は、全顧客に対して、顧客ごとのチャージ金額の合計に比例して、時々、ポイントをプレゼントします(年末ボーナスポイントプレゼントキャンペーンのようなものをイメージしてください)。
    企業がプレゼントしたポイントは、ポイント利用での買い物に使えます。
    この処理は、カード数に比例して処理時間がかかります。

    これをバッチ処理とします。

    2.3. シミュレーションの設定

    2.3.1. 全体

    今回は、カード数を100万枚、店舗数1万としました。
    また、ポイント利用とポイント付与を並列に走らせる前に、初期データとして、100万件のポイント利用履歴を乱数で作成しました。

    シミュレーションは、3分間実行しました。

    システム全体の構成は、以下の図のようにしました。

    画像

    2.3.2. ポイント利用

    ポイント利用については、あるカードを乱数で選び、適当な額のチャージまたは支払いを行います。
    これを100ミリ秒の待ちをはさみながら、指定された並列数で繰り返します。

    1回のポイント利用の処理に100ミリ秒かかるとすると(実際、数十ミリ秒から数百ミリ秒程度でした)、1秒に5回、1分間に300回、12時間で21.6万回のポイント利用が起こります。
    これは、12時間で5枚弱(=100万÷21.6万)に1枚が一度利用された状況に相当します。

    ポイント利用が100並列で理想的に実行できたとすれば、ポイント利用の実行数も100倍となり、1分間に3万回、12時間で2千万回程度に相当します。
    これは、カード数を100万枚としましたので、すべてのカードが12時間で20回程度使われた状況に相当します。

    あるカードの使用頻度が週1回程度から数十分に1回程度までシミュレーションできるよう、ポイント利用の並列実行数は1, 32, 64, 128, 192としました。

    2.3.3. ポイント付与

    ポイント付与については、ポイント利用履歴を参照して、カードごとにポイントのプレゼントを行います。
    シミュレーション開始後10秒待ってから最初のポイント付与を行い、以後、10秒の待ちをはさみながら、シミュレーション終了まで繰り返します。

    現実にこれほどの頻度でポイント付与が行われることはないでしょうが、あえて競合を多く起こすようにしました。

    なお、ポイント付与中にシミュレーション終了となったら、トランザクションをabortし、ノーカウント(ポイント付与の成功・失敗に含めない)としました。

    3. テーブル設計

    3.1. アカウント

    create table if not exists S_ACCOUNT (
        CARD_ID bigint primary key,
        BALANCE bigint,
        REGION_ID int,
        LAST_UPDATE timestamp);
    • CARD_ID:カード番号

    • BALANCE:カードの残高

    • REGION_ID:利用者の居住都道府県番号

    • LAST_UPDATE:最終利用時刻

    3.2. 利用履歴

    create table if not exists H_TRANSACTION (
        PAYMENT_TIME timestamp,
        CARD_ID bigint,
        STORE_ID int,
        AMOUNT bigint,
        BALANCE bigInt,
        primary key (PAYMENT_TIME, CARD_ID, STORE_ID));
    • PAYMENT_TIME:利用時刻(ポイント利用、または、ポイント付与を実行した時刻)

    • CARD_ID:カード番号

    • STORE_ID:ポイント利用した店舗番号(正の値)、ポイント付与ならば0

    • AMOUNT:残高の増減値(チャージなら正、代金支払いなら負)

    • BALANCE:変更後のカードの残高

    3.3. 店舗

    create table if not exists M_STORE (
        STORE_ID int primary key,
        STORE_ZIPCODE varchar(7),
        STORE_ADDRESS varchar(256),
        STORE_NAME varchar(256),
        REGION_ID int,
        S_LATITUDE double precision,
        S_LONGITUDE double precision);
    • STORE_ID:店舗番号

    • STORE_ZIPCODE:店舗の郵便番号

    • STORE_ADDRESS:店舗の住所

    • STORE_NAME:店舗の名前

    • REGION_ID:都道府県番号

    • S_LATITUDE:店舗の緯度

    • S_LONGITUDE:店舗の経度

    3.4. 都道府県

    create table if not exists M_REGION (
        REGION_ID int primary key,
        REGION_NAME varchar(256));
    • REGION_ID:都道府県番号

    • REGION_NAME:都道府県名

    4. ポイント利用

    4.1. 処理の流れ

    1. あるカード番号を乱数で選ぶ。該当するレコードをS_ACCOUNTから取得する

    2. 乱数で選んだある都道府県(REGION_ID)の店舗情報一覧をM_STOREから取得し、その中から乱数で一店舗を選択する

    3. 乱数で残高の増減額(正・負)を決める(=X)

    4. S_ACCOUNTレコードのBALANCEを取得する(=Y)

    5. S_ACCOUNTレコードのBALANCE(=X+Y)とLAST_UPDATEを更新する

    6. H_TRANSACTIONに利用履歴を1レコード追加する

    7. シミュレーション停止まで、100ミリ秒待って、先頭から繰り返す

    4.2. トランザクションの構成

    以下の3つのトランザクションとしました。

    • カード番号指定でカード情報の取得

    • 都道府県指定で店舗一覧取得

    • ポイント利用本体(下記の3つの処理を一つのトランザクションとして実行)

      • S_ACCOUNTの残高取得、S_ACCOUNTの残高など更新、H_TRANSACTIONへの利用履歴追加

    4.3. Tsurugiでの実施設定

    4.3.1. トランザクションの設定

    • カード番号指定でカード情報の取得

      • RTXで実行し、失敗したら最大3回まで試行し、それでもだめなら失敗とする

        • TgTmSetting.ofAlways(TgTxOption.ofRTX(), 3)

    • 都道府県指定で店舗一覧取得

      • 同上

    • ポイント利用本体

      • OCCで最大3回試し、それでもだめならLTXで1回試す。それでもだめならポイント利用失敗とする。

      • LTXでは更新対象テーブルとしてS_ACCOUNTとH_TRANSACTIONを指定する

        • OCC: TgTmSetting.ofAlways(TgTxOption.ofOCC(), 3).commitType(TgCommitType.DEFAULT)

        • LTX: TgTmSetting.ofAlways(TgTxOption.ofLTX("S_ACCOUNT", "H_TRANSACTION"), 1).commitType(TgCommitType.DEFAULT)

    4.3.2. SQL

    • カード番号指定でカード情報の取得

    select CARD_ID, BALANCE, REGION_ID, LAST_UPDATE from S_ACCOUNT where CARD_ID=:CARD_ID
    • 都道府県指定で店舗一覧取得

    select STORE_ID, STORE_ZIPCODE, STORE_ADDRESS, STORE_NAME, REGION_ID, S_LATITUDE, S_LONGITUDE
    	from M_STORE where REGION_ID=:REGION_ID
    • S_ACCOUNTの残高取得

    select CARD_ID, BALANCE, REGION_ID, LAST_UPDATE from S_ACCOUNT where CARD_ID=:CARD_ID
    • S_ACCOUNTの残高更新

    update S_ACCOUNT set BALANCE=:BALANCE, LAST_UPDATE=:LAST_UPDATE where CARD_ID=:CARD_ID
    • H_TRANSACTIONへの利用履歴追加

    insert or replace into H_TRANSACTION (PAYMENT_TIME, CARD_ID, STORE_ID, AMOUNT, BALANCE)
    	values (:PAYMENT_TIME, :CARD_ID, :STORE_ID, :AMOUNT, :BALANCE)

    4.4. PostgreSQLでの実施設定

    4.4.1. トランザクションの設定

    • カード番号指定でカード情報の取得

      • 最大3回試行する

    • 都道府県指定で店舗一覧取得

      • 最大3回試行する

    • ポイント利用本体

      • Connection.setIsolationLevel(SERIALIZABLE)する(参照系ではこの設定をしない)

      • 最大3回試行する。3回とも失敗したらポイント利用失敗とする。

    4.4.2. SQL

    • カード番号指定でカード情報の取得

    select CARD_ID, BALANCE, REGION_ID, LAST_UPDATE from S_ACCOUNT where CARD_ID=?
    • 都道府県指定で店舗一覧取得

    select STORE_ID, STORE_ZIPCODE, STORE_ADDRESS, STORE_NAME, REGION_ID, S_LATITUDE, S_LONGITUDE 
    	from M_STORE where REGION_ID=?
    • S_ACCOUNTの残高取得

    select BALANCE from S_ACCOUNT where CARD_ID=?
    • S_ACCOUNTの残高更新

    update S_ACCOUNT set BALANCE=?, LAST_UPDATE=? where CARD_ID=?
    • H_TRANSACTIONへの利用履歴追加

    replace into H_TRANSACTION (PAYMENT_TIME, CARD_ID, STORE_ID, AMOUNT, BALANCE) values (?, ?, ?, ?, ?)
    	on conflict on constraint H_TRANSACTION_PKEY do update set AMOUNT=?, BALANCE=?

    5. ポイント付与

    5.1. 処理の流れ

    1. 10秒待つ

    2. 指定期間のカード別チャージ金額合計を検索する。検索結果に従い、カードごとに以下を実行する

      1. S_ACCOUNTテーブルのBALANCEとLAST_UPDATEを更新する

      2. H_TRANSACTIONにカードの利用履歴を1レコード追加する

    3. シミュレーション停止まで、先頭から繰り返す

    クエリーでは以下の条件を指定しました。

    • H_TRANSACTIONのPAYMENT_TIMEが指定期間内であること

    • H_TRANSACTIONのAMOUNTが正(チャージに相当)であること

    • H_TRANSACTIONのSTORE_IDがゼロより大きいこと(STORE_ID=0は、ポイント付与による処理の記録であるため、それを除外しました)

    5.2. トランザクションの構成

    • クエリーと更新全体を一つのトランザクションとして実行します

    • 失敗してもリトライしません

    5.3. Tsurugiでの実施設定

    5.3.1. トランザクションの設定

    • ポイント付与トランザクション

      • LTXで1回だけ行う。失敗してもリトライしない。

        • TgTmSetting.ofAlways(TgTxOption.ofLTX("S_ACCOUNT", "H_TRANSACTION"), 1).commitType(TgCommitType.DEFAULT)

    5.3.2. SQL

    • 処理対象のクエリー

    select T.CARD_ID, A.BALANCE, A.REGION_ID, sum(T.AMOUNT) from H_TRANSACTION T
    	join S_ACCOUNT A on T.CARD_ID = A.CARD_ID where
    	T.PAYMENT_TIME >= :FROM and T.PAYMENT_TIME < :TO
    	and T.AMOUNT>0 and T.STORE_ID>0 group by T.CARD_ID, A.BALANCE, A.REGION_ID
    • S_ACCOUNTの残高更新

    最大千レコードまでつなげた長いSQL文を作成して実行しました。
    これは、JDBCのaddBatch(),executeBatch()相当を実現するためです。

    insert or replace into S_ACCOUNT(CARD_ID, BALANCE, REGION_ID, LAST_UPDATE) values
    	(レコード1),(レコード2),...
    • H_TRANSACTIONへの利用履歴追加

    最大千レコードまでつなげた長いSQL文を作成して実行しました。

    insert or replace into H_TRANSACTION (PAYMENT_TIME, CARD_ID, STORE_ID, AMOUNT, BALANCE) values
    	(レコード1),(レコード2),...

    5.4. PostgreSQLでの実施設定

    5.4.1. トランザクションの設定

    • ポイント付与トランザクション

      • 失敗してもリトライしない

      • Connection.setIsolationLevel(SERIALIZABLE)する

    5.4.2. SQL

    • 処理対象のクエリー

    select T.CARD_ID, A.BALANCE, sum(T.AMOUNT) from H_TRANSACTION T
    	join S_ACCOUNT A on T.CARD_ID = A.CARD_ID where
    	T.PAYMENT_TIME >= ? and T.PAYMENT_TIME < ?
    	and T.AMOUNT>0 and T.STORE_ID>0 group by T.CARD_ID, A.BALANCE
    • S_ACCOUNTの残高更新

    ポイント利用とSQL文は同じです。
    ただし、JDBCのaddBatch()で更新要求を溜め込み、千件ごと(と最後に残りがあれば)にexecuteBatch()しました。

    update S_ACCOUNT set BALANCE=?, LAST_UPDATE=? where CARD_ID=?
    • H_TRANSACTIONへの利用履歴追加

    ポイント利用とSQL文は同じです。
    ただし、前項同様、千件ごとまとめて処理しました。

    replace into H_TRANSACTION (PAYMENT_TIME, CARD_ID, STORE_ID, AMOUNT, BALANCE) values (?, ?, ?, ?, ?)
    	on conflict on constraint H_TRANSACTION_PKEY do update set AMOUNT=?, BALANCE=?

    6. データベースサーバーの動作設定

    6.1. Tsurugi

    • flush/sync設定

      • デフォルトのまま=ストレージ書き込み完了を待つ

        • クライアント側:TgCommitType.DEFAULTを指定

        • サーバー設定ファイル:デフォルトのまま(コメントで#commit_response=STORED)

    • タイムアウト設定

      • サーバー接続のみ10秒に設定、それ以外はデフォルト(無限待ち)

        • sessionOption.setTimeout(TgTimeoutKey.SESSION_CONNECT, 10, TimeUnit.SECONDS)

    • 最大接続数

      • TCPの最大接続数を300に増やしました

    • 分離レベル指定

      • Tsurugiは常にSERIALIZABLEなので、明示的な指定はありません

    6.2. PostgreSQL

    • flush/sync設定

      • デフォルトのまま=ストレージ書き込み完了を待つ

        • 参考:https://www.postgresql.jp/document/15/html/runtime-config-wal.html

        • fsync=on

          • 「PostgreSQLサーバはfsync()システムコールを発行する・・・更新が物理的にディスクに確実に書き込まれるように試みます。」

        • synchronous_commit=on

          • 「offモード以外のローカルの振る舞いは、WALがディスクにローカルにフラッシュされるのを待ちます。 」

        • wal_sync_method=fsync

          • 「fsync(コミット毎にfsync()を呼び出す) 」

    • タイムアウト設定

      • デフォルト設定のまま

        • 参考:https://www.postgresql.jp/document/15/html/runtime-config-client.html

        • lock_timeout=0

          • ロック取得待ちは無限待ち

            • 「テーブル、インデックス、行、またはその他のデータベースオブジェクトに対してロック獲得を試みている最中、指定された時間を超えて待機するいかなる命令も停止されます。」

            • 「 値ゼロ(デフォルト)はこの機能を無効にします。 」

        • statement_timeout=0

          • 処理完了待ちは無限待ち

            • 「コマンドがクライアントからサーバに届いた時から数えて、実行時間が指定された時間を越えた文を停止します。」

            • 「値がゼロ(デフォルト)の場合、これを無効にします。 」

    • 最大接続数

      • デフォルトの100から300に増やしました

    • 分離レベル指定

      • サーバーの設定ファイルでdefault_transaction_isolation = 'serializable'としました

      • さらにJDBCのConnection.setIsolationLevel(SERIALIZABLE)を指定しました

    • サーバー設定ファイルのshared_buffers

      • デフォルトの128MBから64GB(実メモリ182GBの35%)に増やしました

      • 参考:https://postgresqlco.nf/doc/ja/param/shared_buffers/

        • 「妥当な初期値はシステムメモリの25%です」

        • 「RAMの40%以上を割り当てても、それより小さい値の時より動作が良くなる見込みはありません」

    7. 動作環境

    7.1. アプリサーバー

    Javaで作成したシミュレーションアプリを実行し、DBサーバーに対して並列に要求を発行しました。

    • AWSのc5.2xlargeインスタンス

      • OS:Linux 6.8.0-1029-aws, Ubuntu 24.04.2 LTS

      • メモリ:約15GB

      • CPU:Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz(物理4コア、論理8コア、1ソケット)

      • ストレージ:Amazon Elastic Block Store(NVMe、約54GB)

    • Java:openjdk version "21.0.7"

      • Tsurugiとの接続はiceaxe、PostgreSQLとの接続はJDBCを利用しました

    • アプリサーバーとDBサーバー間のpingは、平均0.205ミリ秒でした

    7.2. DBサーバー

    TsurugiとPostgreSQLを稼働させました。

    なお、どちらかのデータベースを利用してシミュレーションしているときは、何らかのバックグランド処理が計測に影響しないように、使っていないデータベースは停止しました。

    • AWSのc5.24xlargeインスタンス

      • OS:Linux 6.8.0-1029-aws, Ubuntu 24.04.2 LTS

      • メモリ:約184GB

      • CPU:Intel(R) Xeon(R) Platinum 8275CL CPU @ 3.00GHz(物理24コア、論理48コア)×2ソケット=論理96コア

      • ストレージ:Amazon Elastic Block Store(NVMe、約54GB)

    • Tsurugi 1.4.0

    • PostgreSQL 15.13 (Ubuntu 15.13-1.pgdg24.04+1)

    8. 実験結果

    8.1. ポイント利用の実行速度

    シミュレーション期間中のポイント利用の実行回数や1秒あたりの実行回数などを示します。

    • カード数:ポイントシミュレーションにあるカード数

    • 並列数:ポイント利用の並列実行数

    • OK:ポイント利用の成功回数

    • NG:ポイント利用の失敗回数

    • NG%:ポイント利用の失敗率(%)

    • OK+NG:ポイント利用の試行回数(成功回数+失敗回数)

    • OK+NG/s:ポイント利用の試行速度(回/秒)=試行回数÷シミュレーション実行時間

    • OK/s:ポイント利用の成功回数÷シミュレーション実行時間

    • x:並列数=1のときと比較して、OK/sが何倍になったか

    • ratio:xが並列数の何%に当たるか(100%ならば理想的なスケール)

    画像
    <表8.1 ポイント利用の実行速度>

    この表の「並列数」と「OK/s」をグラフにすると、次のようになります。

    画像

    8.2. ポイント利用の実行時間

    8.2.1. すべての成功の実行時間

    ポイント利用1回あたりの処理時間について回数や平均時間等を示します。

    なお、数十ミリ秒から数秒・数十秒までの実行時間が混在していたため、平均値の比較は無意味です。
    参考のために最頻値や中央値も計測しました。

    1秒未満と1秒以上に分けた集計結果は、この後示します。

    • カード数:ポイントシミュレーションにあるカード数

    • 並列数:ポイント利用の並列実行数

    • 区分:all=すべての成功(実行時間での区分けなし)、<1s=1秒未満の成功、>=1s=1秒以上の成功、NG=失敗

    • 回数:ポイント利用に成功した回数

    • 最小:ポイント利用の実行時間の最小値[msec]

    • 最頻:ポイント利用の実行時間の最頻度値(同一回数が複数ある場合は最初の一つのみ)[msec]

    • 最頻回数:ポイント利用の実行時間の最頻値の発生回数

    • 中央:ポイント利用の実行時間の中央値[msec]

    • 平均:ポイント利用の実行時間の平均値[msec]

    • 分散:ポイント利用の実行時間の分散[msec]

    • 最大:ポイント利用の実行時間の最大値[msec]

    画像
    <表8.2 ポイント利用成功の実行時間>

    8.2.2. 1秒未満の成功の実行時間

    画像
    <表8.3 ポイント利用成功の1秒未満の実行時間>

    この表の「並列数」と「中央」(=1秒未満の成功の実行時間の中央値)をグラフにすると、次のようになります。

    画像

    8.2.3. 1秒以上かかった成功の実行時間


    画像
    <表8.4 ポイント利用成功の1秒以上の実行時間>

    8.2.4. 失敗の実行時間

    PostgreSQLはポイント利用の失敗はなかったので、全てゼロ件となっています。

    画像
    <表8.5 ポイント利用失敗の実行時間>

    8.3. ポイント付与の実行結果

    8.3.1. Tsurugiの実行結果

    ポイント付与の実行時間を、全体と、処理ごとについて示します。

    Tsurugiはすべてのポイント付与に成功しました。

    • カード数:ポイントシミュレーションにあるカード数

    • 並列数:ポイント利用の並列実行数

    • 付与時間:ポイント付与全体の実行時間の平均[msec]

    • query:ポイント付与のクエリーにかかった実行時間の平均[msec]

    • 更新:ポイント付与の更新にかかった実行時間の平均[msec]

      • 「更新=付与時間−query−commit」としているので、更新以外の若干の処理時間(ログ出力など)を含む

    • 更新件数:ポイント付与対象となり、実際に残高更新となったカードの数

      • S_ACCOUNTテーブルの残高更新と、H_TRANSACTIONテーブルへの利用履歴追加が、それぞれこの回数行われた

    • commit:ポイント付与のcommitにかかった実行時間の平均[msec]

    画像
    <表8.6 ポイント付与の平均実行時間(SERIALIZABLE)>

    8.3.2. PostgreSQLの実行結果

    PostgreSQLは、ポイント付与に一度も成功しなかったので、実行時間のデータはありません。

    失敗したときのエラーは、常に以下のようなものでした。

    Batch entry 0 insert into H_TRANSACTION (PAYMENT_TIME, CARD_ID, STORE_ID, AMOUNT, BALANCE)
      values (('2025-05-22 16:55:00.594818+09'::timestamp), ('10009933'::int8), ('0'::int4), ('19'::int8), ('13597'::int8))
      on conflict
      on constraint H_TRANSACTION_PKEY
      do update set AMOUNT=('19'::int8), BALANCE=('13597'::int8)
     was aborted: ERROR: could not serialize access due to read/write dependencies among transactions
      Detail: Reason code: Canceled on identification as a pivot, during write.
      Hint: The transaction might succeed if retried.  Call getNextException to see other errors in the batch.

    クエリー結果をもとに、H_TRANSACTIONテーブルにレコード挿入しようとして、entry 0(千件まとめてexecuteBatch()するバッチの先頭のようです)で失敗したようです。

    上記のHintに従い、ポイント付与全体を最大3回リトライするようにしてみましたが、全く同じエラーが出るだけでした。

    8.3.3. 参考:PostgreSQLのロック最大数を増やしたときの結果

    PostgreSQLのヘルプに、ロック取得最大数を増やすと直列化失敗を回避できるとの記述がありました。

    そこで、max_locks_per_transaction、max_pred_locks_per_transaction、max_pred_locks_per_pageをデフォルト値(順に64, 64, 2)の2倍(128,128,4)や4倍(256, 256, 8)に変えて試してみました。

    しかし、上記と同様のエラーが起きました。

    8.3.4. 参考:PostgreSQLで分離レベルをデフォルトのREAD_COMMITTEDとしたときの結果

    参考までに、分離レベルをデフォルトのREAD_COMMITTEDとしたら、PostgreSQLでもポイント付与がすべて成功しました。

    ただし、READ_COMMITTEDとすると、反復不能読み取り・ファントムリード・直列化異常といった現象が発生する可能性があります(⇒参考1)。
    発生した場合、カードの残高が正しくなくなる可能性があります。

    なお、TsurugiはSERIALIZABLEでの実行しかできないので、Tsurugiの実行データはありません。

    画像
    <表8.7 ポイント付与の平均実行時間(READ_COMMITTED)>

    クエリーにかかる時間が、Tsurugiの約17秒(表8.6)に比べて、2秒程度と、かなり短いものでした。
    コミットの時間もとても短いです。

    explain analyzeで実際のパラメータを指定してクエリー動作を確認したところ、パラレルクエリーとなっていました。
    これによりクエリーの処理時間が大幅に短縮されたと思われます(⇒参考2)。

    Tsurugi 1.4.0もパラレルクエリー(並列スキャン)を正式サポートしていますが、RTX(参照のみ)限定であるため、LTX(参照・更新あり)でのポイント付与では実行時間の短縮は起こらなかったと思われます(⇒参考3)。

    9. 実行結果からわかること

    9.1. 成功・失敗の有無

    • ポイント利用(オンライン処理)について、表8.1から

      • Tsurugiはポイント利用に失敗することがあり、失敗率は1%未満であった

      • PostgreSQLは失敗しなかった

    • ポイント付与(バッチ処理)について、8.3節から

      • 分離レベルがSERIALIZABLEの場合、Tsurugiは失敗しなかったが、PostgreSQLはすべて失敗した

      • ただし、分離レベルがREAD COMMITTEDの場合、PostgreSQLも失敗しなかった

    9.2. 主な性能評価

    • ポイント利用の1秒あたりの成功数について、表8.1のOK/sの欄から

      • 並列数=1のとき、Tsurugiは1.7回/秒、PostgreSQLは7.0回/秒で、PostgreSQLのほうが4.12倍多い

      • 並列数=192のとき、Tsurugiは257.6回/秒、PostgreSQLは224.0回/秒で、Tsurugiのほうが1.15倍多い

      • 並列数が少ない場合はPostgreSQLが有利だが、多い場合はTsurugiが有利になった

    • ポイント利用のスケーラビリティについて、表8.1のratioの欄から

      • Tsurugiは192並列で150倍にまで伸びた

      • PostgreSQLは64並列で32倍弱で、並列数を増やしてもそれ以上伸びなかった

      • Tsurugiのほうがスケーラビリティに優れていた

    • ポイント付与について、表8.6から

      • Tsurugiは、ポイント利用の並列実行数が増えても(=高負荷になっても)、ポイント付与の実行時間はほぼ同一(約40秒)だった

      • (更新処理を含むトランザクションでも)パラレルクエリーが使えるようになれば、さらなる性能改善が期待できる

    9.3. その他

    • ポイント利用成功時間の並列数との関係について、表8.2, 8.3の最頻・中央の欄から

      • Tsurugiは並列数が増えると、ポイント利用の実行時間がおよそ30〜60ミリ秒に増えた

        • Tsurugiはポイント利用の並列数が論理コア数の2倍になっても実行時間はおよそ2倍にしかならなかった

      • 同じく、PostgreSQLはおよそ40〜600ミリ秒にまで増えた

        • PostgreSQLは10倍以上に増えた。スケーラビリティが悪いのはこのためと思われる。

    • 1秒以上かかったポイント利用について、表8.4から

      • Tsurugiは20〜40秒待ってから成功した

        • 成功する場合の実行時間の中央値は数十ミリ秒であるため、その千倍に相当する数十秒の待ちが入ると全体のスループットは大きく下がる。こういったことを加味してトランザクションの実行方法を検討する必要がある。

      • PostgreSQLは最大でも6秒程度の待ちで成功した(ただし、そもそもポイント付与が一度も成功していない)

    • ポイント利用に失敗したときの実行時間について、表8.5から

      • Tsurugiは40秒ほど待ってから成功した。この時間は、表8.6のポイント付与の平均実行時間にほぼ等しい。

    • Tsurugiで秒単位の時間がかかるポイント利用について

      • 表8.4の成功回数と表8.5の失敗回数を見ると、ほぼ同数であった

      • ポイント付与の完了を待ってからポイント利用が成功するか失敗するかは半々だったといえる

    10. おわりに

    多数のオンライン処理とバッチ処理を並列実行したとき、並列数が少ない場合はPostgreSQLのほうが有利なこともありましたが、並列数が増えてくるにつれ、スケーラビリティに優れたTsurugiの優位性が現れることがありました。

    また、分離レベルがSERIALIZABLEの場合、そもそもPostgreSQLはバッチ処理が一度も成功しませんでしたが、Tsurugiはすべて成功しました。
    しかも、競合するオンライン処理の並列実行数が増えても、バッチ処理の処理時間は一定でした。

    こういったところがTsurugiの強みといえるでしょう。

    なお、諸般の事情から、この計測は今年5月頃のデータとなっています。
    その後、Tsurugiはバージョンアップを重ね、機能追加や性能改善も行われています。
    PostgreSQLも、9月にメジャーバージョンアップがあり、非同期I/O対応による性能改善がなされたようです。
    機会があれば、最新のTsurugiとPostgreSQLで再計測してみたいところです。

    あなたへのおすすめ