Ruby YJIT at Underdog
Jerrod Carpenter
Posted on April 22, 2023
Here at Underdog, many of our projects are Rails services. So when Ruby 3.2.0 was released, we were eager to put it through its paces and see how much performance could be gained with this latest version! In this post, we'll discuss some critical changes in the release and how they affect performance.
Per tradition, Ruby 3.2.0 was released this past Christmas. By all accounts, this is a step forward for the language, and with it came the following optimizations:
Shopify has taken a leadership role in pushing Ruby forward — this release is the proof. They were running YJIT in production days before the release, and given that level of rigor, we were comfortable pushing 3.2.0 to our own production environment after some brief testing in our staging environment.
How We Upgraded
We used Ruby’s official Debian-based images for our application containers when we decided we wanted to enable YJIT. Unfortunately, YJIT support was not available in those images at the time, so we opted to switch our container images to be based on Ruby’s Alpine-based images instead. We were using the Debian-based images for stability's sake. However, we had already run into issues with other out-of-date packages due to Debian's general philosophy on this topic, so switching to Alpine gave us additional benefits.
The workload here was small. All we had to do was change our Dockerfile, update some system-level dependency names between the two distros, and then add the RUBY_YJIT_ENABLE
environment variable to our application containers’ Kubernetes manifests. Once we flipped that value to 1
via deployment, we were able to exec into a container and confirm YJIT was enabled with ruby —-version
.
Our Results
We had a few third-party dependencies we needed to update to support Ruby 3.2.0, but after those changes, things went smoothly. We had no issues or surprises over the weekend, and overall we saw roughly a 10% drop in response time:
Our p50s saw about a 9% improvement, which grew to 12.5% at p98. We found this intriguing and consistent with how we expected this update to affect Rails. Our quickest APIs spend most of their time with database IO so there’s less “Ruby time” to optimize, whereas our worst-performing APIs are often doing more logic in Ruby or serializing larger objects. We expected to see the most significant improvement in these more Ruby-intensive requests, and backed this up by cherry picking an API we knew was Ruby intensive.
Below is our Drafts API that serializes draft entries and picks. It is more data than we should be serializing, and running a little more logic than it should be, but it’s been below our SLOs so we have yet to optimize.
Before | After |
---|---|
This improvement is staggering. In this comparison, you can see that the improvement was greatest for the worst-performing scenarios — which is amazing!
At Underdog, we also process stats and grade contests with background jobs via Sidekiq. We were curious if our job execution times improved as well… and absolutely, it did!
The p90 looks a little strange, but other than that, we saw a significant improvement. This makes sense — we expected enhancements here as well because it’s common to have Ruby-heavy processes turned into background jobs.
Conclusions
It is so important to keep projects up-to-date to take advantage of the newest releases, especially considering the free performance and how easy it was for Underdog to upgrade to 3.2.0. For some time, Ruby has been promising performance upgrades with mixed results through the lens of a Rails deployment, but they took a huge step forward with 3.2.0. The Ruby Core team hit it out of the park, and we’re lucky to have such a strong team behind the language.
Want to learn more about our team here at Underdog? Check out our culture and careers page here and see how we are changing the game!
Posted on April 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.