AnyCable for Ruby on Rails: How Does it Improve over Action Cable?

tripplea

Abiodun Olowode

Posted on May 16, 2024

AnyCable for Ruby on Rails: How Does it Improve over Action Cable?

In modern web applications, real-time communication has become more than a feature: it's gradually evolved into a necessity. Users expect instant updates, live interactions, and dynamic content.

In Rails applications, Action Cable has long been the go-to solution, harnessing WebSockets to fulfill these demands. In this article, we introduce:

  • The basics of WebSockets
  • How Action Cable enables real-time communication via WebSockets
  • Why AnyCable was created
  • How to get AnyCable up and running
  • The improvements AnyCable brings to the table

Let's get started!

The WebSocket Technology

The WebSocket technology was introduced in 2011 with the publication of RFC 6455, titled "The WebSocket Protocol". This standardized protocol facilitated the establishment of persistent, real-time communication channels between web browsers and servers, allowing for more efficient and continuous data exchange compared to traditional HTTP.

The WebSocket technology enables continuous bi-directional communication between clients and servers, a stark departure from the request-response model of HTTP (where connections terminate after each exchange). This persistence in communication proves pivotal for real-time applications like chat platforms and gaming systems.

Before the introduction of WebSocket technology, many applications often relied on a technique called polling. Polling involves the client repeatedly sending requests to the server at specified intervals to check for updates or new information. While this method allowed for some level of real-time updates, it was less efficient than WebSocket communication. Polling could lead to unnecessary server load and increased latency, as the client had to initiate a new request each time it wanted to retrieve updated data.

WebSocket technology addressed these limitations by establishing a persistent connection between the client and the server, enabling more immediate and efficient data transmission without the need for constant polling. This shift significantly improved the responsiveness and real-time capabilities of applications, providing a seamless and dynamic experience for users, particularly in scenarios demanding instant, up-to-the-moment information.

Both Action Cable and AnyCable harness the power of web sockets to furnish Rails applications with real-time updates, liberating users from the cumbersome need to refresh pages or poll for the latest information.

Action Cable and Real-time Communication

Action Cable enables the incorporation of real-time features into a Rails application via web sockets. This real-time communication happens between the client side (browser) and the Rails server. A few things are important to note:

  • A connection forms the foundation of this client-server relationship. For every WebSocket connection, Action Cable creates one connection instance.
  • A channel refers to a communication pathway that allows clients to subscribe to and receive updates about specific topics or categories of information, e.g., BookChannel, MovieChannel, SportsChannel.
  • A subscriber is a client subscribed to a channel to receive information.
  • Broadcasting is when the server publishes information that is transmitted by the relevant channel to the subscribed client/s.
  • A stream is the route or pathway through which broadcasts get to the subscribers. By using stream_from, subscriptions can be made for broadcasting.

When a connection is established between the client and the server, Action Cable creates a connection object for that connection. With this connection, the client can subscribe to one or more channels. Action Cable broadcasts updates to all subscribed clients via their respective channels.

A simple example is a browser view showing a library book list that updates when a book is added.

# create the rails application
rails new book_list
cd book_list
# create the needed controller and model
rails g controller books index
rails g model Book name
rails db:migrate
# change the development Action Cable adapter from Async (the default one) to Redis
rails turbo:install:redis
# start the redis server
redis-server
# start the rails server
rails s
Enter fullscreen mode Exit fullscreen mode

To get a real-time update on the book model, we employ Action Cable for broadcasting the updates. We set up Action Cable to broadcast these updates to a channel called "books". Via Turbo Streams, we create the subscriber to this channel and deliver the updates.

 # app/models/books.rb
class Book < ApplicationRecord
  broadcasts_to -> (book) { :books }
  # Broadcasts all the activities on this model -> create, update, destroy.
end
Enter fullscreen mode Exit fullscreen mode

We can now render the list of books:

# app/views/rooms/_book.html.erb
<div id=<%= dom_id(book) %>> <%= book.name %> </div>
Enter fullscreen mode Exit fullscreen mode
# app/views/books/index.html.erb
<h1>Books</h1>
<%= turbo_stream_from "books" %>
<div id="books">
  <%= render @books %>
