データベースリファクタリングやデータ移行のタスクの進め方

よくある当たり前っぽい内容ですが、はてなに入る前はあまりやったことがなかったので勉強しながらやっていました(解析器の結果をapiで見せるみたいなことが多かったので、DBそもそもほとんど使っていなかった…)。最近はデータ移行職人業務をやっている。

前提

前提があったほうが説明が書きやすい。is_hogeのようなbooleanなフィールドがhogefugapiyoのようにenumな値を取るように変更が必要という前提で話を進めます。

作戦: 下の層から丁寧にやっていく

一気にやると大変なことになるので、下の層からちびちび進めていきましょう。Pull Requestを送るときに↓のようなやることリストを付けておくと、全体のどの辺をやっているか分かりやすくなるのでレビュアーにやさしい感じになりそうですね。

  • [model層]hogefugapiyoを表わすようなフィールドを追加
  • [DB層]hogefugapiyoを表わすようなフィールドを追加
  • [model層]is_hogeに破壊的な操作(作成/更新/削除)をするときに、hogefugapiyoを表わすカラムにも変更を反映
  • [DB層]is_hogeカラムの状態からhogefugapiyoを表わすカラムの残りを埋める
    • 例: is_hogeがtrueだったらhoge、falseだったらfugaみたいなSQLを発行したり
  • [model層]is_hogeではなくhogefugapiyoカラムのみで値を返すようにする
    • is_hogeカラムには書き込まれなくなる
  • [DB層]is_hogeカラムをdropする
  • [controller層]必要だったらhogefugapiyoの状態を返す、設定するエンドポイントを生やす
  • [controller層]is_hogeを返すようなAPIがあるようだったらhogefugapiyoフィールドも追加して返すようにする
  • [frontend層]APIでis_hogeを見ている箇所を追加されたhogefugapiyoフィールドを見て処理するように変更
  • [controller層]不要だったらis_hogeを返すようなAPIは消す

基本的には

  • 最初は両方に書く
  • ロジックを新しいほうを使うようにする
  • 使わなくなった箇所を消す

というのを隣接する層毎にやっていくとスムーズでした。

知ってる && 試すといいやつ

CASE式

is_hogeカラムに値によってhoge_fuga_typeカラムの値を埋める、みたいなSQLを発行したいとき。boolなので、UPDATEを二回発行してもいいけど、CASE式を使うと一回で書けるのでよい。

UPDATE target_table SET
hoge_fuga_type =
  CASE is_hoge
    WHEN true THEN 0 -- hoge
    WHEN false THEN 1 -- fuga
  END
;

ALTER TABLEにかかる時間を計測

ALTER TABLEに予想しないくらい時間がかかって、サービスに影響が出るというのは避けたいので、backupサーバー等で時間をはかっておくと安心です。トランザクションを貼って、ROLLBACKすれば大丈夫。

hoge_fuga_service=> BEGIN;
BEGIN
hoge_fuga_service=> \timing
Timing is on.
hoge_fuga_service=> ALTER TABLE "target_table" ADD COLUMN "hoge_fuga_type" INT NOT NULL DEFAULT 1;
ALTER TABLE
Time: 82.552 ms
hoge_fuga_service=> ALTER TABLE "target_table" DROP COLUMN "hoge_fuga__type";
ALTER TABLE
Time: 7.523 ms
hoge_fuga_service=> ROLLBACK;

達人に学ぶDB設計 徹底指南書

達人に学ぶDB設計 徹底指南書