Rails ActionCable 效能測試
本篇文章說明連線數上限相關設定,以及實測 ActionCable 預設模式、Standalone 模式和 AnyCable 三種方案的效能比較。
準備
首先建立一個 Rails 專案來測試 ActionCable 連線效能,先寫一個簡單的 Channel
1 | class MessageChannel < ApplicationCable::Channel |
在 config/environments/development.rb
加上
1 | config.action_cable.disable_request_forgery_protection = true |
然後寫一個 Node Client 程式來測試,使用套件 @anycable/web 和 ws
1 | import WebSocket from 'ws'; |
上面加上了計算連接和接收訊息的時間。原本一開始使用 Ruby 的 action_cable_client 套件來寫 Client,但是 Standalone 模式的時候不知道為什麼沒辦法正常運作,後來改成使用 Node 的套件。
連線數上限
Mac
File Descriptors
首先在自己的 Mac 電腦中測試,運行 rails 之後執行 Node 程式,會發現連線數停在兩百多就沒辦法再增加。查看 Rails Log 會看到錯誤訊息:
1 | 2024-05-19 16:57:24 +0800 Listen loop: #<Errno::EMFILE: Too many open files - accept(2)> |
而且這時候嘗試開啟網頁,會打不開。這是因為每個連線會開啟一個檔案,而 Unix 系統對 Process 使用資源有一些限制,輸入下面指令查看限制:
1 | ulimit -a |
會出現下面資訊
1 | -t: cpu time (seconds) unlimited |
其中 file descriptors 項目在 Mac 中預設是 256,所以連線數差不多到這數字就無法再增加。我們在目前兩個終端機 (Rails Server 和 Node Client) 個別輸入下面指令將的限制修改:
1 | ulimit -n 10000 |
臨時阜
重新跑一次就可以發現連線數可以超過兩百。接著改成輸入一個更大的數字,例如兩萬,會發現連線數會停在一萬六左右沒辦法繼續增加,這是因為 Mac 的系統限制,輸入下面指令查詢:
1 | sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last |
會顯示
1 | net.inet.ip.portrange.first: 49152 |
這是 Mac 預設的臨時阜範圍,可以發現範圍大約 16383 個 Port 可以使用。可以輸入下面指令暫時修改範圍:
1 | sudo sysctl -w net.inet.ip.portrange.first=32768 |
Ubuntu
File Descriptors
查詢和設定同 Mac,不過 Ubuntu 預設是 1024,使用 ulimit 指令最大只可改成 10001。超過會出現錯誤:
1 | ulimit: open files: cannot modify limit: Operation not permitted |
如果要永久修改,編輯 /etc/security/limits.conf
加入:
1 | * soft nofile 10000 |
這設定方式也可以超過 10001 的限制。
臨時阜
在 Ubuntu 中使用下面指令查詢
1 | sysctl net.ipv4.ip_local_port_range |
應該會看到
1 | net.ipv4.ip_local_port_range = 32768 60999 |
所以預設是大概 28231 個 Port 可以使用。可以輸入下面指令暫時修改範圍:
1 | sudo sysctl -w net.ipv4.ip_local_port_range="30000 60999" |
要永久修改,編輯 /etc/sysctl.conf
,加入下面這行:
1 | net.ipv4.ip_local_port_range = 30000 60999 |
Nginx
File Descriptors
使用 Nginx 也有 File Descriptors 的限制,可能會看到下面錯誤:
1 | accept4() failed (24: Too many open files) |
可以修改 nginx.conf,加入:
1 | worker_rlimit_nofile 10000; |
連線數
另外還會有連線數限制,可能會看到下面錯誤:
1 | 768 worker_connections are not enough |
則要修改 nginx.conf events 區塊:
1 | events { |
效能測試
實測 10000 個連線完成所需時間,和發送訊息全部接收到的時間。簡單發送訊息如下:
1 | MessageChannel.broadcast_to('message_channel', 'test') |
測試環境為 Mac 連線本機的情況。分別測試下面三種方案 (使用 Redis adapter):
- 預設的 ActionCable
- Standalone ActionCable
- AnyCable
以及測試不同程序數和執行緒數的影響,AnyCable 固定會跑兩個程序,所以只調整 rpc_pool_size
做測試。
模式 | 程序 | 執行緒 | 連線時間 | 訊息時間 |
---|---|---|---|---|
預設的 ActionCable | 1 | 5 | 35.91 s | 981 ms |
預設的 ActionCable | 1 | 10 | 35.30 s | 1156 ms |
預設的 ActionCable | 2 | 5 | 39.34 s | 689 ms |
Standalone ActionCable | 1 | 5 | 5.73 s | 747 ms |
Standalone ActionCable | 1 | 10 | 6.97 s | 867 ms |
Standalone ActionCable | 2 | 5 | 7.09 s | 424 ms |
AnyCable | 2 | 30 | 50.05 s | 187 ms |
AnyCable | 2 | 60 | 50.23 s | 153 ms |
測試結果
- 使用 Standalone ActionCable 比預設情況效能提升,在佔滿連線的時候也不會影響網頁和 API 使用。
- AnyCable 傳送訊息效能最好。
- 執行緒數沒有影響。
- ActionCable 程序數對於連線速度沒有影響,但對於傳送訊息速度可以提升。
- 另外測試時發現,連線數對記憶體影響不大,沒有明顯變化。
另外如果把上面的 Client 程式的 await 拿掉改成下面,會變成同時 Request:
1 | async start() { |
在這情況下 ActionCable 還是能正常運作,但 AnyCable 在同時大量 Request 時就無法運作,會一直沒辦法連線成功。所以使用 Standalone ActionCable 方案是不錯的選擇。