</div>
Enter fullscreen mode Exit fullscreen mode

As seen below, different actions trigger a broadcast, and Action Cable broadcasts to the channel specified — "books". The view is also subsequently updated to reflect these updates.

irb> Book.create(name: "The Avatar")
  Rendered books/_book.html.erb (Duration: 0.0ms | Allocations: 11)
[ActionCable] Broadcasting to books: "<turbo-stream action=\"append\" target=\"books\"><template><div> The Avatar </div></template></turbo-stream>"
=> #<Book:0x0000000107c13358>

irb> book_1 = Book.find(1)

irb> book_1.update(name: "The Demo")
  Rendered books/_book.html.erb (Duration: 0.0ms | Allocations: 11)
[ActionCable] Broadcasting to books: "<turbo-stream action=\"replace\" target=\"book_1\"><template><div> The Demo </div></template></turbo-stream>"
=> true

irb> book_1.destroy
[ActionCable] Broadcasting to books: "<turbo-stream action=\"remove\" target=\"book_7\"></turbo-stream>"
=> #<Book:0x0000000107bd2948
Enter fullscreen mode Exit fullscreen mode

In Rails, integrating Action Cable (and, as a result, real-time updates into an application) is quite easy: even a beginner can get up and running in minutes. With Action Cable, we can manage connections, channels, and broadcasting to clients without worrying about the underlying WebSocket details. It also uses the same connection for multiple subscriptions, reducing the overhead of creating new connections for each new real-time update.

Why AnyCable for Ruby on Rails?

Action Cable is built on Ruby and has significant differences from other servers written in Golang and Erlang. Check out a detailed summary of AnyCable vs. Action Cable benchmark findings. Here are the key results:

  • Action Cable consumes approximately 4 times more memory when handling 20 thousand idle clients without subscriptions or transmissions.
  • Action Cable requires much higher CPU usage.
  • Action Cable takes around 10 times longer to broadcast to 10,000 clients. Starting at 1 second for a thousand clients, the time required increases by 1 second for every additional thousand clients.

The AnyCable WebSocket Server was created to combine the beauty of Action Cable with the performance benefits gained from Golang. AnyCable handles WebSockets on a different server called AnyCable-Go, effectively reducing the burden on your primary web application.

In addition to performance advantages, AnyCable offers resumability. If a client becomes disconnected from the server (e.g., due to network issues leading to an offline status), upon reconnection, the client will receive all the messages missed during the disconnection. They are also not required to re-subscribe as sessions are recovered. See a brief demo of the resumability feature.

View detailed information on the recent enhancements to AnyCable.

Integrating AnyCable

Let's replace Action Cable with AnyCable in our BookList example.

Install the gem:

# add the gem to your gemfile
gem "anycable-rails"

# install it
bundle install
Enter fullscreen mode Exit fullscreen mode

Next, run the setup generator.

This generator is interactive and asks questions about:

  • The type of development environment in use.
  • How you would like to install the AnyCable-Go websocket server.
  • If Heroku is being used for deployment.
  • If JWT is the intended means of authentication.
  • If you want a Procfile.dev file.

All of this information helps to reduce the amount of manual work you need to do.

rails g anycable:setup
Enter fullscreen mode Exit fullscreen mode

If you did say "yes" to the generation of a Procfile.dev, it would look something like this:

# Procfile.dev

web: bin/rails s
anycable: bundle exec anycable
ws: bin/anycable-go --port=8080
Enter fullscreen mode Exit fullscreen mode

Hence, you do not need to manually run the rest of the commands below.

Run the AnyCable RPC server:

bundle exec anycable
Enter fullscreen mode Exit fullscreen mode

Run the WebSocket server:

anycable-go --port=8080
Enter fullscreen mode Exit fullscreen mode

Restart the Rails server.

In the cable.yml file, you'll find that the adapter changes to any_cable if the ACTION_CABLE_ADAPTER environment variable is not set.

adapter: <%= ENV.fetch("ACTION_CABLE_ADAPTER", "any_cable") %>
Enter fullscreen mode Exit fullscreen mode

In all the $env.rb files, you'll find the Action Cable URL set as:

