<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sami Dghim</title>
    <description>The latest articles on DEV Community by Sami Dghim (@sami_dghim).</description>
    <link>https://dev.to/sami_dghim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F185907%2Ff2555f73-b5d6-4572-acdc-4351526ba8ac.png</url>
      <title>DEV Community: Sami Dghim</title>
      <link>https://dev.to/sami_dghim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sami_dghim"/>
    <language>en</language>
    <item>
      <title>Scaling Sidekiq in Ruby on Rails: Best Practices, War Stories, and WTF Moments</title>
      <dc:creator>Sami Dghim</dc:creator>
      <pubDate>Sun, 24 Aug 2025 22:35:08 +0000</pubDate>
      <link>https://dev.to/sami_dghim/scaling-sidekiq-in-ruby-on-rails-best-practices-war-stories-and-wtf-moments-ce1</link>
      <guid>https://dev.to/sami_dghim/scaling-sidekiq-in-ruby-on-rails-best-practices-war-stories-and-wtf-moments-ce1</guid>
      <description>&lt;p&gt;Imagine that your Rails application is running smoothly. Users are satisfied, payments are processed, emails are sent out, and reports are produced. Sidekiq feels like magic.&lt;/p&gt;

&lt;p&gt;Then one Friday evening (because production issues always happen on Fridays), you check the Sidekiq dashboard 💥&lt;/p&gt;

&lt;p&gt;Jobs are piling up faster than your interns can say, “Did we forget to add sidekiq_options retry: false again?”&lt;/p&gt;

&lt;p&gt;Welcome to Sidekiq at scale—where background jobs stop being background and start being your entire life.&lt;/p&gt;

&lt;p&gt;Here are some lessons I've learned from implementing Sidekiq at scale in production, along with metrics and real-world examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Keep Jobs Small and Fast
&lt;/h2&gt;

&lt;p&gt;Jobs should be atomic and quick. A &lt;strong&gt;5–10 second job is already too long&lt;/strong&gt; in most production systems.&lt;/p&gt;

&lt;p&gt;Bad example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generate_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# CPU-heavy&lt;/span&gt;
    &lt;span class="n"&gt;upload_to_s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# I/O-heavy&lt;/span&gt;
    &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# External API&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails halfway, the entire job retries.&lt;/p&gt;

&lt;p&gt;✅ Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GeneratePdfJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;PdfGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UploadReportJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;S3Uploader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendReportJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ReportMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;:).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chain jobs with callbacks or Sidekiq batches.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Idempotency is Non-Negotiable
&lt;/h2&gt;

&lt;p&gt;Jobs will run &lt;strong&gt;at least once&lt;/strong&gt;, sometimes more. If jobs aren’t idempotent, you’ll double-charge customers or send duplicate emails.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark_as_paid!&lt;/span&gt;
&lt;span class="n"&gt;send_receipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paid?&lt;/span&gt;
&lt;span class="no"&gt;ReceiptMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receipt_sent?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Use Queues Strategically
&lt;/h2&gt;

