<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
  <title>BigBinary Blog: Rails</title>
  <subtitle>Deep technical writing on Ruby on Rails, performance, scaling, monitoring and engineering.</subtitle>
  <link rel="self" type="application/atom+xml" href="https://www.bigbinary.com/blog/categories/rails/feed.xml"/>
  <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/categories/rails"/>
  <id>https://www.bigbinary.com/blog/categories/rails</id>
  <updated>2025-11-25T00:00:00.000Z</updated>
  <author>
    <name>BigBinary</name>
    <uri>https://www.bigbinary.com/</uri>
  </author>
  <icon>https://www.bigbinary.com/favicon.ico</icon>
  <logo>https://www.bigbinary.com/images/bigbinary_square_big_logo.png</logo>
  <rights>Copyright 2025 Big Binary LLC</rights>
  <generator uri="https://nextjs.org">Next.js</generator>
  <entry>
      <title type="text"><![CDATA[Debugging a Stack Overflow in Rails 7.2.1.1]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/debugging-stack-overflow-in-rails"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/debugging_stack_overflow_in_rails.jpg"/>
      <id>https://www.bigbinary.com/blog/debugging-stack-overflow-in-rails</id>
      <published>2025-11-25T00:00:00.000Z</published>
      <updated>2025-11-25T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/debugging_stack_overflow_in_rails.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[A few weeks ago, after upgrading NeetoCal from Rails 7.1.5.2 to Rails 7.2.1.1, we started seeing mysterious crashes in production with the error message SystemStackError: stack level too deep....]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/debugging-stack-overflow-in-rails"><![CDATA[<p>A few weeks ago, after upgrading <a href="https://www.neeto.com/neetocal">NeetoCal</a> from
Rails 7.1.5.2 to Rails 7.2.1.1, we started seeing mysterious crashes in
production with the error message <code>SystemStackError: stack level too deep</code>.</p>
<h2>Identifying the problem</h2>
<p>The crash happened inside <code>Slots::SyncAllCalendarsService</code>, a service that syncs
multiple calendars concurrently using the <code>Async</code> gem. What made it particularly
puzzling was that switching from <code>Async::Barrier</code> to <code>Thread.new</code> made the error
disappear. This led us down a rabbit hole thinking it was an Async-specific
issue.</p>
<p>The stack trace pointed to a strange location:</p>
<pre><code>activerecord (7.2.1.1) lib/arel/visitors/to_sql.rb:898:in `each'
  from lib/arel/visitors/to_sql.rb:898:in `each_with_index'
  from lib/arel/visitors/to_sql.rb:898:in `inject_join'
  from lib/arel/visitors/to_sql.rb:627:in `visit_Arel_Nodes_Or'
  ... 1132 levels...
</code></pre>
<p>Over a thousand levels deep into <code>visit_Arel_Nodes_Or</code>. That's unusual. Why
would visiting OR nodes in a SQL query cause such deep recursion?</p>
<p>The stack trace mentioned <code>Slots::SyncAllCalendarsService</code>, but that service
itself had nothing obviously wrong. It was just iterating through calendars and
syncing them. We traced deeper into the code and found the actual culprit in
Neeto's <code>Integrations::Icloud::SyncEventsService</code>:</p>
<pre><code class="language-ruby">def abandoned_events
  pairs = event_params.map { |e| [e[:url], e[:recurrence_id]] }

  @_abandoned_events = calendar.events
    .where(&quot;start_time BETWEEN ? AND ?&quot;, start_time, end_time)
    .where.not([:url, :recurrence_id] =&gt; pairs)
end
</code></pre>
<p>This code finds &quot;abandoned&quot; events by excluding events that match specific URL
and recurrence ID combinations. In production, <code>pairs</code> contained 233 entries.
Nothing immediately suspicious, right?</p>
<p>To understand what was happening, we needed to see what Rails was actually doing
with this query. The <code>where.not([:url, :recurrence_id] =&gt; pairs)</code> syntax is
Rails' composite key feature. With 233 pairs, Rails generates SQL like this:</p>
<pre><code class="language-sql">WHERE NOT (
  (url = 'url1' AND recurrence_id = 'rec1') OR
  (url = 'url2' AND recurrence_id = 'rec2') OR
  (url = 'url3' AND recurrence_id = 'rec3') OR
  -- ... 230 more OR clauses
)
</code></pre>
<p>233 OR clauses. That's a lot, but databases can handle it. The problem wasn't
the SQL itself - it was how Rails built the internal representation of that
query.</p>
<h2>Going into Rails Internals</h2>
<p>To really understand what changed, we used <code>bundle open activerecord</code> to look at
the Rails source code. We compared the same code in Rails 7.1.5.2 and 7.2.1.1.</p>
<h3>What We Found in Rails 7.1.5.2</h3>
<p>In Rails 7.1.5.2, the code that visits OR nodes looked like this:</p>
<pre><code class="language-ruby">def visit_Arel_Nodes_Or(o, collector)
  stack = [o.right, o.left]

  while o = stack.pop
    if o.is_a?(Arel::Nodes::Or)
      stack.push o.right, o.left
    else
      visit o, collector
      collector &lt;&lt; &quot; OR &quot; unless stack.empty?
    end
  end

  collector
end
</code></pre>
<p>This is an iterative approach using a manual stack. No matter how deeply nested
the OR conditions are, this code only uses a single stack frame for the
<code>visit_Arel_Nodes_Or</code> method. It's stack-safe.</p>
<h3>What Changed in Rails 7.2.1.1</h3>
<p>In Rails 7.2.1.1, the same method became much simpler:</p>
<pre><code class="language-ruby">def visit_Arel_Nodes_Or(o, collector)
  inject_join o.children, collector, &quot; OR &quot;
end
</code></pre>
<p>Where <code>inject_join</code> does:</p>
<pre><code class="language-ruby">def inject_join(list, collector, join_str)
  list.each_with_index do |x, i|
    collector &lt;&lt; join_str unless i == 0
    collector = visit(x, collector)  # Recursive call
  end
  collector
end
</code></pre>
<p>This is a recursive approach. If an OR node contains another OR node, it calls
<code>visit</code>, which calls <code>visit_Arel_Nodes_Or</code>, which calls <code>inject_join</code> again. The
recursion depth grows with the nesting depth of the OR tree.</p>
<h2>The Tree Structure Problem</h2>
<p>But why is the OR tree so deeply nested? We found the answer in Active Record's
<code>PredicateBuilder#grouping_queries</code>:</p>
<pre><code class="language-ruby">def grouping_queries(queries)
  if queries.one?
    queries.first
  else
    queries.map! { |query| query.reduce(&amp;:and) }
    queries = queries.reduce { |result, query| Arel::Nodes::Or.new([result, query]) }
    Arel::Nodes::Grouping.new(queries)
  end
end
</code></pre>
<p>That <code>reduce</code> call is the key. It builds a left-deep nested tree:</p>
<pre><code>Level 1:  Query1 OR Query2 = OR_Node_1
Level 2:  OR_Node_1 OR Query3 = OR_Node_2
Level 3:  OR_Node_2 OR Query4 = OR_Node_3
...
Level 232: OR_Node_231 OR Query233 = OR_Node_232
</code></pre>
<p>With 233 pairs, this creates 232 levels of nesting. Each level adds
approximately 5 stack frames when Rails traverses it recursively. That's 1,160
stack frames just for the query building.</p>
<h2>The Async Connection</h2>
<p>Why did <code>Thread.new</code> work but <code>Async::Barrier</code> didn't?</p>
<p>We wrote a test script to measure the actual stack depth limits in different
contexts:</p>
<pre><code class="language-ruby">def test_recursion_depth(context_name)
  depth = 0

  recurse = lambda do |n|
    depth = n
    recurse.call(n + 1)
  end

  begin
    recurse.call(0)
  rescue SystemStackError
    return depth
  end
end

thread_depth = nil
Thread.new do
  thread_depth = test_recursion_depth(&quot;thread&quot;)
end.join
puts &quot;Thread recursion limit: ~#{thread_depth} calls&quot;

async_depth = nil
Async do
  async_depth = test_recursion_depth(&quot;async&quot;)
end
puts &quot;Async fiber recursion limit: ~#{async_depth} calls&quot;
</code></pre>
<p>When we ran this, the results were revealing:</p>
<pre><code>Thread recursion limit:      ~11910 calls
Async fiber recursion limit: ~1482 calls
</code></pre>
<p>Threads have roughly 8x more stack space than Async fibers. This is because
threads use larger stacks (1MB-8MB) while fibers are designed to be lightweight
with smaller stacks (512KB-1MB). This difference in stack allocation is why
threads can handle deeper recursion before hitting the overflow limit.</p>
<p>With 233 pairs requiring ~1,160 stack frames, we were right at the edge of the
Async fiber limit. But we were still well within the thread limit, which is why
switching to <code>Thread.new</code> seemed to fix it.</p>
<p>When we tested with 500 pairs (which some of our larger calendars had), even
<code>Thread.new</code> failed with a stack overflow. So it wasn't really a fix, it just
pushed the problem further down the road.</p>
<h2>The GitHub Trail</h2>
<p>After we understood the problem, we searched the Rails repository and found the
exact history:</p>
<ol>
<li>
<p><strong>April 4, 2024 - <a href="https://github.com/rails/rails/pull/51492">PR #51492</a></strong>:
This PR changed OR nodes from <strong>binary</strong> to <strong>n-ary</strong> to handle multiple
children in a single node.</p>
</li>
<li>
<p><strong>September 24, 2024 -
<a href="https://github.com/rails/rails/issues/53031">Issue #53031</a></strong>: Someone
reported the exact issue we were seeing. Queries with 800+ OR conditions that
worked in Rails 7.1 now crashed in Rails 7.2.</p>
</li>
<li>
<p><strong>September 25, 2024 -
<a href="https://github.com/rails/rails/pull/53032">PR #53032</a></strong>:
<a href="https://github.com/fatkodima">fatkodima</a> fixed it with a one-line change in
<code>PredicateBuilder#grouping_queries</code>:</p>
</li>
</ol>
<p><strong>Before:</strong></p>
<pre><code class="language-ruby">queries = queries.reduce { |result, query| Arel::Nodes::Or.new([result, query]) }
</code></pre>
<p><strong>After:</strong></p>
<pre><code class="language-ruby">queries = Arel::Nodes::Or.new(queries)
</code></pre>
<p>The difference is profound. The old code using <code>reduce</code> created a deeply nested
structure like Russian nesting dolls:</p>
<pre><code>Or([Or([Or([Query1, Query2]), Query3]), Query4])  # 232 levels with 233 pairs
</code></pre>
<p>The new code creates a flat structure:</p>
<pre><code>Or([Query1, Query2, Query3, Query4, ...Query233])  # Just 1 level
</code></pre>
<p>With a flat structure, the recursive visitor only traverses one level instead of
232, eliminating the stack overflow. The fix is available in Rails 7.2.2+.</p>
<h2>Our Solution</h2>
<p>We're currently on Rails 7.2.1.1 and upgrading immediately would require
extensive testing. So we implemented a workaround in our code:</p>
<pre><code class="language-ruby">def abandoned_events
  return @_abandoned_events if defined?(@_abandoned_events)

  pairs = event_params.map { |e| [e[:url], e[:recurrence_id]] }
  return if pairs.empty?

  events_in_time_range = calendar.events
    .where(&quot;start_time BETWEEN ? AND ?&quot;, start_time, end_time)

  # Get candidate events using simple IN clauses
  urls = pairs.map(&amp;:first).uniq
  recurrence_ids = pairs.map(&amp;:last).uniq

  candidate_events = events_in_time_range
    .where(url: urls)
    .where(recurrence_id: recurrence_ids)

  # Filter to exact pairs in memory
  pairs_set = pairs.to_set
  ids_to_exclude = candidate_events.select { |event|
    pairs_set.include?([event.url, event.recurrence_id])
  }.map(&amp;:id)

  @_abandoned_events = if ids_to_exclude.empty?
    events_in_time_range
  else
    events_in_time_range.where.not(id: ids_to_exclude)
  end
end
</code></pre>
<p>This approach:</p>
<ul>
<li>Uses simple IN clauses instead of complex OR conditions</li>
<li>Filters the exact pairs in memory (fast with a Set)</li>
<li>Excludes by ID, which is a flat list</li>
<li>Never builds deeply nested Arel nodes</li>
</ul>
<p>The trade-off is we run 2 queries instead of 1, but the queries are simpler and
more efficient. The in-memory filtering is negligible since we're already
constrained by time range.</p>
<h2>Measuring and Verifying</h2>
<p>To prove our hypothesis, we wrote verification scripts that patched Rails
internals to measure what was actually happening.</p>
<h3>Counting Arel Visitor Calls</h3>
<p>We created a script that patched <code>visit_Arel_Nodes_Or</code> to count how many times
it was called and how deep the recursion went:</p>
<pre><code class="language-ruby">module Arel
  module Visitors
    class ToSql
      alias_method :original_visit_Arel_Nodes_Or, :visit_Arel_Nodes_Or

      def visit_Arel_Nodes_Or(o, collector)
        @or_call_count ||= 0
        @or_call_count += 1
        @max_depth ||= 0
        @current_depth ||= 0
        @current_depth += 1
        @max_depth = [@max_depth, @current_depth].max

        result = original_visit_Arel_Nodes_Or(o, collector)

        @current_depth -= 1
        result
      end
    end
  end
end
</code></pre>
<p>Results with 50 test pairs:</p>
<ul>
<li><strong>Rails 7.1.5.2</strong>: <code>visit_Arel_Nodes_Or</code> was called 1 time and max recursion
depth was 0.</li>
<li><strong>Rails 7.2.1.1</strong>: <code>visit_Arel_Nodes_Or</code> was called 49 times and max recursion
depth was 100.</li>
</ul>
<p>With 233 pairs in production:</p>
<ul>
<li><strong>Rails 7.1.5.2</strong>: Still called 1 time, depth 0 (iterative approach)</li>
<li><strong>Rails 7.2.1.1</strong>: Called 232 times, depth ~466 (exceeds Async fiber limit)</li>
</ul>
<h3>Testing the Actual Breaking Points</h3>
<p>We ran the problematic query with different pair counts to find exactly where it
breaks:</p>
<pre><code class="language-ruby"># Test with Async
Async do
  pairs = calendar.events.limit(count).pluck(:url, :recurrence_id)
  calendar.events.where.not([:url, :recurrence_id] =&gt; pairs).count
end.wait

# Test with Thread
Thread.new do
  pairs = calendar.events.limit(count).pluck(:url, :recurrence_id)
  calendar.events.where.not([:url, :recurrence_id] =&gt; pairs).count
end.join
</code></pre>
<p>Results:</p>
<ul>
<li><strong>Async Fiber</strong>: Breaks at approximately 233 pairs</li>
<li><strong>Thread.new</strong>: Breaks at approximately 500 pairs</li>
</ul>
<p>This confirmed that Thread.new wasn't a real solution - it just had more
headroom before hitting the same problem.</p>
<p>For now, we're sticking with our workaround. It works reliably, performs well,
and doesn't require upgrading Rails immediately. When we do upgrade to Rails
7.2.2+, we can revert to the original clean syntax, knowing that the Rails team
has fixed the underlying issue.</p>
<p>If you're on Rails 7.2.0 through 7.2.1.x and use composite key queries with
large datasets, watch out for this issue. The fix is in Rails 7.2.2+, or you can
work around it like we did.</p>
<h2>References</h2>
<ul>
<li><a href="https://github.com/rails/rails/pull/51492">PR #51492 - Bug introduced</a></li>
<li><a href="https://github.com/rails/rails/issues/53031">Issue #53031 - Bug reported</a></li>
<li><a href="https://github.com/rails/rails/pull/53032">PR #53032 - Bug fixed</a></li>
</ul>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Active Record adds support for deprecating associations]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/active-record-deprecated-associations"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/active_record_adds_support_for_deprecating_associations.jpg"/>
      <id>https://www.bigbinary.com/blog/active-record-deprecated-associations</id>
      <published>2025-07-03T00:00:00.000Z</published>
      <updated>2025-07-03T00:00:00.000Z</updated>
      <author>
        <name>Chirag Shah</name>
        <uri>https://www.bigbinary.com/blog/authors/chiraggshah</uri>
      </author>
      <category term="Rails"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/active_record_adds_support_for_deprecating_associations.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Active Record adds the ability to safely remove associations by first deprecating them]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/active-record-deprecated-associations"><![CDATA[<p>In Rails 8, we can now mark Active Record associations as deprecated. This makes
it easy to phase out old associations from our codebase, while still keeping
them around to safely remove their usage. Whenever a deprecated association is
used, whether by calling the association, executing a query that references it,
or triggering a side‑effect like <code>:dependent</code> or <code>:touch</code>, Rails will alert us
according to our chosen reporting mode.</p>
<h3>Marking an association as deprecated</h3>
<p>Simply pass the <code>deprecated: true</code> option when declaring an association.</p>
<pre><code class="language-ruby">class User &lt; ApplicationRecord
  has_many :meetings, deprecated: true
end
</code></pre>
<p>Now, every time the meeting association is invoked, we’ll get a deprecation
warning in our logs.</p>
<pre><code class="language-bash">&gt; user.meetings
The association User#meetings is deprecated, the method meetings was invoked ((calendar):4:in '&lt;main&gt;')

&gt; User.includes(:meetings).where(id: 1)
The association User#meetings is deprecated, referenced in query to preload records ()

&gt; User.joins(:meetings).where(id: 1)
The association User#meetings is deprecated, referenced in query to join its table ()
</code></pre>
<h3>Working with different association types</h3>
<p>We can deprecate any association type.</p>
<pre><code class="language-ruby">class Order &lt; ApplicationRecord
  # has_many
  has_many :line_items, deprecated: true

  # belongs_to
  belongs_to :customer, deprecated: true

  # has_one
  has_one :profile, deprecated: true

  # has_many through
  has_many :archived_comments, through: :comments, deprecated: true
end
</code></pre>
<h3>Reporting modes and backtrace support</h3>
<p>This feature supports three deprecation modes:</p>
<ul>
<li><code>:warn</code> (default) Logs a warning to our Active Record logger.</li>
<li><code>:raise</code> Raises an exception when the deprecated association is used.</li>
<li><code>:notify</code> Emits an Active Support notification event with the key
<code>deprecated_association.active_record</code>. This can be used to send notifications
to external services like Honeybadger etc. We can check the details about its
payload
<a href="https://edgeguides.rubyonrails.org/active_support_instrumentation.html#deprecated-association-active-record">here</a>.</li>
</ul>
<p>Backtraces are disabled by default. If <code>:backtrace</code> is true, <code>:warn</code> mode will
include a clean backtrace in the message, and <code>:notify</code> mode will have a
<code>backtrace</code> key in the payload. Exceptions raised via <code>:raise</code> mode will always
have a clean stack trace.</p>
<p>We can change the global default mode in an initializer.</p>
<pre><code class="language-ruby"># config/initializers/deprecated_associations.rb
ActiveRecord.deprecated_associations_options = {
  mode: :warn,      # :warn | :raise | :notify
  backtrace: true   # whether to include a cleaned backtrace
}
</code></pre>
<p>It can also be set at an environment level.</p>
<pre><code class="language-ruby"># config/environments/development.rb
Rails.application.configure do
  config.active_record.deprecated_associations_options = { mode: :raise, backtrace: true }
end

# config/environments/production.rb
Rails.application.configure do
  config.active_record.deprecated_associations_options = { mode: :warn, backtrace: true }
end
</code></pre>
<h3>Why deprecate rather than remove?</h3>
<p>In large applications, it’s often hard to guarantee complete test coverage. Some
association usages may only surface in production.</p>
<p>Deprecating an association first, lets us:</p>
<ul>
<li>Identify every code path (tests, console, background jobs) that relies on it.</li>
<li>Gradually refactor those references before removal.</li>
<li>Ensure confidence that deleting the association won’t break anything
unexpectedly.</li>
</ul>
<p>This new feature in Rails provides a clean and intuitive way to phase out our
associations with deprecation warnings, making it easier to maintain and
refactor large codebases.</p>
<p><em>This feature <a href="https://github.com/rails/rails/pull/55285">was merged</a> recently,
and will be released in the next Rails minor/patch version.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Active Job Continuations]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/active-jobs-continuations"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/active_job_continuations.jpg"/>
      <id>https://www.bigbinary.com/blog/active-jobs-continuations</id>
      <published>2025-06-09T00:00:00.000Z</published>
      <updated>2025-06-09T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Sidekiq"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/active_job_continuations.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Active job feature that allows us to describe jobs in an interruptible manner]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/active-jobs-continuations"><![CDATA[<p>Active Job Continuations was recently merged into Rails. We recommend that you go
through the description in the
<a href="https://github.com/rails/rails/issues/55127">pull request</a> since they are so
well written.</p>
<p>If you prefer watching a video to learn about Active Job Continuations, then we
made a video for you.</p>
<p><a href="https://www.youtube.com/watch?v=r4uuQh1Zog0"><img src="https://img.youtube.com/vi/r4uuQh1Zog0/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=r4uuQh1Zog0">Watch on YouTube</a></p>
<p>In short, this feature allows you to configure your jobs in such a manner that
the job can be interrupted and next time when the job starts, it'll start from a
particular point so that the work done so far is not totally wasted.</p>
<p>This work is highly inspired by Shopify's
<a href="https://github.com/Shopify/job-iteration">job-iteration</a> gem.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Understanding Queueing Theory]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/understanding-queueing-theory"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/understanding_queueing_theory.jpg"/>
      <id>https://www.bigbinary.com/blog/understanding-queueing-theory</id>
      <published>2025-06-03T00:00:00.000Z</published>
      <updated>2025-06-03T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Sidekiq"/>
      <category term="Ruby"/>
      <category term="Solid Queue"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/understanding_queueing_theory.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 6 of our blog series on scaling Rails applications. --- Queueing Systems In web applications, not every task needs to be processed immediately. When you upload a large video file, send a...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/understanding-queueing-theory"><![CDATA[<p><em>This is Part 6 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<hr>
<h2>Queueing Systems</h2>
<p>In web applications, not every task needs to be processed immediately. When you
upload a large video file, send a bulk email campaign, or generate a complex
report, these time-consuming operations are often handled in the background.
This is where queueing systems like <a href="https://sidekiq.org/">Sidekiq</a> or
<a href="https://github.com/rails/solid_queue">Solid Queue</a> comes into play.</p>
<p>Queueing theory helps us understand how these systems behave under different
conditions - from quiet periods to peak load times.</p>
<p>Let's understand the fundamentals of queueing theory.</p>
<h2>Basic Terminology in Queueing Theory</h2>
<ol>
<li>
<p><strong>Unit of Work</strong>: This is the individual item needing service - a job.</p>
</li>
<li>
<p><strong>Server</strong>: This is one &quot;unit of parallel processing capacity.&quot; In queuing
theory, this doesn't necessarily mean a physical server. It refers to the
ability to process one unit of work at a time. For JRuby or TruffleRuby, each
thread can be considered a separate &quot;server&quot; since they can execute in
parallel. For CRuby/MRI, because of the GVL, the concept of a &quot;server&quot; is
different. We'll discuss it later.</p>
</li>
<li>
<p><strong>Queue Discipline</strong>: This is the rule determining which unit of work is
selected next from the queue. For Sidekiq and Solid Queue, it is FCFS(First
Come First Serve). If there are multiple queues, which job is selected
depends on the priority of the queue.</p>
</li>
<li>
<p><strong>Service Time</strong>: The actual time it takes to process a unit of work (how
long a job takes to execute).</p>
</li>
<li>
<p><strong>Latency/Wait Time</strong>: How long jobs spend waiting in the queue before being
processed.</p>
</li>
<li>
<p><strong>Total Time</strong>: The sum of service time and wait time. It's the complete
duration from when a job is enqueued until it finishes executing.</p>
</li>
</ol>
<h2>Little's law</h2>
<p>Little's law is a theorem in queuing theory that states that the average number
of jobs in a system is equal to the average arrival rate of new jobs multiplied
by the average time a job spends in the system.</p>
<pre><code class="language-ruby">L = λW
</code></pre>
<p>L = Average number of jobs in the system <br>
λ = Average arrival rate of new jobs <br>
W = Average time a job spends in the system</p>
<p>For example, if jobs arrive at a rate of 10 per minute (λ), and each job takes
30 seconds (W) to complete:</p>
<p>Average number of jobs in system = 10 jobs/minute * 0.5 minutes = 5 jobs</p>
<p>This helps us understand the current state of our system and that there are 5
jobs in the system on average at any given moment.</p>
<p><code>L</code> is also called <strong>offered traffic</strong>.</p>
<p>Note: Little's Law assumes the arrival rate is consistent over time.</p>
<h3>Managing Utilization</h3>
<p>Utilization measures how busy our processing capacity is.</p>
<p>Mathematically, it is the ratio of how much processing capacity we're using to
the processing capacity we have.</p>
<pre><code class="language-ruby">utilization = (average number of jobs in the system/capacity to handle jobs) * 100
</code></pre>
<p>In other words, it could be written as follows.</p>
<pre><code class="language-ruby">utilization = (offered_traffic / parallelism) * 100
</code></pre>
<p>For example, if we are using Sidekiq to manage our background jobs then in a
single-threaded case, parallelism is equal to the number of Sidekiq processes.</p>
<p>Let's look at a practical case with numbers:</p>
<ul>
<li>We have 30 jobs arriving every minute</li>
<li>It takes 0.5 minutes to process a job</li>
<li>We have 20 Sidekiq processes</li>
</ul>
<p>In this case, the utilization will be:</p>
<pre><code class="language-ruby">utilization = ((30 jobs/minute * 0.5 minutes) / 20 processes) * 100 = 75%
</code></pre>
<h3>High utilization is bad for performance</h3>
<p>Let's assume we maintain 100% utilization in our system. It means that if, on
average, we get 30 jobs per minute, then we have just enough capacity to handle
30 jobs per minute.</p>
<p>One day, we started getting 45 jobs per minute. Since utilization is at 100%,
there is no extra room to accommodate the additional load. This leads to higher
latency.</p>
<p>Hence, having a high utilization rate may result in low performance, as it can
lead to higher latency for specific jobs.</p>
<h3>The knee curve</h3>
<p>Mathematically, it would seem that only when the utilization rate hits 100%
should the latency spike up. However, in the real world, it has been found that
latency begins to increase dramatically when utilization reaches around 70-75%.</p>
<p>If we draw a graph between utilization and performance then the graph would look
something like this.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-queueing-theory/knee-curve.png" alt="The knee curve"></p>
<p>The point at which the curve bends sharply upwards is called &quot;Knee&quot; in the
performance curve. At this point, the exponential effects predicted by queueing
theory become pronounced, causing the queue latency to climb up quickly.</p>
<p>Running any system consistently above 70-75% utilization significantly increases
the risk of spiking the latency, as jobs spend more and more time waiting.</p>
<p>This would directly impact the customer experience, as it could result in delays
in sending emails or making calls to Twilio to send SMS messages, etc.</p>
<p>Tracking this latency will be covered in the upcoming blogs. The tracking of
metrics depends on the queueing backend used (Sidekiq or Solid Queue).</p>
<h2>Concurrency and theoretical parallelism</h2>
<p>In Sidekiq, a process is the primary unit of parallelism. However, concurrency
(threads per process) significantly impacts a process's effective throughput.
Because of GVL we need to take into account how long the job is waiting for I/O.</p>
<p>The more time a job spends waiting on external resources (like databases or
APIs) rather than executing Ruby code, the more other threads within the same
process can run Ruby code while the first thread waits.</p>
<p>We learned about Amdahl's law in
<a href="https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency">Part 3</a>
of this series.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-queueing-theory/amdahls-law.png" alt="Amdahl's law"></p>
<p>Where:</p>
<p><code>p</code> is the portion that can be parallelized (the I/O percentage)</p>
<p><code>n</code> is the number of threads (concurrency)</p>
<p>Speedup is equivalent to theoretical parallelism in this context. In queueing
theory, parallelism refers to how many units of work can be processed
simultaneously. When we calculate speedup using Amdahl's Law, we're essentially
determining how much faster a multi-threaded system can handle work compared to
a single-threaded system.</p>
<p>Let's assume that a system has an I/O of 50% and a concurrency of 10. Then
Speedup will be:</p>
<pre><code class="language-ruby">Speedup = 1 / ((1 - 0.5) + 0.5 / 10) = 1 / 0.55 = 1.82 ≈ 2
</code></pre>
<p>This means one Sidekiq process with 10 threads will handle jobs twice as fast as
Sidekiq with a single process with a single thread.</p>
<p>Let's recap what we are saying here. We are assuming that the system has I/O of
50%. System is using a single Sidekiq process with 10 threads(concurrency). Then
because of 10 threads the system has a speed gain of 2x compared to system
having just a single thread. In other words, just because we have 10 threads
running we are not going to gain 10X performance improvement. What those 10
threads is getting us is what is called &quot;theoretical parallelism&quot;.</p>
<p>Similarly, for other values of I/O and Concurrency, we can get the theoretical
parallelism.</p>
<table>
<thead>
<tr>
<th>I/O</th>
<th>Concurrency</th>
<th>Theoretical parallelism</th>
</tr>
</thead>
<tbody>
<tr>
<td>5%</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>25%</td>
<td>5</td>
<td>1.25</td>
</tr>
<tr>
<td>50%</td>
<td>10</td>
<td>2</td>
</tr>
<tr>
<td>75%</td>
<td>16</td>
<td>3</td>
</tr>
<tr>
<td>90%</td>
<td>32</td>
<td>8</td>
</tr>
<tr>
<td>95%</td>
<td>64</td>
<td>16</td>
</tr>
</tbody>
</table>
<p>Let's go over one more time. In the last example, what we are stating is that if
a system has 95% I/O and if the system has 64 threads running, then that will
give 16x performance improvement over the same system running on a single
thread.</p>
<p>Here is the graph for this data.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-queueing-theory/concurrency-vs-effective-parallelism.png" alt="Theoretical Parallelism"></p>
<p>As shown in the graph, a Sidekiq process with 16 threads handling jobs that are
75% I/O-bound achieves a theoretical parallelism of approximately 3. In other
words, 3x performance improvement is there over a single-threaded system.</p>
<h2>Calculating the number of processes required</h2>
<p>At the beginning of this article, we discussed &quot;Little's law&quot; and we discussed
that <code>L</code> is also called &quot;offered traffic,&quot; which depicts the &quot;average number of
jobs in the system&quot;.</p>
<p>If &quot;offered traffic&quot; is 5, then it means we have 5 units of work arriving on
average that requires processing simultaneously.</p>
<p>We just learned that if the utilization is greater than 75% then it can cause
problems as there is a risk of latency to spike.</p>
<p>For queues with low latency requirements(eg. <code>urgent</code>), we need to target a
lower utilization rate. Let's say we want utilization to be around 50% to be on
the safe side.</p>
<p>Now we know the utilization rate that we need to target and we know the &quot;offered
traffic&quot;. So now we can calculate the &quot;parallelism&quot;.</p>
<pre><code class="language-ruby">utilization = offered_traffic / parallelism
=&gt; 0.50 = 5 / parallelism
=&gt; parallelism = 5 / 0.50 = 10
</code></pre>
<p>This means we need a theoretical parallelism of 10 to ensure that the
utilization is 50% at max.</p>
<p>Let's assume the jobs in this queue have an average of <code>50%</code> I/O. Based on the
above mentioned graph, we can see that if the concurrency is 10 then we get
parallelism of 2. However, increasing the concurrency doesn't increase the
parallelism. It means if we want 10 parallelism, then we can't just switch to
concurrency of 50. Even the concurrency of 50 (or 50 threads) will only yield a
parallelism of 2.</p>
<p>So we have no choice but to add more processes. Since one process with 10
concurrency is yielding a parallelism of 2 we need to add 5 processes to get 10
parallelism.</p>
<p><em>To get the I/O wait percentage, we can make use of perfm.
<a href="https://github.com/bigbinary/perfm?tab=readme-ov-file#sidekiq-gvl-instrumentation">Here</a>
is the documentation on how it can be done.</em></p>
<pre><code class="language-ruby">Total number of Sidekiq processes required = 10 / 2 = 5
</code></pre>
<p>Here we're talking about Sidekiq free version, where we'll only be able to run a
single process per dyno. If we're using Sidekiq Pro, we can run multiple
processes per dyno via Sidekiq Swarm.</p>
<p>We can provision 5 dynos for the urgent queue. But we should always have a queue
time based autoscaler like Judoscale enabled to handle spikes.</p>
<h2>Sources of Saturation</h2>
<p>We discussed earlier that, in the context of queueing theory, the saturation
point is typically reached at around 70-75% utilization. This is from the point
of view of further gains by adding more threads.</p>
<p>However saturation can occur in other parts of the system.</p>
<h3>CPU</h3>
<p>The servers running your Sidekiq processes have finite CPU and memory. While CPU
usage is a metric we can track for Sidekiq, it's generally not the only one we
need to focus on for scaling decisions.</p>
<p>CPU utilization can be misleading. If our jobs spend most of their time doing
I/O (like making API calls or database queries), in which case CPU usage will be
very low, even when our Sidekiq system is at capacity.</p>
<h3>Memory</h3>
<p>Memory utilization impacts performance very differently from CPU utilization.
Memory utilization generally exhibits minimal changes in latency or throughput
from 0% to 100% utilization. However, after 100% utilization, things will start
to deteriorate significantly. The system will start using the swap memory, which
can be very slow and thereby increase the job service times.</p>
<h3>Redis</h3>
<p>Another place where saturation can occur is in our datastore i.e Redis in case
of Sidekiq. We have to make sure that we provision a separate Redis instance for
Sidekiq and also make sure to set the eviction policy to <code>noeviction</code>. This
ensures that Redis will reject new data when the memory limit is reached,
resulting in an explicit failure rather than silently dropping important jobs.</p>
<p><em>This was Part 6 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Understanding Active Record Connection Pooling]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/understanding-active-record-connection-pooling"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/understanding_active_record_connection_pooling.jpg"/>
      <id>https://www.bigbinary.com/blog/understanding-active-record-connection-pooling</id>
      <published>2025-05-13T00:00:00.000Z</published>
      <updated>2025-05-13T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/understanding_active_record_connection_pooling.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 5 of our blog series on scaling Rails applications. Database Connection Pooling When a Rails application needs to interact with a database, it establishes a connection, which is a...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/understanding-active-record-connection-pooling"><![CDATA[<p><em>This is Part 5 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<h2>Database Connection Pooling</h2>
