Open-source project
Shopify/autotuner avatar
Shopify/autotuner

Shopify Autotuner: garbage collector advice for Rails, one experiment at a time

Get suggestions to tune Ruby's garbage collector

619 stars14 forksRubyMIT

At a glance

What is it?
Autotuner is a Rack middleware gem from Shopify that watches the Ruby garbage collector between requests and proposes tuning values for faster boot, warmup and response times. Its own documentation is blunt about the catch: the suggestions are hypotheses, and applying them blindly is not recommended.
Who is it for?
Adopt Shopify Autotuner if you run a Rails app on a tuned Ruby, you can afford to keep one instance group under observation for a week, and you want starting points for GC tuning rather than a fixed configuration. Do not adopt it if your Ruby is old, if you have no way to compare two instance groups at the same time, or if you intend to apply its output automatically.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gem is a Rack middleware that reads GC::stat between requests

Autotuner is a tool for tuning the garbage collector of a Rails app, and it is built as Rack middleware. The mechanism is described plainly: it integrates into Rack and collects data from the garbage collector between requests, then uses that data to suggest tuning values aimed at faster bootup, warmup and response times.

That is a specific and fairly narrow job. It does not profile your code, it does not tell you which line allocates the most, and it does not replace a memory profiler. It watches the collector's behaviour across real traffic and reasons about the GC parameters that govern when the collector runs and how much it grows the heap before it does.

The reason the gem is a middleware rather than a background thread or a rake task is that it needs per-request boundaries. A suggestion about heap growth only makes sense relative to the requests that caused the growth, and the middleware position is where those boundaries are.

The project lives under the Shopify organisation and is MIT licensed. It is a gem, not a service, so it runs inside your application process and needs no separate infrastructure.

Installation is a Gemfile line and one line in config.ru

The gem is added to the application's Gemfile with the bundle add command:

bash
$ bundle add autotuner

Then the middleware goes into the rack file, immediately above the line that runs the Rails application:

ruby
   use(Autotuner::RackPlugin)

The placement matters. The middleware has to sit above run(Rails.application) so that it wraps the request handling and can measure the garbage collector work that happens inside it. Putting it below the application means it never sees the requests it is supposed to measure.

After that, the behaviour is controlled from an initializer at config/initializers/autotuner.rb. The gem ships as autotuner.gemspec with the code under lib, tests under test, a Rakefile and a bin directory, and a .ruby-version file that pins the Ruby the project itself develops against.

The initializer is where you turn it on and decide where the output goes

The initializer example in the README shows the three settings that matter, and the first one is the switch itself:

ruby
   # Enable autotuner. Alternatively, call Autotuner.sample_ratio= with a value
   # between 0 and 1.0 to sample on a portion of instances.
   Autotuner.enabled = true

   # This callback is called whenever a suggestion is provided by this gem.
   # You can output this report to your logging pipeline, stdout, a file,
   # or somewhere else!
   Autotuner.reporter = proc do |report|
     Rails.logger.info(report.to_s)
   end

The reporter callback is required whenever the gem is enabled, and it receives an instance of Autotuner::Report::Base. Calling to_s on it produces a string with the instructions and recommendations, so where you send that string is entirely your choice: the logging pipeline, stdout, a file, or somewhere else.

A third setting is optional and forward-looking. The metrics reporter emits per-request measurements that you can push to your observability service:

ruby
   Autotuner.metrics_reporter = proc do |metrics|
     # stats is a hash of metric name (string) to integer value.
     metrics.each do |key, val|
       StatsD.gauge(key, val)
     end
   end

The comment recommends sending that data to an observability service, and it is explicit about the metric type to use, since you want to calculate averages and percentiles. On Datadog that is the distribution type, on Prometheus the histogram type. A gauge, as in the example, cannot give you the 99th percentile, so the example is illustrative rather than complete for the stated purpose.

Five metrics leave the middleware after every request

The emitted metrics table is short and every entry is well defined. diff.time is the time spent doing garbage collection during the request, diff.minor_gc_count is the number of minor collections, diff.major_gc_count is the number of major collections, and heap_pages is the number of heap pages in use after the request. The first four are produced by GC::stat, and the fifth, request_time, is the total duration of the request.

All of them are passed to the metrics_reporter callback after each request, as a hash of metric name to integer value. That is the raw material of the tuning suggestions, and it is also the baseline you need before you can tell whether any suggestion helped.

The important detail is that these are per-request deltas for the collection counters, not cumulative process totals. diff.time and diff.minor_gc_count describe what happened during this request specifically, which is what makes them comparable across requests with wildly different workloads.

The configuration section also documents a debug reporter, which is called periodically with a hash of heuristic name to debug message. The README is clear that regular users do not need it and that it exists for debugging the internal heuristics.

Sampling is per instance, not per request

