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?

Rails(Puma)の並行処理モデルを、ワーカー数・スレッド数 × I/Oバウンド・CPUバウンドで理解する

0
Last updated at Posted at 2026-02-28

はじめに

前回の記事では、Apache・Nginxの並行処理モデルの変遷を辿りながら、FastAPIにおける非同期処理の挙動を検証しました。

今後Railsをメインで扱っていくことになりそうなので、Ruby on Rails(Puma)の並行処理モデルについても調査してみました。

Pumaは前回のApache(マルチスレッド)の検証に近い挙動になり、「ワーカー数(プロセス数)」と「スレッド数」の2つを組み合わせ、1スレッド=1リクエストを扱うようでした。その上で、I/Oバウンド処理・CPUバウンド処理によって挙動が異なることが分かったので、それぞれ実機で検証しました。

対象読者

  • Rails(Puma)のワーカー数・スレッド数の設定が、実際のパフォーマンスにどう影響するか知りたい人
  • I/Oバウンド処理とCPUバウンド処理で、Pumaの並行処理の挙動がどう変わるか理解したい人

前提知識

I/OバウンドとCPUバウンドとは

  • I/Oバウンド
    • 外部APIの実行やDBのクエリ結果待ちなど、CPUを使っていない(=アプリのRubyが動いていない)状況
  • CPUバウンド
    • 複雑な計算や画像処理など、CPUを使っている(=アプリのRubyが動いている)状況

GVLとは

RubyのGVL(Global VM Lock)は、複数のスレッドがRubyのコードを同時に実行しないようにするためのメカニズムです。
...
Rubyの機能のうち、メモリ管理(ガベージコレクションなど)などはスレッド安全ではありません。そのため、GVLによって一度に1つのスレッドだけがRubyコードを実行するようにし、データの破損を防いでいるのです。

スレッドが実行しているコードがCPUバウンドの場合、以下のいずれかが発生するまでは実行を継続します。
1.スレッドがCPUバウンドの処理を完了させる。
2.スレッドがI/O操作を実行する(この場合自動的にGVLを解放する)
3.100msの上限に達する

→ マルチスレッドにおいて安全に処理を進めるための仕組みと理解しました。
GVLの仕組みのため、1プロセス内では同時に1スレッドしかRubyコードを実行できない = CPUバウンドな処理を複数スレッドで並列に実行できなくなります。
ただし、I/O待ちの間はGVLが解放されるため、I/Oバウンドな処理ではスレッドを複数利用することで、並行で処理を進めることができます。

検証内容

検証環境・手順

  • 以下の検証手順で実施していく

    1. Dockerコンテナによる環境構築
    2. ベースラインの確認: 負荷をかける前のプロセス数・スレッド数の初期状態を ps コマンドで記録する。
    3. 負荷試験の実施: Locustを用い、10並列(10ユーザー/秒)の負荷を1分間注入する。
    4. 負荷時の挙動観測: 負荷ピーク時におけるプロセス・スレッドの変化を ps コマンドで再確認し、初期状態と比較する。
  • psコマンドの見方

    略称 正式名称 (英語) 意味
    PID Process ID プロセスID。実行中のプログラムに割り振られる固有の識別番号です。
    PPID Parent Process ID 親プロセスID。そのプロセスを起動した「親」のPIDです。
    LWP Light Weight Process スレッドID。Linuxではスレッドのことを「軽量プロセス」と呼ぶため、この名称になっています。
    C CPU Utilization CPU使用率(の指標)。プロセスの生存期間中にどれくらいCPUを使ったかを示す整数値です。
    NLWP Number of Light Weight Processes スレッドの総数。そのプロセスの中にスレッドがいくつ存在するかを示します。
  • 負荷試験コマンド: locust -f <script>.py --headless -u 10 -r 10 -t 1m

1. I/Oバウンド処理の検証

I/Oバウンド処理の代わりとして、sleep 3(3秒間スレッドをブロック)を使用しました。

パターン①:ワーカー数1、スレッド3(同時処理数:1×3=3)

