BlogsShopifyRuby Execution Models and Threading

Ruby Execution Models and Threading

Ruby Execution Models and Threading

5
posts
2019–2023

Shopify has evolved its Ruby execution models and threading strategies to optimize garbage collection performance within its monolithic application. Initial efforts focused on reducing the latency impact of the Ruby garbage collector (GC) by analyzing and tuning its behavior. Key developments include addressing major GC triggers like `:oldmalloc` and `:nofree` by adjusting GC limits and pre-allocating heap slots. The introduction of Ruby 3.2's variable width allocation necessitated a finely-grained approach to pre-allocating heap slots across different size pools, which was initially achieved through a 'hack' and later integrated as a proper Ruby feature. These optimizations have led to significant reductions in GC-related latency, particularly for p99 and tail-end requests.

2023

Adventures in Garbage Collection: Improving GC Performance in our Massive Monolith - Shopify

4/6/2023

This post details the technical steps taken to improve Ruby garbage collection (GC) performance in Shopify's monolith. It outlines the problem of GC-induced latency, explains Ruby's generational, incremental, mark-and-sweep GC, and identifies the primary triggers for major GC marks: `:oldmalloc` and `:nofree`. For `:oldmalloc`, the solution involved setting a very high `RUBY_GC_OLDMALLOC_LIMIT` to effectively disable this heuristic. For `:nofree`, the issue was addressed by significantly increasing `RUBY_GC_HEAP_INIT_SLOTS` and, due to Ruby 3.2's variable width allocation, implementing a custom pre-allocation strategy for different slot sizes via environment variables and a temporary `config/boot.rb` hack, which was later upstreamed as a Ruby feature. The post also includes detailed metrics and graphs showing the impact of these changes on GC latency.

2022

Optimizing Ruby’s Memory Layout: Variable Width Allocation - Shopify

12/25/2022

This post details the Variable Width Allocation project for CRuby, which optimizes the memory layout in the garbage collector. It introduces size pools for dynamic object sizing (40, 80, 160, 320, 640 bytes), increasing heap page size to 64 KB, and enhancing resizing and compaction mechanisms. The project covers classes, arrays, and strings, aiming to improve data locality, reduce malloc overhead, and enable advanced GC research.

To Thread or Not to Thread: An In-Depth Look at Ruby’s Execution Models - Shopify

5/31/2022

This post details the mechanics and reasoning behind the popularity of threaded Ruby servers, explaining the concepts of static and processing memory and how they contribute to overall memory usage. It introduces the Copy on Write (CoW) mechanism and its implications for memory sharing between forked processes. The post also highlights the importance of accurate memory metrics, explaining why RSS can be misleading and introducing Proportional Set Size (PSS) and detailed Linux smaps_rollup data. It provides practical advice for improving CoW efficiency, such as using `preload_app` and leveraging Rails' `eager_load_namespaces`. Finally, it presents the case for process-based servers, emphasizing their clean timeout mechanism for handling problematic requests.

2019

Sam Saffron AMA: Performance and Monitoring with Ruby - Shopify

11/5/2019

This post contributes to the thread by highlighting the critical need for improved memory profiling tools in MRI, comparing them unfavorably to Java and .NET. It emphasizes that the primary performance bottleneck for Discourse is memory, not CPU, and advocates for the Ruby community to focus on solving real-world production pain points, especially memory-related issues. The post also touches upon the importance of clear goals and breaking down complex performance problems into manageable steps, using memory leak detection as an example.

How to Write Fast Code in Ruby on Rails - Shopify

10/8/2019

This post details strategies for writing fast code in Ruby on Rails, covering Active Record performance (lazy evaluation, selecting less, query cache, indexing), Rails performance (caching, throttling with rack-attack, background jobs with Active Job, dependency management), and Ruby performance (sparing use of metaprogramming, understanding O(n) vs O(1) complexity, minimizing object allocations with `uniq!` vs `uniq`). It also mentions Large Hadron Migrator (LHM) for database migrations.