<p>When a Rails application needs to interact with a database, it establishes a
connection, which is a dedicated communication channel between the application
and the database server. When a new request comes to Rails, the operation can be
handled like this.</p>
<ol>
<li>Create a connection</li>
<li>Do database operation</li>
<li>Close the connection</li>
</ol>
<p>When the next request comes, then repeat the above process.</p>
<p>Creating new database connections is an expensive operation - it takes time to
establish the connection, authenticate, and set up the communication channel. It
means that every single time a request comes, we are spending time in setting up
the connection.</p>
<p>Wouldn't it be better to store the established connection to somewhere and when
a new request comes, then get the pre-established connection from this pool.
This expedites the process since we don't need to create a connection and close
a connection in every single request.</p>
<p>The new process might look like this.</p>
<ol>
<li>Create a connection</li>
<li>Do database operation</li>
<li>Put the connection in a pool</li>
</ol>
<p>Now, when a new request comes, the operation will look like this.</p>
<ol>
<li>Get the connection from the pool</li>
<li>Do database operation</li>
<li>Return the connection to the pool</li>
</ol>
<p>Database connection pooling is a performance optimization technique that
maintains a set of reusable database connections.</p>
<h2>Active Record Connection Pool Implementation</h2>
<p>Active Record manages a pool of database connections for each web and background
process. Each process will have a connection pool of its own, which means a
Rails application running with multiple processes (like Puma processes, Sidekiq
processes) will have multiple independent connection pools. The pool is a set of
database connections that are shared among threads in the <strong>same process</strong>.</p>
<p>Note that the pooling happens at the process level. A thread from process A
can't get a connection from process B.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-active-record-connection-pooling/without-pg-bouncer.png" alt="Connection pooling"></p>
<p>When a connection is needed, the thread checks out a connection from the pool,
perform operations, and then returns the connection to the pool. This is done at
a query level now. For each individual query, a connection is leased, used, and
then returned back to the pool.</p>
<p>Pre Rails 7.2, the connection used to be leased and held till the end of the
request if it is a web request and till the job is done if it is a background
job. This was a problem for applications that spent a lot of time doing I/O. The
thread will hog the connection for the entire duration of the I/O operation
limiting the number of queries that can be executed concurrently. To facilitate
this change and make query caching work, the query cache has been
<a href="https://github.com/rails/rails/pull/50938/">updated</a> to be owned by the pool.</p>
<p>This means that the query cache is now shared across all connections in the
pool. Previously, each connection had its own query cache. As the whole request
used the same connection, this was fine. But now, as the connection is leased
for each query, the query cache needs to be shared across all connections in the
pool.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-active-record-connection-pooling/connection-leasing-comparison.png" alt="Connection-leasing-comparison"></p>
<h2>Connection Pool Configuration Options</h2>
<p>Active Record's connection pool behavior can be customized through several
configuration options in the database.yml file:</p>
<ul>
<li><a href="https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html#method-i-pool">pool</a>:
Sets the maximum number of connections the pool will maintain. The default is
tied to <code>RAILS_MAX_THREADS</code>, but you can set it to any value. There is a small
problem when you set it to <code>RAILS_MAX_THREADS</code> which we'll discuss later.</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html#method-i-checkout_timeout">checkout timeout</a>:
Determines how long a thread will wait to get a connection before timing out.
The default is 5 seconds. If all connections are in use and a thread waits
longer than this value, an <code>ActiveRecord::ConnectionTimeoutError</code> exception
will be raised.</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html#method-i-idle_timeout">idle timeout</a>:
Specifies how long a connection can remain idle before it's removed from the
pool. The default is 300 seconds. This helps reclaim resources from
connections that aren't being used.</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html#method-i-reaping_frequency">reaping frequency</a>:
Controls how often the Reaper(which we'll discuss shortly) process runs to
remove dead or idle connections. The default is 60 seconds.</li>
</ul>
<h2>Active Record Connection Pool Reaper</h2>
<p>Database connections can sometimes become &quot;dead&quot; due to issues like database
restarts, network problems etc. Active Record provides Reaper to handle this.</p>
<p>The Reaper periodically checks connections in the pool and removes dead
connections as well as idle connections that have been checked out for a long
time.</p>
<p>It acts somewhat like a garbage collector for database connections. The Reaper
uses the <code>idle_timeout</code> setting to determine how long a connection can remain
idle before being removed, tracking idle time based on when connections were
last used.</p>
<p>There is another configuration option called <code>reaping_frequency</code> that controls
how often the Reaper runs to remove dead or idle connections from the pool. By
default, this is set to 60 seconds. It means the Reaper will wake up once every
minute to perform its maintenance tasks.</p>
<p>If your application is spiky and receives a lot of traffic in surges, then set
the reaping frequency and idle timeout to a lower value. This will ensure that
the reaper runs more frequently and removes idle connections more quickly,
helping to keep the connection pool healthy and responsive.</p>
<h2>Why are idle connections bad?</h2>
<p>Idle database connections can significantly impact database performance for
several interconnected reasons:</p>
<p><strong>Memory Consumption</strong>: Each database connection, even when idle, maintains its
own memory allocation. The database must reserve memory for session state,
buffers, user context, and transaction workspace. This memory remains allocated
even when the connection isn't doing any work. For example, if each connection
uses 10 MB of memory, 100 idle connections would unnecessarily consume 1 GB of
your database's memory that could otherwise be used for active queries, caching,
or other productive work.</p>
<p><strong>CPU overhead</strong>: While &quot;idle&quot; suggests no activity, the database still performs
regular maintenance work for each connection. It must monitor connection health
via keepalive checks, manage process tables etc.</p>
<p>The crucial issue is that the overhead of having idle connections scales
non-linearly. As we add more idle connections, the database spends an increasing
proportion of its CPU time just managing these connections rather than
processing actual queries. Thankfully, the reaper handles this for us.</p>
<h2>How many database connections will the web and background processes utilize at maximum?</h2>
<p>As we learned, the connection pool is managed at the process level. Each Rails
process maintains its own pool.</p>
<ol>
<li><strong>In web processes (Puma)</strong>:</li>
</ol>
<p>Each Puma process is a separate process with its own connection pool. In a
process, each thread can check out one connection. Therefore, maximum
connections needed per process equals <code>max_threads</code> setting in Puma.</p>
<ol start="2">
<li><strong>In background processes (Sidekiq)</strong>:</li>
</ol>
<p>Sidekiq runs as a separate process with its own connection pool. The Sidekiq
<code>concurrency</code> setting determines the number of threads and therefore the maximum
connections needed equals the <code>concurrency</code> value.</p>
<p><em>Note: If you're using Sidekiq swarm and running multiple Sidekiq processes,
then take that it into account.</em></p>
<p>We can calculate the total potential connections for a typical application as
shown below.</p>
<pre><code>Web connections = Number of web dynos * Number of Puma processes
                  * `max_threads` value

Background connections = Number of worker dynos * Number of Sidekiq processes
                         * Threads per process

Total number of connections = Web connections + Background connections
</code></pre>
<p>The key thing to note here is that the database needs to support at least these
many simultaneous connections.</p>
<p><em>Note that if preboot is enabled, then the maximum number of connections will be
<strong>double</strong> the above value. This is because during the release phase, there is a
small window in which both the old dynos and new dynos are running.</em></p>
<p>In Rails 7,
<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-load_async"><code>load_async</code></a>
was introduced which allows us to run database queries asynchronously in a
background thread. When <code>load_async</code> is in used, the calculation for maximum
number of connections needed changes a bit. First, let's understand how
<code>load_async</code> works.</p>
<h2>How <code>load_async</code> works</h2>
<p><code>load_async</code> allows Rails to execute database queries asynchronously in
background threads. Unlike regular ActiveRecord queries which are lazily loaded,
<code>load_async</code> queries are always executed immediately in background threads and
joined to the main thread when results are needed.</p>
<p>The async executor is configured through the
<code>config.active_record.async_query_executor</code> setting. There are three possible
configurations:</p>
<ol>
<li><code>nil</code> (default): Async queries are disabled, and <code>load_async</code> will execute
queries synchronously.</li>
<li><code>:global_thread_pool</code>: Uses a single thread pool for all database
connections.</li>
<li><code>:multi_thread_pool</code>: Uses separate thread pools for each database
connection.</li>
</ol>
<p>Rails provides a configuration option named
<a href="https://guides.rubyonrails.org/configuring.html#config-active-record-global-executor-concurrency">global_executor_concurrency</a>
(default: 4) that controls how many concurrent async queries can run per
process. So, the maximum number of connections per process when <code>load_async</code> is
used.</p>
<pre><code class="language-ruby">Maximum connections per process = Process level concurrency
                                  + global_executor_concurrency + 1
</code></pre>
<p>Here Process level concurrency means <code>max_threads</code> for the Puma process and
<code>sidekiq_conurrency</code> for the Sidekiq process.</p>
<p>The &quot;+1&quot; accounts for the main control thread, which may occasionally need a
connection (e.g., during model introspection at class load time).</p>
<p>There is a nice
<a href="https://judoscale.com/tools/heroku-postgresql-connection-calculator">calculator</a>
created by the folks at <a href="https://judoscale.com/">Judoscale</a> which can be used to
calculate the maximum number of connections needed for your application.</p>
<h2>Setting Database Pool Size Configuration</h2>
<p>Our <code>database.yml</code> file has the following line.</p>
<pre><code class="language-yaml">pool: &lt;%%= ENV.fetch(&quot;RAILS_MAX_THREADS&quot;) { 5 } %&gt;
</code></pre>
<p>We know that a thread doesn't take more than one DB connection. So the maximum
number of connections needed per pool is equal to the total number of threads.
So the above configuration looks fine.</p>
<p>However this doesn't take into account whether we use <code>load_async</code> or not. If we
use <code>load_async</code>, then the number of connections needed per process will be
<code>RAILS_MAX_THREADS + global_executor_concurrency + 1</code>.</p>
<p>Do we really need to go into this much detail to determine the pool size? Turns
out there is a much easier answer.</p>
<p>Almost all database hosting providers mention the maximum number of connections
allowed in their plan. We can just set the pool config to the maximum number of
connections supported by our database plan. Let us say we have a Standard-0
database on Heroku. It supports up to 120 connections. So we can set the pool
config to 120.</p>
<pre><code class="language-yaml">pool: 120
</code></pre>
<p>We can do this because the database connections are lazily initialized in the
pool. The application doesn't create more database connections than it needs. So
we needn't be conservative here.</p>
<p>The only thing we need to ensure is that the maximum connection utilization
doesn't exceed the database plan limit. If that happens, then we have another
solution - PgBouncer.</p>
<h2>PgBouncer</h2>
<p>PgBouncer is a lightweight connection pooler for PostgreSQL. It sits between our
application(s) and our PostgreSQL database and manages a pool of database
connections.</p>
<p>While both PgBouncer and Active Record provide connection pooling, they operate
at different levels and serve different purposes.</p>
<p>Active Record connection pool operates within a single Ruby process and manages
connections for threads within the process, whereas PgBouncer is an external
connection pooler that sits between the application and the database and manages
connections across all the application processes.
<img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-active-record-connection-pooling/with-pg-bouncer.png" alt="With PgBouncer"></p>
<h2>The dreaded ActiveRecord::ConnectionTimeoutError</h2>
<p>This error comes up when a thread waits more than <code>checkout_timeout</code> seconds to
acquire a connection. This usually happens when the <code>pool</code> size is set to a
value less than the concurrency.</p>
<p>For example, lets say we have set the Sidekiq concurrency to 10 and pool size
to 5. If we have more than 5 threads wanting a connection at any point of time,
the threads will have to wait.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-active-record-connection-pooling/threads-waiting-for-connection.png" alt="Connection pooling"></p>
<p>What's the solution? As we discussed earlier setting the <code>pool</code> to a really high
value should fix the error in most cases.</p>
<p>Even after setting the config correctly, <code>ActiveRecord::ConnectionTimeoutError</code>
can still happen and it could be puzzling. Let is discuss a few scenarios where
this can happen.</p>
<h2>Custom code spinning up new threads and taking up connections</h2>
<pre><code class="language-ruby">class SomeService
  def process
    threads = []

    5.times do |index|
      threads &lt;&lt; Thread.new do
        ActiveRecord::Base.connection.execute(&quot;select pg_sleep(5);&quot;)
      end
    end

    threads.each(&amp;:join)
  end
end
</code></pre>
<p>Here 5 threads are spun up. Note that these threads also take up connections
from the same pool allotted to the process.</p>
<h3>Active Storage proxy mode</h3>
<p>Even if our application code is not spinning up new threads, Rails itself can
sometimes spin up additional threads. For example Active Storage configured in
<a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#proxy-mode">proxy mode</a>.</p>
<p>Active Storage’s proxy controllers
<a href="https://github.com/rails/rails/blob/b97a7625970c74f2273211ccb17046049f409110/activestorage/app/controllers/active_storage/blobs/proxy_controller.rb">1</a>,
<a href="https://github.com/rails/rails/blob/b97a7625970c74f2273211ccb17046049f409110/activestorage/app/controllers/active_storage/representations/proxy_controller.rb">2</a>
generate responses as streams, which require dedicated threads for processing.</p>
<p>This means that when serving an Active Storage file through one of these proxy
controllers, Rails actually utilizes two separate threads - one for the main
request and another for the streaming process. Each of these threads requires
its own separate database connection from the ActiveRecord connection pool.</p>
<h3>Rack timeouts</h3>
<p><a href="https://github.com/zombocom/rack-timeout">rack-timeout</a> is commonly used across
Rails applications to automatically terminate long-running requests. While it
helps prevent server resources from being tied up by slow requests, it can also
cause a few issues.</p>
<p>Rack timeout uses Ruby's
<a href="https://rubyapi.org/3.4/o/thread#method-i-raise">Thread#raise</a> API to terminate
requests that exceed the configured timeout. When a timeout occurs, rack-timeout
raises a <code>Rack::Timeout::RequestTimeoutException</code> from another thread. If this
exception is raised while a thread is in the middle of database operations, it
can prevent proper cleanup of database connections.</p>
<h2>Tracking down ActiveRecord::ConnectionTimeoutErrors</h2>
<p>If we still frequently see <code>ActiveRecord::ConnectionTimeoutError</code> exceptions in
our application, we can get additional context by logging the connection pool
info to our error monitoring service. This can help identify which all threads
were holding onto the connections when the error occurred.</p>
<pre><code class="language-ruby">config.before_notify do |notice|
  if notice.error_class == &quot;ActiveRecord::ConnectionTimeoutError&quot;
    notice.context = { connection_pool_info: detailed_connection_pool_info }
  end
end

def detailed_connection_pool_info
  connection_info = {}

  ActiveRecord::Base.connection_pool.connections.each_with_index do |conn, index|
    connection_info[&quot;connection_#{index + 1}&quot;] = conn.owner ? conn.owner.inspect : &quot;[UNUSED]&quot;
  end

  connection_info[&quot;current_thread&quot;] = Thread.current.inspect
  connection_info
end
</code></pre>
<p><code>&lt;thread_obj&gt;.inspect</code> gives us the name, id and status of the thread. For
example, if one entry in the hash looks like
<code>#&lt;Thread:0x00006a42eca73ba0@puma srv tp 002 /app/.../gems/puma-6.2.2/lib/puma/thread_pool.rb:106 sleep_forever&gt;</code>
then it means that the connection is taken up by a Puma thread.</p>
<h2>Monitoring Active Record Connection Pool Stats</h2>
<p>If we want to monitor Active Record Connection Pool stats, then periodically we
need to send the stats to a service provider which can display the data
graphically. For periodically checking the stat, we are using
<a href="https://github.com/jmettraux/rufus-scheduler">rufus-scheduler</a> gem. For
collecting the data and showing the data we are using NewRelic but you can use
any APM of your choice. We have configured to send the pool stat every 15
seconds.</p>
<p><a href="https://gist.github.com/vishnu-m/8cfae21cac385aa07819c8805e491872">Here</a> is the
gist which collects and sends data.</p>
<p><em>This was Part 5 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Finding the ideal threads per process with GVL tooling]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/tuning-puma-max-threads-configuration-with-gvl-instrumentation"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/tuning_puma_max_threads_configuration_with_gvl_instrumentation.jpg"/>
      <id>https://www.bigbinary.com/blog/tuning-puma-max-threads-configuration-with-gvl-instrumentation</id>
      <published>2025-05-06T00:00:00.000Z</published>
      <updated>2025-05-06T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/tuning_puma_max_threads_configuration_with_gvl_instrumentation.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 4 of our blog series on scaling Rails applications. In part 1 we saw how to find ideal number of processes for our Rails application. In part 3, we learned about Amdahl's law, which...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/tuning-puma-max-threads-configuration-with-gvl-instrumentation"><![CDATA[<p><em>This is Part 4 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<p>In
<a href="https://bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance">part 1</a>
we saw how to find ideal number of processes for our Rails application.</p>
<p>In
<a href="https://bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency">part 3</a>,
we learned about Amdahl's law, which helps us find the ideal number of threads
theoretically.</p>
<p>In this blog, we'll run a bunch of tests on our real production application to
see what the actual number of threads should be for each process.</p>
<p>In
<a href="https://bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance">part 1</a>
we discussed the presence of GVL and the concept of thread switching. Based on
the GVL's interaction, a thread can be in one of these three states.</p>
<ol>
<li><strong>Running</strong>: The thread has the GVL and is executing Ruby code.</li>
<li><strong>Idle</strong>: The thread doesn't want the GVL because it is performing I/O
operations.</li>
<li><strong>Stalled</strong>: The thread wants the GVL and is waiting for it in the GVL wait
queue.</li>
</ol>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/tuning-puma-max-threads-configuration-with-gvl-instrumentation/thread-states.png" alt="Thread states"></p>
<p>Based on the above diagram, we can approximately equate <code>idle time</code> to
<code>I/O time</code>.</p>
<h2>GVL instrumentation using perfm</h2>
<p>Thanks to Jean Boussier's work on the
<a href="https://bugs.ruby-lang.org/issues/18339">GVL instrumentation API</a> and John
Hawthorn's work on <a href="https://github.com/jhawthorn/gvl_timing">gvl_timing</a>, we can
now measure the time a thread spends in each of these states for apps running on
Ruby 3.2 or higher.</p>
<p>Using the great work done by these folks, we have created
<a href="https://github.com/bigbinary/perfm">perfm</a>, to help us figure out the ideal
number of Puma threads based on the application's workload.</p>
<p>Perfm inserts a Rack middleware to our Rails application. This middleware
instruments the GVL, collects the required metrics and stores them in a table.
It also has a <code>Perfm::GvlMetricsAnalyzer</code> class which can be used to generate a
report on the data collected.</p>
<h3>Using perfm to measure the application's I/O percentage</h3>
<p>To use perfm, we need to add the following line to our Gemfile.</p>
<pre><code class="language-ruby">gem 'perfm'
</code></pre>
<p>We'll run <code>bin/rails generate perfm:install</code>. This will generate the migration
to create <code>perfm_gvl_metrics</code> which will be used to store request-level metrics.</p>
<p>Now we'll create an initializer <code>config/initializers/perfm.rb</code>.</p>
<pre><code class="language-ruby">Perfm.configure do |config|
  config.enabled = true
  config.monitor_gvl = true
  config.storage = :local
end

Perfm.setup!
</code></pre>
<p>After deploying the code to production, we need to collect around 20K requests
as that will give us a fair number of data points to analyze. The GVL monitoring
can be disabled after that by setting <code>config.monitor_gvl</code> to <code>false</code> so that
the table doesn't keep growing.</p>
<p>After collecting the request data, now it's time to analyze it.</p>
<p>Run the following code in the Rails console.</p>
<pre><code class="language-ruby">irb(main):001* gvl_metrics_analyzer = Perfm::GvlMetricsAnalyzer.new(
irb(main):002*   start_time: 2.days.ago, # configure this
irb(main):003*   end_time: Time.current
irb(main):004&gt; )
irb(main):005&gt;
irb(main):006&gt; results = gvl_metrics_analyzer.analyze
irb(main):007&gt; io_percentage = results[:summary][:total_io_percentage]
=&gt; 45.09
</code></pre>
<p>This will give us the percentage of time spent doing I/O. We ran it in our
<a href="https://neeto.com/cal">NeetoCal</a> production application and we got a value of
45%.</p>
<p>As we discussed in
<a href="https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency">part 3</a>,
Amdahl's law gives us a theoretical maximum speedup based on the parallelizable
portion of our workload. The formula is given below.
<img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/tuning-puma-max-threads-configuration-with-gvl-instrumentation/amdahls-law.png" alt="Amdahl's law formula"></p>
<p>Where:</p>
<ul>
<li><code>p</code> is the portion that can be parallelized (in our case, it's 0.45)</li>
<li><code>N</code> is the number of threads</li>
<li><code>(1 - p)</code> is the portion that must run sequentially (in our case, it's 0.55)</li>
</ul>
<p>Let's calculate the theoretical speedup for different numbers of threads with p
= 0.45:</p>
<table>
<thead>
<tr>
<th>Thread Count (N)</th>
<th>Speedup</th>
<th>% Improvement from previous run</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1.00</td>
<td>-</td>
</tr>
<tr>
<td>2</td>
<td>1.29</td>
<td>29%</td>
</tr>
<tr>
<td>3</td>
<td>1.43</td>
<td>11%</td>
</tr>
<tr>
<td>4</td>
<td>1.52</td>
<td>6%</td>
</tr>
<tr>
<td>5</td>
<td>1.57</td>
<td>3%</td>
</tr>
<tr>
<td>6</td>
<td>1.60</td>
<td>2%</td>
</tr>
<tr>
<td>8</td>
<td>1.64</td>
<td>&lt;2%</td>
</tr>
<tr>
<td>16</td>
<td>1.69</td>
<td>&lt;1%</td>
</tr>
<tr>
<td>∞</td>
<td>1.82</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>We can see that after 4 threads, the percentage improvement drops below 5%. This
means that 4 is a reasonable value for <code>max_threads</code>. We can set the value of
<code>RAILS_MAX_THREADS</code> to 4.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/tuning-puma-max-threads-configuration-with-gvl-instrumentation/speedup-vs-thread-count.png" alt="Speedup V/S Number of threads"></p>
<p>Looking at the table, adding a 5th thread would only give us a 3% performance
improvement, which may not justify the additional memory usage and potential GVL
contention.</p>
<p>We have also created a small application to help visualize and find the ideal
number of threads when the I/O percentage is provided as input.
<a href="https://v0-single-page-application-lake.vercel.app/">Here</a> is the link to the
app.</p>
<h2>Validate thread count using stall time</h2>
<p>This value of <code>4</code> we got theoretically by using Amdahl's law. Now it's time to
put this law to test. Let's see in the real world if the value of <code>4</code> is the
correct value or not.</p>
<p>What we need to do is start with <code>RAILS_MAX_THREADS</code> env variable (Puma
<code>max_threads</code>) set to <code>4</code> and then check if this value provides minimal GVL
contention. By GVL contention, we mean the amount of time a thread spends
waiting for the GVL i.e the stall time.</p>
<p>If the stall time is high, that means the set thread count is high. We don't
want our request threads to spend their time doing nothing causing latency
spikes.<code>75ms</code> is an acceptable value for stall time. The lesser the better of
course.</p>
<p>The average stall time can be found in the perfm analyzer results. As we
mentioned earlier, we had collected data for <a href="https://neeto.com/cal">NeetoCal</a>.
Now let's find the average stall time.</p>
<pre><code class="language-ruby">irb(main):001* gvl_metrics_analyzer = Perfm::GvlMetricsAnalyzer.new(
irb(main):002*   start_time: 2.days.ago,
irb(main):003*   end_time: Time.current,
irb(main):004*   puma_max_threads: 4
irb(main):005&gt; )
irb(main):006&gt; results = gvl_metrics_analyzer.analyze
irb(main):007&gt; avg_stall_ms = results[:summary][:average_stall_ms]
=&gt; 110.24
</code></pre>
<p>The stall time seems a bit high. Let us decrease the <code>RAILS_MAX_THREADS</code> value
by 1 and collect a few data points(i.e around 20K requests). Now the value of
<code>RAILS_MAX_THREADS</code> will be <code>3</code>. This process has to be repeated until we find
the value for which the average stall time is less than <code>75ms</code>.</p>
<pre><code class="language-ruby">irb(main):001* gvl_metrics_analyzer = Perfm::GvlMetricsAnalyzer.new(
irb(main):002*   start_time: 2.days.ago,
irb(main):003*   end_time: Time.current,
irb(main):004*   puma_max_threads: 3
irb(main):005&gt; )
irb(main):006&gt; results = gvl_metrics_analyzer.analyze
irb(main):007&gt; avg_stall_ms = results[:summary][:average_stall_ms]
=&gt; 79.38
</code></pre>
<p>Now the output is closer to <code>75 ms</code>.</p>
<p>Hence we can finalize on the value 3 as the value for <code>RAILS_MAX_THREADS</code>. If we
decrease the value again by one i.e set it to 2, the stall time will decrease
but we're limiting the concurrency of our application. It is a trade-off.</p>
<p>Remember that our goal is to maximize concurrency while minimizing GVL
contention. But if our app spends a lot of time doing I/O - for instance, if we
have a proxy application that makes a lot of external API calls directly from
the controller, then we can switch the app server to
<a href="https://github.com/socketry/falcon">Falcon</a>. Falcon is tailor-made for such use
cases.</p>
<p>Broadly speaking, one should take care of the following items to ensure that the
time spent by the request doing I/O is minimal.</p>
<ul>
<li>Remove N+1 queries</li>
<li>Remove long-running queries</li>
<li>Move inline third party API calls to background job processor</li>
<li>Move heavy computational stuff to background job processor</li>
</ul>
<p>For a finely optimized Rails application, the <code>max_threads</code> value will be
around 3. That's why the default value of <code>max_threads</code> for Rails applications
is <code>3</code> now. This has been decided after a lot of discussion
<a href="https://github.com/rails/rails/issues/50450">here</a>. We recommend you read the
whole discussion. It is very interesting.</p>
<p><em>This was Part 4 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Amdahl's Law: the relationship of speedup and concurrency]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/amdahls_law_the_theoretical_relationship_between_speedup_and_concurrency.jpg"/>
      <id>https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency</id>
      <published>2025-04-29T00:00:00.000Z</published>
      <updated>2025-04-29T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/amdahls_law_the_theoretical_relationship_between_speedup_and_concurrency.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 3 of our blog series on scaling Rails applications. We have only two parameters to work with if we want to fine-tune our Puma configuration. 1. The number of processes. 2. The number of...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency"><![CDATA[<p><em>This is Part 3 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<p>We have only two parameters to work with if we want to fine-tune our Puma
configuration.</p>
<ol>
<li>The number of processes.</li>
<li>The number of threads each process can have.</li>
</ol>
<p>In
<a href="https://bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance">part 1</a>
of <a href="https://www.bigbinary.com/blog/scaling-rails-series">Scaling Rails series</a>,
we saw what the number of processes should be. Now let's look at what the number
of threads in each process should be.</p>
<h2>Amdahl's law</h2>
<p>Each application has a few things which must be performed in &quot;serial order&quot; and
a few things which can be &quot;parallelized&quot;. If we draw a diagram, then this is
what it will look like.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency/amdahls-gantt-chart1.png" alt="Amdahl's law Gantt chart"></p>
<p>Let's say that <code>T1'</code> and <code>T2'</code> are the enhanced times. These are the times the
application would take after the enhancement has been applied. In this case the
enhancement will come in the form of increasing the threads in a process.</p>
<p><code>T1'</code> will be same as <code>T1</code> since it's the serial part. <code>T2'</code> will be lower than
<code>T2</code> since we will parallelize some of the code. After the parallelization is
done, the enhanced version would look something like this.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency/amdahls-gantt-chart.png" alt="Amdahl's law Gantt chart"></p>
<p>It's clear that the serial part (T1) will limit how much speedup we can get no
matter how much we parallelize <code>T2</code>.</p>
<p>Computer scientist <a href="https://en.wikipedia.org/wiki/Gene_Amdahl">Gene Amdahl</a> came
up with <a href="https://en.wikipedia.org/wiki/Amdahl%27s_law">Amdahl's law</a> which gives
the mathematical value for the overall speedup that can be achieved.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency/amdahls-law.png" alt="Amdahl's law picture"></p>
<p>I made a video explaining how this formula came about.</p>
<p><a href="https://www.youtube.com/watch?v=2hYs2X6Fb1M"><img src="https://img.youtube.com/vi/2hYs2X6Fb1M/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=2hYs2X6Fb1M">Watch on YouTube</a></p>
<p><em>Amdahl's law states that the theoretical speedup gained from parallelization is
directly determined by the fraction of sequential code in the program.</em></p>
<p>Now, let's see how we can use Amdahl's law to determine the ideal number of
threads.</p>
<p>The parallelizable portion in this case is the portion of the application that
spends time doing I/O. The non-parallelizable portion is the time spent by the
application executing Ruby code. Remember that because of GVL, within a process,
only one thread has access to CPU at any point of time.</p>
<p>Now we need to know what percentage of time our app spends doing I/O. This will
be the value <code>p</code> as per the video.</p>
<p>Later in this series, we'll show you how to calculate what percentage of the
time the Rails application is spending doing I/O. For this blog let's assume
that our application spends 37% of the time doing I/O i.e the value of <code>p</code> is
<strong>0.37</strong>.</p>
<p>Let's calculate how much speedup we will get if we use one thread(n is 1). Now
let's change n to 2 and get the speedup value. Similarly, we bump up n all the
way to 15 and we record the speedup.</p>
<p>Now let's draw a graph between the overall speedup and the number of threads.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency/speedup-vs-thread-count.png" alt="Speedup V/S Number of threads"></p>
<p>From the graph, it can be seen that the speedup increases as the number of
threads increases, but the rate of increase diminishes as more threads are
added. This is because the serial portion remains constant and is unaffected by
the increase in threads.</p>
<table>
<thead>
<tr>
<th>Threads(N)</th>
<th>Speedup(S)</th>
<th>% Improvement from previous run</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1.000</td>
<td>-</td>
</tr>
<tr>
<td>2</td>
<td>1.227</td>
<td>22.7%</td>
</tr>
<tr>
<td>3</td>
<td>1.366</td>
<td>11.3%</td>
</tr>
<tr>
<td>4</td>
<td>1.456</td>
<td>6.6%</td>
</tr>
<tr>
<td>5</td>
<td>1.518</td>
<td>4.2%</td>
</tr>
<tr>
<td>6</td>
<td>1.562</td>
<td>2.9%</td>
</tr>
<tr>
<td>7</td>
<td>1.594</td>
<td>2.0%</td>
</tr>
<tr>
<td>8</td>
<td>1.619</td>
<td>1.6%</td>
</tr>
</tbody>
</table>
<p>By examining the graph we can observe that the speedup gain from increasing
threads seem significant up to 4 threads, after which the incremental gain in
speedup starts to plateau.</p>
<p>Remember that these are theoretical maximums based on Amdahl's law. In practice,
we need to use fewer threads as adding more threads can cause an increase in
memory usage and GVL contention, thereby causing latency spikes.</p>
<p>It's obvious that if we add more threads then more requests can be handled by
Puma concurrently. What it means is that requests will be waiting for lesser
time at the load balancer layer as there are more Puma threads waiting to pick
up the request for processing. But in part 1, we saw that just because we have
more threads, it doesn't mean things will move faster. More threads might cause
other threads to wait for the GVL.</p>
<p>There is no point in accepting requests if our web server can't respond to it
promptly. Whereas, if the <code>max_threads</code> value is set to a lower value, requests
will queue up at the Load Balancer layer which is better than overwhelming the
application server.</p>
<p>If more and more requests are waiting at the load balancer level, then the
request queue time will shoot up. The right way to solve this problem is to add
more Puma processes. It is advised to increase the capacity of the Puma server
by adding more processes rather than increasing the number of threads.</p>
<p><a href="https://gist.github.com/neerajsingh0101/35e5307fb197b08ac6a62aa725cafec6">Here</a>
is a middleware that can be used to track the request queue time. This code is
taken from
<a href="https://github.com/judoscale/judoscale-ruby/blob/15a4e9bd59734defb76656b59cba067b60aed473/judoscale-ruby/lib/judoscale/request_metrics.rb">judoscale</a>.</p>
<p>Note that <strong>Request Queue Time</strong> is the time spent waiting before the request is
picked up for processing.</p>
<p>This middleware will only work if the load balancer is adding the
<code>HTTP_REQUEST_START</code> header. Heroku automatically adds this header.</p>
<p>Now we need to use this middleware and for that open <code>config/application.rb</code>
file and we need to add the following line.</p>
<pre><code class="language-ruby">config.middleware.use RequestQueueTimeMiddleware
</code></pre>
<p><em>This was Part 3 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[The GVL in Ruby and its impact on scaling Rails]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/gvl-in-ruby-and-its-impact-in-scaling-rails-applications"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/gvl_in_ruby_and_its_impact_in_scaling_rails_applications.jpg"/>
      <id>https://www.bigbinary.com/blog/gvl-in-ruby-and-its-impact-in-scaling-rails-applications</id>
      <published>2025-04-24T00:00:00.000Z</published>
      <updated>2025-04-24T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/gvl_in_ruby_and_its_impact_in_scaling_rails_applications.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 2 of our blog series on scaling Rails applications. Let's start from the basics. Let's see how a standard web application mostly behaves. Web applications and CPU usage Code in a web...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/gvl-in-ruby-and-its-impact-in-scaling-rails-applications"><![CDATA[<p><em>This is Part 2 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<p>Let's start from the basics. Let's see how a standard web application mostly
behaves.</p>
<h2>Web applications and CPU usage</h2>
<p>Code in a web application typically works like this.</p>
<ul>
<li>Do some data manipulation.</li>
<li>Make a few database calls.</li>
<li>Do more calculations.</li>
<li>Make some network calls.</li>
<li>Do more calculations.</li>
</ul>
<p>Visually, it'll look something like this.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/work-done-in-processing-web-request.png" alt="Three threads 1 process &amp; 2 cores"></p>
<p>CPU work includes operations like view rendering, string manipulation, any kind
of business logic processing etc. In short, anything that involves Ruby code
execution can be considered CPU work. For the rest of the work, like database
calls, network call etc. CPU is idle. Another way of looking at when the CPU is
working and when it's idle is this picture.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/cpu-working-idle.png" alt="CPU sometimes working &amp; sometimes idle"></p>
<p>When a program is using CPU, then that portion of the code is called <strong>CPU
bound</strong> and when program is not using CPU, then that portion of the code is
called <strong>IO bound</strong>.</p>
<h3>CPU bound or IO bound</h3>
<p>Let us understand what <strong>CPU bound</strong> truly means. Consider the following piece
of code.</p>
<pre><code class="language-ruby">10.times do
  Net::HTTP.get(URI.parse(&quot;https://bigbinary.com&quot;))
end
</code></pre>
<p>In the above code, we are hitting the BigBinary website 10 times sequentially.
Running the above code takes time because making a network connection is a
time-consuming process.</p>
<p>Let's assume the code above takes 10 seconds to finish. We want the code to run
faster. So we bought a better CPU for the server. Do you think now the code will
run faster?</p>
<p>It will not. That's because the above code is <strong>not</strong> CPU bound. CPU is not the
limiting factor in this case. This code is I/O bound.</p>
<p>A program is <strong>CPU bound</strong> if the program will run faster if the CPU were
faster.</p>
<p>A program is <strong>I/O bound</strong> if the program will run faster if the I/O operations
were faster.</p>
<p>Some of the examples of I/O bound operations are:</p>
<ul>
<li><strong>making database calls</strong>: reading data from tables, creating new tables etc.</li>
<li><strong>making network calls</strong>: reading data from a website, sending emails etc.</li>
<li><strong>dealing with file systems</strong>: reading files from the file system.</li>
</ul>
<p>Previously, we saw that our CPU was idle sometimes. Now we know that the
technical term for that idleness is <strong>IO bound</strong> let's update the picture.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/cpu-bound-io-bound.png" alt="CPU bound and IO bound"></p>
<p>When a program is I/O bound, the CPU is not doing anything. We don't want
precious CPU cycles to be wasted. So what can we do so that the CPU is fully
utilized?</p>
<p>So far we have been dealing with only one thread. We can increase the number of
threads in the process. In this way, whenever the CPU is done executing CPU
bound code of one thread and that thread is doing an I/O bound operation, then
the CPU can switch and handle the work from another thread. This will ensure
that the CPU is efficiently utilized. We will look at how the switching between
threads works a bit later in the article.</p>
<h2>Concurrency vs Parallelism</h2>
<p>Concurrency and parallelism sound similar, and in your daily life, you can
substitute one for another, and you will be fine. However, from the computer
engineering point of view, there is a difference between work happening
concurrently and work happening in parallel.</p>
<p>Imagine a person who has to respond to 100 emails and 100 Twitter messages. The
person can reply to an email and then reply to a Twitter message, then do the
same all over again: reply to an email and reply to a Twitter message.</p>
<p>The boss will see the count of pending emails and Twitter messages go down from
100 to 99 to 98. The boss might think that the work is happening in &quot;parallel.&quot;
But that's not true.</p>
<p>Technically, the work is happening concurrently. For a system to be parallel, it
should have two or more actions executed simultaneously. In this case, at any
given moment, the person was either responding to email or responding to
Twitter.</p>
<p>Another way to look at it is that <strong>Concurrency is about dealing</strong> with lots of
things at the same time. <strong>Parallelism is about doing</strong> lots of things at the
same time.</p>
<p>If you find it hard to remember which one is which then remember that
<strong>concurrency</strong> starts with the word <strong>con</strong>. Concurrency is the <em>conman</em>. It's
pretending to be doing things &quot;in parallel,&quot; but it's only doing things
concurrently.</p>
<h2>Understanding GVL in Ruby</h2>
<p>GVL (Global VM Lock) in Ruby is a mechanism that prevents multiple threads from
executing Ruby code simultaneously. The GVL acts like a traffic light in a
one-lane bridge. Even if multiple cars (threads) want to cross the bridge at the
same time, the traffic light (GVL) allows only one car to pass at a time. Only
when one car has made it safely to the other end, the second car is allowed by
the traffic light(GVL) to start.</p>
<p>Ruby's memory management(like garbage collection) and some other parts of Ruby
are not thread-safe. Hence, GVL ensures that only one thread runs Ruby code at a
time to avoid any data corruption.</p>
<p>When a thread &quot;holds the GVL&quot;, it has exclusive access to modify the VM
structures.</p>
<p>It's important to note that GVL is there to protect how Ruby works and manages
Ruby's internal VM state. GVL is not there to protect our application code. It's
worth repeating. The presence of GVL doesn't mean that we can write our code in
a thread unsafe manner and expect Ruby to take care of all threading issues in
our code.</p>
<p>Ruby offers tools like <a href="https://ruby-doc.com/3.3.6/Thread/Mutex.html">Mutex</a> and
<a href="https://github.com/ruby-concurrency/concurrent-ruby">concurrent-ruby</a> gem to
manage concurrent code. For example, the following
code(<a href="https://www.youtube.com/watch?v=rI4XlFvMNEw&amp;t=575s">source</a>) is not thread
safe and the GVL will not protect our code from race conditions.</p>
<pre><code class="language-ruby">from = 100_000_000
to = 0

50.times.map do
  Thread.new do
    while from &gt; 0
      from -= 1
      to += 1
    end
  end
end.map(&amp;:join)

puts &quot;to = #{to}&quot;
</code></pre>
<p>When we run this code, we might expect the result to always equal 100,000,000
since we're just moving numbers from <code>from</code> to <code>to</code>. However, if we run it
multiple times, we'll get different results.</p>
<p>This happens because multiple threads are trying to modify the same variables
(from and to) simultaneously without any synchronization. This is called race
condition and it happens because the operation <code>to += 1</code> and <code>from -= 1</code> are
non-atomic at the CPU-level. In simpler terms operation <code>to += 1</code> can be written
as three CPU-level operations.</p>
<ol>
<li>Read current value of <code>to</code>.</li>
<li>Add 1 to it.</li>
<li>Store the result back to <code>to</code>.</li>
</ol>
<p>To fix this race condition the above code can be re-written using a
<a href="https://docs.ruby-lang.org/en/master/Thread/Mutex.html">Mutex</a>.</p>
<pre><code class="language-rb">from = 100_000_000
to = 0
lock = Mutex.new

50.times.map do
  Thread.new do
    while from &gt; 0
      lock.synchronize do
        if from &gt; 0
          from -= 1
          to += 1
        end
      end
    end
  end
end.map(&amp;:join)

puts &quot;to = #{to}&quot;
</code></pre>
<p>It's worth nothing that Ruby implementations like JRuby and TruffleRuby don't
have a GVL.</p>
<h2>GVL dictates how many processes we will need</h2>
<p>Let's say that we deploy the production app to AWS's EC2's <code>t2.medium</code> machine.
This machine has 2 vCPU as we can see from this chart.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/t2-medium.png" alt="T2 medium"></p>
<p>Without going into CPU vs vCPU discussion, let's keep things simple and assume
that the AWS machine has two cores. So we have deployed our code on a machine
with two cores but we have only one process running in production. No worries.
We have three threads. So three threads can share two cores. You would think
that something like this should be possible.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/puma-one-process-2-cores.png" alt="Three threads 1 process &amp; 2 cores"></p>
<p>But it's not possible. Ruby doesn't allow it.</p>
<p>Currently, it's not possible because Thread 1 and Thread 2 belong to the same
process. This is because of the <strong>Global VM Lock (GVL)</strong>.</p>
<p>The GVL ensures that only one thread can execute CPU bound code at a time within
a single Ruby process. The important thing to note here is that this lock is
<strong>only for the CPU bound code</strong> and <strong>only for the same process</strong>.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/gvl-lock.png" alt="Single Process Multi Core"></p>
<p>In the above case, all three threads can do DB operations in parallel. But two
threads of the same process can't be doing CPU operations in parallel.</p>
<p>We can see that &quot;Thread 1&quot; is using Core 1. Core 2 is available but &quot;Thread 2&quot;
can't use Core 2. GVL won't allow it.</p>
<p>Again, let's revisit what GVL does. For the CPU bound code GVL will ensure that
only one thread from a process can access CPU.</p>
<p>So now the question is how do we utilize Core 2. Well, the GVL is applied at a
process level. Threads of the same process are not allowed to do CPU operations
in parallel. Hence, the solution is to have more processes.</p>
<p>To have two Puma processes we need to set the value of env variable
<code>WEB_CONCURRENCY</code> to 2 and reboot Puma.</p>
<pre><code class="language-ruby">WEB_CONCURRENCY=2 bundle exec rails s
</code></pre>
<p>Now we have two processes. Now both Core 1 and Core 2 are being utilized.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/gvl-two-process.png" alt="Multi Process Multi Core"></p>
<p>What if the machine has 5 cores. Do we need 5 processes?</p>
<p>Yes. In that case, we will need to have 5 processes to utilize all the cores.</p>
<p>Therefore for achieving maximum utilization, the rule of thumb is that the
number of processes i.e <code>WEB_CONCURRENCY</code> should be set to the number of cores
available in the machine.</p>
<h2>Thread switching</h2>
<p>Now let's see how switching between threads happens in a multi-threaded
environment. Note that the number of threads is 2 in this case.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/thread1-thread2.png" alt="CPU bound and IO bound"></p>
<p>As we can see, the CPU switches between Thread 1 and Thread 2 whenever it's
idle. This is great. We don't waste CPU cycles now, as we saw in the
single-threaded case. But the switching logic is much more nuanced than what is
shown in the picture.</p>
<p>Ruby manages multiple threads at two levels: the operating system level and the
Ruby level. When we create threads in Ruby, they are &quot;native threads&quot; - meaning
they are real threads that the operating system (OS) can see and manage.</p>
<p>All operating systems have a component called the scheduler. In Linux, it's
called the
<a href="https://en.wikipedia.org/wiki/Completely_Fair_Scheduler">Completely Fair Scheduler</a>
or CFS. This scheduler decides which thread gets to use the CPU and for how
long. However, Ruby adds its own layer of control through the Global VM Lock
(GVL).</p>
<p>In Ruby a thread can execute CPU bound code only if it holds the GVL. The Ruby
VM makes sure that a thread can hold the GVL for up to 100 milliseconds. After
that the thread will be forced to release GVL to another thread if there is
another thread waiting to execute CPU bound code. This ensures that the waiting
Ruby threads are not
<a href="https://en.wikipedia.org/wiki/Starvation_(computer_science)">starved</a>.</p>
<p>When a thread is executing CPU-bound code, it will continue until either:</p>
<ol>
<li>It completes its CPU-bound work.</li>
<li>It hits an I/O operation (which automatically releases the GVL).</li>
<li>It reaches the limit of 100ms.</li>
</ol>
<p>When a thread starts running, the Ruby VM uses a background timer thread at the
VM level that checks every 10ms how long the current Ruby thread has been
running. If the thread has been running longer than the thread quantum (100ms by
default), the Ruby VM takes back the GVL from the active thread and gives it to
the next thread waiting in the queue. When a thread gives up the GVL (either
voluntarily or is forced to give up), the thread goes to the back of the queue.</p>
<p>The default thread quantum is 100ms and starting from Ruby 3.3, it can be
configured using the <code>RUBY_THREAD_TIMESLICE</code> environment variable.
<a href="https://bugs.ruby-lang.org/issues/20861">Here</a> is the link to the discussion.
This environment variable allows fine-tuning of thread scheduling behavior - a
smaller quantum means more frequent thread switches, while a larger quantum
means fewer switches.</p>
<p>Let's see what happens when we have two threads.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/multi-threaded.png" alt="CPU bound and IO bound"></p>
<ol>
<li>T1 completes quantum limit of 100ms and gives up the GVL to T2.</li>
<li>T2 completes 50ms of CPU work and voluntarily gives up the GVL to do I/O.</li>
<li>T1 completes 75 ms of CPU work and voluntarily gives the GVL to do I/O.</li>
<li>Both T1 and T2 are doing I/O and doesn't want the GVL.</li>
</ol>
<p>It means that Thread 2 would be a lot faster if it had more access to CPU. To
make CPU instantly available, we can have lesser number of threads CPU has to
handle. But we need to play a balancing game. If the CPU is idle then we are
paying for the processing cost for no reason. If the CPU is extremely busy then
that means requests will take longer to process.</p>
<h2>Thread switching can lead to misleading data</h2>
<p>Let's take a look at a simple code given below. This code is taken from
<a href="https://byroot.github.io/ruby/performance/2025/01/23/the-mythical-io-bound-rails-app.html">a blog</a>
by <a href="https://x.com/_byroot">Jean Boussier</a>.</p>
<pre><code>start = Time.now
database_connection.execute(&quot;SELECT ...&quot;)
query_duration = (Time.now - start) * 1000.0
puts &quot;Query took: #{query_duration.round(2)}ms&quot;
</code></pre>
<p>The code looks simple. If the result is say <code>Query took: 80ms</code> then you would
think that the query actually took <code>80ms</code>. But now we know two things</p>
<ul>
<li>Executing database query is an IO operation (IO bound)</li>
<li>Once the IO bound operation is done then the thread might not immediately get
hold of the GVL to execute CPU bound code.</li>
</ul>
<p>Think about it. What if the query took only <code>10ms</code> and the rest of the <code>70ms</code>
The thread was waiting for the CPU because of the GVL. The only way to know
which portion took how much time is by instrumenting the GVL.</p>
<h2>Visualizing the effect of the GVL</h2>
<p>To better understand the effect of multiple threads when it comes to Ruby's
performance, let's do a quick test. We'll start with a <strong>cpu_intensive</strong> method
that performs pure arithmetic operations in nested loops, creating a workload
that is heavily CPU dependent.</p>
<p><a href="https://gist.github.com/neerajsingh0101/de84bf200fae4e2003205ed81fcd9d7f">Here</a>
is the code.</p>
<p>Running this script produced the following output:</p>
<pre><code class="language-ruby">Running demonstrations with GVL tracing...

Starting demo with 1 threads doing CPU-bound work
Time elapsed: 7.4921 seconds

Starting demo with 3 threads doing CPU-bound work
Time elapsed: 7.8146 seconds
</code></pre>
<p>From the output, we can see that for CPU-bound work, a single thread performed
better. Why? Let's visualize the result with the help of the traces generated in
the above script using the <a href="https://github.com/ivoanjo/gvl-tracing">gvl-tracing</a>
gem. The trace files can be visualized using
<a href="https://ui.perfetto.dev/">Perfetto</a>, which provides a timeline view showing how
threads interact with the GVL.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/cpu-single-multi.webp" alt="Single and multi threads CPU bound"></p>
<p>We can see above that in the case of CPU-bound work if we have a single thread
then it's not waiting for GVL. However, if we have three threads then each
thread is waiting for GVL multiple times.</p>
<h3>Understanding the advantage of multiple threads in mixed workloads</h3>
<p>Now let's look at mixed workloads in single-threaded and multi-threaded
environment. We'll use a separate script with a <strong>mixed_workload</strong> method that
combines CPU-bound work with I/O operations. We use <code>IO.select</code> with blocking
behavior to simulate I/O operations. This creates actual I/O blocking that
releases the GVL and shows as &quot;waiting&quot; in the GVL trace, accurately
representing real-world I/O operations like database queries.</p>
<p><a href="https://gist.github.com/neerajsingh0101/af1eb90a79c7da429d4287528d7bb788">Here</a>
is the code for the mixed workload test.</p>
<p>Running this script with 1 thread and 3 threads produced the following output:</p>
<pre><code class="language-ruby">Running demonstrations with GVL tracing...

Starting demo with 1 thread doing Mixed I/O and CPU work
Time elapsed: 9.32 seconds

Starting demo with 3 threads doing Mixed I/O and CPU work
Time elapsed: 6.1344 seconds
</code></pre>
<p>The key advantage of multiple threads in mixed workloads lies in how the GVL is
managed during I/O operations. When a thread encounters an I/O operation (like a
database query, network call, or file read), it voluntarily releases the GVL.
This is fundamentally different from CPU-bound work, where threads compete for
the GVL and one thread must wait for another to finish or reach the 100ms
quantum limit.</p>
<p>During I/O operations, the thread is essentially blocked waiting for an external
resource (database, network, disk). While waiting, the thread doesn't need the
GVL because it's not executing Ruby code. This creates an opportunity for other
threads to acquire the GVL and do useful CPU work. The result is that CPU cycles
that would otherwise be wasted during I/O waits are now being utilized
productively by other threads.</p>
<p>Let's visualize this with the single-threaded case first:</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/mixed-single.png" alt="Single thread mixed workload"></p>
<p>In the single-threaded case, the threads wait for I/O operations to complete.
During these I/O waits, the CPU sits idle. The thread performs some CPU work,
then waits for I/O, then does more CPU work, then waits for I/O again. During
all the I/O wait periods, no productive work is happening. The CPU is available
but there's no other thread to utilize it.</p>
<p>Now let's look at the multi-threaded case with three threads:</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/mixed-multi.png" alt="Multi threaded mixed workload"></p>
<p>When there are three threads, the situation changes a bit. Threads now
occasionally spend time waiting for the GVL, but the overall throughput is
significantly better.</p>
<p>When Thread 1 releases the GVL to perform I/O, Thread 2 can immediately acquire
it and start executing CPU-bound work. While Thread 2 is working, Thread 1 might
still be waiting for its I/O operation to complete. Then when Thread 2 releases
the GVL for its own I/O operation, Thread 3 can acquire it. This creates a
pipeline effect where threads are constantly handing off the GVL to each other
ensuring that the CPU is almost always doing useful work.</p>
<p>The small amount of GVL contention we see in the multi-threaded case (threads
waiting for GVL) is more than compensated for by the elimination of idle CPU
time. Instead of the CPU sitting idle during I/O operations, other threads keep
it busy.</p>
<p>This is why Rails applications with typical workloads (lots of database queries,
API calls, and other I/O operations) benefit significantly from having multiple
threads.</p>
<h2>Why can't we increase the thread count to a really high value?</h2>
<p>In the previous section, we saw that increasing the number of threads can help
in utilizing the CPU better. So why can't we increase the number of threads to a
really high value? Let us visualize it.</p>
<p>In the hope of increasing performance, let us bump up the number of threads in
the previous code snippet to <code>20</code> and see the gvl-tracing result.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/gvl-in-ruby-and-its-impact-in-scaling-rails-applications/20-threads.png" alt="20 threads"></p>
<p>As we can see in the above picture, the amount of GVL contention is massive
here. Threads are waiting to get a hold of the GVL. Same will happen inside a
Puma process if we increase the number of threads to a very high value. As we
know, each request is handled by a thread. GVL contention therefore, means that
the requests keep waiting, thereby increasing latency.</p>
<h2>What's next</h2>
<p>In the coming blogs, we'll see how we can figure out the ideal value for
<code>max_threads</code>, both theoretically and empirically, based on our application's
workload.</p>
<p><em>This was Part 2 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Understanding how Puma handles requests]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/understanding_puma_concurrency_effect_of_gvl.jpg"/>
      <id>https://www.bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance</id>
      <published>2025-04-23T00:00:00.000Z</published>
      <updated>2025-04-23T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <category term="Ruby"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/understanding_puma_concurrency_effect_of_gvl.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[This is Part 1 of our blog series on scaling Rails applications. If we do rails new to create a new Rails application, Puma will be the default web server. Let's start by explaining how Puma handles...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance"><![CDATA[<p><em>This is Part 1 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>.</em></p>
<p>If we do <code>rails new</code> to create a new Rails
application, <a href="https://puma.io">Puma</a> will be the default web server. Let's start
by explaining how Puma handles requests.</p>
<h2>How does Puma handle requests?</h2>
<p>Puma listens to incoming requests on a TCP socket. When a request comes in, then
that request is queued up in the socket. The request is then picked up by a Puma
process. In Puma, a process is a separate OS process that runs an instance of
the Rails application.</p>
<p><em>Note that Puma official documentation calls a Puma process a Puma worker. Since
the term &quot;worker&quot; might confuse people with background workers like Sidekiq or
SolidQueue, in this article, we have used the word Puma process at a few places
to remove any ambiguity</em>.</p>
<p>Now, let's look at how a request is processed by Puma step-by-step.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance/puma-internals.png" alt="Puma internals"></p>
<ol>
<li>
<p>All the incoming connections are added to the socket backlog, which is an OS
level queue that holds pending connections.</p>
</li>
<li>
<p>A separate thread (created by the
<a href="https://msp-greg.github.io/puma/Puma/Reactor.html">Reactor</a> class) reads the
connection from the socket backlog. As the name suggests, this Reactor class
implements the
<a href="https://en.wikipedia.org/wiki/Reactor_pattern">reactor pattern</a>. The reactor
can manage multiple connections at a time thanks to non-blocking I/O and an
event-driven architecture.</p>
</li>
<li>
<p>Once the incoming request is fully buffered in memory, the request is passed
to the thread pool where the request resides in the <code>@todo</code> array.</p>
</li>
<li>
<p>A thread in the thread pool pulls a request from the <code>@todo</code> array and
processes it. The thread calls the Rack application, which, in our case is a
Rails application, and generates a response.</p>
</li>
<li>
<p>The response is then sent back to the client via the same connection. Once
this is complete, the thread is released back to the thread pool to handle
the next item from the <code>@todo</code> array.</p>
</li>
</ol>
<h2>Modes in Puma</h2>
<ol>
<li>
<p><strong>Single Mode</strong>: In single mode, only a single Puma process boots and does
not have any additional child processes. It is suitable only for applications
with low traffic.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance/single-mode.png" alt="Single mode"></p>
</li>
<li>
<p><strong>Cluster Mode</strong>: In cluster mode, Puma boots up a master process, which
prepares the application and then invokes the
<a href="https://en.wikipedia.org/wiki/Fork_(system_call)">fork()</a> system call to
create one or more child processes. These processes are the ones that are
responsible for handling requests. The master process monitors and manages
these child processes.
<img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance/cluster-mode.png" alt="Cluster mode"></p>
</li>
</ol>
<h2>Default Puma configuration in a new Rails application</h2>
<p>When we create a new Rails 8 or higher application, the default Puma
<code>config/puma.rb</code> will have the following code.</p>
<p><em>Please note that we are mentioning Rails 8 here because the Puma configuration
is different in prior versions of Rails.</em></p>
<pre><code class="language-ruby">threads_count = ENV.fetch(&quot;RAILS_MAX_THREADS&quot;, 3)
threads threads_count, threads_count

rails_env = ENV.fetch(&quot;RAILS_ENV&quot;, &quot;development&quot;)
environment rails_env

case rails_env
when &quot;production&quot;
  workers_count = Integer(ENV.fetch(&quot;WEB_CONCURRENCY&quot;, 1))
  workers workers_count if workers_count &gt; 1

  preload_app!
when &quot;development&quot;
  worker_timeout 3600
end
</code></pre>
<p>For a brand new Rails application, the env variables <code>RAILS_MAX_THREADS</code> and
<code>WEB_CONCURRENCY</code> won't be set. This means <code>threads_count</code> will be set to 3 and
<code>workers_count</code> will be 1.</p>
<p>Now let's look at the second line from the above mentioned code.</p>
<pre><code class="language-ruby">threads threads_count, threads_count
</code></pre>
<p>In the above code, <code>threads</code> is a method to which we are passing two arguments.
The default value of <code>threads_count</code> is 3. So effectively, we are calling method
<code>threads</code> like this.</p>
<pre><code>threads(3, 3)
</code></pre>
<p>The threads method in Puma takes two arguments: <code>min</code> and <code>max</code>. These arguments
specify the minimum and maximum number of threads that each Puma process will
use to handle requests. In this case Puma will initialize 3 threads in the
thread pool.</p>
<p>Now let's look at the following line from the above mentioned code.</p>
<pre><code class="language-ruby">workers workers_count if workers_count &gt; 1
</code></pre>
<p>The value of <code>workers_count</code> in this case is <code>1</code>, so Puma will run in <strong>single</strong>
mode. As mentioned earlier in Puma a worker is basically a process. It's not
background job worker.</p>
<p>What we have seen is that if we don't specify <code>RAILS_MAX_THREADS</code> or
<code>WEB_CONCURRENCY</code> then, by default, Puma will boot a single process and that
process will have three threads. In other words Rails will boot with the ability
to handle 3 requests concurrently.</p>
<p>This is the default value for Puma for Rails booting in development or in
production mode - a single process with three threads.</p>
<h2>Configuring Puma's concurrency and parallelism</h2>
<p>When it comes to concurrency and parallelism in Puma, there are two primary
parameters we can configure: the number of threads each process will have and
the number of processes we need.</p>
<p>To figure out the right value for each of these parameters, we need to know how
Ruby works. Specifically, we need to know how GVL in Ruby works and how it
impacts the performance of Rails applications.</p>
<p>We also need to know what kind of Rails application it is. Is it a CPU intensive
application, IO intensive or somewhere in between.</p>
<p>Don't worry in the next blog, we will start from the basics and will discuss all
this and much more.</p>
<p><em>This was Part 1 of our blog series on
<a href="https://www.bigbinary.com/blog/scaling-rails-series">scaling Rails applications</a>. If any part of the
blog is not clear to you then please write to us at
<a href="https://www.linkedin.com/company/bigbinary">LinkedIn</a>,
<a href="https://twitter.com/bigbinary">Twitter</a> or
<a href="https://bigbinary.com/contact">BigBinary website</a>.</em></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Scaling Rails Series]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/scaling-rails-series"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/scaling_rails_series.jpg"/>
      <id>https://www.bigbinary.com/blog/scaling-rails-series</id>
      <published>2025-04-22T00:00:00.000Z</published>
      <updated>2025-04-22T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Puma"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/scaling_rails_series.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Scaling Rails]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/scaling-rails-series"><![CDATA[<p>Rails makes it pretty easy to get started with development. You don't even have
to set up your database. It comes with SQLite. Install Rails, and you start the
development.</p>
<p>Same with deploying to production. You need to change your database. Other than
that it comes with sane defaults. You don't really need to know what's
<code>RAILS_MAX_THREADS</code> and what's <code>WEB_CONCURRENCY</code>. However, as your application
starts getting more traffic, you want to scale your application.</p>
<p>Over the last decade and a half of consultancy, we have seen all types of
applications.</p>
<p>We have seen Rails applications which are IO heavy like scraping websites. There
are applications which are heavy on background jobs. Then there are flash sale
sites where there is no traffic one minute and the next minute there are tons of
traffic. Then there are ticketing sites.</p>
<p>Each application has its own challenges.</p>
<p>If an application is not properly tuned, then we can run into all kinds of
issues. You can run out of memory or database connections. In some cases,
because of the wrong configuration, Sidekiq jobs were failing and these jobs
continued to get enqueued which made the situation worse.</p>
<p>To solve all these types of issues, we need to know what's actually happening.
And that's what we will do. We will look at under the hood to see how Rails
works, how Puma works, how connection pooling works, how to tune Sidekiq and how
to measure what we need to know to make decisions.</p>
<p>It's a journey to understand from the ground up how to scale Rails applications.
This page will have links to all the future blogs. You can follow the Scaling
Rails series by joining the newsletter or following us on
<a href="https://twitter.com/bigbinary">twitter</a> or
<a href="https://www.linkedin.com/company/bigbinary/">LinkedIn</a>. We even have an
<a href="https://bigbinary.com/blog/feed.xml">RSS feed</a>.</p>
<h3><a href="https://www.bigbinary.com/blog/understanding-puma-concurrency-and-the-effect-of-the-gvl-on-performance">Part 1 - Understanding how Puma handles requests</a></h3>
<ul>
<li>How Puma handles requests</li>
<li>Default Puma configuration in a new Rails application</li>
</ul>
<h3><a href="https://www.bigbinary.com/blog/gvl-in-ruby-and-its-impact-in-scaling-rails-applications">Part 2 - GVL in Ruby and the impact of GVL in scaling Rails applications</a></h3>
<ul>
<li>Web applications and CPU usage</li>
<li>CPU bound or IO bound</li>
<li>Concurrency vs Parallelism</li>
<li>Understanding the GVL</li>
<li>GVL dictates how many processes you will need</li>
<li>Thread switching</li>
<li>Visualizing the effect of the GVL</li>
</ul>
<h3><a href="https://www.bigbinary.com/blog/amdahls-law-the-theoretical-relationship-between-speedup-and-concurrency">Part 3 - Amdahl's Law: The Theoretical Relationship Between Speedup and Concurrency</a></h3>
<ul>
<li>Amdahl's law</li>
<li>Relationship between speedup gained and the number of threads</li>
<li>Ideal number of threads in a process</li>
<li>Request queue time</li>
</ul>
<h3><a href="https://www.bigbinary.com/blog/tuning-puma-max-threads-configuration-with-gvl-instrumentation">Part 4 - Finding ideal number of threads per process using GVL Instrumentation</a></h3>
<ul>
<li>GVL instrumentation using perfm</li>
<li>Determining the I/O workload of an application using the GVL data</li>
<li>Empirically determining the ideal number of Puma threads for an application</li>
</ul>
<h3><a href="https://www.bigbinary.com/blog/understanding-active-record-connection-pooling">Part 5 - Understanding Active Record Connection Pooling</a></h3>
<ul>
<li>Database connection pooling</li>
<li>Active Record connection pool implementation</li>
<li>Connection pool configuration options</li>
<li>Active Record connection pool reaper</li>
<li>How many database connections will the web and background processes utilize at
maximum?</li>
<li>How does using <code>load_async</code> affect the connection usage?</li>
<li>Setting database pool size configuration</li>
<li>PgBouncer</li>
<li>Tracking down <code>ActiveRecord::ConnectionTimeoutError</code></li>
<li>Monitoring Active Record connection pool stats</li>
</ul>
<h3><a href="https://www.bigbinary.com/blog/understanding-queueing-theory">Part 6 - Understanding Queueing Theory</a></h3>
<ul>
<li>Queueing systems</li>
<li>Basic terminology in queueing theory</li>
<li>Little's law</li>
<li>The knee curve</li>
<li>Theoretical parallelism</li>
<li>Concurrency and effective parallelism</li>
</ul>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Benchmarking caching in Rails with Redis vs the alternatives]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/caching-in-rails-with-redis-vs-alternatives"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/benchmarking_caching_rails_with_redis.jpg"/>
      <id>https://www.bigbinary.com/blog/caching-in-rails-with-redis-vs-alternatives</id>
      <published>2025-02-04T00:00:00.000Z</published>
      <updated>2025-02-04T00:00:00.000Z</updated>
      <author>
        <name>Sandip Mane</name>
        <uri>https://www.bigbinary.com/blog/authors/sandip</uri>
      </author>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/benchmarking_caching_rails_with_redis.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Benchmarking caching in Rails including Redis, Memcached, Valkey, DragonflyDB, SolidCache with PostgreSQL, SolidCache with SQLite3, and LiteCache with SQLite3.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/caching-in-rails-with-redis-vs-alternatives"><![CDATA[<p>Recently, we have seen the rise of Redis alternatives. Some of them claimed
substantial performance gains. We did this benchmarking to see how much
performance gain one would get by switching from Redis to one of the
alternatives.</p>
<p>We explored several new contenders like <a href="https://valkey.io/">Valkey</a>,
<a href="https://www.dragonflydb.io/">DragonflyDB</a>, and <a href="https://dicedb.io/">DiceDB</a>,
which serve as drop-in Redis replacements. We also looked at Rails' own
<a href="https://github.com/rails/solid_cache">SolidCache</a>, which challenges in-memory
storage by favoring database-based approach. For this comparison, we included a
<a href="https://andyatkinson.com/solid-cache-rails-postgresql#postgresql-optimizations">tuned SolidCache for PostgreSQL</a>,
as suggested by <a href="https://x.com/andatki">Andrew Atkinson</a>. We also included
<a href="https://fractaledmind.github.io/2024/10/16/sqlite-supercharges-rails/">SolidCache with sqlite3</a>,
inspired by Stephen Margheim, who claims to offer significant performance gains.
Finally, we added <a href="https://github.com/oldmoe/litestack">litecache with sqlite3</a>
to compare it directly against SolidCache.</p>
<p>Although SolidCache brings various advantages to Rails, this benchmarking
focused solely on pure performance.</p>
<h2>Benchmarking details</h2>
<p>We used
<a href="https://gist.github.com/sandip-mane/e4671c3cd01c247a5e8ff9133aa2eca6">this script</a>
for benchmarking. It performed 100k read and write operations on a DigitalOcean
droplet with a 4GB RAM, 25GB SSD Ubuntu 24.10 x64 setup running a Rails 8
application.</p>
<p>In the case of &quot;Single thread benchmarking&quot; only one thread was created. &quot;Multi
thread benchmarking&quot; had five threads.</p>
<p>The data gathered is based on running the same tests five times, with the
averages calculated.</p>
<p>A simpler version of the script looks like this.</p>
<pre><code class="language-rb">require &quot;benchmark&quot;

# Run it in the SolidCache with PG-tuned setup
# ActiveRecord::Base
#   .connection
#   .execute(&quot;SELECT pg_prewarm('solid_cache_entries')&quot;)

# Run the benchmarking for 100k reads and writes
n = 100_000

def cache_fetch(i)
  Rails.cache.fetch([&quot;key&quot;, i], expires_in: 5.minutes) do
    &quot;Hello World!&quot;
  end
end

# Clear the existing cache
Rails.cache.clear

puts &quot;\nSingle thread:&quot;
Benchmark.bm(6) do |x|
  x.report(&quot;write&quot;) {
    n.times { |i| cache_fetch(i) }
  }
  x.report(&quot;read&quot;) {
    n.times { |i| cache_fetch(i) }
  }
end

# Spawn &quot;x&quot; threads and run &quot;n&quot; operations combined
def spawn_threads(x = 5, n)
  threads = []
  x.times do
    threads &lt;&lt; Thread.new do
      (n/x).times { |i| cache_fetch(i) }
    end
  end
  threads.each(&amp;:join)
end

# Clear cache again before running another benchmark
Rails.cache.clear

puts &quot;\nMultiple threads:&quot;
Benchmark.bm(6) do |x|
  x.report(&quot;write&quot;) {
    spawn_threads(5, n)
  }
  x.report(&quot;read&quot;) {
    spawn_threads(5, n)
  }
end
</code></pre>
<h3>Single thread performance</h3>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/caching-in-rails-with-redis-vs-alternatives/single-thread.png" alt="code"></p>
<p>For our comparison, we used Redis as a benchmark.</p>
<p>Memcached performs similarly to Redis, with Redis having a slight edge in the
benchmark results. However, this difference is unlikely to be significant in a
real-world application.</p>
<p>Valkey and DiceDB deliver similar performance, with Valkey having a slight edge
in both read and write operations. However, Redis still remains 1.5x faster than
both.</p>
<p>DragonflyDB (not shown in the graph) performed significantly slower in the
benchmarks, which is why it was excluded. However, its performance in Rails 7
was comparable to Valkey, suggesting it may require optimizations for Rails 8.</p>
<p>SolidCache on PostgreSQL is approximately twice as slow as Redis for read
operations and the slowest for writes among all options. However, the tuned
version improves performance significantly, making it 1.5x faster than the
standard PostgreSQL setup. Consider optimizing your cache database if you want
to get maximum performance benefits.</p>
<p>SolidCache with SQLite3 has shown significant improvement, with read speeds now
comparable to Redis and write speeds surpassing those of a tuned PostgreSQL
setup.</p>
<p>Finally, litecache with sqlite3 delivers exceptional performance, being
approximately 4x faster in read operations and 2.5x faster in writes compared to
Redis, making it the fastest option available for Rails and Ruby applications.</p>
<h3>Multithreaded performance</h3>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2025/caching-in-rails-with-redis-vs-alternatives/multi-thread.png" alt="code"></p>
<p>In this test, Redis, Memcache, Valkey, and DiceDB performed similarly to the
single-thread benchmarks, with slight improvements in write operations. Redis
maintained its position as the top performer, continuing to outpace the others.</p>
<p>Unfortunately, SolidCache with PostgreSQL was the slowest option. However, when
tuned, it significantly improved write performance, highlighting the importance
of optimizing the database for better results.</p>
<p>Surprisingly, SolidCache with SQLite3 delivered performance on par with Redis
alternatives while outperforming a tuned PostgreSQL setup by a factor of two,
effectively doubling its speed.</p>
<p>Additionally, LiteCache with SQLite3 outperformed single-thread performance in
write operations, making it an impressive 4x faster than Redis in both read and
write operations.</p>
<h3>Summary</h3>
<p>It’s clear that while data stores like Valkey, Dragonfly, and others claim
significantly better performance than Redis, using them through Rails APIs
doesn’t fully leverage those advantages, leading to performance levels similar
to Redis.</p>
<p>SolidCache with PostgreSQL offers benefits such as fewer dependencies and easier
maintenance, but it was the slowest performer in this test, with high memory
consumption. However, if you must use SolidCache with PostgreSQL, tuning it as
recommended by Andrew Atkinson is crucial, as it can notably enhance write
performance.</p>
<p>On the other hand, SolidCache with SQLite3 was a pleasant surprise, particularly
in the multi-threaded test, where it performed on par with Redis. For a
database-backed caching solution, this is impressively fast.</p>
<p>Lastly, while LiteCache with SQLite3 is the fastest option, boasting a
significant 4x performance gain, it’s not recommended for several reasons.
First, it doesn’t yet support Rails 8 (we used a forked branch for this test).
Second, the <a href="https://github.com/oldmoe/litestack">litestack</a> gem required for
this comes bundled with a variety of additional addons, such as LiteJob,
LiteCable, and others. Adding this entire suite of packages just for LiteCache
doesn’t make sense. Finally, since the package is still new and in its early
stages, we would not recommend using it for production applications.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Safely dropping tables and renaming columns in Rails]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-8-deleting-tables-columns-using-rubocop"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/dropping_tables_in_safe_way_ruby_on_rails.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-8-deleting-tables-columns-using-rubocop</id>
      <published>2024-09-17T00:00:00.000Z</published>
      <updated>2024-09-17T00:00:00.000Z</updated>
      <author>
        <name>Abhay V Ashokan</name>
        <uri>https://www.bigbinary.com/blog/authors/abhayvashokan</uri>
      </author>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/dropping_tables_in_safe_way_ruby_on_rails.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Dropping tables and columns in a safe way in Ruby on Rails]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-8-deleting-tables-columns-using-rubocop"><![CDATA[<p>We are building <a href="https://neeto.com/cal">NeetoCal</a>, which is a Calendly
alternative. Recently, we deployed the latest code to production. The code
change involved deleting a table. To our horror, during the deployment, we
noticed that some users experienced errors with status code 500 for a few
minutes. This happened because the migration to drop the tables ran quickly and
the tables got deleted. However, the old code was still referring to those
tables.</p>
<p>This kind of issue is pretty common with schema migrations, especially when
you're dropping tables or columns. If the migration finishes before the code
deployment catches up, you end up with the old process still trying to access tables
or columns that no longer exist. This mismatch can cause temporary errors, like
the 500s we saw.</p>
<p>The safest bet might be to turn on the maintenance mode every time we run a
schema migration. At NeetoCal, we deploy changes to production every day. We
only want to schedule downtime when it's absolutely necessary. So this option
was ruled out. We also heard that some companies manually restart their dynos
during schema migrations to roll out the new code changes. However, this did not
sit well with us either.</p>
<p>Most of the people we talked to solved this problem by having two step
deployments.</p>
<p>Deployment 1: Deploy the code that does not use the table.</p>
<p>Deployment 2: Drop the table.</p>
<p>This can work, and it does work. However, we were worried about a potential edge
case. Let's say that a piece of code is still referring the table. After the
deployment 1 this code is working and we don't see anything going wrong. It
could be due to recent merges slipping in unnoticed, as we ship very fast.</p>
<p>However, when do we deployment 2 the migrations will run first, dropping the
table, followed by the deployment of the code. When new code boots up, we
realize that one part of the app is not working.</p>
<p>Now we are in trouble. We are in trouble because the table is gone. If we have
taken the database backup, then we can restore the backup but that causes all
kinds of issues because we might not catch this bug for sometime. In the
meantime other tables are getting new data. So restoring backup is a messy
solution.</p>
<p>The only solution is to fix the code. Now we need to fix the code in a rush.
That's what we want to avoid. Before we look at our solution, let's look at what
we found when we looked at other solutions.</p>
<h2>strong_migrations didn't protect dropping of table</h2>
<p>At NeetoCal, we are using the
<a href="https://github.com/ankane/strong_migrations">strong_migrations</a> gem to catch
unsafe migrations. The gem catches unsafe migration like
<a href="https://github.com/ankane/strong_migrations?tab=readme-ov-file#removing-a-column">removing a column</a>
but it doesn't capture unsafe operations like dropping a table.</p>
<p>Upon some digging, we found
<a href="https://github.com/ankane/strong_migrations/issues/49#issuecomment-419107996">this issue</a>
where the author of the gem expressed unwillingness to add <code>drop_table</code> as an
unsafe operation.</p>
<p>No worries. We can add dropping of a table as an unsafe operation in
<code>strong_migration</code> ourselves. Here's how it can be done.</p>
<pre><code class="language-rb"># config/initializers/strong_migrations.rb

StrongMigrations.add_check do |method, args|
  if method == :drop_table
    stop! &quot;Dropping tables via migrations is discouraged.&quot;
  end
end
</code></pre>
<p>To drop the table, we can use the <code>safety_ensured</code> block provided by the
<code>strong_migrations</code> gem to mark the step as safe.</p>
<pre><code class="language-rb"># db/migrate/20240809131941_drop_users.rb

class DropUsers &lt; ActiveRecord::Migration[8.0]
  def change
    safety_assured { drop_table :users }
  end
end
</code></pre>
<p>While this gets the work done, it doesn't solve the problem of &quot;some code still
referring the table&quot; problem. Hence, this solution was a &quot;no go&quot; from our side.</p>
<h2>Sam wants to delay dropping of tables and columns</h2>
<p><a href="https://x.com/samsaffron">Sam Saffron</a> had run into similar problems. He came
up with a solution and he wrote about it in
<a href="https://samsaffron.com/archive/2018/03/22/managing-db-schema-changes-without-downtime">this blog</a>.</p>
<p>His solution was not to drop the tables and columns immediately. Instead use
&quot;defer drops&quot; to drop column or tables at least 30 minutes after the particular
migration was run.</p>
<p>He introduced
<a href="https://github.com/discourse/discourse/blob/6a3c8fe69c16ad7360046f145db6689c18e91005/lib/migration/column_dropper.rb">ColumnDropper</a>
and
<a href="https://github.com/discourse/discourse/commit/6a3c8fe69c16ad7360046f145db6689c18e91005#diff-d4fcc7d7501c6256f67a8a2ea0f1d3ef27136b86213809563d9b583592774c5d">TableDropper</a>
to get this work done.</p>
<p>We felt that this solution adds an extra layer of complexity and we rejected
this solution. In fact later we found that they ran into some issues with &quot;defer
drops&quot; as discussed <a href="https://github.com/discourse/discourse/pull/6406">here</a>.</p>
<h2>Dropping tables and columns should be allowed if it follows a pattern</h2>
<p>After some internal discussion, we also decided to follow a three-step
deployment process to ensure zero downtime and easy roll back without any data
loss.</p>
<p>In &quot;Deployment 1&quot;, we remove all the code that refers to the table we want to
drop. This ensures that nothing in the application depends on that table
anymore.</p>
<p>In &quot;Deployment 2&quot;, the table will be renamed. For example, table <code>users</code> will be
renamed to <code>users-deprecated-on-2024-08-09</code>. This step helps catch any dangling
code that is still referring to the old table. If any part of the app still tries
to use the table, the errors will show up, and we can fix the problem in one of the
two ways. We can revert the migration and the code. Or we can change the code.
We have a choice. If we delete the table, then we don't have a choice.</p>
<p>Finally, in &quot;Deployment 3&quot;, once we’re confident that the table is no longer
in use, we can drop it completely. Since the table follows a specific naming
pattern, it's clear that it's ready to be safely deleted.</p>
<p>We can follow a similar approach when dropping columns. To add an extra layer of
safety, we mark the column that we need to drop as ignored using ActiveRecord's
<a href="https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-ignored_columns-3D">ignored_columns</a>
method. For example, if we need to drop the <code>display_name</code> column from the
<code>users</code> table, start by marking it as ignored:</p>
<pre><code class="language-rb">class User &lt; ActiveRecord::Base
  self.ignored_columns += [:display_name]
end
</code></pre>
<p>By doing this, even if the <code>display_name</code> column still exists in some lingering
code, our model won’t recognize it. This helps avoid any accidental references
to the column in your code. Once you’ve successfully dropped the column, you can
remove this line from your model.</p>
<p>If our model won't recognize it, then why do we need RuboCop for dropping a
column. Once again, the answer is to avoid an edge case. Let's say that we are
using executing SQL directly. And this SQL is referring the column
<code>display_name</code>. Since a direct SQL is being used, adding this column to
<code>ignored_columns</code> will have no impact. Once this column is delete,d then only we
will get to know about the error.</p>
<p>By renaming the column, we maintain the data but at the same time all the
lingering code would start failing.</p>
<h2>RuboCop rules to ensure the policy is followed</h2>
<p>Now the task was to build a custom cop to enforce the policy.</p>
<pre><code class="language-rb"># bad
drop_table :users

# bad
drop_table :users do |t|
  t.string :email, null: false
  t.string :first_name, null: false
end

# good
drop_table :users_deprecated_on_2024_08_09

# good
drop_table :users_deprecated_on_2024_08_09 do |t|
  t.string :email, null: false
  t.string :first_name, null: false
end
</code></pre>
<p>We need to handle removal of column similarly.</p>
<pre><code class="language-rb"># bad
remove_column :users, :email

# bad
change_table :users do |t|
  t.remove :email
end

# good
remove_column :users, :email_deprecated_on_2024_08_09

# good
drop_table :users do |t|
  t.remove :email_deprecated_on_2024_08_09
end
</code></pre>
<p>We added these two cops to our
<a href="https://github.com/bigbinary/rubocop-neeto">rubocop-neeto</a> repo.</p>
<h2>Safely renaming database columns</h2>
<p>Renaming a column brings the same challenges as we have discussed in the
previous sections. Renaming a column directly will cause temporary downtime
since the new code references the new column name while the old code refers to
the old column name. To avoid downtime, we need to deliberately carry out this
operation in multiple deployments.</p>
<p>Here are the steps to rename the <code>username</code> column to <code>display_name</code> in the
<code>users</code> table:</p>
<p><strong>Deployment 1</strong></p>
<ol>
<li><strong>Create the new column</strong>: Start by adding the new <code>display_name</code> column to
the table.</li>
</ol>
<pre><code class="language-rb">class AddDisplayNameToUsers &lt; ActiveRecord::Migration[8.0]
  def change
    add_column :users, :display_name, :string
  end
end
</code></pre>
<ol start="2">
<li><strong>Write to both columns</strong>: Update your app so it writes to both the old and
new columns. <code>ActiveRecord</code> callbacks can help with this:</li>
</ol>
<pre><code class="language-rb">class User &lt; ApplicationRecord
  before_save do
    self.display_name = username if will_save_change_to_username? }
  end
end
</code></pre>
<ol start="3">
<li><strong>Backfill data from the old column to the new column</strong>: Next, backfill the
data from the <code>username</code> column to the <code>display_name</code> column:</li>
</ol>
<pre><code class="language-rb">User.update_all('display_name = username')
</code></pre>
<p><strong>Deployment 2</strong></p>
<ol start="4">
<li><strong>Move reads from the old column to the new column</strong>: Update application to
read from the <code>display_name</code> column instead of the old <code>username</code> column, and
then remove the double writes to both columns.</li>
</ol>
<p><strong>Deployment 3</strong></p>
<ol start="5">
<li><strong>Drop the old column</strong>: Finally, drop the old column once everything is in
place.</li>
</ol>
<pre><code class="language-rb">class DropUsernameFromUsers &lt; ActiveRecord::Migration[8.0]
  def change
    remove_column :users, :username
  end
end
</code></pre>
<p>This approach might seem tedious, but it's essential for achieving zero downtime
during the migration and avoiding any edge cases. We can apply the same steps
when renaming tables as well. For more details on that process, check out
<a href="https://github.com/ankane/strong_migrations?tab=readme-ov-file#renaming-a-table">the steps mentioned by the strong_migrations gem</a>.</p>
<p>Running schema migrations can be scary, especially when they involve dropping
tables and columns. But with the right safeguards in place, we can confidently
deploy updates without worrying about any surprises.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Upgrade Ruby using dual boot]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/ruby-upgrade-using-dual-boot"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/upgrade_ruby_using_dual_boot.jpg"/>
      <id>https://www.bigbinary.com/blog/ruby-upgrade-using-dual-boot</id>
      <published>2024-09-04T00:00:00.000Z</published>
      <updated>2024-09-04T00:00:00.000Z</updated>
      <author>
        <name>Vijay Vinod</name>
        <uri>https://www.bigbinary.com/blog/authors/vijay2136</uri>
      </author>
      <category term="Ruby"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/upgrade_ruby_using_dual_boot.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Recently, we upgraded all neeto product's Ruby version from 3.2.2 to 3.3.0 using "dual-booting". Dual-booting is a process that allows you to run your application with different sets of dependencies,...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/ruby-upgrade-using-dual-boot"><![CDATA[<p>Recently, we upgraded all <a href="https://www.neeto.com/">neeto</a> product's Ruby version
from 3.2.2 to 3.3.0 using &quot;dual-booting&quot;.</p>
<p>Dual-booting is a process that allows you to run your application with different
sets of dependencies, making it easy to switch between them. This approach
enables you to quickly test your code with both the current and the newer
version of what you are upgrading, ensuring that everything works smoothly
before fully committing to the upgrade.</p>
<h4>How to dual-boot Ruby?</h4>
<p>The dual-boot technique involves maintaining two separate <code>Gemfile.lock</code> files
within your Rails project: one for the current version of Ruby and another for
the next version you're upgrading to.</p>
<p>To get started, first create a symbolic link for <code>Gemfile.next</code> with the
following command:</p>
<pre><code class="language-bash">ln -s Gemfile Gemfile.next
</code></pre>
<p>This command creates <code>Gemfile.next</code> file, which you'll use for the new Ruby
version. This file isn't a separate copy but rather a pointer to your original
<code>Gemfile</code> in the Rails project directory.</p>
<p>Next, add the following snippet at the top of your <code>Gemfile</code>:</p>
<pre><code class="language-ruby">def next?
  File.basename(__FILE__) == &quot;Gemfile.next&quot;
end
</code></pre>
<p>This code snippet helps determine which <code>Gemfile</code> is in use, whether it's the
standard one or the <code>Gemfile.next</code> for the new Ruby version.</p>
<p>Now, let's utilize the <code>next?</code> method to dynamically set the Ruby version in the
<code>Gemfile</code> using a conditional operator:</p>
<pre><code class="language-ruby">ruby_version = next? ? &quot;3.3.0&quot; : &quot;3.2.2&quot;
ruby ruby_version
</code></pre>
<p>This code snippet helps determine the Ruby version based on the <code>next?</code> method.
If <code>next?</code> returns true, meaning you're operating within <code>Gemfile.next</code>, it sets
the Ruby version to &quot;3.3.0&quot;. Otherwise, if the standard <code>Gemfile</code> is being
processed, it defaults to &quot;3.2.2&quot;.</p>
<h4>How to install dependencies?</h4>
<p>With the dual-boot setup in place, you can effectively manage dependencies for
both your current and next versions of the application.</p>
<p>Installing current dependencies: To install dependencies for your current
version, simply run:</p>
<pre><code class="language-bash">bundle install
</code></pre>
<p>This command uses the standard <code>Gemfile</code> to resolve and install dependencies for
your current application.</p>
<p>Installing next dependencies: To install dependencies for the next version of
your application, use the following command:</p>
<pre><code class="language-bash">BUNDLE_GEMFILE=Gemfile.next bundle install
</code></pre>
<p>This command specifies <code>Gemfile.next</code> as the <code>Gemfile</code>, allowing you to install
dependencies specifically for the next version.</p>
<p>Managing commands for the next version: To perform various operations for the
next version, you can use the syntax <code>BUNDLE_GEMFILE=Gemfile.next &lt;command&gt;</code>.
For example, to start the Rails server with the next version's dependencies, you
would use:</p>
<pre><code class="language-bash">BUNDLE_GEMFILE=Gemfile.next rails s
</code></pre>
<p>This approach ensures that <code>Gemfile.next</code> is explicitly used for the specified
command, allowing you to work with the next version of your application while
keeping dependencies and configurations correctly managed.</p>
<h4>Considerations for dual-boot setup</h4>
<p>When implementing a dual-boot setup, it's crucial to consider where both Ruby
and gem versions are defined. While gem versions are usually specified only in
the <code>Gemfile</code>, the Ruby version can be defined in several locations. The most
common are:</p>
<ul>
<li><code>.ruby-version</code> File: Used by version managers like rbenv to specify the Ruby
version.</li>
<li><code>Gemfile</code>: This is where Bundler, RVM, Heroku, and similar tools reference the
Ruby version to manage the Ruby environment.</li>
<li><code>Gemfile.lock</code>: Although the Ruby version in this file is mostly informative,
it's worth noting.</li>
<li><code>Dockerfile</code>: Defines the Ruby version for Docker containers if you're using
Docker.</li>
<li>CI setup steps and configurations.</li>
<li>Other less common locations within applications.</li>
</ul>
<p>We need to make sure that dual-boot setup is compatible with all of them to
ensure a smooth transition and consistency across different environments.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Standardizing pagination, sorting and search in code]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/standardize-pagination-keywords"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/simplifying_code_with_standardized_pagination_sorting_search.jpg"/>
      <id>https://www.bigbinary.com/blog/standardize-pagination-keywords</id>
      <published>2024-08-27T00:00:00.000Z</published>
      <updated>2024-08-27T00:00:00.000Z</updated>
      <author>
        <name>Abilash Sajeev</name>
        <uri>https://www.bigbinary.com/blog/authors/abilash-sajeev</uri>
      </author>
      <category term="ReactJS"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/simplifying_code_with_standardized_pagination_sorting_search.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Explore the power of standardized naming practices in creating a unified and scalable codebase.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/standardize-pagination-keywords"><![CDATA[<p><a href="https://www.neeto.com">Neeto</a> is a collection of products. Here, we standardize