コード
class SampleController < ApplicationController
  def io_bound
    puts "リクエストを受け付けました。3秒待機します..."
    sleep 3
    puts "3秒待機が完了しました。レスポンスを返します。"
    render json: {
      message: "3秒待ちました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1 32    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        16     0    16  0    1 05:11 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  1    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    9 05:11 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        22     0    22  0    1 05:12 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                                                                 # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
--------|-------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
GET      /io_bound                                                                57     0(0.00%) |   9477    3052   12161   9100 |    0.99        0.00
--------|-------------------------------------------------------------------|-------|-------------|-------|-------|-------|-------|--------|-----------
         Aggregated                                                               57     0(0.00%) |   9477    3052   12161   9100 |    0.99        0.00
ログ(抜粋)
Puma starting in single mode...
*  Min threads: 3
*  Max threads: 3

Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:15 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:15 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:15 +0000
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
Completed 200 OK in 3005ms
Completed 200 OK in 3006ms
Completed 200 OK in 3010ms

Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:18 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:18 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:29:18 +0000
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。

...
結果

ログに「リクエスト受付」が3つずつ表示される = 3スレッド分処理されており、10リクエスト中3リクエストが処理され、そのほかの7リクエストは待たされていることがわかりました。

パターン②:ワーカー数1、スレッド6(同時処理数:1×6=6)

コード
class SampleController < ApplicationController
  def io_bound
    puts "リクエストを受け付けました。3秒待機します..."
    sleep 3  # ★ ここでスレッドを3秒間ブロック(外部API呼び出しの代わり)
    puts "3秒待機が完了しました。レスポンスを返します。"
    render json: {
      message: "3秒待ちました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 6)
threads threads_count, threads_count

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  1   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    16  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    17  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    18  0   12 05:05 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        25     0    25  0    1 05:05 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  1   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    16  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    17  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    18  0   12 05:08 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        19     0    19  0    1 05:08 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
GET      /io_bound              114     0(0.00%) |   4976    3045    6112   6100 |    1.97        0.00
ログ(抜粋)
Puma starting in single mode...
*  Min threads: 6
*  Max threads: 6

Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:31:27 +0000
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。

...
結果 ログに「リクエスト受付」が6つずつ表示される = 6スレッド分処理されており、10リクエスト中6リクエストが処理され、そのほかの4リクエストは待たされていることがわかりました。

パターン③:ワーカー数2、スレッド3(同時処理数:2×3=6)

コード
class SampleController < ApplicationController
  def io_bound
    puts "リクエストを受け付けました。3秒待機します..."
    sleep 3  # ★ ここでスレッドを3秒間ブロック(外部API呼び出しの代わり)
    puts "3秒待機が完了しました。レスポンスを返します。"
    render json: {
      message: "3秒待ちました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

workers ENV.fetch("WEB_CONCURRENCY") { 2 }

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  8    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        10     1    10  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    12  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    13  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    18  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    19  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    20  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    25  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    27  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    29  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    31  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        14     1    14  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    16  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    17  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    21  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    22  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    23  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    24  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    26  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    28  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    30  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        32     0    32  0    1 05:21 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  0    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    3 05:21 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        10     1    10  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    12  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    13  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    18  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    19  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    20  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    25  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    27  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    29  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    31  0   10 05:21 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        14     1    14  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    16  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    17  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    21  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    22  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    23  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    24  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    26  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    28  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    30  0   10 05:21 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        38     0    38  0    1 05:22 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
GET      /io_bound              114     0(0.00%) |   4944    3016    6123   6000 |    1.98        0.00
ログ(抜粋)
[1] Puma starting in cluster mode...
[1] *  Min threads: 3
[1] *  Max threads: 3
[1] *      Workers: 2
[1] - Worker 0 (PID: 10) booted in 0.0s, phase: 0
[1] - Worker 1 (PID: 14) booted in 0.0s, phase: 0

Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:35:20 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:35:20 +0000
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
Completed 200 OK in 3004ms (Views: 0.8ms | GC: 0.0ms)
Completed 200 OK in 3005ms (Views: 0.2ms | GC: 0.0ms)
Completed 200 OK in 3005ms (Views: 0.2ms | GC: 0.0ms)
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:35:23 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:35:23 +0000
Started GET "/io_bound" for 172.17.0.1 at 2026-02-28 06:35:23 +0000
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
Processing by SampleController#io_bound as */*
リクエストを受け付けました。3秒待機します...
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
3秒待機が完了しました。レスポンスを返します。
Completed 200 OK in 3014ms (Views: 0.9ms | GC: 0.0ms)
Completed 200 OK in 3003ms (Views: 0.2ms | GC: 0.0ms)
Completed 200 OK in 3003ms (Views: 0.1ms | GC: 0.0ms)
...
結果

ログに「リクエスト受付」が2つのワーカーに3リクエストずつ、合計で6つずつ表示される = 6スレッド分処理されており、10リクエスト中6リクエストが処理され、そのほかの4リクエストは待たされていることがわかりました。
先ほどの1ワーカーで6スレッドと同じような結果になることがわかりました。

I/Oバウンドのまとめ

比較項目 パターン①
ワーカー1×スレッド3
パターン②
ワーカー1×スレッド6
パターン③
ワーカー2×スレッド3
同時処理数 3 6 6(2×3)
プロセス構成 シングルモード(1プロセス、NLWP=9) シングルモード(1プロセス、NLWP=12) クラスタモード(マスター+ワーカー2、各NLWP=10)
平均レスポンス 9,477ms 4,976ms 4,944ms
中央値 9,100ms 6,100ms 6,000ms
req/s 0.99 1.97 1.98
総リクエスト数 57 114 114
ログの挙動 3リクエストずつバッチ処理 6リクエストずつバッチ処理 6リクエストずつバッチ処理(2プロセスに分散)

I/Oバウンド処理(sleep=I/O待ち)では、GVLの制約を受けにくいため、スレッド数を増やすだけで並行性が向上しました。スレッド3→6に増やすことでreq/sが約2倍(0.99→1.97)に改善されており、同時処理数の増加がスループットに直結していることが分かります。また、ワーカーを増やしてもスレッドを増やしても、同時処理数が同じであれば結果はほぼ同等でした。

2. CPUバウンド処理の検証

CPUバウンド処理として、3,000万回のカウントアップ(純粋なRubyのCPU処理)を使用します。
単体で実行すると以下の通り約1.4秒ほどで完了します。

Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 08:42:28 +0000
Processing by SampleController#cpu_bound as HTML
リクエストを受け付けました。0から30_000_000までカウントします。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 1369ms (Views: 0.2ms | GC: 0.0ms)

パターン①:ワーカー数1、スレッド3(同時処理数:1×3=3)

コード
class SampleController < ApplicationController
  def cpu_bound
    puts "リクエストを受け付けました。0から30_000_000までカウントします。"
    count = 0
    30_000_000.times { count += 1 }
    count
    puts "#{count}までカウントが完了しました。レスポンスを返します。"
    render json: {
      message: "#{count}までカウントしました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  5    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10 14    9 06:52 ?        00:00:01 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        16     0    16  0    1 06:52 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  1    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10 21    9 06:52 ?        00:00:10 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11 17    9 06:52 ?        00:00:08 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12 18    9 06:52 ?        00:00:08 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    9 06:52 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        28     0    28  0    1 06:53 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
GET      /cpu_bound               39     0(0.00%) |  13704    4124   18528  14000 |    0.65        0.00
ログ(抜粋)
Puma starting in single mode...
*  Min threads: 3
*  Max threads: 3

Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 06:53:10 +0000
Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 06:53:10 +0000
Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 06:53:10 +0000
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 3682ms
30000000までカウントが完了しました。レスポンスを返します。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 4100ms
Completed 200 OK in 3521ms

...
結果

psコマンドを見ると、LWP 10, 11, 12の3つのスレッドでCPU使用率(C列)が高くなっている=3スレッドが同時にCPU処理を実行しています。
しかし、ログを見ると1リクエストが3〜4秒かかっている=GVLの制約により、同時に実行できるRubyコードは1スレッドのみのため、スレッド間でGVLの取り合いが発生しており、本来は1リクエストあたり約1.4秒で済むはずの処理に時間がかかってしまっていることが分かりました。

パターン②:ワーカー数1、スレッド6(同時処理数:1×6=6)

コード
class SampleController < ApplicationController
  def cpu_bound
    puts "リクエストを受け付けました。0から30_000_000までカウントします。"
    count = 0
    30_000_000.times { count += 1 }
    count
    puts "#{count}までカウントが完了しました。レスポンスを返します。"
    render json: {
      message: "#{count}までカウントしました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 6)
threads threads_count, threads_count

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  4   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11 11   12 06:55 ?        00:00:01 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    16  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    17  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    18  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        19     0    19  0    1 06:56 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  1   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     8  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    10  9   12 06:55 ?        00:00:04 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    11 10   12 06:55 ?        00:00:05 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    12  8   12 06:55 ?        00:00:04 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    13  7   12 06:55 ?        00:00:04 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    14  8   12 06:55 ?        00:00:04 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  9   12 06:55 ?        00:00:04 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    16  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    17  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    18  0   12 06:55 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        25     0    25  0    1 06:56 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
GET      /cpu_bound               36     0(0.00%) |  14751    7635   19804  15000 |    0.62        0.00
ログ(抜粋)
Puma starting in single mode...
*  Min threads: 6
*  Max threads: 6

Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 06:56:16 +0000
(6リクエスト同時受付)
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
30000000までカウントが完了しました。レスポンスを返します。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 4600ms
Completed 200 OK in 6593ms
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 6266ms
30000000までカウントが完了しました。レスポンスを返します。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 7617ms
Completed 200 OK in 3473ms
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 5824ms

...
結果

psコマンドを見ると、LWP 10〜15の6つのスレッドでCPU使用率(C列)が高くなっている=6スレッドが同時にCPU処理を実行しています。
しかし、ログを見ると1リクエストが3.5〜7秒かかっている=3スレッドと同様GVLの制約によりスレッド間でGVLの取り合いが発生しており、3スレッドの時よりもパフォーマンスが悪化しているのが分かりました。

パターン③:ワーカー数2、スレッド3(同時処理数:2×3=6)

コード
class SampleController < ApplicationController
  def cpu_bound
    puts "リクエストを受け付けました。0から30_000_000までカウントします。"
    count = 0
    30_000_000.times { count += 1 }
    count
    puts "#{count}までカウントが完了しました。レスポンスを返します。"
    render json: {
      message: "#{count}までカウントしました",
    }
  end
end
pumaの設定
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count

workers ENV.fetch("WEB_CONCURRENCY") { 2 }

port ENV.fetch("PORT", 3000)
plugin :tmp_restart
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
初期状態のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1 12    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        10     1    10  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    12  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    13  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    17  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    18  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    20  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    24  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    25  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    26  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    27  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        14     1    14  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    16  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    19  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    21  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    22  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    23  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    28  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    29  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    30  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    31  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        32     0    32  0    1 06:59 ?        00:00:00 ps -efL
10並列負荷時のプロセス・スレッド状況(psコマンド)
$ docker exec performance-sample-api ps -efL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root         1     0     1  0    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0     9  0    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root         1     0    15  0    3 06:58 ?        00:00:00 puma 7.2.0 (tcp://0.0.0.0:3000) [app]
root        10     1    10  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    12  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    13  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    17 20   10 06:58 ?        00:00:15 puma: cluster worker 0: 1 [app]
root        10     1    18 17   10 06:58 ?        00:00:13 puma: cluster worker 0: 1 [app]
root        10     1    20 14   10 06:58 ?        00:00:11 puma: cluster worker 0: 1 [app]
root        10     1    24  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    25  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    26  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        10     1    27  0   10 06:58 ?        00:00:00 puma: cluster worker 0: 1 [app]
root        14     1    14  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    16  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    19  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    21 14   10 06:58 ?        00:00:11 puma: cluster worker 1: 1 [app]
root        14     1    22 14   10 06:58 ?        00:00:11 puma: cluster worker 1: 1 [app]
root        14     1    23 15   10 06:58 ?        00:00:12 puma: cluster worker 1: 1 [app]
root        14     1    28  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    29  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    30  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        14     1    31  0   10 06:58 ?        00:00:00 puma: cluster worker 1: 1 [app]
root        44     0    44 33    1 07:00 ?        00:00:00 ps -efL
パフォーマンス
Type     Name                # reqs      # fails |    Avg     Min     Max    Med |   req/s  failures/s
GET      /cpu_bound               50     0(0.00%) |   9706    1932   22642   2700 |    0.84        0.00
ログ(抜粋)
[1] Puma starting in cluster mode...
[1] *  Min threads: 3
[1] *  Max threads: 3
[1] *      Workers: 2
[1] - Worker 0 (PID: 10) booted in 0.0s, phase: 0
[1] - Worker 1 (PID: 14) booted in 0.0s, phase: 0

Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 07:00:25 +0000
Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 07:00:25 +0000
Started GET "/cpu_bound" for 172.17.0.1 at 2026-02-28 07:00:25 +0000
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
Processing by SampleController#cpu_bound as */*
リクエストを受け付けました。0から30_000_000までカウントします。
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 2365ms
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 2818ms
30000000までカウントが完了しました。レスポンスを返します。
Completed 200 OK in 2480ms

...
結果

psコマンドを見ると、worker 0のLWP 17, 18, 20とworker 1のLWP 21, 22, 23の6つのスレッドでCPU使用率(C列)が高くなっている=6スレッドが同時にCPU処理を実行しています。
req/sのパフォーマンスは改善しており、各プロセスが独立したGVLを持つため、2プロセスで並列にCPU処理が実行されたことにより、改善されたことが分かりました。

CPUバウンドのまとめ

比較項目 パターン①
ワーカー1×スレッド3
パターン②
ワーカー1×スレッド6
パターン③
ワーカー2×スレッド3
同時処理数 3 6 6(2×3)
プロセス構成 シングルモード(1プロセス) シングルモード(1プロセス) クラスタモード(マスター+ワーカー2)
平均レスポンス 13,704ms 14,751ms ↑悪化 9,706ms ↓改善
中央値 14,000ms 15,000ms ↑悪化 2,700ms ↓大幅改善
req/s 0.65 0.62 ↓悪化 0.84 ↑改善
総リクエスト数 39 36 ↓悪化 50 ↑改善

CPUバウンド処理では、GVLの制約により1プロセス内でスレッドを増やしても性能は向上しない(むしろ悪化する)ことが明らかになりました。
単体で実行すると1.4秒ほどの処理でも、GVL待ちの時間のために1リクエストの処理時間自体が遅くなっていました。
一方で、ワーカー(プロセス)を増やすことで、各プロセスが独立したGVLを持ち、GVL待ちの時間が改善され、性能改善が得られました。

まとめ:処理タイプ別のPumaチューニング指針

観点 I/Oバウンド処理
(sleep / 外部API / DB待ち)
CPUバウンド処理
(計算 / ループ / 画像処理)
GVLの影響 小さい
I/O待ちの間GVLが解放されるため、スレッドの並行性が活きる。
大きい
1プロセス内で同時にRubyコードを実行できるのは1スレッドのみ。
スレッド数の増加 効果あり
同時に待機できるリクエストが増え、スループットが向上する。
逆効果
GVLの取り合いによるオーバーヘッドが増え、性能が悪化する。
ワーカー数の増加 効果あり(ただしスレッド増と同等)
メモリは増えるが、I/Oバウンドなら同時処理数が同じであればスレッド増と結果は変わらない。
効果大
各プロセスが独立したGVLを持つため、並列実行が可能。CPUコア数に応じた改善が見込める。
推奨設定 スレッド数を多めに設定。ワーカー数は少なめでもOK。 ワーカー数をCPUコア数に合わせて設定。スレッド数は少なめが望ましい。

結論

今回の検証で、I/Oバウンド処理ではスレッド数の増加が素直に効く一方、CPUバウンド処理ではGVLの制約によりワーカー(プロセス)を増やさないと改善しないことが明らかになりました。

前回のFastAPI(イベントループ方式)と今回のRails(マルチプロセス×マルチスレッド方式)では、表面的な実装方式は大きく異なりますが、どちらもプロセス・スレッドという同じ土台の上に成り立っているため、その動きから理解することで「なぜこの設定で速くなるのか」「なぜこの書き方だと詰まるのか」を本質的に捉えられるようになると感じました。

パフォーマンスチューニングにおいては、単純にサーバースペックをスケールアップ/スケールアウトさせたり、プロセス・スレッドの数を増やしたりするだけでなく、アプリケーションの処理特性とアプリケーションサーバーの並行処理モデルを踏まえた上で、適切にチューニングしていく必要があると実感しました。
さらに規模が大きくなった場合には、処理特性ごとにサービスを分離するマイクロサービス化など、インフラ設計レベルでの対応もできるようになりたいと思います。

補足

FastAPI(イベントループ)(前回記事参照)とRails(マルチスレッド)の比較

特徴 FastAPI (Uvicorn)
【イベントループ方式】
Rails (Puma)
【マルチプロセス × マルチスレッド】
並行処理モデル シングルスレッド + イベントループ マルチプロセス(ワーカー) + マルチスレッド
I/Oバウンド処理 async/awaitで非同期化すれば、1スレッドで大量の同時接続を処理可能。 sleepやI/O待ちでGVLが解放されるため、スレッド数分だけ並行処理が可能。
CPUバウンド処理 イベントループがブロックされ、全リクエストが停止する。 GVLの制約はあるが、ワーカー(プロセス)を増やせば並列実行が可能。
開発者の意識 async/awaitを正しく書かないとブロッキングが発生する。 同期的なコードをそのまま書ける。チューニングはPumaの設定で行う。

参考記事

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?