config.action_cable.url = ActionCable.server.config.url = ENV.fetch("CABLE_URL", "/cable") if AnyCable::Rails.enabled?
Enter fullscreen mode Exit fullscreen mode

In the any_cable.yml file, the Redis channel is set to __anycable__. You can confirm this in the terminal via:

redis-cli PUBSUB CHANNELS
# 1) "__anycable__"
Enter fullscreen mode Exit fullscreen mode

Read more about the AnyCable configuration.

In the Rails server, you'll find a routing error related to the /cable endpoint. You'll also find that, when you filter by websockets in the Network tab of the browser, the Request URL for cable is ws://localhost:3000/cable, which is wrong. We should be attempting to connect to ws://localhost:8080/cable, the value of config.action_cable.url.

To fix this, the action_cable_meta_tag has to be added to the application.html.erb file.

# application.html.erb
<head>
# ...
  <%= action_cable_meta_tag %>
# ...
</head>
Enter fullscreen mode Exit fullscreen mode

This resets the url to the set value. At this point, any broadcasted stream will show up!

Swapping Action Cable for AnyCable in Ruby

To enable resumability, AnyCable utilizes the Action Cable Extended Protocol, which is implemented by the AnyCable server version 1.4 and later. This protocol is inherently supported by the AnyCable JS client.

To integrate this with Turbo Streams, it's necessary to swap out the default Action Cable client for the AnyCable client. However, when using the @hotwired/turbo-rails package, the Action Cable getConsumer function is invoked before the setConsumer function during the initial page load, leading to the consumer not being overridden, and two web socket connections being opened. The @anycable/turbo-stream package was created to handle this (read additional details about this issue).

Here's the procedure for this swap.

Add the @anycable/web and @anycable/turbo-stream packages:

bin/importmap pin @anycable/web @anycable/turbo-stream
Enter fullscreen mode Exit fullscreen mode

This pins the stated packages, along with @anycable/core, @hotwired/turbo, and nanoevents to your importmap.rb file.

Create the anycable client:

// app/javascript/cable.js
import { createCable } from "@anycable/web";

export default createCable();
Enter fullscreen mode Exit fullscreen mode

Refer to this as the new "cable" client:

# config/importmap.rb
pin "cable", to: "cable.js", preload: true
Enter fullscreen mode Exit fullscreen mode

Start the new cable client:

// app/javascript/application.js
// We no longer need @hotwired/turbo-rails
import "@hotwired/turbo";
import { start } from "@anycable/turbo-stream";
import cable from "cable";

start(cable);
Enter fullscreen mode Exit fullscreen mode

With this, we can restart our server, and the consumer/client is replaced with AnyCable.

Read more about advanced configurations for the Turbo Streams package.

With the client replaced, resumability can be implemented.

With resumable sessions, the supported adapters are http and redisx (if you use Redis). It is, however, important to note that the broker preset command automatically sets the adapter as http. Hence, when using redisx, it must specified explicitly like so:

$ anycable-go --broker=memory --broadcast_adapter=redisx
Enter fullscreen mode Exit fullscreen mode

There you go! You should have resumable sessions working!

Community Adoption and Feedback

AnyCable has existed for several years and has gained widespread adoption among numerous organizations. Adopters have cited several compelling reasons for its attractiveness, including:

  • Seamless migration without the need for architectural changes.
  • No requirement to modify Action Cable channels or connections.
  • Utilization of a dedicated server for WebSockets.
  • Ability to handle a substantial number of concurrent connections without encountering any problems.

See a few testimonials from satisfied users.

Wrapping Up

In this post, we've seen that AnyCable offers performance enhancements to Action Cable and can be used as an alternative when dealing with a large number of connections or high traffic.

With recent improvements to AnyCable (such as the introduction of reliable streams), AnyCable has become a solid choice regardless of performance concerns. It supports broadcasting and subscription confirmation, thereby making it consistent and reliable. It can also be used in non-Rails applications, and to power serverless JavaScript applications.

Overall, AnyCable is a reliable choice for modern web applications and supports a variety of servers, allowing for greater flexibility in development.

Happy coding!

P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

💖 💪 🙅 🚩
tripplea
Abiodun Olowode

Posted on May 16, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related