<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Cactus Blog: Engineering]]></title><description><![CDATA[Thoughts and writings by the developers and engineers behind Cravd]]></description><link>https://blog.oncactus.com/s/engineering</link><image><url>https://substackcdn.com/image/fetch/$s_!jCX0!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58a6eefe-9a67-4251-9844-74c407e412b5_1024x1024.png</url><title>Cactus Blog: Engineering</title><link>https://blog.oncactus.com/s/engineering</link></image><generator>Substack</generator><lastBuildDate>Wed, 22 Apr 2026 11:24:27 GMT</lastBuildDate><atom:link href="https://blog.oncactus.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Cactus]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[cravd@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[cravd@substack.com]]></itunes:email><itunes:name><![CDATA[Cravd]]></itunes:name></itunes:owner><itunes:author><![CDATA[Cravd]]></itunes:author><googleplay:owner><![CDATA[cravd@substack.com]]></googleplay:owner><googleplay:email><![CDATA[cravd@substack.com]]></googleplay:email><googleplay:author><![CDATA[Cravd]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Building a Reward System with Rails]]></title><description><![CDATA[After implementing our basic referral system using the Refer gem, we needed to build a flexible reward system on top of it.]]></description><link>https://blog.oncactus.com/p/building-reward-system-rails</link><guid isPermaLink="false">https://blog.oncactus.com/p/building-reward-system-rails</guid><dc:creator><![CDATA[Avinash Joshi]]></dc:creator><pubDate>Fri, 31 Jan 2025 15:59:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qQ2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After <a href="https://blog.cravd.com/p/referral-system-in-rails">implementing our basic referral system</a> using the Refer gem, we needed to build a flexible reward system on top of it. This system needed to handle various reward scenarios: when users refer others, when referred users complete trial bookings, and when they subscribe to our service. Here's how we approached this challenge in our Rails monolith application.</p><h2><strong>Understanding the Components</strong></h2><p>Before diving into the implementation, let's understand the key components:</p><ul><li><p><strong>Trigger</strong>: The specific event that causes a reward to be created (e.g., completing a trial booking)</p></li><li><p><strong>Source</strong>: The original cause that led to the reward (typically the referral)</p></li><li><p><strong>Recipient</strong>: The user or household that receives the reward</p></li><li><p><strong>Amount</strong>: The reward value, stored in cents for precision using the money-rails gem</p></li></ul><h2><strong>The Core: The Reward Model</strong></h2><p>At the heart of our system is the <code>Reward</code> model:</p><pre><code>class Reward &lt; ApplicationRecord
  belongs_to :recipient, polymorphic: true
  belongs_to :trigger, polymorphic: true, optional: true
  belongs_to :source, polymorphic: true, optional: true

  monetize :amount_cents

  enum reason: {
    trial_start: 'trial_start',
    sub_complete: 'sub_complete',
    signup_discount: 'signup_discount'
  }, _prefix: true

  scope :unredeemed, -&gt; { where(redeemed_at: nil) }

  # ... methods for creating specific rewards ...
end</code></pre><h2><strong>The Rewardable Concern</strong></h2><p>To make it easy to add reward functionality to different models, we created a Rewardable concern:</p><pre><code># app/models/concerns/rewardable.rb

module Rewardable
  extend ActiveSupport::Concern

  included do
    has_many :rewards, as: :recipient
  end

  def total_earned
    Money.new(rewards.sum(:amount_cents), "CAD")
  end

  def unredeemed_rewards
    rewards.unredeemed
  end

  def redeem_rewards!
    unredeemed_rewards.update_all(redeemed_at: Time.current)
  end
end</code></pre><h2><strong>Extending the Referral Model</strong></h2><p>We extended the Refer gem's Referral model to integrate with our reward system:</p><pre><code><code>module ReferralExtensions
  extend ActiveSupport::Concern

  included do
    has_many :rewards, as: :source
  end

  def create_trial_rewards(trial_booking)
    Reward.create_for_trial_start(self, trial_booking)
  end

  def complete_subscription(subscription)
    Reward.create_for_sub_complete(self, subscription)
  end
end

# In config/initializers/refer.rb
Rails.application.config.to_prepare do
  Refer::Referral.include ReferralExtensions
