0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring MVC+Kotlin Coroutinesで JDBC vs R2DBC を負荷検証してみた

Posted at

なぜ JDBC と R2DBC を比べるのか

どちらもDBアクセスのための手段ですが、性質は大きく異なります。

  • JDBC: 古くからある標準APIで、SQL実行中はスレッドがブロックされます
  • R2DBC: 非ブロッキングI/Oを前提に設計された新しい仕様で、I/O待ちの間にスレッドを他の処理に回せます。そのため高負荷環境ではスループット向上が期待できます

R2DBCは一般的にSpring WebFluxと組み合わせて利用されることが多いですが、Spring MVC+Kotlin Coroutinesでも違いが出るのか気になり、勉強がてら試してみました。

結論

今回の検証から言えることは、

  • Spring MVCのような同期モデルでは、R2DBCのメリットはほとんどなく、積極的に選ぶ必要はない
  • ただし、一部の条件のP95レイテンシではR2DBCが有利になることもある

検証環境

  • MacBook Pro (Apple M3, メモリ16GB)
  • Spring Boot 3.5(Kotlin Coroutines 有効)
  • PostgreSQL 16(Docker上で稼働)

プロジェクト構成

検証用に作成したプロジェクトは以下のような構成です。
シンプルな三層構造です、OrderController/orders エンドポイントを公開し、OrderService を経由してOrderRepositoryに処理を委譲します。

  • OrderController...APIエンドポイント
  • OrderService...リポジトリを束ねる
  • OrderRepository...リポジトリのインターフェース
    • JdbcOrderRepository...JDBCを利用した実装(Dispatchers.IO利用)
    • R2dbcOrderRepository...R2DBCを利用した実装

application.ymlのプロファイルで JdbcOrderRepository / R2dbcOrderRepository を切り替えて動作を確認できるようにしています。
2つのリポジトリが実行するクエリは同じで以下のような条件で検索してページングを行います。

SELECT id, customer_id, created_at, total_amount, note
FROM orders
WHERE customer_id = :customerId
  AND created_at BETWEEN :from AND :to
ORDER BY created_at DESC
LIMIT :size OFFSET :offset;

ソースコード全体は GitHub に公開していますので、詳細はこちらを参照してください。

検証シナリオ設計

  1. ordersテーブルに 20万行相当のデータを投入
  2. 負荷テストツールのk6を利用し、指定した数の仮想ユーザーが60 秒間/ordersへリクエストを投げ続ける
  3. JDBC / R2DBC それぞれで2を行い、スループットを計測する
  4. 並行ユーザー数を変えて2~3を繰り返す

TomcatのmaxThreadsを200に設定していたため、その前後の負荷領域を観測できるよう10 → 300の範囲で検証を行いました。
コネクションプールはJDBC / R2DBCともに最大20。

結果

クエリが早すぎると非同期化メリットがなくなると思いインデックスを外した検証も行いました。

インデックスありの場合

JDBC R2DBC
並行ユーザー数 1秒あたりの処理件数(req/s) 平均応答時間(ms) P95応答時間(ms) 1秒あたりの処理件数(req/s) 平均応答時間(ms) P95応答時間(ms)
10 570.8 6.56 11.09 506.8 9.02 12.82
50 3723.5 3.10 7.40 3487.4 4.05 9.83
100 5903.4 5.86 15.26 4320.9 12.76 35.76
150 5924.8 13.09 31.35 4254.4 24.71 47.65
200 6041.2 19.54 43.06 3782.1 42.22 67.26
250 6076.1 26.21 55.96 4197.9 48.90 73.12
300 6149.8 32.88 65.23 4360.7 58.08 84.55

インデックスなしの場合

JDBC R2DBC
並行ユーザー数 1秒あたりの処理件数(req/s) 平均応答時間(ms) P95応答時間(ms) 1秒あたりの処理件数(req/s) 平均応答時間(ms) P95応答時間(ms)
10 451.9 11.75 16.96 354.3 17.84 20.81
50 589.8 73.92 119.31 502.1 88.14 139.50
100 607.6 153.92 386.18 508.6 185.41 286.74
150 521.8 276.97 717.36 505.6 285.58 736.69
200 526.8 369.33 859.68 489.9 397.73 863.02
250 509.4 480.93 1050 557.9 437.77 495.45
300 551.7 534.07 1050 504.6 584.66 1050

まとめ

今回の条件では平均やスループットでR2DBCの明確な優位は見られませんでした。一方で高並行時にはP95でR2DBCが良い場面も観測されたため、長尾(P99)抑制の可能性はありそうです。P99は未計測なので、今後あらためて検証してみます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?