boilerplate and repetitive code into
<a href="https://blog.neeto.com/p/nanos-make-neeto-better">Nanos</a>. The search, sorting,
and pagination functionalities are essential for every listing page. We realized
that each product had its own custom implementations for these operations,
resulting in a lot of duplicated code. We wanted this logic to be abstracted and
handled uniformly.</p>
<h2>Sorting and pagination</h2>
<p>If you have worked with tables, you may already be familiar with the following
code. In this example, we are using the
<a href="https://neeto-ui.neeto.com/?path=/docs/components-table--docs">Table</a> component
from <a href="https://neeto-ui.neeto.com/">NeetoUI</a>. It is the developer's
responsibility to handle the sorting and pagination logic, make the API calls
with an updated payload, and ensure that the URL is updated accordingly.</p>
<pre><code class="language-javascript">import React, { useState } from &quot;react&quot;;
import { Table } from &quot;@bigbinary/neetoui&quot;;

const Teams = () =&gt; {
  const [page, setPage] = useState(1);
  const [sortBy, setSortBy] = useState(null);
  const [orderBy, setOrderBy] = useState(null);

  const handleSort = ({ sortBy, orderBy }) =&gt; {
    setSortBy(sortBy);
    setOrderBy(orderBy);
    // Custom sort logic
  };

  const handlePageChange = page =&gt; {
    setPage(page);
    // Custom pagination logic.
  };

  const { data: teams, isLoading } = useFetchTeams({ page, sortBy, orderBy });

  return (
    &lt;Table
      currentPageNumber={page}
      handlePageChange={handlePageChange}
      onChange={(_, __, sorter) =&gt; handleSort(sorter)}
      {...{ totalCount, rowData, columnData, ...otherProps }}
    /&gt;
  );
};

