RubyKaigi Takeout 2021

Ractor's speed is not light-speed

Ractor is the new feature, introduced in Ruby 3.0, to run Ruby code on multiple CPU cores. But unfortunately, Ruby 3.0 is not fully ready for actual workloads. This session will show how we can improve web-app performance by Ractor, and what we have to do to run our web apps on Ractor.

RubyKaigi Takeout 2021: https://rubykaigi.org/2021-takeout/presentations/tagomoris.html

RubyKaigi Takeout 2021

00:00:03.199 Hello everyone. The title of this session is "Ractor's Speed is Not Light-Speed". This talk is about Ractor, a new feature of Ruby 3.0, and its application in web applications.
00:00:10.320 I am Satoshi Tagomori speaking. I am a freelance technical consultant since this summer, after leaving a company called Treasury Data.
00:00:12.000 I am also a hobby Ruby programmer and a member of the Sakai Ruby Meetup, which is a regional Ruby meetup near Sakai. Currently, it is an online meetup, and we welcome new people to the Sakai Ruby community. You can easily join us online.
00:01:06.560 Today, I will talk about Ractor, an experimental feature of Ruby 3.0 designed to run Ruby code in parallel on multiple CPU cores. This is a long-anticipated feature, and RubyKaigi has three sessions focused on Ractor. The first session, about parallel testing with Ractors, was held on Day 1 yesterday, and I am presenting this session. The other session, titled "Ruby React Quick," will take place on Day 3 tomorrow.
00:01:35.840 Ractor's feature allows the execution of Ruby code in parallel across multiple CPU cores by managing objects via Ractors. Each Ractor operates independently, enabling parallel runtime on several CPU cores. Managing state concurrency is essential, particularly in addressing multi-threading problems. Ractor divides the object space into multiple Ractor object spaces, meaning we do not have to worry about solving or managing multi-threading issues when using Ractor.
00:02:44.640 Additionally, we can move objects between Ractors. Once an object is moved, it becomes invisible from the original Ractor, which is part of the object management strategy. Ractors can share shareable objects, which include modules, classes, and application code. If marked as shareable, these definitions like constants and configurations need to be managed.
00:03:19.920 What does "shareable" mean? Shareable objects must be marked using the `make_shareable` method of the Ractor class. These objects will typically be frozen. However, a shareable proc is a special case. While the make_shareable method isolates proc objects, isolated procs are not frozen. If you call binding methods on an isolated proc, it raises an ArgumentError, and all referred values from the proc must also be shareable.
00:04:34.880 For example, if we have a usual proc on the left side of the comparison, and we assign a value of 1 to x, invoking p1 will return x + 2. After assigning 5 to x, p1 will return 7 since x has been updated due to its reference. However, for an isolated proc, when we assign 1 and mark it as shareable, the context does not change even when x is updated. Hence, the value returned by p2 will still be 3, as it remains unaltered in context.
00:05:31.280 Moreover, isolated procs cannot refer to unshareable objects, as illustrated in the first example. If s1 is a mutable string, it cannot be a shareable object. Attempting to call make_shareable on it will raise an isolation error. However, if s2 is a frozen string, then p4 can be marked as a shareable isolated proc.
00:06:41.440 The main point about Ractor revolves around speed because Ractor is designed to run Ruby code in parallel across multiple CPU cores. We expect a performance enhancement that can be several times faster if the last operation uses n CPU cores.
00:07:12.960 The key question is whether this holds true for web applications, and if it truly makes web applications faster than processes created via fork. Is my web application running faster with Ractor than with the current deployment? Speed is crucial, so we need an experimental application server that utilizes Ractor.
00:08:02.640 This server should be a Rack application server that supports Rack application protocols between the server and applications. It needs to handle processing workers, accept established connections, read requests, run Ruby Rack applications, and write responses, all while maintaining speed and optimal performance with minimal overhead.
00:08:43.760 I apologize for my poor English pronunciation. This server, called "Write Speed," is an application server that I developed myself. Currently, it has limited features as a Rack application server, but we can start running Rack applications on it.
00:09:19.600 Now let me show you a demonstration of Write Speed. This is a very simple Rack application that will respond with a status 200 and a string "OK" to any request. Let's execute our web request server with this application. I will also run this application using Write Speed.
00:10:24.960 It started running with eight workers. The server is experimental, and it's listening on all ports. Sending a request to that endpoint returns a good response. We also have a Rack server, so we can learn and run Sinatra applications. However, I couldn't resolve all issues relating to Sinatra due to many unshareable objects and settings that involve configurations, which leads to complications.
00:11:06.640 Although I have patched some elements, right now Sinatra applications cannot run on Write Speed. I have a layered application that is too massive and involves many instances of unshareable objects, making it unsuitable for Write Speed. We need many patches on Sinatra and other Ruby frameworks to ensure compatibility with Ractor.
00:12:08.800 The significant challenge is ensuring that our applications can integrate with Ractor without issues. I'd like to demonstrate the benchmark in terms of traffic, as speed is crucial. When executing a benchmark tool using Write Speed with a single connection and thread, the server processed 5000 requests per second, which is impressive.
00:13:02.560 Next, I will run a similar traffic test using multiple connections and threads to gauge performance under load. However, unexpectedly, a segmentation fault caused by the Ruby runtime occurred, indicating that Ractor is still in the experimental phase and that there are issues yet to be resolved.
00:14:01.600 There are numerous issues with running web applications on Ractor that need resolution, and I hope the crash I experienced will be fixed soon. I will investigate the root cause after this session. One issue arises from accessing module or class instance variables from non-main Ractors, which causes isolation errors.
00:14:32.960 Many frameworks like Sinatra and Rails rely heavily on these instance variables, which makes it a significant limitation in terms of Ractor compatibility. Therefore, it's advisable to avoid using these patterns in production-level code until a definitive solution emerges.
00:15:16.640 Furthermore, directly using defaults tied to instance variable behavior is problematic in Ractors. For instance, methods such as JSON.dump cannot be used in non-main Ractors currently. These challenges need to be addressed, and while there are no fixes yet, there are plans to allow for the access of module or class instance variables in the future.
00:17:00.800 Moreover, accessing unfrozen or unshareable constants raises exceptions. Shareable constants can be accessed but must still conform to the rules around shareability. Values marked as mutable or containing references to unshareable objects can lead to breakdowns.
00:17:19.920 My recommendation is to use frozen strings and to ensure your string literals are immutable. Using new forms of magic comments to define arrays and hashes can help streamline development while maintaining compatibility with Ractor. Overall, let's develop Ractor-safe code while improving performance.
00:18:02.800 To summarize, there are many opportunities for development within Ruby core and major libraries, which could lead to improved performance in Ruby's web applications. Contributions are welcome, and we have spaces for patches across all frameworks.
00:18:54.240 In conclusion, we must focus on writing patches for Ruby core and enhancing Ractor's compatibility with frameworks and libraries to make it production-ready in the future. Thank you very much for your attention.