Ruby on Rails

Summarized using AI

The pitfalls of realtime-ification

Vladimir Dementyev • May 17, 2022 • Portland, OR

In the presentation titled "The Pitfalls of Realtime-ification," Vladimir Dementyev discusses the challenges and intricacies involved in developing real-time applications with Ruby on Rails, particularly focusing on Action Cable and Hotwire. He begins by highlighting the evolution of developing reactive web applications using Rails, emphasizing that modern applications must support real-time functionality without constant user intervention.

Key Points Discussed:

  • Real-Time Web Applications: Real-time applications differ from traditional request-response models by allowing continuous data flow between the client and server via WebSockets, which can create unforeseen challenges.
  • Early Adoption and Tools: Although Action Cable introduced real-time capabilities to Rails, its adoption was slow initially. The introduction of Hotwire has simplified the process, making it easier to incorporate real-time features using just HTML.
  • Common Pitfalls: Dementyev identifies specific pitfalls in developing real-time applications:
    • Personalization Issues: Broadcasting the same data to multiple clients can lead to inconsistencies in user experiences, especially in scenarios like chat applications.
    • Delivery Guarantees: Action Cable operates on a "fire-and-forget" model, meaning messages may not reliably reach clients, especially in unstable network conditions, leading to potential data loss.
    • Concurrency Challenges: When users connect from multiple devices, maintaining state consistency becomes complicated. Developers must handle synchronization issues carefully.
    • Performance Limitations: As the number of connected clients grows, scalability and performance become critical concerns, requiring optimizations to avoid creating bottlenecks.
  • Solutions Provided: The presentation concludes with possible solutions to these pitfalls, such as implementing improved delivery guarantees, history restoration for missed messages, and leveraging client-side solutions for better data integrity and user experience. Notably, he presents the idea of a custom 'Turbo History Stream' to handle message consistency more effectively.
  • Key Takeaway: Developers are encouraged to embrace the complexities of real-time web applications by understanding underlying mechanics and utilizing available tools like AnyCable for enhanced performance and reliability.

Dementyev’s insights serve as an essential guide for Rails developers, urging them to be aware of the intricacies of real-time functionalities to support the creation of robust applications successfully.

The pitfalls of realtime-ification
Vladimir Dementyev • May 17, 2022 • Portland, OR

Building realtime applications with Rails has become a no-brainer since Action Cable came around. With Hotwire, we don't even need to leave the comfort zone of HTML and controllers to introduce live updates to a Rails app. Realtime-ification in every house!

Switching to realtime hides many pitfalls you'd better learn beforehand. How to broadcast personalized data? How not to miss updates during connection losses? Who's online? Does it scale?

Let me dig into these problems and demonstrate how to resolve them for Action Cable and Hotwire.

RailsConf 2022

