Back to Course

Spot the Tax · Card 2 of 20

External I/O belongs outside the transaction

Why an HTTP call inside before_save slows down every save and can leave your data in a broken state.

The code

What will this cost you in six months?

class User < ApplicationRecord
  before_save :sync_to_mailchimp

  private

  def sync_to_mailchimp
    Mailchimp::Subscribers.upsert(
      email: email,
      merge_fields: { name: name }
    )
  end
end

The problem

Every time a user gets saved, the save now has to wait for Mailchimp to respond. That waiting happens inside the database transaction Rails opens for the save, which means the transaction stays open the whole time the API call is in flight. If Mailchimp is slow, every save gets slow with it; if Mailchimp is down, your saves fail entirely. And if anything later in the transaction causes it to roll back, the user save gets undone — but the Mailchimp call already went out, so you end up with a user that exists in Mailchimp but doesn't exist in your database.

Take a moment. Before revealing, try to work out the fix yourself. Where should the Mailchimp call live instead, and how do you want to handle a Mailchimp outage?