Rails: 巨大なテーブルのマイグレーション

いま、MySQLのデータベースにusersというテーブルがあって、bdayというカラム名をbirthdayに変更しつつ、新たにdeletedというBoolean型のカラムを追加したいとします。Railsマイグレーションを使えば簡単です。


class AlterUsers < ActiveRecord::Migration
def change
rename_column :users, :bday, :birthday
add_column :users, :deleted, :boolean
end
end

しかし、usersテーブルがすでに巨大である(例えば1,000万レコードを超えている)場合、ことは簡単ではありません。

何十分あるいは何時間もかかり、その間は何の経過報告もされません。一発勝負の場合にはかなり不安です。

そこで、私は http://onehub.com/blog/posts/adding-columns-to-large-mysql-tables-quickly/ を参考にして、次のようなマイグレーションスクリプトを作りました。


class AlterUsers < ActiveRecord::Migration
def up
create_table :new_users do |t|
t.string :email, null: false
t.string :password_digest
t.date :birthday
t.boolean :deleted

t.timestamps
end

r = execute("SELECT MAX(id) FROM users")
max = r.first[0]
loop do
r = execute("SELECT MAX(id) FROM new_users")
offset = r.first[0] || 0
break if offset == max
query = %Q{
INSERT INTO new_users
SELECT id, email, password_digest, bday, '0', created_at, updated_at
FROM users
WHERE id > #{offset}
ORDER BY id
LIMIT 10000
}.gsub(/\s+/, ' ').strip
execute(query)
end

rename_table :user, :old_users
rename_table :new_users, :users
end

def down
drop_table :users
rename_table :old_users, :users
end
end

これなら途中経過をターミナルに表示させることができます。なお、LIMITに指定する値を大きくしすぎると、「ERROR 1206 (HY000): The total number of locks exceeds the lock table size」というエラーが出る場合があります。