Railsの零ダウンタイム移行ガイド!renameやカラム追加の安全な順序
生徒
「先生、サービスを動かしたままデータベースの項目名を変えたいんですけど、普通に名前を変えても大丈夫ですか?」
先生
「実は、動いているサービスでいきなり名前を変えるのは危険なんです。一瞬でもアプリがデータベースを見失うと、エラー画面が出てしまいます。」
生徒
「えっ、サイトが止まっちゃうんですか? ユーザーさんに迷惑をかけずに更新する方法を知りたいです!」
先生
「それが『零(ゼロ)ダウンタイム移行』ですね。安全な順序を守れば、誰にも気づかれずに裏側でこっそり変更できますよ。その手順を詳しく解説します!」
1. 零ダウンタイム(ゼロダウンタイム)とは何か?
Ruby on Rails(ルビーオンレイルズ)などのウェブ開発において、零ダウンタイム(ゼロダウンタイム)とは、新しいプログラムを公開したり、データベースをメンテナンスしたりする際に、サービスを一時停止(メンテナンス画面に)させることなく更新を完了させることを指します。
パソコンをあまり触ったことがない方に例えると、「走行中の電車の部品を、乗客を降ろさずに交換する」ような職人技です。通常、データベースのマイグレーション(設計図の変更)を行うと、一瞬だけ古いプログラムと新しいデータベースの間に「ズレ」が生じます。このズレが原因でサイトが表示されなくなることを防ぐために、特別な手順が必要になります。特に rename_column(名前変更)などは、そのまま使うと非常に危険な命令の一つです。
2. なぜいきなり「名前変更」をしてはいけないのか
Railsのマイグレーションファイルで rename_column を使うと、データベース上の項目名は一瞬で変わります。しかし、サーバーの上で動いている「古いプログラム」は、まだ古い名前の項目を探しに行こうとします。この時、データベースにはもうその名前がないため、「項目が見つかりません!」というエラー(例外)が発生し、ユーザーの画面が真っ白になってしまいます。
これを回避するには、古い名前と新しい名前が「共存」する期間を作る必要があります。これを「段階的な移行」と呼び、プロの現場では必須の知識となっています。安全な順序は、追加 → 同期 → 削除 の3ステップが基本です。これを守ることで、サイトの信頼性を保つことができます。
3. ステップ1:add_columnで新しい名前を追加する
最初のステップは、名前を変えるのではなく、新しい名前の項目(カラム)を新しく追加することです。元々ある old_name はそのままにして、新しく new_name を作ります。
この段階では、まだアプリは古い項目を使っています。新しい項目を追加するだけであれば、古いプログラムの動作を邪魔することはありません。いわば、古い看板の横に、こっそり新しい看板を立てるような作業です。Railsでは以下のようなマイグレーションファイルを作成します。
class AddNewColumnToUsers < ActiveRecord::Migration[7.0]
def change
# 元々の項目は残したまま、新しい項目を追加する
add_column :users, :new_title, :string
end
end
4. ステップ2:データを同期させ、読み書き先を切り替える
次に、古い項目に入っているデータを新しい項目へコピーします(これをbackfillと呼びます)。さらに、プログラムを修正して、「古い項目と新しい項目の両方に同時に書き込む」ように設定します。
データの不一致がなくなったことを確認したら、今度は「読み取る時だけ新しい項目を見る」ようにプログラムを書き換えて、本番環境へ公開します。これで、見た目上は新しい名前の項目を使っている状態になりますが、裏側ではまだ古い項目も最新の状態を保っています。もし新しい項目に不具合があった場合、すぐに古い方に戻せるという安全策(切り戻し)になります。
# モデル(app/models/user.rb)での記述イメージ
class User < ApplicationRecord
# 保存する時に古い項目にもコピーしておく
before_save :sync_names
def sync_names
self.old_title = self.new_title
end
end
5. ステップ3:add_indexを「同時」ではなく「安全」に
新しい項目で検索を行う場合、add_index(目次の追加)が必要になります。しかし、大きなテーブルに普通にインデックスを貼ると、その間データベースの書き込みができなくなる「テーブルロック」が発生し、サイトが止まってしまいます。
これを防ぐのが algorithm: :concurrently というオプションです。これは「他の人が使っている横で、こっそり目次を作る」という命令です。ただし、この命令を使うときはマイグレーションの中で disable_ddl_transaction! という設定を書く必要があります。これは「一度に全部やろうとしないで」という指示で、大規模なサイトを運営する上で欠かせないテクニックです。
class AddIndexToUsersNewTitle < ActiveRecord::Migration[7.0]
# ひとまとめに処理しない(インデックス作成中のロックを防ぐ)
disable_ddl_transaction!
def change
# こっそり並行してインデックスを作成する
add_index :users, :new_title, algorithm: :concurrently
end
end
6. ステップ4:不要になった古い項目を削除する
全てのプログラムが新しい項目 new_title を使い、データも完璧に揃い、検索速度も問題ないことが確認できたら、ようやく最後の仕上げです。remove_column を使って、古い項目 old_title を削除します。
この時、注意点があります。プログラムの中に古い項目を参照している場所が一箇所でも残っていると、削除した瞬間にエラーになります。そのため、まずは「プログラムから古い項目を見えないようにする(無視させる)」という修正を公開してから、数日待って、本当に誰も使っていないことを確認した上で削除を実行するのが、最も安全なベストプラクティスです。
class RemoveOldColumnFromUsers < ActiveRecord::Migration[7.0]
def change
# ついに役目を終えた古い項目を削除する
remove_column :users, :old_title, :string
end
end
7. 零ダウンタイム移行のメリットとコスト
この手順を踏むのは非常に手間がかかります。一回の rename_column で済むところを、3回や4回に分けて作業するからです。しかし、その対価として「ユーザーを一人も待たせない」という最高のサービス品質が手に入ります。
プログラミング未経験の方は、まず「データベースの変更は一歩間違えるとサイトが止まるデリケートな作業なんだ」と覚えるだけで十分です。そして、将来大きなサイトを運営するようになった時、この安全な順序を思い出してください。サイトが止まらないことで、夜中に緊急対応する辛い時間を減らすこともできるのです。これを実現するためのスキーマ設計こそが、エンジニアとしての信頼に直結します。
8. スキーマ設計の健全性を保つために
最後に、こうした複雑な移行を繰り返していると、データベースの構造(スキーマ)が複雑になりがちです。Railsには db/schema.rb という「現在の正解」を記録したファイルがあります。移行が終わるたびにこのファイルを確認し、余計な項目が残っていないか、インデックスが二重に貼られていないかを確認しましょう。
「とりあえず動くからいいや」ではなく、「将来誰が見ても分かりやすく、かつ安全に」設計することを心がけてください。地味な作業の積み重ねが、エラーのない美しいアプリケーションを生み出します。まずは小さな練習から、一歩ずつ進んでいきましょう!