export default Teams;
</code></pre>
<p>To make both the frontend and backend more standardized and reusable, we
established some conventions. The NeetoUI Table will handle both sorting and
pagination internally. When the user navigates to a different page, NeetoUI will
update the <code>page</code> and <code>page_size</code> query parameters in the URL.</p>
<p>Similarly, when a column is sorted, the <code>sort_by</code> and <code>order_by</code> query
parameters are updated. We no longer need a dedicated state to store these
values. When the query parameter changes, the API will be called with the
modified payload.</p>
<p>Here is what the simplified code looks like:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import { Table } from &quot;@bigbinary/neetoui&quot;;
import { useQueryParams } = &quot;@bigbinary/neeto-commons-frontend/react-utils&quot;

const Teams = () =&gt; {
  const { page, sortBy, orderBy } = useQueryParams();

  const { data: teams, isLoading } = useFetchTeams({ page,
                                                     sortBy,
                                                     orderBy });

  return (
    &lt;Table {...{ totalCount, rowData, columnData, ...otherProps }} /&gt;
  );
};
export default Teams;
</code></pre>
<p>Here, we utilize a <code>useQueryParams</code> utility helper. It parses the URL and
returns the query parameters after converting them to camel case.</p>
<p>We noticed that a similar cleanup can be done on the backend side. After
establishing a consistent variable naming convention for <code>page</code>, <code>page_size</code>,
<code>order_by</code>, and <code>sort_by</code> in every listing page, it became easier to create
common utility functions for the backend. Here's what it looks like:</p>
<pre><code class="language-ruby">module Filterable
  extend ActiveSupport::Concern
  include Pagy::Backend

  def sort_and_paginate(records)
    sorted_records = apply_sort(records)
    apply_pagination(sorted_records)
  end

  def apply_sort(records)
    records.order(sort_by =&gt; order_by)
  end

  def sort_by
    params[:sort_by].presence || &quot;created_at&quot;
  end

  def order_by
    case params[:order_by]&amp;.downcase
    when &quot;asc&quot;, &quot;ascend&quot;
      &quot;ASC&quot;
    when &quot;desc&quot;, &quot;descend&quot;
      &quot;DESC&quot;
    else
      &quot;ASC&quot;
    end
  end

  def apply_pagination(records)
    pagination_method = records.is_a?(ActiveRecord::Relation) ? :pagy : :pagy_array
    pagy, paginated_records = send(pagination_method, records, page: params[:page], items: params[:page_size])

    [pagy, paginated_records]
  end
end
</code></pre>
<p>The <code>apply_pagination</code> helper method can be used to paginate the results. It
uses the <code>Pagy</code> gem internally to perform the pagination. It provides a generic
method <code>pagy</code> that works with <code>ActiveRecord</code> out of the box. It also provides a
<code>pagy_array</code> method which paginates an array of records. Both these methods
returns a <code>Pagy</code> instance along with the paginated records.</p>
<p>The <code>apply_sort</code> method sorts the given records based on a specified column name
and direction. By default, records are sorted in ascending order of <code>created_at</code>
timestamps, but this can be customized by specifying values for <code>sort_by</code> and
<code>order_by</code> params. As sorting and pagination are common requirements of the
listing pages, we also added a <code>sort_and_paginate</code> method which performs both
these operations on provided records.</p>
<h2>Searching</h2>
<p>Searching is a common functionality in any web application. Developers need to
ensure that the results are refetched whenever the search term changes. The
debouncing logic is added to minimize API requests and improve user experience.
As you can see in the below snippet, we had to maintain a dedicated state for
search term as well.</p>
<pre><code class="language-javascript">import React, { useState } from &quot;react&quot;;
import { useDebounce } from &quot;@bigbinary/neeto-commons-frontend/react-utils&quot;;
import { useFetchTeams } from &quot;hooks/reactQuery/useFetchTeamsApi&quot;;

const Teams = () =&gt; {
  const [searchString, setSearchString] = useState(&quot;&quot;);
  const debouncedSearchString = useDebounce(searchString.trim());

  const { data: teams, isLoading } = useFetchTeams(debouncedSearchString);

  return (
    &lt;Input
      type=&quot;search&quot;
      value={searchString}
      onChange={({ target: { value } }) =&gt; setSearchString(value)}
    /&gt;
  );
};

export default Teams;
</code></pre>
<p>As we standardized the naming pattern for the search term, we were able to directly
incorporate the search term value into the URL query parameters. This helped us
to retrieve the search term, eliminating the need for a separate state.</p>
<p>We also introduced a new component in
<a href="https://neeto-molecules.neeto.com">NeetoMolecules</a>, to handle the search
functionality in all products. This
<a href="https://neeto-molecules.neeto.com/?path=/docs/search--docs">Search</a> component
will internally handle the debounced updates when the search term changes. It
will also update the <code>search_term</code> query param in URL. Here is what the
simplified code looks like:</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import Search from &quot;@bigbinary/neeto-molecules/Search&quot;;
import { useFetchTeams } from &quot;hooks/reactQuery/useFetchTeamsApi&quot;;

const Teams = () =&gt; {
  const { searchTerm = &quot;&quot; } = useQueryParams();

  const { data: teams, isLoading } = useFetchTeams(searchTerm);

  return &lt;Search /&gt;;
};

export default Teams;
</code></pre>
<p>We also updated the <code>Filterable</code> concern mentioned earlier to simplify the logic
in the backend. The <code>search_term</code> method retrieves the keyword specified in the
URL query parameters, while <code>search?</code> checks whether the results should be
filtered based on any keyword.</p>
<pre><code class="language-ruby">module Filterable
  extend ActiveSupport::Concern

  # ... previous code

  def search_term
    filter_params[:search_term]
  end

  def search?
    search_term.present?
  end
end
</code></pre>
<p>Let's consolidate everything in the <code>TeamsController</code>. In the following code, we
need to fetch all the teams belonging to an organization, filter them based on
the search term and return the results after sorting and pagination.</p>
<pre><code class="language-ruby">def index
  if params[:search_string].present?
    @teams = @organization.teams.filter_by_name(params[:search_string])
  end

  @teams = @teams
    .order(params[:column] =&gt; params[:direction])
    .page(params[:current_page])
    .per(params[:limit])
end
</code></pre>
<p>We were able to simplify this logic with the help of the methods provided by the
<code>Filterable</code> concern. As you can see below, this helped us in removing a lot of
boilerplate code and improving the readability.</p>
<pre><code class="language-ruby">def index
  @teams = @organization.teams.filter_by_name(search_term) if search?
  @teams = sort_and_paginate(@teams)
end
</code></pre>
<p>This standardized approach helped us to extract and centralize the common logic.
It also accelerated the development and maintenance cycle as the code structure
is now clear and consistent across all the products.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.2 adds SQL query counts to rendering logs]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-8-adds-sql-queries-count-to-template-rendering-logs"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_introduces_sql_count_in_template_logs.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-8-adds-sql-queries-count-to-template-rendering-logs</id>
      <published>2024-08-20T00:00:00.000Z</published>
      <updated>2024-08-20T00:00:00.000Z</updated>
      <author>
        <name>Navaneeth D</name>
        <uri>https://www.bigbinary.com/blog/authors/navaneethsdk</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7.2"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_introduces_sql_count_in_template_logs.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.2 simplifies debugging database queries by adding query count information to the application logs.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-8-adds-sql-queries-count-to-template-rendering-logs"><![CDATA[<p>For Rails developers, debugging database queries is a frequent task. Whether
it's addressing the notorious N+1 query problem or fine-tuning caching
strategies, developers often find themselves diving into logs to scrutinize SQL
query counts.</p>
<p>Traditionally, this involved manually inspecting the logs and counting the
number of queries. Needless to say, this becomes tedious and error-prone for
actions generating a significant number of queries in the order of tens or
hundreds.</p>
<p>Thankfully, Rails 7.2 introduces a helpful improvement by enhancing the log
output to include the query count alongside existing information.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/rails-8-adds-sql-queries-count-to-template-rendering-logs/sql-query-count-in-template-log.gif" alt="sql-query-count-in-template-log"></p>
<p>The improved log output now includes the query count within the ActiveRecord
section. It shows <code>3 queries, 1 cached</code>, indicating that three database queries
were executed, with one being served from the cache.</p>
<p>This seemingly small addition allows for quick identification of query volume
and potential optimization areas. You can easily see if caching is working
effectively and if the number of queries aligns with expectations, saving
developers valuable time and effort.</p>
<p>Check <a href="https://github.com/rails/rails/pull/51457">this pull request</a> for more
details:</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 8 introduces a basic authentication generator]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-8-introduces-a-basic-authentication-generator"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_basic_authentication_generator.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-8-introduces-a-basic-authentication-generator</id>
      <published>2024-08-09T00:00:00.000Z</published>
      <updated>2024-08-09T00:00:00.000Z</updated>
      <author>
        <name>Jaimy Simon</name>
        <uri>https://www.bigbinary.com/blog/authors/jaimysimon</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 8"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_basic_authentication_generator.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 8.0 adds a generator that streamlines the process of adding basic authentication to Rails applications]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-8-introduces-a-basic-authentication-generator"><![CDATA[<p>DHH recently posted the following in an issue titled
<a href="https://github.com/rails/rails/issues/50446">Add basic authentication generator</a>.</p>
<blockquote>
<p>Rails now include all the key building blocks needed to do basic
authentication, but many new developers are still uncertain of how to put them
together, so they end up leaning on all-in-one gems that hide the mechanics.</p>
</blockquote>
<p>To address this, Rails 8 has introduced a generator that simplifies the addition
of basic authentication to Rails applications. In this blog post, we'll explore
the components included in this authentication scaffold.</p>
<h2>Adding authentication to your Rails application</h2>
<p>To set up a basic authentication system in your Rails application, execute the
following code.</p>
<pre><code class="language-bash">bin/rails generate authentication
</code></pre>
<p>This command generates a set of essential files that provide a foundational
start for implementing authentication, including support for database-tracked
sessions and password reset functionality. Here is the
<a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3">gist</a>
of the generated scaffold.</p>
<p>Let’s examine the components created by the authentication generator.</p>
<h3>Models &amp; migrations</h3>
<p>To lay the groundwork for handling user accounts and session management, the
models
<a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3#file-user-rb"><code>User</code></a>,
<a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3#file-current-rb"><code>Current</code></a>
and
<a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3#file-session-rb"><code>Session</code></a>
are set up along with their corresponding migrations. This setup includes:</p>
<ul>
<li>
<p><a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3#file-xxxxxxx_create_users-rb"><code>CreateUsers</code></a>
migration: It creates a <code>users</code> table, with a uniquely indexed <code>email_address</code>
field and <code>password_digest</code> for storing the securely hashed passwords using
<a href="https://api.rubyonrails.org/v5.2/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password"><code>has_secure_password</code></a>.</p>
</li>
<li>
<p><a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-xxxxxxx_create_sessions-rb"><code>CreateSessions</code></a>
migration: It sets up the <code>sessions</code> table, which includes a unique <code>token</code>
field, along with <code>ip_address</code> and <code>user_agent</code> fields to record the user's
device and network details. The <code>Session</code> model uses
<a href="https://api.rubyonrails.org/v7.1.3.4/classes/ActiveRecord/SecureToken/ClassMethods.html"><code>has_secure_token</code></a>
to generate unique session tokens.</p>
</li>
<li>
<p>The <code>Current</code> model manages per-request state and provides access to the
current user's information through a delegated <code>user</code> method.</p>
</li>
<li>
<p>Additionally, the <a href="https://github.com/bcrypt-ruby/bcrypt-ruby">bcrypt</a> gem is
added to the Gemfile. If this gem is not present, it is added; if it is
commented, it is uncommented, and then <code>bundle install</code> is run to install the
gem.</p>
</li>
</ul>
<h3>Authentication concern</h3>
<p>The core authentication logic and session management are encapsulated in the
<a href="https://gist.github.com/neerajsingh0101/55593d0ba2d6b24fb3fd728aa94c3fb3#file-authentication-rb"><code>Authentication</code></a>
concern.</p>
<ul>
<li>
<p><code>require_authentication</code>: This is a <code>before_action</code> callback which attempts to
restore an existing session with <code>resume_session</code>. If no session is found, it
redirects the user to the login page using <code>request_authentication</code>.</p>
</li>
<li>
<p><code>resume_session</code>: Retrieves a session using a signed token from the cookie via
the <code>find_session_by_cookie</code> method. Sets it as the current session, and saves
the token in a permanent, HTTP-only cookie using the <code>set_current_session</code>
method.</p>
</li>
<li>
<p><code>authenticated?</code>: A helper method that checks if the current user has an
active session.</p>
</li>
<li>
<p><code>allow_unauthenticated_access</code>: This class method allows specific actions to
bypass the <code>require_authentication</code> callback.</p>
</li>
<li>
<p><code>after_authentication_url</code>: Returns the URL to redirect to after
authentication.</p>
</li>
<li>
<p><code>start_new_session_for(user)</code>: Creates a new session for the given user with
details of the user's device and IP address and then sets the current session.</p>
</li>
<li>
<p><code>terminate_session</code>: Destroys the current session and removes the session
token from cookies.</p>
</li>
</ul>
<h3>Managing sessions</h3>
<p>The
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-sessions_controller-rb"><code>SessionsController</code></a>
handles user session management and includes the following actions:</p>
<ul>
<li>
<p><code>new</code>: Renders a login form for the user credentials. The
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-sessions_new-html-erb"><code>new.html.erb</code></a>
file includes fields for email and password, displays flash messages for
success and errors, and provides a link for password recovery.</p>
</li>
<li>
<p><code>create</code>: Authenticates the user with the provided credentials. On success, it
creates a new session and redirects to the <code>after_authentication_url</code>; on
failure, it redirects to the login form with an error message.</p>
</li>
<li>
<p><code>destroy</code>: Terminates the user’s session and redirects to the login form.</p>
</li>
</ul>
<p>Combining all of this, a basic authentication flow would look like this.</p>
<br />
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/rails-8-introduces-a-basic-authentication-generator/user-authentication.png" alt="Flow of a basic authentication system"></p>
<br />
<h3>Password reset functionality</h3>
<p>The basic password reset functionality includes initiating a password reset
request, dispatching reset instructions via email, and updating the password.
The
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-passwords_controller-rb"><code>PasswordsController</code></a>
manages these actions as follows:</p>
<ul>
<li>
<p><code>new</code>: Displays the form to request a password reset using the
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-passwords_new-html-erb"><code>new.html.erb</code></a>
template.</p>
</li>
<li>
<p><code>create</code>: Handles the reset request, sends a reset email via the
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-passwords_mailer-rb"><code>PasswordMailer</code></a>
if the user exists, and redirects with a notice. This email contains a link to
the password reset page, which includes a <code>password_reset_token</code> as a
parameter. The <code>password_reset_token</code> is part of the
<a href="https://github.com/rails/rails/pull/52483">newly added configuration</a> to
<code>has_secure_password</code>. It has a default expiration period of 15 minutes.</p>
</li>
<li>
<p><code>edit</code>: Renders the
<a href="https://gist.github.com/jaimysimon/c0cf64cd0c6e5ab8adb470acf90db38e#file-edit-html-erb"><code>edit.html.erb</code></a>
template to set a new password.</p>
</li>
<li>
<p><code>update</code>: Updates the user's password, redirects on success, or shows an alert
on failure.</p>
</li>
<li>
<p><code>set_user_by_token</code>: It is the <code>before_action</code> callback for the <code>edit</code> and
<code>update</code> actions which retrieve the user based on the <code>password_reset_token</code>
provided in the request parameters, ensuring proper identification of the
user.</p>
<p>Putting these together, the flow of resetting the password would be as
follows:</p>
</li>
</ul>
<br />
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/rails-8-introduces-a-basic-authentication-generator/password-reset.png" alt="Flow of password reset"></p>
<br />
<h2>Current limitations and considerations</h2>
<p>The current authentication generator supports email-password login for existing
users but does not handle new account creation. Future updates may incorporate
user account creation and other customizations.</p>
<p>Please check out the following pull requests for more details:</p>
<ul>
<li>https://github.com/rails/rails/pull/52328</li>
<li>https://github.com/rails/rails/pull/52472</li>
<li>https://github.com/rails/rails/pull/52483</li>
</ul>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Managing templates across Neeto with neeto-templates-nano]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/how-build-neeto-templates-nano"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/introducing_neeto_templates_nano.jpg"/>
      <id>https://www.bigbinary.com/blog/how-build-neeto-templates-nano</id>
      <published>2024-07-30T00:00:00.000Z</published>
      <updated>2024-07-30T00:00:00.000Z</updated>
      <author>
        <name>Sooraj Bhaskaran</name>
        <uri>https://www.bigbinary.com/blog/authors/soorajbhaskaran</uri>
      </author>
      <category term="Rails"/>
      <category term="React"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/introducing_neeto_templates_nano.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[A deep dive into the architecture and implementation of neeto-templates-nano, a highly customizable and extensible template builder for neeto.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/how-build-neeto-templates-nano"><![CDATA[<h2>Overview</h2>
<p><code>neeto-templates-nano</code> serves as an incredibly flexible and user-friendly
template builder within the Neeto ecosystem. In this blog post, we will explore
the evolution from previous approaches to the development of
<strong>neeto-templates-nano</strong> and how it effectively addresses the challenges
encountered.</p>
<h2>Evolution of templates at NeetoSite</h2>
<p><a href="https://www.neeto.com/neetosite">NeetoSite</a> is a website builder.</p>
<p>Before the advent of <code>neeto-templates-nano</code>, NeetoSite relied on three tables -
<strong>templates_sites</strong>, <strong>templates_pages</strong>, and <strong>template_blocks</strong> to store site,
page, and blocks data for templates. This structure duplicated the schema
already employed in NeetoSite for sites, pages, and blocks, resulting in
redundancy.</p>
<p>Another challenge arose with the storage of each template in a YAML file,
necessitating a custom rake task for maintenance. Tasks like updating templates,
such as changing font families, required laborious manual edits to every YAML
file, affecting scalability and maintainability.</p>
<p>This <strong>YAML</strong> snippet portrays the complexity of maintaining templates. The need
for manual edits in each YAML file hampers scalability and maintainability.</p>
<pre><code class="language-yaml">## Example of a template in YAML format
name: Document Signer
keywords: &quot;E-signature, AI, Cost effective, Paperless&quot;
pages:
  - name: &quot;Home&quot;
    url: &quot;/&quot;
    blocks:
      - name: &quot;header_with_logo_title&quot;
        category: &quot;header&quot;
        kind: &quot;header_with_logo_title&quot;
        identifier: &quot;Header&quot;
        configurations:
          design:
            body:
              border:
                borderColor: &quot;#FFFFFF&quot;
                borderStyle: none
                borderWidth: 0
              backgroundColor: &quot;#FFFFFF&quot;
              paddingVertical: 8
              paddingHorizontal: 48
            logo:
              height: 52
            links:
              color: &quot;#1F2433&quot;
              fontSize: &quot;1em&quot;
              fontFamily: Lato
              fontWeight: 500
              letterSpacing: 0
            buttons:
              color: &quot;#ffffff&quot;
              border:
                borderColor: &quot;#f4620c&quot;
                borderStyle: solid
                borderWidth: 1
              fontSize: &quot;0.875em&quot;
              fontFamily: Open Sans
              fontWeight: 500
              borderRadius: 9999
              letterSpacing: 0
              backgroundColor: &quot;#f4620c&quot;
            logoTitle:
              color: &quot;#1F2433&quot;
              fontSize: &quot;0.875em&quot;
              fontFamily: Inter
              fontWeight: 500
              letterSpacing: 0
            hamburgerMenu:
              color: &quot;#000000&quot;
          properties:
            logo:
              alt: Max Chat
              url: &quot;#!&quot;
              title: &quot;&quot;
            links:
              - to: &quot;#Clients&quot;
                label: Clients
                action: internal
              - to: &quot;#Insights&quot;
                label: Insights
                action: internal
            position: sticky
            enableAnimation: true
</code></pre>
<p>Recognizing these challenges, we opted for a more scalable and maintainable
solution.</p>
<h2>The neeto-templates-nano solution</h2>
<h3>Eliminating redundancy</h3>
<p>To streamline template creation and management, <strong>neeto-templates-nano</strong>
introduces a comprehensive solution. It includes a frontend package,
<a href="https://www.npmjs.com/package/@bigbinary/neeto-templates-frontend">@bigbinary/neeto-templates-frontend</a>,
and a ruby gem, <strong>neeto-templates-engine</strong> (private gem).</p>
<h3>Architecture</h3>
<p>The architecture addresses redundancy by whitelisting the <code>templates</code>
organization for template creation. Once a site is constructed from the
<strong>templates</strong> organization and published, it becomes a template accessible from
other organizations.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/how-build-neeto-templates-nano/architecture.png" alt="Architecture"></p>
<p>In this illustration, we've published 5 sites at <code>templates</code> organization of
<a href="https://www.neeto.com/neetosite">NeetoSite</a>. After publication, the sites
become accessible to users across all organizations. Choosing any template will
clone it from the <strong>templates</strong> organization to the user's organization. This
straightforward mechanism simplifies the management and sharing of templates
across different organizations.</p>
<p>The <code>CreateTemplateModal</code> was exported from
<code>@bigbinary/neeto-templates-frontend</code>, allows users to see and select templates
when creating a new site.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/how-build-neeto-templates-nano/create_templates_screen.gif" alt="CreateTemplateModal"></p>
<p>This GIF shows how simple it is to browse through available templates using the
<code>CreateTemplateModal</code>. Users can preview templates, pick the one that suits
their needs, and quickly start their projects without reinventing the wheel.</p>
<h3>Advantages of porting to neeto-templates-nano</h3>
<ol>
<li>
<p><strong>Effortless Template Management:</strong> With neeto-templates-nano, managing
templates is a breeze. Administrators can easily add, update, or delete
templates by accessing the templates organization and simply publishing the
changes. This streamlines the template lifecycle, ensuring that users always
have access to the latest versions.</p>
</li>
<li>
<p><strong>Enhanced Template Customization:</strong> neeto-templates-nano introduces new
customizations that enable administrators to add tags and cover images for
templates directly within the templates organization. This functionality
enhances the visual appeal of templates and facilitates better organization
and searchability. Administrators can tailor templates to specific project
requirements, improving overall user experience and productivity.</p>
</li>
</ol>
<h3>Implementation across Neeto products</h3>
<p><strong>neeto-templates-nano</strong> has seamlessly integrated into
<a href="https://www.neeto.com/neetoform">NeetoForm</a> and
<a href="https://www.neeto.com/neetosite">NeetoSite</a>, offering a standardized approach
to template management across all Neeto products.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.2 makes counter_cache integration safer and easier]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-8-adds-ability-to-ignore-counter_cache-column-while-backfilling"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_makes_counter_cache_integration_safer_and_easier.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-8-adds-ability-to-ignore-counter_cache-column-while-backfilling</id>
      <published>2024-07-16T00:00:00.000Z</published>
      <updated>2024-07-16T00:00:00.000Z</updated>
      <author>
        <name>Navaneeth D</name>
        <uri>https://www.bigbinary.com/blog/authors/navaneethsdk</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7.2"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_makes_counter_cache_integration_safer_and_easier.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.2 adds ability to ignore counter_cache column whilst the data is getting backfilled]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-8-adds-ability-to-ignore-counter_cache-column-while-backfilling"><![CDATA[<p>Counter caches are a cornerstone of performance optimization in Rails