end
</code></code></pre><h2><strong>Triggering Rewards</strong></h2><p>We set up our Booking model to trigger rewards when a booking is completed, as an example:</p><pre><code>class Booking &lt; ApplicationRecord
  after_save :create_trial_start_reward, if: :status_changed_to_completed?

  private

  def create_trial_start_reward
    referral = Refer::Referral.find_by(referred: household.owner)
    Reward.create_for_trial_start(referral, self) if referral
  end

  def status_changed_to_completed?
    saved_change_to_status? &amp;&amp; completed?
  end
end</code></pre><h2><strong>Conclusion</strong></h2><p>This flexible reward system allows us to easily add new types of rewards and associate them with different actions in our application. The use of polymorphic associations provides the flexibility to link rewards to various models, while the money-rails gem ensures precise handling of monetary amounts. The system has proven to be robust and easily extendable as our referral program grows.</p><p>The combination of the Refer gem for handling referrals and our custom reward system has created a powerful tool for incentivizing user growth and engagement on our platform.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qQ2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qQ2b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 424w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 848w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 1272w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qQ2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png" width="1280" height="852" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:852,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81242,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qQ2b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 424w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 848w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 1272w, https://substackcdn.com/image/fetch/$s_!qQ2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7283df09-fbd0-4d71-8e3b-42e83121bc59_1280x852.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[Implementing a Referral System in Rails]]></title><description><![CDATA[We recently implemented a referral system for Cravd, our marketplace platform that connects customers with personal home chefs.]]></description><link>https://blog.oncactus.com/p/referral-system-in-rails</link><guid isPermaLink="false">https://blog.oncactus.com/p/referral-system-in-rails</guid><dc:creator><![CDATA[Avinash Joshi]]></dc:creator><pubDate>Sun, 12 Jan 2025 23:34:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UOr5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We recently implemented a referral system for Cravd, our marketplace platform that connects customers with personal home chefs. As we've been growing organically through word of mouth, we wanted to accelerate this growth by implementing a formal referral program. Here's how we set it up using the Refer gem in our Rails application.</p><h2><strong>Setting Up the Refer Gem</strong></h2><p>First, we added the <a href="https://github.com/excid3/refer">Refer gem</a> to our Gemfile:</p><pre><code>gem "refer"</code></pre><p>After running <code>bundle install</code>, we generated the necessary migrations:</p><pre><code>rails generate refer:install
rails db:migrate</code></pre><p>This set up the basic database structure for tracking referrals.</p><h2><strong>Configuring the Referral Code Generator</strong></h2><p>We customized the referral code generation in an initializer file:</p><pre><code># config/initializers/refer.rb

Refer.code_generator = lambda do |referrer|
  get_existing_code(referrer).presence || generate_new_code(referrer.id)
end

Refer.param_name = :code

private

def get_existing_code(referrer)
  referrer.referral_codes.last&amp;.code
end

def generate_new_code(referrer_id)
  letters = ("A".."Z").to_a.sample(4).join
  padded_id = referrer_id.to_s.rjust(4, "0")
  "#{letters}-#{padded_id}"
end</code></pre><p>This creates unique, readable codes combining random letters and the referrer's ID.</p><h2><strong>Integrating with User Model</strong></h2><p>We added the <code>has_referrals</code> macro to our User model:</p><pre><code>class User &lt; ApplicationRecord
  has_referrals
  # ... other code ...
end</code></pre><h2><strong>Handling Referrals in the Controller</strong></h2><p>We updated our RegistrationsController to handle referrals during sign up:</p><pre><code>class RegistrationsController &lt; ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      refer @user
      start_new_session_for @user
      send_welcome_email
      redirect_to after_authentication_url, notice: "Welcome to Cravd!"
    else
      render :new, status: :unprocessable_content
    end
  end
end</code></pre><h2><strong>Handling Referrals in the Controller</strong></h2><p>In our <code>ReferralsController</code>, we implemented two key methods to manage referral codes and cookies:</p><pre><code>class ReferralsController &lt; ApplicationController
  set_referral_cookie only: [:show], if: -&gt; { Current.user.blank? &amp;&amp; !browser.bot? }
  before_action :set_referral_code, only: [:index, :show]

  private

  def set_referral_code
    return unless Current.user
    @referral_code = Current.user.referral_codes.last || Current.user.referral_codes.create
  end
