最近の仕事で得た経験談
重複するデータの直し方
何かしらの事件によって同一データが重複して登録されてしまっていて誰も気が付かず数ヶ月の間そのまま運用されているアプリケーションがあって、見た目は問題ないんだけど、データを正規化をするタイミングでマイグレートする必要があるときの話です。
また、レコードの重複が発覚するのは、何ヶ月もの先のことが多く、重複する片方のデータだけを修正して、月末処理なんかをしたときに修正されていない方の重複データを使っているデータのほうが壊れる、などしてやっと発覚するので早い段階で気がつけるようにそもそも重複データが入ってはまずいテーブルには適切な制約をはるのがいいでしょう。
以下解決するときにやったこととか考えたこと。
まずはその不整合なデータがどの範囲まで影響しているのかを確かめる。
データベースのスキーマ などをgrepして特定のキーがどのテーブルにあるかを見る。その時にそのテーブル(データモデル)が、あるイベントを機に削除されるなども考慮する必要がある。データモデルがステートメントを持っている場合もあるので、安易にデータを書き換えるのは危険なので、きちんと仕様を確認してから修正をするのが望ましい。
データの不整合の影響範囲を特定できたら、その次に正しいデータを再定義する。その場合も方法はいくつかあり、重複したデータが複数入っている(例えば同一商品が別のidで複数入っているなど)の場合には一つの商品だけ残し、影響範囲のテーブルに対してUPDATEをかける。その後重複したデータは消して、外部キー制約や商品が持つ一意のコード(例えばJANコードとか)でユニーク制約をはり、再発防止に努めましょう。
より厄介だと、例えば外部の値(GPSの座標など)を計算をした結果を距離にしてカラムに格納している場合などはこれはSQLだけでは処理するのが難しいので別の方法で対処することになります。
例えばRuby on Railsをつかっている場合には rails runner などでバッチ処理するなどでActiveRecord そのまま使えたり、座標計算のロジックが詰まっているクラスを再利用することができるので楽ができるでしょう。
新しい仕組みへマイグレートさせる
例えば、アプリケーションの振る舞いが大きくかわり、データモデルも新しいものに書き換える場合に感じ取れたこと。
まず、マイグレートするときにサービスのメンテナンスの時間を取れるのであれば、メンテナンス画面を出している間にデータをマイグレートすればいいでしょう。
逆にメンテナンス画面を出さない場合はいくつかの工夫が必要で 先にマイグレートする処理だけを書いて本番に出します。そのあと、バッチ処理でマイグレートします。そのあと、古いデータモデルの after_createやafter_updateなどをhookしてデータの変更があれば常に新しい仕組みにマイグレートさせるようにします。
この方法だと、余計にINSERTやUPDATEのつどSQLが余計に流れるのでサービスのパフォーマンスなどと一緒に考えてやる必要があります。
小技として、こういうマイグレート系の処理を書くときに息の長いサービスだと、壊れたデータが何年も眠っていたりします。しかし開発時に全てのデータを毎回バッチ処理しているとスクリプトの実装に時間がかかるので、例えば、開発環境では月ごとに100件無作為に取得してそれに対してバッチ処理を走らせるなど、対象のレコードを絞りトライできる回数を増やすことで前進感が演出されます。当然ですが、最後にすべてのレコードに対してバッチ処理をかける必要があるので忘れないようにしましょう。
まとめ
上で紹介したものでもコーナーケースを見落とすことが沢山あるので、一概には言えませんが、渡ろうとする石橋を何度も爆破しつつ何重にも検証コードを書くのが重要です。
そもそもこういう事態にならないためにも、データベースの制約はきちんとはりましょう。