applications. They efficiently keep track of the number of associated records
for a model, eliminating the need for frequent database queries. However, adding
counter caches to existing applications, especially those with large tables,
often can be challenging. Rails 7.2 brings an exciting update to address just
that!</p>
<h2>Counter cache integration challenges</h2>
<p>When introducing counter caches to large datasets, developers often encounter
two primary challenges:</p>
<ul>
<li>Backfilling data efficiently: Adding a counter cache column to an existing
table with a substantial amount of data can be problematic. Backfilling the
counter cache values separately from the column addition is necessary to
prevent prolonged table locks, which can significantly impact application
performance. This process requires careful consideration to ensure data
integrity while minimizing downtime and avoiding disruptions to user
experience.</li>
<li>Ensuring data consistency: Once the counter cache is in place, maintaining
data consistency becomes paramount. Methods such as <code>size</code>, <code>any?</code>, and others
that utilize counter caches internally must return accurate results. However,
during the backfilling process, relying solely on the counter cache may
produce incorrect counts until all records are appropriately updated.</li>
</ul>
<h2>Safer counter cache implementation in Rails 7.2</h2>
<p>The new update in Rails 7.2 introduces a feature that allows developers to
manage counter caches more effectively, especially in scenarios involving
existing large datasets. By introducing the <code>active</code> option in the counter cache
configuration, developers can control when the counter cache is actively
utilized. This enables them to backfill counter cache columns separately from
their addition, minimizing table locks and potential performance issues. Once
the backfilling process is complete, developers can activate the counter cache,
ensuring accurate association counts without compromising application
performance.</p>
<p>Let's illustrate the implementation of this update in Rails using a
straightforward example involving a blog application with articles and comments.
In our blog application, each article can have multiple comments. We want to add
a counter cache to track the number of comments associated with each article.
However, our database already contains a significant amount of data, making
traditional counter cache implementation challenging.</p>
<h3>Implementation Steps</h3>
<ol>
<li>Define the association: Initially, we define the association between the
<code>Article</code> model and the <code>Comment</code> model, specifying the counter cache with
the <code>active: false</code> option to keep it inactive during the initial setup.</li>
</ol>
<pre><code class="language-ruby">class Comment &lt; ApplicationRecord
  belongs_to :article, counter_cache: { active: false }
end
</code></pre>
<ol start="2">
<li>
<p>Backfill the Counter Cache: With the association configured, we proceed to
backfill the counter cache column in the <code>articles</code> table. During this phase,
the counter cache remains inactive, and methods like <code>size</code>, <code>any?</code>, etc.,
retrieve results <strong>directly from the database</strong>. This prevents incorrect
values from getting displayed during backfilling.</p>
</li>
<li>
<p>Activate the Counter Cache: Once the backfilling process is complete, we
activate the counter cache by removing the <code>active: false</code> option from the
counter cache definition.</p>
</li>
</ol>
<pre><code class="language-ruby">class Comment &lt; ApplicationRecord
  belongs_to :article, counter_cache: true
end
</code></pre>
<p>Upon activation, the counter cache integrates into the association, efficiently
tracking the number of comments associated with each article.</p>
<p>This <a href="https://github.com/rails/rails/pull/51453">PR introduced</a> active option.
Checkout full feature discussion
<a href="https://discuss.rubyonrails.org/t/new-feature-to-make-introducing-counter-caches-safer-and-easier/85456">here</a>.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Automating error pages based on API responses]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/automatically-displaying-errors"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/how_we_automated_displaying_error_pages_based_on_api_responses.jpg"/>
      <id>https://www.bigbinary.com/blog/automatically-displaying-errors</id>
      <published>2024-05-28T00:00:00.000Z</published>
      <updated>2024-05-28T00:00:00.000Z</updated>
      <author>
        <name>Farhan CK</name>
        <uri>https://www.bigbinary.com/blog/authors/farhanlatheef</uri>
      </author>
      <category term="ReactJS"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/how_we_automated_displaying_error_pages_based_on_api_responses.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[How we automated displaying error pages based on API responses]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/automatically-displaying-errors"><![CDATA[<p>Error handling is an important aspect of building software. We've automated
error handling wherever possible so that we can deal with it in a consistent
manner. By automating this process, we've allowed our product engineers to focus
on shipping quality software.</p>
<p>This blog is about how we automated handling and displaying error pages in
<a href="https://www.neeto.com/">neeto</a> applications.</p>
<p>Before we jump in, let's look at <a href="https://axios-http.com/">Axios</a> and
<a href="https://github.com/pmndrs/zustand">Zustand</a>, two npm packages we heavily rely
on.</p>
<h4>Axios</h4>
<p>We use <a href="https://axios-http.com/">Axios</a> to make API calls. Axios allows us to
intercept API calls and modify them if necessary.</p>
<pre><code class="language-js">axios.interceptors.request.use(request =&gt; {
  // intercept requests
});
axios.interceptors.response.use(request =&gt; {
  // intercept responses
});
</code></pre>
<p>This feature is useful for tasks such as setting authentication tokens, handling
case conversion for requests and responses, cleaning up sensitive headers when
sending requests to third-party domains, etc. This is also a good place to
universally handle any errors on API calls.</p>
<h4>Zustand</h4>
<p><a href="https://github.com/pmndrs/zustand">Zustand</a> is a small, fast, scalable,
barebones state management solution using simplified flux principles. We use
Zustand for state management instead of Redux/React context. Here is a simple
example of how to create a store using Zustand.</p>
<pre><code class="language-js">import { create } from &quot;zustand&quot;;

const useBearStore = create(set =&gt; ({
  bears: 0,
  increasePopulation: () =&gt; set(state =&gt; ({ bears: state.bears + 1 })),
  removeAllBears: () =&gt; set({ bears: 0 }),
}));
</code></pre>
<p>To learn more about Zustand, check out there
<a href="https://zustand-demo.pmnd.rs/">documentation</a>.</p>
<h3>Detecting errors</h3>
<p>Let's look into how we handle errors in our applications. As a first step, let's
examine the universal Axios response interceptor we wrote to detect any error.</p>
<pre><code class="language-js">import axios from &quot;axios&quot;;

axios.interceptors.response.use(null, error =&gt; {
  if (error.response?.status === 401) {
    resetAuthTokens();
    window.location.href = `/login?redirect_uri=${encodeURIComponent(
      window.location.href
    )}`;
  } else {
    const fullUrl = error.request?.responseURL || error.config.url;
    const status = error.response?.status;
    //TODO: Notify the user that an error is occurred.
  }
  return Promise.reject(error);
});
</code></pre>
<p>For each response, if it detects a 401 HTTP error, the code resets the
authentication tokens and redirects the user to the login page. For any other
error, we need to notify the user with an appropriate message.</p>
<h3>Storing errors</h3>
<p>When an error happens, the user could be on any page, so we should be able to
display an error message no matter which page the user is on. For that, we need
to store the error in a way that it can be retrieved from anywhere. This is
where Zustand comes into play. It is very easy to create a store in Zustand and
can be used as a hook in React applications.</p>
<pre><code class="language-js">import { prop } from &quot;ramda&quot;;
import { create } from &quot;zustand&quot;;

const useDisplayErrorPage = () =&gt; useErrorDisplayStore(prop(&quot;showErrorPage&quot;));

export const useErrorDisplayStore = create(() =&gt; ({
  showErrorPage: false,
  statusCode: 404,
  failedApiUrl: &quot;&quot;,
}));

export default useDisplayErrorPage;
</code></pre>
<p>The code snippet above creates a Zustand store for storing error-related data.
Additionally, it provides a hook that conveniently checks whether an error has
occurred anywhere within the application.</p>
<p>Back to our Axios interceptor, we can store the error using the
<code>useDisplayErrorPage</code> hook.</p>
<pre><code class="language-js">import axios from &quot;axios&quot;;
import { useErrorDisplayStore } from &quot;./useDisplayErrorPage&quot;;

axios.interceptors.response.use(null, error =&gt; {
  if (error.response?.status === 401) {
    resetAuthTokens();
    window.location.href = `/login?redirect_uri=${encodeURIComponent(
      window.location.href
    )}`;
  } else {
    const fullUrl = error.request?.responseURL || error.config.url;
    const status = error.response?.status;
    useErrorDisplayStore.setState({
      showErrorPage: true,
      statusCode: status,
      failedApiUrl: fullUrl,
    });
  }
  return Promise.reject(error);
});
</code></pre>
<h3>Display errors</h3>
<p>Now, we can use the <code>useDisplayErrorPage</code> hook at the root of our React
application. When an error happens, <code>showErrorPage</code> will become <code>true</code>; we can
use that to show an error page.</p>
<pre><code class="language-jsx">import useDisplayErrorPage from &quot;./useDisplayErrorPage&quot;;
import ErrorPage from &quot;./ErrorPage&quot;;
const Main = () =&gt; {
  const showErrorPage = useDisplayErrorPage();
  if (showErrorPage) {
    return &lt;ErrorPage /&gt;;
  }

  return &lt;&gt;Our App&lt;/&gt;;
};
</code></pre>
<p>Let's look at the <code>ErrorPage</code> component. The component reads the error data from
the Zustand stores and displays the appropriate error message and the picture.</p>
<pre><code class="language-jsx">import { useErrorDisplayStore } from &quot;./useDisplayErrorPage&quot;;
import { shallow } from &quot;zustand/shallow&quot;;

const ERRORS = {
  404: {
    imageSrc: &quot;not-found.png&quot;,
    errorMsg: &quot;The page you're looking for can't be found.&quot;,
    title: &quot;Page not found&quot;,
  },
  403: {
    imageSrc: &quot;unauthorized.png&quot;,
    errorMsg: &quot;You don't have permission to access this page.&quot;,
    title: &quot;Unauthorized&quot;,
  },
  500: {
    imageSrc: &quot;server-error.png&quot;,
    errorMsg:
      &quot;The server encountered an error and could not complete your request.&quot;,
    title: &quot;Internal server error&quot;,
  },
};

const ErrorPage = ({ statusCode }) =&gt; {
  const { storeStatusCode, showErrorPage } = useErrorDisplayStore(
    pick([&quot;statusCode&quot;, &quot;showErrorPage&quot;]),
    shallow
  );
  const status = statusCode || storeStatusCode;
  const { imageSrc, errorMsg, title } = ERRORS[status] || ERRORS[404];

  return (
    &lt;div className=&quot;flex flex-col items-center justify-center h-screen&quot;&gt;
      &lt;title&gt;{title}&lt;/title&gt;
      &lt;img src={imageSrc} className=&quot;mb-4&quot; alt=&quot;Error Image&quot; /&gt;
      &lt;div className=&quot;text-lg font-medium&quot;&gt;{errorMsg}&lt;/div&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<h2>Custom error pages in Rails</h2>
<p>Ruby on Rails comes with default error pages for commonly encountered requests
such as 404, 500, and 422 errors. Each request has an associated static HTML
page located within the public directory. Even though we can customize them to
look like our <code>ErrorPage</code> component, maintaining error pages in different places
will be difficult, and in the long run, they can go out of sync. So, we decided
to use the <code>ErrorPage</code> component for these scenarios as well.</p>
<p>To accomplish this, we'll start by creating a controller named
<code>ErrorsController</code>. This controller will contain a single show action, where
we'll extract the error code from the raised exception and render the
appropriate view.</p>
<pre><code class="language-rb">class ErrorsController &lt; ApplicationController
  before_action :set_default_format

  def show
    @status_code = params[:status_code] || &quot;404&quot;
    error = @status_code == &quot;404&quot; ?  &quot;Not Found&quot; : &quot;Something went wrong!&quot;

    if params[:url]
      Rails.logger.warn &quot;ActionController::RoutingError (No route matches [#{request.method}] /#{params[:url]})&quot;
    end

    respond_to do |format|
      format.json { render json: { error: }, status: @status_code }
      format.any { render status: @status_code }
    end
  end

  private

    def set_default_format
      request.format = :html unless request.format == :json
    end
end

</code></pre>
<p>Next, we'll create a <code>view</code> file for this show action. In this view file, we
will render the <code>Error</code> component and pass <code>error_status_code</code> to it as props.</p>
<pre><code class="language-rb">&lt;%= react_component(&quot;Error&quot;, { error_status_code: @status_code }, { class: &quot;root-container&quot; }) %&gt;
</code></pre>
<p>In the <code>Error</code> component, we will render the same <code>ErrorPage</code> component created
above and pass the error status code received.</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import ErrorPage from &quot;./ErrorPage&quot;;

const reactProps = JSON.parse(
  document.getElementsByClassName(&quot;root-container&quot;)[0]?.dataset?.reactProps ||
    &quot;{}&quot;
);

const Error = () =&gt; &lt;ErrorPage status={reactProps.error_status_code} /&gt;;

export default Error;
</code></pre>
<p>Now add the following route in the <code>config/routes.rb</code> file to point <code>404</code> and
<code>500</code> requests to the <code>show</code> action in the <code>ErrorsController</code>.</p>
<pre><code class="language-rb">Rails.application.routes.draw do
  match &quot;/:status_code&quot;, constraints: { status_code: /404|500/, format: :html }, to: &quot;errors#show&quot;, via: :all
end
</code></pre>
<p>Finally, we need to tell the Rails application to use our new controller and
routes setup instead of those HTML templates in the public directory. For this,
add the following to the <code>class Application &lt; Rails::Application</code> block in
<code>config/application.rb</code> file.</p>
<pre><code class="language-rb">config.exceptions_app = self.routes
</code></pre>
<h3>Handling unmatched routes</h3>
<p>Sometimes, we get random requests from bots and crawlers to access the paths
that don't exist. When this occurs, our server raises an
<code>ActionController::RoutingError</code> exception. This also happens when a user
mistypes a URL. However, in such cases, instead of just throwing the exception,
we should inform the user that what they are requesting does not exist by
showing a 404 page.</p>
<p>To handle this, we implemented a <code>catch_all</code> route, as the name suggests, which
catches any routes that don't match already defined routes, which will be placed
at the end of our routes configuration. This placement ensures that it is only
called if a request doesn't match any other defined route.</p>
<pre><code class="language-rb">Rails.application.routes.draw do
  #Define other routes here...

  #Catch-all route for handling errors
  match &quot;:url&quot;, to: &quot;errors#show&quot;, via: :all, url: /.*/, constraints: -&gt; (request) do
    request.path.exclude?(&quot;/rails/&quot;)
  end
end
</code></pre>
<p>This route also uses the same <code>ErrorsController</code> mentioned above for handling
unmatched requests and will show a 404 page.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[How we migrated from Sidekiq to Solid Queue]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/migrating-to-solid-queue-from-sidekiq"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/how_we_migrated_from_sidekiq_to_solid_queue.jpg"/>
      <id>https://www.bigbinary.com/blog/migrating-to-solid-queue-from-sidekiq</id>
      <published>2024-03-05T00:00:00.000Z</published>
      <updated>2024-03-05T00:00:00.000Z</updated>
      <author>
        <name>Chirag Shah</name>
        <uri>https://www.bigbinary.com/blog/authors/chiraggshah</uri>
      </author>
      <category term="Rails"/>
      <category term="Solid Queue"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/how_we_migrated_from_sidekiq_to_solid_queue.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[How we migrated to Solid Queue from Sidekiq]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/migrating-to-solid-queue-from-sidekiq"><![CDATA[<p>BigBinary is building a suite of products under <a href="https://neeto.com">neeto</a>. We
currently have around 22 products under development, and all of the products are
using <a href="https://github.com/sidekiq/sidekiq">Sidekiq</a>. After the
<a href="https://dev.37signals.com/introducing-solid-queue/">launch of Solid Queue</a>, we
decided to migrate <a href="https://neeto.com/neetoform">NeetoForm</a> from Sidekiq to
Solid Queue.</p>
<p>Please note that Solid Queue currently doesn't support cron-style or recurring
jobs. There is a <a href="https://github.com/basecamp/solid_queue/pull/155">PR open</a>
regarding this issue. We have only partially migrated to Solid Queue. For
recurring jobs, we are still using Sidekiq. Once the PR is merged, we will
migrate completely to Solid Queue.</p>
<h2>Migrating to Solid Queue from Sidekiq</h2>
<p>Here is a step-by-step migration guide you can use to migrate your Rails
application from Sidekiq to Solid Queue.</p>
<h2>1. Installation</h2>
<ul>
<li>Add <code>gem &quot;solid_queue&quot;</code> to your Rails application's Gemfile and run
<code>bundle install</code>.</li>
<li>Run <code>bin/rails generate solid_queue:install</code> which copies the config file and
the required migrations.</li>
<li>Run the migrations using <code>bin/rails db:migrate</code>.</li>
</ul>
<h2>2. Configuration</h2>
<p>The installation step should have created a <code>config/solid_queue.yml</code> file.
Uncomment the file and modify it as per your needs. Here is how the file looks
for our application.</p>
<pre><code class="language-yaml">default: &amp;default
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: &quot;auth&quot;
      threads: 3
      processes: 1
      polling_interval: 0.1
    - queues: &quot;urgent&quot;
      threads: 3
      processes: 1
      polling_interval: 0.1
    - queues: &quot;low&quot;
      threads: 3
      processes: 1
      polling_interval: 2
    - queues: &quot;*&quot;
      threads: 3
      processes: 1
      polling_interval: 1

development:
  &lt;&lt;: *default

staging:
  &lt;&lt;: *default

heroku:
  &lt;&lt;: *default

test:
  &lt;&lt;: *default

production:
  &lt;&lt;: *default
</code></pre>
<h2>3. Starting Solid Queue</h2>
<p>On your development machine, you can start Solid Queue by running the following
command.</p>
<pre><code>bundle exec rake solid_queue:start
</code></pre>
<p>This will start Solid Queue's supervisor process and will start processing any
enqueued jobs. The supervisor process forks
<a href="https://github.com/basecamp/solid_queue?tab=readme-ov-file#workers-and-dispatchers">workers and dispatchers</a>
according to the configuration provided in the <code>config/solid_queue.yml</code> file.
The supervisor process also controls the heartbeats of workers and dispatchers,
and sends signals to stop and start them when needed.</p>
<p>Since we use <a href="https://github.com/ddollar/foreman">foreman</a>, we added the above
command to our Procfile.</p>
<pre><code class="language-ruby"># Procfile
web:  bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml
solidqueueworker: bundle exec rake solid_queue:start
release: bundle exec rake db:migrate
</code></pre>
<h2>4. Setting the Active Job queue adapter</h2>
<p>You can set the Active Job queue adapter to <code>:solid_queue</code> by adding the
following line in your <code>application.rb</code> file.</p>
<pre><code class="language-ruby"># application.rb
config.active_job.queue_adapter = :solid_queue
</code></pre>
<p>The above change sets the queue adapter at the application level for all the
jobs. However, since we wanted to use Solid Queue for our regular jobs and
continue using Sidekiq for cron jobs, we didn't make the above change in
<code>application.rb</code>.</p>
<p>Instead, we created a new base class that inherited from <code>ApplicationJob</code> and
set the queue adapter to <code>:solid_queue</code> inside that.</p>
<pre><code class="language-ruby"># sq_base_job.rb
class SqBaseJob &lt; ApplicationJob
  self.queue_adapter = :solid_queue
end
</code></pre>
<p>Then we made all the classes implementing regular jobs inherit from this new
class <code>SqBaseJob</code> instead of <code>ApplicationJob</code>.</p>
<pre><code class="language-diff"># send_email_job.rb
- class SendEmailJob &lt; ApplicationJob
+ class SendEmailJob &lt; SqBaseJob
  # ...
end
</code></pre>
<p>By making the above change, all our regular jobs got enqueued via Solid Queue
instead of Sidekiq.</p>
<p>But we realized later that emails were still being sent via Sidekiq. On
debugging and looking into Rails internals, we found that <code>ActionMailer</code> uses
<code>ActionMailer::MailDeliveryJob</code> for enqueuing or sending emails.</p>
<p><code>ActionMailer::MailDeliveryJob</code> inherits from <code>ActiveJob::Base</code> rather than the
application's <code>ApplicationJob</code>. So even if we set the queue_adapter in
<code>application_job.rb</code>, it won't work. <code>ActionMailer::MailDeliveryJob</code> fallbacks
to using the adapter defined in <code>application.rb</code> or environment-specific
(production.rb / staging.rb / development.rb) config files. But we can't do that
because we still want to use Sidekiq for cron jobs.</p>
<p>To use Solid Queue for mailers, we needed to override the queue_adapter for
mailers. We can do that in <code>application_mailer.rb</code>.</p>
<pre><code class="language-ruby"># application_mailer.rb
class ApplicationMailer &lt; ActionMailer::Base
  # ...
  ActionMailer::MailDeliveryJob.queue_adapter = :solid_queue
end
</code></pre>
<p>This change is only until we use both Sidekiq and Solid Queue. Once cron style
jobs feature lands in Solid Queue, we can remove this override and set the
queue_adapter directly in <code>application.rb</code>, which will enforce the setting
globally.</p>
<h2>5. Code changes</h2>
<p>For migrating from Sidekiq to Solid Queue, we had to make the following changes
to the syntax for enqueuing a job.</p>
<ul>
<li>Replaced <code>.perform_async</code> with <code>.perform_later</code>.</li>
<li>Replaced <code>.perform_at</code> with <code>.set(...).perform_later(...)</code>.</li>
</ul>
<pre><code class="language-diff">- SendMailJob.perform_async
+ SendMailJob.perform_later

- SendMailJob.perform_at(1.minute.from_now)
+ SendMailJob.set(wait: 1.minute).perform_later
</code></pre>
<p>At some places we were storing the Job ID on a record, for querying the job's
status or for cancelling the job. For such cases, we made the following change.</p>
<pre><code class="language-diff">def disable_form_at_deadline
- job_id = DisableFormJob.perform_at(deadline, self.id)
- self.disable_job_id = job_id
+ job_id = DisableFormJob.set(wait_until: deadline).perform_later(self.id)
+ self.disable_job_id = job.job_id
end

def cancel_form_deadline
- Sidekiq::Status.cancel(self.disable_job_id)
+ SolidQueue::Job.find_by(active_job_id: self.disable_job_id).destroy!
  self.disable_job_id = nil
end
</code></pre>
<h2>6. Error handling and retries</h2>
<p>Initially, we thought the
<a href="https://github.com/basecamp/solid_queue?tab=readme-ov-file#other-configuration-settings"><code>on_thread_error</code> configuration</a>
provided by Solid Queue can be used for error handling. However, during the
development phase, we noticed that it wasn't capturing errors. We raised
<a href="https://github.com/basecamp/solid_queue/issues/120">an issue with Solid Queue</a>
as we thought it was a bug.</p>
<p><a href="https://github.com/rosa">Rosa Gutiérrez</a>
<a href="https://github.com/basecamp/solid_queue/issues/120#issuecomment-1894413948">responded</a>
on the issue and clarified the following.</p>
<blockquote>
<p><code>on_thread_error</code> wasn't intended for errors on the job itself, but rather
errors in the thread that's executing the job, but around the job itself. For
example, if you had an Active Record's thread pool too small for your number
of threads and you got an error when trying to check out a new connection,
on_thread_error would be called with that.</p>
<p>For errors in the job itself, you could try to hook into Active Job's itself.</p>
</blockquote>
<p>Based on the above information, we modified our <code>SqBaseJob</code> base class to handle
the exceptions and report it to <a href="https://www.honeybadger.io/">Honeybadger</a>.</p>
<pre><code class="language-ruby"># sq_base_job.rb
class SqBaseJob &lt; ApplicationJob
  self.queue_adapter = :solid_queue

  rescue_from(Exception) do |exception|
    context = {
      error_class: self.class.name,
      args: self.arguments,
      scheduled_at: self.scheduled_at,
      job_id: self.job_id
    }
    Honeybadger.notify(exception, context:)
    raise exception
  end
end
</code></pre>
<p>Remember we mentioned that <code>ActionMailer</code> doesn't inherit from <code>ApplicationJob</code>.
So similarly, we would have to handle exceptions for Mailers separately.</p>
<pre><code class="language-ruby"># application_mailer.rb
class ApplicationMailer &lt; ActionMailer::Base
  # ...
  ActionMailer::MailDeliveryJob.rescue_from(Exception) do |exception|
    context = {
      error_class: self.class.name,
      args: self.arguments,
      scheduled_at: self.scheduled_at,
      job_id: self.job_id
    }
    Honeybadger.notify(exception, context:)
    raise exception
  end
end
</code></pre>
<p>For retries, unlike Sidekiq, Solid Queue doesn't include any automatic retry
mechanism, it
<a href="https://edgeguides.rubyonrails.org/active_job_basics.html#retrying-or-discarding-failed-jobs">relies on Active Job for this</a>.
We wanted our application to retry sending emails in case of any errors. So we
added the retry logic in the <code>ApplicationMailer</code>.</p>
<pre><code class="language-ruby"># application_mailer.rb
class ApplicationMailer &lt; ActionMailer::Base
  # ...
  ActionMailer::MailDeliveryJob.retry_on StandardError, attempts: 3
end
</code></pre>
<p>Note that, although the queue adapter configuration can be removed from
<code>application_mailer.rb</code> once the entire application migrates to Solid Queue,
error handling and retry override cannot be removed because of the way
<code>ActionMailer::MailDeliveryJob</code> inherits from <code>ActiveJob::Base</code> rather than
application's <code>ApplicationJob</code>.</p>
<h2>7. Testing</h2>
<p>Once all the above changes were done, it was obvious that a lot of tests were
failing. Apart from fixing the usual failures related to the syntax changes,
some of the tests were failing inconsistently. On debugging, we found that the
affected tests were all related to controllers, specifically tests inheriting
from <code>ActionDispatch::IntegrationTest</code>.</p>
<p>We tried debugging and searched for solutions when we stumbled upon
<a href="https://github.com/bensheldon">Ben Sheldon's</a>
<a href="https://github.com/bensheldon/good_job/issues/846#issuecomment-1432375562">comment on one of Good Job's issues</a>.
Ben points out that this is actually
<a href="https://github.com/rails/rails/issues/37270">an issue in Rails</a> where Rails
sometimes inconsistently overrides ActiveJob's queue_adapter setting with
TestAdapter. A <a href="https://github.com/rails/rails/pull/48585">PR is already open</a>
for the fix. Thankfully, Ben, in the same comment, also mentioned a workaround
for it until the fix has been added to Rails.</p>
<p>We added the workaround in our test <code>helper_methods.rb</code> and called the method in
each of our controller tests which were failing.</p>
<pre><code class="language-ruby"># test/support/helper_methods.rb
def ensure_consistent_test_adapter_is_used
  # This is a hack mentioned here: https://github.com/bensheldon/good_job/issues/846#issuecomment-1432375562
  # The actual issue is in Rails for which a PR is pending merge
  # https://github.com/rails/rails/pull/48585
  (ActiveJob::Base.descendants + [ActiveJob::Base]).each(&amp;:disable_test_adapter)
end
</code></pre>
<pre><code class="language-ruby"># test/controllers/exports_controller_test.rb
class ExportsControllerTest &lt; ActionDispatch::IntegrationTest
  def setup
    ensure_consistent_test_adapter_is_used
    # ...
  end

  # ...
end
</code></pre>
<h2>8. Monitoring</h2>
<p>Basecamp has released
<a href="https://github.com/basecamp/mission_control-jobs">mission_control-jobs</a>, which
can be used to monitor background jobs. It is generic, so it can be used with
any compatible ActiveJob adapter.</p>
<p>Add <code>gem &quot;mission_control-jobs&quot;</code> to your Gemfile and run <code>bundle install</code>.</p>
<p>Mount the mission control route in your <code>routes.rb</code> file.</p>
<pre><code class="language-ruby"># routes.rb
Rails.application.routes.draw do
  # ...
  mount MissionControl::Jobs::Engine, at: &quot;/jobs&quot;
</code></pre>
<p>By default, mission control would try to load the adapter specified in your
<code>application.rb</code> or individual environment-specific files. Currently, Sidekiq
isn't compatible with mission control, so you will face an error while loading
the dashboard at <code>/jobs</code>. The fix is to explicitly specify <code>solid_queue</code> to the
list of mission control adapters.</p>
<pre><code class="language-ruby"># application.rb
# ...
config.mission_control.jobs.adapters = [:solid_queue]
</code></pre>
<p>Now, visiting <code>/jobs</code> on your site should load a dashboard where you can monitor
your Solid Queue jobs.</p>
<p>But that isn't enough. There is no authentication. For development environments,
it is fine, but the <code>/jobs</code> route would be exposed on production too. By
default, Mission Control's controllers will extend the host app's
<code>ApplicationController</code>. If no authentication is enforced, <code>/jobs</code> will be
available to everyone.</p>
<p>To implement some kind of authentication, we can specify a different controller
as the base class for Mission Control's controllers and add the authentication
there.</p>
<pre><code class="language-ruby"># application.rb
# ...
MissionControl::Jobs.base_controller_class = &quot;MissionControlController&quot;
</code></pre>
<pre><code class="language-ruby"># app/controllers/mission_control_controller.rb
class MissionControlController &lt; ApplicationController
  before_action :authenticate!, if: :restricted_env?

  private

    def authenticate!
      authenticate_or_request_with_http_basic do |username, password|
        username == &quot;solidqueue&quot; &amp;&amp; password == Rails.application.secrets.mission_control_password
      end
    end

    def restricted_env?
      Rails.env.staging? || Rails.env.production?
    end
end
</code></pre>
<p>Here, we have specified that <code>MissionControlController</code> would be our base
controller for mission control related controllers. Then in
<code>MissionControlController</code> we implemented basic authentication for staging and
production environments.</p>
<h2>Observations</h2>
<p>We haven't had any complaints so far. Solid Queue offers simplicity, requires no
additional infrastructure and provides visibility for managing jobs since they
are stored in the database.</p>
<p>In the coming days, we will migrate all of our 22 Neeto products to Solid Queue.
And once cron-style job support lands in Solid Queue, we will completely migrate
from Sidekiq.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 8 introduces a built-in rate limiting API]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-8-rate-limiting-api"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_introduces_a_built_in_rate_limiting_api.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-8-rate-limiting-api</id>
      <published>2024-02-13T00:00:00.000Z</published>
      <updated>2024-02-13T00:00:00.000Z</updated>
      <author>
        <name>Yedhin Kizhakkethara</name>
        <uri>https://www.bigbinary.com/blog/authors/yedhink</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 8"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_introduces_a_built_in_rate_limiting_api.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 8.0 adds rate limiting to ActionController which uses Active Support cache by default]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-8-rate-limiting-api"><![CDATA[<p>In the dynamic world of web development, managing the flow of requests is
crucial for maintaining a responsive and reliable application. Rate limiting is
a powerful technique that acts as the traffic cop for your API, ensuring fair
access to resources and preventing potential chaos. In a nutshell, rate limiting
is the practice of controlling the rate of requests a user, device, or
application can make within a set timeframe.</p>
<p>In this blog post, we'll delve into the concept of rate limiting, exploring its
significance and implementation in Rails 8.0.</p>
<h2>The Need for Rate Limiting</h2>
<p>It's crucial in ensuring the following:</p>
<ul>
<li>Security Shield: Guards against Denial-of-Service (DoS) attacks, thwarting
malicious attempts to flood your app and crash it.</li>
<li>Resource Balance: Prevents resource abuse by capping heavy users, ensuring
fair resource distribution and maintaining optimal app performance.</li>
<li>Brute-Force Defender: Stalls hackers attempting password guesses or exploiting
vulnerabilities through relentless, repeated attempts.</li>
</ul>
<h2>How Rack::Attack Paved the Way</h2>
<p>In the pre-Rails 8.0 era, the go-to solution for rate limiting was the
<a href="https://github.com/rack/rack-attack"><code>rack-attack</code></a> gem. This gem allowed
developers to set up rate-limiting rules by adding custom code in the
<code>rack_attack.rb</code> file. While effective, this approach required manual
intervention for each endpoint and could become cumbersome in a growing
application. This external dependency brought its own set of challenges, such as
the need for custom code and constant vigilance over rate-limited endpoints.</p>
<h2>Rails 8.0: Native Rate Limiting</h2>
<p>Rails 8.0 brings a native rate-limiting feature to the Action Controller,
streamlining the process and eliminating the need for external gems. Developers
can now set rate limits directly within their controllers using the <code>rate_limit</code>
method.</p>
<p>Let's take a look at the usage:</p>
<h3>Define Limits</h3>
<p>Utilize the <code>rate_limit</code> method within your controller actions, specifying the
maximum allowed requests and the corresponding timeframe. The <code>rate_limit</code>
method accepts the following parameters:</p>
<ul>
<li><code>to</code>: The maximum number of requests allowed, beyond which the rate limiting
error will be raised, that is with a <strong>429 Too Many Requests</strong> response.</li>
<li><code>within</code>: The maximum number of requests allowed in a given time window.</li>
<li><code>only</code>: Which all controller actions ought to be rate limited.</li>
<li><code>except</code>: Which all controller actions to be omitted from rate limiting.</li>
</ul>
<p>An example:</p>
<pre><code class="language-rb">class SignupController &lt; ApplicationController
  rate_limit to: 4, within: 1.minute, only: :create

  def create
    # ...
  end
end
</code></pre>
<h3>Granular Control</h3>
<p>Target specific actions or employ custom logic for nuanced control. For
instance, you can limit requests based on domain instead of IP address.</p>
<pre><code class="language-rb">rate_limit to: 4, within: 1.minute, by: -&gt; { request.domain }, only: :create
</code></pre>
<h3>Tailored Responses</h3>
<p>By default, rate-limited requests receive a <code>429 Too Many Requests</code> error.
However, you can personalize the response for a more informative user
experience.</p>
<pre><code class="language-rb">rate_limit to: 4, within: 1.minute, with: -&gt; { redirect_to(ip_restrictions_controller_url), alert: &quot;Signup Attempts failed four times. Please try again later.&quot; }, only: :create
</code></pre>
<h2>Custom Cache Stores</h2>
<p>The Rails 8.0 rate-limiting implementation at the moment allows us to make use a
wide range of backends as its store.</p>
<p>It includes:</p>
<ul>
<li>Memcached</li>
<li>Redis (including alternative Redis clients like Dalli)</li>
<li>Database-backed stores</li>
<li>File-based stores</li>
</ul>
<p>The Rate Limiting API seamlessly integrates with Rails' caching mechanisms,
leveraging
<a href="https://api.rubyonrails.org/classes/ActiveSupport/Cache.html"><code>ActiveSupport::Cache</code></a>
stores. Developers can specify custom cache stores if they require separate
handling for rate limits compared to other cache data. This integration ensures
efficient storage and retrieval of rate limit data, optimizing performance. An
example:</p>
<pre><code class="language-rb">class SignupController &lt; ApplicationController
  RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV[&quot;REDIS_URL&quot;])
  rate_limit to: 8, within: 2.minutes, store: RATE_LIMIT_STORE
end
</code></pre>
<h2>What to look forward to</h2>
<p>At the moment the built-in rate limiting feature is limited in its
extensibility. There are cases where this feature cannot be used as a direct
replacement for logic that's based on <code>rack-attack</code> gem. A good example is a
scenario where we might want to use an exponential backoff based rate limiting
algorithm, which can be implemented using <code>rack-attack</code> gem but not with the
built-in Rails feature at the moment.</p>
<p>Right now, we are constrained to stick to the default cache counter algorithm
that Rails comes shipped with. In the future, we can expect a more generic limiter
interface, allowing users to explore advanced rate-limiting algorithms such as
exponential backoff, leaky bucket, or token bucket, etc. This change would open
the door for developers to swap the implementation based on their specific
requirements.</p>
<p>Please check out the following pull requests for more details:</p>
<ul>
<li>https://github.com/rails/rails/pull/50490</li>
<li>https://github.com/rails/rails/pull/50781</li>
</ul>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Sharing data across React apps with globalProps]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/global-props"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/using_globalprops_to_make_it_easier_to_share_data_in_reactjs_application.jpg"/>
      <id>https://www.bigbinary.com/blog/global-props</id>
      <published>2024-02-06T00:00:00.000Z</published>
      <updated>2024-02-06T00:00:00.000Z</updated>
      <author>
        <name>Deepak Jose</name>
        <uri>https://www.bigbinary.com/blog/authors/deepakjosp</uri>
      </author>
      <category term="React"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/using_globalprops_to_make_it_easier_to_share_data_in_reactjs_application.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Using globalProps to make it easier to share data in React.js applications]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/global-props"><![CDATA[<h3>Our technology stack for Neeto</h3>
