<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Speedshop - Ruby on Rails performance consulting</title>
    <description>Speedshop is a Ruby on Rails performance consultancy that optimizes the full stack - frontend, backend and environment - to generate revenue and cut scaling costs for businesses on Rails. Fast sites are profitable sites. Speed is a feature.
</description>
    <link>https://www.speedshop.co/</link>
    <atom:link href="https://www.speedshop.co/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 17 Oct 2026 00:48:14 +0000</pubDate>
    <lastBuildDate>Sat, 17 Oct 2026 00:48:14 +0000</lastBuildDate>
    <generator>Jekyll v4.3.3</generator>
    
      <item>
        <title>Organization for Transformative Works Performance Audit</title>
        <description>&lt;p&gt;What follows is the complete text of an audit report I produced for the Organization for Transformative Works, a non-profit which was for many years run one of the largest fan-fiction archives on the Internet.&lt;/p&gt;

&lt;p&gt;This was produced as part of my &lt;a href=&quot;https://www.speedshop.co/retainer.html&quot;&gt;Ruby on Rails performance retainer service&lt;/a&gt;. Since the app is open source, OTW has kindly agreed to let me publish the report online for all to see.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/otwcode/otwarchive&quot;&gt;Archive Of Our Own&lt;/a&gt;, also known as “AO3”, is a Rails application that is more than 16 years old. It was the focus of my engagement.&lt;/p&gt;

&lt;p&gt;Without any further ado, here’s what I handed over to OTW and their crack team of volunteers. I think I am one of a very, very small number of people who has ever been &lt;em&gt;paid&lt;/em&gt; to work on AO3. The sheer amount of volunteer love and sweat in this project is truly astounding.&lt;/p&gt;

&lt;h2 id=&quot;the-report&quot;&gt;The Report&lt;/h2&gt;

&lt;p&gt;It’s an honor to work on behalf of such an engaged community like the one surrounding AO3. It’s also a tremendous honor to be one of the few people (maybe ever?) to get paid to work on this application. What you’ve accomplished with the volunteer time you have is truly incredible.&lt;/p&gt;

&lt;p&gt;For some context, AO3 is certainly one of the largest scale applications I’ve worked on in terms of requests per second. Certainly, in terms of just requests per dollar spent on the server rack, it’s one of the most efficient I’ve ever worked on.&lt;/p&gt;

&lt;p&gt;I see three main areas for improvement:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Removing footguns which could potentially result in incidents in the future&lt;/li&gt;
  &lt;li&gt;Make the app feel faster for your users&lt;/li&gt;
  &lt;li&gt;Targeted reduction of technical debt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do this, I strongly suggest tracking and improving these two numbers:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The number of active, non-idle database connections to the primary.&lt;/li&gt;
  &lt;li&gt;Controller’s p95 web transaction service time.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;AI Disclosure:&lt;/strong&gt; In compiling this report, I made extensive use of LLMs for research. I did not use LLMs to generate any of the text in this report.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This document is organized at the top level by our desired Outcomes, which are my goals for your performance improvements over the next six months. Underneath that are specific Recommendations to achieve those outcomes. Each Recommendation has an associated cost and benefit, rated subjectively on a 5 point scale.&lt;/p&gt;

&lt;p&gt;Ratings are designed mostly to be relative to each other, i.e. a 5 is always harder or more valuable than a 4, etc. Even cost/benefit roughly means I think it’s a toss-up whether or not you should do it, while a cost higher than the benefit rating means I think it’s not worth doing in the near term future.&lt;/p&gt;

&lt;p&gt;I hope you enjoy this document and find it a useful guide for the next 6-12 months of performance work on your application.&lt;/p&gt;

&lt;h2 id=&quot;outcome-remove-footguns&quot;&gt;Outcome: Remove Footguns&lt;/h2&gt;

&lt;p&gt;In the intro, I said you should focus on &lt;strong&gt;reducing the number of active, non-idle database connections&lt;/strong&gt; to the primary. I see this as your primary scale bottleneck. If that number consistently stays at or below ~1/2 the number of CPUs on the DB (which, IIRC, was like 196 or something?) you’ll be golden! Easy as that, right?&lt;/p&gt;

&lt;p&gt;This section is mostly about reducing opportunities for downtime (though not all directly relate to that DB metric).&lt;/p&gt;

&lt;h3 id=&quot;recommendation-remove-puma_worker_killer-cost-1-benefit-2&quot;&gt;Recommendation: Remove puma_worker_killer. Cost: 1 Benefit: 2&lt;/h3&gt;

&lt;p&gt;You have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;. There’s no other config in the gem for it, but this gem is frequently configured with env variables.&lt;/p&gt;

&lt;p&gt;PWK’s job is to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt; processes which go over a particular memory threshold. This sounds like a nice-to-have “safety” feature, but it’s a footgun in waiting.&lt;/p&gt;

&lt;p&gt;Several years ago I was consulting on a client project and the app was just really slow. I was looking at the response times and thinking, this app is about twice as slow as it probably should be. What’s going on? We removed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; and watched response times drop in half instantly.&lt;/p&gt;

&lt;p&gt;It turned out that someone had installed and configured &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; years ago and had forgotten about it. As the app grew over time, it continued to &lt;em&gt;work&lt;/em&gt;, but the baseline memory usage of the app just kept increasing, leading to more and more frequent process restarts triggered by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt;. In the end, it turned out Puma processes were processing about 20 requests on average before being restarted.&lt;/p&gt;

&lt;p&gt;Old processes are fast processes. We want processes to stick around for as long as possible.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; combines two things which are almost always &lt;em&gt;very difficult&lt;/em&gt; to observe as an SRE:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Configuration in ENV variables. ENV variables are almost always forgotten about. No one knows what they are and what they are set to, because they often contain secrets. Access to them is restricted as a result, and they’re not easily checked or in the front of anyone’s mind.&lt;/li&gt;
  &lt;li&gt;The “failure mode” is high process restarts, which is almost never properly observed. If PWK’s thresholds are set too low, the only way you’re going to find out is if you look at how often process restarts are happening and think: hm, that’s a bit high (which, you may not even realize after looking at it). Your app could be restarting &lt;em&gt;after serving only a single request&lt;/em&gt; and you might not notice it, if load is low enough and you’ve provisioned enough puma processes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; is sort of a relic from a Heroku-dominant era, where you needed to fit processes into teeny-tiny dynos of like 512MB. Nowadays, most people buy cloud hardware with 4GB of RAM per CPU, and an application which cannot fit within that constraint reliably is far more sick than something &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; can even address.&lt;/p&gt;

&lt;p&gt;For that reason, I think &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puma_worker_killer&lt;/code&gt; should be removed from the setup.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-change-send_data-to-a-background-job-powered-workflow-cost-2-benefit-3&quot;&gt;Recommendation: Change send_data to a background job powered workflow. Cost: 2 Benefit: 3&lt;/h3&gt;

&lt;p&gt;You have a couple places in the codebase where you’re using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_data&lt;/code&gt; to send files of fairly significant size down to the client:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;DownloadsController#show&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;send_csv_data&lt;/strong&gt; helper, used by ChallengeSignupsController, TagWranglers controller, AdminUsers controller.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are two big issues with what’s happening here.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Memory usage&lt;/strong&gt;. You’re loading ~several mb of stuff into memory at once to put some of these CSVs together. In a web process, that memory usage sticks around and is difficult to get rid of. In a background process, usually this kind of “dirtiness” in the heap is already occurring so you’re not making things much worse, and it’s in general easier to stop and restart background job processes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transmitting large files to the client is slow&lt;/strong&gt; Ruby web servers aren’t really designed for this. Puma does not buffer client responses and so you’re essentially locking up the thread for as long as it takes to stream this response back to the client. It’s worse for single-threaded situations (which you’re in I think, for web today).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these I think could get out of hand and cause some minor incidents.&lt;/p&gt;

&lt;p&gt;For me, the best path forward would be to reorient the whole thing around a background job:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Controller kicks off a job to generate the CSV.&lt;/li&gt;
  &lt;li&gt;Controller returns to the web client a URL and says “your CSV will be here when it’s done”.&lt;/li&gt;
  &lt;li&gt;Background job does the CSV work and uploads to S3.&lt;/li&gt;
  &lt;li&gt;S3 serves the CSV.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This would solve both problems for you at once. You could optionally try to work to stream the CSV upload as well to reduce memory usage in the worker, but honestly I think just getting it out of the web process is enough.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-install-some-kind-of-database-monitoring-product-cost-1-benefit-3&quot;&gt;Recommendation: Install some kind of database monitoring product Cost: 1 Benefit: 3&lt;/h3&gt;

&lt;p&gt;I think of observability stacks for Rails app as having three main components:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;. Most of the time this is coupled to your provider, e.g. AWS Cloudwatch, but can include 3rd parties like Datadog.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;APM/RUM&lt;/strong&gt;. Options include Sentry, Datadog, New Relic, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Database&lt;/strong&gt;. Options include pganalyze, pghero, Datadog’s Database Monitor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I feel like we’re missing the last one, particularly for an app like this where performance and stability is so sensitive to DB conditions.&lt;/p&gt;

&lt;p&gt;I’m flying a bit in the dark here because I think there are really good options here for Postgres but less so for InnoDB/MySQL. It is, of course, possible to use &lt;em&gt;only parts&lt;/em&gt; of Datadog and not the whole hog, and I believe DD’s database monitoring is like $70/80 a month. I &lt;em&gt;think&lt;/em&gt; the only open source option here is &lt;a href=&quot;https://github.com/percona/pmm&quot;&gt;Percona PMM&lt;/a&gt;? But I’ve never used it and can’t say if it’s any good, and it doesn’t have an index advisor.&lt;/p&gt;

&lt;p&gt;A good database monitoring solution does the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Index suggestion. Based on actual query data, can do things like gauge how much impact a new index has on writes, how big it would be, what queries it would alleviate.&lt;/li&gt;
  &lt;li&gt;Capture and visualize EXPLAIN plans.&lt;/li&gt;
  &lt;li&gt;CPU, Memory, active connection count metrics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m not really sure what the best direction to go here is, but I was definitely feeling the absence of this tooling when preparing this report.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-move-load-to-readers-with-queues-and-get-requests-cost-3-benefit-3&quot;&gt;Recommendation: Move load to readers with queues and GET requests. Cost: 3 Benefit: 3&lt;/h3&gt;

&lt;p&gt;I see a lot of people create replicas and then basically struggle to direct any meaningful usage to them. You’re bottlenecked pretty much entirely by your SQL primary, so moving more usage to them would be helpful.&lt;/p&gt;

&lt;p&gt;I’ve only seen two strategies really work here.&lt;/p&gt;

&lt;p&gt;The first is to create &lt;strong&gt;read only job queues&lt;/strong&gt;. These are job queues which are duplicates of an existing queue, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;high&lt;/code&gt;, but only allow jobs in that queue to talk to the replica database using ActiveRecord’s multi-db/role support.&lt;/p&gt;

&lt;p&gt;So, instead of just a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;utilities&lt;/code&gt; queue, you might also have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;utilities-read-only&lt;/code&gt; queue. You’d have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;around_perform&lt;/code&gt; hook in ApplicationJob that does something like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;use_replica_if_read_only&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_only_queue?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connected_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;role: :reading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ReadOnlyError&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;queue: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writable_queue_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_later&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_only_queue?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;read-only&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;writable_queue_name&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;queue_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/[-_]?read-only[-_]?/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This pattern makes it easy to transition significant amounts of background jobs to only touch the reader.&lt;/p&gt;

&lt;p&gt;Second, you can make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; requests only talk to the reader. &lt;em&gt;In theory&lt;/em&gt;, GET requests should not have any side effects or writes, so this should be possible. In addition, Rails makes it &lt;a href=&quot;https://guides.rubyonrails.org/active_record_multiple_databases.html#activating-automatic-role-switching&quot;&gt;easy to “read your own writes”&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Rails guarantees “read your own write” and will send your GET or HEAD request to the writer if it’s within the delay window. By default the delay is set to 2 seconds. You should change this based on your database infrastructure. Rails doesn’t guarantee “read a recent write” for other users within the delay window and will send GET and HEAD requests to the replicas unless they wrote recently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I took a brief look around and don’t see a lot of major issues with GET side effects. Lots of stuff writes to Redis but obviously that’s not a problem.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-improve-the-es-failure-handling-cost-2-benefit-4&quot;&gt;Recommendation: Improve the ES failure handling. Cost: 2 Benefit: 4&lt;/h3&gt;

&lt;p&gt;Currently, the app has a pretty hard failure path when Elastic goes down.&lt;/p&gt;

&lt;p&gt;If ES is unreachable, searches, connection refusal, or brownouts/timeouts end up in 500s, which will cascade and take down the app as the service degrades. It’s my impression that this has happened in the past.&lt;/p&gt;

&lt;p&gt;I think it could be instructive to look at how &lt;a href=&quot;https://github.com/rubygems/rubygems.org&quot;&gt;the Rubygems.org codebase tends to hold Elasticsearch much more at a distance&lt;/a&gt;, which means the service is a lot more resilient to ES outages.&lt;/p&gt;

&lt;p&gt;Here’s a few things I think you could improve:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add a query timeout in every query body.&lt;/li&gt;
  &lt;li&gt;Add a tighter client timeout - probably something like ~2s. I saw a few timeouts in the Sentry data that showed 30s timeouts in use. If you don’t get a response in 2 seconds, you’re not gonna get one in 30.&lt;/li&gt;
  &lt;li&gt;Add more layered error handling. When you catch a connection failure, timeout or other Elasticsearch error, return what you can and the rest can 200.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The app current treats ES as implicitly available, which I think the past shows is not a great assumption. If ES was treated as a non-critical read-only feature, you could degrade the site into a no-search mode that I think would greatly improve your overall uptime.&lt;/p&gt;

&lt;p&gt;I would also install timeouts in queries and clients so that brownouts affect you far less - better not to tie up a Ruby process for that long.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-move-audits-to-a-different-database-or-truncate-cost-1-benefit-2&quot;&gt;Recommendation: Move audits to a different database or truncate. Cost: 1 Benefit: 2&lt;/h3&gt;

&lt;p&gt;Like most people who have a “paper trail” feature in the database, I think you’re just starting to feel some pain on this one, given the size of this table.&lt;/p&gt;

&lt;p&gt;It’s unclear to me what your obligations are regarding how long these audits need to be kept. Obviously, the easiest thing to do is have a policy to only keep them around for short periods of time and dump the rest.&lt;/p&gt;

&lt;p&gt;Multi-db has a number of weird risks that just aren’t that important for audits: transactional integrity, cascading deletes. The “best-effort” compromises you’ll make by doing multi-db here I think are fine for the use-case.&lt;/p&gt;

&lt;p&gt;If you can’t do that, I think audits make good sense to move to a separate database. You only read from them rarely, and the read performance isn’t a big deal for them (since only admins can view) so any performance loss on “I can’t JOIN natively on these tables anymore” is basically not an issue.&lt;/p&gt;

&lt;p&gt;Since user model changes and admin activity updates don’t happen &lt;em&gt;that&lt;/em&gt; often, I kinda doubt there’s much write load being added here. It would just be an improvement to the overall database size, making it easier to backup and possibly improving cache hitrates a little.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-shard-a-works-database-workers-chapters-kudos-by-work_id-and-activity-database-readings-inbox_comments-by-user_id-cost-4-benefit-5&quot;&gt;Recommendation: Shard a “works” database (workers, chapters, kudos) by work_id, and “activity” database (readings, inbox_comments) by user_id. Cost: 4 Benefit: 5&lt;/h3&gt;

&lt;p&gt;You asked me in Slack about shards.&lt;/p&gt;

&lt;p&gt;I do see two pretty clear groupings of models that could shard:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Work-owned objects&lt;/strong&gt;, like workers, chapters, kudos.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Activity objects, owned by users&lt;/strong&gt;, like readings, inbox_comments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could potentially split &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;comments&lt;/code&gt; as well into two models, one for comments on Works and the other for comments on tags and admin posts somewhere else. The polymorphism means it wouldn’t work to shard in my scheme today.&lt;/p&gt;

&lt;p&gt;There’s still some important relations though that wouldn’t survive this scheme:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reading.visible&lt;/code&gt; joins directly between works and readings.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Work.update_stat_counter&lt;/code&gt; might need a rewrite if you don’t also shard comments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It would also mean you’d need to probably do some kind of event/async system to get things to sync across databases. Comments created on a work would have to emit an event or job which then means inbox rows get created for users, etc. This is &lt;em&gt;kind of&lt;/em&gt; already how Reading creation works via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SADD&lt;/code&gt;, so maybe this isn’t that hard actually? And since you already cache so much, perhaps moving the source of those caches from 1 db to several just doesn’t feel that painful in the view layer?&lt;/p&gt;

&lt;p&gt;Of course it introduces eventual consistency, but I don’t think that’s actually a huge concern, given what I’ve seen in the app’s usage?&lt;/p&gt;

&lt;p&gt;It may be that audits are the easiest way to get your feet wet on ActiveRecord multi-db, and then readings and inbox_comments in a new (sharded) db are the second step.&lt;/p&gt;

&lt;h2 id=&quot;outcome-top-controllers-by-traffic-should-all-have-a-p95-of-less-than-1-second&quot;&gt;Outcome: Top controllers, by traffic, should all have a p95 of less than 1 second.&lt;/h2&gt;

&lt;p&gt;I think the second most important thing I’d like to accomplish, beyond just keeping the website up more, would be to make it feel faster for your users.&lt;/p&gt;

&lt;p&gt;Currently, Sentry reports the overall p95 of all web transactions as 200ms, which is actually quite good. However, today, you have several transactions where the p95 is significantly higher:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;BookmarksController#index&lt;/strong&gt;, 2.58 seconds.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AutocompleteController#tag&lt;/strong&gt;, 2.98 seconds. Most autocomplete controller actions have similar p95s.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;DownloadsController#show&lt;/strong&gt;, 7.72 seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three are what I would like to focus effort on, because the rest feel pretty good (or at least, additional effort here would not cause significant user-noticeable gains in performance).&lt;/p&gt;

&lt;h3 id=&quot;recommendation-swap-bullet-for-prosopite-default-to-fail-in-tests-cost-2-benefit-3&quot;&gt;Recommendation: Swap Bullet for Prosopite, default to fail in tests. Cost: 2 Benefit: 3&lt;/h3&gt;

&lt;p&gt;I’ve never been all that high on Bullet, but recently I’ve had a lot of success with &lt;a href=&quot;https://github.com/charkost/prosopite&quot;&gt;prosopite&lt;/a&gt;. It’s highly accurate, with basically zero false positives. That means you can set up a workflow where Prosopite is configured to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise&lt;/code&gt; by default in the test environment. That means you can’t ship new features with proven N+1s! That’s an extremely powerful thing to be always checking for in the background without any additional effort.&lt;/p&gt;

&lt;p&gt;Usually what I do with clients is work along the following axis:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Install prosopite, log in dev and test.&lt;/li&gt;
  &lt;li&gt;Add prosopite as an RSpec context tag, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:check_for_n_plus_ones&lt;/code&gt;. Add it to all test classes which don’t have N+1s in them today (add them everywhere they wouldn’t fail a test today).&lt;/li&gt;
  &lt;li&gt;Gradually burn through the remaining bad examples/classes, adding the context one by one, until you have just a few left. At that point, you can…&lt;/li&gt;
  &lt;li&gt;Make prosopite &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise&lt;/code&gt; by default in test and remove the remaining N+1s as global ignores in prosopite’s config.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s extremely useful!&lt;/p&gt;

&lt;h3 id=&quot;recommendation-turn-yjit-on-cost-1-benefit-2&quot;&gt;Recommendation: Turn YJIT on. Cost: 1 Benefit: 2&lt;/h3&gt;

&lt;p&gt;You have YJIT disabled in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application.rb&lt;/code&gt;. I recommend trying it. Pretty consistently, what I see is that it makes that app ~10-15% faster for 20-30% higher memory use.&lt;/p&gt;

&lt;p&gt;You’ll have to look at your current server provisioning to decide if that’s worth it for you.&lt;/p&gt;

&lt;p&gt;If you have any memory left, might as well use it and reduce load on your servers by ~10-15%. If you don’t have any memory left, then I guess you’re stuck with keeping it off.&lt;/p&gt;

&lt;p&gt;In my experience, Ruby 3.4+ does quite well with YJIT on. Of course it’s also quite stable, since basically everyone has it turned on now.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-refactor-railscachefetch-into-view-layer-fragment-caches-cost-2-benefit-3&quot;&gt;Recommendation: Refactor Rails.cache.fetch into view-layer fragment caches. Cost: 2 Benefit: 3&lt;/h3&gt;

&lt;p&gt;Looking through the traces, my overall picture is that you are over-using cache, peppering sometimes hundreds of cache calls all over the place. I can see how you got here. You’re trying to remove load from the SQL database and put it somewhere else. But from the perspective of total latency of the entire transaction, you haven’t improved things much (Redis isn’t magically faster than a hot, in-the-buffers hit from a MySQL db).&lt;/p&gt;

&lt;p&gt;If you look at, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_intro_module.html.erb&lt;/code&gt;, there’s several cache calls happening right in a row.&lt;/p&gt;

&lt;p&gt;I think this could be improved by focusing less on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rails.cache&lt;/code&gt; manual usage, but instead focusing on more cache (and cache-reuse) of entire HTML fragments at the view layer. You’re caching &lt;em&gt;more&lt;/em&gt; activity (not just the query but also the HTML generation) and also across potentially &lt;em&gt;multiple&lt;/em&gt; queries and datasources.&lt;/p&gt;

&lt;p&gt;Another example: on the homepage, for each marked for later work, there are 5 cache round trips to render the blurb, even though the fragment itself is already cached. If the entire readings section was single fragment keyed per-user, you could turn 30 cache ops into 1.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-use-turbo-drive-or-turbo-frames-cost-4-benefit-5&quot;&gt;Recommendation: Use Turbo Drive or Turbo Frames. Cost: 4 Benefit: 5.&lt;/h3&gt;

&lt;p&gt;I harp on this one on social media a lot but it’s true. &lt;strong&gt;The biggest performance impact you can have on a web app user is to turn a full page navigation into an SPA-style route change&lt;/strong&gt;. For “golden path” Rails apps like AO3, that means using Turbo.&lt;/p&gt;

&lt;p&gt;The reason why these kinds of requests are so much faster is because the CSSOM and Javascript VM are re-used. You don’t need to completely relayout everything, recalculate the CSSOM and re-execute all your JS. You can just move on! It’s truly the only thing that remove &lt;strong&gt;seconds&lt;/strong&gt; from a user waiting on the page to do something, rather than milliseconds.&lt;/p&gt;

&lt;p&gt;For AO3, I think the most realistic transition would be to use Turbo Frames. It asks the least amount from the current setup in order to make the transition. I’ve done this migration myself on a legacy ActiveAdmin panel with lots of custom JQuery stuff, and I was able to make it happen pretty quickly.&lt;/p&gt;

&lt;p&gt;A more ambitious transition is Turbo Drive. I find that pretty much every jQuery plugin’s assumption about how pageloads work will break, however. It would require an extensive inventory of &lt;em&gt;all&lt;/em&gt; your JavaScript and the behaviors you expect to work, and probably adding a lot more tests for that level of integration/browser behavior than what you have today.&lt;/p&gt;

&lt;p&gt;It’s a lot of work, but there’s really nothing that makes pageloads faster. If you can turn a full-page-nav into something else, people really feel it.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-turn-on-cloudflare-polish-cost-1-benefit-3&quot;&gt;Recommendation: Turn on Cloudflare Polish Cost: 1 Benefit: 3&lt;/h3&gt;

