Action Cable vs AnyCable: fight!

Introduction
I’m a long time Ruby on Rails developer. When Action Cable was released, the first thing that came to mind was: “will it perf?”, given that Ruby has never been recognized for its ability with concurrency management, nor Rails for its lightness.
Browsing around, I stumbled upon AnyCable, an alternative to Action Cable created for some reason. What if that reason is performance? Let’s find out!
How Action Cable works
Action Cable runs in an application server instance separate from the one that handles the standard HTTP requests. It uses
the hijack
API, a Rack API added
specifically for sessions requiring evented IO, like Server-Sent Events and WebSockets, giving to the application the
whole control of the socket. Within Action Cable, the WebSocket management is fully implemented in Ruby.
How AnyCable works
Like for Action Cable, AnyCable receives and sends messages using Ruby, but the socket management is forwarded via gRPC to a server compatible with AnyCable specifications, which can be written in any language. Currently, the official implementations are AnyCable-Go, written in Go, and ErlyCable, written in Erlang. For details about AnyCable architecture, I recommend reading the related posts listed in the AnyCable README.
Setup
If you’re trying to run the test by yourself you should read the Caveats section first
We are going to load test Action Cable and AnyCable with Tsung (credits to this excellent post which showed me the right way). Below the testing environment details:
-
CPU: Intel i7-2600K
-
OS: Ubuntu 18.04.1
-
Ruby: 2.5.3
-
Rails (along with Action Cable): 5.2.2
-
AnyCable: 0.6.0
-
AnyCable-Go: 0.6.0
-
Load testing: Tsung 1.7.0
I wrote two Procfile configurations respectively for Action Cable and AnyCable in order to run them using Foreman:
# Action Cable
foreman start -e .env.production,.env.actioncable -f Procfile.actioncable
# AnyCable
foreman start -e .env.production,.env.anycable -f Procfile.anycable
Once the server is up and running, we can start Tsung:
tsung -f tsung.xml -k start
Tsung is configured to
open 500 WebSocket connections per second (and keep them open) until it gets 60000 connections. 60000 is around the
limit that can be reached on a single machine since each connection needs a port to go through. Tsung writes its
results in the ~/.tsung
directory and serves them live at http://localhost:8091
.
Caveats
Performing the test is a bit tricky; there are some workarounds and tweaks we have to take:
tsung_stats.pl not found error
Tsung runs correctly, but it fails to generate the report showing an error like the following:
Fail to generated reports: tsung_stats.pl was not found in the $PATH
It happens because Tsung can’t find its binaries. Add them to $PATH
:
export PATH=/usr/lib/x86_64-linux-gnu/tsung/bin:$PATH
Overcome OS caps
As suggested here, we need to increase OS limits
related to open files and port ranges. Add the following to /etc/sysctl.conf
:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65000
fs.file-max = 2097152
And add the following to /etc/security/limits.conf
:
* soft nofile 1048576
* hard nofile 1048576
Reboot or reload the configuration and everything should be fine.
Image paths are broken
Visiting http://localhost:8091/es/ts_web:graph
we can’t see any report image, basically because image paths are
broken. Run the following code in your browser console:
const prependSlash = (_i, attr) => (attr.charAt(0) !== '/') ? `/${attr}` : undefined
$('.graph').attr('src', prependSlash).closest('a').attr('href', prependSlash)
Benchmarks
Enough talking! Let’s look at the results:
Action Cable
Action Cable performs fairly well for the first 20000 users, then it starts suffering and honorably tries to overtake this limit. Not without issues, as we can see from the errors rate report:
AnyCable
AnyCable doesn’t miss a beat, facing up to all the 60000 connections we throw at it. Moreover, the errors rate is… inexistent! Tsung refused to generate it.
Conclusion
Action Cable is rather capped regarding the connections amount that it can handle, though it can be enough for use cases with a restricted amount of users. If instead your application has stronger requirements, AnyCable is a perfectly viable option.