普段 Controller や Model を書いているぶんには、Rails の下で何が動いているかを意識することはあまりありません。
ところが、Rails アプリにリアルタイムチャットや通知のような「つなぎっぱなし」の機能を入れようとすると、急に「Unicorn だと相性が悪い」「Action Cable なら大丈夫」といった話が出てきます。
この記事では、そうした話を理解するための土台となる Rails の足元の仕組みを、レストラン にたとえて整理します。
1. Rack:注文票の共通フォーマット
Rack は、Ruby の Web サーバーと Web アプリをつなぐ共通の約束事です。
call(env)を持ち、[ステータス, ヘッダ, ボディ]を返すオブジェクトなら Rack アプリ
class HelloApp
def call(env)
[200, { 'content-type' => 'text/plain' }, ['Hello']]
end
end
レストランでいうと 注文票のフォーマット です。フォーマットが決まっているので、ホール(Unicorn、Puma)と厨房(Rails、Sinatra)を自由に組み合わせられます。
注文票が厨房に届くまでには、ミドルウェア(入口の警備員や受付)を通ります。
お客さん → 警備員(Rack::Attack) → 受付(Rack::Cors) → 厨房(Rails)
2. 層:ホール → 受付 → 厨房
Web サーバー(Unicorn / Puma) ホール:お客さんを迎え、店員を割り当てる
Rack + ミドルウェア 注文票と、受付・警備
アプリケーション層(Rails) 厨房:Controller や Model が動く
ここでいう「アプリケーション層」は、OSI 参照モデルのものとは別物で、Rails のコードが動いている段階のことです。
3. プロセスとスレッド:店舗と店員
プロセス = 店舗(専用の厨房・冷蔵庫を持つ)
スレッド = 店員(同じ店の冷蔵庫を共用する)
| プロセス(店舗) | スレッド(店員) | |
|---|---|---|
| メモリ | 別々 | 共有 |
| 作るコスト | 重い | 軽い |
| 怖いところ | メモリを食う | 共有データの取り合い |
x = 0
Thread.new { x = 100 }.join
p x # => 100(共有しているので書き換わる)
y = 0
Process.wait(fork { y = 100 })
p y # => 0(別の店舗なので影響しない)
店員同士が冷蔵庫を共有していると、「2人が同時に卵の数を数えて、在庫数がずれる」といった事故が起きます。これが スレッドセーフ問題 です。Ruby では、クラス変数やグローバル変数がこの「共有の冷蔵庫」にあたります。
4. ブロッキング:テーブルの横で立ち尽くす店員
スレッドが何かを待っている間、他のことが何もできずに止まっている状態を ブロッキング と呼びます。
店員:「ご注文は?」
お客さん:(メニューを30分眺めている……)
店員:(ずっとテーブルの横で待っている)
普通のリクエストの待ち時間は一瞬なので問題になりません。困るのは、ロングポーリングや WebSocket のような「つなぎっぱなし」の通信です。
5. Unicorn:店員1人の店舗をたくさん出す
Unicorn マスター(オーナー)
├ ワーカー1(店員1人)
├ ワーカー2(店員1人)
└ ワーカー3(店員1人)
- 1ワーカーが同時に処理できるのは1件だけ
- 店舗が独立しているので安全。ただしメモリを食う
つなぎっぱなしの通信が来ると、こうなります。
ワーカー1〜3:全員がお客さんの横で待機
→ 次のお客さんの相手が誰もできない 💀
6. Puma:1店舗に店員をたくさん置く
Puma プロセス
├ スレッド1(店員)
├ スレッド2(店員)
└ スレッド3(店員)
- Unicorn より少ないメモリで、多くのお客さんをさばける
- コードはスレッドセーフである必要がある
Ruby には GVL があり、Ruby のコードを実行できるのは同時に1スレッドだけです(包丁が1本しかないイメージ)。それでも DB などを待っている間は包丁を手放すので、Web アプリなら十分に速く動きます。
ただし、つなぎっぱなしの通信ではスレッドが待機で埋まり、結局 Unicorn と同じく詰まります。
7. Action Cable:呼び出しベルで見回るホール係
(Rails上で動きます)
発想を変えて、テーブルの横で待つのをやめます。
ホール係(1人):
各テーブルに呼び出しベルを置く
→ ベルが鳴ったテーブルにだけ、さっと対応する
これが イベントループ です。待機中の接続に誰も張り付かないので、接続が大量にあってもさばけます。
Action Cable は、Rack の rack.hijack で接続そのものを引き取り、このイベントループに渡します。そのため Web サーバーのワーカーはすぐに手が空き、Unicorn の上でも動きます。
弱点:ホール係が1件の重い仕事に没頭すると、全テーブルが待たされます。
→ Action Cable の上では重いクエリや sleep を避け、重い処理はバックグラウンドジョブに任せます。
まとめ
| 待機中の接続に張り付くもの | 接続が増えると | 重い処理 | |
|---|---|---|---|
| Unicorn | プロセス | メモリが足りなくなる | 強い |
| Puma | スレッド | スレッドが足りなくなる | まあまあ |
| Action Cable | なし | ほぼ平気 | 弱い |
Top comments (0)