&lt;p&gt;Cloudflare Polish automatically serves &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webp&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avif&lt;/code&gt; formatted images to compatible clients. You have Cloudflare on, but Polish is not.&lt;/p&gt;

&lt;p&gt;These image formats can save ~10-50% filesize, depending on the image. I love it, it’s probably one of the easiest frontend optimizations you can make. Underneath, since it’s working based on the Accept header, it literally can’t break anything for anyone because the client &lt;em&gt;has to tell you&lt;/em&gt; they accept the format before Cloudflare decides to serve it to them!&lt;/p&gt;

&lt;p&gt;I recommend the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lossy&lt;/code&gt; setting because in practice I haven’t seen any visible degradation.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-change-csrf-policy-to-header_or_legacy_token-cost-2-benefit-3&quot;&gt;Recommendation: Change CSRF policy to :header_or_legacy_token Cost: 2 Benefit: 3&lt;/h3&gt;

&lt;p&gt;You’ve got this somewhat-unusual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;token_dispenser.json&lt;/code&gt; route which you use to generate a CSRF token, which then gets provided to any form that needs it. You need to do this because you want to be able to cache logged-out pages with HTTP, which means of course you can’t put a CSRF token in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since Rails 8.2, you now have the option to change how CSRF authentication works.&lt;/p&gt;

&lt;p&gt;In Rails 8.2, you can configure Rails to check the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-Fetch-Site&lt;/code&gt; header&lt;/a&gt;, which is sent by browsers on every request.&lt;/p&gt;

&lt;p&gt;From MDN:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;this header tells a server whether a request for a resource is coming from the same origin, the same site, a different site, or is a “user initiated” request. The server can then use this information to decide if the request should be allowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Possible values:&lt;/p&gt;

&lt;p&gt;cross-site
The request initiator and the server hosting the resource have a different site (i.e., a request by “potentially-evil.com” for a resource at “example.com”).&lt;/p&gt;

&lt;p&gt;same-origin
The request initiator and the server hosting the resource have the same origin (same scheme, host and port).&lt;/p&gt;

&lt;p&gt;same-site
The request initiator and the server hosting the resource have the same site, including the scheme.&lt;/p&gt;

&lt;p&gt;none
This request is a user-originated operation. For example: entering a URL into the address bar, opening a bookmark, or dragging-and-dropping a file into the browser window.&lt;/p&gt;

&lt;p&gt;So, Rails allows updates from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;same-origin&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;same-site&lt;/code&gt;, disallows all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-site&lt;/code&gt; (unless in Rails’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trusted_origins&lt;/code&gt;) or header missing (except GET of course). Since browsers themselves set Sec-Fetch-Site, it cannot be forged.&lt;/p&gt;

&lt;p&gt;With the header, the following all becomes unnecessary:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;HomeController#token_dispenser&lt;/li&gt;
  &lt;li&gt;updatedCachedTokens()&lt;/li&gt;
  &lt;li&gt;loadedCSRF&lt;/li&gt;
  &lt;li&gt;csrf_meta_tags in layouts, authentication_token in forms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, this is not supported in every browser ever.&lt;/p&gt;

