Running Ruby 3.2's YJIT in Production at Discourse
At Discourse, we have been eager to adopt YJIT ever since Shopify’s Ruby & Rails Infrastructure team declared it production-ready. After witnessing promising local benchmarks, we began running our production Rails applications with Ruby 3.2’s YJIT enabled on selected clusters in early May 2023. We then spent some time measuring its real-world performance. We are excited to share the positive results we observed. Based on these findings, we have now enabled YJIT across all of our hosting, and self-hosters can opt-in to do the same.
Discourse's YJIT Experiment
While local Discourse benchmarks have shown promising results, we wanted to verify these outcomes in our production environment before deciding to enable YJIT across our hosted instances. To accomplish this, we conducted a YJIT experiment in our production environment, selecting two production clusters and dividing each cluster into two groups: one with instances running YJIT enabled and the other without. This division was easily achieved since we rely on Hashicorp Nomad for orchestrating our deployments. All we needed to do was specify an additional group of tasks with the RUBY_YJIT_ENABLE environment variable within our existing Nomad job and then had our load balancer allocate requests between the groups using the round-robin algorithm.
Subsequently, we introduced a new http_application_duration_seconds Prometheus metric in the discourse-prometheus plugin that measures the amount of time spent executing Ruby code during a request. This metric is calculated by taking the total request time and subtracting the time spent executing SQL queries and Redis commands during a request. This allowed us to isolate the impact that YJIT had on running our Ruby application code without any of the noise introduced by executing SQL queries and Redis commands.
With those changes, we were able to easily visualize and measure the impact of YJIT on our production workload with graphs like the following:
After running the experiment for 24 hours, we consolidated the results, and this was what we saw on our free and business clusters that serve our free and business tier customers, respectively.
On our free cluster, enabling YJIT reduced the median time spent executing Ruby code for viewing topic lists and individual topics in the range of 15.8% to 19.6%.
In terms of memory, we observed an approximate 18% increase in memory usage for our Docker containers running the Unicorn web server with six worker processes.
We noticed similar speedups on the business cluster as well, where the median request time for viewing topic lists and individual topics decreased in the range of 12.9%to 19.3%. Memory usage increase was quite similar to the free cluster for the business cluster where we observed an increase of approximately 16.7% for the Docker containers running the Unicorn web servers with 10 worker processes.
Given the positive results we observed, we gained the confidence to enable YJIT across our hosting platform since the only trade-off is the increase in memory usage, which is not a concern as we have ample memory available on our servers.
Hosted Discourse Users
If you are a hosted Discourse user, you will be pleased to know that all hosted instances are now running with YJIT enabled which means faster response times leading to a better browsing experience for your users.
For those who self-host Discourse, you can enable YJIT by adding the `enable-ruby-yjit.template.yml` to your container definition file. Example:
templates: - 'templates/enable-ruby-yjit.yml'
Please note that we have opted not to enable YJIT for self-hosted instances as a default due to the increase in memory usage. Self-hosters enabling YJIT should monitor memory usage to ensure that your servers can handle the additional memory requirements.
Enabling Ruby 3.2’s YJIT in production at Discourse has led to significant performance improvements for our users. We are excited about the positive impact this will have on Discourse communities. Finally, we would like to extend our thanks to the Ruby core team, the YJIT team at Shopify and anyone who has contributed to YJIT in one way or another.