👋 Hey friends,

Here’s what I’ve got in store for you this week:

  • Another snippet from The Backend Lowdown

  • A cool tool for making code changes

Let’s get into it 👇

The Backend Lowdown: Chapter 1 Preview

Every newsletter will include a snippet from my book in progress, The Backend Lowdown, available for $5 right now on Gumroad!

Get The Backend Lowdown →

Patterns for Safe Migrations

Now that we've covered how migrations can go wrong, let's talk about how to make them go right.

Most safe migrations boil down to a few key principles:

  • Avoid locking large tables

  • Break changes into multiple steps

  • Backfill data carefully

  • Avoid assumptions about data shape

  • Use the database's features to your advantage

Here are some common safe patterns, with real examples and tradeoffs explained:

Add Columns in Multiple Steps

Never add a column with a NOT NULL constraint and a default in a single migration. This forces the database to backfill and rewrite the entire table in one transaction - locking it.

Bad:

add_column :users, :status, :string, null: false, default: "active"

Good:

# Step 1 - Add as nullable, no default
add_column :users, :status, :string

# Step 2 - Backfill in batches
User.find_in_batches do |batch|
  batch.each { |user| user.update_columns(status: "active") }
end

# Step 3 - Add NOT NULL constraint
change_column_null :users, :status, false

# Optional Step 4 - Set default for new rows
change_column_default :users, :status, "active"

This avoids locking, gives you control over the backfill, and lets you verify before locking in constraints.

Create Indexes Concurrently

Creating indexes on large tables without concurrency will block writes until the index is done.

Safe ActiveRecord pattern for Postgres:

disable_ddl_transaction! # note: this line goes outside the `class`
add_index :orders, :created_at, algorithm: :concurrently

This avoids blocking but requires:

  • No wrapping transaction

  • Careful migration ordering (you can't combine this with other schema changes)

Tool of the week: ast-grep (grep/sed, but it understands syntax)

One of the sneakiest ways systems “scale” poorly isn’t traffic, it’s codebase change. Upgrading libraries, sweeping API migrations, or cleaning up patterns across 200 files usually devolves into either:

  • risky regex replacements, or

  • a week of manual edits + review fatigue

ast-grep is a CLI for structural search + rewrite: instead of matching raw text, it matches AST nodes (syntax structure). Their own pitch is basically “syntax-aware grep/sed” built for finding and modifying patterns across lots of files.

What I like about it: you write patterns that look like code, not regex soup, and you can use meta-variables (wildcards) to capture parts of the match.

Say you want to replace the classic JS pattern:

a && a()

with optional chaining:

a?.()

ast-grep has a one-liner example in their docs:

ast-grep -p '$A && $A()' -r '$A?.()'

(That $A is a meta-variable that captures the “thing” you’re checking/calling.)

I think it’s an awesome tool that can help make sweeping code changes a lot easier and less cumbersome, so go check it out!

That’s a wrap for this week. If something here made your day smoother, feel free to reply and tell me about it. And if you think a friend or teammate would enjoy this too, I’d be grateful if you shared it with them.

Until next time,
Steven

Keep Reading