Autotuner.enabled turns the gem on or off, and defaults to false. When it is disabled, no data is collected and no suggestions are given.

The alternative is Autotuner.sample_ratio, which takes a value between 0 and 1.0 and enables the gem on a portion of instances. The configuration notes make a distinction that changes how you think about it: this does not sample requests, it samples the portion of instances that have autotuner enabled, and for those instances it is enabled for all requests. The two settings are alternatives, and you are told not to configure Autotuner.enabled when you use sample_ratio.

So the sampling is a fleet-level switch rather than a request-level one. A machine that is in the sampled group reports on every request it serves, and a machine that is out of the group reports on none. That design is what makes the side by side comparison the README recommends possible, since the group membership is stable for the life of the process.

It also means the cost of running the gem is not proportional to the traffic you want to learn about. To get statistics from five percent of your fleet, that five percent pays the full overhead on all of its traffic.

The README tells you not to apply the suggestions blindly

The section on experimenting with tuning suggestions is the most valuable part of the documentation, and it starts with an admission: the gem aims to analyse your traffic comprehensively, but not all of the suggestions it gives will be perfect, and some may produce undesired outcomes. The stated recommendation is not to apply suggestions blindly but to use a scientific approach.

The steps it outlines are specific. First, before applying anything, collect system metrics from your app and send them to your observability service so you can measure average, 50th, 99th and 99.9th percentile data, and the metrics_reporter is the way to collect GC time, major and minor GC cycle counts, request time and heap pages allocated.

Second, establish an experimental group of machines or containers in production, and assign a random number to each at boot to decide its group. The reason given is that response times and the state of the garbage collector are highly variable, so comparing two groups at the same time is more reliable than comparing across periods with different traffic patterns. The suggested experimental group size runs from 5 percent for high traffic apps to 50 percent for low traffic ones.

Third, apply the suggestions one at a time in that group, and observe over a few days to a week, covering warmup after a deploy and long quiet stretches such as a weekend.

Some suggestions will trade one metric for another

The README is honest about outcomes, and two cases are called out. Some suggestions may improve average response time at the cost of worse extremes, the 99th or 99.9th percentile, and whether that trade is worth taking is left to you. Others may simply make performance worse, and in that case the advice is to discard the suggestion and move on to the next one.

That is the honest shape of this tool. It proposes changes to a collector whose behaviour has complicated effects, and the effect of a given change depends on your traffic mix, your memory budget and your latency objectives. A suggestion that looks good on the average can be a bad idea for a service with a hard tail latency target, which is the usual reason those percentiles are called out in the first place.

The last step in the documented loop is to promote a suggestion to the default group only after you have seen a positive result in the experimental group, and then to experiment with the next suggestion. One change at a time, measured against a live control group, is the whole method, and the gem is the thing that proposes the candidates.

The alternative worth naming is tuning the garbage collector by hand, reading the Ruby documentation and adjusting environment variables based on your own profiling. That route is slower to start and gives you full control, and it is what you end up doing anyway for the settings the gem does not cover.

Editorial conclusion

Adopt Shopify Autotuner if you run a Rails app on a tuned Ruby, you can afford to keep one instance group under observation for a week, and you want starting points for GC tuning rather than a fixed configuration. Do not adopt it if your Ruby is old, if you have no way to compare two instance groups at the same time, or if you intend to apply its output automatically. Before you enable it, wire up Autotuner.metrics_reporter so you have baseline percentiles, and read the configuration section, because Autotuner.enabled and Autotuner.sample_ratio are mutually exclusive ways of turning it on and the reporter callback is mandatory.

Frequently asked questions

How do I install and enable the Shopify Autotuner gem?

Run `bundle add autotuner` to add it to the Gemfile, then add `use(Autotuner::RackPlugin)` in your config.ru immediately above `run(Rails.application)`. In an initializer set `Autotuner.enabled = true` and provide the required `Autotuner.reporter` callback.

What does Autotuner.sample_ratio do?

It sets the portion of instances where autotuner is enabled, from 0 for no instances to 1.0 for all of them. It samples instances rather than requests, so an enabled instance reports on all of its requests, and you should not configure Autotuner.enabled when you use it.

Which metrics does the Shopify Autotuner emit?

After each request the metrics_reporter callback receives diff.time, diff.minor_gc_count, diff.major_gc_count and heap_pages, all produced by GC::stat, plus request_time for the total duration of the request. The first four describe the garbage collection work during that request.

Should I apply Autotuner suggestions straight to production?

The README says not to apply them blindly. It recommends collecting baseline metrics first, running an experimental group of 5 to 50 percent of instances at the same time as a control group, and applying one suggestion at a time while watching percentiles over several days.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. Shopify/autotuner on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shopify-autotuner.svg)](https://hysenlabs.com/projects/shopify-autotuner)

Was this useful?