On January 10, 2024, our code base turns 12 years old. For a repo with this much history, we’d expect the business needs and features to have evolved over time. Surely in the quest to ship new features, we might have forgotten to remove all the dead code.

One of our more entangled models is our company spaghetti model (or god object or all-knowing object). Ideally, our domain logic lives in domain modules whereas spaghetti models can become dumping grounds for domain concepts. While attempting to clean up the company model, I didn’t know where to start. After a code read-through, I looked at our data model. I’m glad that I did. I discovered that we had started a column migration to another table but had not finished the move.

Addressing tech debt in our database tables, makes it easier to remove some deprecated methods in our code.

Explore your table

We have many nullable columns, so I was curious if any of them were no longer used. For each column, I ran this query

I came across five columns that hadn’t any recent inserts

Research the history of the column

This is strong evidence that the column isn’t used. Since I was new to the code base, I did additional research to see what’s up.

1. Check out the Ruby on Rails database migrations

2. Examine the history of the code

The migrations told me when the columns were added. The git commits told me who to talk to if I had more questions.

Notify people the column is going away

If you have a business intelligence team or data analysts, you’ll want to verify that removing the column won’t break their systems. Any downstream integrations with Kafka, salesforce, or database replication could be affected by your change. I like giving them a heads-up when I start the work so that they are surprised when I’m ready to drop the column.

What If — Let’s learn what will break and start fixing it

What if the column was gone, what would break? Let’s find out and then fix it. On a temporary branch, drop the column.

1. bin/rails g migration DropIsTrustedFromCompany

2. bin/rake db:migrate

3. verify that the branch passes in CI

You can now start fixing anything that’s broken with new pull requests.

For example, I learned that we had a .rabl file (ruby templating for JSON) that displayed the is_trusted which caused many tests to fail. I then removed it from the .rabl file and re-ran my tests.

Add logging

The tests have given us the confidence that it’s safe to delete the column. However, there still might be untested production code that’s using it.

I’ve been burned by meta-programming. If this table is in the center of all the action, taking a few extra steps is worth giving you the confidence that nothing is going to fall apart in the next step.

After deploying to production, wait for what is a reasonable amount of time in your business context. You might just be surprised!

Ignore the column

The tests and logging have given us the confidence that it’s safe to delete the column. Yet, we will proceed with confidence and caution.

It’s tempting to jump to the next step and run the delete migration. In a large Ruby on Rails system, it’s a best practice to never roll back a database migration. This ignore column step is our last line of defense before dropping the column for real. If anything goes wrong here, at most, it will cause a rails exception.

  1. We now ignore the column. This allows us to deploy to production, and if anything is still using the column, we can roll back the deployment and still have all our existing data.

2. If you are using the annotate gem, you’ll want to update your annotations. bin/rails annotate_models

3. If you are using the active_record_doctor gem, you’ll want to update its output too.

Drop the Column

Now it’s time to use the migration you wrote earlier for real!

On a large production system, it may be tricky to find the right time to drop a column. We use Ghost to manage our MySQL database changes. Holding a lock onto a key table may prove tricky. If it takes you a while to run your migration on production, double-check that the `ignored_column` still contains your column. If it doesn’t, you’ll have a production incident if your rails servers are not rebooted during the migration. Rails keeps a cache of column names in memory, and it will continue to generate SQL that contains your old column until it restarts. The safest course of action is to verify that ignored_columns contains your column each time you attempt to run your production migration.

You did it. Congratulations. Take a break. Go for a walk. You’ve earned it.

Clean up

  1. Remove the ignore column from the model.
  2. Remove the logging that you added for the setter and getter methods.

Conclusion

This blog post walks you through the process of deleting a column from a Ruby on Rails production database in a way that minimizes creating a production incident.