8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby on Rails】稼働中アプリケーションでいきなりremove_columnするのが危険な理由

Last updated at Posted at 2025-03-19

はじめに

Ruby on Railsでアプリケーションを構築していて「仕様変更に伴いカラムを使わなくなったので削除しよう」という機会はあるかと思います。その際、ソースコードをチェックして当該カラムを参照している箇所が無かったからといって、いきなり remove_column をしてしまうのはちょっと待った方が良いかもしれません。
特に、下記のような冗長構成を取っている場合は注意が必要です。

何が発生するのか

例えば、以下のように「projects」「tasks」テーブルを作成していたとします。(projectsとtasksは「1 対 多」の関係)

class CreateProjects < ActiveRecord::Migration[7.1]
  def change
    create_table :projects do |t|
      t.string :name, null: false, comment: 'プロジェクト名'
      t.string :category,          comment: 'カテゴリ名' # このカラムを削除します
      t.timestamps
    end
  end
end

class CreateTasks < ActiveRecord::Migration[7.1]
  def change
    create_table :tasks do |t|
      t.references :project
      t.string     :name, null: false, comment: 'タスク名'
      t.timestamps
    end
  end
end
project.rb
class Project < ApplicationRecord
  has_many :tasks
end
task.rb
class Task < ApplicationRecord
  belongs_to :project
end

categoryカラムを使わなくなったので、ソースコード上で参照している箇所が無いことを確かめたのち、カラム削除用のマイグレーションファイルを作って「サーバ1」「サーバ2」で動かしているRailsアプリケーションへ反映します。

class RemoveCategoryToProjects < ActiveRecord::Migration[7.1]
  def change
    remove_column :projects, :category, :string
  end
end

すると、変更をアプリケーションへ反映中に、サーバ2で下記のエラーが流れ始めました。

An error occurred when inspecting the object: #<ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'projects.category' in 'field list'>

ActiveRecordがテーブル情報をキャッシュしている

実は、ActiveRecordはテーブル情報をキャッシュしており、eager_load 等のメソッドではキャッシュ情報を元にクエリを組み立てています。

irb(main):003> Task.eager_load(:project)
  SQL (1.2ms)  SELECT `tasks`.`id` AS t0_r0, `tasks`.`project_id` AS t0_r1, `tasks`.`name` AS t0_r2, `tasks`.`created_at` AS t0_r3, `tasks`.`updated_at` AS t0_r4, `projects`.`id` AS t1_r0, `projects`.`name` AS t1_r1, `projects`.`category` AS t1_r2, `projects`.`created_at` AS t1_r3, `projects`.`updated_at` AS t1_r4 FROM `tasks` LEFT OUTER JOIN `projects` ON `projects`.`id` = `tasks`.`project_id` /* loading for pp */ LIMIT 11
An error occurred when inspecting the object: #<ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'projects.category' in 'field list'>

今回の例は、「サーバ1への変更反映が完了してcategoryカラムが削除されたが、サーバ2が持っているキャッシュがcategoryカラムを参照し続けてエラーを引き起こした」というものでした。
サーバ2のキャッシュも更新されれば解決しますが、どうしても複数サーバへの変更反映(≒キャッシュ更新)にタイムラグが発生する以上、稼働中のアプリケーションでいきなりカラムを削除するのは危険なようです。

ignored_columnsを使う

安全にカラムを削除するには、ignored_columns を用いた2段階のアプローチが推奨されているようです。
具体的には、まずは削除予定のカラムに対し ignored_columns を指定して参照とキャッシュを断ち、その後に remove_column のマイグレーションを実行するというものです。

class Project < ApplicationRecord
  has_many :tasks

  # カラムが存在していても参照・キャッシュしなくなる
  self.ignored_columns += [:category]
end

まだcategoryカラムがprojectsテーブルに存在している状態でも、当該カラムを参照しなくなっていることが確認できます。

irb(main):005> Project.columns_hash["category"]
=> nil

まとめ

  • 削除する予定のカラムを直接参照していなくても、ActiveRecordがキャッシュとして情報を保持している
  • 安全にカラムを削除するならば、先に ignored_columns 指定をかけたソースコードを反映させてから、remove_column を行う

参考

ActiveRecord::ModelSchema::ClassMethods
Rails: Active Recordモデルのカラムを安全に削除する(翻訳)
ActiveRecord の Schema Cache と運用 Tips - Please Sleep

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?