概要
前置き
最近、サービスを止めずDBのデータを違うテーブルに移すのをよく行っています。
せっかくなので、その際の手順を記事にしました。
この記事では、ユーザーテーブルのメールアドレスの移行で話を進めます。
具体的には、ユーザーテーブルからメールアドレスを切り出し、新しく作成したメールアドレステーブルに移行する話です。
環境
- Ruby 3.1.2
- Rails 7.0.4
- ridgepole 1.1.0
移行前の状態
ユーザースキーマ
下記が、移行前のユーザーテーブルのスキーマです。
create_table 'users', charset: 'utf8mb4', force: :cascade do |t|
t.string 'name', null: false
t.string 'email', null: false
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index ['email'], name: 'index_users_on_email', unique: true
end
移行
1. ダブルライトするように変更しデプロイ
ユーザーモデルが追加/更新された際に、そのメールアドレスをユーザーメールアドレステーブルにもUPSERTされるように変更します。
以下が、追加するユーザーメールアドレススキーマです。
create_table 'user_emails', charset: 'utf8mb4', force: :cascade do |t|
t.string 'email', null: false
t.references :user, null: false, foreign_key: true
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index ['email'], name: 'index_user_emails_on_email', unique: true
end
ユーザーモデルに、ユーザーメールアドレスモデルをUPSERTするメソッドを追加し、after_save
に設定します。
class User < ApplicationRecord
after_save :upsert_user_email!
has_one :user_email, dependent: :destroy
private
def upsert_user_email!
if user_email.present?
user_email.update!(email:)
else
create_user_email!(email:)
end
end
end
これで新しく追加されるユーザーや変更されたユーザーに対して、ユーザーメールアドレステーブルにもデータが作成/更新されるようになりました。
2. 古いデータを移行する
移行用のRakeTaskを実装し、古いデータに対しての移行を行ないます。
本筋から少し外れるのでここは割愛します。
3. 古いカラムを参照している箇所を切り替えるデプロイ
2でDB上のデータ移行は終わりました。
次はソースコード上でユーザーモデルのメールアドレスを参照している箇所を、ユーザーメールアドレスモデルのメールアドレスに切り替えます。
4. 古いカラムを削除する準備
いきなり古いカラムを削除すると、マイグレーションとソースコードのデプロイ間にエラーが発生する場合があります。
なのでユーザーモデルに下記記述を追加し、事前にデプロイしておきます。
class User < ApplicationRecord
after_save :upsert_user_email!
has_one :user_email, dependent: :destroy
# 下記を追加
self.ignored_columns = [:email]
private
def upsert_user_email!
if user_email.present?
user_email.update!(email:)
else
create_user_email!(email:)
end
end
end
5. 古いカラムを削除する
4のデプロイ後、数日様子を見て問題なければ古いカラムを削除します。
まとめ
移行を段階的に行うことで、何か問題が発生した場合でも元の状態に戻すことができます。
全ての移行が完了するまでに時間はかかりますが、安全に移行できるのでおすすめです。