<p>We are building <a href="https://neeto.com">neeto</a>, and our technology stack is quite
simple. On the front end, we use React.js. On the backend we use Ruby on Rails,
PostgreSQL, Redis and Sidekiq.</p>
<p>The term <code>globalProps</code> might not ring a bell for most people. It was coined by
the BigBinary team for our internal use. <code>globalProps</code> is data that is directly
retrieved from our backend and assigned to the browser global object that's
<code>window</code>. To view the global props, we can type <code>globalProps</code> in the browser
console, which prints out useful information set by the backend service.</p>
<img alt="desktop view" src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/global-props/global-props-console.png">
<h3>How is <code>globalProps</code> implemented</h3>
<p>To understand where the <code>globalProps</code> came from and how it works, we need to
examine the <a href="https://github.com/reactjs/react-rails">React-Rails</a> gem. It uses
Ruby on Rails asset pipeline to automatically transform JSX into Ruby on Rails
compatible assets using the Ruby Babel transpiler.</p>
<p><code>react_component</code> helper method takes a component name as the first argument,
<code>props</code> as the second argument and a list of <code>HTML attributes</code> as the third
argument. The documentation has more
<a href="https://github.com/reactjs/react-rails/blob/master/docs/view-helper.md">details</a>.</p>
<pre><code class="language-erb">&lt;%= react_component(&quot;App&quot;, get_client_props, { class: &quot;root-container&quot; }) %&gt;
</code></pre>
<h3>Limitations of the default behavior</h3>
<p>In the <code>react-rails</code> gem, the hash is set as the props of the component
specified in the <code>react_component</code> method by default. In the example above, the
hash returned by the <code>get_client_props</code> method is passed as props to the <code>App</code>
component in the front end.</p>
<p>The limitation of this approach is that we need to pass down globalProps through
all the components by prop-drilling or React Context.</p>
<h5>The concept of nanos</h5>
<p>At neeto, anything that does not contain product-specific business logic and can
be extracted into a reusable tool is extracted into an independent package. We
call them nanos.</p>
<p>You can read more about it in our blog on how
<a href="https://blog.neeto.com/p/nanos-make-neeto-better">nanos make Neeto better</a>.</p>
<h5>Limitations in accessing the props by nanos and utility functions</h5>
<p>The issue with the above approaches in handling the props is that it won't be
directly available in utility functions or nano. We explicitly need to pass it
as arguments to utility functions after prop drilling. If we use React Context,
it can only be accessed in React components or hooks, it cannot be accessed in
utility functions. Also, we cannot directly obtain the reference of the Context
within the nanos.</p>
<h5>Why we didn't chose environment variables</h5>
<p>Some of the variables inside the <code>globalProps</code> are environment variables, which
is usually accessed as <code>process.env.VARIABLE_NAME</code>. If we set environment
variables, they will be hardcoded into the JavaScript bundle at the time of
bundling. This implies that whenever we need to change the environment variable,
we must trigger a redeployment.</p>
<h3>The solution is <code>globalProps</code></h3>
<p>The advantage of <code>globalProps</code> over these approaches is, it's accessible
everywhere since it's in the browser global object window. All the nanos and
utility functions that we integrate into the application have seamless access to
the props without any extra step of wiring.</p>
<h3>Seeding the hash at the backend into the browser's global object, window</h3>
<p>Seeding the hash at the backend into the browser's global object, window, is
accomplished using the above-mentioned helper method, <code>react_component</code>. As we
discussed earlier, an HTML node is created that contains <code>data-react-class</code>
representing the component name and<code>data-react-props</code> attribute representing the
hash we passed from the backend as an HTML-encoded string.</p>
<img alt="desktop view" src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/global-props/element-console.png" >
<h3>Decode the HTML-encoded string into JavaScript object</h3>
<p>The next step is to decode the HTML-encoded string and parse it into a
JavaScript object. The hash is read from the <code>root-container</code> HTML node and
parsed into a JavaScript object.</p>
<pre><code class="language-js">const rootContainer = document.getElementsByClassName(&quot;root-container&quot;)[0];

const reactProps = JSON.parse(rootContainer?.dataset?.reactProps || &quot;{}&quot;);
</code></pre>
<h3>Convert the case of the keys in the object</h3>
<p>The JavaScript object that we have obtained have the keys in snake case. We will
convert them into camel case using the helper method
<a href="https://github.com/bigbinary/neeto-cist/blob/b4375525ac5ffbb0aeb6548cc64d3970379493ee/docs/pure/objects.md#keystocamelcase">keysToCamelCase</a>.</p>
<p>We convert the case because React prefers camel case keys, while Rails prefers
snake case keys.</p>
<pre><code class="language-js">const globalProps = keysToCamelCase(reactProps);
</code></pre>
<h3>Deepfreeze the global props</h3>
<p>Additionally, we take an extra step to deep-freeze the global props object,
ensuring immutability. The helper function used here is
<a href="https://github.com/bigbinary/neeto-cist/blob/b4375525ac5ffbb0aeb6548cc64d3970379493ee/docs/pure/objects.md#deepfreezeobject">deepFreezeObject</a>.
This prevents modifications to global props from within the product and thus
ensures data integrity when working with different nanos. All these steps are
performed before the initial rendering of the React component.</p>
<pre><code class="language-js">window.globalProps = globalProps;

deepFreezeObject(window.globalProps);
</code></pre>
<p>Let's see the final codeblock that seeds the hash at the backend to the
browser's global object.</p>
<pre><code class="language-js">export default function initializeGlobalProps() {
  const rootContainer = document.getElementsByClassName(&quot;root-container&quot;);
  const htmlEncodedReactProps = rootContainer[0]?.dataset?.reactProps;

  const reactProps = JSON.parse(htmlEncodedReactProps || &quot;{}&quot;);
  const globalProps = keysToCamelCase(reactProps);

  window.globalProps = globalProps;
  deepFreezeObject(window.globalProps);
}
</code></pre>
<p>If we take a closer look at the content of <code>globalProps</code>, we can see that it
carries a lot of data. As discussed earlier this data is useful to other nanos.
Some of the data being passed are appName, honeyBadgerApiKey, organization, user
info, etc.</p>
<img alt="desktop view" src="https://www.bigbinary.com/blog/images/images_used_in_blog/2024/global-props/global-props-console.png">
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Solid Queue & understanding UPDATE SKIP LOCKED]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/solid-queue"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/solid_queue.jpg"/>
      <id>https://www.bigbinary.com/blog/solid-queue</id>
      <published>2024-01-23T00:00:00.000Z</published>
      <updated>2024-01-23T00:00:00.000Z</updated>
      <author>
        <name>Chirag Shah</name>
        <uri>https://www.bigbinary.com/blog/authors/chiraggshah</uri>
      </author>
      <category term="Rails"/>
      <category term="Solid Queue"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/solid_queue.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Introduction to solid queue]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/solid-queue"><![CDATA[<h2>What is solid queue?</h2>
<p>Recently, <a href="https://37signals.com/">37signals</a> open sourced
<a href="https://dev.37signals.com/introducing-solid-queue">Solid Queue</a>.</p>
<p>Solid Queue is database based queuing backend for Active Job. In contrast
Sidekiq and Resque are Redis-based queuing backends.</p>
<p>In her blog <a href="https://github.com/rosa">Rosa Gutiérrez</a> mentioned following lines
which captured our attention.</p>
<blockquote>
<p>In our case, one feature that PostgreSQL has had for quite some time and that
was finally introduced in MySQL 8, has been crucial to our implementation:</p>
<p>SELECT ... FOR UPDATE SKIP LOCKED</p>
<p>This allows Solid Queue’s workers to fetch and lock jobs without locking other
workers.</p>
</blockquote>
<p>As per her, this feature had been
<a href="https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE">in PostgreSQL</a>
for a while, and now this feature has landed
<a href="https://dev.mysql.com/blog-archive/mysql-8-0-1-using-skip-locked-and-nowait-to-handle-hot-rows">in MySQL</a>
making it possible to build Solid Queue.</p>
<p>We had never heard of <code>UPDATE SKIP LOCKED</code> feature either in PostgreSQL or in
MySQL. We were wondering what is this <code>UPDATE SKIP LOCKED</code> without which it was
not possible to build Solid Queue. So we decided to look into it.</p>
<h2>Processing jobs from a queue</h2>
<p>Consider a case where we need to build a system where a bunch of jobs need to be
processed in the background.</p>
<p>There are a bunch of workers waiting to grab a job and start processing the
moment a job becomes available. The challenge is when multiple workers attempt
to claim the same job simultaneously how do we ensure that only one of the
workers claims the job for processing. At any point of time, a worker should
claim only the &quot;unclaimed&quot; job, and an &quot;unclaimed&quot; job should be claimed by one
and only one worker.</p>
<p>Here is how one might go about it implementing it.</p>
<pre><code class="language-sql">START TRANSACTION
SELECT * FROM JOBS WHERE processed='no' LIMIT 1;
-- Process the job
COMMIT;
</code></pre>
<p>With the above code, it's possible that two workers might claim the same job.</p>
<p>One way to resolve this issue is by marking a particular row as locked for
update.</p>
<pre><code class="language-sql">START TRANSACTION;
SELECT * FROM JOBS WHERE processed='no' FOR UPDATE LIMIT 1;
-- Process the job
COMMIT
</code></pre>
<p><code>SELECT ... FOR UPDATE</code> locks a particular row, and hence no one else can lock
that record.</p>
<p>As soon as a new job comes in, multiple workers will execute the above query and
will try to take a lock on that record. The database will ensure that only one
of the workers gets the lock.</p>
<p>The first worker will take the lock on the record using <code>FOR UPDATE</code>. When other
workers come to that record and they see that there is a lock <code>FOR UPDATE</code>, they
will wait for the lock to be lifted. Yes, these workers will wait until the lock
is released.</p>
<p>The lock will only be released when the transaction is committed. When the
transaction is committed and the lock is released, then other workers will get
hold of the record only to find that the job has already been processed. As you
can see, this is a highly inefficient process.</p>
<p>That is where <code>FOR UPDATE SKIP LOCKED</code> comes in.</p>
<h2>SKIP LOCKED skips locked rows</h2>
<pre><code class="language-sql">START TRANSACTION;
SELECT * FROM jobs_table FOR UPDATE SKIP LOCKED LIMIT 1;
-- Process the job
COMMIT;
</code></pre>
<p>Imagine the same scenario here. A job comes in. Multiple workers compete to
claim the job. The database ensures that only one worker gets the lock. However
in this case the other workers will move on to the next record. They will not
wait. That's what <code>SKIP LOCKED</code> does.</p>
<p>MySQL has detailed documentation on
<a href="https://dev.mysql.com/blog-archive/mysql-8-0-1-using-skip-locked-and-nowait-to-handle-hot-rows/">how SKIP LOCKED works</a>
If you want to read in more detail.</p>
<p>Solid Queue uses <code>FOR UPDATE SKIP LOCKED</code> feature to ensure that a job is
claimed by only one worker.</p>
<h2>How GoodJob manages job processing without SKIP LOCKED</h2>
<p><a href="https://github.com/bensheldon/good_job">GoodJob</a> burst into the scene
<a href="https://island94.org/2020/07/introducing-goodjob-1-0">around July, 2020</a>.
GoodJobs supports only PostgreSQL database because it uses advisory locks to
guarantee that no two workers claim the same job.</p>
<p>PostgreSQL folks understand that the lock mechanism provided by the database
would not satisfy all the variety of cases that might arise in an application.
Advisory locks are a mechanism that allows applications to establish a
communication channel to coordinate actions between different sessions or
transactions. Unlike regular row-level locks enforced by the database system,
advisory locks are implemented as a set of low-level functions that applications
can use to acquire and release locks based on their requirements. We can read
more about it
<a href="https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS">here</a>.</p>
<p><a href="https://www.postgresql.org/docs/9.1/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS">pg_advisory_lock function</a>
will lock the given resource. However, if another session already holds a lock on
the same resourc,e then this function will wait. This is similar to the
<code>FOR UPDATE</code> case we saw above.</p>
<p>However <code>pg_try_advisory_lock function</code> will either obtain the lock immediately
and return true, or return false if the lock cannot be acquired immediately. As
you can see, the name has the word <code>try</code>. This function attempts to acquire a lock.
If it can't get the lock, then it won't wait. Now this function can be utilized
to build a queuing system.</p>
<p>Any usage of an advisory lock means the application needs to coordinate action. It
gives more power to the application but it also means more work by the
application. In contrast, <code>FOR UPDATE SKIP RECORD</code> is natively supported by
PostgreSQL.</p>
<p>Based on the discussions
<a href="https://github.com/bensheldon/good_job/issues/896">here</a> and
<a href="https://github.com/bensheldon/good_job/discussions/831#discussioncomment-6780579">here</a>,
it seems GoodJob is evaluating the possibility of migrating from advisory locks
to using <code>FOR UPDATE SKIP LOCKED</code> for better performance. Going through these
issues was quite revealing and I got to learn a lot about things I was unaware
of.</p>
<h2>Delayed job implementation</h2>
<p><a href="https://github.com/collectiveidea/delayed_job">DelayedJob</a> has been there since
2009, long before Sidekiq. It doesn't use <code>SKIP LOCK</code>. Instead it uses a row
level locking system by
<a href="https://github.com/tobi/delayed_job/blob/719b628bdd54566f80ae3a99c4a02dd39d386c07/lib/delayed/job.rb#L164-L181">updating a field in the job record</a>
to indicate that the job is being processed. In short DelayedJob ensures that no
two workers take the same job at the application level without taking any help
in this direction from the database.</p>
<h2>What about SQLite</h2>
<p>So far, we discussed PostgreSQL and MySQL. What about SQLite? Does it support
<code>SKIP LOCK</code>. No, it doesn't support it, but it's ok. As per the
<a href="https://www.sqlite.org/whentouse.html#dbcklst">documentation</a>, it supports
<code>only writer at any instant in time</code>.</p>
<blockquote>
<p>High Concurrency</p>
<p>SQLite supports an unlimited number of simultaneous readers, but it will only
allow one writer at any instant in time. For many situations, this is not a
problem. Writers queue up. Each application does its database work quickly and
moves on, and no lock lasts for more than a few dozen milliseconds. But there
are some applications that require more concurrency, and those applications
may need to seek a different solution.</p>
</blockquote>
<h2>NOWAIT</h2>
<p>For completeness, let's discuss the <code>NOWAIT</code> feature. We saw earlier that
if we take a lock on a row using <code>FOR UPDATE</code>, then other workers will wait until
the lock is released.</p>
<pre><code class="language-sql">START TRANSACTION;
SELECT * FROM JOBS WHERE processed='no' FOR UPDATE NOWAIT LIMIT 1;
-- Process the job
COMMIT
</code></pre>
<p><code>NOWAIT</code> feature allows other transactions to not wait for the lock to be
released. In this case if a transaction is not able to get a lock on the given
row then it will raise an error and the application needs to handle the error.</p>
<p>In contrast, <code>SKIP LOCKED</code> will allow the transaction to move on to the next row
if a lock is already taken.</p>
<h2>Redis backed queue vs database backed queue</h2>
<p>Now that we looked at how <code>FOR UPDATE SKIP LOCK</code> helps build queuing system
using database itself, let's see some pros and cons of each type of queuing
system.</p>
<h4>Simplicity and familiarity</h4>
<p>Database-backed queues are often simpler to set up and manage, especially if
your application is already using a relational database. There's no need for an
additional dependency like Redis.</p>
<h4>No Additional Infrastructure</h4>
<p>Since the job information is stored in the same database as your application
data, you don't need to set up and maintain a separate infrastructure like a
Redis server.</p>
<h4>Transactionality</h4>
<p>Database-backed queues can leverage database transactions, ensuring that both
the job creation and any related database operations are committed or rolled
back together. This can be important in scenarios where data consistency is
critical.</p>
<h4>Modifiability</h4>
<p>It is easier to modify the jobs stored in the database than Redis, but doing so
requires caution, and it's generally not recommended. In Redis, jobs are often
stored as serialized data, and modifying them directly is not as straightforward
or common. Redis provides commands to interact with data, but modifying job data
directly is not a standard practice and could result in data corruption.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 allows subscribing to transaction events]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-allows-subscribing-to-active-record-transaction-events"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_allows_subscribing_active_record_transaction_events.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-allows-subscribing-to-active-record-transaction-events</id>
      <published>2024-01-16T00:00:00.000Z</published>
      <updated>2024-01-16T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_allows_subscribing_active_record_transaction_events.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 allows subscribing to Active Record transaction events for instrumentation.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-allows-subscribing-to-active-record-transaction-events"><![CDATA[<p>The Active Support instrumentation API provides us with hooks that allow us to
choose to be notified when certain events occur inside our application. Rails
provides a set of built-in events that we can subscribe to.
<a href="https://edgeguides.rubyonrails.org/active_support_instrumentation.html#rails-framework-hooks">Here</a>
is the list of framework hooks.</p>
<p>One of the recent additions to this is the <code>transaction.active_record</code> event
That is triggered when Active Record-managed transactions occur. This is
particularly useful if you want to build a monitoring system like NewRelic, where
you need to track and analyze database transactions for performance monitoring
and optimization purposes.</p>
<p>The event payload contains the connection, outcome, and the timing details. The
connection helps us identify the database where the transaction occurred, which
is particularly valuable in a multi-database environment. The outcome, which may
be one of the following: <code>:commit</code>, <code>:rollback</code>, <code>:restart</code>, or <code>:incomplete</code>
signifies the transaction's result.</p>
<p>To make use of this, we can subscribe to the event in an initializer
<code>config/initializers/events.rb</code> like this.</p>
<pre><code class="language-ruby">ActiveSupport::Notifications.subscribe(
  &quot;transaction.active_record&quot;
) do |event|
  MetricsLogger.record_transaction(event.payload)
end
</code></pre>
<p>In the above example, <code>MetricLogger</code> is responsible for recording the
transaction details. It then analyzes and reports slow transactions so that
proper action can be taken. You can modify this to suit your instrumentation
needs.</p>
<p>Please check out these pull requests -
<a href="https://github.com/rails/rails/pull/49192">1</a>,
<a href="https://github.com/rails/rails/pull/49262">2</a> for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Using Cloudflare as CDN for Rails applications]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/using-cloudflare-as-cdn-for-rails-applications"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/using_cloudflare_as_cdn_for_rails_applications.jpg"/>
      <id>https://www.bigbinary.com/blog/using-cloudflare-as-cdn-for-rails-applications</id>
      <published>2023-12-19T00:00:00.000Z</published>
      <updated>2023-12-19T00:00:00.000Z</updated>
      <author>
        <name>Abhijith Sheheer</name>
        <uri>https://www.bigbinary.com/blog/authors/abhijithsheheer</uri>
      </author>
      <category term="Cloudflare"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/using_cloudflare_as_cdn_for_rails_applications.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[How we used Cloudflare to cache assets for Rails applications to improve website performance]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/using-cloudflare-as-cdn-for-rails-applications"><![CDATA[<p>We use <a href="https://www.cloudflare.com/">Cloudflare</a> as our DNS server. But,
Cloudflare is much more than a DNS server. It can be used as a content delivery
network (CDN).</p>
<p>A CDN is a geographically distributed group of servers that cache content close
to end users. Let's say that a user from London is hitting a website that has a
JavaScript file hosted in Chicago. That JavaScript file has to travel all the
way from Chicago to London. This means the site will load slowly.</p>
<p>A CDN will have a copy of that JavaScript file in London itself. In this way,
for that user, that JavaScript file will be loaded from the London server and
not from the Chicago server. This is what a CDN primarily does.</p>
<p>Here's how we set up Cloudflare as a CDN for our Rails applications.</p>
<h2>1. Configure CNAME record for CDN subdomain in Cloudflare</h2>
<p>We need a subdomain that will act as a CDN. For
<a href="https://www.neeto.com/neetocal">NeetoCal</a>, we use https://cdn.neetocal.com as
the subdomain. You can use any subdomain you want.</p>
<p>To set up this subdomain, follow these steps.</p>
<ol>
<li>Add a new record with the <strong>type</strong> <code>CNAME</code>.</li>
<li>In the <strong>name</strong> field, enter <code>*</code>.</li>
<li>In the <strong>target</strong> field, enter the URL where the Rails app is deployed.</li>
</ol>
<p>If we already have a <code>CNAME</code> record for <code>*</code> that handles all the subdomains,
then we don't need to create a new one.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2023/using-cloudflare-as-cdn-for-rails-applications/create-cname-record.png" alt="Creating  record"></p>
<h2>2. Turn on the Proxy feature in Cloudflare</h2>
<p>In the previous step, when creating a new <code>CNAME</code> record, by default, Cloudflare
will enable proxy for each record.</p>
<p>Once the <strong>Proxy status</strong> column is turned on, it will display 'Proxied' as
shown below.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2023/using-cloudflare-as-cdn-for-rails-applications/proxy-status-on.png" alt="&quot;Proxy status&quot; turned ON"></p>
<p>When we turn on &quot;Proxy&quot;, then the DNS queries will resolve to the Cloudflare IP
address instead of the target. It means Cloudflare will get all the requests and
then Cloudflare in turn will forward those requests to the target.</p>
<p>Let's say that a user from Paris loads a webpage which is hosted in Chicago and
Cloudflare has a server in Paris. When &quot;Proxy&quot; is turned on, then when the user
makes the first request to the server, that request will go to Cloudflare.
Cloudflare doesn't have the JavaScript file, so Cloudflare will forward that
request to the server. The server will send that JavaScript file to Cloudflare.
Cloudflare will cache this JavaScript file and serve the JavaScript file to the
user.</p>
<p>Two seconds later, another user from a different part of Paris hits the same
webpage. This time, Cloudflare has a cached copy of the JavaScript file and
Cloudflare will serve the JavaScript from its server in Paris.</p>
<p>This has two benefits. The first benefit is that the user gets to see the
webpage really fast. The second benefit is that the server cost is a lot lower
since fewer requests need to be served by the server in Chicago.</p>
<h3>What all things Cloudflare can cache</h3>
<p>Cloudflare can only cache files that are stored on the domain that is proxied
through Cloudflare. If we have an image file that is stored on Amazon S3 and is
accessed via a direct S3 URL, then Cloudflare <strong>won't</strong> be able to cache it.</p>
<p>Cloudflare caches are based on file extensions, not MIME types.
<a href="https://developers.cloudflare.com/cache/about/default-cache-behavior/#default-cached-file-extensions">Here</a>
is the list of file extensions that will be cached by default. HTML is not on
this list.</p>
<p>Cloudflare allows us to cache other file types by setting up suitable
<a href="https://developers.cloudflare.com/cache/how-to/create-page-rules/">page rules</a>.</p>
<h2>3. Set appropriate Cache-Control headers in Rails</h2>
<p>When utilizing Cloudflare as a CDN, it's crucial to configure our Rails app to
serve assets with specific <code>Cache-Control</code> headers. This ensures optimal caching
behavior for resources.</p>
<p>The Rails app should serve assets with a header similar to the one given below.</p>
<pre><code>cache-control: public, max-age=31536000
</code></pre>
<p>The <code>cache-control</code> must be public, and <code>max-age</code> must be a positive value for
Cloudflare to
<a href="https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#default-cached-behavior">cache the resource</a>.</p>
<p>For this, we need to modify the Rails configuration file
<code>config/environments/production.rb</code>.</p>
<pre><code class="language-ruby">config.public_file_server.headers = {
  'Cache-Control' =&gt; 'public, max-age=31536000'
}
</code></pre>
<p>Here, <code>Cache-Control</code> is set to 'public', allowing any cache (including
Cloudflare) to store the resource. The <code>max-age</code> is specified as 31,536,000
seconds (one year), indicating how long the cache should retain the object
before considering it stale.</p>
<h2>4. Configure <code>asset_host</code> in Rails.</h2>
<p>We added the secrets for the <code>asset_host</code> in our Rails application's
<code>config/secrets.yml</code> file.</p>
<pre><code class="language-yml">production:
  asset_host: &lt;%= ENV[&quot;ASSET_HOST&quot;] %&gt;
</code></pre>
<p>We also added an environment variable called <code>ASSET_HOST</code> that contains our new
CDN host URL.</p>
<pre><code>ASSET_HOST=cdn.neetoX.com
</code></pre>
<p>Once this is done all asset URLs will point to <code>https://cdn.neetoX.com</code>.</p>
<h2>5. Verify changes</h2>
<p>Once all the setup is done, it can take a few hours for the changes to be
reflected. Hit the page and see the response header of <code>.js</code> file.</p>
<p>If the response header <code>cf-cache-status</code> had a value of <code>HIT</code>, then that means
Cloudflare is serving that asset from its cache.</p>
<p><img src="https://www.bigbinary.com/blog/images/images_used_in_blog/2023/using-cloudflare-as-cdn-for-rails-applications/browser-console-cache-status.png" alt="Viewing cache status in browser console"></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds path_params option to url_for]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/url-for-path-params-options"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_new_option_path_params_for_url_for_helper_method.jpg"/>
      <id>https://www.bigbinary.com/blog/url-for-path-params-options</id>
      <published>2023-11-07T00:00:00.000Z</published>
      <updated>2023-11-07T00:00:00.000Z</updated>
      <author>
        <name>Neenu Chacko</name>
        <uri>https://www.bigbinary.com/blog/authors/neenu-chacko</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_new_option_path_params_for_url_for_helper_method.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 adds new option path_params for url_for helper method]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/url-for-path-params-options"><![CDATA[<p>With Rails 7.1, the <code>url_for</code> helper now supports a new option called
<code>path_params</code>.</p>
<p>Prior to Rails 7.1, if you had your routes configured to be scoped under say,
<code>user_id</code> as shown below, you would have to explicitly specify the <code>user_id</code> in
every single place where you want to generate a link for the scoped routes, such
as when writing view files.</p>
<pre><code class="language-ruby">#routes.rb
Rails.application.routes.draw do
  root &quot;articles#index&quot;

  scope &quot;:user_id&quot; do
    get &quot;/articles&quot;, to: &quot;articles#index&quot;
    get &quot;/articles/:id&quot;, to: &quot;articles#show&quot;
  end

  get &quot;/categories&quot;, to: &quot;categories#index&quot;
end
</code></pre>
<pre><code class="language-html">&lt;!-- app/views/articles/index.html.erb --&gt;

&lt;a href=&quot;&lt;%= articles_path(user_id: @current_user.id) %&gt;&quot;&gt; Articles &lt;/a&gt;
</code></pre>
<p>This could be solved by updating <code>ApplicationController</code> and overwriting the
<code>default_url_options</code> method:</p>
<pre><code class="language-ruby"># application_controller.rb
class ApplicationController &lt; ActionController::Base
  def default_url_options
    { user_id: &quot;unique-id&quot; }
  end
end
</code></pre>
<p>The <code>default_url_options</code> method is used to overwrite and set default options
for all the methods based on <code>url_for</code>. However, this meant that all routes,
even those that did not belong to the <code>user_id</code> scope, would have the
<code>?user_id=unique-id</code> query param added to the end of them, resulting in the
following output:</p>
<pre><code class="language-ruby">articles_path # =&gt; /unique-id/articles
categories_path # =&gt; /categories?user_id=unique-id
</code></pre>
<p>Rails 7.1 fixes this issue with the addition of the <code>path_params</code> option. If you
pass a hash of parameters to this key, those parameters will only be used for
the named segments of the route. If they aren't used, they are discarded instead
of being appended to the end of the route as query params.</p>
<p>With this change, we can implement the <code>default_url_options</code> method as follows:</p>
<pre><code class="language-ruby">class ApplicationController &lt; ActionController::Base
  def default_url_options
    { path_params: { user_id: &quot;unique-id&quot; } }
  end
end
</code></pre>
<p>The <code>url_for</code> helper method will now give you the following output:</p>
<pre><code class="language-ruby">articles_path # =&gt; /unique-id/articles
articles_path(user_id: &quot;test-id&quot;) # =&gt; /test-id/articles
categories_path # =&gt; /categories
categories_path(user_id: &quot;test-id&quot;) # =&gt; /categories
</code></pre>
<p>The view file can now be written as:</p>
<pre><code class="language-html">&lt;a href=&quot;&lt;%= articles_path %&gt;&quot;&gt; Articles &lt;/a&gt;
</code></pre>
<p>This is very useful in situations where you only want to add a required param
that is part of the route's URL without introducing unnecessary query params for
other routes.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/43770">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 optimizes the default SQLite3 configuration]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-comes-with-an-optimized-default-sqlite3-adapter-connection-configuration"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rail_comes_with_an_optimized_sqlite3_adapter_connection_configuration.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-comes-with-an-optimized-default-sqlite3-adapter-connection-configuration</id>
      <published>2023-10-31T00:00:00.000Z</published>
      <updated>2023-10-31T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rail_comes_with_an_optimized_sqlite3_adapter_connection_configuration.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 comes with an optimized default SQLite3 adapter connection configuration]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-comes-with-an-optimized-default-sqlite3-adapter-connection-configuration"><![CDATA[<p>The default SQLite3 Active Record adapter connection configuration has been
updated in Rails 7.1 to better tune it to work for modern Rails applications.</p>
<p>Before moving on to the configuration changes, let us understand what a PRAGMA is.
<a href="https://www.sqlite.org/pragma.html">PRAGMA</a> is a special kind of SQL
statement(only available in SQLite) which is used to query or manipulate various
aspects of the database's behaviour and configuration.</p>
<h2>Configuration Changes</h2>
<h3>1. <code>journal_mode</code> is now set use <code>WAL</code> instead of using the rollback journal</h3>
<p><a href="https://www.sqlite.org/pragma.html#pragma_journal_mode">journal_mode</a> is a
configuration setting in SQLite that determines how the database handles
transactions and maintains data integrity in the face of system crashes or
unexpected shutdowns. Previously, a brand new Rails application used to use the
rollback journal with <code>DELETE</code> journal mode. Now it uses a much more efficient
journaling method named Write-Ahead Logging (<code>WAL</code>). Let us understand the
difference between them.</p>
<p><strong>Rollback journal</strong>: In this implementation, the database engine first records
the original unchanged database content in a rollback journal, and the writes are
made directly to the database file. If the system crashes, the rollback journal
can be used to restore the database to the state it was in before the
transaction began. The issue with this approach is that a writer can alter the
database or readers can read from the database — but not both at the <strong>same</strong>
time.</p>
<p><strong>Write-ahead logging</strong>: In <code>WAL</code> mode, SQLite maintains a separate write-ahead
log. Instead of writing directly to the database file, changes are written to
the log first. When a reader needs a page of content, it first checks the <code>WAL</code>
to see if the page appears there, and if so it pulls in the latest copy of the
page in the <code>WAL</code>. If no copy of the page exists in the <code>WAL</code>, then the page is
read from the original database file. So this means readers and writers can work
together and there is no contention.</p>
<p><strong>How is the database file made up-to-date with the <code>WAL</code> file?</strong></p>
<p>SQLite routinely moves the <code>WAL</code> file transactions back into the database. This
process is called checkpointing. By default, SQLite does a checkpoint
automatically when the <code>WAL</code> file reaches a threshold size of 1000 pages.</p>
<p>If a system crash occurs, the last commit record has not been written to the
<code>WAL</code> file. Without the commit record, the new data is not considered valid, and
the database simply ignores it.</p>
<p><code>WAL</code> is a better choice for web applications because of the increased
concurrency it offers.</p>
<p><a href="https://fly.io/blog/sqlite-internals-wal/">Here</a> is a nice article on how <code>WAL</code>
works under the hood.</p>
<h3>2. <code>synchronous</code> is now set to <code>NORMAL</code> instead of <code>FULL</code></h3>
<p>The <a href="https://www.sqlite.org/pragma.html#pragma_synchronous">synchronous</a> pragma
controls how and when SQLite flushes content to disk. The two common options are
<code>FULL</code> and <code>NORMAL</code>, which map to “sync on every write” and “sync every 1000
written pages” respectively. <code>FULL</code> synchronous is very safe but slow. When
synchronous is <code>NORMAL</code>, the database engine will sync at the most critical
moments, but less often than in <code>FULL</code> mode. We trade an aggressive approach to
durability for speed.</p>
<p>The SQLite documentation suggests using <code>NORMAL</code> for applications running in
<code>WAL</code> mode.</p>
<h3>3. <code>journal_size_limit</code> is now capped at 64MB</h3>
<p>The
<a href="https://www.sqlite.org/pragma.html#pragma_journal_size_limit">journal_size_limit</a>
pragma tells SQLite how much of the write-ahead log data to keep in the on-disk
file. Previously, it was set to <code>-1</code> which means there was no limit set on the
journal size, which allows it to grow unbounded, thereby potentially affecting
read performance. Now it is capped at an appropriate size of 64MB.</p>
<h3>4. <code>cache_size</code> is now set to 8MB</h3>
<p>The <a href="https://www.sqlite.org/pragma.html#pragma_cache_size">cache_size</a> pragma
sets the maximum number of database disk pages that SQLite will hold in memory
at once, per open database file. The default value was -2000 i.e 2000 bytes.
Please note that SQLite interprets a negative value as a byte limit and positive
number as a page limit. Now the cache_size is set to 2000(pages) with a default
page size of 4096 bytes, which means the cache limit is ~8MB.</p>
<h3>5. <code>mmap_size</code> is now set to 128MB</h3>
<p>The <a href="https://www.sqlite.org/pragma.html#pragma_mmap_size">mmap_size</a> pragma sets
the maximum number of bytes that are set aside for memory-mapped I/O on a single
database. Let us first understand what memory-mapped I/O is.</p>
<p>Memory-mapped (mmap) I/O is an OS-provided feature that maps the contents of a
file on secondary storage into a program’s address space. The program then
accesses pages via pointers as if the file resided entirely in memory. The OS
transparently loads pages only when the program references them and
automatically evicts pages if memory fills up. The advantage of using mmap is that
it bypasses the step where we need to copy the pages from secondary to primary
storage, thereby making it faster.</p>
<p><strong>How is memory-mapped I/O implemented in SQLite?</strong></p>
<p>SQLite accesses and updates database files using <code>xRead()</code> and <code>xWrite()</code>
methods by default. These methods are typically implemented as <code>read()</code> and
<code>write()</code> system calls, which cause the OS to copy disk content between the
kernel buffer cache and user space. SQLite also has the option of accessing disk
content directly using memory-mapped I/O via the <code>xFetch()</code> and <code>xUnfetch()</code>
methods. Using the legacy <code>xRead()</code> method in SQLite, a page-sized heap memory
block is allocated, and the <code>xRead()</code> call copies the entire database page
content into this allocated memory. Whereas if memory-mapped I/O is enabled, it
calls the <code>xFetch()</code> method. The <code>xFetch()</code> method asks the operating system to
return a pointer to the requested page. If the requested page has been or can be
mapped into the application address space, then <code>xFetch()</code> returns a pointer to
that page for SQLite to use without having to copy anything. Skipping the copy
step is what makes memory-mapped I/O faster.</p>
<p>The <code>mmap_size</code> is the maximum number of bytes of the database file that SQLite
will try to map into the process address space at one time. Now it is set to
128MB.</p>
<p>With these changes, there is a considerable improvement in performance. That
being said, SQLite makes a strong case for single-node production applications,
as it is highly performant, especially when used in conjunction with NVMe disks.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/49349">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds multi-column ordering to batch processing]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-support-for-multi-column-ordering-in-activerecord-batches"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_support_for_multi_column_ordering.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-support-for-multi-column-ordering-in-activerecord-batches</id>
      <published>2023-10-03T00:00:00.000Z</published>
      <updated>2023-10-03T00:00:00.000Z</updated>
      <author>
        <name>Navaneeth D</name>
        <uri>https://www.bigbinary.com/blog/authors/navaneethsdk</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_support_for_multi_column_ordering.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 adds support for multi-column ordering in ActiveRecord::Batches.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-support-for-multi-column-ordering-in-activerecord-batches"><![CDATA[<p>In Rails 7.1, an enhancement has been introduced to
<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Batches.html"><code>ActiveRecord::Batches</code></a>
methods, related to models with composite primary keys. This update allows
developers to specify ascending or descending order for each key within a
composite primary key.</p>
<h3>Before Rails 7.1</h3>
<p>In Rails versions prior to 7.1, when batch processing records with a composite
primary key, like <code>id_1</code> and <code>id_2</code>, developers could use the <code>:asc</code> or <code>:desc</code>
argument to control the sorting order. However, there was a limitation in how
this sorting worked. When you specified the sorting order using <code>:asc</code> or
<code>:desc</code>, it affected both <code>id_1</code> and <code>id_2</code> simultaneously. In other words, if
you requested ascending order, both <code>id_1</code> and <code>id_2</code> would be sorted in
ascending order together. Similarly, if you requested descending order, both
<code>id_1</code> and <code>id_2</code> would be sorted in descending order together.</p>
<p>This limitation had practical implications, especially when you needed to sort
records by different criteria for each part of the composite primary key.</p>
<h3>After Rails 7.1</h3>
<p>With the new enhancement in Rails 7.1, developers can now select the sorting
order for each key within a composite primary key. Let's see this with an
example.</p>
<p>Consider a scenario where you have a <code>Product</code> model with a composite primary
key, <code>category_id</code> and <code>product_id</code>. You want to fetch products in descending
order of <code>category_id</code> and ascending order of <code>product_id</code>. With Rails 7.1, this
becomes straightforward:</p>
<pre><code class="language-ruby">class Product &lt; ActiveRecord::Base
  self.primary_key = [:category_id, :product_id]
end

# Retrieving products in descending order of
# category_id and ascending order of product_id
Product.find_each(order: [:desc, :asc]) do |product|
  # Your processing logic for each product goes here
