module WriteAmplificationGuard
extend ActiveSupport::Concern
def update_if_changed(attrs)
changed = attrs.each_with_object({}) do |(key, value), acc|
cast = self.class.type_for_attribute(key.to_s).cast(value)
acc[key] = value unless attribute_for_database(key.to_s) == self.class.type_for_attribute(key.to_s).serialize(cast)
end
return false if changed.empty?
update(changed)
end
def touch_throttled(column = :updated_at, every: 5.minutes, now: Time.current)
stored = public_send(column)
return false if stored.present? && stored > (now - every)
update_column(column, now)
end
end
class User < ApplicationRecord
include WriteAmplificationGuard
enum role: { member: 0, moderator: 1, admin: 2 }
after_update :sync_role_permissions, if: :saved_change_to_role?
def record_activity(now: Time.current)
touch_throttled(:last_seen_at, every: 2.minutes, now: now)
end
def promote_to(new_role)
update_if_changed(role: new_role)
end
private
def sync_role_permissions
PermissionSyncJob.perform_later(id, saved_change_to_role.last)
end
end
class ActivityController < ApplicationController
before_action :authenticate_user!
def heartbeat
current_user.record_activity
head :no_content
end
def promote
user = User.find(params[:id])
authorize user, :promote?
if user.promote_to(params[:role])
render json: { status: "promoted", role: user.role }
else
render json: { status: "unchanged", role: user.role }, status: :ok
end
end
end
This snippet tackles a subtle source of database load in Rails apps: "write amplification" caused by UPDATE statements that touch columns whose values never actually changed. Even when ActiveRecord's dirty tracking prevents a full-row UPDATE, code paths that assign the same value, or that call touch/update_column unconditionally, still generate write traffic — new row versions in Postgres, WAL records, index maintenance, and replication bytes. On hot rows (a User whose last_seen_at is bumped on every request, a counter, a status flag) this churn dwarfs the useful work.
WriteAmplificationGuard concern centralizes the defensive logic. update_if_changed builds a hash of only the attributes whose incoming value differs from the current one, using attribute_for_database so type casting and comparison match what Postgres stores. If nothing differs it returns early without issuing SQL at all. touch_throttled guards the classic timestamp-bump pattern: it only writes when the stored timestamp is older than every, collapsing bursty updates (many requests per second) into at most one write per interval. The guard leans on will_save_change_to_attribute? semantics rather than blindly trusting the caller.
User model shows the concern in use. record_activity funnels through touch_throttled so a chatty endpoint cannot amplify last_seen_at writes, while promote_to uses update_if_changed so re-running a promotion is a genuine no-op. The after_update callback is written to fire side effects only when saved_change_to_role? is true, avoiding cascade work on rows that did not really change.
ActivityController is the trigger. It calls record_activity on every authenticated request but relies on the throttle to keep the write rate bounded, and it uses promote_to idempotently so retries and double-clicks are cheap.
The trade-off is an extra read/compare in Ruby to avoid a write in Postgres, which is almost always favorable because writes are far more expensive and contend on locks and indexes. The main pitfall is stale in-memory objects: comparisons use the loaded attributes, so a caller working from a fresh reload (or accepting a small throttle window) keeps the guard correct under concurrency.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.