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.