&lt;p&gt;Don’t dump everything into &lt;code&gt;default&lt;/code&gt;. Separate workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;critical&lt;/code&gt; → user-facing (emails, webhooks, payments)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; → normal jobs (notifications, syncs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;low&lt;/code&gt; → heavy, non-urgent (reports, exports, ETL)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;sidekiq.yml&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;:queues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;critical&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Monitor Everything
&lt;/h2&gt;

&lt;p&gt;Visibility is critical at scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidekiq Web UI&lt;/strong&gt; for queue depth &amp;amp; retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SQL snippet to monitor retries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sidekiq_jobs&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Control Concurrency
&lt;/h2&gt;

&lt;p&gt;More threads ≠ better throughput. High concurrency can overwhelm Postgres or external APIs.&lt;/p&gt;

&lt;p&gt;✅ Start with concurrency = &lt;strong&gt;2–5x your CPU cores&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;:concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Redis is Your Heartbeat
&lt;/h2&gt;

&lt;p&gt;Redis is the single point of truth for Sidekiq.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;dedicated Redis&lt;/strong&gt; (not shared with cache/session store)&lt;/li&gt;
&lt;li&gt;Monitor memory &amp;amp; latency&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Smart Retries Only
&lt;/h2&gt;

&lt;p&gt;Default retries (up to 25) can flood your system. Customize them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyJob&lt;/span&gt;
  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;retry: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="no"&gt;ExternalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call!&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ExternalService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InvalidCredentials&lt;/span&gt;
    &lt;span class="c1"&gt;# Don’t retry, just fail fast&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Shutdown&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Deploy with Process Separation
&lt;/h2&gt;

&lt;p&gt;Don’t run one mega Sidekiq instance. Separate by queue type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiq &lt;span class="nt"&gt;-q&lt;/span&gt; critical,5 &lt;span class="nt"&gt;-q&lt;/span&gt; default,2
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiq &lt;span class="nt"&gt;-q&lt;/span&gt; low
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Dead Jobs are Zombie Failures
&lt;/h2&gt;

&lt;p&gt;Dead jobs = silent failures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiqctl clean &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or query them in Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DeadSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Autoscale or Suffer
&lt;/h2&gt;

&lt;p&gt;In Kubernetes, ECS, or Heroku, autoscale workers on queue latency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale up if latency &amp;gt; 30s&lt;/li&gt;
&lt;li&gt;Scale down if latency &amp;lt; 5s&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Chain Jobs for External API Pagination
&lt;/h2&gt;

&lt;p&gt;Sometimes, the firehose comes from outside your app. A classic case: syncing a huge dataset from a third-party API that only gives you paginated results.&lt;/p&gt;

&lt;p&gt;If you naively fetch all pages in one job, you’ll hit rate limits, blow up memory, and make retries a nightmare.&lt;/p&gt;

&lt;p&gt;✅ Better: chain jobs page by page. Each job handles one page, then enqueues the next, until the API says you’re done. That way:&lt;br&gt;
Jobs stay tiny and idempotent.&lt;/p&gt;

&lt;p&gt;Failures retry gracefully without restarting the whole sync.&lt;br&gt;
You respect API rate limits with backoff and jitter.&lt;/p&gt;

&lt;p&gt;You can fan out each page’s items to dedicated jobs for massive throughput.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SyncExternalPage&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
  &lt;span class="n"&gt;sidekiq_options&lt;/span&gt; &lt;span class="ss"&gt;queue: :sync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;retry: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ExternalClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cursor: &lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:items&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;UpsertExternalItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# idempotent per item&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:next_cursor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;# Chain the next page&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:next_cursor&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Sync complete ✅"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpsertExternalItem&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ExternalRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;external_id: &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;unique_by: :index_external_records_on_external_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern lets you chew through millions of external records without drowning your system in retries or hitting API bans.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sidekiq is incredibly robust, but at scale, you need production-grade practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep jobs &lt;strong&gt;small, fast, idempotent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Prioritize with &lt;strong&gt;queues&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Monitor &lt;strong&gt;latency &amp;amp; retries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tune &lt;strong&gt;concurrency &amp;amp; Redis&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;autoscaling&lt;/strong&gt; to survive traffic spikes&lt;/li&gt;
&lt;li&gt;Chain jobs for external API pagination to handle massive datasets gracefully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these practices, you can confidently handle &lt;strong&gt;millions of jobs per day&lt;/strong&gt; without breaking a sweat.&lt;/p&gt;




&lt;p&gt;✍️ Your turn: what’s your worst Sidekiq horror story? Did you also accidentally email your entire user base at 3am?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🔐 Why Your API Needs Rate Limiting ?</title>
      <dc:creator>Sami Dghim</dc:creator>
      <pubDate>Sun, 10 Aug 2025 12:04:43 +0000</pubDate>
      <link>https://dev.to/sami_dghim/why-your-api-needs-rate-limiting--2ckc</link>
      <guid>https://dev.to/sami_dghim/why-your-api-needs-rate-limiting--2ckc</guid>
      <description>&lt;p&gt;If you're running an API — whether it powers a mobile app, third-party integrations, or internal tools — you're always at risk of &lt;strong&gt;misuse&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes it’s &lt;strong&gt;malicious&lt;/strong&gt;: DDoS attacks, brute-force login attempts, or data scrapers.&lt;br&gt;&lt;br&gt;
Other times, it’s &lt;strong&gt;accidental&lt;/strong&gt;: a buggy client stuck in a loop, or QA testing without throttling.&lt;/p&gt;

&lt;p&gt;Either way, &lt;strong&gt;without rate limiting&lt;/strong&gt;, your API becomes a wide-open door for traffic floods that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💥 Overload your servers and databases
&lt;/li&gt;
&lt;li&gt;💸 Skyrocket your cloud costs
&lt;/li&gt;
&lt;li&gt;🐌 Degrade performance for real users
&lt;/li&gt;
&lt;li&gt;🛑 Expose security vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news? There's a &lt;strong&gt;simple, proactive defense&lt;/strong&gt;: &lt;strong&gt;Rate Limiting&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚦 What Is Rate Limiting?
&lt;/h2&gt;

&lt;p&gt;Rate limiting sets a maximum number of requests a client can make to your API within a given time window.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: &lt;em&gt;"Each IP can make 100 requests per 10 minutes."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s not just about protection — it’s also about &lt;strong&gt;fairness&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
You don’t want one aggressive user (or bot) consuming all your resources while others get timeouts.&lt;/p&gt;


&lt;h2&gt;
  
  
  💡 Why Rate Limiting Matters
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ⚙️ Performance Protection
&lt;/h3&gt;

&lt;p&gt;A sudden burst of requests can queue up, slow down, or even crash your app. Rate limiting prevents this by smoothing out traffic spikes.&lt;/p&gt;
&lt;h3&gt;
  
  
  🔒 Security Layer
&lt;/h3&gt;

&lt;p&gt;Brute-force attacks, credential stuffing, and scraping become much harder when request volume is capped.&lt;/p&gt;
&lt;h3&gt;
  
  
  💰 Cost Control
&lt;/h3&gt;

&lt;p&gt;Cloud infrastructure scales with traffic — but so do your bills. Preventing junk traffic keeps costs predictable.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✅ Quality of Service
&lt;/h3&gt;

&lt;p&gt;Legitimate users get consistent, fast responses instead of frustrating timeouts or errors.&lt;/p&gt;


&lt;h2&gt;
  
  
  ❌ Common Mistakes Without Rate Limiting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A single misconfigured client breaks the experience for everyone because its loop forgot &lt;code&gt;sleep()&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Botnets consume all your API capacity during an attack.
&lt;/li&gt;
&lt;li&gt;Your own QA team accidentally triggers a self-inflicted DoS by hammering endpoints without throttling.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🔥 One uncontrolled script can bring your entire service to its knees.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  ✅ Solution 1: &lt;code&gt;Rack::Attack&lt;/code&gt; + Redis (Great for Rails APIs)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/rack/rack-attack" rel="noopener noreferrer"&gt;Rack::Attack&lt;/a&gt; is a battle-tested gem for rate limiting in Ruby on Rails apps. Paired with &lt;strong&gt;Redis&lt;/strong&gt;, it scales across multiple servers and handles distributed traffic.&lt;/p&gt;
&lt;h3&gt;
  
  
  How It Works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define limits per IP, user token, or endpoint&lt;/li&gt;
&lt;li&gt;Automatically return &lt;code&gt;429 Too Many Requests&lt;/code&gt; when exceeded&lt;/li&gt;
&lt;li&gt;Allow safe-lists for health checks or internal services&lt;/li&gt;
&lt;li&gt;Log and monitor throttled requests for visibility&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"rack-attack"&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/rack_attack.rb&lt;/span&gt;
&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RedisCacheStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REDIS_URL"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"requests per IP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;period: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When the limit is hit, clients receive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rate_limited"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Too many requests. Please retry later."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With HTTP status &lt;code&gt;429 Too Many Requests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 Pro tip: Use custom throttling keys for authenticated users (&lt;code&gt;req.session['user_id']&lt;/code&gt;) or API keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Solution 2: Rails 7.2 Built-In Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Starting in &lt;strong&gt;Rails 7.2&lt;/strong&gt;, you can add rate limiting &lt;strong&gt;directly in controllers&lt;/strong&gt; — no extra gems needed!&lt;/p&gt;

&lt;p&gt;This feature is built into &lt;code&gt;ActionController&lt;/code&gt; and uses your existing cache store (Redis, Memcached, or memory).&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Limit Login Attempts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: :create&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents brute-force attacks by limiting login attempts to 5 per minute per session/IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Limit API Calls Per User
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::WidgetsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to:     &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;by:     &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="ss"&gt;with:   &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="s2"&gt;"Rate limit exceeded"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :too_many_requests&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="ss"&gt;only:   :index&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Options:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;to:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max number of allowed requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;within:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time window (e.g., &lt;code&gt;1.hour&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;by:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discriminator (user ID, IP, API key)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;with:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom response block when throttled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;only:&lt;/code&gt; / &lt;code&gt;except:&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Apply to specific actions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ Perfect for simple, declarative, per-action limits without middleware complexity.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🛡 Final Thoughts: Rate Limiting = API Armor
&lt;/h2&gt;

&lt;p&gt;Rate limiting isn’t just a performance tweak — it’s &lt;strong&gt;essential infrastructure hygiene&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents single points of failure&lt;/li&gt;
&lt;li&gt;Defends against common attacks&lt;/li&gt;
&lt;li&gt;Keeps your cloud costs under control&lt;/li&gt;
&lt;li&gt;Ensures fair access for all users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're using &lt;code&gt;Rack::Attack&lt;/code&gt; or Rails 7.2’s native tools, &lt;strong&gt;adding rate limiting is one of the highest-impact, lowest-effort improvements&lt;/strong&gt; you can make to your API.&lt;/p&gt;

&lt;p&gt;🛠 Start small. Protect critical endpoints. Scale as needed.&lt;/p&gt;

&lt;p&gt;Your future self will thank you.&lt;/p&gt;




&lt;p&gt;💬 &lt;strong&gt;Have questions?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Are you using rate limiting in production? What strategy works best for your app?&lt;/p&gt;

&lt;p&gt;Let me know in the comments 👇&lt;/p&gt;

&lt;h1&gt;
  
  
  rubyonrails #api #security #devops #webdevelopment #backend #rate-limiting #rails7 #redis #tutorial
&lt;/h1&gt;

</description>
    </item>
  </channel>
</rss>
