🏀

DBのパフォーマンスを最大限に出すシンプルな方法(コネクション数とレイテンシー)

2024/12/03に公開

この記事は個人の検証ベースとなりますので参考程度にしていただければと思います。

環境: 16コア、Go 1.23, PostgreSQL 16

きっかけは以下のスライドです。面白いのでぜひ読んでみてください。

都市伝説バスターズ「WebアプリのボトルネックはDBだから言語の性能は関係ない」 - Kaigi on Rails 2024

その記事の性能測定の例

まずは自分が利用する環境のデータベースのレーテンシーを調べます。

例えば以下のクエリです。元のスライドのページのスクショ

curl とかで叩いて何回かログに出して見ます。Go では time.Since などで測定可能です。

{"queryTime":"126.421µs","queryResultTime":"137.252µs","":"User1"}
{"queryTime":"140.378µs","queryResultTime":"145.919µs","":"User1"}
{"queryTime":"127.253µs","queryResultTime":"131.421µs","":"User1"}
{"queryTime":"150.497µs","queryResultTime":"158.522µs","":"User1"}
{"queryTime":"172.66µs","queryResultTime":"181.667µs","":"User1"}
{"queryTime":"100.281µs","queryResultTime":"103.798µs","":"User1"}
{"queryTime":"129.316µs","queryResultTime":"133.013µs","":"User1"}
{"queryTime":"104.98µs","queryResultTime":"108.267µs","":"User1"}
{"queryTime":"130.429µs","queryResultTime":"133.886µs","":"User1"}
{"queryTime":"129.407µs","queryResultTime":"133.495µs","":"User1"}
{"queryTime":"125.439µs","queryResultTime":"129.357µs","":"User1"}

一番早いクエリは約 100µs ですね。100µs は 0.1 ms ですので RPS でいうと1 つのコネクションで秒間約1万(10000 RPS) になります。 コネクション 10 で 10 万, 20 で 20万 RPS ということとなります。実際どこまで出るかは CPU 次第です。自分の環境ではコネクション20で最大 260K RPS でした。

  8 threads and 30000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.15ms  837.24us  51.16ms   80.01%
    Req/Sec    88.26k    10.65k  111.54k    75.84%
  Latency Distribution
     50%  819.00us
     75%    1.31ms
     90%    2.38ms
     99%    2.77ms
  3926018 requests in 15.09s, 198.44MB read
Requests/sec: 260096.55
Transfer/sec:     13.15MB

今度は 50ms かかるクエリはどうでしょう?

こちらも元の記事の検証コードを参考にします。0.05s ⇒ 50ms

PostgreSQL では sleep ではなく、pg_sleep になるのでそちらを使います。

50ms だと1コネクションで秒間 20 RPS しかでませんね。

>>> 1000 / 50
20.0

コネクション 20 で 20x20 = 400 です。実際もそのような数字ですね。

  8 threads and 30000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   997.95ms  563.57ms   1.95s    58.97%
    Req/Sec    97.01     51.76   400.00     88.52%
  Latency Distribution
     50%    1.00s 
     75%    1.50s 
     90%    1.80s 
     99%    1.95s 
  5927 requests in 15.09s, 306.77KB read
  Socket errors: connect 0, read 0, write 0, timeout 5147
Requests/sec:    392.66
Transfer/sec:     20.32KB

10万 RPS 出したい場合はどうしましょう(CPUが耐えられる場合)

>>> 100000 / 20
5000.0

コネクション約 5000 必要です。

測定の結果約 94000 RPS です。

  8 threads and 30000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    51.28ms   12.83ms 449.39ms   98.98%
    Req/Sec    13.73k     4.48k   17.89k    84.41%
  Latency Distribution
     50%   50.22ms
     75%   50.32ms
     90%   50.84ms
     99%   64.26ms
  1419640 requests in 15.06s, 71.76MB read
Requests/sec:  94247.31
Transfer/sec:      4.76MB

CPUを見るとまだ余裕があります。

コネクション数を 6000 に上げます。今度はCPUを最大限利用してくれました。

10万 RPS達成です。

  8 threads and 30000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    58.05ms    6.03ms 120.35ms   56.26%
    Req/Sec    12.95k     5.86k   23.45k    58.61%
  Latency Distribution
     50%   59.52ms
     75%   62.38ms
     90%   64.68ms
     99%   71.03ms
  1534466 requests in 15.04s, 77.56MB read
Requests/sec: 102000.61
Transfer/sec:      5.16MB

実際 50ms は遅いので 5ms の場合はどうでしょう。

1コネクションで 1000/5 = 200 RPS、20万 RPS目指してみます。

200*1000 = 20万、コネクション 1000必要となります。

  8 threads and 30000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.84ms    1.84ms 112.65ms   93.51%
    Req/Sec    32.09k    11.88k   50.32k    58.72%
  Latency Distribution
     50%    6.56ms
     75%    7.32ms
     90%    8.23ms
     99%   11.02ms
  1903478 requests in 15.08s, 96.21MB read
Requests/sec: 126265.77
Transfer/sec:      6.38MB

126K RPS。20万 RPS はでませんでしたね。CPU は最大に使っているのでこれ以上できることはあまりありません。

以上、意見やコメント大歓迎です。

Discussion