Data Migration on Rails
Rails固有の話もあるが、DBを利用するアプリケーションの運用では必ずつきまとう問題なので言語やフレームワークが変わっても大枠は変わらないと思う
データ移行の定義
繰り返し実行されるようなデータ移行は範疇外とする
定期的に行ったり、ある程度柔軟性が必要だったりするデータ修正は画面とかで機能として提供する
ただ、新規事業の立ち上げ初期などではその前提がないこともある(運用でカバー) マスタデータではなくトランザクションデータを対象にしたデータ移行を主に考える
マスタデータとは全環境に共通して存在し、アプリケーションが正常に稼働するための前提条件となるデータ、としておく
とはいえマスタデータに1レコード追加するような操作を本記事中のやり方でやることもできるし、完全に切り離すのは難しいこともある
画面などからできるようにしておけ、というのは正論ではあるがすべてのマスタデータに対する操作をすべて画面で完結できるようなシステムは見たことがない(大規模なデータ変更などは画面からは難しいし気軽に実行されるのは困る)
前提
唯一の正解はない
各チーム・環境・事業フェーズ・事業特性などの変数が生み出すトレードオフの中で選択されていく
メリット・デメリットの整理、どのようなトレードオフがあるのかを理解して選択できるようになる必要がある
トレードオフ
何が交換されるのか?
コスト
仕組みを整備するコスト
仕組みごとに必要とされる開発者・チームの時間
マシンリソース
得られるメリット
オペレーションの実行記録
実行の安全性
誰でも好きなスクリプトを走らせられるのは危険なので権限管理の問題がある 緊急時にはより速く実行できる手段を用意すべきか
NO
緊急時に普段やらない手順をミスをしないと断言できるほど人間はよくできていない
かといって最も高速に実行できる手段を平常時で選ぶと失うものが大きい
平常時であろうと緊急時であろうと堅牢な手段でデータ移行を行うことが望ましい
どうしても緊急でデータを直さないといけないような状況(放置すると被害が広がるなど)は、システムメンテナンスが必要である可能性が高い
ohbarye.icon システムを止めることができた東証と、止められなかったドコモ口座
事業特性にもよる
Rails discussion
2020年に「公式な方法がない」として議論が開始
script/migrate/*ディレクトリを用意
require_relative "../../config/environment"から始まるRubyスクリプト
ruby script/migrate/foo.rbで実行できるようにするtrick
bin/rails r script/migrate/foo.rbを使うならこの行は不要
他にもscript/benchmark/*ディレクトリなどがある
ohbarye.icon このスクリプトをどの環境でどう実行するかの問題は残る
data migrationのアプローチと"公式"の推奨についてトピックを立てた
Rails Guides
2024 Rails 7.2のリリース付近でガイドが刷新され、以下の記述が加わった
Data migrations involve transforming or moving data within your database. In Rails, it is generally not advised to perform data migrations using migration files. Here’s why:
・ Separation of Concerns: Schema changes and data changes have different lifecycles and purposes. Schema changes alter the structure of your database, while data changes alter the content.
・Rollback Complexity: Data migrations can be hard to rollback safely and predictably.
・Performance: Data migrations can take a long time to run and may lock your tables, affecting application performance and availability.
Instead, consider using the maintenance_tasks gem. This gem provides a framework for creating and managing data migrations and other maintenance tasks in a way that is safe and easy to manage without interfering with schema migrations. 1. Rails consoleから実行
推奨してる記事は見つからなかった
ohbarye.icon
長所
最もコストが低い方法の一つ
短所
オペレーションミスのリスクが高い
長いコード辺を貼り付けるのが本当に安全か
(場合によっては)実行ログが残らない
監査ログを残すことが求められる企業ではNG
rails consoleをラップしたCLIで操作がログに残るようにするなど対応の余地はある
秘伝のタレ化、属人化の危険
(場合によっては)Gitのログに残らない
コードスニペットをチケット管理ツールに貼り付けたりレビューする
ペアで作業するなどの工夫が見られる
検証が自動テストではない
本番相当のDBに対してテストして成功したとしても、実行の瞬間まで本番のデータがそのままとは限らない
ガードコードで対処するなど、パターンの想定を行うならテストを書いたほうが再現性もあり楽
接続不可にするか、どうしても必要でも繋いだらwarningが出るようにしておくなど
rails consoleでdebug用のgem (byebug, pry etc.) を使用する場合、本番環境で動く or requireされることが想定されていない可能性がある
性能劣化
思わぬバグを踏むかもしれない
2. rails runnner
サーバにSSHしてone linerを実行したりスニペットを貼り付けて実行してるならrails consoleとほぼ一緒に思える スニペットをGitレポジトリに含めてデプロイしておいて、それからSSHして実行するならrake taskとほぼ一緒に思える
3. SQLをDBに直接実行
推奨してる記事は見つからなかった
ohbarye.icon
Rails consoleとほぼ同じだがより辛いケースがある
長所
最もコストが低い方法の一つ
DBに対して行われる変更が完全に明示的
短所
コールバックが実行されず、予期しないデータを生む可能性がある
Railsの思想としてDBよりもアプリケーションが賢く振る舞うようになっているので、アプリケーションコードでの実行結果と完全に等しいSQLを書き上げるぐらいならコードを実行したほうが早いケースは多そう
ただ、さらに悪いアイデアに思える
意外にもSQLクライアントで実行していた
4. rake task書いて実行
Railsのmigrationはdata migrationではなくschema migrationにのみ適用されるべき
db/migrateの難点
後世まで残り、不要なスクリプトも実行され続ける。ビジネスロジックではないものが永遠に残るべきではない
データ移行の再現性を意図したものであっても、rake db:schema:loadやrake db:resetしたときにはデータ移行されない
どちらもschema.rbをロードするだけ
デプロイがデータ移行に依存してしまう
当時はなかったのか?と思ったが8年前からこの段落はあるようだ
rake taskを使うメリット
データ移行のカプセル化、堅牢なデプロイ
データ移行の失敗がデプロイに影響しない
データ移行プロセスの柔軟なコントロールが可能
デプロイのタイミングによらない
パラメータによる制御
デメリット
rake taskの実行をデプロイスクリプトに足すか、手動実行の手間がある
用が済んだら消す手間もある
いずれにしても常に開発環境やステージング環境で最初にドライランを行うべき
基本的にThoughtbot社の記事と同じ
基本的にThoughtbot社の記事と同じ
rake taskの手動実行は環境が増えると大変になり、実行漏れが起きることがあるので別の方法を模索する旨のコメント
データ変更はrakeのタスクとして書く
migrationには書かない
初期データはseedに書く
5. db/migrateで実行
seedの使い方いくつか
最初の一回のためのもの
マスタデータのためのもの
ActiveRecord::Base.reset_column_informationしないと困る
schema cacheのためテーブル定義が変わるとマイグレーションが失敗する
専用のモデルを用意するアプローチ
2024 Rails Guideが7.2で刷新されるまではこの手法が紹介されていたが、刷新以降は非推奨となった GitLabだとmigrationファイルの中でデータ移行を行っている
これは推測ですが、GitLabの場合は
・ 本番用のアプリケーションをOSSとして配布している
・GitLabの運用者がデータ移行を意識せずにバージョンアップできるようにしたい
という事情があるのでこういう手法をとってるのではないかと思います。
db:migrateに伴うデータの変更は db/post_migrate 内のmigrationファイルでそういうことをやってることが多い
ohbarye.icon
長所
用意するものが少ない
コードをバージョン管理に残せる
短所
モデルクラスを触るとdb:migrateが失敗する要因になりやすい
reset_column_informationの問題
モデルクラス側の変更
その他
6. migration_data gem
db/migrateのファイル内で直接モデルを触る
schema cacheの問題があるのでおすすめしない
アジャイルならモデル定義が変わることは当然
db/migrateのファイル内でマイグレーション単位のnamespaceを切ってモデルに触る
schema cacheの問題を避けられる
ただしテーブル間の関連付けが行われるとnamespace付きのモデルへ関連付けられてしまう
まったくおすすめしない
ohbarye.icon Rails communityでの標準的なやり方とあるが、そうだったのか…?
raw SQL
SQLの知識が時に強く求められる
修正コードを書くのに時間がかかるぐらい、そこそこおすすめ
ohbarye.icon コールバックのことには触れてない
db:seed
最初のデータ投入では便利だが肥大化していく
ファイル分割などで対応する
2回目の実行でどうなるか、を考慮しないといけない
初期データ投入とデータ修正という異なる概念のものを混ぜ合わせることになるのでまったくおすすめしない
ohbarye.icon マスタデータとトランザクションデータの違いもあると思う
マスタはseedに書いてメンテナンスしてもよいがトランザクションデータをseedに書く意味は(ほとんどの環境にとって)ない
rake task
ohbarye.icon 触れているが何もデメリットを語ってないのでちょっとアンフェアに感じた
rails console
やらないことを望む
migration_data gem
db/migrateのときにマイグレーションファイル内のdataメソッドを実行する
db/rollbackでは実行しない
データ移行のテストを書いて正しさを検証しよう、というアイデア
2015追記
古いデータ移行スクリプトが邪魔になったらdb:migrate:squashで掃除できるようにした
が、squashにはいくつか課題がある。複数開発者でコンフリクトすると不整合が起きる
2017追記
データ処理量が多い場合、このgemを使わないほうが良いかもしれない
デプロイ処理が遅くなりダウンタイムが伸びる可能性
代替手段として以下がある
rake task
data-migrate, seed_migrationのようなgemの利用
ohbarye.icon うーん、最初からrake taskで良かったような気も
7. data-migrate
データ移行用のスクリプトをdb/dataに配置する
シーケンシャルに実行されることを保証できる
schema migrationと同時に実行するか、単体で実行する
他の手法の問題点を指摘したあとでdata-migrate gemを紹介
db:migrate
長所
数分でデータ移行をshipできるので早い
データ移行が複数回実行されないことをmigrationの機構で保証できる
欠点
schemaが変わると失敗することがある。そのようにマイグレーションが古くなることがあるが、outdatedなものをすぐに見つける方法がない
頻繁にマイグレーションを実行し続けるとか
Railsの規約に従っていない。db/migrateのファイルはデータベースレコードではなく、データベース構造を移行すべき
rake taskを書く
すべてのデータ修正をまとめてやる
過去から現在まですべてのデータ移行を行う
bundle exec rake data:migrations
Rake.application.in_namespace(:data) {|namespace| namespace.tasks.each(&:invoke)}
個別のデータ移行
bundle exec rake data:change_foo
data namespaceに置く
taskの中身をPOROで記述すればテスト容易性も高まる 長所
データ移行のテストカバレッジが上がる
失敗するデータ移行に気付ける
欠点
コードの記述量は増える
このルールを強制する仕組み(文書化しておくなど)が必要
data-migrate
利点
コードの記述量をさほど増やさない
rake taskのとき同様、テストが書けて失敗に気づける
データの追跡を可能
テスト時にもデータ移行後の状態がほしければrake db:schema:load:with_dataを予め実行しておく
欠点
プロジェクトに新しい依存関係を追加しなければならない
この新しい慣習を文書化しなければならない
上記の利点から、OmbuLabsではdata_migrateを使うことが最適な戦略と考えている
ohbarye.icon qsonaさんから教えてもらった
data-migrate が依存していた Rails の内部挙動がpatchバージョンで変わって、その影響で、db:migrate:with_data がステータス0で終了してて(内部的には新規migrationが存在しない扱いになってた)必要なmigrationが実行されないまま本番にデプロイ
8. seed_migration
data-migrateとほぼ同じ
db:migrateにフックしてないだけ
メンテナンス止まり気味
9. maintenance_tasks
2020年の9月頃に開発開始
@sanfrecce_osakaの推しポイント
テストが書ける
AR を使える
実行履歴が残る
運用にも使える
ダッシュボード上の操作に対して権限設定できる
Job Iteration を活用したJobの一時停止・再開機能がある
大量件数のレコードを処理する際にDB負荷を抑えつつ処理できる 長時間Jobを実行する際のJob実行中にサーバーとの接続が切れるような問題を回避できる
ruby-jpでのアンケートログ
2020-10
本番DBのデータ修正は基本的には (Choose one)
Results (45 voters):
████████ (23) rake task書いて実行
████ (11) Rails consoleから修正
██ (5) DBつないでSQL実行
█ (4) その他
█ (2) Abstain
2024-09
本番DBの一時的なデータの更新・追加・削除(≒DMLの実行)は
Results (42 voters):
████████ (22) rake task書いて実行
███ (8) その他(maintenance_tasks gem利用・場合による等)
███ (7) db:migrateで実行
█ (3) Rails consoleから修正
(0) DBつないでSQL実行
引き続きrake taskが主流
DBつないでSQL実行がなくなった
関連