end
</code></pre>
<p>The
<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each"><code>find_each</code></a>
method is a part of <code>ActiveRecord::Batches</code> and is used for efficient batch
processing of records from the database. It retrieves records in small batches,
reducing memory consumption and improving performance. The method takes an
optional <code>order</code> argument, which, as of Rails 7.1, can accept an array of
symbols to specify the sorting order for each key within the composite primary
key.</p>
<p>The enhancement for specifying sorting orders for composite primary keys is not
limited to <code>find_each</code>. It applies to other batch processing methods provided by
<code>ActiveRecord::Batches</code>, such
as<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_in_batches"> <code>find_in_batches</code></a>
and
<a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-in_batches"><code>in_batches</code></a>.
These methods allow you to retrieve and process records in batches efficiently,
just like <code>find_each</code>.</p>
<h3>Conclusion</h3>
<p>In Rails 7.1, the support for multiple-column ordering in batches for models
with composite primary keys brings more flexibility and control to your
application's data retrieval process. Now you can tailor the sorting of
composite keys to match your specific needs, providing a more powerful and
versatile toolset for your Rails development endeavors.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/48268">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds *_deliver callbacks to Action Mailer]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-_-deliver-callbacks-to-action-mailer"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/deliver_callbacks_for_action_mailer.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-_-deliver-callbacks-to-action-mailer</id>
      <published>2023-09-26T00:00:00.000Z</published>
      <updated>2023-09-26T00:00:00.000Z</updated>
      <author>
        <name>Calvin Chiramal</name>
        <uri>https://www.bigbinary.com/blog/authors/calvinchiramal</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/deliver_callbacks_for_action_mailer.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 has added \\deliver callbacks to Action Mailer. Let's understand the use of these callbacks with an example. Consider a case where you want to send an email after a user has signed up. When...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-_-deliver-callbacks-to-action-mailer"><![CDATA[<p>Rails 7.1 has added *_deliver callbacks to Action Mailer. Let's understand the
use of these callbacks with an example. Consider a case where you want to send
an email after a user has signed up.</p>
<pre><code class="language-ruby">class UserMailer &lt; ApplicationMailer
  default from: 'notifications@neeto.com'

  def welcome_email
    @user = params[:user]
    mail(to: @user.email, subject: 'Welcome to neeto')
  end
end
</code></pre>
<p>When the <code>mail</code> method is called in the above code, it just renders the mail
template from a view. The mail is actually not sent. The actual delivery may
happen synchronously or asynchronously. To send the mail, we need to call one of
many deliver methods.</p>
<pre><code class="language-ruby">UserMailer.with(user: @user).welcome_email.deliver_later
</code></pre>
<p>Before Rails 7.1, we did not have any callbacks around the deliver methods to
execute code around the delivery lifecycle. You would need to use
<a href="https://guides.rubyonrails.org/action_mailer_basics.html#intercepting-and-observing-emails">interceptors and observers</a>
to hook into the mail delivery lifecycle. Say, you need to send emails to only a
few allowed email addresses in the staging environment. You would need to use an
interceptor and register it to Action Mailer.</p>
<pre><code class="language-ruby"># interceptors/staging_email_interceptor.rb
module Interceptors
  class StagingEmailInterceptor
    def self.delivering_email(mail)
      if rails.env.staging? &amp;&amp; !allowed_emails.include?(mail.to)
        mail.perform_deliveries = false
      end
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># config/initializers/action_mailer.rb
ActionMailer::Base.register_interceptor(Interceptors::StagingEmailInterceptor)
</code></pre>
<p>The new <code>before_deliver</code> callback allows you to handle this situation without
using the Interceptors or Observers. You would just need to include the
following in <code>UserMailer</code>.</p>
<pre><code class="language-ruby">class UserMailer &lt; ApplicationMailer
  default from: 'notifications@neeto.com'
  before_deliver :filter_allowed_emails

  def welcome_email
    @user = params[:user]
    mail(to: @user.email, subject: 'Welcome to neeto')
  end

  private

    def filter_allowed_emails
      if rails.env.staging? &amp;&amp; !allowed_emails.include?(mail.to)
        mail.perform_deliveries = false
      end
    end
end
</code></pre>
<p>Similarly, suppose you want to update the <code>mail_delivered_at</code> attribute of the
<code>user</code> instance, you would have to use an observer and register it like so:</p>
<pre><code class="language-ruby"># observers/staging_email_interceptor.rb
module Observers
  class SetDeliveredAtObserver
    def self.delivered_email(mail)
      user = User.find_by(email: mail.to)
      user.update(mail_delivered_at: mail.date)
    end
  end
end
</code></pre>
<pre><code class="language-ruby"># config/initializers/action_mailer.rb
ActionMailer::Base.register_interceptor(Interceptors::StagingEmailInterceptor)
ActionMailer::Base.register_observer(Observers::SetDeliveredAtObserver)
</code></pre>
<p>With the new <code>after_deliver</code> callback this becomes as simple as defining a
method in <code>UserMailer</code>.</p>
<pre><code class="language-ruby">class UserMailer &lt; ApplicationMailer
  default from: 'notifications@neeto.com'
  before_deliver :filter_allowed_emails
  after_deliver :set_delivered_at

  def welcome_email
    @user = params[:user]
    mail(to: @user.email, subject: 'Welcome to neeto')
  end

  private

    def filter_allowed_emails
      if rails.env.staging? &amp;&amp; !allowed_emails.include?(mail.to)
        mail.perform_deliveries = false
      end
    end

    def set_delivered_at
      @user.update(mail_delivered_at: mail.date)
    end
end
</code></pre>
<p>An important thing to keep in mind is the order of execution of the callbacks.</p>
<ul>
<li>before_action</li>
<li>after_action</li>
<li>before_deliver</li>
<li>after_deliver</li>
</ul>
<p>This makes sense as the deliver callbacks only wrap around the deliver methods
while the action callbacks wrap around the mail render method.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/47630">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds options to ActiveRecord::Relation#explain]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-options-to-activerecord-relation-explain"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_detailed_query_plan_analysis.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-options-to-activerecord-relation-explain</id>
      <published>2023-07-04T00:00:00.000Z</published>
      <updated>2023-07-04T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_detailed_query_plan_analysis.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 enables detailed query plan analysis with options in ActiveRecord::Relation#explain.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-options-to-activerecord-relation-explain"><![CDATA[<p>In Rails 7.1, an enhancement has been introduced to the
<a href="https://apidock.com/rails/ActiveRecord/Relation/explain"><code>ActiveRecord::Relation#explain</code></a>
method. This enhancement allows us to obtain detailed query plan analysis by
specifying options for the <code>explain</code> output.</p>
<h3>Understanding the EXPLAIN method</h3>
<p>Before diving into the new options available in Rails 7.1, let's quickly recap
the purpose and usage of the <code>explain</code> method in Active Record. The <code>explain</code>
method is used to retrieve the execution plan of an SQL query chosen by the
database optimizer. It provides insight into how the database intends to execute
the query, including the sequence of operations, indexes used, and estimated
costs.</p>
<p>By analyzing the query plan, we can identify potential performance bottlenecks,
optimize database schema design, and fine-tune queries for better efficiency.
However, prior to Rails 7.1, the level of detail available in the explain output
was limited.</p>
<h3>Detailed query plan analysis with options</h3>
<p>In Rails 7.1, the <code>explain</code> method accepts options, enabling us to customize the
output and obtain a more detailed query plan analysis. It's important to note
that these options are the same ones that are already available in native SQL.
Options may vary depending on the database used. In this blog post, we will
focus on examples using PostgreSQL. Let us take a look at some of the available
options.</p>
<h4>ANALYZE</h4>
<p>The <code>analyze</code> option causes the statement to be actually executed, not only
planned. Then actual run time statistics are added to the display, including the
total elapsed time expended within each plan node and the total number of rows
it actually returned.</p>
<pre><code class="language-ruby">Service.where('age &gt; ?', 25).joins(:user).explain(:analyze)
</code></pre>
<pre><code class="language-sql">EXPLAIN (ANALYZE) SELECT &quot;services&quot;.* FROM &quot;services&quot; INNER JOIN &quot;users&quot; ON &quot;users&quot;.&quot;id&quot; = &quot;services&quot;.&quot;user_id&quot; WHERE (age &gt; 25)
                          QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=15.50..29.42 rows=103 width=232) (actual time=0.015..0.017 rows=0 loops=1)
   Hash Cond: (services.user_id = users.id)
   -&gt;  Seq Scan on services  (cost=0.00..13.10 rows=310 width=232) (actual time=0.012..0.012 rows=0 loops=1)
   -&gt;  Hash  (cost=14.12..14.12 rows=110 width=8) (never executed)
         -&gt;  Seq Scan on users  (cost=0.00..14.12 rows=110 width=8) (never executed)
               Filter: (age &gt; 25)
 Planning Time: 0.915 ms
 Execution Time: 0.472 ms
</code></pre>
<h4>VERBOSE</h4>
<p>The <code>verbose</code> option provides a more detailed output by including additional
information about each step in the execution plan. This includes statistics,
cost estimates, and other relevant details.</p>
<pre><code class="language-ruby">Service.where('age &gt; ?', 25).joins(:user).explain(:verbose)
</code></pre>
<pre><code class="language-sql">EXPLAIN (VERBOSE) SELECT &quot;services&quot;.* FROM &quot;services&quot; INNER JOIN &quot;users&quot; ON &quot;users&quot;.&quot;id&quot; = &quot;services&quot;.&quot;user_id&quot; WHERE (age &gt; 25)
                          QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=15.50..29.42 rows=103 width=232)
   Output: services.id, services.user_id, services.provider, services.uid, services.access_token, services.access_token_secret, services.refresh_token, services.expires_at, services.auth, services.created_at, services.updated_at
   Inner Unique: true
   Hash Cond: (services.user_id = users.id)
   -&gt;  Seq Scan on public.services  (cost=0.00..13.10 rows=310 width=232)
         Output: services.id, services.user_id, services.provider, services.uid, services.access_token, services.access_token_secret, services.refresh_token, services.expires_at, services.auth, services.created_at, services.updated_at
   -&gt;  Hash  (cost=14.12..14.12 rows=110 width=8)
         Output: users.id
         -&gt;  Seq Scan on public.users  (cost=0.00..14.12 rows=110 width=8)
               Output: users.id
               Filter: (users.age &gt; 25)
(11 rows)
</code></pre>
<p>For more options available in PostgreSQL's EXPLAIN command, you can refer to the
<a href="https://www.postgresql.org/docs/current/sql-explain.html">official documentation</a>.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/47043">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds ActiveRecord::Base::normalizes]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-activerecord-base-normalizes"/>
      <link rel="enclosure" type="image/jpeg" href="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_activerecord_base_normalizes.jpg"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-activerecord-base-normalizes</id>
      <published>2023-05-09T00:00:00.000Z</published>
      <updated>2023-05-09T00:00:00.000Z</updated>
      <author>
        <name>Abhijith Sheheer</name>
        <uri>https://www.bigbinary.com/blog/authors/abhijithsheheer</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/rails_adds_activerecord_base_normalizes.jpg" width="1200" height="627"/>
      <summary type="text"><![CDATA[Rails 7.1 adds ActiveRecord::Base::normalizes.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-activerecord-base-normalizes"><![CDATA[<p>Rails 7.1 has introduced a new method in Active Record that can be used to
declare normalizations for attribute values. This can be especially useful for
sanitizing user input, ensuring consistent formatting, or cleaning up data from
external sources.</p>
<p>Before Rails 7.1, you could normalize attributes using <code>before_save</code> callback.</p>
<pre><code class="language-ruby">model User &lt; ApplicationRecord
  before_save :downcase_email, if :email_present?

  private

    def email_present?
      email.present?
    end

    def downcase_email
      email.downcase!
    end
end
</code></pre>
<p>In Rails 7.1, you can refactor the above to the code below.</p>
<pre><code class="language-ruby">model User &lt; ApplicationRecord
  normalizes :email, with: -&gt; email { email.downcase }
end
</code></pre>
<p>The normalization is applied when the attribute is assigned or updated, and the
normalized value will be persisted to the database. The normalization is also
applied to the corresponding keyword argument of finder methods. This allows a
record to be created and later queried using unnormalized values.</p>
<p>By default, normalization is not applied to <code>nil</code> values. To normalize
<code>nil</code> value, you can enable it using <code>:apply_to_nil</code> option.</p>
<pre><code class="language-ruby">model User &lt; ApplicationRecord
  normalizes :user_name, with:
    -&gt; user_name { user_name.parameterize.underscore }

  normalizes :email, with: -&gt; { _1.strip.downcase }

  normalizes :profile_image, with:
    -&gt; profile_image {
      profile_image.present? ? URI.parse(profile_image).to_s :
      &quot;https://source.boringavatars.com/beam&quot; },
    apply_to_nil: true
end
</code></pre>
<pre><code class="language-ruby"># rails console
&gt;&gt; User.create!(user_name: &quot;Eve Smith&quot;, email: &quot;eveSmith@EXAMPLE.com&quot;)

#&lt;User:0x000000010b757090 id: 1, user_name: &quot;eve_smith&quot;, profile_image:&quot;https://source.boringavatars.com/beam&quot;, email: &quot;evesmith@example.com&quot;, created_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00, updated_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00&gt;

&gt;&gt; user = User.find_by!(email: &quot;EVESMITH@example.COM&quot;)
&gt;&gt; user.email # =&gt; &quot;evesmith@example.com&quot;

&gt;&gt; User.exists?(email: &quot;EveSmith@Example.COM&quot;)          # =&gt; true
</code></pre>
<p>If a user's email was already stored in the database before the normalization
statement was added to the model, the email will not be retrieved in the
normalized format.</p>
<p>This is because in the database it's stored in mixed case, that is, without the
normalization. If you have legacy data, you can normalize it explicitly using
the <code>Normalization#normalize_attribute</code> method.</p>
<pre><code class="language-ruby"># rails console
&gt;&gt; legacy_user = User.find(1)
&gt;&gt; legacy_user.email  # =&gt; &quot;adamSmith@EXAMPLE.com&quot;
&gt;&gt; legacy_user.normalize_attribute(:email)
&gt;&gt; legacy_user.email  # =&gt; &quot;adamsmith@example.com&quot;
&gt;&gt; legacy_user.save
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/43945">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 logs the caller that enqueued a job]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-support-for-logging-background-job-enqueue-callers"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-support-for-logging-background-job-enqueue-callers.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-support-for-logging-background-job-enqueue-callers</id>
      <published>2023-04-18T00:00:00.000Z</published>
      <updated>2023-04-18T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-support-for-logging-background-job-enqueue-callers.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 adds support for logging background job enqueue callers.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-support-for-logging-background-job-enqueue-callers"><![CDATA[<p>Rails 7.1 has introduced a new option in Active Job that allows us to add
support for logging background job enqueue callers. It provides information
about the location from where a job was enqueued, which can be immensely helpful
during debugging. The following is an example of what the log output looks like:</p>
<pre><code class="language-ruby">[ActiveJob] Enqueued NotifySubscribersJob (Job ID: 5945980f-303e-4c3f-af5b-e22be170f7c5) to Sidekiq(default)
[ActiveJob] ↳ app/models/post.rb:14 in `notify_subscribers`
</code></pre>
<p>By examining the logs above, we can determine that the <code>NotifySubscribersJob</code>
was enqueued from the <code>notify_subscribers</code> method in the <code>Post</code> model, providing
us with a clear picture of the job's origin.</p>
<p>To enable this functionality, we need to set the following configuration in
<code>config/environments/development.rb</code>.</p>
<pre><code class="language-rb">config.active_job.verbose_enqueue_logs = true
</code></pre>
<p>However, Rails 7.1 ships with this configuration set by default.</p>
<p>It's important to note that using verbose enqueue logs in a production environment
is not recommended, as it uses the
<a href="https://apidock.com/ruby/Kernel/caller">Kernel#caller</a> method to retrieve the
execution stack, which can be slow.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/47839">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds ActiveJob.perform_all_later]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-activejob-perform-all-later"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-activejob-perform-all-later.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-activejob-perform-all-later</id>
      <published>2023-02-28T00:00:00.000Z</published>
      <updated>2023-02-28T00:00:00.000Z</updated>
      <author>
        <name>Vishnu M</name>
        <uri>https://www.bigbinary.com/blog/authors/vishnu-m</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-activejob-perform-all-later.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 adds ActiveJob.perform_all_later to enqueue multiple jobs at once.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-activejob-perform-all-later"><![CDATA[<p>Rails 7.1 adds <code>ActiveJob.perform_all_later</code> to enqueue multiple jobs at once.
This method accepts an array of job instances. Just like Active Record bulk
methods, <code>perform_all_later</code> doesn't run any callbacks.</p>
<p>For example, if we want to send a welcome email to multiple users, we can do
this:</p>
<pre><code class="language-ruby">welcome_email_jobs = users.map do |user|
  WelcomeEmailJob.new(user)
end

ActiveJob.perform_all_later(welcome_email_jobs)
</code></pre>
<p>The benefit of doing it this way, rather than looping through the user records
and using <code>perform_later</code> is that <code>perform_all_later</code> cuts down on the number of
round-trips to the queue datastore. That means reducing Redis round-trip latency
if the queuing backend is Sidekiq. Whereas if we're using a queuing backend like
<a href="https://github.com/bensheldon/good_job">GoodJob</a>, which is backed by Postgres,
<code>perform_all_later</code> will enqueue all the jobs using a single INSERT statement
which is more performant.</p>
<p>Please note that if the queuing backend doesn't support bulk enqueuing,
<code>perform_all_later</code> will fallback to enqueuing each job individually.</p>
<p>Active Job is designed to abstract away the differences between different job
processing libraries and to provide a unified interface. It is made possible
using
<a href="https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html">adapters</a>.</p>
<p>The popular queuing backend Sidekiq already has a
<a href="https://github.com/sidekiq/sidekiq/wiki/Bulk-Queueing">push_bulk</a> method.
Hence, the author of this pull request has made
<a href="https://github.com/rails/rails/pull/46603/files#diff-e1ee600ca5fd100da20810ef5acbab89546064fc323c7b1e6bdb6ed5e681a9d3">changes</a>
to the Sidekiq adapter so that <code>perform_all_later</code> uses the <code>push_bulk</code> method
from Sidekiq.</p>
<p>Recently, GoodJob has also added support for bulk enqueuing in this
<a href="https://github.com/bensheldon/good_job/pull/790">pull request</a>.</p>
<p>Practically, <code>ActiveJob.perform_all_later</code> is only useful if we want to push
thousands of jobs at once. For a smaller number of jobs, the performance
benefits will not be significant.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/46603">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds adapter option to disallow foreign keys]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-adapter-option-to-disallow-foregin-keys"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-adapter-option-to-disallow-foregin-keys.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-adapter-option-to-disallow-foregin-keys</id>
      <published>2023-02-21T00:00:00.000Z</published>
      <updated>2023-02-21T00:00:00.000Z</updated>
      <author>
        <name>Aditya Bhutani</name>
        <uri>https://www.bigbinary.com/blog/authors/adityabhutani</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-adapter-option-to-disallow-foregin-keys.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[There are times when an application can choose not to use foreign keys. Data transfer between services is a tricky thing. When we are importing the data, we need to import the data in the right order...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-adapter-option-to-disallow-foregin-keys"><![CDATA[<p>There are times when an application can choose <strong>not</strong> to use foreign keys. Data
transfer between services is a tricky thing. When we are importing the data, we
need to import the data in the right order if &quot;foreign keys&quot; are enabled. In such
cases one might want to not enforce &quot;foreign keys&quot; and load the data and then
add &quot;foreign keys&quot; constraint back.</p>
<p>Situations like this can be handled using migrations. Using migrations, we can
disable &quot;foreign keys&quot;. However, this would mean writing migrations for all the
tables and removing all &quot;foreign keys&quot;. This could be cumbersome.</p>
<pre><code class="language-ruby">def change
  create_table :authors do |t|
    t.string :name
    t.timestamps
  end

  create_table :books do |t|
    t.belongs_to :author, foreign_key: false
    t.datetime :published_at
    t.timestamps
  end
end
</code></pre>
<p>Rails 7.1 adds an option to database.yml that enables skipping foreign key
constraints usage even if the underlying database supports them and solves the
above issue of writing migrations to disable foreign keys for all the tables.</p>
<pre><code class="language-yaml">development:
  &lt;&lt;: *default
  database: db/development.sqlite3
  foreign_keys: false
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/45301">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 allows aliased attributes in insert_all]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-allows-using-aliased-attributes-with-insert_all-upsert_all"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-allows-using-aliased-attributes-with-insert_all-upsert_all.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-allows-using-aliased-attributes-with-insert_all-upsert_all</id>
      <published>2023-01-11T00:00:00.000Z</published>
      <updated>2023-01-11T00:00:00.000Z</updated>
      <author>
        <name>Aditya Bhutani</name>
        <uri>https://www.bigbinary.com/blog/authors/adityabhutani</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-allows-using-aliased-attributes-with-insert_all-upsert_all.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 allows using aliased attributes with insert_all/upsert_all]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-allows-using-aliased-attributes-with-insert_all-upsert_all"><![CDATA[<p>Before Rails 6 we had update_all and delete_all. Rails 6 added insert_all and
upsert_all.</p>
<p><a href="https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all">insert_all</a>
: This method can insert multiple records with a single SQL INSERT statement.</p>
<p><a href="https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-upsert_all">upsert_all</a>
: This method updates the records if they exist or inserts them into the
database with a single SQL INSERT statement.</p>
<p><a href="https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods/ClassMethods.html#method-i-alias_attribute">alias_attribute</a>
: Allows you to make aliases for attributes, which include a getter, a setter,
and a predicate.</p>
<p>Rails 7.1 allows the use of aliased attributes with <code>insert_all</code> and <code>upsert_all</code>.
Previously whenever we added an alias for an attribute, we couldn't use it for
insert_all and upsert_all.</p>
<pre><code class="language-ruby">class User &lt; ApplicationRecord
  # database column is `name`. `full_name` is the alias.
  alias_attribute :full_name, :name
end
</code></pre>
<h3>Before Rails 7.1</h3>
<pre><code class="language-ruby"># rails console
&gt; User.insert_all [{ full_name: &quot;John Doe&quot; }]
=&gt; # unknown attribute 'full_name' for User. (ActiveModel::UnknownAttributeError)
</code></pre>
<h3>After Rails 7.1</h3>
<pre><code class="language-ruby"># rails console
&gt; User.insert_all [{ full_name: &quot;Jane Doe&quot; }]
=&gt; # User Insert

&gt; User.last
=&gt; #&lt;User id: 6, name: &quot;Jane Doe&quot;, created_at: Mon, 21 Nov 2022 18:07:11.349000000 UTC +00:00, updated_at: Mon, 21 Nov 2022 18:07:11.349000000 UTC +00:00&gt;
</code></pre>
<p>Now we can use the alias attribute with <code>insert_all</code> and <code>upsert_all</code>.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/45036">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[How we upgraded from Rails 6 to Rails 7]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/how-we-upgraded-from-rails-6-to-rails-7.png"/>
      <id>https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7</id>
      <published>2022-09-20T00:00:00.000Z</published>
      <updated>2022-09-20T00:00:00.000Z</updated>
      <author>
        <name>Abhishek T</name>
        <uri>https://www.bigbinary.com/blog/authors/abhishekt98</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/how-we-upgraded-from-rails-6-to-rails-7.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Recently, we upgraded all neeto products to Rails 7 using Rails upgrade guide. Here are the issues we faced during the upgrade. Migrating to Active Record Encryption This was the biggest challenge we...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/how-we-upgraded-from-rails-6-to-rails-7"><![CDATA[<p>Recently, we upgraded all <a href="https://www.neeto.com/">neeto</a> products to Rails 7
using
<a href="https://guides.rubyonrails.org/upgrading_ruby_on_rails.html">Rails upgrade guide</a>.</p>
<p>Here are the issues we faced during the upgrade.</p>
<h3>Migrating to Active Record Encryption</h3>
<p>This was the biggest challenge we faced during the upgrade. For encrypting
columns, we had used the
<a href="https://github.com/attr-encrypted/attr_encrypted">attr_encrypted</a> gem. However
Rails 7 came with
<a href="https://guides.rubyonrails.org/active_record_encryption.html">Active Record Encryption</a>.
So we needed to decrypt the records in the production database and encrypt them
using Active Record Encryption. We found that &quot;attr_encrypted&quot; gem was
incompatible with Rails 7. So the only option was to remove the &quot;attr_encrypted&quot;
gem and decrypt the records using a script. We used the following method to
decrypt the records.</p>
<pre><code class="language-rb">def decrypted_attribute(attribute_name, record)
  value = record.send(attribute_name)
  return if value.blank?

  value = Base64.decode64(value)

  cipher = OpenSSL::Cipher.new(&quot;aes-256-gcm&quot;)
  cipher.decrypt

  cipher.key = Rails.application.secrets.attr_encrypted[:encryption_key]
  cipher.iv = Base64.decode64(record.send(:&quot;#{attribute_name}_iv&quot;))

  cipher.auth_tag = value[-16..]
  cipher.auth_data = &quot;&quot;

  cipher.update(value[0..-17]) + cipher.final
end
</code></pre>
<h3>Broken images in Active Storage</h3>
<p>After the upgrade, we started getting broken images in some places. This
happened for Active Storage links embedded in Rich Text. After some debugging, we
found that we were getting incorrect Active Storage links because of a change in
the key generation algorithm. The following configuration was loading images
using the old algorithm.</p>
<pre><code class="language-rb">config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
</code></pre>
<p>Since the new algorithm provides more security, we decided to migrate the links
instead of using the old algorithm. We used the following code to migrate the
old link to the new valid link.</p>
<pre><code class="language-rb"># Usage:
# text_with_new_links = ActiveStorageKeyConverter.new(text_with_old_links).process
# If no links are there to replace, the original text will be returned as it is.

class ActiveStorageKeyConverter
  def initialize(text)
    @text = text
  end

  def process
    replace(@text)
  end

  private
    def convert_key(id)
      verifier_name = &quot;ActiveStorage&quot;
      key_generator =  ActiveSupport::KeyGenerator.new(Rails.application.secrets.
      secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1)
      key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
      secret = key_generator.generate_key(verifier_name.to_s)
      verifier = ActiveSupport::MessageVerifier.new(secret)

      ActiveStorage::Blob.find_by_id(verifier.verify(id, purpose: :blob_id))
      .try(:signed_id) rescue nil
    end

    def replace(text)
      keys = text.scan(URI.regexp).flatten.select{|x|
      x.to_s.include? (&quot;rails/active_storage&quot;)}.map{|x| x.split(&quot;/&quot;)[-2]}
      keys.each do |key|
        new_key = convert_key(key)
        text = text.gsub(key, new_key) if new_key
      end
      text
    end
end
</code></pre>
<p>Following one time rake task was used to update the Active Storage links in the
<code>content</code> column of <code>Task</code> model:</p>
<pre><code class="language-rb">desc &quot;Update active storage links embedded in rich text to support in rails 7&quot;
task migrate_old_activestorage_links: :environment do

  table_colum_map = {
    &quot;Task&quot; =&gt; &quot;content&quot;,
  }
  match_term = &quot;%rails/active_storage%&quot;

  table_colum_map.each do |model_name, column_name|
    model_name.to_s.constantize.where(&quot;#{column_name} ILIKE ?&quot;, match_term).find_each do|row|
      row.update_column(column_name, ActiveStorageKeyConverter.new(row[column_name]).process)
    end
  end
end
</code></pre>
<h3>Test failures with the mailer jobs</h3>
<p>After upgrading to Rails 7, tests related to mailers started to fail. This was
because the mailer jobs were enqueued in the <code>default</code> queue instead of
<code>mailers</code>. We fixed this by adding the following configuration.</p>
<pre><code class="language-rb">config.action_mailer.deliver_later_queue_name = :mailers
</code></pre>
<h3>Autoloading during initialization failed</h3>
<p>After the upgrade, if we startthe Rails sever then we were getting the following
error.</p>
<pre><code>$ rails s
=&gt; Booting Puma
=&gt; Rails 7.0.3.1 application starting in development
=&gt; Run `bin/rails server --help` for more startup options
Exiting
/Users/BB/Neeto/neeto_commons/lib/neeto_commons/initializers/session_store.rb:13:in
`session_store': uninitialized constant #&lt;Class:NeetoCommons::Initializers&gt;::ServerSideSession     (NameError)

    ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
                                                                ^^^^^^^^^^^^^^^^^
   from /Users/BB/Neeto/neeto-planner-web/config/initializers/common.rb:10:in `&lt;main&gt;'
</code></pre>
<p>That error was coming from our internal <code>neeto-commons</code> initializer called
<code>session_store.rb</code>. The code looked like this.</p>
<pre><code class="language-rb">#session_store.rb

module NeetoCommons
  module Initializers
    class &lt;&lt; self
      def session_store
        Rails.application.config.session_store :active_record_store,
          key: Rails.application.secrets.session_cookie_name, expire_after: 10.years.to_i

        ActiveRecord::SessionStore::Session.table_name = &quot;server_side_sessions&quot;
        ActiveRecord::SessionStore::Session.primary_key = &quot;session_id&quot;
        ActiveRecord::SessionStore::Session.serializer = :json
        ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
      end
    end
  end
end
</code></pre>
<p>In order to fix the issue we had to put the last statement in a block like shown
below.</p>
<pre><code class="language-rb">Rails.application.config.after_initialize do
 ActionDispatch::Session::ActiveRecordStore.session_class = ServerSideSession
end
</code></pre>
<h3>Missing template error with pdf render</h3>
<p>After the Rails 7 upgrade the following test started failing.</p>
<pre><code class="language-rb">def test_get_task_pdf_download_success
  get api_v1_project_section_tasks_download_path(@project.id, @section, @task, format: :pdf)

  assert_response :ok

  assert response.body.starts_with? &quot;%PDF-1.4&quot;
  assert response.body.ends_with? &quot;%EOF\n&quot;
end
</code></pre>
<p>The actual error is <code>Missing template api/v1/projects/tasks/show.html.erb</code>.</p>
<p>In order to fix it we renamed the file name from <code>/tasks/show.html.erb</code> to
<code>/tasks/show.pdf.erb</code>. Similarly we changed the layout from
<code>/layouts/pdf.html.erb</code> to <code>/layouts/pdf.pdf.erb</code>.</p>
<p>Initially the controller code looked like this.</p>
<pre><code class="language-rb">format.pdf do
  render \
    template: &quot;api/v1/projects/tasks/show.html.erb&quot;
    pdf: pdf_file_name,
    layout: &quot;pdf.html.erb&quot;
end
</code></pre>
<p>After the change the code looked like this.</p>
<pre><code class="language-rb">format.pdf do
  render \
    template: &quot;api/v1/projects/tasks/show&quot;,
    pdf: pdf_file_name,
    layout: &quot;pdf&quot;
end
</code></pre>
<h3>Open Redirect protection</h3>
<p>After the Rails 7 upgrade the following test started failing.</p>
<pre><code class="language-rb">def test_that_users_are_redirected_to_error_url_when_invalid_subdomain_is_entered
  invalid_subdomain = &quot;invalid-subdomain&quot;

  auth_subdomain_url = URI(app_secrets.auth_app[:url].gsub(
   app_secrets.app_subdomain, invalid_subdomain))

  auth_app_url = app_secrets.auth_app[:url]

  host! test_domain(invalid_subdomain)
  get &quot;/&quot;

  assert_redirected_to auth_app_url
end
</code></pre>
<p>In the test we are expecting the application to redirect to <code>auth_app_url</code> but
we are getting <code>UnsafeRedirectError</code> error for open redirections. In Rails 7 the
new Rails defaults
<a href="https://api.rubyonrails.org/v7.0.3.1/classes/ActionController/Redirecting.html#method-i-redirect_to-label-Open+Redirect+protection">protects</a>
applications against the
<a href="https://cwe.mitre.org/data/definitions/601.html">Open Redirect Vulnerability</a>.</p>
<p>To allow any external redirects we can pass <code>allow_other_host: true</code>.</p>
<pre><code class="language-rb">redirect_to &lt;External URL&gt;, allow_other_host: true
</code></pre>
<p>Since we use open redirection in many places we disabled this protection
globally.</p>
<pre><code class="language-rb">config.action_controller.raise_on_open_redirects = false
</code></pre>
<h3>Argument Error for Mailgun signing key</h3>
<p>After the upgrade, we started getting the following error in production.</p>
<pre><code>&gt;&gt; ArgumentError: Missing required Mailgun Signing key. Set action_mailbox.mailgun_signing_key
in your application's encrypted credentials or provide the MAILGUN_INGRESS_SIGNING_KEY
environment variable.

</code></pre>
<p>Before Rails 7, we used the <code>MAILGUN_INGRESS_API_KEY</code> environment variable to
set up the
<a href="https://guides.rubyonrails.org/action_mailbox_basics.html#mailgun">Mailgun signing key</a>
. In Rails 7, that is changed to <code>MAILGUN_INGRESS_SIGNING_KEY</code>. So we renamed
the environment variable to fix the problem.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Three case studies of debugging redis running out of memory]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/debugging-redis-memory-issue"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/debugging-redis-memory-issue.png"/>
      <id>https://www.bigbinary.com/blog/debugging-redis-memory-issue</id>
      <published>2022-09-12T00:00:00.000Z</published>
      <updated>2022-09-12T00:00:00.000Z</updated>
      <author>
        <name>Unnikrishnan KP</name>
        <uri>https://www.bigbinary.com/blog/authors/unni</uri>
      </author>
      <category term="Ruby"/>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/debugging-redis-memory-issue.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[In this blog, we will discuss three separate case studies of Redis running out of memory. All three case studies have videos demonstrating how the debugging was done. All three videos were prepared...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/debugging-redis-memory-issue"><![CDATA[<p>In this blog, we will discuss three separate case studies of Redis running
out of memory. All three case studies have videos demonstrating how the
debugging was done.</p>
<p>All three videos were prepared for my team members to show how to go about
debugging. The videos are being presented &quot;as it was recorded&quot;.</p>
<h2>First Case Study</h2>
<p>When a job fails in <a href="https://sidekiq.org/">Sidekiq</a>, Sidekiq puts that job in
<a href="https://github.com/mperham/sidekiq/wiki/API#retries">RetrySet</a> and retries that
job until the job succeeds or the job reaches the maximum number of retries. By
default the maximum number of retries is 25. If a job fails 25 times, then that
job is moved to the <a href="https://github.com/mperham/sidekiq/wiki/API#dead">DeadSet</a>.
By default, Sidekiq will store up to 10,000 jobs in the deadset.</p>
<p>We had a situation where Redis was running out of memory. Here is how the
debugging was done.</p>
<p><a href="https://www.youtube.com/watch?v=dg-K_IoT-x0"><img src="https://img.youtube.com/vi/dg-K_IoT-x0/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=dg-K_IoT-x0">Watch on YouTube</a></p>
<h3>How to inspect the deadset</h3>
<pre><code class="language-ruby">ds = Sidekiq::DeadSet.new
ds.each do |job|
  puts &quot;Job #{job['jid']}: #{job['class']} failed at #{job['failed_at']}&quot;
end
</code></pre>
<p>Running the following to view the latest entry to the dataset:</p>
<pre><code class="language-ruby">ds.first
ds.count
</code></pre>
<p>To see the memory usage following commands were executed in the Redis console.</p>
<pre><code>&gt; memory usage dead
30042467

&gt; type dead
zset
</code></pre>
<p>As discussed in the video large amount of payload was being sent. This is not
the right way to send data to the worker. Ideally, some sort of <code>id</code> should be
sent to the worker and the worker should be able to get the necessary data from
the database based on the received <code>id</code>.</p>
<h4>References</h4>
<ol>
<li><a href="https://github.com/mperham/sidekiq/discussions/5011">How to increase the number of jobs in the Sidekiq deadset or disable deadset</a></li>
<li><a href="https://github.com/mperham/sidekiq/blob/main/lib/sidekiq/job_retry.rb#L71">Maximum number of job retries in Sidekiq</a></li>
<li><a href="https://github.com/mperham/sidekiq/blob/a89d84509c569a78882e24e0e28913a22c9311f5/lib/sidekiq.rb#L38">Maximum number of jobs in Sidekiq Deadset</a></li>
</ol>
<h2>Second case study</h2>
<p>In this case, the Redis instance of <a href="https://www.neeto.com/neetochat/">neetochat</a>
was running out of memory. The Redis instance had 50MB capacity, but we were
getting the following error.</p>
<pre><code>ERROR: heartbeat: OOM command not allowed when used memory &gt; 'maxmemory'.
</code></pre>
<p>We were pushing too many geo info records to Redis and that caused the memory to
fill up. Here is the video capturing the debugging session.</p>
<p><a href="https://www.youtube.com/watch?v=oz7Pcbc_zxM"><img src="https://img.youtube.com/vi/oz7Pcbc_zxM/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=oz7Pcbc_zxM">Watch on YouTube</a></p>
<p>The following are the commands executed while debugging.</p>
<pre><code>&gt; ping
PONG

&gt; info

&gt; info memory

&gt; info keyspace

&gt; keys *failed*

&gt; keys *process*

&gt; keys *geocoder*

&gt; get getocoder:http://ipinfo.io/41.174.30.55/geo?
</code></pre>
<h2>Third Case Study</h2>
<p>In this case, the authentication service of <a href="https://www.neeto.com/">Neeto</a> was
failing because of memory exhaustion.</p>
<p>Here the number of keys was limited, but the payload data was huge and all that
payload data was hogging the memory. Here is the video capturing the debugging
session.</p>
<p><a href="https://www.youtube.com/watch?v=a_Ygbcreokw"><img src="https://img.youtube.com/vi/a_Ygbcreokw/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=a_Ygbcreokw">Watch on YouTube</a></p>
<p>The following are the commands executed while debugging.</p>
<pre><code>&gt; ping

&gt; info keyspace
db0:keys=106, expires=86,avg_ttl=1233332728573

&gt; key * (to see all the keys)
</code></pre>
<p>The last command listed all 106 keys. Next, we needed to find how much memory
each of these keys are using. For that, the following commands were executed.</p>
<pre><code>&gt; memory usage organizations/subdomains/bigbinary/neeto_app_links
736 bytes

&gt; memory usage failed
10316224 (10MB)

&gt; memory usage dead
29871174 (29MB)
</code></pre>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 allows infinite ranges in length validators]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-endless-ranges-for-activemodel-validations"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-endless-ranges-for-activemodel-validations.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-endless-ranges-for-activemodel-validations</id>
      <published>2022-08-30T00:00:00.000Z</published>
      <updated>2022-08-30T00:00:00.000Z</updated>
      <author>
        <name>Ghouse Mohamed</name>
        <uri>https://www.bigbinary.com/blog/authors/ghousemohamed</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-endless-ranges-for-activemodel-validations.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 allows infinite ranges for LengthValidators and Clusivity validators]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-endless-ranges-for-activemodel-validations"><![CDATA[<p>Rails 7.1 adds support for infinite ranges for Active Model validations, which
use the <code>:in</code>, and <code>:within</code> options. It was already possible to query using
infinite ranges in Active Record like so:</p>
<pre><code class="language-ruby">Book.where(purchases: 20..)
# Returns a collection of records with purchases from 20 and upwards.
Book.where(purchases: ..20)
# Returns a collection of records with purchases from 20 and below.
</code></pre>
<p>But the same idea of using infinite ranges in Active Model validations was
limited in scope. Rails 7.1 extends this scope of usage by adding support for
infinite ranges in Active Model validations. For example, validating the length
of <code>first_name</code> without an upper bound for a <code>User</code> will be as simple as
writing:</p>
<pre><code class="language-ruby">class User
    # ...
    validates_length_of :first_name, in: 20..
end
</code></pre>
<p>The length of the <code>:first_name</code> does not have an upper bound. As long as the
length is greater than or equal to 20, it will remain valid.</p>
<p>The above example also holds true when using the <code>:within</code> option as well:</p>
<pre><code class="language-ruby">class User
    # ...
    validates_length_of :first_name, within: 20..
end
</code></pre>
<p>In a similar example, let's look at how we would use Active Model validations
along with the <code>:inclusion</code> option:</p>
<pre><code class="language-ruby">class User
    # ...
    validates :age, inclusion: { in: proc { (25..) } }
end
</code></pre>
<p>The above example would validate the <code>:age</code> field such that, it's value needs to
be 25 or above for the record to be valid.</p>
<p>Please check out the following pull requests for more details:</p>
<ol>
<li><a href="https://github.com/rails/rails/pull/45138">Infinite ranges for LengthValidator</a></li>
<li><a href="https://github.com/rails/rails/pull/45123">Infinite ranges for Clusivity validator</a></li>
</ol>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 raises if Active Storage service is missing]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-raises-error-if-active-storage-service-not-specified"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-raises-error-if-active-storage-service-not-specified.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-raises-error-if-active-storage-service-not-specified</id>
      <published>2022-08-23T00:00:00.000Z</published>
      <updated>2022-08-23T00:00:00.000Z</updated>
      <author>
        <name>Ghouse Mohamed</name>
        <uri>https://www.bigbinary.com/blog/authors/ghousemohamed</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-raises-error-if-active-storage-service-not-specified.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 raises RuntimeError if Active Storage service is not specified]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-raises-error-if-active-storage-service-not-specified"><![CDATA[<p>If Active Storage has been configured, and the service type has not been
explicitly set in the respective environment's configuration file, then trying
to use active storage throws the following error message:</p>
<pre><code class="language-plaintext">Failed to replace attachments_attachments because one or more of the new records could not be saved.
</code></pre>
<p>This is not helpful, and it doesn't indicate where to make the required changes
for Active Storage to be able to save the attachment(s). It also allows the
application to start as if a valid service has been set for Active Storage to
use. Starting Rails 7.1, if <code>config.active_storage.service</code> has not been
explicitly set, then even attempting to start the application would throw a
<code>RuntimeError</code> with the following error message:</p>
<pre><code class="language-plaintext">Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/production.rb
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/44372">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 adds Action Cable connection-level callbacks]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-1-adds-callbacks-for-action-cable-connection"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-callbacks-for-action-cable-connection.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-1-adds-callbacks-for-action-cable-connection</id>
      <published>2022-08-02T00:00:00.000Z</published>
      <updated>2022-08-02T00:00:00.000Z</updated>
      <author>
        <name>Ghouse Mohamed</name>
        <uri>https://www.bigbinary.com/blog/authors/ghousemohamed</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-1-adds-callbacks-for-action-cable-connection.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 adds callbacks for Action Cable commands at the connection level]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-1-adds-callbacks-for-action-cable-connection"><![CDATA[<p>Action Cable allows us to add callbacks for individual channels. These are:
<code>after_subscribe</code>, <code>after_unsubscribe</code>, <code>before_subscribe</code>,
<code>before_unsubscribe</code>, <code>on_subscribe</code>, <code>on_unsubscribe</code>. These callbacks are
registered individually for each Channel. Before Rails 7.1, there was no way in
which we could register callbacks to be called before every command generically.
Rails 7.1 solves this problem by providing a set of callbacks that can be
registered at the connection level. And these callbacks are called for every
command, regardless of which channel's command is being invoked.</p>
<p>These are the following callbacks, which can be registered:</p>
<ol>
<li><code>before_command</code>: This callback is invoked before any command can be
processed by the channel.</li>
<li><code>around_command</code>: This callback is generally invoked around the command. Any
piece of code before <code>yield</code> is run before the actual command and any piece
of code after <code>yield</code> is run after the command has been processed by the
channel.</li>
<li><code>after_command</code>: This callback is invoked after the command has been
processed by the channel.</li>
</ol>
<p>Let's take a look at some example code to understand this behaviour better:</p>
<pre><code class="language-ruby">class Connection &lt; ActionCable::Connection::Base
  identified_by :current_user
  before_command :set_current_user
  around_command :register_telemetry_data
  after_command :update_current_user

  private

    def set_current_user
      if request.params[&quot;user_id&quot;].present?
        self.current_user = User.find_by(request.params[&quot;user_id&quot;])
      end
      reject_unauthorized_connection if self.current_user.nil?
    end

    def register_telemetry_data
      self.current_user.register_telemetry({ start: true })
      yield
      self.current_user.register_telemetry({ end: true })
    end

    def update_current_user
      self.current_user.touch(:updated_at)
    end
end
</code></pre>
<p>Here, we can expect <code>set_current_user</code> to be invoked before every command is
processed. Similar to this, we can also expect <code>update_current_user</code> to be
invoked after every command is processed. Whereas for the
<code>register_telemetry_data</code>,
<code>self.current_user.register_telemetry({ start: true })</code> is run before the
command is processed and then after the command is processed,
<code>self.current_user.register_telemetry({ end: true })</code> is run.</p>
<p>Please check out this <a href="https://github.com/rails/rails/pull/44696">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 audio_tag and video_tag accept Active Storage]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-extends-support-for-audio-tag-and-video-tag"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-extends-support-for-audio-tag-and-video-tag.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-extends-support-for-audio-tag-and-video-tag</id>
      <published>2022-07-27T00:00:00.000Z</published>
      <updated>2022-07-27T00:00:00.000Z</updated>
      <author>
        <name>Ghouse Mohamed</name>
        <uri>https://www.bigbinary.com/blog/authors/ghousemohamed</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-extends-support-for-audio-tag-and-video-tag.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 allows audio_tag and video_tag to receive Active Storage attachments]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-extends-support-for-audio-tag-and-video-tag"><![CDATA[<p>Rails 7.1 allows <code>audio_tag</code> and <code>video_tag</code> ActionView helpers to receive
Active Storage Attachments which implicitly unpacks the asset path to be
included in the <code>src</code> attribute of the <code>&lt;audio&gt;&lt;/audio&gt;</code> and <code>&lt;video&gt;&lt;/video&gt;</code>
tags.</p>
<p>Previously, the helper methods received only the asset path/url. To get the
asset path of an Active Storage Attachment, we had to explicitly call
<code>polymorphic_path</code> on the attachment, which returned the desired asset path.</p>
<h3>Before</h3>
<pre><code class="language-ruby">audio_tag(polymorphic_path(user.audio_file))
# =&gt; &lt;audio src=&quot;/...&quot;&gt;&lt;/audio&gt;
video_tag(polymorphic_path(user.video_file))
# =&gt; &lt;video src=&quot;/...&quot;&gt;&lt;/video&gt;
</code></pre>
<h3>After</h3>
<pre><code class="language-ruby">audio_tag(user.audio_file)
# =&gt; &lt;audio src=&quot;/...&quot;&gt;&lt;/audio&gt;
video_tag(user.video_file)
# =&gt; &lt;video src=&quot;/...&quot;&gt;&lt;/video&gt;
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/44085">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7.1 returns Active Storage attachments after save]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-returns-active-storage-blob-after-save"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-returns-active-storage-blob-after-save.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-returns-active-storage-blob-after-save</id>
      <published>2022-07-26T00:00:00.000Z</published>
      <updated>2022-07-26T00:00:00.000Z</updated>
      <author>
        <name>Ghouse Mohamed</name>
        <uri>https://www.bigbinary.com/blog/authors/ghousemohamed</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-returns-active-storage-blob-after-save.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.1 returns the Active Storage attachment(s) after saving the attachment]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-returns-active-storage-blob-after-save"><![CDATA[<p>Previously, saving an attachment to a record returned a boolean, indicating
whether or not the attachment was saved to the record. This is not helpful since
we have to dig into the record again to retrieve the attachment. Starting Rails
7.1, saving an attachment to a record returns the saved attachment. We can now
conveniently use blob methods like <code>#download</code>, <code>#url</code>, <code>#variant</code> etc on the
attachment without having to dig into the record again.</p>
<h3>Before</h3>
<pre><code class="language-ruby"># rails console
&gt;&gt; @user = User.create!(name: &quot;Josh&quot;)
&gt;&gt; @user.avatar.attach(params[:avatar])
=&gt; true
</code></pre>
<h3>After</h3>
<pre><code class="language-ruby"># rails console
&gt;&gt; @user = User.create!(name: &quot;Josh&quot;)
&gt;&gt; @user.avatar.attach(params[:avatar])
=&gt; #&lt;ActiveStorage::Attached::One:0x00007f075e592380 @name=&quot;avatar&quot; @record=#&lt;User:0x00007f075e5924e8 id: &quot;1&quot;, name: &quot;Josh&quot;&gt;
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/44439">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Handling SIGHUP error from Heroku]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/fix-sighup-error-from-heroku"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/fix-sighup-error-from-heroku.png"/>
      <id>https://www.bigbinary.com/blog/fix-sighup-error-from-heroku</id>
      <published>2022-04-04T00:00:00.000Z</published>
      <updated>2022-04-04T00:00:00.000Z</updated>
      <author>
        <name>Unnikrishnan KP</name>
        <uri>https://www.bigbinary.com/blog/authors/unni</uri>
      </author>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/fix-sighup-error-from-heroku.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[A video on handling SIGHUP error from Heroku. We also get to see how to use notifyatexit feature of Honeybadger. Please note that this was an internal video which we are publishing as is.]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/fix-sighup-error-from-heroku"><![CDATA[<p>A video on handling SIGHUP error from Heroku. We also get to see how to use <code>notify_at_exit</code> feature of
<a href="https://www.honeybadger.io">Honeybadger</a>. Please note that this was an internal video which we are publishing as is.</p>
<p><a href="https://www.youtube.com/watch?v=f8yXrceAotQ"><img src="https://img.youtube.com/vi/f8yXrceAotQ/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=f8yXrceAotQ">Watch on YouTube</a></p>]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 adds only_numeric option to numericality validator]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-only_numeric-option-to-numericality-validator"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-only_numeric-option-to-numericality-validator.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-only_numeric-option-to-numericality-validator</id>
      <published>2022-01-17T00:00:00.000Z</published>
      <updated>2022-01-17T00:00:00.000Z</updated>
      <author>
        <name>Aditya Bhutani</name>
        <uri>https://www.bigbinary.com/blog/authors/adityabhutani</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-only_numeric-option-to-numericality-validator.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7.0.1 introduces onlynumeric option to the numericality validator which specifies whether the value has to be an instance of Numeric. The default behavior is to attempt parsing the value if it...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-only_numeric-option-to-numericality-validator"><![CDATA[<p>Rails 7.0.1 introduces <code>only_numeric</code> option to the numericality validator which
specifies whether the value has to be an instance of Numeric. The default
behavior is to attempt parsing the value if it is a String.</p>
<p>When the database field is a float column, the data will get serialized to the
correct type. In the case of a JSON column, serialization doesn't take place.</p>
<p>As a resolution, Rails 7 has added <code>only_numeric</code> option to numericality
validator.</p>
<p>We will see it in action.</p>
<p>To demonstrate, we need to generate a table that has a json/jsonb column.</p>
<pre><code class="language-ruby"># migration
create_table :users do |t|
  t.jsonb :personal
end
</code></pre>
<h3>Before Validation</h3>
<pre><code class="language-ruby"># Model
class User &lt; ApplicationRecord
  store_accessor :personal, %i[age tooltips]
end
</code></pre>
<pre><code class="language-ruby"># rails console
&gt;&gt; User.create!(age: '29')
=&gt; #&lt;User id: 1, preferences: {&quot;age&quot;=&gt;&quot;29&quot;}, created_at: Sun, 16 Jan 2022 14:09:43.045301000 UTC +00:00, updated_at: Sun, 16 Jan 2022 14:09:43.045301000 UTC +00:00&gt;
</code></pre>
<h3>After Validation</h3>
<pre><code class="language-ruby"># Model
class User &lt; ApplicationRecord
  store_accessor :personal, %i[age tooltips]
  validates_numericality_of :age, only_numeric: true, allow_nil: true
end
</code></pre>
<pre><code class="language-ruby"># rails console
&gt;&gt; User.create!(age: '29')
=&gt; 'raise_validation_error': Validation failed: Age is not a number (ActiveRecord::RecordInvalid)

&gt;&gt; User.create!(age: 29)
=&gt; #&lt;User id: 2, preferences: {&quot;age&quot;=&gt;29}, created_at: Sun, 16 Jan 2022 14:15:44.599934000 UTC +00:00, updated_at: Sun, 16 Jan 2022 14:15:44.599934000 UTC +00:00&gt;
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/43914">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 adds Pathname#existence]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-pathname-existence"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-pathname-existence.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-pathname-existence</id>
      <published>2021-12-07T00:00:00.000Z</published>
      <updated>2021-12-07T00:00:00.000Z</updated>
      <author>
        <name>Ashik Salman</name>
        <uri>https://www.bigbinary.com/blog/authors/ashiksp</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-pathname-existence.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7 introduces Pathname#existence method, which returns the receiver if the given file path exists, otherwise returns nil. Before We need to first check whether the given file path exists or not...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-pathname-existence"><![CDATA[<p>Rails 7 introduces
<a href="https://api.rubyonrails.org/classes/Pathname.html#method-i-existence"><code>Pathname#existence</code></a>
method, which returns the receiver if the given file path exists, otherwise
returns nil.</p>
<h3>Before</h3>
<p>We need to first check whether the given file path exists or not to perform any
other operations on it.</p>
<pre><code class="language-ruby">=&gt; file_path = &quot;config/schedule.yml&quot;
=&gt; Pathname.new(file_path).read if Pathname.new(file_path).exist?
</code></pre>
<h3>Rails 7 onwards</h3>
<p>The <code>Pathname#existence</code> method acts like <code>Object#presence</code> for file existence.</p>
<pre><code class="language-ruby">=&gt; file_path = &quot;config/schedule.yml&quot;
=&gt; Pathname.new(file_path).existence&amp;.read
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/43726">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Fixing slow Rails page loads by finding n+1 queries]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/fix-slow-page-loads-in-a-ruby-on-rails-application"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/fix-slow-page-loads-in-a-ruby-on-rails-application.png"/>
      <id>https://www.bigbinary.com/blog/fix-slow-page-loads-in-a-ruby-on-rails-application</id>
      <published>2021-12-01T00:00:00.000Z</published>
      <updated>2021-12-01T00:00:00.000Z</updated>
      <author>
        <name>Unnikrishnan KP</name>
        <uri>https://www.bigbinary.com/blog/authors/unni</uri>
      </author>
      <category term="Rails"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/fix-slow-page-loads-in-a-ruby-on-rails-application.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Fix slow page loads in a Ruby on Rails application by identifying n+1 queries]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/fix-slow-page-loads-in-a-ruby-on-rails-application"><![CDATA[<p>In one of our internal products, we received complaints about slow page loads and longer response times on a specific page. Our team members used New Relic to identify the cause of the slowness and resolve the issue. After the issue was resolved, I made a video for internal purposes. In this blog we are posting that video as it was recorded.</p>