&lt;p&gt;It is supported in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Chrome 76+ (2019)&lt;/li&gt;
  &lt;li&gt;Firefox 90+ (2021)&lt;/li&gt;
  &lt;li&gt;Safari 16.4+ (2023)&lt;/li&gt;
  &lt;li&gt;Edge 79+ (2020)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don’t think AO3 has any official compatibility targets (e.g. &lt;a href=&quot;https://browsersl.ist/&quot;&gt;https://browsersl.ist/&lt;/a&gt;) (maybe it’s time to officially come up with some?).&lt;/p&gt;

&lt;p&gt;You can test this out by switching to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;header_or_legacy_token&lt;/code&gt;, which will allow browsers which present the header to do so.&lt;/p&gt;

&lt;p&gt;The “feature check” though on the browser side is a bit weird. How do you know if you’re sending that header or not? You don’t! So, the approach (if you can’t drop incompatible clients) would be to probably modify the token dispenser so that after the first request, it notices if the browser sent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-Fetch-Site&lt;/code&gt;, and if it did, it tells the frontend “hey, you don’t have to come back for any more CSRF tokens. Just present sec-fetch-site.” I think you’d probably want to store that in the session cookie or something.&lt;/p&gt;

&lt;p&gt;The primary benefit I see here would be then that form actions can happen immediately without waiting for CSRF tokens on a network roundtrip.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-reduce-cache-fetches-on-bookmarksindex-cost-3-benefit-3&quot;&gt;Recommendation: Reduce cache fetches on Bookmarks#index Cost: 3 Benefit: 3&lt;/h3&gt;

&lt;p&gt;I took a look at a &lt;a href=&quot;https://organization-for-transformativ.sentry.io/insights/backend/summary/trace/913a2990d3074f349a9203042bbf62aa/?environment=staging&amp;amp;node=span-bd6b6c3a2376473f&amp;amp;project=4507539185991680&amp;amp;query=transaction.op%3Ahttp.server&amp;amp;referrer=insights-backend-overview&amp;amp;sort=-p95%28span.duration%29&amp;amp;source=performance_transaction_summary&amp;amp;statsPeriod=30d&amp;amp;timestamp=1773123577&amp;amp;transaction=BookmarksController%23index&quot;&gt;random Bookmarks#index trace&lt;/a&gt; and saw the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;179 cache spans&lt;/li&gt;
  &lt;li&gt;148 reads&lt;/li&gt;
  &lt;li&gt;31 writes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s pretty high. Ideally we only go to the cache a handful of times. These were split between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemcacheStore&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalStore&lt;/code&gt;. Since the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemCacheStore&lt;/code&gt; is the only one that costs us $$$ to use and has network latency, I’ll focus on that.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reads: 114&lt;/li&gt;
  &lt;li&gt;Hits: 84&lt;/li&gt;
  &lt;li&gt;Misses: 30&lt;/li&gt;
  &lt;li&gt;Hit rate: 73.7%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s a pretty poor hitrate. If we look in a bit more detail, here’s how that breaks down by key shape:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ao3-v8.0:views/bookmark-blurb-bookmarks/…-v3
    &lt;ul&gt;
      &lt;li&gt;20 reads&lt;/li&gt;
      &lt;li&gt;19 hits&lt;/li&gt;
      &lt;li&gt;1 miss&lt;/li&gt;
      &lt;li&gt;1 put&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:…/blurb_css_classes
    &lt;ul&gt;
      &lt;li&gt;20 reads&lt;/li&gt;
      &lt;li&gt;20 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:…/bookmark_count
    &lt;ul&gt;
      &lt;li&gt;40 reads total&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;18 remote misses&lt;/strong&gt;!&lt;/li&gt;
      &lt;li&gt;22 local hits&lt;/li&gt;
      &lt;li&gt;18 puts
 ### Work metadata / fragments&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:byline_data/…
    &lt;ul&gt;
      &lt;li&gt;17 reads&lt;/li&gt;
      &lt;li&gt;17 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:/v4/work_blurb_tag_cache_key/…
    &lt;ul&gt;
      &lt;li&gt;7 reads&lt;/li&gt;
      &lt;li&gt;7 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:views/works/…-showwarn-showfreeform-v11
    &lt;ul&gt;
      &lt;li&gt;7 reads&lt;/li&gt;
      &lt;li&gt;7 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:views/works/…/stats-v4
    &lt;ul&gt;
      &lt;li&gt;7 reads&lt;/li&gt;
      &lt;li&gt;2 hits&lt;/li&gt;
      &lt;li&gt;5 misses&lt;/li&gt;
      &lt;li&gt;5 puts&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:works/…/count_visible_comments
    &lt;ul&gt;
      &lt;li&gt;5 reads&lt;/li&gt;
      &lt;li&gt;5 misses&lt;/li&gt;
      &lt;li&gt;5 puts&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:works/…/kudos_count-v2
    &lt;ul&gt;
      &lt;li&gt;5 reads&lt;/li&gt;
      &lt;li&gt;5 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;ao3-v8.0:/v1/public_bookmarks_count/…
    &lt;ul&gt;
      &lt;li&gt;5 reads&lt;/li&gt;
      &lt;li&gt;5 hits&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I basically see these as a kind of “fast” N+1 problem. Ideally I think we’d only be going to each keyspace one time. It’s a sign of an “insufficiently Russian-dolled-cache”. I think it also gets back to the observation I had earlier that you are in a lot of caches replacing single database queries with single cache lookups, which doesn’t do much for you.&lt;/p&gt;

&lt;p&gt;I’d try to roll these up a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bookmark_row&lt;/code&gt; fragment, which should be able to wrap around ~8 of those keys (kudos, blurb, views, byline data, etc etc). Then you can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MGET&lt;/code&gt; for all the bookmarks you want.&lt;/p&gt;

&lt;p&gt;It’s also possible the cache keys like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byline_data&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blurb_css_classes&lt;/code&gt; are just too small to help to begin with and should be removed completely.&lt;/p&gt;

&lt;p&gt;I think this transition is difficult because it requires a deep domain understanding of the cache keys and ERD involved here, but it doesn’t look impossible to me.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-always-preload-current_user-roles-cost-1-benefit-2&quot;&gt;Recommendation: Always preload current_user roles. Cost: 1 Benefit: 2&lt;/h3&gt;

&lt;p&gt;I noticed a lot of Sentry traces with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;roles&lt;/code&gt; lookups peppered all over the place. You already do the right thing in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permit_yo&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;has_role?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;any?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;role_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loaded?&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;role&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So that’s good. The fact that these queries are happening then is a sign that the association wasn’t &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I’ve encountered this permissions thing a ton of times. I think it’s almost always worth it to just load all user roles upfront rather than try to be clever about which you load when.&lt;/p&gt;

&lt;p&gt;Since you’re using Devise, you override &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serialize_from_session&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;serialize_from_session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;salt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:roles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;authenticatable_salt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;salt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are also a handful of places in views where you inspect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.roles&lt;/code&gt; directly, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@user.roles.any?&lt;/code&gt;. I’d replace all these with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has_role?&lt;/code&gt; and then make sure the association is loaded.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-timeout-autocompletes-add-a-hard-limit-and-consider-lru-caches-around-common-3-char-words-cost-2-benefit-4&quot;&gt;Recommendation: Timeout Autocompletes, add a hard LIMIT, and consider LRU caches around common 3+ char words. Cost: 2 Benefit: 4&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AutocompleteSource.autocomplete_lookup&lt;/code&gt; is a very performance sensitive method that powers all the various autocomplete controllers. It routinely has a p95 of 2sec+.&lt;/p&gt;

&lt;p&gt;There are a couple of things I’d do to improve it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a hard LIMIT on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zrevrangebyscore&lt;/code&gt; for 3+ character terms.&lt;/strong&gt;. Currently the ZREVRANGEBYSCORE can take 2 seconds or more when the search term is something really common. Adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;limit: [0,50]&lt;/code&gt; here would, I think, really help bring that P95 down a lot, because the score key of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autocomplete_tag_all_score_some_popular_word&lt;/code&gt; could be thousands of tags, which you then just truncate down to 15 in the end. Ouch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get a small LRU cache together for popular 3+ character words&lt;/strong&gt;. After trying the previous fix, if I still wanted more “juice”, I think I’d try to create a separate CacheStore with a given size limit (so that I can use LRU behavior to only cache “the most important” stuff) for 3+ character search parameters. I think this could even be process-local using MemoryStore and a size setting of like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.megabytes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a timeout to these queries, particularly the zrevrangebyscore&lt;/strong&gt;. One thing I’m always thinking about with Redis is that it’s single-threaded. It’s really not designed to service long-running queries, because those queries may end up blocking other concurrent operations. Unlike a SQL database, which parallelizes quite well across CPU cores, your Redis DB is just locked to a single core and so a very slow query can end up affecting others.&lt;/p&gt;

&lt;p&gt;I would also consider a circuit breaker and/or open timeout to the autocomplete Redis. If it goes down, the site should be able to quickly recover and not go down completely.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-use-a-test-queue-to-make-builds-faster-more-reliably-fast-cost-2-benefit-2&quot;&gt;Recommendation: Use a test queue to make builds faster, more reliably fast Cost: 2 Benefit: 2&lt;/h3&gt;

&lt;p&gt;You currently have 13 Cucumber jobs working as explicit directory splits, and 3 RSpec jobs also statically configured. So, if you get unlucky and one of those Cucumber jobs takes 14 minutes, well, that’s how long the build takes. There’s no balancing happening there.&lt;/p&gt;

&lt;p&gt;I tell basically everyone these days to &lt;strong&gt;balance tests between workers using a queue&lt;/strong&gt;. This allows the maximum amount of redistribution to occur, so that the minimum and maximum total execution time per worker (process on a single-machine approach, or per agent/runner/VM as well) to be as close together as possible.&lt;/p&gt;

&lt;p&gt;The “enterprisey” way is to pay for Knapsack Pro. However, there are a number of open source projects that can do similar stuff.&lt;/p&gt;

&lt;p&gt;I’m going to take as a constraint here that we have to use the default, free Github Actions Runner hardware. Those machines have 4 vCPU and 16GB of RAM per.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/tmm1/test-queue&quot;&gt;test-queue&lt;/a&gt; by the prolific tmm1, author of stackprof. I’m pretty sure the networked mode wouldn’t work in Github Actions, but the fork-based model would. That would allow you to use the remaining 3 idle cores on each Github Actions worker. Has cucumber support though maybe it’s not very heavily used.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/grosser/parallel_tests&quot;&gt;parallel_tests&lt;/a&gt; by grosser. Popular, I’ve seen this one in use a few times. Not networked, but supports Cucumber.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/danielwestendorf/specwrk/blob/main/.github/workflows/specwrk-multi-node.yml&quot;&gt;spec-wrk&lt;/a&gt; by Daniel Westendorf. No Cucumber support here but &lt;em&gt;does&lt;/em&gt; support networked runners in Github Actions. Could be cool if you added a Cucumber adapter!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With some networked/parallel runners I think you could easily take builds down to 5 minutes or less.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-remove-resque-inline-in-tests-cost-2-benefit-3&quot;&gt;Recommendation: Remove resque inline in tests. Cost: 2 Benefit: 3&lt;/h3&gt;

&lt;p&gt;You have the following in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resque&lt;/code&gt; config:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;no&quot;&gt;Resque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RAILS_ENV&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;active_job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;queue_adapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:inline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So in tests, all Resque enqueue calls execute synchronously.&lt;/p&gt;

&lt;p&gt;In my experience (which is more with the equivalent setting in Sidekiq), this ends up creating two big problems for you:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tests now have a ton of extra state and state modification flying around that’s hard to reason about because it’s all implicit. This makes the tests harder to understand.&lt;/li&gt;
  &lt;li&gt;You will also be doing a lot of work which is not necessary to make the assertions pass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For both of those reasons, I prefer that any background queue draining be done &lt;strong&gt;explicitly&lt;/strong&gt; instead of implictly. If you use the traditional arrange/act/assert framework, I think tests are much clearer if you include these queue drain/execute job calls in the &lt;strong&gt;Act&lt;/strong&gt; or &lt;strong&gt;Arrange&lt;/strong&gt; phase.&lt;/p&gt;

&lt;p&gt;It looks like you kind of already started to do this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspend_resque_workers&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec_helper.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For ActiveJob, switching is easy. You just change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue_adapter&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:test&lt;/code&gt; and then drain only when necessary. Some specs already are written this way.&lt;/p&gt;

&lt;p&gt;Resque doesn’t really have a Sidekiq fake mode built in. I think we basically could extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspend_resque_workers&lt;/code&gt; to be the default.&lt;/p&gt;

&lt;h2 id=&quot;outcome-reduce-technical-debt&quot;&gt;Outcome: Reduce technical debt&lt;/h2&gt;

&lt;p&gt;These are just a couple of things I noticed as I was looking around.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-complete-the-migration-to-puma-cost-1-benefit-1&quot;&gt;Recommendation: Complete the migration to Puma. Cost: 1 Benefit: 1&lt;/h3&gt;

&lt;p&gt;Of course &lt;em&gt;I’m&lt;/em&gt; gonna say this, as I’m the maintainer. But I really do think it’s a great application server! It looks like you’re about halfway to transitioning.&lt;/p&gt;

&lt;p&gt;Moving from single-threaded Unicorn to single-threaded Puma should just be a “straight swap”. When you use the servers in this way they don’t really differ that much in terms of behavior.&lt;/p&gt;

&lt;p&gt;I think probably this application never becomes multi-threaded. It’s probably just more hassle than it’s worth. I usually tell people: what changing your concurrency model gives you (from processes to threads, threads to fibers, etc) is allows you to run more open, idle connections with low switching cost and low memory use. If your workload doesn’t have that need, process based concurrency probably works just fine for you. Almost every major Rails app, the real giga-shops like Shopify, Github, Intercom, Gusto: they’re all running single-threaded. They decided the threading bugs aren’t worth the ~30% memory savings. I suspect, given the limited volunteer resources and the age of this app, that the calculus is the same for you.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-load_admin_banner-doesnt-need-caching-cost-1-benefit-1&quot;&gt;Recommendation: load_admin_banner doesn’t need caching. Cost: 1 Benefit: 1&lt;/h3&gt;

&lt;p&gt;This is just one of a couple of places I noticed unnecessary caching. From what I can see in the traces, this takes about 1ms whether or not it’s hot or cold. Since it’s on every page load via before_action, that stood out to me.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
  &lt;span class=&quot;n&quot;&gt;before_action&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:load_admin_banner&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_admin_banner&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;development?&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@admin_banner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AdminBanner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# http://stackoverflow.com/questions/12891790/will-returning-a-nil-value-from-a-block-passed-to-rails-cache-fetch-clear-it&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Basically we need to store a nil separately.&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@admin_banner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v1/admin_banner&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;banner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AdminBanner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;active: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;banner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;banner&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@admin_banner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@admin_banner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fetching &lt;em&gt;the exact same row every time&lt;/em&gt; in SQL is just not any slower than reading it out of Redis. Just a bit of unnecessary complexity.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-move-jqueryjqueryui-to-first-party-serving-cost-1-benefit-1&quot;&gt;Recommendation: Move JQuery/JQueryUI to first party serving. Cost: 1 Benefit: 1&lt;/h3&gt;

&lt;p&gt;I noticed you’re serving the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/jquery-ui.min.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think in general, for a site with the kinds of concerns about privacy that AO3 has, you generally just want to “talk” to as few third parties as possible. So just for user privacy reasons, I think it’s better to serve these first-party. And they’re like 15 year old versions, so who knows, maybe one day Google axes them.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;used&lt;/em&gt; to be thought in the frontend communities that 3rd party CDNs were a performance “boost” because “oh, everyone will use the same CDN cached version of this library” but that became less true over the years as people realized this could be used to fingerprint clients and so browsers ended up changing how caching worked and breaking this behavior completely. And of course, not a lot of people use 13-year-old versions of jQuery anymore.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-remove-active_record_query_trace-and-replace-with-native-rails-verbose-query-logs-cost-1-benefit-1&quot;&gt;Recommendation: Remove active_record_query_trace and replace with native Rails verbose query logs Cost: 1 Benefit: 1&lt;/h3&gt;

&lt;p&gt;You currently have three overlapping query source trace mechanisms.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active_record_query_trace&lt;/code&gt; gem. Appends a colorized multi-line call stack after each query in dev logs.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.active_record.verbose_query_logs&lt;/code&gt; = true.  Rails 5.2+ built-in, appends ↳ file:line after each query&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/initializers/active_record_log_subscriber.rb&lt;/code&gt; — a custom LogQuerySource module that also prepends ↳ file:line&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While I love this feature and of course recommend everyone use it, it is kinda baked into Rails now and I prefer to use the Rails mainline stuff where I can rather than bring in dependencies. Probably we only need one of these right?&lt;/p&gt;

&lt;p&gt;It is a little bit of a bummer but verbose_query_logs only displays a single line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Before (with gem):
User Load (0.4ms)  SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE ...
  app/models/concerns/is_active.rb:11:in `active?&apos;
  app/models/user.rb:67:in `active?&apos;
  app/controllers/users_controller.rb:42:in `index&apos;

After (without gem):
User Load (0.4ms)  SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE ...
  ↳ app/controllers/users_controller.rb:42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think you can just keep verbose_query_logs and delete the other two.&lt;/p&gt;

&lt;h3 id=&quot;recommendation-move-to-slo-queues-cost-3-benefit-5&quot;&gt;Recommendation: Move to SLO queues Cost: 3 Benefit: 5&lt;/h3&gt;

&lt;p&gt;SLO queues are a concept introduced in my book, Sidekiq in Practice. It was adopted successfully at Gusto and the community at large has really picked up and run with the idea.&lt;/p&gt;

&lt;p&gt;The basic idea is that all queues in your system are named after their queue time SLO - how long they promise that a job will be in the queue until it is a executed.&lt;/p&gt;

&lt;p&gt;For example, a common setup is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;within_0_seconds
within_5_minutes
within_15_minutes
within_1_hour
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You then launch any number of processes in Resque which listen to one of those queues (and one queue only).&lt;/p&gt;

&lt;p&gt;This allows you to intelligently autoscale: we don’t need to start scaling up the within 1 hour queue until the latency of that queue is above ~45 minutes or so.&lt;/p&gt;

&lt;p&gt;Using many queues which are based on just the “domain” or “subject” of the queue (e.g. imports, exports, orders, etc) leads to an explosion of queue types, whose SLOs are not clear. You don’t know when to page someone or wake them up because the queue time expectation is not clear.&lt;/p&gt;

&lt;p&gt;For a volunteer-run shop, I think getting completely crystal-clear about how long it’s OK to wait in the queue until someone needs to be notified would be really helpful. It also makes it easy for app developers in the future to “sign up” for what level of queue time they need (it’s right in the name!).&lt;/p&gt;
</description>
        <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/performance-lessons-from-ao3/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/performance-lessons-from-ao3/</guid>
        
        
      </item>
    
      <item>
        <title>Announcing the Rails Performance Apocrypha</title>
        <description>&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/apocrypha_cover.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Hello Rubyists!&lt;/p&gt;

&lt;p&gt;Today, I’m launching a new product: The Ruby on Rails Performance Apocrypha.&lt;/p&gt;

&lt;p&gt;Over the last four years, I’ve written a lot of stuff to this newsletter. Until now, none of that stuff has been publicly accessible if you wanted to go back and read it again. If something useful was posted to the newsletter before you subscribed, you were just sort of screwed.&lt;/p&gt;

&lt;p&gt;So, I’ve compiled 4 years of writing to this newsletter into a book. It covers my usual topics: performance science and engineering, frontend performance, Ruby performance, and scaling. It’s a fun ramble around all of these topics with a lot of tidbits and useful information scattered about. Each chapter is quite short, so it’s easy to pick up and put down again.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://gum.co/apocrypha&quot;&gt;It’s available now on Gumroad for just $10.&lt;/a&gt; As always, it’s DRM-free and available in PDF, e-reader and even HTML and plain-text formats.&lt;/p&gt;

&lt;p&gt;I called this book the “apocrypha”, because I consider my “main-line” of Rails performance instruction, the canonical “scripture”, to be my Rails Performance Workshop. By contrast, this book is a bit of an all-over-the-place ramble, and it covers some things that I didn’t cover in great detail in my other instructional books and workshops, such as HTTP/2 resource prioritization, and a detailed how-to on how to use New Relic’s Ruby VM information.&lt;/p&gt;

&lt;p&gt;Here’s the chapter titles, in case you’re wondering what’s covered:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What I Value&lt;/li&gt;
  &lt;li&gt;Performance Science&lt;/li&gt;
  &lt;li&gt;Why Performance?&lt;/li&gt;
  &lt;li&gt;You Are Not a Compiler&lt;/li&gt;
  &lt;li&gt;What does 10% faster really mean?&lt;/li&gt;
  &lt;li&gt;Benchmarks for Rails Apps&lt;/li&gt;
  &lt;li&gt;Build-your-own APM&lt;/li&gt;
  &lt;li&gt;Reading Flamegraphs&lt;/li&gt;
  &lt;li&gt;DRM: Database, Ruby, Memory&lt;/li&gt;
  &lt;li&gt;Performance in the Design Space&lt;/li&gt;
  &lt;li&gt;Microservices and Trends&lt;/li&gt;
  &lt;li&gt;On Minitest&lt;/li&gt;
  &lt;li&gt;Corporate Support for Ruby&lt;/li&gt;
  &lt;li&gt;Why is Ruby Slow?&lt;/li&gt;
  &lt;li&gt;Popularity&lt;/li&gt;
  &lt;li&gt;Stinky Dependencies&lt;/li&gt;
  &lt;li&gt;Why Cache?&lt;/li&gt;
  &lt;li&gt;Software Quality at Startups&lt;/li&gt;
  &lt;li&gt;Frontend&lt;/li&gt;
  &lt;li&gt;Simple Frontend Config Changes&lt;/li&gt;
  &lt;li&gt;What is TTFB?&lt;/li&gt;
  &lt;li&gt;Always Use a CDN&lt;/li&gt;
  &lt;li&gt;Page Weights and Frontend Load Times&lt;/li&gt;
  &lt;li&gt;Lazy-loading&lt;/li&gt;
  &lt;li&gt;What’s Resource Prioritization?&lt;/li&gt;
  &lt;li&gt;HTML on the Wire&lt;/li&gt;
  &lt;li&gt;Exceptions: Silent, not Free&lt;/li&gt;
  &lt;li&gt;On Thread-Safety&lt;/li&gt;
  &lt;li&gt;What is the GVL?&lt;/li&gt;
  &lt;li&gt;Timeslicing the GVL&lt;/li&gt;
  &lt;li&gt;The GVL and C&lt;/li&gt;
  &lt;li&gt;Bloat&lt;/li&gt;
  &lt;li&gt;Minimum Viable Rails&lt;/li&gt;
  &lt;li&gt;The Weird Setting No One Used&lt;/li&gt;
  &lt;li&gt;Object Allocation&lt;/li&gt;
  &lt;li&gt;You Should Always Use a Production Profiler&lt;/li&gt;
  &lt;li&gt;Reproducing Issues Locally&lt;/li&gt;
  &lt;li&gt;Worker Killers&lt;/li&gt;
  &lt;li&gt;What’s QueryCache?&lt;/li&gt;
  &lt;li&gt;Reading New Relic’s Ruby VM Tab&lt;/li&gt;
  &lt;li&gt;Test Setup&lt;/li&gt;
  &lt;li&gt;What is Time Consumed?&lt;/li&gt;
  &lt;li&gt;Request Queue Times&lt;/li&gt;
  &lt;li&gt;Amdahl’s Law&lt;/li&gt;
  &lt;li&gt;Threads&lt;/li&gt;
  &lt;li&gt;CPU-bound or IO-bound?&lt;/li&gt;
  &lt;li&gt;What is Swap?&lt;/li&gt;
  &lt;li&gt;Database Pools&lt;/li&gt;
  &lt;li&gt;Single-thread Performance&lt;/li&gt;
  &lt;li&gt;Read Replicas&lt;/li&gt;
  &lt;li&gt;Why Lambda?&lt;/li&gt;
  &lt;li&gt;Never use Perf-M&lt;/li&gt;
  &lt;li&gt;Daily Restarts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://gum.co/apocrypha&quot;&gt;Check it out on Gumroad.&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 14 Jan 2021 00:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/announcing-apocrypha/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/announcing-apocrypha/</guid>
        
        
      </item>
    
      <item>
        <title>We Made Puma Faster With Sleep Sort</title>
        <description>&lt;p&gt;Puma 5 (codename Spoony Bard (When Puma gets a new ‘supercontributor’ that submits lots of important work to the project, we let them name the next release. This release features a lot of code from Will Jordan, who named this release ‘Spoony Bard’. Will said: ‘Final Fantasy IV is especially nostalgic for me, the first big open-source project I ever worked on was a fan re-translation of the game back in the late 90s.’)) was released today (my birthday!). There’s a lot going on in this release, so I wanted to talk about the different features and changes to give Puma users confidence in upgrading.&lt;/p&gt;

&lt;h2 id=&quot;experimental-performance-features-for-cluster-mode-on-mri&quot;&gt;Experimental Performance Features For Cluster Mode on MRI&lt;/h2&gt;

&lt;p&gt;This is probably the headline of the release - two features for reducing memory usage, and one for reducing latency.&lt;/p&gt;

&lt;p&gt;Puma 5 contains 3 new experimental performance features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait_for_less_busy_worker&lt;/code&gt; config. This may reduce latency on MRI through inserting a small delay (sleep sort!) before re-listening on the socket if worker is busy. Intended result: If enabled, should reduce latency in high-load (&amp;gt;50% utilization) Puma clusters.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork_worker&lt;/code&gt; option and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refork&lt;/code&gt; command for reduced memory usage by forking from a worker process instead of the master process. Intended result: If enabled, should reduce memory usage.&lt;/li&gt;
  &lt;li&gt;Added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nakayoshi_fork&lt;/code&gt; config option. Reduce memory usage in preloaded cluster-mode apps by GCing before fork and compacting, where available. Intended result: If enabled, should reduce memory usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these experiments are only for &lt;strong&gt;cluster mode&lt;/strong&gt; Puma configs running on &lt;strong&gt;MRI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We’re calling them &lt;em&gt;experimental&lt;/em&gt; because we’re not sure if they’ll actually have any benefit. We’re pretty sure they’re stable and won’t break anything, but we’re not sure they’re actually going to have big benefits in the real world. People’s workloads are often not what we anticipate, and synthetic benchmarks are usually not of any help in figuring out if a change will be beneficial or not.&lt;/p&gt;

&lt;p&gt;We do not believe any of the new features will have a negative effect or impact the stability of your application. This is either a “it works” or “it does nothing” experiment.&lt;/p&gt;

&lt;p&gt;If any of the features turn out to be particularly beneficial, we may make them defaults in future versions of Puma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you upgrade and try any of the 3 new features, please post before and after results or screenshots to &lt;a href=&quot;https://github.com/puma/puma/issues/2258&quot;&gt;this Github issue&lt;/a&gt;.&lt;/strong&gt; “It didn’t do anything” is still a useful report in this case. Posting ~24 hours of “before” and ~24 hours of “after” data would be most helpful.&lt;/p&gt;

&lt;h3 id=&quot;wait_for_less_busy_worker-sleep-sort-for-faster-apps&quot;&gt;wait_for_less_busy_worker: sleep sort for faster apps?!&lt;/h3&gt;

&lt;p&gt;This feature was contributed to Puma by Gitlab. Turn it on by adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait_for_less_busy_worker&lt;/code&gt; to your Puma config.&lt;/p&gt;

&lt;p&gt;When a request comes in to a Puma cluster, the operating system randomly selects a listening, free Puma worker process to pick up the request. “Listening” and “free” being the key words - a Puma process will only listen to the socket (and pick up more requests) if it has nothing else to do. However, when running Puma with multiple threads, Puma will also listen on the socket when all of its busy threads are waiting on I/O or have otherwise released &lt;a href=&quot;/blog/the-ruby-gvl-and-scaling/&quot;&gt;the Global VM Lock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When Gitlab investigated switching from Unicorn to Puma, they encountered an issue with this behavior. Under high load with moderate thread settings (a max pool size of 5 in their case), average request latency increased. Why?&lt;/p&gt;

&lt;p&gt;Remember, I said that the operating system &lt;em&gt;randomly&lt;/em&gt; assigns a request to a &lt;em&gt;listening&lt;/em&gt; worker process. So, it will never send a request to a worker process that’s busy doing other things, but what about a worker process that’s got 4 threads that are processing other requests, but all 4 of those threads happen to be waiting on I/O right now?&lt;/p&gt;

&lt;p&gt;Imagine a Puma cluster with 3 workers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Worker 1: 0/5 threads busy.&lt;/li&gt;
  &lt;li&gt;Worker 2: 1/5 threads busy.&lt;/li&gt;
  &lt;li&gt;Worker 3: 4/5 threads busy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Worker 3’s 4 active threads happen to all have released the GVL, allowing that worker to listen to the socket, and a new request comes in - which worker process should we assign the request to, ideally? Worker 1, right? Unfortunately, most operating systems will assign the request to Worker 3 33% of the time.&lt;/p&gt;

&lt;p&gt;So, what do we do? We want the operating system to prefer less-loaded workers. It would be really cool if we could sort the list of workers listening on the socket so that the operating system would give requests to the least-loaded worker. Well, we can’t really do that easily, but we can do something else.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait_for_less_busy_worker&lt;/code&gt; causes a worker to &lt;em&gt;wait&lt;/em&gt; to re-listen on the socket if it’s thread pool isn’t completely empty. This means that in high-load scenarios, the operating system will assign requests to less-loaded workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is basically sleep-sorting our workers&lt;/strong&gt;. We’re kind of doing doing this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[].tap { |a| workers.map { |e| Thread.new{ sleep worker_busyness.to_f/1000; a &amp;lt;&amp;lt; e} }.each{|t| t.join} }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and hiding “more loaded” workers from the operating system by letting less-loaded workers listen first!&lt;/p&gt;

&lt;p&gt;Originally the proposal was for a more complicated sort - processes slept longer if they had more busy threads - but that was removed when it was found that a simpler on/off sleep was just as effective.&lt;/p&gt;

&lt;p&gt;The net effect is that in high-load scenarios, request latency decreases. This is because workers with more busy threads are slower than workers with no busy threads. We’re assuring that requests get assigned to the faster workers. Prior to this patch, Gitlab saw an increase in latency using Puma compared to Unicorn - after this patch, latency was the same (they also were able to reduce their fleet size by almost 30% thanks to Puma’s memory-saving multithreaded design).&lt;/p&gt;

&lt;p&gt;There may be even more efficient ways for us to implement this behavior in the future. There’s some magic you can do with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libev&lt;/code&gt;, I’m pretty sure, or we can just implement a different sleep/wait strategy.&lt;/p&gt;

&lt;h3 id=&quot;fork_worker&quot;&gt;fork_worker&lt;/h3&gt;

&lt;p&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork_worker&lt;/code&gt; to your puma.rb config file (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fork-worker&lt;/code&gt; from the CLI) turns on this feature. This mode causes Puma to fork additional workers from worker 0, instead of directly from the master process:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10000   \_ puma 5.0.0 (tcp://0.0.0.0:9292) [puma]
10001       \_ puma: cluster worker 0: 10000 [puma]
10002           \_ puma: cluster worker 1: 10000 [puma]
10003           \_ puma: cluster worker 2: 10000 [puma]
10004           \_ puma: cluster worker 3: 10000 [puma]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Similar to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preload_app!&lt;/code&gt; option, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork_worker&lt;/code&gt; option allows your application to be initialized only once for copy-on-write memory savings, and it has two additional advantages:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Compatible with phased restart.&lt;/strong&gt; Because the master process itself doesn’t preload the application, this mode works with phased restart (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGUSR1&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pumactl phased-restart&lt;/code&gt;), unlike &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preload_app!&lt;/code&gt;. When worker 0 reloads as part of a phased restart, it initializes a new copy of your application first, then the other workers reload by forking from this new worker already containing the new preloaded application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows a phased restart to complete as quickly as a hot restart (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGUSR2&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pumactl restart&lt;/code&gt;), while still minimizing downtime by staggering the restart across cluster workers.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;‘Refork’ for additional copy-on-write improvements in running applications.&lt;/strong&gt; Fork-worker mode introduces a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refork&lt;/code&gt; command that re-loads all nonzero workers by re-forking them from worker 0.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This command can potentially improve memory utilization in large or complex applications that don’t fully pre-initialize on startup, because the re-forked workers can share copy-on-write memory with a worker that has been running for a while and serving requests.&lt;/p&gt;

&lt;p&gt;You can trigger a refork by sending the cluster the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGURG&lt;/code&gt; signal or running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pumactl refork&lt;/code&gt; command at any time. A refork will also automatically trigger once, after a certain number of requests have been processed by worker 0 (default 1000). To configure the number of requests before the auto-refork, pass a positive integer argument to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork_worker&lt;/code&gt; (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork_worker 1000&lt;/code&gt;), or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; to disable.&lt;/p&gt;

&lt;h3 id=&quot;nakayoshi_fork&quot;&gt;nakayoshi_fork&lt;/h3&gt;

&lt;p&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nakayoshi_fork&lt;/code&gt; to your puma.rb config to try this option.&lt;/p&gt;

&lt;p&gt;Nakayoshi means “friendly”, so this is a “friendly fork”. The concept was &lt;a href=&quot;https://github.com/ko1/nakayoshi_fork&quot;&gt;originally implemented by MRI supercontributor Koichi Sasada&lt;/a&gt; in a gem, but we wanted to see if we could bring a simpler version into Puma.&lt;/p&gt;

&lt;p&gt;Basically, we just do the following before forking a worker:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compact&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# if available&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The concept here is that we’re trying to get as clean of a Ruby heap as possible before forking to maximize &lt;a href=&quot;https://en.wikipedia.org/wiki/Copy-on-write&quot;&gt;copy-on-write&lt;/a&gt; benefits. That should, in turn, lead to reduced memory usage.&lt;/p&gt;

&lt;h2 id=&quot;other-new-features&quot;&gt;Other New Features&lt;/h2&gt;

&lt;p&gt;A few more things in the grab-bag:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You can now compile Puma on machines where OpenSSL is not installed.&lt;/li&gt;
  &lt;li&gt;There is now a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thread-backtraces&lt;/code&gt; command in pumactl to print all active threads backtraces. This has been available via SIGINFO on Darwin, but now it works on Linux via this new command.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Puma.stats&lt;/code&gt; now has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requests_count&lt;/code&gt; counter.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lowlevel_error_handler&lt;/code&gt; got some enhancements - we also pass the status code to it now.&lt;/li&gt;
  &lt;li&gt;Phased restarts and worker timeouts should be faster.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Puma.stats_hash&lt;/code&gt; provides Puma statistics as a hash, rather than as JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;loads-of-bugfixes&quot;&gt;Loads of Bugfixes&lt;/h2&gt;

&lt;p&gt;The number of bugfixes in this release is pretty huge. Here’s the most important ones:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Shutdowns should be more reliable.&lt;/li&gt;
  &lt;li&gt;Issues surrounding socket closing on shutdown have been resolved.&lt;/li&gt;
  &lt;li&gt;Fixed some concurrency bugs in the Reactor.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;out_of_band&lt;/code&gt; should be much more reliable now.&lt;/li&gt;
  &lt;li&gt;Fixed an issue users were seeing with ActionCable and not being able to start a server.&lt;/li&gt;
  &lt;li&gt;Many stability improvements to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prune_bundler&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;nicer-internals-and-tests&quot;&gt;Nicer Internals and Tests&lt;/h2&gt;

&lt;p&gt;This release has seen a massive improvement to our test coverage. We’ve pretty much doubled the size of the test suite since 4.0, and it’s way more stable and reproducible now too.&lt;/p&gt;

&lt;p&gt;A number of breaking changes come with this major release. &lt;a href=&quot;https://github.com/puma/puma/blob/master/History.md&quot;&gt;For the complete list, see the HISTORY file.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;thanks-to-our-contributors&quot;&gt;Thanks to Our Contributors!&lt;/h2&gt;

&lt;p&gt;This release is our first major or minor release with new maintainer MSP-Greg on the team. Greg has been doing tons of work on the test suite to make it more reliable, as well as a lot of work on our SSL features to bring them up-to-date and more extendable. Greg is also our main Windows expert.&lt;/p&gt;

&lt;p&gt;The following people contributed more than 10 commits to this release:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/seven1m&quot;&gt;Tim Morgan&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/alexeevit&quot;&gt;Vyacheslav Alexeev&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wjordan&quot;&gt;Will Jordan&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jalevin&quot;&gt;Jeff Levin&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dentarg&quot;&gt;Patrik Ragnarsson&lt;/a&gt;, who’s also been very helpful in our Issues tracker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve like to make a contribution to Puma, please see our &lt;a href=&quot;https://github.com/puma/puma/blob/master/CONTRIBUTING.md&quot;&gt;Contributors Guide&lt;/a&gt;. We’re always looking for more help and try to make it as easy as possible to contribute.&lt;/p&gt;

&lt;p&gt;Enjoy Puma 5!&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Sep 2020 00:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/we-made-puma-faster-with-sleep-sort/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/we-made-puma-faster-with-sleep-sort/</guid>
        
        
      </item>
    
      <item>
        <title>The Practical Effects of the GVL on Scaling in Ruby</title>
        <description>&lt;p&gt;The Global Virtual Machine Lock confuses many Rubyists. Most Rubyists I’ve met have a vague sense that the GVL is somehow bad, and has something to do concurrency or parallelism.&lt;/p&gt;

&lt;p&gt; (‘CRuby’ refers to the mainline Ruby implementation, written in C. Sometimes people call this ‘MRI’.)
The GVL (formerly known as GIL, as you’re about to learn) is a unique feature to CRuby, and doesn’t exist in JRuby or TruffleRuby.&lt;/p&gt;

&lt;p&gt;JavaScript’s popular V8 virtual machine also has a VM lock. CPython also has a &lt;em&gt;global&lt;/em&gt; VM lock. That’s three of the most popular dynamic languages in the world! VM locks in dynamic languages are very common.&lt;/p&gt;

&lt;p&gt; (Instead of removing the GVL, Ruby core has signaled that it will take an approach similar to V8 Isolates with inspiration from the Actor concurrency model (discussed at the end).)&lt;/p&gt;

&lt;p&gt;Understanding CRuby’s Global VM Lock is important when thinking about scaling Ruby applications. It will probably never be removed from CRuby completely, and its behavior changes how we scale Ruby apps efficiently.&lt;/p&gt;

&lt;p&gt;Understanding what the GVL is and why the current GVL is “global” will help you to answer questions like these:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What should I set my Sidekiq concurrency to?&lt;/li&gt;
  &lt;li&gt;How many threads should I use with Puma?&lt;/li&gt;
  &lt;li&gt;Should I switch to Puma or Sidekiq from Unicorn, Resque, or DelayedJob?&lt;/li&gt;
  &lt;li&gt;What are the advantages of event-driven concurrency models, like Node?&lt;/li&gt;
  &lt;li&gt;What are the advantages of a global-lock-less language VM, like Erlang’s BEAM or Java’s JVM?&lt;/li&gt;
  &lt;li&gt;How will Ruby’s concurrency story change in Ruby 3?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll deal with these questions and more in this article.&lt;/p&gt;

&lt;h2 id=&quot;what-were-locking-the-language-virtual-machine&quot;&gt;What we’re locking: the language virtual machine&lt;/h2&gt;

&lt;p&gt; (Most descriptions of the GVL immediately dive into concepts like atomicity and thread-safety. This description will start from a more basic premise and work up to that.)&lt;/p&gt;

&lt;p&gt; (YARV was essentially &lt;a href=&quot;https://en.wikipedia.org/wiki/YARV&quot;&gt;Koichi Sasada’s graduate thesis.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Wait: isn’t it the GIL? What’s the GVL?&lt;/p&gt;

&lt;p&gt;GIL stands for Global Interpreter Lock, and it’s something that was removed from Ruby (or just mutated, depending on how you look at it) in Ruby 1.9, when Koichi Sasada introduced YARV (Yet Another Ruby VM) to Ruby. YARV changed CRuby’s internal structure so that the lock existed around the Ruby virtual machine, not an interpreter. The correct terminology for over a decade now has been GVL, not GIL.&lt;/p&gt;

&lt;p&gt; (You can interact with instruction sequences &lt;a href=&quot;https://ruby-doc.org/core-2.5.1/RubyVM/InstructionSequence.html&quot;&gt;via the InstructionSequence class&lt;/a&gt;. Everything is an object in Ruby!)&lt;/p&gt;

&lt;p&gt;How does an interpreter differ from a virtual machine?&lt;/p&gt;

&lt;p&gt;A virtual machine is a little like a CPU-within-a-CPU. Virtual machines are computer programs that usually take simple instructions, and those instructions manipulate some internal state. A &lt;a href=&quot;https://en.wikipedia.org/wiki/Turing_machine&quot;&gt;Turing machine&lt;/a&gt;, if it was implemented in software, would be a kind of virtual machine. We call them virtual machines and not machines because they’re implemented in software, rather than in hardware, like a CPU is.&lt;/p&gt;

&lt;p&gt;Before Ruby 1.9, Ruby didn’t really have a separate virtual machine step - it just had an interpreter. As your Ruby program ran, it actually interpreted each line of Ruby as it went. Now, we just interpret the code once, turn it into a series of VM instructions, and then execute those instructions. This is much faster than interpreting Ruby constantly.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/turingmachine.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;A Turing machine, implemented in software, would be a kind of virtual machine. &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:TuringBeispielDiskretAnimatedGIF_uk.gif&quot;&gt;Wikimedia Commons by RosarioVanTuple&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The Ruby Virtual Machine understands a simple instruction set. Those instructions are generated from the Ruby code you write by the interpreter, and then the virtual machine instructions are fed into the Ruby VM.&lt;/p&gt;

&lt;p&gt;Let’s watch this in action. First, in case you didn’t know, you can execute Ruby from the command line using the -e option:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ruby -e &quot;puts 1 + 1&quot;
2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/escanor_stack_meme_opt.jpeg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;
Now, you can then dump the instructions for this simple program by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dump=insns&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ruby --dump=insns -e &quot;puts 1 + 1&quot;
== disasm: #&amp;lt;ISeq:&amp;lt;main&amp;gt;@-e:1 (1,0)-(1,10)&amp;gt; (catch: FALSE)
0000 putself                                                          (   1)[Li]
0001 putobject_INT2FIX_1_
0002 putobject_INT2FIX_1_
0003 opt_plus                     &amp;lt;callinfo!mid:+, argc:1, ARGS_SIMPLE&amp;gt;, &amp;lt;callcache&amp;gt;
0006 opt_send_without_block       &amp;lt;callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE&amp;gt;, &amp;lt;callcache&amp;gt;
0009 leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ruby is a “stack-based” VM. You can see how this works by looking at the generated instructions here - we add the integer 1 to the stack two times, than call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plus&lt;/code&gt;. When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plus&lt;/code&gt; is called, there are two integers on the stack. Those two integers are replaced by the result, 2, which is then on the stack.&lt;/p&gt;

&lt;p&gt;So, what does the Ruby VM have to do with threading, concurrency and parallelism?&lt;/p&gt;

&lt;h2 id=&quot;concurrency-and-paralellism&quot;&gt;Concurrency and Paralellism&lt;/h2&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/checkout_counter.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;You may be aware that there’s a difference between concurrency and parallelism. Imagine a grocery store. At this grocery store, we have a line and some checkout clerks working to pull customers from the line and get their groceries checked out.&lt;/p&gt;

&lt;p&gt;Each of our grocery store checkout clerks works in parallel. They don’t need to talk to each other to do their job, and what one clerk is doing doesn’t affect the other in any way. They’re working 100% in parallel.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/concurrent_checkout.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Now, a clerk &lt;em&gt;can&lt;/em&gt; work on multiple customers &lt;em&gt;concurrently&lt;/em&gt;. This would look like a clerk grabbing multiple customers from the line, working on one customer’s groceries for a moment, then switching to another customer’s groceries, and so on. This would be working concurrently.&lt;/p&gt;

&lt;p&gt;Let’s take a more concrete example. Compare three grocery store clerks working in parallel with a single one working concurrently. To check out a customer, we must perform two operations: scanning their groceries, and then bagging them. Imagine each customer’s groceries take the exact same amount of time to scan and bag.&lt;/p&gt;

&lt;p&gt;Three customers arrive. Let’s say scanning takes time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; and bagging takes time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt;. Our three parallel clerks will process these three customers in time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(A + B)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/parallel.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;The parallel case.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;What about our concurrent clerk? All three of her customers arrive at the same time. The clerk scans each customer’s groceries, then bags each customer’s groceries. Each customer is worked on concurrently, but never in parallel.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/concurrency.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;The concurrent case.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;In the concurrent case, our first customer experiences a total service time of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(3A + B)&lt;/code&gt;. They had to wait for everyone else’s groceries to be checked out for their own groceries to get bagged. The second customer will exerience a total service time of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(3A + 2B)&lt;/code&gt;, and the final customer will experience a service time of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(3A + 3B)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice how the customers who got the concurrent checkout clerk experienced a longer total service time than the customers who used our three parallel clerks.&lt;/p&gt;

&lt;p&gt;In short: &lt;strong&gt;concurrency is interesting, but parallelism is what speeds up systems and allows them to handle increased load&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Performing two operations concurrently means that the start and end times of those operations overlapped at some point. For example, you and I sit down to a sign a contract. However, there is only one pen. I sign where I’m supposed to, hand the pen to you, and then you sign. Then, you hand the pen back to me and I initial a few lines. You might say that we signed the contract concurrently, but never in parallel - there was only one pen, so we couldn’t sign the contract at the exact same time.&lt;/p&gt;

&lt;p&gt;Peforming operations in parallel means that we are doing those operations &lt;em&gt;at the exact same instant&lt;/em&gt;. In my contract example, a parallel contract signing would involve two pens (and probably two copies of the contract, otherwise it would get a little crowded).&lt;/p&gt;

&lt;h2 id=&quot;concurrency-and-paralellism-on-a-computer&quot;&gt;Concurrency and Paralellism on a Computer&lt;/h2&gt;

&lt;p&gt;On a modern operating system, programs are run with a combination of processes and threads. Processes have at least one thread, and can have up to thousands.&lt;/p&gt;

&lt;p&gt;To extend the grocery store analogy, processes are like the checkout counters that our clerks use. They contain tools and common resources, like the point-of-sale terminal and the barcode scanner, but they don’t actually &lt;em&gt;do&lt;/em&gt; anything. A process usually contains a memory allocation (the heap), file descriptors (sockets, files, etc), and other such computer resources.&lt;/p&gt;

&lt;p&gt;Threads actually run our code. Each process has at least one thread. In our analogy, they’re like the store clerks. They also hold a small amount of information. For example, if we’re adding two local variables in a Rails application, our thread contains information about those two variables (&lt;em&gt;thread-local storage&lt;/em&gt;) and also what line of code we’re currently running (the &lt;em&gt;stack&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/pentium.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Threads run the code when they are scheduled to by the operating system’s kernel. The Ruby runtime itself doesn’t actually manage when threads are executed - the operating system decides that.&lt;/p&gt;

&lt;p&gt;When Ruby was written in the 90s, all processes had just one thread. This started to change in the early 2000s, necessitating the rewrite of the language VM in Ruby 1.9 (YARV), which is what gave us the GVL as we know it today.&lt;/p&gt;

&lt;h2 id=&quot;what-the-gvl-actually-does&quot;&gt;What the GVL actually does&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, the Ruby Virtual Machine is what actually turns Ruby virtual machine instructions (generated by the interpreter) into CPU instructions.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/vm_lock_bernie.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The Ruby Virtual Machine is not internally thread-safe. If two threads try to access the Ruby VM at the same time, really Bad Things would happen. This is a bit like the point of sale terminal at our grocery store checkout counters. If two checkout clerks tried to use the same POS terminal, they would interrupt each other and probably keep losing their work or corrupting each other’s work. You would end up paying for someone else’s groceries!&lt;/p&gt;

&lt;p&gt;So, because it isn’t safe for multiple threads to access the Ruby Virtual Machine at the same moment, instead we use a global lock around it so that only one thread can access it in parallel.&lt;/p&gt;

&lt;p&gt; (One caveat of the Javascript GVL is that it isn’t actually global: you can create additional Isolates. Koichi Sasada’s proposal for Ractors (formerly Guilds) would be similar.)&lt;/p&gt;

&lt;p&gt;It is extremely common for dynamic language VMs to not be thread-safe. As mentioned, CPython and V8 are the most prominent examples. Java is probably the best example of a semi-dynamic language that &lt;em&gt;does&lt;/em&gt; have a threadsafe VM. It’s also why so many languages are written on top of the JVM: writing your own threadsafe VM is really hard.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/realize.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;TFW you realize that there’s always going to be locks, the only difference is what level they’re implemented at and who implements them&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;There’s a few good reasons that having a GVL is so popular:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s faster. Single-threaded performance improves because you don’t have to constantly lock and unlock internals.&lt;/li&gt;
  &lt;li&gt;Integrating with extensions, such as C extensions, is easier.&lt;/li&gt;
  &lt;li&gt;It’s easier to write a lockless VM than one with a lot of locks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each Ruby process has its own Global VM Lock, so it might be more accurate to say that it’s a “process-wide VM lock”. Its “global” in the same sense that a “global variable” is global.&lt;/p&gt;

&lt;p&gt;Only one thread in any Ruby process can hold the global VM lock at any given time. Since a thread needs access to the Ruby Virtual Machine to actually run any Ruby code, effectively only one thread can run Ruby code at any given time.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/songofmyppl.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Let me play you the song of my people: “&lt;em&gt;GGVVVVLLLLLLLLLLLLLLLL&lt;/em&gt;”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Think of the GVL like the conch shell in the Lord of the Flies - if you have it, you get to speak (or execute Ruby code in this case). If the GVL is already locked by a different thread, other threads must wait for the GVL to be released before they can hold the GVL.&lt;/p&gt;

&lt;h2 id=&quot;amdahls-law-why-1-sidekiq-process-can-be-2x-as-efficient-as-delayedjob-or-resque&quot;&gt;Amdahl’s Law: Why 1 Sidekiq Process Can Be 2x as Efficient as DelayedJob or Resque&lt;/h2&gt;

&lt;p&gt;Your programs actually do many things that don’t need to access the Ruby Virtual Machine. The most important is waiting on I/O, such as database and network calls. These actions are executed in C, and the GVL is explicitly released by the thread waiting on that I/O to return. When the I/O returns, the thread attempts to reacquire the GVL and continue to do whatever the program says.&lt;/p&gt;

&lt;p&gt;This has enormous real-world performance impacts.&lt;/p&gt;

&lt;p&gt;Imagine you have a stack of satellite image data you have to process (with Ruby). You have written a Sidekiq job, called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SatelliteDataProcessorJob&lt;/code&gt;, and each job works on a small fraction of all of the satellite data.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SatelliteDataProcessorJob&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sidekiq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Worker&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_satellite_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_satellite_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;touch_external_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_satellite_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_data_to_database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_satellite_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s imagine that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process&lt;/code&gt; is a 100% Ruby method, which does not call C extensions or external services. Further, let’s imagine that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch_external_service&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add_data_to_database&lt;/code&gt; are effectively 100% I/O methods that spend all of their time waiting on the network.&lt;/p&gt;

&lt;p&gt;First, an easy question: if each run of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SatelliteDataProcessorJob&lt;/code&gt; takes 1 second, and you have 100 enqueued jobs and just 1 Sidekiq process with 1 thread, how long will it take to process all the jobs? Assume infinite CPU and memory resources.&lt;/p&gt;

&lt;p&gt;100 seconds.&lt;/p&gt;

&lt;p&gt;How about if you two processes? 50 seconds. And 25 seconds for 4 processes and so on. That’s parallelism.&lt;/p&gt;

&lt;p&gt;Now, let’s say you have 1 Sidekiq process with 10 threads. How long will it take to process all of those jobs?&lt;/p&gt;

&lt;p&gt;The answer is &lt;em&gt;it depends&lt;/em&gt;. If you’re on JRuby or TruffleRuby, it will take about 10 seconds, because each thread is fully parallel with all the other threads.&lt;/p&gt;

&lt;p&gt;But on MRI, we have the GVL. Does adding threads increase concurrency?&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/AmdahlsLaw.svg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;From &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:AmdahlsLaw.svg&quot;&gt;Daniels 220 @ Wikipedia&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;It turns out, this exact problem interested computer scientist Gene Amdahl back in 1967. He proposed something called Amdahl’s Law, which gives the theoretical speedup in latency for the execution of tasks with fixed workloads when resources increase.&lt;/p&gt;

&lt;p&gt;Amdahl figured out that the speedup you got from additional parallelism was related to the proportion of execution time that could be done in parallel. Sound familiar?&lt;/p&gt;

&lt;p&gt;Amdahl’s Law is simply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1 / (1 - p + p/s)&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; is the percentage of the task that could be done in parallel, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; is the speedup factor from the part of the task that gained improved resources (the parallel part).&lt;/p&gt;

&lt;p&gt;So, in our example, let’s say that half of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SatelliteDataProcessorJob&lt;/code&gt; is GVL-bound and half is IO-bound. In this case, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.5&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; is 10, because we can wait for IO in parallel and there are 10 threads. &lt;strong&gt;In this case, Amdahl’s Law shows that a Sidekiq process would go through our jobs up to 1.81x faster than a single-threaded Resque or DelayedJob process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many background jobs in Ruby spend at least 50% of their time waiting on IO. For those jobs, Sidekiq can lead to a 2x decrease in resource usage, because 1 Sidekiq process can do the work of what used to take 2 single-threaded processes.&lt;/p&gt;

&lt;p&gt;So, even with a GVL, adding threads to applications increases throughput-per-process, which in turn lowers memory consumption.&lt;/p&gt;

&lt;h2 id=&quot;threads-puma-and-gvl-caused-latency&quot;&gt;Threads, Puma and GVL-caused Latency&lt;/h2&gt;

&lt;p&gt;This also means that “how many threads does my Sidekiq or Puma process need” is a question answered by “how much time does that thread spend in non-GVL execution?” or “how much time does my program spend waiting on I/O?” Workloads with high percentages of time spent in I/O (75%+ or more) often benefit from 16 threads or even more, but more typical workloads see benefit from just 3 to 5 threads.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/paralellizable.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;It’s possible to configure your thread pools to be &lt;em&gt;too large&lt;/em&gt;. Setting Puma or Sidekiq to thread settings higher than 5 can lead to contention for the GVL if the work is not parallelizable enough. This increases service latency.&lt;/p&gt;

&lt;p&gt;While total time to process all of the units of work remains the same, the latency experienced by each individual unit of work increases.&lt;/p&gt;

&lt;p&gt;Imagine a grocery store where a checkout clerk grabbed 16 people off of the checkout queue and checked those 16 people’s groceries concurrently, scanning one item per person before scanning one item from the next person’s cart. Rather than experiencing checkout time as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(A + B)&lt;/code&gt;, they experience a checkout time of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16(A+B)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt; (This effect is generally present in a concurrent-but-not-100%-parallel system where overall utilization is not extremely high. &lt;a href=&quot;https://github.com/puma/puma/pull/2079&quot;&gt;We’re mitigating this effect slightly in Puma 5&lt;/a&gt; by having Puma workers with more than one thread delay listening to the socket, so less-loaded workers pick up requests first.)&lt;/p&gt;

&lt;p&gt;Some people misidentify this additional latency as “context switching” costs. However, latency experienced by the individual unit of work is increasing  &lt;em&gt;without additional switching cost&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In any case, context switching on modern machines and operating systems is pretty cheap relative to the time it takes to service a typical web app request or background job. It does not add hundreds of milliseconds to response times - but oversaturating the GVL can.&lt;/p&gt;

&lt;p&gt;If adding threads to a CRuby process can increase latency, why is it still useful?&lt;/p&gt;

&lt;p&gt; (Shouldn’t adding an additonal thread only increase memory usage by 8MB, which is the size of the thread’s stack allocation? Ah, if only memory usage was that simple. &lt;a href=&quot;/blog/malloc-doubles-ruby-memory/&quot;&gt;Learn more about the complexities of RSS and thread-induced fragmentation here.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding more threads to a Ruby process helps us to improve CPU utilization at less memory cost than an entire additional process.&lt;/strong&gt; Adding 1 process might use 512MB of memory, but adding 1 thread will probably cause less than 64MB of additional memory usage. With 2 threads instead of 1, when the first thread releases the GVL and listens on I/O, our 2nd thread can either pick up new work to do, increasing throughput and utilization of our server.&lt;/p&gt;

&lt;p&gt;GitLab switched from Unicorn (single-thread model) to Puma (multi-thread model) and &lt;a href=&quot;https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7455#note_239070865&quot;&gt;saw a 30% decrease in memory usage across their fleet.&lt;/a&gt; If you’re memory-constrained on your host, this allows you to run 30% more throughput for the same money. That’s awesome.&lt;/p&gt;

&lt;h2 id=&quot;the-future&quot;&gt;The Future&lt;/h2&gt;

&lt;p&gt;For a decade now, bystanders have declared that Ruby is dead because it “doesn’t have a proper concurrency story”.&lt;/p&gt;

&lt;p&gt;I think we’ve shown that there is a concurrency story in Ruby. First, we have process-based concurrency. We multiply GVLs by multiplying processes. This works perfectly fine, if you have enough memory.&lt;/p&gt;

&lt;p&gt;If you’re out of memory, you can use Sidekiq or Puma, which provides a threaded container for our apps, and then let pre-emptive threading do its thing.&lt;/p&gt;

&lt;p&gt;Ruby has proven that process-based concurrency (which is really what the GVL forces us to do) scales well. It’s not much more expensive than other models, especially these days when memory is so cheap on cloud providers. Think critically about what an Actor-style approach or an Erlang Process-style approach would &lt;em&gt;actually change&lt;/em&gt; about your deployment at the end of the day: you would use less memory per CPU. But on large deployments, most web applications are already CPU-bottlenecked, not memory!&lt;/p&gt;

&lt;h4 id=&quot;ractor&quot;&gt;Ractor&lt;/h4&gt;

&lt;p&gt;Koichi Sasada, author of YARV, is proposing a new concurrency abstraction for Ruby 3 called Ractors. It’s a proposal based on the Actor concurrency model (hence Ruby Actor -&amp;gt; Ractor). Basically, Actors are boxes for objects to go into, and each actor can only touch its own objects, but can send and receive objects to/from other Actors. Here’s an example written by Koichi Sasada:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;r&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;r0&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;r0r10r9r8r7r6r5r4r3r2r1&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Eventually (not yet in the current implementation), each Ractor will get their own VM lock. That means the example code above will execute in parallel.&lt;/p&gt;

&lt;p&gt;This is made possible because Ractors don’t share mutable state. Instead, they only share immutable objects, and can send mutable objects between each other. This should mean that we don’t need a VM lock inside of a Ractor.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ruby/ruby/compare/master...ko1:ractor&quot;&gt;Koichi Sasada’s Ractor proposal is now public&lt;/a&gt;, though as of this writing the docs are mostly in Japanese, and “each Ractor gets its own VM lock” has not yet been implemented. Ractors will essentially allow us to “multiply” GVLs in a process, which would make the GVL no longer “global”, although the lock will still exist in each Ractor. The Global VM Lock will become a Ractor VM Lock.&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL:DR;&lt;/h2&gt;

&lt;p&gt;Thanks for listening to me whinge. Here’s what you need to remember:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you are memory bottlenecked on Ruby, you need to &lt;strong&gt;saturate the GVL&lt;/strong&gt; by adding more threads, which will allow you to get &lt;em&gt;more CPU work done with less memory use&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;The GVL means that parallelism is limited to I/O in Ruby, so &lt;strong&gt;switch to a multithreaded background job processor before you switch to a multithreaded web server&lt;/strong&gt;. Also, you’ll probably use much higher threadpool sizes with your background jobs than with your web server.&lt;/li&gt;
  &lt;li&gt;Ruby 3 &lt;strong&gt;might make the GVL no longer global&lt;/strong&gt; by allowing you to multiply VMs using Ractors. Application servers and background job processors will probably change their backend to take advantage of this, you won’t really have to change much of your code at all, but you will no longer have to worry about thread safety (yay).&lt;/li&gt;
  &lt;li&gt;Process based concurrency scales very well, and while it might lose a few microseconds to other concurrency models, these &lt;strong&gt;concurrency switching costs generally don’t matter for the typical Rails application&lt;/strong&gt;. Instead, the important thing is saturating CPU, which is the most scarce resource in today’s computing environments.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 11 May 2020 00:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/the-ruby-gvl-and-scaling/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/the-ruby-gvl-and-scaling/</guid>
        
        
      </item>
    
      <item>
        <title>The World Follows Power Laws: Why Premature Optimization is Bad</title>
        <description>&lt;p&gt; (This post is a sample of the content available in the &lt;a href=&quot;https://railsspeed.com&quot;&gt;Complete Guide to Rails Performance&lt;/a&gt;. It’s actually the first lesson - there are 30+ more lessons and 18 hours of video in the course itself.)&lt;/p&gt;

&lt;p&gt;I want to tell you about a physicist from Schenectady, a Harvard linguist,
and an Italian economist.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/pareto.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Pareto’s poltiical views are bit suspect, unfortunately, because he chose to see the way things &lt;em&gt;are&lt;/em&gt; (unequal) as the way they &lt;em&gt;ought to be&lt;/em&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The Italian economist you may already have heard of - Vilifredo Pareto. He became
famous for something called &lt;strong&gt;The Pareto Principle&lt;/strong&gt;, the idea that for most
things, 80% of the effect comes from just 20% of the causes. The Pareto
Principle is fundamental to performance work because it reminds us why premature optimization is so inefficient and useless.
While you’ve probably &lt;em&gt;heard&lt;/em&gt;
of the Pareto Principle, I want you to &lt;em&gt;understand why&lt;/em&gt; it actually works. And
to do that, we’re going to have to talk about probability distributions.&lt;/p&gt;

&lt;h2 id=&quot;benford---the-physicist&quot;&gt;Benford - the physicist&lt;/h2&gt;

&lt;p&gt;Frank Benford was an American electrical engineer and physicist who worked for
General Electric. It was the early 20th century, when you had a job for life
rather than a startup gig for 18 months, so he worked there from the day he
graduated from the University of Michigan until his death 38 years later in 1948.
&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/logtables.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;A page from Henry Briggs’ first table of common logarithms, Logarithmorum Chilias Prima, from 1617. &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Logarithmorum_Chilias_Prima_page_0-67.jpg&quot;&gt;Wikipedia&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Back in that time, before calculators, if you wanted to know the logarithm
of a number - say, 12 - you looked it up in a book. The books were usually
organized by the leading digit. If you wanted to know the logarithm of 330, you
first went to the section for 3, then looked for 330. Benford noticed that the
first pages of the book were far more worn out than the last pages. Benford
realized this meant that the numbers looked up in the table began more often
with 1 than with 9.&lt;/p&gt;

&lt;p&gt;Most people would have noticed that and thought nothing of it. But Benford
pooled 20,000 numbers from widely divergent sources (he used the numbers in
newspaper stories) and found that the leading digit of all those numbers
followed a power law too.&lt;/p&gt;

&lt;p&gt;This became known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Benford%27s_law&quot;&gt;Benford’s Law&lt;/a&gt;. Here are some other sets of numbers that
conform to this power law:&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/physicalconstants.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Physical constants of the universe (pi, the molar constant, etc.)&lt;/li&gt;
  &lt;li&gt;Surface areas of rivers&lt;/li&gt;
  &lt;li&gt;Fibonacci numbers&lt;/li&gt;
  &lt;li&gt;Powers of 2&lt;/li&gt;
  &lt;li&gt;Death rates&lt;/li&gt;
  &lt;li&gt;Population censuses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That the &lt;em&gt;physical constants of the universe&lt;/em&gt; follow this
distribution is probably the most mind-blowing revelation of Benford’s Law, for
me, anyway.&lt;/p&gt;

&lt;p&gt;Benford’s Law is so airtight that it’s been admitted in US courts as evidence of
accounting fraud (someone used RAND in their Excel sheet!). It’s been used to
identify other types of fraud too - elections, scientific and even macroeconomic data.&lt;/p&gt;

&lt;p&gt;What would cause numbers that have (seemingly) little relationship with each
other to conform so perfectly to this non-random distribution?&lt;/p&gt;

&lt;h2 id=&quot;zipf---the-linguist&quot;&gt;Zipf - the linguist&lt;/h2&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/zipf_wiki.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;A plot of
the rank versus frequency for the first 10 million words in 30 different
languages of Wikipedia. Note the logarithmic scales. &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Zipf_30wiki_en_labels.png&quot;&gt;Licensed CC-BY-SA by SergioJimenez.&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;At almost exactly the same time as Benford was looking at first leading digits,
George Kingsley Zipf was studying languages at
Harvard. Uniquely, George was applying the techniques of a new and interesting
field - statistics - to the study of language. This landed him an astonishing
insight: in nearly every language, some words are used a lot, but most (nearly
all) words are used hardly at all.&lt;/p&gt;

&lt;p&gt;Only a few words account for most of our use of language. The Brown Corpus is a collection of literature used by linguistics researchers.
It consists of 500 samples of English-language text comprising 1 million words.
Just 135 unique words are needed to account for 50% of those million words.
That’s insane.&lt;/p&gt;

&lt;p&gt;Zipf’s probability distribution is &lt;em&gt;discrete&lt;/em&gt;. Discrete
distributions are comprised of whole integers. Continuous distributions can take
on any value. If you take Zipf’s distribution and make it continuous instead of
discrete, you get the Pareto distribution.&lt;/p&gt;

&lt;h2 id=&quot;pareto---the-economist&quot;&gt;Pareto - the economist&lt;/h2&gt;

&lt;p&gt;Pareto initially noticed a curious distribution when he was thinking about
wealth in society - he noticed that 80% of the wealth and income came from 20%
of the people in it.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/pareto.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The Pareto distribution, pictured, has been found to hold for a scary number of
completely different and unrelated fields in the sciences. For example, here are
some natural phenomena that exhibit a Pareto (power law) distribution:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Wealth inequality&lt;/li&gt;
  &lt;li&gt;Sizes of rocks on a beach&lt;/li&gt;
  &lt;li&gt;Hard disk drive error rates (!)&lt;/li&gt;
  &lt;li&gt;File size distribution of Internet traffic (!!!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We tend to think of the natural world as random or chaotic. In schools, we’re
taught the bell curve/normal distribution. &lt;strong&gt;But reality isn’t normally
distributed.&lt;/strong&gt; It’s log-normal. Many probability distributions, in the wild,
support the Pareto Principle:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;80% of the output will come from 20% of the input&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/Normal_Distribution_PDF.svg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Normal distributions are taught in schools because they’re quite easy to talk about mathematically, not because they’re particularly good descriptions of the natural world.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;While you may have heard this before, what I’m trying to get across to you is
that it isn’t made up. The Pareto distribution is used in hundreds of otherwise
completely unrelated scientific fields - and we can use its ubiquity to our
advantage.&lt;/p&gt;

&lt;p&gt;It doesn’t matter what area you’re working in - if you’re applying equal effort
to all areas, you &lt;em&gt;are wasting your time&lt;/em&gt;. What the Pareto distribution shows us
is that most of the time, our efforts would be better spent &lt;em&gt;finding&lt;/em&gt; and
&lt;em&gt;identifying&lt;/em&gt; the crucial 20% that accounts for 80% of the output.&lt;/p&gt;

&lt;p&gt;Allow me to reformulate and apply this to web application performance:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;80% of an application’s work occurs in 20% of its code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are other applications in our performance realm too:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;80% of an application’s traffic will come from 20% of its features.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;80% of an application’s memory usage will come from 20% of its allocated
objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The ratio isn’t always 80/20. Actually, usually it’s way more severe - 90/10,
95/5, 99/1. Sometimes it’s less severe. So long as it isn’t 50/50 we’re talking
about a non-normal distribution.&lt;/p&gt;

&lt;p&gt;This is why premature optimization is so bad and why performance monitoring,
profiling and benchmarking are so important. The world is full of power-law distributions, not normal distributions. Spreading your effort evenly across a power-law distribution is a massive waste of effort.&lt;/p&gt;

&lt;p&gt;What the Pareto Principle reveals to us is that optimizing any random line of code in our application is in fact
unlikely to speed up our application at all! 80% of the “slowness” in any given
app will be hidden away in a minority of the code.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/haskell.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;So instead of optimizing
blindly, applying principles at random that we read from blog posts, or engaging
in Hacker-News-Driven-Development by using the latest and “most performant” web
technologies, we need to measure where the bottlenecks and problem areas are in
our application.&lt;/p&gt;

&lt;h2 id=&quot;an-optimization-story---measurement-profiling-and-benchmarking&quot;&gt;An Optimization Story - Measurement, Profiling and Benchmarking&lt;/h2&gt;

&lt;p&gt;There’s only one skill in performance work that you need to understand
completely and deeply - how to &lt;em&gt;measure&lt;/em&gt; your application’s performance. Once
you have that skill mastered, knowing every possible thing about performance might
be a waste of time. Your problems are not other’s problems. There are going to
be lessons to learn that solve problems you don’t have (or don’t comprise
that crucial 20% of the causes of slowness in your application).&lt;/p&gt;

&lt;p&gt;On the flip side, you should realize that the Pareto Principle is extremely
liberating. You &lt;em&gt;don’t&lt;/em&gt; need to fix every performance issue in your application.
You don’t need to go line-by-line to look for problems under every rock. You
need to &lt;em&gt;measure&lt;/em&gt; the actual performance of your application, and focus on the
20% of your code that is the worst performance offender.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/minitest_knows.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;My first conference talk ever, actually.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;I once gave &lt;a href=&quot;https://www.youtube.com/watch?v=ojd1G4gOMdk&quot;&gt;a conference talk that was a guided read-through of Minitest&lt;/a&gt;, the
Ruby testing framework. Minitest is a great read if you’ve got a spare hour or two -
it’s fairly short at just 1,500 lines. As I was reading Minitest’s code, I
came across this funny line:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;runnable_methods&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;methods_matching&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^test_/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_order&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:parallel&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:sorted&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Unknown test_order: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code is extremely readable as to what’s going on; we determine which
methods on a class are runnable with a regex (“starts with test_”), and then
sort them depending upon this test class’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test_order&lt;/code&gt;. Minitest uses the
return value to execute all of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runnable_methods&lt;/code&gt; on all the test classes
you give it. Usually this is a randomized array of method names, because the default test order is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:random&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What I was honing in on was this line, which is run when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:test_order&lt;/code&gt; is
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:random&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:parallel&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This seemed like a really roundabout way to do  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;methods.shuffle&lt;/code&gt; to me. Maybe
Ryan (Minitest’s author) was doing some weird thing to ensure deterministic
execution given a seed. Minitest runs your tests in the same order given the
same seed to the random number generator. It turns out methods.shuffle is
deterministic, though, just like the code as written. So, I decided to benchmark
it, mostly out of curiosity.&lt;/p&gt;

&lt;p&gt;Whenever I need to write a micro benchmark of Ruby code, I reach for
&lt;a href=&quot;https://github.com/evanphx/benchmark-ips&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;benchmark/ips&lt;/code&gt;&lt;/a&gt;. (The reason I use benchmark/ips rather than the stdlib benchmark is because
the stdlib version requires you to run a certain line of code X number of times
and tells you how long that took. The problem with that is that I don’t usually
know how fast the code is to begin with, so I have no idea how to set X. Usually
I run the code a few times, guess at a number of X that will make the benchmark
take 10 seconds to run, and then move on. benchmark/ips does that work for me by
running my benchmark for 10 seconds and calculating iterations-per-second.) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ips&lt;/code&gt; stands
for iterations-per-second. The gem is an extension of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Benchmark&lt;/code&gt; module,
something we get in the Ruby stdlib.&lt;/p&gt;

&lt;p&gt;Here’s that benchmark:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;benchmark/ips&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestBench&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;methods&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@methods&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fast&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shuffle&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;slow&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TestBench&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;faster alternative&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fast&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;current minitest code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slow&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This benchmark suggested that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle&lt;/code&gt; was 12x faster than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sort.sort_by {
rand methods.size }&lt;/code&gt;. This makes sense - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle&lt;/code&gt; randomizes the array with C,
which will always be faster than randomizing it with pure Ruby. In addition,
Ryan was actually sorting the array twice - once in alphabetical order, followed
by a random shuffle based on the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/ryans_talk.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5KVcsV_jseQ&quot;&gt;Ryan’s conference talks&lt;/a&gt; are pretty good, too.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;I asked Ryan Davis, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minitest&lt;/code&gt; author, what was up with this. He gave me a great
reply: “you benchmarked it, but did you profile it?”&lt;/p&gt;

&lt;p&gt;What did he mean by this? Well, first, you have to know the difference between
&lt;strong&gt;benchmarking and profiling - the two fundamental performance measurement tools.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of different ways to define this difference. Here’s my attempt:&lt;/p&gt;

&lt;h3 id=&quot;benchmarking&quot;&gt;Benchmarking&lt;/h3&gt;

&lt;p&gt;A benchmark is a test of one or many different pieces of code that measures
how fast they execute or how many resources they consume.&lt;/p&gt;

&lt;p&gt;When we benchmark, we take two competing pieces of code and compare them. It could be as simple as a one liner, like in my story, or as
complex as an entire web framework. Then, we put them up against each other
(usually comparing them in terms of iterations/second) using a simple,
contrived task. At the end of the task, we come up with a single metric - a
score. We use the score to compare the two competing options.&lt;/p&gt;

&lt;p&gt;In my example
above, it was just how fast each line could shuffle an array. If you were
benchmarking web frameworks, you might test how fast a framework can return a
simple “Hello World” response. Benchmarks put the competing alternatives on
exactly equal footing by coming up with a contrived, simple, non-real-world
example.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/rails-sucks.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;a href=&quot;/blog/is-ruby-too-slow-for-web-scale/&quot;&gt;I wrote a v v long post once about why this benchmark doesn’t mean much for Rails&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;It’s usually too difficult to benchmark real-world code because the
alternatives aren’t doing &lt;em&gt;exactly&lt;/em&gt; the same thing. For example, comparing
Rails against Sinatra isn’t entirely fair because Rails has many features that
Sinatra does not - even for a simple Hello World response, the Rails
application is, for example, performing many security checks that the Sinatra
app doesn’t. Comparing these frameworks in a 1-to-1 benchmark will always be
slightly misleading for that reason.&lt;/p&gt;

&lt;h3 id=&quot;profiling&quot;&gt;Profiling&lt;/h3&gt;

&lt;p&gt;Profiles are a accounting of all the sub-steps required to run a
given piece of code. When we profile, we’re
usually examining the performance characteristics of an entire, real-world
application. For example, this might be a web application or a test suite.
Because profiling works with real-world code, we can’t really use it to
compare competing alternatives, because the alternative usually doesn’t
exactly match what we’re profiling. Profiling doesn’t usually produce a
comparable “score” at the end with which to measure these alternatives,
either. But that’s not to say profiling is useless - it can tell us a lot of
valuable things, like what percentage of CPU time was used where, where memory
was allocated, and what lines of code are important and which ones aren’t.&lt;/p&gt;

&lt;p&gt;What Ryan was asking me was - “Yeah, that way is faster on this one line, but
does it really matter in the grand scheme of Minitest”? How much time does a
Minitest test run actually spend shuffling the methods? 1%? 10%? 0.001%? Profiling
can tells us that.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/thatwasalie.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;You said that this one-line change would speed up minitest. A higher-level benchmark determined &lt;em&gt;that&lt;/em&gt; was a lie.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Is this one line really part of Pareto’s “20%”? We can assume, based on the
Principle, that 80% of Minitest’s execution time will come from just 20% of its
code. Was this line part of that 20%?&lt;/p&gt;

&lt;p&gt;I’ve already shown you how to benchmark on the micro scale. But before we get to
profiling, I’m going to do a quick macro-benchmark to test my assumption that
using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sort.sort_by&lt;/code&gt; will speed up Minitest.&lt;/p&gt;

&lt;p&gt;Minitest is used to run tests, so we’re going to benchmark a whole test suite.
&lt;a href=&quot;https://github.com/rubygems/rubygems.org/&quot;&gt;Rubygems.org&lt;/a&gt;, an open-source Rails application with a Minitest suite, will make a good example test suite.&lt;/p&gt;

&lt;p&gt;When micro-benchmarking, I reach for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;benchmark-ips&lt;/code&gt;. When macro-benchmarking
(and especially in this case, with a test suite), I usually reach first for the
simplest tool available: the unix utility &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;! We’re going to run the tests
10 times, and then divide the total time by 10.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ time for i in {1..10}; do bundle exec rake; done

...

real	15m59.384s
user	11m39.100s
sys	1m15.767s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;, we’re usually only going to pay attention the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;
statistic. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;real&lt;/code&gt; gives the actual total time (as if you had used a stopwatch),
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sys&lt;/code&gt; gives the time spent in the kernel (in a test run, this would be things
like shelling out to I/O), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt; will be the closest approximation to time
actually spent running Ruby. You’ll notice that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sys&lt;/code&gt; don’t add up to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;real&lt;/code&gt; - the difference is time spent waiting on the CPU while other operations
(like running my web browser, etc) block.&lt;/p&gt;

&lt;p&gt;With stock &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minitest&lt;/code&gt;, the whole thing takes 11 minutes and 39 seconds, for an
average of 69.9 seconds per run. Now, let’s alter the Gemfile to point to a
modified version (with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shuffle&lt;/code&gt; on the line in question) of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minitest&lt;/code&gt; on my
local machine:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;minitest&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;path: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;../minitest&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make sure the test is 100% fair, I only make the change to my local version
after I check out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minitest&lt;/code&gt; to the same version that Rubygems.org is running
(5.8.1).&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/computers.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Even so-called performance experts mess this shit up sometimes.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The result? 11 minutes 56 seconds. Longer than the original test! We know my
code is faster in micro, but the macro benchmark told me that it actually takes
longer. A lot of things can cause this (the most likely being other stuff
running on my machine), but what’s clear is this - my little patch doesn’t seem
to be making a big difference to the big picture of someone’s test suite. While
making this change &lt;em&gt;would&lt;/em&gt;, in &lt;em&gt;theory&lt;/em&gt;, speed up someone’s suite, in reality,
the impact is so minuscule that it didn’t really matter.&lt;/p&gt;

&lt;p&gt;So, while a benchmark told me one thing - X is 10x faster than Y! - a higher-level
benchmark told me another (make your change and this thing didn’t really matter.)
Not only does this show the value of profiling (which would have told me before that
the sorting didn’t take much of the total time) but also how microbenchmarks and relative
comparisons can mislead.&lt;/p&gt;

&lt;p&gt;Performance measurement is a critical skill. Anywhere along the way, I could have been mislead by a single number or a rogue measurement. By applying a scientific, empirical approach, I was able to put my benchmark in context of a larger program.&lt;/p&gt;

&lt;p&gt;Premature optimization is ignoring these lessons and optimizing “when we feel like it”, or optimizing constantly all the time. Hopefully I’ve convinced you: it’s a guaranteed waste of time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeat after me: I will not optimize anything in my application until my
metrics tell me so.&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Dec 2019 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/why-premature-optimization-is-bad/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/why-premature-optimization-is-bad/</guid>
        
        
      </item>
    
      <item>
        <title>Why Your Rails App is Slow: Lessons Learned from 3000+ Hours of Teaching</title>
        <description>&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/setofskills.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;“What I do have is a particular set of skills, a set of skills which makes me a nightmare for slow Rails applications like you.”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;For the last 4 years, I’ve been working on making Rails applications faster and more scalable. I teach workshops, I &lt;a href=&quot;https://www.railsspeed.com/&quot;&gt;sell a course&lt;/a&gt;, and I &lt;a href=&quot;https://www.speedshop.co/retainer.html&quot;&gt;consult&lt;/a&gt;. If you do anything for a long period of time, you start to see patterns. I’ve noticed four different factors that prevent software organizations from improving the performance of their Rails applications, and I’d like to share them here.&lt;/p&gt;

&lt;h2 id=&quot;performance-becomes-a-luxury-good-especially-when-no-one-is-watching&quot;&gt;Performance becomes a luxury good, especially when no one is watching&lt;/h2&gt;

&lt;p&gt;Often times at my workshops, I discover that an attendee simply has no visiblity into what their application is doing in production - they either don’t understand their dashboards, they don’t have them, or they’re not allowed to even access them (“DevOps team only”).&lt;/p&gt;

&lt;p&gt;Performance metrics are often just not tracked. No one is aware if the app is over or underscaled, no one knows if the app is “slow” or “fast”. Is it any wonder, then, that no one spends any time working on it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance is rarely the first priority of any organization, and often gets “trickled down” hours and resources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/workcleanfast.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Some of this is actually a good thing. There’s a reason that the classic programming mantra of “make it work, make it clean, make it fast” is in that order and not the opposite way around. People pay for software that does stuff. If it does that stuff quickly and in a pleasantly performant way, then that’s great, but it’s not always required (especially if the organization is first to market in their space and customers have no other options).&lt;/p&gt;

&lt;p&gt;Often, my consulting clients are at a point in their organization where they’re no longer scraping by on ramen and cheeto dust, but have a solid business that’s expanding (slowly or quickly). They’ve finally gotten their heads above water and they’re ready to start thriving, not just surviving. People don’t come to me when they’re still trying to achieve product market fit unless things have become untenable, and then that’s more of a rescue job.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/travoltawallet.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;When the time comes to set the budget on performance instead of feature velocity.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;This is a natural and correct progression. It also means organizations accumulate performance debt during that initial period of building and obtaining product-market fit. I think there may be some that believe that all kinds of technical debt are some kind of sinful black stain on any organization, and that if you just Coded The Right Way or were a Software Craftsperson™ this would not have happened. I think that’s probably wrong. Before achieving ramen profitability, businesses must take out technical debt as a kind of financing of their own product development runway. This will happen regardless of one’s coding techniques or knowledge level.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/pmburn.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;However, performance is not a luxury good. It isn’t something that can simply be ignored until one’s organization has a spare four or five figures in the couch cushions. Like technical debt, there is a point when feature work grinds to a halt because the organization is too busy maintaining the performance debt that has accrued. Requests are timing out. Customers are complaining about slow the app feels and switching to competitors. You’re scared to check the AWS bill.&lt;/p&gt;

&lt;p&gt;Ideally, organizations monitor and sensibly take out performance debt when required, and understand the full extent of the work that must be done in the future.&lt;/p&gt;

&lt;p&gt;To do this sort of “sensible debt accrual”, &lt;strong&gt;you need performance monitoring/metrics and you need to understand how to present numbers to management&lt;/strong&gt;. I find that while most people know subscribe to a performance monitoring service, such as New Relic, Skylight or Scout, they often have no idea how to read it and extract useful insights from it, making it a very expensive average latency monitor. Being able to actually use your APM is a critical performance skill that I cover in great detail in my workshops and course.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/scoutexample.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;If you can’t draw insights from this, you’re just throwing cash out the door.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Monitoring these metrics allows you to assess where you’re at and to figure out what parts of the application have accrued performance debt. It also helps you to make decisions on the “cost/benefit” of future work.&lt;/p&gt;

&lt;p&gt;It also means you need to be able to “speak manager” or “speak business”. The business case for adding more features is obvious to the non-technical side of your organization. There is a great business case for performance, fortunately, both &lt;a href=&quot;https://wpostats.com/&quot;&gt;from the side of the customer&lt;/a&gt; and from the cost side as well - reducing average latency by 50% means you can spend 50% less on your application’s servers thanks to queueing theory and something called &lt;a href=&quot;https://en.wikipedia.org/wiki/Little&apos;s_law&quot;&gt;Little’s Law&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At my workshops, I spend a lot of time simply discussing terminology, like request queueing, latency, throughput, tracing and profiling. Giving people the vocabulary they need to understand the tools out there seems to be half the battle of getting everyone comfortable reading their own metrics.&lt;/p&gt;

&lt;h2 id=&quot;complex-apps-and-complex-problems-with-little-training&quot;&gt;Complex apps and complex problems, with little training&lt;/h2&gt;

&lt;p&gt;This leads me to the second cause of performance problems in software - a simple lack of knowledge. We can’t optimize what we don’t understand and we can’t fix what we can’t see.&lt;/p&gt;

&lt;p&gt;I wrote the &lt;a href=&quot;https://railsspeed.com&quot;&gt;Complete Guide to Rails Performance&lt;/a&gt; simply because there was so much information about this topic that had simply never been compiled before into one place.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/confusedscaleman.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;“What’s request queueing?”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;This shows itself most when scaling for throughput. Most organizations simply aren’t tracking critical scaling metrics or even know what they are, often because they believe the platform-as-a-service that they’re using should “take care of this” for them. By the time I’ve been called in, they’re spending thousands of dollars a month more than they need to, and could have fixed this months or even years ago with some simple autoscaling policies and a bit of organizational knowledge around scaling. Or, the flipside is happening and they’re massive under-scaled, with 25-50% of their total request latency being just time spent queueing for resources.&lt;/p&gt;

&lt;p&gt;Performance work is not rocket science. However, unlike a lot of other areas in software (The only other area in software that requires an even wider base of knowledge is security. Consider &lt;a href=&quot;https://en.wikipedia.org/wiki/Row_hammer&quot;&gt;Rowhammer&lt;/a&gt; - basically an electrical engineering exploit in very particular configurations of DRAM.), it can require an extremely broad base of knowledge. When your customer says the site “feels slow”, the problem can quite reasonably be almost anywhere between the pixels on the user’s screen (say, an issue with the customer’s client machine) and the electrons running through the silicon on your cloud service provider (for example, a mitigation for a recent Intel security issue puts your servers above capacity). Feature work and even to a large extent refactoring work generally only requires knowledge of the language and frameworks in use. Performance work often needs esoteric knowledge from other fields (such as queueing theory) in addition to highly in-depth knowledge in your frameworks and language. (I wrote a &lt;a href=&quot;/blog/three-activerecord-mistakes/&quot;&gt;3000+ word blog&lt;/a&gt; about the critical performance differences between English-language synonyms .present? and .exists? in Rails, for example, but my &lt;a href=&quot;https://www.railsspeed.com&quot;&gt;Rails performance course&lt;/a&gt; spends the majority of the time talking about things which are not Ruby-specific.)&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/debuggingrails.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Looking at a flamegraph of a Rails app for the first time often leads to this reaction.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;This depth of knowledge simply isn’t present in many organizations, especially those who place sprint velocity before the development of engineering capacity and skills in the organization.&lt;/p&gt;

&lt;p&gt;The workshops I’ve been doing have really allowed me to go in deep on complex problems and help people deal with the “wrinkles” introduced by their application. Getting to look over people’s shoulders while they experience an error or something I hadn’t anticipated has been very rewarding, both for them and for me as an educator.&lt;/p&gt;

&lt;p&gt;Also, during those workshops, I don’t emphasize “pre-baked” problem/solutions, but instead have the attendees bring their real world applications, and we immediately try to apply what we’ve learned on their actual apps right then and there. I don’t want anyone to go home and run into a problem caused by the complexity of their app - rather, I’d like that to happen while we’re both in the same room!&lt;/p&gt;

&lt;h2 id=&quot;boiling-frogs---even-when-tracked-performance-slips-without-fix&quot;&gt;Boiling frogs - even when tracked, performance slips without fix&lt;/h2&gt;

&lt;p&gt;In the Slack channel for the Complete Guide to Rails Performance, we’ve had a few conversations about managing performance work in the software organization.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/elmoflames.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Walking into the office on Monday like&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;An organizational culture that always places completeness over quality inevitably runs into issues. Often when I get new clients, they’re experiencing not just performance issues but have problems with all the various dimensions of software quality: low correctness (an excess of bugs and lack of test coverage), high complexity (“technical debt”, spaghetti organization), and a poor deployment pipeline (broken builds, janky deploys). These aspects of software quality tend to either all be good or all be bad. Project management can (and often should) sacrifice quality for a period of time to prioritize completeness and features, but when it’s done pathologically, it inevitably leads to ruin.&lt;/p&gt;

&lt;p&gt;I find that the lack of software quality culture often arises because no one is measuring it (I actually really don’t vibe with the ‘software craftsperson’ aesthetic that people like Uncle Bob try to push. Quality is great but it isn’t everything. It’s possible to turn this into navelgazing, and building ivory towers.). Feature velocity is measured, or at least vaguely tracked, with things like pull request counts, sprint points, or user stories. We shipped 5 stories last week, so management expects us to ship 5 this week.&lt;/p&gt;

&lt;p&gt;Fortunately, many software quality measures are actually very easy to track. How many bugs were reported or experienced by customers last week? How much downtime did we have? How many deploys were there? Are these numbers rising or falling?&lt;/p&gt;

&lt;p&gt;In terms of performance, most organizations would benefit from setting simple thresholds that, if exceeded, move performance work into the “bug fixing” pipeline that the organization employs. For example, an organization can commit to a maximum 95th percentile latency of 1 second. If a transaction (In New Relic parlance - a single controller action is a ‘transaction’.) exceeds that threshold, a new bug is recorded.&lt;/p&gt;

&lt;p&gt;For organizations that want to improve the customer’s experience and perceived performance of the application, other budgets may be necessary. For example, a first-page-load time of 5 seconds. This page load target has implications that flow down throughout the stack, as one simply cannot ship 10 megabytes of JavaScript and also have a page load in 5 seconds (In fact, I would estimate that to keep page load times below 5 seconds on the average connection and hardware, you can probably ship only a few hundred KB).&lt;/p&gt;

&lt;p&gt;Software engineers are often poor communicators, and they very often fail to communicate to other parts of the organization that prioritizing feature velocity at all costs is not sustainable.&lt;/p&gt;

&lt;p&gt;Think of it this way: how do you think the project managers in your organization would answer the following questions?&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/bezos.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Bezos showering in your AWS bill&lt;/span&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Is your tolerance for the slowness of our application infinite? (i.e. can the app just beachball for all customers all the time?)&lt;/li&gt;
  &lt;li&gt;Do you have infinite money to spend on our EC2 instance bills?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer to either of those questions is “no”, then it is &lt;strong&gt;your job as a software developer&lt;/strong&gt; to find and make explicit those tolerances. They will be different for every organization. These performance requirements can be easily translated into automated alerts and thresholds.&lt;/p&gt;

&lt;p&gt;You just have to have the conversation beforehand. The difference between “Hey boss - we’ve been shipping 12 points a week for the last 8 weeks and now we can’t ship anything for 6 weeks because we need to write tests and make the homepage load time somewhat bearable” and “we exceeded the limit for page load time that we all agreed upon 6 months ago, and we’ll need to reduce velocity for a while to compensate” is miles apart.&lt;/p&gt;

&lt;p&gt;As a result of seeing this pattern often enough, I’ve changed how I phrase my consulting deliverables, as I now realize I need to provide ammunition for the engineers when bringing back my recommendations to the “business side”.&lt;/p&gt;

&lt;h2 id=&quot;its-not-ruby-and-it-isnt-really-rails&quot;&gt;It’s not Ruby, and it isn’t (really) Rails&lt;/h2&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/leavematzalone.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;LEAVE MATZ ALONE&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;And, finally, here’s what isn’t the reason why your web application is slow: your framework or language choice. Once 90th percentile latency is lower than 500 milliseconds and median latency is below 100 milliseconds, most web application backends are no longer the bottleneck in their customer’s experience (if they ever were to begin with, which, in the age of 10 megabyte JavaScript bundles, they are usually not).&lt;/p&gt;

&lt;p&gt;It’s 2017 and web applications don’t return flat HTML files anymore (CNN.com took 5MB of resources and 112 requests to render for me, today. R.I.P. the old light web.). Websites are gargantuan, with JavaScript bundles stretching into the size of megabytes and stylesheets that couldn’t fit in ten Apollo Guidance Computers. So how much of a difference does a web application which responds in 1 millisecond or less make in this environment?&lt;/p&gt;

&lt;p&gt;Vanishingly little. Nowadays, the average webpage takes 5 seconds to render. Some JavaScript single-page-applications can take 12 seconds or more on initial render.&lt;/p&gt;

&lt;p&gt;Server response times simply make up a minority part of the actual user experience of loading and interacting with a webpage - cutting 99 milliseconds off the server response time just doesn’t make a difference.&lt;/p&gt;

&lt;p&gt;Not to mention: if Ruby on Rails, frequently maligned “as too slow” or “can’t scale”, can run several of the top 1000 websites in the world by traffic, including that little fly-by-night outfit called GitHub, then it’s a fine choice for whatever your application is. Rails is just an example here - there are many comparable frameworks in comparable languages that you could substitute like Python and Django. There are some web applications for which 100 milliseconds of latency is an unacceptable eon (advertising is the most common case), but for the vast majority of us delivering HTML or JSON to a client, that’s zippy quick.&lt;/p&gt;

&lt;h2 id=&quot;whither-rails-today&quot;&gt;Whither Rails Today?&lt;/h2&gt;

&lt;p&gt;One of the questions I ask in my post-workshop survey is “How do you feel writing Ruby on Rails? Would you like to keep doing it?”.&lt;/p&gt;

&lt;p&gt;The answers I get back are always astoundingly positive. For all the FUD on the web at large, people writing Ruby are incredibly happy doing it. And that’s what keeps me writing and teaching: as long as people feel that performance concerns are keeping them from enjoying Ruby or choosing it as their tech stack, I’ll keep doing what I do.&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Jun 2019 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/what-i-learned-teaching-rails-performance/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/what-i-learned-teaching-rails-performance/</guid>
        
        
      </item>
    
      <item>
        <title>3 ActiveRecord Mistakes That Slow Down Rails Apps: Count, Where and Present</title>
        <description>&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/weirdriddles.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;“When does ActiveRecord execute queries? No one knows!”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;ActiveRecord is great. Really, it is. But it’s an abstraction, intended to insulate you from the actual SQL queries being run on your database. And, if you don’t understand how ActiveRecord works, you may be causing SQL queries to run that you didn’t intend to.&lt;/p&gt;

&lt;p&gt;Unfortunately, the performance costs of many features of ActiveRecord means we can’t afford to ignore unnecessary usage or treat our ORM as just an implementation detail. We need to understand exactly what queries are being run on our performance-sensitive endpoints. Freedom isn’t free, and neither is ActiveRecord.&lt;/p&gt;

&lt;p&gt;One particular case of ActiveRecord misuse that I find is common amongst my clients is that ActiveRecord is executing SQL queries that aren’t really necessary. Most of my clients are completely unaware that this is even happening.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/dirtythree.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Unnecessary SQL is a common cause of overly slow controller actions, especially when the unnecessary query appears in a partial which is rendered for every element in a collection. This is common in search actions or index actions. This is one of the most common problems I encounter in my performance consulting. It’s a problem in nearly every app I’ve ever worked on.&lt;/p&gt;

&lt;p&gt;One way to eliminate unnecessary queries is to poke our heads into ActiveRecord and understand its internals, and know exactly how certain methods are implemented. &lt;strong&gt;Today, we’re going to look at the implementation and usage of three methods which cause lots of unnecessary queries in Rails applications: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-do-i-know-if-a-query-is-unnecessary&quot;&gt;How Do I Know if a Query is Unnecessary?&lt;/h2&gt;

&lt;p&gt;I have a rule of thumb to judge whether or not any particular SQL query is unnecessary. Ideally, a Rails controller action should execute &lt;strong&gt;one SQL query per table&lt;/strong&gt;. If you’re seeing more than one SQL query per table, you can usually find a way to reduce that to one or two queries. If you’ve got more than a half-dozen or so queries on a single table, you almost definitely have unnecessary queries.  (Please don’t email or tweet with me with ‘Well ackshually…’ on this one. It’s a guideline, not a rule, and I understand there are circumstances where more than one query per table is a good idea.)&lt;/p&gt;

&lt;p&gt;The number of SQL queries per table can be easily seen on NewRelic, for example, if you have that installed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/posts/img/nplusoneposts.png&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;199&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/washeyes.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;I keep an eyewash station next to my desk for really bad N+1s&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Another rule of thumb is that &lt;strong&gt;most queries should
execute during the first half of a controller action’s response, and almost never during partials&lt;/strong&gt;. Queries executed during partials are usually unintentional, and are often N+1s. These are easy to spot during a controller’s execution if you just read the logs in development mode. For example, if you see this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;User Load (0.6ms)  SELECT  &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;id&quot; = $1 LIMIT 1  [[&quot;id&quot;, 2]]
Rendered posts/_post.html.erb (23.2ms)
User Load (0.3ms)  SELECT  &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;id&quot; = $1 LIMIT 1  [[&quot;id&quot;, 3]]
Rendered posts/_post.html.erb (15.1ms)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… you have an N+1 in this partial.&lt;/p&gt;

&lt;p&gt;Usually, when a query is executed halfway through a controller action (somewhere deep in a partial, for example) it means that you haven’t &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-preload&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preload&lt;/code&gt;ed&lt;/a&gt; the data that you needed.&lt;/p&gt;

&lt;p&gt;So, let’s look specifically at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; methods, and why they cause unnecessary SQL queries.&lt;/p&gt;

&lt;h2 id=&quot;count-executes-a-count-every-time&quot;&gt;.count executes a COUNT every time&lt;/h2&gt;

&lt;p&gt;I see this one at almost every company I contract for. It seems to be little-known that calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; on an ActiveRecord relation will &lt;em&gt;always&lt;/em&gt; try to execute a SQL query, every time. This is inappropriate in most scenarios, but, in general, &lt;strong&gt;only use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; if you want to always execute a SQL COUNT &lt;em&gt;right now&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/count.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;“How many queries do we want per table?”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The most common cause of unnecessary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; queries is when you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; an association you will use later in the view (or have already used):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# _messages.html.erb
# Assume @messages = user.messages.unread, or something like that

&amp;lt;h2&amp;gt;Unread Messages: &amp;lt;%= @messages.count %&amp;gt;&amp;lt;/h2&amp;gt;

&amp;lt;% @messages.each do |message| %&amp;gt;
blah blah blah
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This executes 2 queries, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT&lt;/code&gt;. The COUNT is executed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@messages.count&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@messages.each&lt;/code&gt; executes a SELECT to load all the messages. Changing the order of the code in the partial and changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; eliminates the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; query completely and keeps the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% @messages.each do |message| %&amp;gt;
blah blah blah
&amp;lt;% end %&amp;gt;

&amp;lt;h2&amp;gt;Unread Messages: &amp;lt;%= @messages.size %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why is this the case? We need not look any further than &lt;a href=&quot;https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation.rb#L210&quot;&gt;the actual method definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; on ActiveRecord::Relation:&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# File activerecord/lib/active_record/relation.rb, line 210&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;loaded?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/triggeredcount.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;If the relation is loaded (that is, the query that the relation describes has been executed and we have stored the result), we call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;length&lt;/code&gt; on the already loaded record array. &lt;a href=&quot;https://ruby-doc.org/core-2.5.0/Array.html#method-i-length&quot;&gt;That’s just a simple Ruby method on Array&lt;/a&gt;. If the ActiveRecord::Relation &lt;em&gt;isn’t&lt;/em&gt; loaded, we trigger a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;a href=&quot;https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation/calculations.rb#L41&quot;&gt;here’s how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; is implemented&lt;/a&gt; (in ActiveRecord::Calculations):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block_given?&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And, of course, &lt;a href=&quot;https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activerecord/lib/active_record/relation/calculations.rb#L131&quot;&gt;the implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;calculate&lt;/code&gt;&lt;/a&gt; doesn’t memoize or cache anything, and executes a SQL calculation every time it is called.&lt;/p&gt;

&lt;p&gt;Simply changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; in our original example would have still triggered a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt;. The record’s wouldn’t be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt; when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; was called, so ActiveRecord will still attempt a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt;. Moving the method &lt;em&gt;after&lt;/em&gt; the records are loaded eliminates the query. Now, moving our header to the end of the partial doesn’t really make any logical sense. Instead, we can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; method.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;h2&amp;gt;Unread Messages: &amp;lt;%= @messages.load.size %&amp;gt;&amp;lt;/h2&amp;gt;

&amp;lt;% @messages.each do |message| %&amp;gt;
blah blah blah
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; just causes all of the records described by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@messages&lt;/code&gt; to load immediately, rather than lazily. &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-load&quot;&gt;It returns the ActiveRecord::Relation, not the records.&lt;/a&gt; So, when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; is called, the records are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt; and a query is avoided. Voilà.&lt;/p&gt;

&lt;p&gt;What if, in that example, we used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;messages.load.count&lt;/code&gt;? We’d still trigger a COUNT query!&lt;/p&gt;

&lt;p&gt;When &lt;em&gt;doesn’t&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; trigger a query? Only if the result has been cached by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::QueryCache&lt;/code&gt;. (I have some Opinions on the use of QueryCache, but that’s a post for another day.) This could occur by trying to run the same SQL query twice:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;h2&amp;gt;Unread Messages: &amp;lt;%= @messages.count %&amp;gt;&amp;lt;/h2&amp;gt;

... lots of other view code, then later:

&amp;lt;h2&amp;gt;Unread Messages: &amp;lt;%= @messages.count %&amp;gt;&amp;lt;/h2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/pissed.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Every time you use count when you could have used size&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In my opinion, most Rails developers should be using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; in most of the places that they use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;.&lt;/strong&gt; I’m not sure why everyone seems to write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; where it is appropriate, and it doesn’t when the records are already loaded. I think it’s because when you’re writing an ActiveRecord relation, you’re in the “SQL” mindset. You think: “This is SQL, I should write count because I want a COUNT!”&lt;/p&gt;

&lt;p&gt;So, when do you actually want to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;? Use it when you won’t actually &lt;em&gt;ever&lt;/em&gt; be loading the full association that you’re &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;ing. For example, take this view on Rubygems.org, which displays a single gem:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/posts/img/rspecview.png&quot; loading=&quot;lazy&quot; width=&quot;1414&quot; height=&quot;922&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the “versions” list, the view does a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; to get the total number of releases (versions) of this gem.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/rubygems/rubygems.org/blob/d8a48488d29cbfc83efd2e936c74290c54041288/app/views/rubygems/show.html.erb#L36&quot;&gt;Here’s the actual code:&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% if show_all_versions_link?(@rubygem) %&amp;gt;
  &amp;lt;%= link_to t(&apos;.show_all_versions&apos;, :count =&amp;gt; @rubygem.versions.count), rubygem_versions_url(@rubygem), :class =&amp;gt; &quot;gem__see-all-versions t-link--gray t-link--has-arrow&quot; %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The thing is, this view &lt;em&gt;never&lt;/em&gt; loads &lt;em&gt;all&lt;/em&gt; of the Rubygem’s versions. It only loads five of the most recent ones, in order to show that versions list.&lt;/p&gt;

&lt;p&gt;So, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; makes perfect sense here. Even though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; would be logically equivalent (it would just execute a COUNT as well because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@versions&lt;/code&gt; is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt;), it states the intent of the code in a clear way.&lt;/p&gt;

&lt;p&gt;My advice is to grep through your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/views&lt;/code&gt; directory for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; calls and make sure that they actually make sense. If you’re not 100% sure that you really need a real SQL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; right then and there, switch it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt;. Worst case, ActiveRecord will still execute a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; if the association isn’t loaded. If you’re going to use the association later in the view, change it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load.size&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;where-means-filtering-is-done-by-the-database&quot;&gt;.where means filtering is done by the database&lt;/h2&gt;

&lt;p&gt;What’s the problem with this code (let’s say its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_post.html.erb&lt;/code&gt;)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% @posts.each do |post| %&amp;gt;
  &amp;lt;%= post.content %&amp;gt;
  &amp;lt;%= render partial: :comment, collection: post.active_comments %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and in Post.rb:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;active_comments&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;soft_deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/whoaguy.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;If you said, “this causes a SQL query to be executed on every rendering of the post partial”, you’re correct! &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; always causes a query. I didn’t even bother to write out the controller code, because &lt;em&gt;it doesn’t matter&lt;/em&gt;. You can’t use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includes&lt;/code&gt; or other preloading methods to stop this query. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; will always try to execute a query!&lt;/p&gt;

&lt;p&gt;This also happens when you call scopes on associations. Imagine instead our Comment model looked like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;soft_deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Allow me to sum this up with two rules: &lt;strong&gt;Don’t call scopes on associations when you’re rendering collections&lt;/strong&gt; and &lt;strong&gt;don’t put query methods, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt;, in instance methods of an ActiveRecord::Base class&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Calling scopes on associations means we cannot preload the result. In the example above, we can preload the comments on a post, but we can’t preload the &lt;em&gt;active&lt;/em&gt; comments on a post, so we have to go back to the database and execute new queries for every element in the collection.&lt;/p&gt;

&lt;p&gt;This isn’t a problem when you only do it once, and not on every element of a collection (like every post, as above). Feel free to use scopes galore in those situations - for example, if this was a PostsController#show action that only displayed one post and its associated comments. But in collections, scopes on associations cause N+1s, every time.&lt;/p&gt;

&lt;p&gt;The best way I’ve found to fix this particular problem is to &lt;strong&gt;create a new association&lt;/strong&gt;. &lt;a href=&quot;https://www.justinweiss.com/&quot;&gt;Justin Weiss&lt;/a&gt;, of “Practicing Rails”, taught me this in &lt;a href=&quot;https://www.justinweiss.com/articles/how-to-preload-rails-scopes/&quot;&gt;this blog post about preloading Rails scopes&lt;/a&gt;. The idea is that you create a new association, which you &lt;em&gt;can&lt;/em&gt; preload:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:comments&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:active_comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;active&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Comment&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comment&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;soft_deleted: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostsController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:active_comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The view is unchanged, but now executes just 2 SQL queries, one on the Posts table and one on the Comments table. Nice!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% @posts.each do |post| %&amp;gt;
  &amp;lt;%= post.content %&amp;gt;
  &amp;lt;%= render partial: :comment, collection: post.active_comments %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second rule of thumb I mentioned, &lt;strong&gt;don’t put query methods, like where, in instance methods of an ActiveRecord::Base class&lt;/strong&gt;, may seem less obvious. Here’s an example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;latest_comment&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;comments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;published_at desc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What happens if the view looks like this?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;% @posts.each do |post| %&amp;gt;
  &amp;lt;%= post.content %&amp;gt;
  &amp;lt;%= render post.latest_comment %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/rules.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;That’s a SQL query on every post, regardless of what you preloaded. In my experience, &lt;strong&gt;every instance method on an ActiveRecord::Base class will eventually get called inside a collection&lt;/strong&gt;. Someone adds a new feature and isn’t paying attention. Maybe it’s by a different developer than the one who wrote the method originally, and they didn’t fully read the implementation. Ta-da, now you’ve got an N+1. The example I gave could be rewritten as an association, like I described earlier. That can still cause an N+1, but at least it can be fixed easily with the correct preloading.&lt;/p&gt;

&lt;p&gt;Which ActiveRecord methods should we &lt;em&gt;avoid&lt;/em&gt; inside of our ActiveRecord model instance methods? Generally, it’s pretty much everything in the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QueryMethods&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FinderMethods&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Calculations.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Calculations&lt;/code&gt;&lt;/a&gt;. Any of these methods will usually &lt;em&gt;try&lt;/em&gt; to run a SQL query, and are resistant to preloading. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; is the most frequent offender, however.&lt;/p&gt;

&lt;h2 id=&quot;any-exists-and-present&quot;&gt;any?, exists? and present?&lt;/h2&gt;

&lt;p&gt;Rails programmers have been struck by a major affliction - they’re adding a particular predicate method to just about every variable in their applications. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; has spread across Rails codebases faster than the plague in 13th century Europe. The vast majority of the time, the predicate adds nothing but verbosity, and really, all the author needed was a truthy/falsey check, which they could have done by just writing the variable name.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/codetriage/codetriage/blob/b92e347e0f4714b4646be930e341be5a44761b95/app/models/doc_comment.rb#L9&quot;&gt;Here’s an example&lt;/a&gt; from &lt;a href=&quot;https://www.codetriage.com/&quot;&gt;CodeTriage&lt;/a&gt;, a free and open-source Rails application written by my friend &lt;a href=&quot;https://schneems.com/&quot;&gt;Richard Schneeman&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DocComment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:doc_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;counter_cache: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# ... things removed for clarity...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doc_method?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;doc_method_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; doing here? One, it transforms the value of doc_method_id from either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; or an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;. Some people have Strong Opinions about whether predicates should return true/false or can return truthy/falsey. I don’t. But adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; also does something else, and we have to &lt;a href=&quot;https://github.com/rails/rails/blob/94b5cd3a20edadd6f6b8cf0bdf1a4d4919df86cb/activesupport/lib/active_support/core_ext/object/blank.rb#L26&quot;&gt;look at the implementation&lt;/a&gt; to figure out what:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blank?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt; is a more complicated question than “is this object truthy or falsey”. Empty arrays and hashes are truthy, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank&lt;/code&gt;, and empty strings are also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt;. In the example above from CodeTriage, however, the only things that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc_method_id&lt;/code&gt; will &lt;em&gt;ever&lt;/em&gt; be is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt;, meaning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; is logically equivalent to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!!&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doc_method?&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;!!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc_method_id&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# same as doc_method_id.present?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/oldmanyellscloud.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; in cases like this is the wrong tool for the job. If you don’t care about “emptiness” in the value you’re calling the predicate on (i.e. the value cannot be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt;), use the simpler (and much faster) language features available to you. I sometimes see people even do this on values &lt;em&gt;which are already boolean&lt;/em&gt;, which means you’re just adding verbosity and making me wonder if there’s some weird edge cases I’m not seeing.&lt;/p&gt;

&lt;p&gt;Alright, that’s my style gripe. I understand that you may not agree. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; makes more sense when dealing with strings, which can frequently be empty (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;&quot;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where people get into trouble is calling predicates, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt;, on ActiveRecord::Relation objects.&lt;/strong&gt; Let’s say you need to know if an ActiveRecord::Relation has any records. You can use the English-language synonyms any?/present?/exists? or their negations none?/blank?/empty?. Surely it doesn’t matter which method you choose, right? Just pick the one that sounds the most natural when read aloud? Nope.&lt;/p&gt;

&lt;p&gt;What SQL queries do you think the following code will execute? Assume &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@comments&lt;/code&gt; is an ActiveRecord::Relation.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- if @comments.any?
  h2 Comments on this Post
  - @comments.each do |comment|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The answer is &lt;em&gt;two&lt;/em&gt;. One will be an existence check, triggered by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@comments.any?&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT  1 AS one FROM ... LIMIT 1&lt;/code&gt;), then the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@comments.each&lt;/code&gt; line will trigger a loading of the entire relation (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT &quot;comments&quot;.* FROM &quot;comments&quot; WHERE ...&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;What about this?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- unless @comments.load.empty?
  h2 Comments on this Post
  - @comments.each do |comment|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This one only executes one query - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@comments.load&lt;/code&gt; loads the entire relation right away with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT &quot;comments&quot;.* FROM &quot;comments&quot; WHERE ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And this one?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- if @comments.exists?
  This post has
  = @comments.size
  comments
- if @comments.exists?
  h2 Comments on this Post
  - @comments.each do |comment|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Four! &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; doesn’t memoize itself and it doesn’t load the relation. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; here triggers a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT 1 ...&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.size&lt;/code&gt; triggers a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; because the relation hasn’t been loaded yet, and then the next &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; triggers ANOTHER &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT 1 ...&lt;/code&gt; and finally &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@comments&lt;/code&gt; loads the entire relation! Yay! Isn’t this fun? You could reduce this down to just 1 query with the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- if @comments.load.any?
  This post has
  = @comments.size
  comments
- if @comments.any?
  h2 Comments on this Post
  - @comments.each do |comment|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And it just gets better - this behavior changes depending if you’re on Rails 4.2, Rails 5.0 or Rails 5.1+.&lt;/p&gt;

&lt;p&gt;Here’s how it works in Rails 5.1+:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;method&lt;/th&gt;
      &lt;th&gt;SQL generated&lt;/th&gt;
      &lt;th&gt;memoized?&lt;/th&gt;
      &lt;th&gt;implementation&lt;/th&gt;
      &lt;th&gt;Runs query if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;present?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;Object (!blank?)&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;blank?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;any?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!empty?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;empty?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; if !&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;none?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;exists?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;ActiveRecord::Calculations&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here’s how it works in Rails 5.0:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;method&lt;/th&gt;
      &lt;th&gt;SQL generated&lt;/th&gt;
      &lt;th&gt;memoized?&lt;/th&gt;
      &lt;th&gt;implementation&lt;/th&gt;
      &lt;th&gt;Runs query if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;present?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;Object (!blank?)&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;blank?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt;; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;any?&lt;/td&gt;
      &lt;td&gt;SELECT COUNT(*) FROM “users”&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!empty?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;empty?&lt;/td&gt;
      &lt;td&gt;SELECT COUNT(*) FROM “users”&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;count(:all) &amp;gt; 0&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;none?&lt;/td&gt;
      &lt;td&gt;SELECT COUNT(*) FROM “users”&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;exists?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;ActiveRecord::Calculations&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here’s how it works in Rails 4.2:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;method&lt;/th&gt;
      &lt;th&gt;SQL generated&lt;/th&gt;
      &lt;th&gt;memoized?&lt;/th&gt;
      &lt;th&gt;implementation&lt;/th&gt;
      &lt;th&gt;Runs query if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;present?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Object (!blank?)&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;blank?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;to_a.blank?&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;any?&lt;/td&gt;
      &lt;td&gt;SELECT COUNT(*) FROM “users”&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!empty?&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;empty?&lt;/td&gt;
      &lt;td&gt;SELECT COUNT(*) FROM “users”&lt;/td&gt;
      &lt;td&gt;no unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;count(:all) &amp;gt; 0&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;none?&lt;/td&gt;
      &lt;td&gt;SELECT “users”.* FROM “users”&lt;/td&gt;
      &lt;td&gt;yes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; called)&lt;/td&gt;
      &lt;td&gt;Array&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;exists?&lt;/td&gt;
      &lt;td&gt;SELECT 1 AS one FROM “users” LIMIT 1&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;ActiveRecord::Calculations&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;none?&lt;/code&gt; remind me of the implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; - if the records are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loaded?&lt;/code&gt; do a simple method call on a basic Array, if they’re not loaded, &lt;em&gt;always run a SQL query&lt;/em&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; has no caching or memoization built in, just like other ActiveRecord::Calculations. This means that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt;, which is another method people like to write in these circumstances, is actually much worse than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; in some cases!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These six predicate methods, which are English-language synonyms all asking the same question, have completely different implementations and performance implications, and these consequences depend on which version of Rails you are using.&lt;/strong&gt; So, let me distill all of the above into some concrete advice:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt; should not be used if the ActiveRecord::Relation will never be used in its entirety after you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt;. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@my_relation.present?; @my_relation.first(3).each&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;none?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt; should probably be replaced with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt; unless you will only take a section of the ActiveRecord::Relation using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;first&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;last&lt;/code&gt;. They will generate an extra existence SQL check if you’re just going to use the entire relation if it exists. In essence, change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@users.any?; @users.each...&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@users.present?; @users.each...&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@users.load.any?; @users.each...&lt;/code&gt;, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@users.any?; @users.first(3).each&lt;/code&gt; is fine.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; is a lot like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; - it is never memoized, and always executes a SQL query. Most people probably do not actually want this behavior, and would be better off using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/doless.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;As your app grows in size and complexity, unnecessary SQL can become a real drag on your application’s performance. Each SQL query involves a round-trip back to the database, which entails, usually, at &lt;em&gt;least&lt;/em&gt; a millisecond, and sometimes much more for complex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; clauses. Even if one extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; check isn’t a big deal, if it suddenly happens in every row of a table or a partial in a collection, you’ve got a big problem!&lt;/p&gt;

&lt;p&gt;ActiveRecord is a powerful abstraction, but since database access will never be “free”, we need to be aware of how ActiveRecord works internally so that we can avoid database access in unnecessary cases.&lt;/p&gt;

&lt;h2 id=&quot;app-checklist&quot;&gt;App Checklist&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Look for uses of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;none?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blank?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt; on objects which may be ActiveRecord::Relations. Are you just going to load the entire array later if the relation is present? If so, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; to the call (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@my_relation.load.any?&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Be careful with your use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exists?&lt;/code&gt; - it ALWAYS executes a SQL query. Only use it in cases where that is appropriate - otherwise use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; or any other the other methods which use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;empty?&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Be extremely careful using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where&lt;/code&gt; in instance methods on ActiveRecord objects - they break preloading and often cause N+1s when used in rendering collections.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; always executes a SQL query - audit its use in your codebase, and determine if a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; check would be more appropriate.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 10 Jan 2019 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/three-activerecord-mistakes/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/three-activerecord-mistakes/</guid>
        
        
      </item>
    
      <item>
        <title>The Complete Guide to Rails Performance, Version 2</title>
        <description>&lt;p&gt;Today, the Complete Guide to Rails Performance has been updated to version 2.0. &lt;a href=&quot;https://www.railsspeed.com&quot;&gt;You can purchase it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All existing purchasers have had their copies updated on Gumroad. When I started this project, I always believed that a digital course should be &lt;em&gt;better&lt;/em&gt; than a typical paperback programming book. That’s why I don’t include any DRM or proprietary video codecs. That’s why I think, like most software, updates should be free.&lt;/p&gt;

&lt;p&gt;“Version 2.0” isn’t quite as drastic a change as a software v2.0, though. The world of Rails performance has actually changed very little since I wrote the course 2 years ago. The apps I consult on still have many of the same problems. The V2 update reflects this: I have revised the content for clarity, and updated a few places to reflect changes in Ruby 2.5 and Rails 5.2, but it is mostly still the same. I have also added four lessons: memory fragmentation, application server config, GC tuning, and PGBouncer config. These lessons were added based on new problems and thinking I’ve had since the course was released. Web-Scale Package purchasers will also get a new interview with Noah Gibbs of Appfolio next week.&lt;/p&gt;

&lt;p&gt;So, what does it mean that not much has changed in the Rails performance world?&lt;/p&gt;

&lt;p&gt;This tweet put me in an introspective mood this morning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is profoundly sad how Rails has institutionalized a &amp;quot;nobody cares&amp;quot; attitude toward performance. &lt;a href=&quot;https://t.co/UhzvxyLjuz&quot;&gt;https://t.co/UhzvxyLjuz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;mdash; Jeff Atwood (@codinghorror), June 1, 2018&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To summarize, Jeff’s cofounder, Sam Saffron (who I interviewed for the CGRP), wrote a great, in-depth blog post about memory use in ActiveRecord. In short, Sam finds that ActiveRecord creates excessive amounts of objects, even when doing simple and supposedly “optimized” work. Sam posted a proof-of-concept patch which improves this quite a bit.&lt;/p&gt;

&lt;p&gt;Jeff’s tweet diminishes the work of many Rails contributors. Aaron Patterson has spent the last two years working on Rails performance and a compacting garbage collector. Richard Schneeman has improved Sprockets’ performance a great deal. Sam Saffron himself has contributed over a dozen performance improvements to Rails, which, as far I can tell, have all been accepted. I know also that Andrew White, Eileen Uchitelle, and Rafael Franca are all Rails core members that care deeply about performance (probably because all of them have day-jobs running large Rails applications!). So any idea that Rails’ contributors or core members “don’t care” about performance is laughingly misguided, and is an opinion that can only really be held by someone outside the community. The way Jeff tried to turn it around in the replies into a “hot take” that people should “get angry” and “punk rock” about the “status quo” just made it more obvious.&lt;/p&gt;

&lt;p&gt;It’s pretty easy to take potshots at a mature framework like Ruby on Rails. It has almost 13 years of history behind it. There’s going to be cruft, baggage, and outdated decisions baked in. That’s what happens. But there’s also tremendous productivity, something gained from the thousands of contributors who have all contributed their “lessons learned” back to the framework. But if you forget about that history, it’s easy to craft a benchmark to make it look like that history has overtaken it’s usefulness in the present.&lt;/p&gt;

&lt;p&gt;This is the gap I’ve tried to bridge in my writing and in publishing The Complete Guide to Rails Performance. &lt;strong&gt;I believe that performance problems in Rails are pedagogical, not technical&lt;/strong&gt;. It’s not because we don’t have enough people working on performance (though it helps!). It’s not because we don’t value it as a community (how many times do I have to cite all of the top 10,000 websites that run Rails at speed?). &lt;strong&gt;It’s because Rails (and Ruby) optimizes for programmer happiness, and that means we provide sharp tools which are easy to cut yourself on.&lt;/strong&gt; Rather than throw the tools out, I think we need to teach people to use them safely.&lt;/p&gt;

&lt;p&gt;ActiveRecord is probably the best example of what I’m talking about. It’s an extremely productive tool. It works very well for 80% of web-app use-cases. But every year, someone wants to throw it out and thinks that some other Rubygem or pattern (e.g. DataMapper) will save them. It’s so easy to craft a line of code with ActiveRecord that will slow your application to a crawl if you’re not thinking through through the consequences, as anyone who has written &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.all.each&lt;/code&gt; can tell you.&lt;/p&gt;

&lt;p&gt;There is no One True Pattern or One True Framework. But there is a Thing Which Works For Most People. And if you end up being one of the 20% for whom it doesn’t work so well, or the tool’s productivity preference means that it’s easier to make performance mistakes, I don’t think that’s the tool or framework’s fault.&lt;/p&gt;

&lt;p&gt;In this way, I think publishing the Complete Guide to Rails Performance was placing my faith in the developer community of Rails. If I didn’t think that people could make their Rails apps faster through knowledge and skills, and instead they had to wait until the framework or the language itself got faster, I would have gone to work at Github or Shopify and made a bunch of patches to Rails and Ruby. I might have started an alternative, “lightweight” framework or ORM that prioritized performance over usefulness and productivity. Instead, I think that &lt;strong&gt;teaching Rails developers how to find and fix performance problems&lt;/strong&gt; will make a bigger dent in the average Rails app’s response time than improving the language or framework’s performance by even 2-3x, or by removing “dangerous” features.&lt;/p&gt;

&lt;p&gt;As I think we’ve slowly discovered over the course of trying to make Ruby 3x faster, there is no “waste” or “bloat” that can be cut out of a framework or language without cost that suddenly makes the whole thing faster. It’s sort of like how politicians always promise to “cut waste in government spending”, but no-one can ever tell you exactly where or how which programs will be cut. Everything was implemented for a reason. There is no magic wand or amount of man-hours that can be waved at these problems. I’ve discovered this in my consulting and writing as well. I wish it was that easy. But it isn’t.&lt;/p&gt;

&lt;p&gt;However, far from Jeff’s doomsday attitude, I believe that the macro picture for Ruby and Rails performance looks good, as it always had. Ruby 2.0 to 2.5 made a number of incremental performance improvements, particularly in garbage collection. I feel like the community has become more mature and performance-savvy over the last few years too. We’re waking up the mainstream Rails developer to things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; and teaching them how to use ActiveRecord and avoid performance issues.&lt;/p&gt;

&lt;p&gt;The technical future of Ruby looks strong, too. Ruby 2.6 will contain a JIT compiler. How cool is that? TruffleRuby has made great progress to becoming useable enough to run a Rails application. JRuby continues to truck along with more performance improvements and compatibility fixes all the time. The technical future of the language hardly looks dim - in fact, I think it’s much brighter than it was in 2011, when I got started in Ruby and Rails.&lt;/p&gt;

&lt;p&gt;I’ll continue to do my part for the Rails performance community by publishing and writing, to improve the technical skills and capacity of the average Rails developer so that they can make their apps faster. Here’s to you, developers!&lt;/p&gt;
</description>
        <pubDate>Fri, 01 Jun 2018 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/rails-performance-version-two/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/rails-performance-version-two/</guid>
        
        
      </item>
    
      <item>
        <title>A New Ruby Application Server: NGINX Unit</title>
        <description>&lt;p&gt;There’s a new application server on the block for Rubyists - NGINX Unit. As you could probably guess by the name, it’s a project of &lt;a href=&quot;https://www.nginx.com/company/&quot;&gt;NGINX Inc.&lt;/a&gt;, the for-profit open-source company that owns the NGINX web server. In fall of 2017, they announced the &lt;a href=&quot;https://unit.nginx.org/&quot;&gt;NGINX Unit&lt;/a&gt; project. It’s essentially an application server designed to replace all of the various application servers used with NGINX. In Ruby’s case, that’s Puma, Unicorn, and Passenger. (For a far more in-depth comparison of these application servers, read &lt;a href=&quot;/blog/appserver/&quot;&gt;my article about configuring Puma, Passenger and Unicorn&lt;/a&gt;) NGINX Unit also runs Python, Go, PHP and Perl.&lt;/p&gt;

&lt;p&gt;The overarching idea seems to be to make microservice administration a lot easier. One NGINX Unit process can run any number of applications running any number of languages - for example, one NGINX Unit server can manage a half-dozen different Ruby applications, each running a different version of the Ruby runtime. Or you can run a Ruby application and a Python application side-by-side. The combinations are only limited by your system resources.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/bullshit-meter.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the “microservice” space is quite prone to buzzword-laden marketing pages. (I really don’t like when software projects advertise themselves as “modern”. It’s like “subtweeting” all pre-existing software projects in this problem space and saying they’re all old and busted, and this is the New Way To Do Things. Why it’s better than the “old busted ways” is never explicitly stated. This kind of marketing preys on software developer’s fear of becoming obsolete in their skillset, rather than making any substantive point.) Words like “dynamic”, “modular”, “lightweight” are mixed in with “service mesh”, “seamless” and “graceful”. This article is going to be about cutting through the marketing and getting into what NGINX Unit means for those of us running production Ruby applications.&lt;/p&gt;

&lt;p&gt;Before I move on to more about NGINX Unit’s architecture and what makes it unique, let’s make sure we all understand the difference between an application server and a web server. A &lt;strong&gt;web server&lt;/strong&gt; connects to clients over HTTP, and usually serves static files or &lt;strong&gt;proxies&lt;/strong&gt; to other HTTP-enabled servers, and acts as a middleman. An &lt;strong&gt;application server&lt;/strong&gt; is the thing which actually starts and runs the language runtime. In Ruby, these functions are sometimes combined. For example, all of the major Ruby application servers &lt;em&gt;also&lt;/em&gt; are web servers. However, many web servers, such as Nginx and Apache, are &lt;em&gt;not&lt;/em&gt; also application servers. Nginx UNIT is both a web &lt;em&gt;and&lt;/em&gt; application server.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/nginx-unit.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;NGINX Unit runs four different types of processes: main, router, controller, and application. Application processes are the self-explanatory ones - this would just be the Ruby runtime running your Rails application. The router and controller processes, and how they interact with each other and the application processes, is what defines how NGINX Unit works.&lt;/p&gt;

&lt;p&gt;The main process creates the router and application processes. That’s really all it does. Application processes in NGINX Unit are dynamic, however - the number of processes running can be changed at any time, Ruby versions can be changed, or even entire new applications can be added while the server is running. The thing that tells the main process what application processes to run is the controller process.&lt;/p&gt;

&lt;p&gt;The controller process (like the main process, there’s only one) has two jobs: expose a JSON configuration API over HTTP, and configure the router and main processes. This is probably the most novel and interesting part of NGINX Unit for Rubyists. Rather than working with configuration files, you POST JSON objects to the controller process to tell it what to do. For example, with this json file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;listeners&quot;: {
        &quot;*:3000&quot;: {
            &quot;application&quot;: &quot;rails&quot;
        }
    },

    &quot;applications&quot;: {
        &quot;rails&quot;: {
            &quot;type&quot;: &quot;ruby&quot;,
            &quot;processes&quot;: 5,
            &quot;script&quot;: &quot;/www/railsapp/config.ru&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… we can PUT it to an NGINX Unit controller process with this (assuming our NGINX Unit server is listening on port 8443):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -d &quot;myappconfig.json&quot; -X PUT &apos;127.0.0.1:8443&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and create a new Ruby application.&lt;/p&gt;

&lt;p&gt;NGINX Unit’s JSON configuration object is divided into &lt;em&gt;listeners&lt;/em&gt; and &lt;em&gt;applications&lt;/em&gt;. Applications are the actual apps you want to run. Listeners are where those apps are exposed to the world (i.e. what port they’re on).&lt;/p&gt;

&lt;p&gt;Changes in application and listener configuration are supposed to be seamless. For example, a “hot deploy” of a new version of your application would be accomplished by adding a new application to the configuration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;rails-new&quot;: {
      &quot;type&quot;: &quot;ruby&quot;,
      &quot;processes&quot;: 5,
      &quot;script&quot;: &quot;/www/rails-new-app/config.ru&quot;
  }
}

curl -d &quot;mynewappconfig.json&quot; -X PUT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and then switching the listener to the new application:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -X PUT -d &apos;&quot;rails-new&quot;&apos; &apos;127.0.0.1:8443/listeners/*:3000/application`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This transition is (supposedly) seamless, and clients won’t notice. This is similar to a Puma “phased restart”. In a phased restart in Puma, each worker process is restarted one at a time, which means that the other works processes are up and available to take requests. Puma accomplishes this using a control server (managed by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pumactl&lt;/code&gt; utility). However, unlike Puma, NGINX Unit “hot restarts” will not have two versions of the application taking requests at the same time.&lt;/p&gt;

&lt;p&gt;In a &lt;a href=&quot;https://github.com/puma/puma/blob/master/docs/restart.md&quot;&gt;Puma phased restart&lt;/a&gt;, say your application has six workers. Halfway through the phased restart, 3 works will be running the old code, and half will be running new code. This can cause some problems with database schema changes, for example. NGINX Unit restarts happen “all at once”, so while two versions of the code will be running at once, only one version will be taking requests at any point in time.&lt;/p&gt;

&lt;p&gt;This functionality seems quite useful to those who are running their own Ruby applications on a service such as AWS, where you have to manage your own deployment. However, Heroku users won’t find any of this useful, as you’ve already had this sort of “hot deploy” functionality using &lt;a href=&quot;https://devcenter.heroku.com/articles/preboot&quot;&gt;Heroku’s preboot system&lt;/a&gt;. However, these two features aren’t doing exactly the same thing. Heroku creates an entirely new virtual server and hot-swaps the whole thing, whereas NGINX Unit is just changing processes on a single machine, but they’re completely the same from a client perspective.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/nginx-unit-router.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The router process is pretty much what it sounds like - the thing which turns HTTP connections from clients into requests to the web application processes. NGINX claims a single Unit router can handle thousands of simultaneous connections. The router works a lot like an NGINX web server, and has a number of worker threads to accept, buffer and parse incoming connections.&lt;/p&gt;

&lt;p&gt;To me, this is one of the most exciting parts of NGINX Unit for Rubyists. It is very difficult for Ruby application servers to deal with HTTP connections without some kind of reverse proxy in front of the app server. Unicorn, for example, is recommended for use only behind a reverse proxy because it cannot buffer requests. That is, if a client sends one byte of their request and then stops (due to network conditions, a bad cellphone connection perhaps), then the Unicorn process just stops all work and cannot continue until that request has finished buffering. Using NGINX, for example, in front of Unicorn allows NGINX to buffer that request before it reaches Unicorn. Since NGINX is written in highly optimized C and it’s &lt;em&gt;not&lt;/em&gt;  restricted by Ruby’s GVL, it can buffer hundreds of connections for Unicorn. Passenger solves this problem by basically just being an addon for NGINX or Apache (Now you know why it’s called &lt;em&gt;Passenger&lt;/em&gt;!) (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mod_ruby&lt;/code&gt;!) and offloading all of the connection-related work to the webserver. In this way, NGINX Unit is more similar to Passenger than it is to Unicorn.&lt;/p&gt;

&lt;p&gt;The application configuration has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;processes&lt;/code&gt; key. This key can have a minimum number and maximum number of processes:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
  &quot;rails-new&quot;: {
      &quot;type&quot;: &quot;ruby&quot;,
      &quot;processes&quot;: {
        &quot;spare&quot;: 5
        &quot;max&quot;: 10
      },
      &quot;script&quot;: &quot;/www/rails-new-app/config.ru&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For some reason, the “minimum” number of processes is called “spare”. The config above will start 5 processes immediately, and will scale to 10 if the load requires it.&lt;/p&gt;

&lt;p&gt;No word yet on if any settings like Puma’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preload_app!&lt;/code&gt; and similar settings in Passenger and Unicorn are available so you will be able to start up processes before they are needed &lt;em&gt;and&lt;/em&gt; take advantage of copy-on-write memory.&lt;/p&gt;

&lt;p&gt;This leaves the application processes. The interesting and novel thing here is that the router does not communicate with the application processes via HTTP - it uses Unix sockets and shared memory. This looks like an optimization aimed at microservice architectures, as communicating between services on the same machine will be considerably faster without any HTTP in between. I have yet to see any Ruby code examples of how this could work, however.&lt;/p&gt;

&lt;p&gt;It is unclear to me in the long-term if it is intended for you to run NGINX in front of NGINX Unit, or if NGINX Unit can run on it’s own without anything in front of it. As of right now (Q1 2018), you should probably be running NGINX &lt;em&gt;in front&lt;/em&gt; of NGINX Unit as a reverse proxy, because NGINX Unit lacks static file serving, HTTPS (TLS), and HTTP/2. Obviously, the &lt;a href=&quot;http://unit.nginx.org/integration/&quot;&gt;integration is pretty seamless&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;NGINX Unit is approaching a stable 1.0 release. You can’t really run it in production right now for Ruby applications: As I write this sentence, the Ruby module is literally 5 days old. It’s still under very active development right now - minor versions are released every few weeks. TLS and HTTP-related features seem like the next “big features” to come down the pipe, with static file serving being next. There is &lt;em&gt;some&lt;/em&gt; discussion about support for Java, which could probably be turned into support for JRuby and TruffleRuby as well.&lt;/p&gt;

&lt;p&gt;There is no Windows support, and I don’t think I would hold my breath for any in the future. NGINX Unit only supports Ruby 2.0 and above.&lt;/p&gt;

&lt;p&gt;I will not be benchmarking NGINX Unit in this post. It’s Ruby module is extremely new and probably not ready for any kind of benchmarking. However, the real reason I won’t be benchmarking NGINX Unit against Puma, Unicorn or Passenger is because application server choice in Ruby is not a matter of speed (techincally, latency) but throughput. Application servers tend to differ in &lt;em&gt;how many requests&lt;/em&gt; they can serve in parallel, rather than &lt;em&gt;how quickly they do it&lt;/em&gt;. Application servers impose very little latency overhead on the applications they serve, probably on the order of a couple of milliseconds.&lt;/p&gt;

&lt;p&gt;The most important Ruby application server setting which affects throughput is &lt;em&gt;threading&lt;/em&gt;. The reason is that it is the only application server setting which can increase the number of requests served &lt;em&gt;concurrently&lt;/em&gt;. A multithreaded Ruby application server can make greater and more efficient use of the available CPU and memory resources and serve more requests-per-minute than a single-threaded Ruby application process.&lt;/p&gt;

&lt;p&gt;Currently, the only &lt;em&gt;free&lt;/em&gt; application server which runs Ruby web applications in multiple threads is Puma. Passenger Enterprise will do it, but you must pay for a license.&lt;/p&gt;

&lt;p&gt;NGINX Unit plans support for multiple threads in Python applications, so it is not inconceivable that it will support Ruby applications in multiple threads sometime in the future.&lt;/p&gt;

&lt;p&gt;So, how does NGINX Unit currently “shake out” in comparison to Unicorn, Passenger and Puma? I think that the traditional Rails application setup: one monolithic application, run on a Plaform-as-a-Service provider like Heroku will probably not see any benefit at all from NGINX Unit’s current features and planned roadmap. Puma already serves these users very well.&lt;/p&gt;

&lt;p&gt;NGINX Unit may be interesting for Unicorn users who want to stop using a reverse proxy. Once NGINX Unit’s HTTP features are fleshed out, it could replace a Unicorn/NGINX setup with just a single NGINX Unit server.&lt;/p&gt;

&lt;p&gt;NGINX Unit is probably most &lt;em&gt;directly&lt;/em&gt; comparable to Phusion Passenger, which also recently went into the “microservice” realm by supporting Javascript and Python as well as Ruby applications. NGINX Unit currently supports more languages and will probably support even more in the future, so those that need greater language support will probably switch. However, Phusion is a Ruby-first company, so I expect Passenger to always “support” Ruby in a better, more complete way than NGINX Unit ever will. And, as mentioned above, Phusion Passenger Enterprise supports multithreaded execution &lt;em&gt;today&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So, what is the ideal NGINX Unit app? If you’re running your own cloud (that is, not on a service which manages the routing for you, like Heroku) and you have many Ruby applications running on different Ruby versions or many services in many different languages &lt;em&gt;and&lt;/em&gt; those services/apps need to talk to each other, quickly, it looks like NGINX Unit was designed for you. If you don’t fit that profile, though, it’s probably best to stick to the existing top three options (Puma, Passenger, and Unicorn).&lt;/p&gt;
</description>
        <pubDate>Wed, 28 Mar 2018 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/nginx-unit-for-ruby/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/nginx-unit-for-ruby/</guid>
        
        
      </item>
    
      <item>
        <title>Malloc Can Double Multi-threaded Ruby Program Memory Usage</title>
        <description>&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/easy-button.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Sometimes, it really is that simple.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;It’s not every day that a simple configuration change can completely solve a problem.&lt;/p&gt;

&lt;p&gt;I had a client whose Sidekiq processes were using a lot of memory - about a gigabyte each. They would start at about 300MB each, then slowly grow over the course of several hours to almost a gigabyte, where they would start to level off.&lt;/p&gt;

&lt;p&gt;I asked him to change a single environment variable: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX&lt;/code&gt;. “Please set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;.”&lt;/p&gt;

&lt;p&gt;His processes restarted, and immediately the slow growth was eliminated. Processes settled at about half the memory usage they had before - around 512MB each.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/ilied.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Actually, it’s not that simple. There are no free lunches. Though this one might be close to free. Like a ten cent lunch.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Now, before you go copy-pasting this “magical” environment variable into all of your application environments, know this: there are drawbacks. You may not be suffering the problem it solves. There are no silver bullets.&lt;/p&gt;

&lt;p&gt;Ruby is not known for being a language that’s light on memory use. Many Rails applications suffer from up to a gigabyte of memory use &lt;em&gt;per process&lt;/em&gt;. That’s approaching Java levels. &lt;a href=&quot;https://github.com/mperham/sidekiq&quot;&gt;Sidekiq&lt;/a&gt;, the popular Ruby background job processor, has processes which can get just as large or even larger. The reasons are many, but one reason in particular is extremely difficult to diagnose and debug: fragmentation.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/log.jpeg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Typical Ruby memory growth looks logarithmic.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The problem manifests itself as a slow, creeping memory growth in Ruby processes. It is often mistaken for a memory leak. However, unlike a memory leak, memory growth due to fragmentation is logarithmic, while memory leaks are linear.&lt;/p&gt;

&lt;p&gt;A memory leak in a Ruby program is usually caused by a C-extension bug. For example, if your Markdown parser leaks 10kb every time you call it, your memory growth will continue forever &lt;em&gt;at a linear rate&lt;/em&gt;, since you tend to call the markdown parser at a regular frequency.&lt;/p&gt;

&lt;p&gt;Memory fragmentation causes logarithmic growth in memory. It looks like a long curve, approaching some unseen limit. All Ruby processes experience &lt;em&gt;some&lt;/em&gt; memory fragmentation. It’s an inevitable consequence of how Ruby manages memory.&lt;/p&gt;

&lt;p&gt;In particular, Ruby cannot &lt;em&gt;move&lt;/em&gt; objects in memory. Doing so would potentially break any C language extensions which are holding raw pointers to a Ruby object. If we can’t move objects in memory, fragmentation is an inevitable result. It’s a fairly common issue in C programs, not just Ruby.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/malloc-arena-max.png&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Actual client graph. This is what fragmentation looks like. Note the enormous drop after MALLOC_ARENA_MAX changed to 2.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However, fragmentation can sometimes cause Ruby programs to &lt;em&gt;twice&lt;/em&gt; as much memory as they would otherwise, sometimes as much as four times more!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ruby programmers aren’t used to thinking about memory, especially not at the level of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt;. And that’s OK: the entire language is designed to abstract memory away from the programmer. It’s right in the manpage. But while Ruby can guarantee memory &lt;em&gt;safety&lt;/em&gt;, it cannot provide perfect memory &lt;em&gt;abstraction&lt;/em&gt;. One cannot be completely ignorant of memory. Because Ruby programmers are often inexperienced with how computer memory works, when problems occur, they often have no idea where to even start with debugging it, and may dismiss it as an intrinsic feature of a dynamic, interpreted language like Ruby.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/princess.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;“And underneath 4 layers of memory abstraction, she noticed some fragmentation!”&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;What makes it worse is that memory is abstracted away from Rubyists through &lt;em&gt;four separate layers&lt;/em&gt;. First is the Ruby virtual machine itself, which has its own internal organization and memory tracking features (sometimes called the &lt;a href=&quot;http://ruby-doc.org/core-2.4.0/ObjectSpace.html&quot;&gt;ObjectSpace&lt;/a&gt;). Second is the allocator, which differs &lt;em&gt;greatly&lt;/em&gt; in behavior depending on the particular implementation you’re using. Third is the operating system, which abstracts actual physical memory addresses away into virtual memory addresses. The way it does this varies significantly depending on the kernel - Mach does this much differently than Linux, for example. Finally, there’s the actual hardware itself, which uses several strategies to keep frequently-accessed data in “hot” locations where it can be more quickly accessed. There are even special parts of the CPU involved here, such as the &lt;a href=&quot;https://en.wikipedia.org/wiki/Translation_lookaside_buffer&quot;&gt;translation lookaside buffer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is what makes memory fragmentation so difficult for Rubyists to deal with. It’s a problem that generally happens at the level of the virtual machine and the allocator, parts of the Ruby language that 95% of Rubyists are probably unfamiliar with.&lt;/p&gt;

&lt;p&gt;Some fragmentation is inevitable, but it can also get so bad that it doubles the memory usage of your Ruby processes. How can you know if you’re suffering the latter rather than the former? What causes critical levels of memory fragmentation? Well, I have one thesis about a cause of memory fragmentation which affects multithreaded Ruby applications, like webapps running on Puma or Passenger Enterprise, and multithreaded job processors such as Sidekiq or Sucker Punch.&lt;/p&gt;

&lt;h2 id=&quot;per-thread-memory-arenas-in-glibc-malloc&quot;&gt;Per-Thread Memory Arenas in glibc Malloc&lt;/h2&gt;

&lt;p&gt;It all boils down to a particular feature of the standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glibc&lt;/code&gt; malloc implementation called “per-thread memory arenas”.&lt;/p&gt;

&lt;p&gt;To understand why, I need to explain how garbage collection works in CRuby &lt;em&gt;really quickly&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/heapfrag.gif&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;ObjectSpace visualization by Aaron Patterson. Each pixel is an RVALUE. Green is “new”, red is “old”. See &lt;a href=&quot;https://github.com/tenderlove/heapfrag&quot;&gt;heapfrag&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;All objects have a entry in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; is a big list which contains an entry for &lt;em&gt;every&lt;/em&gt; Ruby object currently alive in the process. The list entries take the form of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;s, which are 40-byte C &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;s that contain some basic data about the object. The exact contents of these structs varies depending on the class of the object. As an example, if it is a very short String like “hello”, the actual bits that contain the character data are embedded directly in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;. However, we only have 40 bytes - if the string is 23 bytes or longer, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt; contains only a raw pointer to where the object data &lt;em&gt;actually&lt;/em&gt; lies in memory, outside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;s are further organized in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; into 16KB “pages”. Each page contains about 408 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;These numbers can be confirmed by looking at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC::INTERNAL_CONSTANTS&lt;/code&gt; constant in any Ruby process:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;INTERNAL_CONSTANTS&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;:RVALUE_SIZE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;:HEAP_PAGE_OBJ_LIMIT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;408&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Creating a long string (let’s say it’s a 1000-character HTTP response for example) looks like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; list. If we are out of free slots in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt;, we lengthen the list by 1 heap page, calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc(16384)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc(1000)&lt;/code&gt; and receive a address to a 1000-byte memory location. (Actually, Ruby will request an area slightly larger than it needs in case the string is added to or resized.) This is where we’ll put our HTTP response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The malloc calls here are what I want to bring your attention to. All we’re doing is asking for a memory location of a particular size, &lt;em&gt;somewhere&lt;/em&gt;. &lt;strong&gt;Actually, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt;’s contiguity is &lt;em&gt;undefined&lt;/em&gt;&lt;/strong&gt;, that is, it makes no guarantees about &lt;em&gt;where&lt;/em&gt; that memory location will actually be. This means that, from the perspective of the Ruby VM, fragmentation (which is fundamentally a problem about &lt;em&gt;where&lt;/em&gt; memory is) is a problem of the allocator. (However, allocation patterns and sizes can definitely make things harder for the allocator.)&lt;/p&gt;

&lt;p&gt;Ruby can, in a way, measure the fragmentation of its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt;. A method in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC&lt;/code&gt; module, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat&lt;/code&gt;, provides a wealth of information about the current memory and GC state. It’s a little overwhelming and is under-documented, but the output is a hash that looks like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stat&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;:heap_allocated_pages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;91&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;:heap_sorted_length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;91&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ... way more keys ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are two keys in this hash that I want to point your attention to: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat[:heap_live_slots]&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat[:heap_eden_pages]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:heap_live_slots&lt;/code&gt; refers to the number of slots in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; currently occupied by live (not marked for freeing) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt; structs. This is roughly the same as “currently live Ruby objects”.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/eden.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;The Eden heap&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:heap_eden_pages&lt;/code&gt; is the number of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; pages which currently contain &lt;em&gt;at least one&lt;/em&gt; live slot. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; pages which have at least one live slot are called eden pages. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; pages which contain no live objects are called tomb pages. This distinction is important from the GC’s perspective, because tomb pages can be returned back to the operating system. Also, the GC will put new objects into eden pages first, and then tomb pages after all the eden pages have filled up. This reduces fragmentation.&lt;/p&gt;

&lt;p&gt;If you divide the number of live slots by the number of slots in all eden pages, you get a measure of the current fragmentation of the ObjectSpace. As an example, here’s what I get in a fresh &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;irb&lt;/code&gt; process:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:heap_live_slots&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 24508&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:heap_eden_pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 83&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;INTERNAL_CONSTANTS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:HEAP_PAGE_OBJ_LIMIT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 408&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# live_slots / (eden_pages * slots_per_page)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 24508 / (83 * 408) = 72.3%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;About 28% of my eden page slots are currently unoccupied. A high percentage of free slots indicates that the ObjectSpace’s RVALUEs are spread across many more heap pages than they would be if we could move them around. This is a kind of internal memory fragmentation.&lt;/p&gt;

&lt;p&gt;Another measure of internal fragmentation in the Ruby VM comes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat[:heap_sorted_length]&lt;/code&gt;. This key is the “length” of the heap. If we have three ObjectSpace pages, and I &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free&lt;/code&gt; the 2nd one (the one in the middle), I only have two heap pages remaining. However, I cannot move heap pages around in memory, so the “length” of the heap (essentially the highest index of the heap pages) is still 3.&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/swisscheese.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Yes, this heap is fragmented, but it looks &lt;em&gt;really tasty&lt;/em&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Dividing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat[:heap_eden_pages]&lt;/code&gt; by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.stat[:heap_sorted_length]&lt;/code&gt; gives a measure of internal fragmentation at the level of ObjectSpace pages - a low percentage here would indicate a lot of heap-page-sized “holes” in the ObjectSpace list.&lt;/p&gt;

&lt;p&gt;While these measures are interesting, most memory fragmentation (and most allocation) doesn’t happen in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; - it happens in the process of allocating space for objects which don’t fit inside a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVALUE&lt;/code&gt;. It turns out that’s most of them, according to experiments performed by Aaron Patterson and Sam Saffron. A typical Rails app’s memory usage will be 50%-80% in these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; calls to get space for objects larger than a few bytes.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Well this sucks. Looks like only 15% of the heap in a basic Rails app is managed by the GC. 85% is just mallocs &lt;a href=&quot;https://t.co/sPbtAq4g8j&quot;&gt;pic.twitter.com/sPbtAq4g8j&lt;/a&gt;&lt;/p&gt;&amp;mdash; Aaron Patterson (@tenderlove) &lt;a href=&quot;https://twitter.com/tenderlove/status/879870368680255489?ref_src=twsrc%5Etfw&quot;&gt;June 28, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;When Aaron says “managed by the GC” here, he means “inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectSpace&lt;/code&gt; list”.&lt;/p&gt;

&lt;p&gt;Ok, so let’s talk about where per-thread memory arenas come in.&lt;/p&gt;

&lt;p&gt;The per-thread memory arena was an optimization introduced in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glibc&lt;/code&gt; 2.10, &lt;a href=&quot;https://github.molgen.mpg.de/git-mirror/glibc/blob/master/malloc/arena.c&quot;&gt;and lives today in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arena.c&lt;/code&gt;&lt;/a&gt;. It’s designed to decrease contention between threads when accessing memory.&lt;/p&gt;

&lt;p&gt;In a naive, basic allocator design, the allocator makes sure only one thread can request a memory chunk from the main arena at a time. This ensures that two threads don’t accidentally get the same chunk of memory. If they did, that would cause some pretty nasty multi-threading bugs. However, for programs with a lot of threads, this can be slow, since there’s a lot of contention for the lock. &lt;em&gt;All&lt;/em&gt; memory access for &lt;em&gt;all&lt;/em&gt; threads is gated through this lock, so you can see how this could be a bottleneck.&lt;/p&gt;

&lt;p&gt;Removing this lock has been an area of major effort in allocator design because of its performance impact. There are even a few lockless allocators out there.&lt;/p&gt;

&lt;p&gt;The per-thread memory arena implementation alleviates lock contention with the following process (paraphrased from &lt;a href=&quot;https://siddhesh.in/posts/malloc-per-thread-arenas-in-glibc.html&quot;&gt;this article by Siddhesh Poyarekar&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; in a thread. The thread attempts to obtain the lock for the memory arena it accessed previously (or the main arena, if no other arenas have been created).&lt;/li&gt;
  &lt;li&gt;If that arena is not available, try the next memory arena (if there are any other memory arenas).&lt;/li&gt;
  &lt;li&gt;If none of the memory arenas are available, create a new arena and use that. This new arena is linked to to the last arena in a linked list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this way, the main arena is basically extended into a linked list of arenas/heaps. The number of arenas is limited by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mallopt&lt;/code&gt;, specifically the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;M_ARENA_MAX&lt;/code&gt; parameter (documented &lt;a href=&quot;http://man7.org/linux/man-pages/man3/mallopt.3.html&quot;&gt;here&lt;/a&gt;, note the “environment variables” section). By default, the limit on the number of per-thread memory arenas that can be created is 8 times the number of available cores. Most Ruby web applications run about 5 threads per core, and Sidekiq clusters can often run far more than that. In practice, this means that many, many per-thread memory arenas can get created by a Ruby application.&lt;/p&gt;

&lt;p&gt;Let’s take a look at exactly how this would play out in a multithreaded Ruby application.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You are running a Sidekiq process with the default setting of 25 threads.&lt;/li&gt;
  &lt;li&gt;Sidekiq begins running 5 new jobs. Their job is to communicate with an external credit card processor - so they POST a request via HTTPS and receive a response ~3 seconds later.&lt;/li&gt;
  &lt;li&gt;Each job (which is running a separate thread in Rubyland) sends an HTTP request and waits for a response using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IO&lt;/code&gt; module. Generally, almost all IO in CRuby releases the Global VM lock, which means that these threads are working &lt;em&gt;in parallel&lt;/em&gt; and may contend for the main memory arena lock, causing the creation of new memory arenas.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If multiple CRuby threads are running but &lt;em&gt;not&lt;/em&gt; doing I/O, it is pretty much impossible for them to contend for the main memory arena because the Global VM Lock prevents two Ruby threads from executing Ruby code at the same time. Thus, per-thread-memory arenas only affect CRuby applications which are both multithreaded and performing I/O.&lt;/p&gt;

&lt;p&gt;How does this lead to memory fragmentation?&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;marginnote no-mobile&quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/tetris.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;Bin-packing can be fun, too!&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Memory fragmentation is essentially a &lt;a href=&quot;https://en.wikipedia.org/wiki/Bin_packing_problem&quot;&gt;bin packing problem&lt;/a&gt; - how can we efficiently distribute oddly-sized items between multiple bins so that they take up the least amount of space? Bin-packing is made much more difficult for the allocator because a) Ruby never moves memory locations around (once we allocate a location, the object/data stays there until it is freed) b) per-thread memory arenas essentially create a &lt;em&gt;lot&lt;/em&gt; of different bins, which cannot be combined or “packed” together. Bin-packing is already NP-hard, and these constraints just make it even more difficult to achieve an optimal solution.&lt;/p&gt;

&lt;p&gt;Per-thread memory arenas leading to large amounts of RSS use over time is something of a &lt;a href=&quot;https://sourceware.org/bugzilla/show_bug.cgi?id=11261&quot;&gt;known issue on the glibc malloc tracker&lt;/a&gt;. In fact, the &lt;a href=&quot;https://sourceware.org/glibc/wiki/MallocInternals&quot;&gt;MallocInternals wiki&lt;/a&gt; says specifically:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;As pressure from thread collisions increases, additional arenas are created via mmap to relieve the pressure. The number of arenas is capped at eight times the number of CPUs in the system (unless the user specifies otherwise, see mallopt), which means a heavily threaded application will still see some contention, but the trade-off is that there will be less fragmentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There you have it - lowering the number of available memory arenas reduces fragmentation. There’s an explicit tradeoff here: fewer arenas decreases memory use, but may slow the program down by increasing lock contention.&lt;/p&gt;

&lt;p&gt;Heroku discovered this side-effect of per-thread memory arenas when they created the Cedar-14 stack, which upgraded glibc to version 2.19.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior&quot;&gt;Heroku customers reported greater memory consumption of their applications when upgrading their apps to the new stack.&lt;/a&gt; Testing by Terrence Hone of Heroku produced some interesting results:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Configuration&lt;/th&gt;
      &lt;th&gt;Memory Use&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Base (unlimited arenas)&lt;/td&gt;
      &lt;td&gt;1.73x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Base (before arenas introduced)&lt;/td&gt;
      &lt;td&gt;1x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MALLOC_ARENA_MAX=1&lt;/td&gt;
      &lt;td&gt;0.86&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MALLOC_ARENA_MAX=2&lt;/td&gt;
      &lt;td&gt;0.87&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Basically, the default memory arena behavior in libc 2.19 reduced execution time by 10%, but increased memory use by 75%! Reducing the maximum number of memory arenas to 2 essentially eliminated the speed gains, but reduced memory usage over the old Cedar-10 stack by 10% (and reduced memory usage by about 2X over the default memory arena behavior!).&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Configuration&lt;/th&gt;
      &lt;th&gt;Response Times&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Base (unlimited arenas)&lt;/td&gt;
      &lt;td&gt;0.9x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Base (before arenas introduced)&lt;/td&gt;
      &lt;td&gt;1x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MALLOC_ARENA_MAX=1&lt;/td&gt;
      &lt;td&gt;1.15x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MALLOC_ARENA_MAX=2&lt;/td&gt;
      &lt;td&gt;1.03x&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;For almost &lt;em&gt;all&lt;/em&gt; Ruby applications, a 75% memory gain for 10% speed gain is &lt;em&gt;not&lt;/em&gt; an appropriate tradeoff. But let’s get some more real-world results in here.&lt;/p&gt;

&lt;h2 id=&quot;a-replicating-program&quot;&gt;A Replicating Program&lt;/h2&gt;

&lt;p&gt;&lt;span class=&quot;marginnote &quot;&gt;&lt;img src=&quot;https://www.speedshop.co/assets/posts/img/2arenas.jpg&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href=&quot;https://github.com/speedshop/sidekiqdemo&quot;&gt;a demo application&lt;/a&gt;, which is a Sidekiq job which generates some random data and writes the response to a database.&lt;/p&gt;

&lt;p&gt;After switching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX&lt;/code&gt; to 2, memory usage was 15% lower after 24 hours.&lt;/p&gt;

&lt;p&gt;I’ve noticed that real-world workloads magnify this effect greatly, which means I don’t fully understand the allocation pattern which can cause this fragmentation yet. I’ve seen plenty of memory graphs on the &lt;a href=&quot;https://www.railsspeed.com/&quot;&gt;Complete Guide to Rails Performance&lt;/a&gt; Slack channel that show 2-3x memory savings in production with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX=2&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;fixing-the-problem&quot;&gt;Fixing the Problem&lt;/h2&gt;

&lt;p&gt;There are two main solutions for this problem, along with one possible solution for the future.&lt;/p&gt;

&lt;h3 id=&quot;fix-1-reduce-memory-arenas&quot;&gt;Fix 1: Reduce Memory Arenas&lt;/h3&gt;

&lt;p&gt;One fairly obvious fix would be to reduce the maximum number of memory arenas available. We can do this by changing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX&lt;/code&gt; environment variable. As mentioned before, this increases lock contention in the allocator and &lt;em&gt;will&lt;/em&gt; have a negative impact on the performance of your application across the board.&lt;/p&gt;

&lt;p&gt;It’s impossible to recommend a generic setting here, but it seems like 2 to 4 arenas is appropriate for most Ruby applications. Setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX&lt;/code&gt; to 1 seems to have a high negative impact on performance with only a very marginal improvement to memory usage (1-2%). Experiment with these settings and &lt;em&gt;measure the results&lt;/em&gt; both in memory use reduction and performance reduction until you’ve made a tradeoff appropriate for your app.&lt;/p&gt;

&lt;h3 id=&quot;fix-2-use-jemalloc&quot;&gt;Fix 2: Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt;&lt;/h3&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;This is CodeTriage&amp;#39;s Sidekiq worker memory use with and without jemalloc. I&amp;#39;m really starting to wonder how much of Ruby&amp;#39;s memory problems are just caused by the allocator. &lt;a href=&quot;https://t.co/FD0fVbJCLt&quot;&gt;pic.twitter.com/FD0fVbJCLt&lt;/a&gt;&lt;/p&gt;&amp;mdash; Nate Berkopec (@nateberkopec) &lt;a href=&quot;https://twitter.com/nateberkopec/status/936627901071466496?ref_src=twsrc%5Etfw&quot;&gt;December 1, 2017&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Another possible solution is to simply use a different allocator. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; also implements per-thread arenas, but their design seems to avoid the fragmentation issues present in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above tweet was from when I removed jemalloc from &lt;a href=&quot;https://www.codetriage.com/&quot;&gt;CodeTriage&lt;/a&gt;’s background job processes. As you can see, the effect was pretty drastic. I also experimented with using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX=2&lt;/code&gt;, but memory usage was still almost &lt;em&gt;4 times&lt;/em&gt; greater than memory usage with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt;. &lt;strong&gt;If you can switch to jemalloc with Ruby, do it.&lt;/strong&gt; It seems to have the same or better performance than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt; with far less memory use.&lt;/p&gt;

&lt;p&gt;This isn’t a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; blog post, but some finer points on using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; with Ruby:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mojodna/heroku-buildpack-jemalloc&quot;&gt;You can use it on Heroku with this buildpack.&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Do not use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; 4.x with Ruby. It has a bad interaction with Transparent Huge Pages that reduces the memory savings you’ll see. Instead, use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt; 3.6. 5.0’s performance with Ruby is currently unknown.&lt;/li&gt;
  &lt;li&gt;You do not need to compile Ruby with jemalloc (though you can). &lt;a href=&quot;https://github.com/jemalloc/jemalloc/wiki/Getting-Started&quot;&gt;You can dynamically load it with LD_PRELOAD.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;fix-3-compacting-gc&quot;&gt;Fix 3: Compacting GC&lt;/h3&gt;

&lt;p&gt;Fragmentation can generally be reduced if one can &lt;em&gt;move&lt;/em&gt; locations in memory around. We can’t do that in CRuby because C-extensions may use raw pointers to refer to Ruby’s memory - moving that location would cause a segfault or incorrect data to be read.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8Q7M513vewk&quot;&gt;Aaron Patterson has been working on a compacting garbage collector for a while now.&lt;/a&gt; The work looks promising, but perhaps a ways off in the future.&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR:&lt;/h2&gt;

&lt;p&gt;Multithreaded Ruby programs may be consuming 2 to 4 times the amount of memory that they really need, due to fragmentation caused by per-thread memory arenas in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;malloc&lt;/code&gt;. To fix this, you can reduce the maximum number of arenas by setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MALLOC_ARENA_MAX&lt;/code&gt; environment variable or by switching to an allocator with better performance, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The potential memory savings here are so great and the penalties so minor that &lt;strong&gt;I would recommend that if you are using Ruby and Puma or Sidekiq in production, you should always use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jemalloc&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While this effect is most pronounced in CRuby, &lt;a href=&quot;https://github.com/cloudfoundry/java-buildpack/issues/320&quot;&gt;it may also affect the JVM and JRuby.&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 04 Dec 2017 07:00:00 +0000</pubDate>
        <link>https://www.speedshop.co/blog/malloc-doubles-ruby-memory/</link>
        <guid isPermaLink="true">https://www.speedshop.co/blog/malloc-doubles-ruby-memory/</guid>
        
        
      </item>
    
  </channel>
</rss>
