stevencodes.swe - May 24, 2025

Weekly notes on what I’m building, breaking, and learning

Hey, it’s Steven.

Here’s what I’ve been up to this week - a bit of writing, some backend cleanup, and revisiting old habits that make development smoother. As always, I’m sharing the things that actually helped me build better systems this week.

Let’s get into it 👇

🧠 From The Backend Lowdown: What Makes a Migration Unsafe?

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

Not all migrations are created equal. Some are quick and harmless. Others can lock your most important tables, block traffic, spike I/O, and even take down production. The difference usually comes down to how much data is affected, when you run the migration, and how much traffic is hitting your database at the time.

A dangerous migration might go unnoticed in staging or a low-traffic app - but the same change in production, under load, can cause outages.

Let's break down the most common causes of unsafe migrations - and why they're risky.

Locking Large Tables

Certain schema changes force the database to acquire an exclusive lock on a table. During that time:

  • Reads and writes are blocked from other sessions,

  • Any queued queries pile up,

  • Application threads start timing out,

  • And eventually, users see errors or your workers hang.

This is how seemingly innocent schema changes can bring down production.

Example:

class AddStatusToUsers < ActiveRecord::Migration[8.0]
  def change
    add_column :users, :status, :string, null: false
  end
end

This locks the entire users table while it:

  • Adds the column,

  • Applies the NOT NULL constraint,

  • And rewrites every row (because of the constraint).

On a high-traffic table, even a few seconds of blocked writes can trigger a chain reaction. As the migration holds a lock, incoming write queries start to pile up - and each one consumes a database connection while it waits. Before long, your app's connection pool fills up, and any new request that needs a DB connection is forced to wait.

Workers begin to hang as they time out waiting for connections. Requests start to fail. If this happens during a deploy, your health checks may fail too - which can trigger a deploy rollback or leave your app in a partially updated state. All of this can unfold in under a minute if the table is busy enough.

🛠 Dev Tool Highlight: Raycast Clipboard History

Raycast is already a supercharged Spotlight replacement, but its clipboard history is low-key life-changing. I’ve stopped re-copying the same shell commands or ENV values over and over. It just remembers. If you also like sending a ton of GIFs, it’s got that baked in too.

If you’re still copy-pasting like it’s 2007, give it a try.

📚 Reading Lately

Database Internals by Alex Petrov
I’m slowly working through this one. It’s dense, but if you want to understand how storage engines and indexing really work, it’s worth the effort.

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