List

Untangling cables and demystifying twisted transistors

Untangling cables and demystifying twisted transistors

by Vladimir Dementyev

In this presentation titled "Untangling Cables and Demystifying Twisted Transistors," Vladimir Dementyev explores the complexities of Action Cable, addressing how it enhances real-time features in Rails applications. Celebrating the 20th anniversary of Rails, he highlights the framework's development philosophy, particularly the principle of conceptual compression, which abstracts complexity to focus on core application needs.

Key Points Discussed:
- Rails Development Philosophy: Rails represents more than just a framework; it embodies principles and conventions tailored to simplify development.
- Understanding Action Cable: The primary abstraction in Action Cable is the channel, which operates similarly to controllers but is designed for real-time communications. Developers can interact with it without delving into the underlying WebSocket mechanics.
- Client-Side Architecture:
- The consumer acts as a WebSocket wrapper, managing user sessions efficiently with a single connection for multiple subscriptions.
- The subscription object allows for receiving server updates based on parameters.
- Monitor functions manage connections and ensure they remain alive during network interruptions.
- WebSocket Communication Lifecycle: Initial client-server connection handling and subsequent message processing involve intricate management of WebSocket events and resources, emphasizing the necessity of tuning database pools and task queues to prevent performance bottlenecks.
- Turbo Streams: Turbo, part of the Hotwire approach, automates many actions related to Action Cable, although improper management of Turbo elements can lead to overhead issues due to excessive subscriptions.
- Challenges and Solutions: Dementyev addresses potential pitfalls such as connection overload, known as the 'Thundering Herd' problem, and the importance of staggered reconnections to manage server loads effectively.

Conclusion:
The presentation concludes with the assertion that while Action Cable significantly enhances Rails applications, understanding its intricate workings is essential for developers to prevent and address potential issues. Key takeaways include the importance of mastering the abstractions provided by Rails, optimizing subscriptions, and being aware of performance considerations in real-time applications, especially under high-traffic conditions.

More and more Rails applications adopt real-time features, and it’s not surprising - Action Cable and Hotwire brought development experience to the next level regarding dealing with WebSockets. You need zero knowledge of the underlying tech to start crafting a new masterpiece of web art.

However, you will need this knowledge later to deal with ever-sophisticated feature requirements and security and scalability concerns. @evil.martians Cable Engineer Vladimir Dementyev helps you understand Rails’ real-time component, Action Cable so you can work with it efficiently and confidently.

Slides available at: https://speakerdeck.com/palkan/railsworld-2023-untangling-cables-and-demystifying-twisted-transistors

Links:
https://rubyonrails.org/

#RailsWorld #RubyonRails #rails #actioncable

Rails World 2023