00:00:00.900 Hello everyone! Thank you for joining me today.
00:00:13.519 I've been a bit late. I tried to practice my talk, but I failed, so this is a bit of an improvisation.
00:00:22.260 Today we’re going to talk about real-time applications, particularly in relation to Rails.
00:00:27.480 I'd like to start by referencing the new homepage of Ruby on Rails because I wanted to find a good starting point for this talk.
00:00:39.960 The homepage has an interesting and catchy phrase: "Build amazing web applications. Rails has everything you need." While this is compelling, it doesn't answer the question of what makes an application amazing.
00:00:51.840 If you ask people on the street, many will say that amazing web applications should have a sleek UI or beautiful designs. While that's true to an extent, especially in this room, many would argue that they shouldn’t have any JavaScript.
00:01:04.140 In the modern web, however, one of the properties of amazing web applications is that they are reactive. You might wonder, what does reactive mean? I would define it by providing an anti-example: a non-reactive interface is one that forces users to refresh the page to see updates. In 2020, that is simply unacceptable.
00:01:24.920 A reactive application, on the other hand, sends updates to users immediately, without requiring any action on their part to receive information about changes happening in the system. We typically achieve this through some form of streaming functionality, often web sockets. Fortunately, Rails has supported this since version 5.0.
00:01:57.420 Real-timeification in Rails began with Action Cable approximately seven years ago. However, it wasn't highly adopted in the early days.
00:02:05.340 The real boost for real-time applications came about a year ago with the advent of Hotwire. Between these two significant events, we also saw the emergence of the Stimulus framework and its ecosystem, which enables building reactive Rails applications without JavaScript.
00:02:25.680 In this talk, we’ll primarily focus on Action Cable and touch a bit on Hotwire. About a year ago, I gave an online talk about new ways of building modern Rails applications. One key feature of these applications is the use of HTML through web sockets. I tried to convince you all that using Turbo with Rails was an amazing approach. You could simply include it in your Gemfile and have everything you need for free.
00:03:05.340 I have to admit that saying it would be free was misleading. You still need to think about how to implement this new way of doing things related to real-time applications.
00:03:38.519 My name is Vladimir Dementyev, and I work at Evil Martians. I specialize in Ruby and Rails as a principal backend engineer. I'm currently based in New York. If you want to talk about Ruby, Rails, or life on Mars, feel free to reach out. I've contributed to Rails, particularly around Action Cable, which I’ll discuss shortly.
00:04:02.879 It's worth mentioning that I also started a screencast series around Christmas where I explore the capabilities of Rails 7, Hotwire, and Action Cable. I’ll be using a demo application from these screencasts for demonstration purposes today, so you can learn more about it on YouTube.
00:04:58.860 Now, let’s move on to the main topic. What has changed in the shift to real-time applications? We used to write request-response applications—synchronous interactions where a client requests something from the server, and the server returns a payload.
00:05:10.979 In contrast, web sockets allow for bi-directional and continuous data flow. You cannot predict when the server will send you data; this opens up a lot of new delivery guarantees.
00:05:32.940 Although it remains HTTP under the surface, it's important to note that web sockets are quite different. Rails effectively hides these differences behind familiar APIs, providing channels for web sockets and controllers for request-response.
00:05:55.080 With Hotwire, you don’t even need to write client-side code; you're simply dealing with HTML while the magic happens behind the scenes. However, this can lead to several pitfalls, which we’ll explore.
00:06:24.660 Let's start with the first pitfall: personalization. In a request-response model, a client requests something, and the server generates a unique payload for that client.
00:06:58.320 In a publish-subscribe pattern commonly used in web sockets, the same payload is broadcast to multiple clients. This means everyone receives the same data, leading to questions around how to personalize content for individual users.
00:07:20.099 For example, if you want to show messages for the current user, how do you broadcast that to everyone but this one user? The problem lies in the fact that we don’t know about users in the same way we do when sending unique requests.
00:08:02.400 Typically, our messaging problem requires us to show messages for a particular user with a unique user interface tailored for them.
00:08:36.000 I've worked with junior Rails developers extensively, and I've seen common techniques they use to tackle these issues. Often the first attempt is to send a create message with an Ajax request and return data uniquely for the requesting user while allowing everyone else to receive updates via Action Cable.
00:09:15.060 The problem with this approach is that a user may be connected across multiple tabs or devices. This inconsistency can lead to issues. It's important to remember that the 'current user' isn’t necessarily the same as the current connected client.
00:09:50.079 Another problem is that Action Cable updates and Ajax responses can become unsynchronized, leading to race conditions and strange UI artifacts.
00:10:16.560 A common approach to mitigate this is to send data to all connected clients, which breaks the idea of a centralized broadcast system.
00:10:40.260 If we have many broadcast messages sent repeatedly to clients that aren’t connected, it creates unnecessary overhead. This issue is compounded when the ratio of connected users to total subscribers is low.
00:11:12.690 For situations where the number of subscribers is significantly greater than the number of connected users, sending many broadcasts creates a lot of redundant work.
00:11:54.030 When considering the Channel per user approach, we can discuss another concept. Channel per group, where you send data to groups based on roles or locales, can provide a better trade-off.
00:12:38.160 Using Hotwire, one can prepare streams based on locales and perform broadcasts to those streams quite easily—just ten lines of code can make your application support multiple languages.
00:13:12.720 Moving on to another pitfall: sending updates through web sockets often leads clients to perform Ajax requests for updated versions of this data.
00:13:30.360 In a Hotwire context, this could involve sending Turbo frames without content but with a source URL pointing to the data's location. Imagine if that was sent to thousands of clients in a chat application—potentially overwhelming the server.
00:14:17.640 Before we discuss potential solutions, I attempted a unified data context-free broadcast that needed some client-side enhancements.
00:14:57.060 In my blog post from a year ago, I described this more thoroughly with snippets of code showcasing client-side enhancements for managing message visibility.
00:15:19.500 Some existing tools readily adopt this concept. For instance, Basecamp employs data visibility attributes to hide delete buttons for messages that don’t belong to the current user.
00:15:44.580 While this approach solves the problem of scaling, it does require some amount of client-side logic.
00:15:58.260 I think there has to be a more cohesive way to build personalized updates without having to consider the difficulties of existing solutions.
00:16:16.680 When we reflect on Elixir and Phoenix, they manage to bypass this problem by not broadcasting data directly to clients. Instead, updates go through a server-side component that represents the current state.
00:16:41.880 However, with Action Cable we also have stream callbacks to help manage sending messages selectively per client.
00:17:03.839 Next we need to consider consistency or delivery guarantees.
00:17:34.920 Action Cable has a simple model where data is transmitted to connected clients without ensuring delivery. This means you're on a 'fire and forget' protocol, which can lead to issues when network conditions are poor.
00:18:15.240 For example, if you're underground with no mobile connection and try to chat, when you reconnect, you may not receive the messages others have sent. This can result in a lack of feedback if a user is waiting for responses.
00:18:54.780 After reconnecting, you'll want to restore the state, ideally by syncing back to the database for the missing messages.
00:19:35.400 Here’s where a series of potential fixes come into play. First, we need to have a reliable source of truth or data that we can restore from.
00:20:23.160 When reconnecting, you should always be able to request this data, even if you have a stable connection. This can become a complex process with many requests involved during data transmission.
00:21:07.440 We need to address how messages are sent before we finalize the reconnection process. Messages could be sent at different points, possibly creating inconsistencies.
00:21:50.040 We could implement a state machine behavior for our subscriptions to track messages sent during a disconnect.
00:22:32.520 To address the issue of delivery guarantees, we can also implement a client-side solution that keeps track of which messages have been viewed, simply dropping duplicates.
00:23:20.310 While this can solve various issues, the overhead of additional code makes it cumbersome, particularly with large applications.
00:24:10.740 Moving on, we should improve the consistency of transactions within Action Cable.
00:24:56.760 Consistent updates can be achieved by extending the Action Cable protocol to include information that identifies the position of a message.
00:25:32.400 This change would allow you to implement history functionality seamlessly without worrying about it from the perspective of your Ruby application.
00:26:08.760 It’s essential to build a reliable process for managing presence tracking as well, determining who is online and when they join or leave.
00:26:58.380 Using a dedicated online channel can often lead to more complications as people may connect from multiple devices.
00:27:38.580 A more scalable solution would involve using a counter-based system to track active users.
00:28:15.240 This can be supported by implementing heartbeat mechanisms so clients regularly signal their activity to the server.
00:28:55.020 However, we need to explore built-in support for presence tracking within Action Cable, making it easier to manage this feature without additional work.
00:29:22.680 It’s crucial to understand that the responsibility for maintaining consistency should lie well within the websocket server, not the client model.
00:30:09.480 As we wrap up, I want to highlight that transport issues still persist in some scenarios, particularly with applications that restrict websocket usage.
00:30:45.540 If you're interested in ensuring robust real-time capabilities, I recommend exploring existing solutions, such as Action Cable, while remaining aware of its limitations.
00:31:23.400 Real-time application development does require a mindset shift. Understanding the intricacies enables you to build amazing web applications.
00:32:00.360 To finalize, if you want to explore some additional opportunities and gain access to advanced features, I'd suggest checking out the Evil Martians booth.
00:32:38.300 We have a special offer for conference attendees regarding our pro version and are happy to answer any questions.
00:33:08.760 Thank you for your time today! Let's make the real-time web in Rails better together.
00:33:45.890 And just a heads up—I have some stickers for you all!
Explore all talks recorded at RailsConf 2022
+68