end</code></pre><p>The <code>set_referral_cookie</code> method is used to store the referral code in a cookie when it's present in the request parameters, and <code>set_referral_code</code> method is used to create or retrieve a referral code for the current user. It&#8217;s used in the referrals controllers where I want to display the user's referral code.</p><h2><strong>Conclusion</strong></h2><p>With these pieces in place, <a href="https://cravd.com/refer">we now have a functional referral system</a>. Users can generate unique referral codes, share them, and we can track successful referrals. This implementation provides a solid foundation for our referral program, which we'll build upon to create a comprehensive reward system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UOr5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UOr5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UOr5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UOr5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!UOr5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b4761c2-4605-4432-944b-c36ac2ec2b63_1200x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item><item><title><![CDATA[Dropping Redis for Rails Slashed Our AWS Bill by 62%]]></title><description><![CDATA[Recently, we integrated Redis through AWS ElastiCache to power real-time updates in our AI-driven menu recommendation system. Our initial cloud setup was straightforward: a Rails 8 application deployed with Kamal on a t3a.small EC2 instance ($27.07/month) and RDS for the database ($32.47/month), totaling around $70/month across environments.]]></description><link>https://blog.oncactus.com/p/dropping-redis-for-rails-slashed</link><guid isPermaLink="false">https://blog.oncactus.com/p/dropping-redis-for-rails-slashed</guid><dc:creator><![CDATA[Avinash Joshi]]></dc:creator><pubDate>Mon, 09 Dec 2024 22:46:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gqxt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, we integrated Redis through AWS ElastiCache to power real-time updates in our <a href="https://cravd.com/maya/overview">AI-driven menu recommendation system</a>. Our initial <a href="https://world.hey.com/avinash/kamal-2-my-upgrade-journey-a1af9920#:~:text=A%20quick%20detour%20with%20my%20%22cloud%22%20setup">cloud setup</a> was straightforward: a Rails 8 application deployed with Kamal on a t3a.small EC2 instance ($27.07/month) and RDS for the database ($32.47/month), totaling around $70/month across environments.</p><h1><strong>Cost Surge</strong></h1><p>What started as an "elegant solution" quickly became a concern. By July 2024, ElastiCache costs had skyrocketed to $120.53, pushing our total AWS expenses to $197.811. The graph clearly shows how ElastiCache dominated our infrastructure costs, accounting for over 60% of monthly expenses.</p><h1><strong>The Solution</strong></h1><p>After successfully implementing <a href="https://github.com/rails/solid_queue">Solid Queue</a> and following the release of <a href="https://github.com/rails/solid_cable">Solid Cable</a> at <a href="https://rubyonrails.org/world/2024">Rails World 2024</a>, we pivoted to this database-backed alternative for real-time features. This eliminated our Redis dependency while maintaining full functionality. The data shows remarkable improvements:</p><ul><li><p>Monthly AWS costs decreased from $197.81 to $74.55</p></li><li><p>Infrastructure costs reduced by approximately 62%</p></li><li><p>Complete elimination of the $120.53 monthly ElastiCache expense</p></li></ul><h1><strong>Beyond the Numbers</strong></h1><p>The switch to Solid Queue significantly reduced our infrastructure complexity. By eliminating Redis, we've simplified our stack, making it easier to maintain, debug, and scale. This change aligns with Rails' philosophy of convention over configuration, keeping things simple and streamlined. Following this success, we integrated <a href="https://github.com/rails/solid_cache">Solid Cache</a>, completing the Solid trifecta.</p><h1><strong>Key Takeaway</strong></h1><p>This migration reinforces a valuable lesson: staying aligned with framework defaults often leads to both technical and financial benefits. Our tech stack is now simpler, more maintainable, and more cost-effective while maintaining all the functionality our application requires.<br><br>The cost breakdown graph demonstrates this transformation, showing sustained lower costs from October 2024 onward, with stable expenses around $75/month</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gqxt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gqxt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 424w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 848w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 1272w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gqxt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png" width="1456" height="1549" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1549,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:353733,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gqxt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 424w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 848w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 1272w, https://substackcdn.com/image/fetch/$s_!gqxt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F329f03c3-6eb2-4023-a835-5ee12abeb70c_2676x2847.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>]]></content:encoded></item></channel></rss>