00:00:17.760 Hello everyone! It’s such an honor for me to present in front of all of you at Rails World. Today, we're celebrating the 20th anniversary of Rails.
00:00:23.599 Twenty years ago, I had no idea what programming was. I didn't even own a computer. My only passion back then was music.
00:00:30.560 I still remember the feeling of buying a new tape or CD, eagerly looking for the tracklist of a newly released album.
00:00:36.280 That pleasant feeling of anticipation is somewhat similar to what I hope you feel today.
00:00:42.280 So, I've placed the tracklist for today's topic, "Untangling Cables and Demystifying Twisted Transistors," on the first slide to build your expectations. At the end of the presentation, we can see if I matched them or not.
00:00:52.760 Let's start with the first track called "Rails" and discuss why we are here today. Hundreds of people have come to Amsterdam from around the world.
00:01:06.080 They're not here just to enjoy museums and canals, but to spend two days discussing the craft of software engineering.
00:01:12.000 That's amazing! It almost sounds cult-like, but I’m good with that.
00:01:17.799 Rails is not merely a tool or a framework; it represents a software development philosophy. It embodies conventions, principles, and the doctrine of 'the Rails way'.
00:01:30.280 One of the cornerstones of this philosophy is conceptual compression. Rails aims to hide all the complexity inside the black boxes of its components, allowing you to focus on what's truly important for your application.
00:01:42.399 While black boxes are beneficial, you should master your tools to improve as an engineer.
00:01:49.079 This involves finding brevity and taking a look inside those boxes. Today, I’ll help you explore one specific box: the Action Cable box.
00:02:05.960 A couple of words about myself: my name is Vladimir Dementyev. You may know me by my GitHub handle, pcon.
00:02:11.039 I work at a company called Evil Martians, a product development consultancy. We have a really cool blog and build amazing web products.
00:02:18.440 We're proficient in Rails, so if you need any help, feel free to reach out to me or my colleagues here.
00:02:33.360 In addition, I work on AnyCable, a real-time server that can serve as a drop-in replacement for Action Cable, offering new features and better performance.
00:02:41.680 However, I'm not discussing AnyCable today; I'm focusing on Rails, especially since we're at Rails World.
00:02:49.239 That said, you may still spot AnyCable's principles in my discussion today. Additionally, I've recently published a book about Rails and the abstractions we encounter.
00:03:06.080 This naturally leads us to the start of exploring Action Cable because the exterior of the black box contains the abstraction that Rails provides us with.
00:03:18.440 Let's start by looking at the Action Cable box and what we see as developers. First, let's recall how Action Cable works, its interface, and its abstractions.
00:03:33.640 The primary abstraction of Action Cable is a channel. Channels are a bit like controllers but are specifically designed for real-time communications.
00:03:49.680 They have familiar conventions and interfaces similar to other parts of Rails. For instance, using a single Ruby class with business logic and a bit of JavaScript to establish a communication channel.
00:04:01.680 You don't need to know about WebSockets or any of the underlying low-level details. It's all hidden from you because you only interact with the abstractions provided by the framework.
00:04:09.360 The goal of proper abstraction is to hide complexity. Conceptual compression applies here, as channel abstraction is just a part of the Action Cable equation.
00:04:21.320 In the original presentation back in 2015, we saw other components present, but that's just the tip of the iceberg when it comes to Action Cable.
00:04:33.960 The real map of Action Cable is more complicated and less readable, but don’t worry, I'll guide you through it today.
00:04:40.319 We'll learn about how this design affects the interesting side effects and phenomena we observe when using Action Cable.
00:04:54.880 Let’s start with the client-side part of this map. We've already seen some client-side code.
00:05:01.680 The code uses the official Rails Action Cable library. We are discussing what Rails provides out of the box, which comes with a couple of abstractions.
00:05:14.960 First is a consumer, which acts as a WebSocket wrapper. The second abstraction is a subscription, an object that receives updates from the server based on parameters we pass.
00:05:29.640 The channel name and some additional data are crucial for this process. A single consumer is usually used by a single user session, like a browser tab or mobile device.
00:05:40.640 We do not need multiple connections for different channels; just one connection is sufficient.
00:05:52.600 I want to make a quick note regarding browsers: there is a cool project called Cable Shared Worker.
00:06:01.280 It enables a Cable session to be shared across the whole browser, which is particularly useful for users who keep multiple tabs open.
00:06:08.160 This helps reduce the overhead on the server since now there’s a single connection reused for all tabs.
00:06:21.360 Now, moving on: we can have multiple subscriptions. They are all multiplexed internally, meaning that all messages for different subscriptions come through the same WebSocket.
00:06:35.280 From the WebSocket's perspective, it has no understanding of subscriptions or channels; it only operates on messages.
00:06:46.639 The client-side library takes on the responsibility of sorting this data into proper JavaScript objects.
00:06:59.760 Now, a question arises: how many subscriptions can we have? Is there any overhead involved?
00:07:05.040 Since subscriptions are just abstractions, like JavaScript objects, can we have many of them?
00:07:13.760 The short answer is yes; in theory, the design of the library and Action Cable does not restrict the number of subscriptions.
00:07:20.639 However, by the end of this talk, we'll note some factors that we might need to consider when dealing with them.
00:07:34.800 Another interesting point is that since subscriptions are abstractions, we can have duplicate subscriptions.
00:07:47.760 We could subscribe to the same channel multiple times, for instance, from different front-end components.
00:07:59.360 The client takes care of ensuring that the actual subscription on the server side occurs just once.
00:08:07.760 When the server receives messages, it sends them to all relevant subscriptions and unsubscribes when they're no longer needed.
00:08:18.040 The last critical piece of the client is the monitor. Action Cable not only provides a wrapper over WebSockets and a subscription API.
00:08:26.360 It also ensures that your connection remains alive during network issues.
00:08:37.160 The monitor functions by detecting broken connections. The biggest challenge with persistent connections is how to identify if a connection has broken.
00:08:50.960 Since WebSockets use TCP, if we do not send any data in either direction, we cannot ascertain the connection's status.
00:09:01.760 TCP allows for arbitrarily long gaps in data transmission because it assumes the connection is still alive.
00:09:09.080 To handle this, the Action Cable server sends ping messages every three seconds by default, and the monitor keeps track of the last ping received.
00:09:22.400 If a ping is received within the timeout period, it resets the timer. However, if pings are missed for a specified duration, the monitor forces a reconnection.
00:09:33.520 It then determines if the server is still available or if there's a persistent problem.
00:09:39.840 That said, pings are often effective but not foolproof. Issues can arise with cases involving proxy servers or intermediary representations.
00:09:54.280 In such scenarios, the lack of response can cause problems since Action Cable currently does not implement a pong response.
00:10:02.160 It's important to consider that the ping interval is configured for every three seconds, and this might be resource-intensive for mobile devices.
00:10:11.640 Cheaper mobile devices with less powerful batteries may find this consumes substantial resources.
00:10:21.400 If an application uses WebSocket and has to wake every three seconds, it can waste CPU cycles.
00:10:29.960 Thus, a higher ping interval would be beneficial, but configuring this in Action Cable requires intricate patching on both the client and server sides.
00:10:38.560 Although it's possible, it’s a challenge to redefine the ping interval for any particular connection.
00:10:46.720 If the client setup feels restrictive, you might look into alternative clients. The AnyCable client, for instance, has a different architecture and is more extensible.
00:10:54.240 As I mentioned, I’m not discussing AnyCable today, yet the flexibility it offers can be interesting.
00:11:00.880 So, why can you write your own? Simply put, Action Cable is not just a tool or framework, but a communication protocol.
00:11:07.120 You can implement a client in any programming language as long as it adheres to the protocol.
00:11:11.920 If it implements the protocol, it will be compatible with your server. Similarly, you can build a custom server and use it with the Action Cable client.
00:11:19.840 The formal specification of the protocol is available on the AnyCable website, although it’s not yet incorporated into the Rails documentation.
00:11:36.160 For those interested in building a custom protocol, if your platform does not yet support Action Cable, the specification details a few types of messages.
00:11:47.680 The commands sent from the client to the server could differ but most of them are well-defined and specified.
00:12:01.680 However, it's important to note that the protocol lacks identifiers for sessions and messages.
00:12:15.440 This absence complicates implementing features like presence tracking or history support.
00:12:21.440 We also lack proper acknowledgments for performed actions, which is crucial since if there is an issue in processing commands, clients do not receive feedback.
00:12:36.640 This leads to situations where a client might continue operations without knowing a failure occurred.
00:12:46.760 Consider this synthetic example: if we create a subscription and then unsubscribe immediately, the server may process these commands in an unpredictable order.
00:13:00.480 Will the client be subscribed in the end? To find out, we can emulate this scenario using a tool called WebSocket Director.
00:13:09.440 This tool allows you to write scenarios in YAML, defining messages you want to send and expect to receive.
00:13:23.120 I ran a scenario with ten clients and noticed that not all were subscribed by the end. Eight succeeded but two clients failed, revealing a race condition problem.
00:13:35.440 This inconsistency demonstrates how Rails addresses the exception handling issue by trying to subscribe indefinitely until a rejection or confirmation is received.
00:13:47.520 This can lead to server resource wastage, processing unnecessary commands that may have been already fulfilled.
00:14:01.680 Moving on, let's explore the lifecycle of WebSocket events. First, the connection establishes between client and server.
00:14:16.800 Then, the underlying socket object is hijacked, allowing Action Cable server to manage it as a single object.
00:14:30.280 Once the connection is established, it issues the "open" event, entering the main executor part of the Rails server that handles WebSocket communication.
00:14:46.720 A worker pool takes over this process using a queue of tasks for concurrent processing without blocking.
00:14:59.040 Each unit of work is executed within the Rails executor context, which becomes essential throughout the process.
00:15:07.280 Different callbacks are invoked for different WebSocket events, like connection opens translating to connection establishes.
00:15:20.920 This includes processing incoming messages and handling disconnections in a similar manner.
00:15:35.520 Understanding the worker pool's behavior is crucial when shared resources like a database pool are involved.
00:15:45.520 If you use Puma with multiple threads and have Action Cable within the same process, your total database pool size must factor in both components.
00:15:57.760 Failure to scale correctly could lead to contention and slower responses for Action Cable callbacks.
00:16:04.960 As the connection object lives throughout the socket's lifetime, you may want to set current attributes during the connect callback.
00:16:21.680 However, beware, the executor will reset everything at the end of its run if context isn't properly established.
00:16:34.240 The rescue context is different than the connection's lifetime, so considerations must be made during each interaction.
00:16:46.800 We also need to consider the lifecycle callbacks and how incoming messages are broadcasted.
00:17:00.080 When a message is sent out, it goes through an internal executor and reaches the client, but what happens if we need to ensure ordering?
00:17:16.720 The queues can quickly become more complex if we try to ensure the order due to concurrent processing!
00:17:29.760 We need to account for how messages may become non-sequential and non-deterministic depending on the processing pipeline.
00:17:40.800 Whether the order of delivery is maintained becomes an important problem.
00:17:55.600 Another issue arises when we subscribe to a stream and deliver a message immediately — did it arrive?
00:18:06.680 I encourage you to write a test scenario and observe how this behaves because you might be surprised by the outcomes.
00:18:19.640 Now, let’s shift to the application level and discuss Turbo since this is part of the Hotwire track.
00:18:30.240 Turbo Streams serve as another abstraction that wraps the Action Cable black box, which compresses the concepts even further.
00:18:42.160 You don’t even have to write any Action Cable code; just by adding HTML elements to your page, Turbo takes care of the underlying Action Cable setup.
00:18:54.800 For instance, by adding an HTML element like "turbo-stream-source," a connection and subscription to Action Cable is created automatically.
00:19:07.680 Turbo streams let you send broadcasts and appropriately render updates when necessary, all seamlessly handled.
00:19:19.200 Whenever the Turbo element is removed from the page, the unsubscribe command is issued, helping to maintain an efficient connection.
00:19:34.960 However, an important thing to consider is that even without Turbo stream elements present, the connection remains active.
00:19:47.360 This can lead to clients connecting without genuinely needing to and unnecessarily consuming server resources.
00:20:01.440 Unfortunately, there are situations where executing subscribe and unsubscribe callbacks may occur frequently due to Turbo's navigation.
00:20:15.720 This could happen when Turbo stream elements are frequently added and removed from the DOM tree, resulting in excessive command processing.
00:20:30.280 This leads to the timing issue with the subscribe and unsubscribe commands. As a result, it becomes necessary to add a guard mechanism in Rails.
00:20:47.360 This mechanism is in place because Turbo experiences this frequent navigation workflow.
00:21:00.240 Additionally, managing subscriptions using HTML has its challenges.
00:21:10.680 Developers might not have a clear view of how many Turbo streams exist on a page, especially when deeply nested within partials.
00:21:23.760 There exists a concern regarding excessive Turbo streams without sufficient control.
00:21:33.080 On that note, I've addressed this question in our newsletter called "AnyCable Monthly", which discusses news and updates related to real-time development.
00:21:49.280 Each issue features insights on various topics, including whether having too many streams poses a real issue.
00:22:04.800 To understand this, we need to consider the reconnection scenarios when the monitor attempts to reconnect to the WebSocket.
00:22:18.240 When the Rails server starts, all connections are terminated, leading each to attempt reconnection automatically.
00:22:35.360 With numerous clients, server load is determined by the number of connections and channels subscribed to.
00:22:46.320 As connections increase, the workload on the server's queue rises, compounding the pressure.
00:22:56.120 This challenge is exacerbated when confirmations from the server slow down processing periods.
00:23:08.840 The unintended consequence is a potential server bottleneck, burdened with repeated commands that may no longer be needed.
00:23:21.480 This situation is reminiscent of a classic software performance issue known as "Thundering Herd," or as I also refer to it in this context: "Connection Avalanche."
00:23:34.960 It’s a significant concern for Rails developers to account for, especially during high traffic events.
00:23:47.960 To mitigate this, we should arrange reconnections to happen at staggered intervals rather than simultaneously.
00:24:04.680 In Rails 7, this has been addressed to alleviate the previous issue in Rails 6.
00:24:17.240 Moreover, it's best practice to merge subscriptions and use fewer channels to optimize responsiveness.
00:24:32.560 You can also serialize subscription requests so they follow a one-at-a-time process rather than being sent all at once.
00:24:46.640 This approach dramatically reduces the server's workload, ensuring that action cable commands are both efficient and effective.
00:25:02.480 Finally, if you anticipate large amounts of traffic from both WebSockets and HTTP, consider shutting up clusters to separate the workloads more efficiently.
00:25:16.960 This separation counterbalances Ruby’s GIL, allowing for better resource utilization.
00:25:30.640 Moving towards using an alternative like AnyCable could address many of these concerns, preserving connection state across deployments.
00:25:43.920 That said, it's the beginning of understanding how to tackle these challenges for Action Cable developers.
00:25:56.240 As we conclude, remember that the Avalanche problem can pose a significant challenge when faced with multiple connections.
00:26:05.520 Understanding the internal workings of Action Cable and these core concepts helps in preventing and handling potential issues.
00:26:20.480 I hope this talk was enlightening and will assist you in navigating the intricacies of Action Cable.
00:26:30.480 I'm out of time, but please feel free to ask any questions or reach out after this session.
00:26:39.360 Thank you all for your time, and I hope you found this presentation valuable. Enjoy the rest of your day!
00:27:05.440 Thank you, everyone!
00:27:11.040 It was a pleasure to present.
00:27:14.520 Have a great day!