PIYO - Tech & Life -

Railsでマイグレーションを介さずにサーバーでDBを変更してしまったとき

本番環境で動いているRailsアプリケーションのDBにおいて、問題に緊急対応するために直接ALTER_TABLEしてしまったようなとき、Railsアプリケーション側ではどんな対応をすればいいか、というお話。

レアケースかもしれないし意外とあるかもしれない。

僕は実際に1年ほど前に開発していたRSSリーダーのときに経験したことがある。ある日の夜中、サービスにアクセスが集中してデータベースのレスポンスがめちゃくちゃ悪くなったとき、僕は普通に寝ていた。いや、それまでの対応で力尽きていたと言っていい。

サーバーがうんともすんとも言わないそんな状況の中、ヘルプで入ってくれている人がスキーマを調べてインデックスが足りない(!)ということに気がついたらしい。翌朝起きてみると「とりあえず直接インデックス追加してなんとかしました」という連絡が来ていた。

インデックス足りないとか!しょぼすぎるミスなのはさておいてひとまず問題は解決した。あれ?でも直接DBいじっちゃったらRails側のマイグレーションと整合性とれないぞ?

そういうときの対処法です。

そもそもマイグレーションとは?

そもそもrake db:migrateは何をやっているのかというと、db/migrate以下にあるファイルに書いてあるDBに対する変更処理をファイルの日付順に実施していくというものだ。このとき、DBのschema_migrationsというテーブルにこれまでに適用したマイグレーションの番号(日付の文字列)を持っている。

rake db:migrateではこのテーブルを見に行って未適用のマイグレーションを実行するような感じになっている。その都度schema_migrationsは更新される。

具体的な対応方法

Railsアプリケーション側

% rails generate migration AddIndexToHoge みたいなコマンドで新しいマイグレーションを生成する。そして、手作業で変更してしまった処理に相当する処理をマイグレーションファイルには書いておく。

例えば僕の経験したケースでは、プロダクションのデータベースでインデックスの追加を行った。そういうときはこのような感じのマイグレーションを書く。

class AddIndexToHoge < ActiveRecord::Migration
  def change
    add_index :hoges, :fuga, name:'idx_hoge_name'
  end
end

nameを敢えて指定しているのはプロダクションDBで行った変更で追加したインデックスの名前がRailsのデフォルトとは異なっていたから。

さて、これでRailsアプリケーション側での準備は整った。

プロダクションDB側

先ほど作ったマイグレーションの内容はすでに手動で適用済みであるため、もう一度マイグレーションが走るとエラーになってしまう。そこで先程作ったマイグレーションの番号をschema_migrationsに手動で追加してやる。

> INSERT INTO schema_migrations (version) VALUES(20131031113857);

これであたかもrake db:migrateでマイグレーションを適用したような結果にできる。

一応これらの対策で、プロダクションDBに対して手動で変更を行ってしまったとしてもRailsアプリケーションの流れに戻せるということになる。