Discover more from Any Cables Monthly
Any Cables Monthly #10
Our newsletter latency reached one week, which can hardly be called “real-time”. But here we are; the delivery guarantees still hold 😉
We continue polishing the groundbreaking AnyCable v1.4. The latest release candidate brings more improvements, such as a new Redis Streams-based broadcasting adapter and order guarantees for broadcasted messages.
In this episode, Emmanuel Hayford and I discussed recent Rails additions related to Action Cable and beyond. What didn’t make it into the final cut was our discussion of the order guarantees of broadcasted Action Cable messages, which made me work on the improvement mentioned above for AnyCable v1.4.
Marco is one of the key figures in the Rails open-source community, especially its real-time-ish part. Read his story of discovering Rails, Stimulus, and becoming a member of StimulusReflex, CableReady, and Stimulus teams.
Learn how Poll Everywhere leveraged Hotwire and AnyCable to build an instant feedback live audience engagement platform.
Polyphony, a library to build concurrent (Fiber-driven) Ruby applications, had its first major release. Its companion web server, Tipi, has built-in support for HTTP/1, HTTP/2, and WebSockets. The major release is a good sign to give it a try, isn’t it?
Say hello to a new kid on the real-time block! Mayu is a full-featured Ruby framework to build server-side rendered web applications with live streaming capabilities. The design reminds me of Phoenix LiveView with its focus on self-contained live view components and efficient updates propagation (Mayu uses VirtualDOM under the hood).
The frame of curiosity: Why JIT?
Ruby 3.3.0-preview1 has been announced at RubyKaigi recently. The major highlights are the new JIT compiler, RJIT, and YJIT (yet another Ruby JIT) improvements. You’ve probably heard of noticeable performance improvements by turning on YJIT for large Ruby and Rails apps (e.g., Discourse), but what about WebSockets? Can Action Cable benefit from a JIT compiler? What about AnyCable? I decided to figure this out.
tl;dr YJIT rocks!
For this kind of analysis, we need to write a scenario reflecting a normal application load. We want to measure if enabling JIT improves the real-time characteristics of the application. For example, we can monitor the latency of broadcasted messages or how long it takes for a message to reach all subscribed clients. We can also measure the round-trip time (RTT) or for how long a client that sent a message waits for it to come back (boomerang time 🪃).
For that, I wrote a k6 scenario (using xk6-cable). You can find it in the anycable repository. In a nutshell, it allows me to create hundreds of clients gradually, let some of them trigger broadcasts, and measure RTT and latencies for these broadcasts.
Below you can see the results I obtained from running the benchmark script against Ruby 3.3.0.preview1 with and without YJIT enabled.
The baseline is Action Cable running via Puma with 4 workers:
broadcasts_sent......: 7870 18.195954/s
broadcast_duration...: avg=4.86s med=3.82s p(90)=11.97s
rtt..................: avg=2.12s med=545ms p(90)=5.64s
ws_connecting........: avg=545.03ms med=28.56ms p(90)=1.93s
We only show the relevant metrics in the snippet above:
broadcast_sent shows how many broadcasts per second we trigger on average during the benchmark run (so, ~20 per second)
broadcast_duration is the delivery latency among all subscribed clients
rtt is the broadcast message round-trip time
ws_connecting shows the time it takes to complete the Action Cable handshake (from
new WebSocket() to
Now, let's see what happens if we enable YJIT and warm it up:
broadcasts_sent......: 8016 19.150457/s
broadcast_duration...: avg=2.92s med=1.82s p(90)=7.82s
rtt..................: avg=1.26s med=156ms p(90)=4.14s
ws_connecting........: avg=318.39ms med=21.43ms p(90)=1.18s
And after few more runs:
broadcasts_sent......: 7858 18.880432/s
broadcast_duration...: avg=1.04s med=326ms p(90)=3.34s
rtt..................: avg=490.88ms med=63ms p(90)=1.9s
ws_connecting........: avg=198.98ms med=16.82ms p(90)=808.59ms
Wow! That's much better than I could imagine. Over time, the performance of a YJIT-enabled Action Cable server became times better!
How can we explain this? My guess is that JIT fits perfectly for such tasks as packing/unpacking WebSocket frames and encoding/decoding JSON—and this is a significant part of what happens in our cables.
P.S. What about AnyCable? You can draw your own conclusions by looking at the results below 😉:
broadcasts_sent......: 7956 19.233065/s
broadcast_duration...: avg=21.01ms med=17ms p(90)=37ms
rtt..................: avg=14.34ms med=10ms p(90)=32ms
ws_connecting........: avg=6.61ms med=3.17ms p(90)=14.94ms