<p><a href="https://www.youtube.com/watch?v=pmCcey4BiG0"><img src="https://img.youtube.com/vi/pmCcey4BiG0/hqdefault.jpg" alt="Watch on YouTube" width="480" height="360"></a><br><a href="https://www.youtube.com/watch?v=pmCcey4BiG0">Watch on YouTube</a></p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 adds nested attributes support to delegated_type]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-accepts-nested-attributes-for-support-for-delegated_type"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-accepts-nested-attributes-for-support-for-delegated_type.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-accepts-nested-attributes-for-support-for-delegated_type</id>
      <published>2021-11-23T00:00:00.000Z</published>
      <updated>2021-11-23T00:00:00.000Z</updated>
      <author>
        <name>Ashik Salman</name>
        <uri>https://www.bigbinary.com/blog/authors/ashiksp</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-accepts-nested-attributes-for-support-for-delegated_type.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7 adds accepts_nested_attributes_for support for delegated_type]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-accepts-nested-attributes-for-support-for-delegated_type"><![CDATA[<p>Rails 6.1 introduced the <code>delegated_type</code> to Active Record, which makes it easier
for models to share responsibilities. Please see our
<a href="https://www.bigbinary.com/blog/rails-6-1-adds-delegated-type-to-active-record">blog</a>
to read more about <code>delegated_type</code>.</p>
<pre><code class="language-ruby">class Entry &lt; ApplicationRecord
  # Schema
  #  entryable_type, entryable_id, ...
  delegated_type :entryable, types: %w[ Message Comment ]
end

class Message
  # Schema
  #  subject, ...
end

class Comment
  # Schema
    #  content, ...
end
</code></pre>
<p>The <code>accepts_nested_attributes_for</code> option is very helpful while handling nested
forms. We can easily create and update associated records by passing details
along with the main object parameters when the <code>accepts_nested_attributes_for</code>
option is enabled.</p>
<h3>Before</h3>
<p>The <code>accepts_nested_attributes_for</code> option is not available for
<code>delegated_type</code>, hence we can't use nested forms for associated objects
configured via <code>delegated_type</code>.</p>
<h3>Rails 7 onwards</h3>
<p>Rails 7 adds <code>accepts_nested_attributes_for</code> support to <code>delegated_type</code>, which
allows to create and update records easily without needing to write specific
methods or logic.</p>
<pre><code class="language-ruby">class Entry &lt; ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ]
  accepts_nested_attributes_for :entryable
end

params = {
  entry: {
    entryable_type: 'Message',
    entryable_attributes: { subject: 'Delegated Type' }
  }
}

message_entry = Entry.create(params[:entry])

params = {
  entry: {
    entryable_type: 'Comment',
    entryable_attributes: { content: 'Looks Cool!' }
  }
}

comment_entry = Entry.create(params[:entry])
</code></pre>
<p>If we want to deal with more logic or validations while creating the entryable
objects, we'll have to create a method specifically and do the logic there.</p>
<pre><code class="language-ruby">class Entry &lt; ApplicationRecord
  def self.create_with_comment(content)
    # Validation logic goes here
    create! entryable: Comment.new(content: content)
  end
end
</code></pre>
<p>Please check out this <a href="https://github.com/rails/rails/pull/41717">pull request</a>
for more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 adds from option to assert_no_changes]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-from-option-to-assert_no_changes"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-from-option-to-assert_no_changes.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-from-option-to-assert_no_changes</id>
      <published>2021-11-19T00:00:00.000Z</published>
      <updated>2021-11-19T00:00:00.000Z</updated>
      <author>
        <name>Gaurav Varma</name>
        <uri>https://www.bigbinary.com/blog/authors/gaurav-varma</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-from-option-to-assert_no_changes.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 7 Adds from option to ActiveSupport::TestCase#assert_no_changes]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-from-option-to-assert_no_changes"><![CDATA[<p>Rails
<a href="https://guides.rubyonrails.org/active_support_core_extensions.html">Active Support</a>
provides various extensions, utilities, and helpers. It provides a collection of
utility classes and standard library extensions that are very useful.</p>
<p>Rails 5.1 introduced <code>assert_no_changes</code> and <code>assert_changes</code>, making it easier
to observe changes. Using
<a href="https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_changes">ActiveSupport::TestCase#assert_no_changes</a>
we can easily assert that the result of evaluating an expression has not changed
before and after calling the passed block.</p>
<p>To assert the expected change in the value of an object, we can use
<code>assert_changes</code>.</p>
<pre><code class="language-ruby">assert_changes -&gt; { user.address } do
  user.update address: 'Miami'
end
</code></pre>
<p><code>assert_changes</code> also supports <code>from</code> and <code>to</code> options.</p>
<pre><code class="language-ruby">assert_changes -&gt; { user.address }, from: 'San Francisco', to: 'Miami' do
  user.update address: 'Miami'
end
</code></pre>
<p>Similarly, <code>assert_no_changes</code> allows us to assert a value that is expected to
not change.</p>
<pre><code class="language-ruby">assert_no_changes -&gt; { user.address } do
  user.update address: 'Miami'
end
</code></pre>
<p>We can also specify an error message with <code>assert_no_changes</code>.</p>
<pre><code class="language-ruby">assert_no_changes -&gt; { user.address }, 'Expect the address to not change' do
  user.update address: 'Miami'
end
</code></pre>
<h3>Before</h3>
<p><code>assert_no_changes</code> did not support the <code>from</code> option, similar to
<code>assert_changes</code>.</p>
<pre><code class="language-ruby">assert_no_changes -&gt; { user.address } do
  user.update address: 'Miami'
end
</code></pre>
<p>However,
<a href="https://github.com/rails/rails/pull/42277">Rails 7 has added from: option to ActiveSupport::TestCase#assert_no_changes</a>,
allowing us to assert on the initial value that is expected to not change.</p>
<h3>Rails 7 onwards</h3>
<p>Provides the optional <code>from</code> argument to specify the expected initial value.</p>
<pre><code class="language-ruby">assert_no_changes -&gt; { user.address }, from: 'San Francisco' do
  user.update address: 'Miami'
end
</code></pre>
<p>Check out this <a href="https://github.com/rails/rails/pull/42277">pull request</a> for
more details.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 replaced byebug with ruby/debug]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-replaced-byebug-with-ruby-debug"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-replaced-byebug-with-ruby-debug.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-replaced-byebug-with-ruby-debug</id>
      <published>2021-11-09T00:00:00.000Z</published>
      <updated>2021-11-09T00:00:00.000Z</updated>
      <author>
        <name>Gaurav Varma</name>
        <uri>https://www.bigbinary.com/blog/authors/gaurav-varma</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-replaced-byebug-with-ruby-debug.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[Rails 5 introduced byebug which is an easy-to-use, feature-rich ruby debugger. It offers features like Stepping, Breaking, Evaluating, Tracking. Using byebug we can easily control the execution of a...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-replaced-byebug-with-ruby-debug"><![CDATA[<p>Rails 5 introduced <a href="https://github.com/deivid-rodriguez/byebug">byebug</a> which is
an easy-to-use, feature-rich ruby debugger. It offers features like <code>Stepping</code>,
<code>Breaking</code>, <code>Evaluating</code>, <code>Tracking</code>.</p>
<p>Using <a href="https://github.com/deivid-rodriguez/byebug">byebug</a> we can easily control
the execution of a program and the debug inspector for call stack navigation.
This allows us to handle and track the execution flow.</p>
<p>Here is
<a href="https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-byebug-gem">byebug documentation</a>
and here is the <a href="https://github.com/rails/rails/pull/14646">pull request</a> where
it was added.</p>
<p><a href="https://github.com/rails/rails/pull/43187">Rails 7 is replacing byebug with ruby/debug</a>.
<code>debug</code> is Ruby’s new debugger, which will be included in Ruby 3.1. To align
Rails with Ruby <code>debug</code> has been added to Rails 7.</p>
<p>Let's see an example of debugging with both Byebug and Debug.</p>
<h3>Before</h3>
<p>Let's assume we have a <code>NameController</code>. Inside any Rails application, you can
call the debugger by calling the <code>byebug</code> method.</p>
<pre><code class="language-ruby"># app/controllers/name_controller.rb
class NameController &lt; ApplicationController
  def index
    name = &quot;John Doe&quot;
    byebug # Call to debugger
    city = &quot;San Francisco&quot;
  end
end
</code></pre>
<p>Then the invoked debugger results in the following.</p>
<pre><code class="language-ruby">    [1, 7] in app/controllers/test_controller.rb
    1: class NameController &lt; ApplicationController
    2:   def index
    3:     name = &quot;John Doe&quot;
    4:     byebug # Call to debugger
=&gt;  5:    city = &quot;San Francisco&quot;
    6:   end
    7: end

  (byebug) name # variable call
  &quot;John Doe&quot;
</code></pre>
<h3>Rails 7 onwards</h3>
<p>We can use the <code>binding.break</code> method for calling Ruby Debug.</p>
<pre><code class="language-ruby"># app/controllers/name_controller.rb
class NameController &lt; ApplicationController
  def index
    name = &quot;John Doe&quot;
    binding.break # Call to debugger
    city = &quot;San Francisco&quot;
  end
end
</code></pre>
<p>The invoked debugger results in the following.</p>
<pre><code class="language-ruby">  [1, 7] in app/controllers/test_controller.rb
  1| class NameController &lt; ApplicationController
  2|   def index
  3|     name = &quot;John Doe&quot;
  4|     binding.break # Call to debugger
&gt; 5|    city = &quot;San Francisco&quot;
  6|   end
  7| end
&gt;#0 NameController#index at ~/demo_app/app/controllers/name_controller.rb:5
  #1  ActionController::BasicImplicitRender#send_action(method=&quot;index&quot;, args=[])
(rdbg) name # variable call
&quot;John Doe&quot;
</code></pre>
<p>Check out this <a href="https://github.com/rails/rails/pull/43187">pull request</a> for
more details and for commands or features of Ruby Debug, please visit
<a href="https://github.com/ruby/debug">Ruby-Debug</a>.</p>
]]></content>
    </entry>
  <entry>
      <title type="text"><![CDATA[Rails 7 adds weekday_options_for_select and weekday_select]]></title>
      <link rel="alternate" type="text/html" href="https://www.bigbinary.com/blog/rails-7-adds-weekday_options_for_select-and-weekday_select"/>
      <link rel="enclosure" type="image/png" href="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-weekday_options_for_select-and-weekday_select.png"/>
      <id>https://www.bigbinary.com/blog/rails-7-adds-weekday_options_for_select-and-weekday_select</id>
      <published>2021-11-09T00:00:00.000Z</published>
      <updated>2021-11-09T00:00:00.000Z</updated>
      <author>
        <name>Gaurav Varma</name>
        <uri>https://www.bigbinary.com/blog/authors/gaurav-varma</uri>
      </author>
      <category term="Rails"/>
      <category term="Rails 7"/>
      <media:thumbnail url="https://www.bigbinary.com/blog/images/social_preview_images/generated/rails-7-adds-weekday_options_for_select-and-weekday_select.png" width="1200" height="630"/>
      <summary type="text"><![CDATA[In web applications, forms are one of the most essential interfaces for user input, and it can be tedious to write and maintain form markups with many attributes. Rails provide Action View Form...]]></summary>
      <content type="html" xml:base="https://www.bigbinary.com/blog/rails-7-adds-weekday_options_for_select-and-weekday_select"><![CDATA[<p>In web applications, forms are one of the most essential interfaces for user
input, and it can be tedious to write and maintain form markups with many
attributes. Rails provide
<a href="https://guides.rubyonrails.org/form_helpers.html">Action View Form Helpers</a> for
generating form markup.</p>
<p>Using
<a href="https://guides.rubyonrails.org/form_helpers.html#making-select-boxes-with-ease">select</a>
helper we can easily create select boxes in HTML with one <code>&lt;option&gt;</code> element for
each option to choose from.</p>
<p>For example, let's say we have a list of cities for the user to choose from.</p>
<pre><code class="language-ruby"># app/views/address/new.html.erb
&lt;%= form.select :city, [&quot;Pune&quot;, &quot;Mumbai&quot;, &quot;Delhi&quot;] %&gt;
</code></pre>
<p>Rails will generate the following markup.</p>
<pre><code class="language-html">&lt;select name=&quot;city&quot; id=&quot;city&quot;&gt;
  &lt;option value=&quot;Pune&quot;&gt;Pune&lt;/option&gt;
  &lt;option value=&quot;Mumbai&quot;&gt;Mumbai&lt;/option&gt;
  &lt;option value=&quot;Delhi&quot;&gt;Delhi&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<p>Previously for generating a select box for weekday select, we needed to write a
custom helper. Rails did not have anything out of the box for the weekday
selection.</p>
<p>However,
<a href="https://github.com/rails/rails/pull/42979">Rails 7 has added weekday_options_for_select and weekday_select</a>
using which we can easily generate a dropdown field of selecting a weekday.</p>
<h3>Before</h3>
<p>In Rails 6.1, we can create a dropdown field for weekdays as shown here.</p>
<pre><code class="language-ruby"># app/views/users/new.html.erb
&lt;%= form_with(model: user) do |form| %&gt;
  &lt;div class=&quot;field&quot;&gt;
    &lt;%= form.label :weekly_off %&gt;
    &lt;%= form.select :weekly_off, I18n.t('date.day_names') %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</code></pre>
<p>Or we can do something like this.</p>
<pre><code class="language-ruby"># app/views/users/new.html.erb
&lt;%= form_with(model: user) do |form| %&gt;
  &lt;div class=&quot;field&quot;&gt;
    &lt;%= form.label :weekly_off %&gt;
    &lt;%= form.select :weekly_off, I18n.t('date.day_names').map.with_index.to_h %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</code></pre>
<p>Then the generated markup looks like this.</p>
<pre><code class="language-html">&lt;select name=&quot;user[weekly_off]&quot; id=&quot;user_weekly_off&quot;&gt;
  &lt;option value=&quot;Sunday&quot;&gt;Sunday&lt;/option&gt;
  &lt;option value=&quot;Monday&quot;&gt;Monday&lt;/option&gt;
  &lt;option value=&quot;Tuesday&quot;&gt;Tuesday&lt;/option&gt;
  &lt;option value=&quot;Wednesday&quot;&gt;Wednesday&lt;/option&gt;
  &lt;option value=&quot;Thursday&quot;&gt;Thursday&lt;/option&gt;
  &lt;option value=&quot;Friday&quot;&gt;Friday&lt;/option&gt;
  &lt;option value=&quot;Saturday&quot;&gt;Saturday&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<p>Here is how it would look if we go with the second option.</p>
<pre><code class="language-html">&lt;select name=&quot;user[weekly_off]&quot; id=&quot;user_weekly_off&quot;&gt;
  &lt;option value=&quot;0&quot;&gt;Sunday&lt;/option&gt;
  &lt;option value=&quot;1&quot;&gt;Monday&lt;/option&gt;
  &lt;option value=&quot;2&quot;&gt;Tuesday&lt;/option&gt;
  &lt;option value=&quot;3&quot;&gt;Wednesday&lt;/option&gt;
  &lt;option value=&quot;4&quot;&gt;Thursday&lt;/option&gt;
  &lt;option value=&quot;5&quot;&gt;Friday&lt;/option&gt;
  &lt;option value=&quot;6&quot;&gt;Saturday&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<h3>Rails 7 onwards</h3>
<p>We can use the <code>weekday_options_for_select</code> or <code>weekday_select</code> helper for
generating a dropdown field for selecting a weekday.</p>
<pre><code class="language-ruby"># app/views/users/new.html.erb
&lt;%= form_with(model: user) do |form| %&gt;
  &lt;div class=&quot;field&quot;&gt;
    &lt;%= form.label :weekly_off %&gt;
    &lt;%= form.select :weekly_off, weekday_options_for_select(&quot;Monday&quot;, day_format: :abbr_day_names) %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</code></pre>
<p>Or we can do something like this.</p>
<pre><code class="language-ruby"># app/views/users/new.html.erb
&lt;%= form_with(model: user) do |form| %&gt;
  &lt;div class=&quot;field&quot;&gt;
    &lt;%= form.label :weekly_off %&gt;
    &lt;%= form.weekday_select :weekly_off, { selected: &quot;Monday&quot;, day_format: :abbr_day_names } %&gt;
  &lt;/div&gt;
&lt;% end %&gt;
</code></pre>
<p>Then The generated markup looks like this.</p>
<pre><code class="language-html">&lt;select name=&quot;user[weekly_off]&quot; id=&quot;user_weekly_off&quot;&gt;
  &lt;option value=&quot;Sunday&quot;&gt;Sunday&lt;/option&gt;
  &lt;option selected=&quot;selected&quot; value=&quot;Monday&quot;&gt;Monday&lt;/option&gt;
  &lt;option value=&quot;Tuesday&quot;&gt;Tuesday&lt;/option&gt;
  &lt;option value=&quot;Wednesday&quot;&gt;Wednesday&lt;/option&gt;
  &lt;option value=&quot;Thursday&quot;&gt;Thursday&lt;/option&gt;
  &lt;option value=&quot;Friday&quot;&gt;Friday&lt;/option&gt;
  &lt;option value=&quot;Saturday&quot;&gt;Saturday&lt;/option&gt;
&lt;/select&gt;
</code></pre>
<p><code>weekday_options_for_select</code> accepts a few options and all of them have a
default value.</p>
<p><code>selected</code> defaults to nil and if we provide this argument, the value passed will
be used as the selected option.</p>
<pre><code class="language-html"> &lt;!-- weekday_options_for_select(&quot;Friday&quot;) --&gt;
&lt;option value=\&quot;Sunday\&quot;&gt;Sunday&lt;/option&gt;\n
&lt;option value=\&quot;Monday\&quot;&gt;Monday&lt;/option&gt;\n
&lt;option value=\&quot;Tuesday\&quot;&gt;Tuesday&lt;/option&gt;\n
&lt;option value=\&quot;Wednesday\&quot;&gt;Wednesday&lt;/option&gt;\n
&lt;option value=\&quot;Thursday\&quot;&gt;Thursday&lt;/option&gt;\n
&lt;option selected=\&quot;selected\&quot; value=\&quot;Friday\&quot;&gt;Friday&lt;/option&gt;\n
&lt;option value=\&quot;Saturday\&quot;&gt;Saturday&lt;/option&gt;&quot;
</code></pre>
<p><code>index_as_value</code> defaults to false and if true it will set the value of each
option to the index of that day.</p>
<pre><code class="language-html">&lt;!-- weekday_options_for_select(index_as_value: true) --&gt;
&lt;option value=\&quot;0\&quot;&gt;Sunday&lt;/option&gt;\n
&lt;option value=\&quot;1\&quot;&gt;Monday&lt;/option&gt;\n
&lt;option value=\&quot;2\&quot;&gt;Tuesday&lt;/option&gt;\n
&lt;option value=\&quot;3\&quot;&gt;Wednesday&lt;/option&gt;\n
&lt;option value=\&quot;4\&quot;&gt;Thursday&lt;/option&gt;\n
&lt;option value=\&quot;5\&quot;&gt;Friday&lt;/option&gt;\n
&lt;option value=\&quot;6\&quot;&gt;Saturday&lt;/option&gt;&quot;
</code></pre>
<p><code>day_format</code> defaults to :day_names and passing this a different <code>I18n</code> key will
use different formats for the option display names and their values.</p>
<pre><code class="language-html">&lt;!-- weekday_options_for_select(day_format: :abbr_day_names) --&gt;
&lt;option value=\&quot;Sun\&quot;&gt;Sun&lt;/option&gt;\n
&lt;option value=\&quot;Mon\&quot;&gt;Mon&lt;/option&gt;\n
&lt;option value=\&quot;Tue\&quot;&gt;Tue&lt;/option&gt;\n
&lt;option value=\&quot;We\&quot;&gt;Wedn&lt;/option&gt;\n
&lt;option value=\&quot;Thu\&quot;&gt;Thu&lt;/option&gt;\n
&lt;option value=\&quot;Fri\&quot;&gt;Fri&lt;/option&gt;\n
&lt;option value=\&quot;Sat\&quot;&gt;Sat&lt;/option&gt;&quot;
</code></pre>
<p>Note that <code>:abbr_day_names</code> options are built into Rails, but we can define your
array.</p>
<p>Check out this <a href="https://github.com/rails/rails/pull/42979">pull request</a> for
more details.</p>
]]></content>
    </entry>
</feed>