Data Migration on Rails
Railsでデータ移行を行う方法について考察
Rails固有の話もあるが、DBを利用するアプリケーションの運用では必ずつきまとう問題なので言語やフレームワークが変わっても大枠は変わらないと思う
データ移行の定義
スキーマのマイグレーション(≒DDLの実行)ではなく一時的なデータの更新・追加・削除(≒DMLの実行)をデータマイグレーションとする
繰り返し実行されるようなデータ移行は範疇外とする
定期的に行ったり、ある程度柔軟性が必要だったりするデータ修正は画面とかで機能として提供する
ただ、新規事業の立ち上げ初期などではその前提がないこともある(運用でカバー)
マスタデータではなくトランザクションデータを対象にしたデータ移行を主に考える
マスタデータとは全環境に共通して存在し、アプリケーションが正常に稼働するための前提条件となるデータ、としておく
とはいえマスタデータに1レコード追加するような操作を本記事中のやり方でやることもできるし、完全に切り離すのは難しいこともある
画面などからできるようにしておけ、というのは正論ではあるがすべてのマスタデータに対する操作をすべて画面で完結できるようなシステムは見たことがない(大規模なデータ変更などは画面からは難しいし気軽に実行されるのは困る)
関連 マスタデータ管理 in Rails
前提
唯一の正解はない
各チーム・環境・事業フェーズ・事業特性などの変数が生み出すトレードオフの中で選択されていく
メリット・デメリットの整理、どのようなトレードオフがあるのかを理解して選択できるようになる必要がある
トレードオフ
何が交換されるのか?
コスト
仕組みを整備するコスト
仕組みごとに必要とされる開発者・チームの時間
マシンリソース
得られるメリット
自動テストを伴う堅牢なデータ修正
デプロイからの分離
バージョン管理ツールへの記録
オペレーションの実行記録
実行の安全性
誰でも好きなスクリプトを走らせられるのは危険なので権限管理の問題がある
ただ、品質と速度はトレードオフではないので上述のメリットを「高い品質」の一因と捉えるなら早期に整備することが開発の速度へのダメージをへらすことになる
緊急時にはより速く実行できる手段を用意すべきか
NO
緊急時に普段やらない手順をミスをしないと断言できるほど人間はよくできていない
かといって最も高速に実行できる手段を平常時で選ぶと失うものが大きい
平常時であろうと緊急時であろうと堅牢な手段でデータ移行を行うことが望ましい
どうしても緊急でデータを直さないといけないような状況(放置すると被害が広がるなど)は、システムメンテナンスが必要である可能性が高い
ohbarye.icon システムを止めることができた東証と、止められなかったドコモ口座
事業特性にもよる
Rails discussion
https://discuss.rubyonrails.org/t/is-there-any-official-way-to-organize-one-off-scripts/74186
2020年に「公式な方法がない」として議論が開始
そのため様々な車輪の再発明が起きている
https://github.com/dimroc/datafix dead project
https://github.com/anjlab/rails-data-migrations
https://github.com/ilyakatz/data-migrate
37signalsのBasecampでは以下のようにしている by DHH
script/migrate/*ディレクトリを用意
require_relative "../../config/environment"から始まるRubyスクリプト
ruby script/migrate/foo.rbで実行できるようにするtrick
bin/rails r script/migrate/foo.rbを使うならこの行は不要
他にもscript/benchmark/*ディレクトリなどがある
2024年に https://github.com/rails/rails/pull/52335 がマージされ、rails g scriptコマンドが増えた
Rails 8に入るかも
ohbarye.icon このスクリプトをどの環境でどう実行するかの問題は残る
ohbarye.icon https://discuss.rubyonrails.org/t/approaches-to-data-migration-on-rails-applications/86705
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.
https://github.com/rails/rails/pull/51928/files#r1639787463
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に直接実行
推奨してる記事は見つからなかった
Change data in migrations like a bossでマシな方法として触れられているぐらい
ohbarye.icon
Rails consoleとほぼ同じだがより辛いケースがある
長所
最もコストが低い方法の一つ
DBに対して行われる変更が完全に明示的
短所
コールバックが実行されず、予期しないデータを生む可能性がある
Railsの思想としてDBよりもアプリケーションが賢く振る舞うようになっているので、アプリケーションコードでの実行結果と完全に等しいSQLを書き上げるぐらいならコードを実行したほうが早いケースは多そう
Sequel ProやMySQL WorkbenchやマネージドサービスのWeb UIなどGUIでデータを操作できるツールを使う、というのもこれとほぼ同じ
ただ、さらに悪いアイデアに思える
2024 開発者が安心して実行可能なSQL実行基盤の導入と運用 #ベッテク月間
意外にもSQLクライアントで実行していた
Bytebaseというツールに移行したとのこと
4. rake task書いて実行
2015 Data Migrations in Rails
Thoughtbot社
Railsのmigrationはdata migrationではなくschema migrationにのみ適用されるべき
db/migrateの難点
後世まで残り、不要なスクリプトも実行され続ける。ビジネスロジックではないものが永遠に残るべきではない
データ移行の再現性を意図したものであっても、rake db:schema:loadやrake db:resetしたときにはデータ移行されない
どちらもschema.rbをロードするだけ
デプロイがデータ移行に依存してしまう
ohbarye.icon Separation of schema migration and deployを行っていない場合の話か
Rails Guide https://guides.rubyonrails.org/active_record_migrations.html のmigrationの説明の趣旨にはdata migrationの話はないため
ohbarye.icon 一応 https://guides.rubyonrails.org/active_record_migrations.html#migrations-and-seed-data がある
当時はなかったのか?と思ったが8年前からこの段落はあるようだ
https://github.com/rails/rails/commit/4a938362915366e791e8d279605b5f0c12c5541f
rake taskを使うメリット
データ移行のカプセル化、堅牢なデプロイ
データ移行の失敗がデプロイに影響しない
データ移行プロセスの柔軟なコントロールが可能
デプロイのタイミングによらない
パラメータによる制御
デメリット
rake taskの実行をデプロイスクリプトに足すか、手動実行の手間がある
用が済んだら消す手間もある
いずれにしても常に開発環境やステージング環境で最初にドライランを行うべき
2017 Database migration best practices for Rails
基本的にThoughtbot社の記事と同じ
2017 migration 時にデータを操作するのは悪手か?
基本的にThoughtbot社の記事と同じ
rake taskの手動実行は環境が増えると大変になり、実行漏れが起きることがあるので別の方法を模索する旨のコメント
2016 RailsでDBのデータ変更はどこにかく?〜真相編〜
データ変更はrakeのタスクとして書く
migrationには書かない
初期データはseedに書く
5. db/migrateで実行
2013 RailsでDBのデータ変更はどこに書く?
のちに2016 RailsでDBのデータ変更はどこにかく?〜真相編〜でrevised
rails_best_practices (Isolating Seed Data) に怒られるがmigrationファイルに書く
seedの使い方いくつか
最初の一回のためのもの
マスタデータのためのもの
ActiveRecord::Base.reset_column_informationしないと困る
schema cacheのためテーブル定義が変わるとマイグレーションが失敗する
2017 migration の中で model を触ったら必ず reset_column_information する
2015 Rails で信頼性の高い Migration を書くには
専用のモデルを用意するアプローチ
2024 Rails Guideが7.2で刷新されるまではこの手法が紹介されていたが、刷新以降は非推奨となった
2024 GitLab社の事例 by sue445
GitLabだとmigrationファイルの中でデータ移行を行っている
https://gitlab.com/gitlab-org/gitlab/-/blob/v17.3.2-ee/db/post_migrate/20230710024903_swap_todos_note_id_to_bigint_for_self_managed.rb?ref_type=tags
これは推測ですが、GitLabの場合は
・ 本番用のアプリケーションをOSSとして配布している
・GitLabの運用者がデータ移行を意識せずにバージョンアップできるようにしたい
という事情があるのでこういう手法をとってるのではないかと思います。
db:migrateに伴うデータの変更は db/post_migrate 内のmigrationファイルでそういうことをやってることが多い
ohbarye.icon
長所
用意するものが少ない
コードをバージョン管理に残せる
短所
モデルクラスを触るとdb:migrateが失敗する要因になりやすい
reset_column_informationの問題
モデルクラス側の変更
その他
migrationファイルをsquashするsquasher gemが使いにくくなる
ridgepoleやsqldefなど宣言的なmigration toolを使う場合はこの手法はそもそも使えない
6. migration_data gem
2014 Change data in migrations like a boss
migration_dataの作者
他の手法の問題点を指摘したあとで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を使わないほうが良いかもしれない
デプロイ処理が遅くなりダウンタイムが伸びる可能性
ohbarye.icon Separation of schema migration and deployをしていない前提の話ではある
代替手段として以下がある
rake task
data-migrate, seed_migrationのようなgemの利用
ohbarye.icon うーん、最初からrake taskで良かったような気も
7. data-migrate
https://github.com/ilyakatz/data-migrate
データ移行用のスクリプトをdb/dataに配置する
シーケンシャルに実行されることを保証できる
schema migrationと同時に実行するか、単体で実行する
2019 Three Useful Data Migration Patterns for Rails
他の手法の問題点を指摘したあとでdata-migrate gemを紹介
db:migrate
長所
数分でデータ移行をshipできるので早い
データ移行が複数回実行されないことをmigrationの機構で保証できる
欠点
schemaが変わると失敗することがある。そのようにマイグレーションが古くなることがあるが、outdatedなものをすぐに見つける方法がない
頻繁にマイグレーションを実行し続けるとか
Railsの規約に従っていない。db/migrateのファイルはデータベースレコードではなく、データベース構造を移行すべき
OmbuLabsではアンチパターンと考えている
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さんから教えてもらった
https://x.com/qsona/status/1839097814971978231
https://github.com/ilyakatz/data-migrate/pull/326
data-migrate が依存していた Rails の内部挙動がpatchバージョンで変わって、その影響で、db:migrate:with_data がステータス0で終了してて(内部的には新規migrationが存在しない扱いになってた)必要なmigrationが実行されないまま本番にデプロイ
ohbarye.icon フィヨルドブートキャンプではdata-migrate gemを使っている
https://github.com/fjordllc/bootcamp/wiki/DBのデータをmigrateする方法
8. seed_migration
https://github.com/harrystech/seed_migration
data-migrateとほぼ同じ
db:migrateにフックしてないだけ
メンテナンス止まり気味
9. maintenance_tasks
maintenance_tasks
Shopify製
2020年の9月頃に開発開始
推しポイント
テストが書ける
AR を使える
実行履歴が残る
運用にも使える
ダッシュボード上の操作に対して権限設定できる
Active Jobで非同期で処理される
Job Iteration を活用したJobの一時停止・再開機能がある
大量件数のレコードを処理する際にDB負荷を抑えつつ処理できる
長時間Jobを実行する際のJob実行中にサーバーとの接続が切れるような問題を回避できる
APIモードのRailsで使うには
Rack Middlewareの追加が必要
https://github.com/Shopify/maintenance_tasks/pull/1128
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実行がなくなった
関連
https://www.slideshare.net/slideshow/rails-data-migrations/147674444#2