- スキーマの変更
- ダウンタイムが必要なものとは?
- ダウンタイム・タギング
- 可逆性
- 原子性
- 単一トランザクションでの重いオペレーション
- データベースロック取得時のリトライ機構
- マルチスレッド
- インデックスの削除
- インデックスの追加
- 外部キー制約の追加
NOT NULL
制約- デフォルト値を持つカラムの追加
- 列のデフォルトの変更
- 既存の列の更新
- データベーステーブルの削除
- 整数カラム型
- 文字列とTextデータ型
- タイムスタンプ列タイプ
- データベースへのJSONの保存
- テスト
- データ移行
マイグレーションスタイルガイド
GitLabのマイグレーションを書くときは、何十万ものあらゆる規模の組織で実行されることを考慮しなければなりません。
さらに、アップグレードの大小にかかわらず、サーバーをオフラインにしなければならないことは、ほとんどの組織にとって大きな負担です。 このため、マイグレーションを慎重に記述し、オンラインで適用できるようにし、以下のスタイルガイドを遵守することが重要です。
マイグレーションでは、_絶対に必要な_場合を除き、GitLabのインストールをオフラインにする必要はありません。
ダウンタイムが必要な場合、その移行は承認者により承認されなければなりません:
- エンジニアリング担当副社長
- バックエンドメンテナー
- データベース・メンテナー
これらの肩書きを持つ人物の最新リストは、https://about.gitlab.com/company/team/でご覧いただけます。
マイグレーションを記述する際には、データベースに古いデータや不整合があるかもしれないことも考慮し、そのような事態に備えましょう。 データベースの状態については、できるだけ仮定しないようにしましょう。
GitLab固有のコードは将来のバージョンで変更される可能性があるため、依存しないでください。 必要であれば、GitLabのコードをマイグレーションにコピーペーストして互換性を持たせてください。
GitLab.comでは、Canaryがデプロイされる前に通常のマイグレーション(db/migrate
)が実行され、本番環境へのデプロイが完了した後にデプロイ後のマイグレーション(db/post_migrate
)が実行されることを考慮してください。
スキーマの変更
スキーマへの変更はdb/structure.sql
.N db/structure.sql
ETにコミットしてください。db/structure.sql
このファイルはRailsによって自動的に生成されるので、通常はこのファイルを手で編集するべきではありません。 マイグレーションによってテーブルにカラムが追加される場合、そのカラムは一番下に追加されます。 既存のテーブルのカラムを手動で並べ替えると db/structure.sql
、Railsによって生成されたdb/structure.sql
テーブルを使っている他の人を混乱さ db/structure.sql
せる原因になるのでやめてください。
GDK の内部データベースがmaster
のスキーマから分岐している場合、スキーマの変更を Git にきれいにコミットするのは難しいかもしれません。そのような場合は、scripts/regenerate-schema
スクリプトを使って、追加する移行用のdb/structure.sql
をきれいに再生成することができます。このスクリプトはdb/migrate
やdb/post_migrate
にある移行をすべて適用します。スキーマにコミットしたくない移行がある場合は、名前を変更するか削除してください。ブランチがmaster
を対象としていない場合は、TARGET
環境変数を設定します。
# Regenerate schema against `master`
scripts/regenerate-schema
# Regenerate schema against `12-9-stable-ee`
TARGET=12-9-stable-ee scripts/regenerate-schema
ダウンタイムが必要なものとは?
What Requires Downtime? “というドキュメントでは、以下のようなさまざまなデータベースオペレーションを規定しています。
また、ダウンタイムが必要かどうか、可能な限りダウンタイムを回避するにはどうしたらよいか、などです。
ダウンタイム・タギング
すべての移行はダウンタイムが必要かどうかを指定し、ダウンタイムが必要な場合はその理由も指定しなければなりません。 ダウンタイムが必要_な_移行を見つけやすくするため、99%の移行がダウンタイムを必要としない場合でも、これは必要です。
マイグレーションにタグを付けるには、マイグレーション・クラスの本体に次の2つの定数を追加します:
-
DOWNTIME
true
、マイグレーションにダウンタイムが必要であることを示します。 -
DOWNTIME_REASON
この定数は、DOWNTIME
がtrue
に設定されている場合に設定されなければなりません。
使用例:
class MyMigration < ActiveRecord::Migration[4.2]
DOWNTIME = true
DOWNTIME_REASON = 'This migration requires downtime because ...'
def change
...
end
end
マイグレーションクラスにDOWNTIME
定数がない場合、エラーとなります。
可逆性
脆弱性やバグがあった場合にダウングレードできるようにする必要があります。
マイグレーションでは、マイグレーションの可逆性がどのようにテストされたかを記述したコメントを追加してください。
マイグレーションの中には元に戻せないものもあります。 たとえば、マイグレーション前のデータベースの状態に関する情報が失われるため、元に戻せないデータマイグレーションもあります。 マイグレーション中に実行された変更が元に戻せなくても、マイグレーション自体は元に戻せるように、up
メソッドで実行された変更が元に戻せない理由を説明するコメントを添えたdown
メソッドを作成する必要があります:
def down
# no-op
# comment explaining why changes performed by `up` cannot be reversed.
end
原子性
デフォルトでは、マイグレーションは単一トランザクションです。 つまり、マイグレーションの開始時にトランザクションがオープンされ、すべてのステップが処理された後にコミットされます。
単一のトランザクションでマイグレーションを実行することで、いずれかのステップが失敗しても、どのステップも実行されず、データベースは有効な状態のままとなります。 したがって、以下のいずれかを実行します:
- すべてのマイグレーションを1つの単一トランザクションマイグレーションにまとめます。
- 必要であれば、ほとんどのアクションを1つのマイグレーションにまとめ、1つのトランザクションで実行できないステップについては別のマイグレーションを作成します。
例えば、空のテーブルを作成し、インデックスを作成する必要がある場合、通常のシングルトランザクションマイグレーションとデフォルトのRailsスキーマステートメントを使用することをお勧めします:add_index
. これはブロッキングオペレーションですが、テーブルがまだ使用されていないため、まだレコードを持っていないため、問題は発生しません。
単一トランザクションでの重いオペレーション
シングルトランザクションマイグレーションを使用する場合、トランザクションはマイグレーションの期間中データベース接続に留まります。そのため、マイグレーションでのアクションに時間がかかりすぎないようにする必要があります。一般的に、マイグレーションで実行されるクエリはGitLab.comの15s
に快適に収まる必要があります。
大量のデータを挿入、更新、または削除する必要がある場合:
-
disable_ddl_transaction!
で単一トランザクションを無効にする必要があります。 - バックグラウンドの移行を検討すべきです。
データベースロック取得時のリトライ機構
データベーススキーマを変更する際には、DDL (Data Definition Language) ステートメントを呼び出すヘルパーメソッドを使用します。 場合によっては、これらの DDL ステートメントは特定のデータベースロックを必要とします。
使用例:
def change
remove_column :users, :full_name, :string
end
このマイグレーションを実行するには、users
テーブルに対する users
排他ロックが必要ですusers
。 テーブルが他のプロセスによって同時にアクセスされ変更されている場合、ロックの取得に時間がかかることがあります。 ロック要求はキューで待機しており users
、いったんキューに入れられるとusers
テーブルに対する他の users
クエリをusers
ブロックすることもあります users
。
PostgresSQLのロックの詳細:明示的ロック
安定性の理由から、GitLab.com には特定のstatement_timeout
が設定されています。マイグレーションが起動されると、データベースクエリの実行時間は一定になります。 最悪のシナリオでは、リクエストはロックキューに置かれ、設定されたステートメントのタイムアウトの間、他のクエリをブロックし、canceling statement due to statement timeout
エラーで失敗します。
このイシューが発生すると、アプリケーションのアップグレード処理に失敗したり、テーブルが短時間アクセスできなくなるため、アプリケーションの安定性に問題が生じたりする可能性があります。
データベース移行の信頼性と安定性を高めるために、GitLabコードベースはlock_timeout
の設定と試行間の待ち時間を変えてオペレーションを再試行するヘルパーメソッドを提供しています。 必要なロックを取得するために複数の小さな試行を行うことで、データベースは他のステートメントを処理できるようになります。
使用例
カラムの削除
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
remove_column :users, :full_name
end
end
def down
with_lock_retries do
add_column :users, :full_name, :string
end
end
外部キーの削除
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
remove_foreign_key :issues, :projects
end
end
def down
with_lock_retries do
add_foreign_key :issues, :projects
end
end
カラムのデフォルト値の変更
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
change_column_default :merge_requests, :lock_version, from: nil, to: 0
end
end
def down
with_lock_retries do
change_column_default :merge_requests, :lock_version, from: 0, to: nil
end
end
外部キーを持つ新しいテーブルの作成
create_table
メソッドをwith_lock_retries
でラップするだけです:
def up
with_lock_retries do
create_table :issues do |t|
t.references :project, index: true, null: false, foreign_key: { on_delete: :cascade }
t.string :title, limit: 255
end
end
end
def down
drop_table :issues
end
外部キーが2つある場合の新しいテーブルの作成
そのためには、3つのマイグレーションが必要です:
- 外部キーなし(インデックス付き)のテーブルを作成します。
- 最初のテーブルに外部キーを追加します。
- 2つ目のテーブルに外部キーを追加します。
テーブルの作成
def up
create_table :imports do |t|
t.bigint :project_id, null: false
t.bigint :user_id, null: false
t.string :jid, limit: 255
end
add_index :imports, :project_id
add_index :imports, :user_id
end
def down
drop_table :imports
end
projects
への外部キーの追加 :
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
add_foreign_key :imports, :projects, column: :project_id, on_delete: :cascade
end
end
def down
with_lock_retries do
remove_foreign_key :imports, column: :project_id
end
end
users
への外部キーの追加 :
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
add_foreign_key :imports, :users, column: :user_id, on_delete: :cascade
end
end
def down
with_lock_retries do
remove_foreign_key :imports, column: :user_id
end
end
使用方法disable_ddl_transaction!
通常、with_lock_retries
ヘルパーはdisabled_ddl_transaction!
で動作するはずです。カスタム RuboCop ルールによって、許可されたメソッドだけがロック再試行ブロック内に配置できるようになっています。
disable_ddl_transaction!
def up
with_lock_retries do
add_column :users, :name, :text
end
add_text_limit :users, :name, 255 # Includes constraint validation (full table scan)
end
RuboCopルールでは、以下に示す標準的なRailsマイグレーションメソッドを一般的に許可しています。 この例では、RuboCop違反が発生します:
disabled_ddl_transaction!
def up
with_lock_retries do
add_concurrent_index :users, :name
end
end
ヘルパーメソッドを使用する場合
with_lock_retries
ヘルパーメソッドは、通常Rails標準のマイグレーションヘルパーメソッドを使用する場合に使用できます。 複数のマイグレーションヘルパーを呼び出しても、同じテーブルに対して実行されるのであれば問題ありません。
データベースの移行にトラフィックの多いテーブルが含まれる場合は、with_lock_retries
ヘルパーメソッドを使用することをお勧めします:
users
projects
namespaces
issues
merge_requests
ci_pipelines
ci_builds
notes
変更例
-
add_foreign_key
/remove_foreign_key
-
add_column
/remove_column
change_column_default
-
create_table
/drop_table
注: with_lock_retries
メソッドはchange
メソッド内部では使用できません。up
メソッドとdown
メソッドを手動で定義して、移行を可逆にする必要があります。
ヘルパーメソッドの仕組み
- 50回繰り返します。
- 各反復ごとに、事前に設定した
lock_timeout
を設定します。 - 指定されたブロックの実行を試みます (
remove_column
)。 -
LockWaitTimeout
エラーが発生した場合は、事前に設定されたsleep_time
の間スリープし、ブロックを再試行します。 - エラーが発生しなければ、現在のイテレーションでブロックが正常に実行されたことになります。
詳しくはGitlab::Database::WithLockRetries
クラスをご覧ください。with_lock_retries
ヘルパーメソッドはGitlab::Database::MigrationHelpers
モジュールに実装されています。
最悪の場合、その方法は
- 40分間に最大50回ブロックを実行します。
- ほとんどの時間は、各反復の後にあらかじめ設定されたスリープ期間に費やされます。
- 50回目のリトライの後、ブロックは
lock_timeout
なしで実行されます。 - ロックを取得できない場合、マイグレーションは
statement timeout
エラーで失敗します。
users
テーブルにアクセスするトランザクションの実行時間が非常に長い(40分以上)場合、移行に失敗する可能性があります。
マルチスレッド
マイグレーションを高速化するために、複数のRubyスレッドを使用する必要がある場合があります。 そのためには、マイグレーションにモジュールGitlab::Database::MultiThreadedMigration
を含める必要があります:
class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
end
その後、with_multiple_threads
メソッドを使って、別々のスレッドで作業を行うことができます:
class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
def up
with_multiple_threads(4) do
disable_statement_timeout
# ...
end
end
end
ここでは、disable_statement_timeout
の呼び出しは、グローバル接続プールを再利用する代わりに、with_multiple_threads
ブロックのローカル接続を使用します。 これにより、各スレッドが独自の接続オブジェクトを持つようになり、接続オブジェクトを取得しようとしたときにタイムアウトすることがなくなります。
注意:PostgreSQLには許可される最大接続数があります。 この制限はインストールによって異なる可能性があります。 そのため、1回の移行で32スレッドを超えるスレッドを使用しないことを推奨します。 通常は4〜8スレッドで十分です。
インデックスの削除
インデックスを削除するときにテーブルが空でない場合は、通常のremove_index
メソッドではなくremove_concurrent_index
メソッドを使用するようにしてください。remove_concurrent_index
メソッドはインデックスを同時に削除するので、ロックは不要で、ダウンタイムも発生しません。 このメソッドを使用するには、マイグレーション・クラスの内部でdisable_ddl_transaction!
メソッドを以下のように呼び出して、シングル・トランザクション・モードを無効にする必要があります:
class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
remove_concurrent_index :table_name, :column_name
end
end
インデックスを削除する前に、インデックスが存在するかどうかを確認する必要はないことに注意してください。
小さなテーブル(空のテーブルやレコード数が1,000
未満のテーブルなど)では、remove_index
をシングルトランザクションのマイグレーションで使用し、disable_ddl_transaction!
を必要としない他のオペレーションと組み合わせることをお勧めします。
インデックスの追加
インデックスを追加する前に、このインデックスが必要かどうかを検討してください。 たとえば、インデックスが必要ない場合もあります:
- テーブルのサイズは小さく(
1,000
レコード以下)、指数関数的に大きくなることはありません。 - 既存のインデックスは十分な行をフィルタリングします。
- インデックス追加後のクエリタイミングの短縮はそれほど大きくありません。
さらに、ワイドインデックスは、クエリのすべてのフィルタ条件にマッチする必要はありません。 インデックス検索が十分に小さい選択性を持つように、十分なカラムをカバーする必要があるだけです。 詳細については、データベースインデックスの追加ガイドをレビューしてください。
空でないテーブルにインデックスを追加する場合、通常のadd_index
メソッドではなく、add_concurrent_index
メソッドを必ず使用してください。 PostgreSQLを使用する場合、add_concurrent_index
メソッドは自動的に同時インデックスを作成し、ダウンタイムの必要性を取り除きます。
このメソッドを使うには、マイグレーション・クラスの内部でdisable_ddl_transaction!
:
class MyMigration < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :table, :column
end
def down
remove_concurrent_index :table, :column
end
end
一意インデックスを追加する必要がある場合、データベース内に既存の重複が存在する可能性があることに留意してください。 つまり、一意インデックスを追加する前に、必ず重複を削除するマイグレーションを_最初に_追加する必要があります。
小さなテーブル(空のテーブルやレコード数が1,000
未満のテーブルなど)では、add_index
をシングルトランザクションのマイグレーションで使用し、disable_ddl_transaction!
を必要としない他のオペレーションと組み合わせることをお勧めします。
外部キー制約の追加
既存のカラムまたは新しいカラムに外部キー制約を追加する場合は、カラムにインデックスを追加することも忘れないでください。
これは、すべての外部キーに必要です。たとえば、効率的なカスケード削除をサポートするためです。 テーブルの行が大量に削除されると、参照されたレコードも削除される必要があります。 データベースは、参照されたテーブルから対応するレコードを探さなければなりません。 インデックスがないと、テーブルを順次スキャンすることになり、長い時間がかかります。
外部キー制約を持つ新しいカラムを追加する例です。インデックスを作成するためにindex: true
を含むことに注意してください。
class Migration < ActiveRecord::Migration[4.2]
def change
add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
end
end
空でないテーブルの既存の列に外部キー制約を追加する場合、add_reference
の代わりに、add_concurrent_foreign_key
とadd_concurrent_index
を使用する必要があります。
空のテーブル(新しいテーブルなど)については、disable_ddl_transaction!
を必要としない他のオペレーションと組み合わせて、add_reference
を単一トランザクションのマイグレーションで使用することをお勧めします。
既存のカラムに外部キー制約を追加する方法については、こちらを参照してください。
NOT NULL
制約
GitLab 13.0から導入されました。
詳しくは、NOT NULL
制約 のスタイルガイドをご覧ください。
デフォルト値を持つカラムの追加
GitLab 13.0以降、PostgreSQL 11が最小バージョンとなったことで、デフォルト値を持つカラムの追加がより簡単になり、どのような場合でも標準のadd_column
ヘルパーを使うようになりました。
PostgreSQL 11以前では、デフォルトを持つ列を追加することは、テーブルの全面的な書き換えを引き起こすため問題がありました。 対応するヘルパーadd_column_with_default
は非推奨となり、後のリリースで削除される予定です。
add_column_with_default
ヘルパーを使用する必要があります。add_column_with_default
大きなテーブルがadd_column_with_default
含まれる場合は、%12.9 へのバックポートはお勧めできません。列のデフォルトの変更
change_column_default
、デフォルトのカラムを変更することは、大規模なテーブルでは高価で破壊的なオペレーションだと思われるかもしれませんが、実際にはそうではありません。
次の移行を例にとってみましょう:
class DefaultRequestAccessGroups < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
change_column_default(:namespaces, :request_access_enabled, from: false, to: true)
end
end
上記の移行は、私たちの最大のテーブルの1つのデフォルトカラム値を変更します:namespaces
. これは次のように変換できます:
ALTER TABLE namespaces
ALTER COLUMN request_access_enabled
DEFAULT false
この特別なケースでは、デフォルト値は存在し、request_access_enabled
カラムのメタデータを変更するだけです。namespaces
テーブルの既存のレコードをすべて書き換えるわけではありません。デフォルト値を持つ新しいカラムを作成する場合のみ、すべてのレコードが書き換わります。
上記の理由から、disable_ddl_transaction!
を必要とせず、シングルトランザクショ ン・マイグレーションでchange_column_default
を使用することは安全です。
既存の列の更新
既存のカラムを特定の値に更新するには、update_column_in_batches
を使用します。 これは更新をバッチに分割するので、1回のステートメントで多くの行を更新することはありません。
これにより、projects
テーブルのカラムfoo
が 10 に更新され、some_column
は'hello'
になります:
update_column_in_batches(:projects, :foo, 10) do |table, query|
query.where(table[:some_column].eq('hello'))
end
計算による更新が必要な場合は、値をArel.sql
でラップすることで、ArellはSQLリテラルとして扱います。 Rails6では必須の非推奨事項でもあります。
以下の例は上の例と同じですが、値はbar
とbaz
列の積に設定されます:
update_value = Arel.sql('bar * baz')
update_column_in_batches(:projects, :foo, update_value) do |table, query|
query.where(table[:some_column].eq('hello'))
end
add_column_with_default
と同様、RuboCop コップがあり、大きなテーブルでの使用を検出することができます。update_column_in_batches
の場合、テーブルの行のごく一部だけを更新するのであれば、大きなテーブルで実行しても問題ないかもしれません。しかし、GitLab.com のステージング環境で検証することなく、あるいは他の誰かにお願いすることなく、それを無視しないでください。
データベーステーブルの削除
データベースのテーブルを削除するのは珍しいことで、Railsが提供するdrop_table
。テーブルを削除する前に、以下を検討してください:
もしあなたのテーブルがトラフィックの多いテーブル(projects
など)の外部キーを持っている場合、DROP TABLE
ステートメントはステートメントタイムアウトエラーで失敗するかもしれません。 どのテーブルがトラフィックの多いテーブルなのかを判断するのは難しいかもしれません。セルフマネージドインスタンスではGitLabのさまざまな機能をさまざまな使用パターンで使っている可能性があり、GitLab.comから推測するだけでは不十分です。
テーブルにはレコードがなく(機能は使用されていません)、外部キーもありません:
- 移行には
drop_table
メソッドを使用します。
def change
drop_table :my_table
end
テーブルにはレコードがありますが、外部キーがありません:
- 最初のリリース: モデル、コントローラ、サービスなど、テーブルに関連するアプリケーションコードを削除します。
- 2つ目のリリース:マイグレーションに
drop_table
メソッドを使用します。
def up
drop_table :my_table
end
def down
# create_table ...
end
テーブルには外部キーがあります:
- 最初のリリース: モデル、コントローラ、サービスなど、テーブルに関連するアプリケーションコードを削除します。
- 2番目のリリース:
with_lock_retries
ヘルパーメソッドを使用して外部キーを削除します。 別のマイグレーションファイルでdrop_table
を使用します。
2回目のリリースのための移行:
projects
テーブルの外部キーを削除します:
# first migration file
def up
with_lock_retries do
remove_foreign_key :my_table, :projects
end
end
def down
with_lock_retries do
add_foreign_key :my_table, :projects
end
end
テーブルを落とします:
# second migration file
def up
drop_table :my_table
end
def down
# create_table ...
end
整数カラム型
デフォルトでは、整数カラムは4バイト(32ビット)までの数値を保持することができます。 つまり、最大値は2,147,483,647です。 ファイルサイズをバイト単位で保持するカラムを作成する場合は、この点に注意してください。 ファイルサイズをバイト単位で追跡する場合、最大ファイルサイズは2GB強に制限されます。
整数カラムに 8 バイト (64 ビット) までの数値を保持させるには、明示的に 8 バイトに制限を設定します。 これにより、カラムは9,223,372,036,854,775,807
までの値を保持できるようになります。
Railsマイグレーションの例:
add_column(:projects, :foo, :integer, default: 10, limit: 8)
文字列とTextデータ型
GitLab 13.0から導入されました。
詳しくはテキストデータ型のスタイルガイドを参照してください。
タイムスタンプ列タイプ
デフォルトでは、Railsはtimestamp
タイムゾーン情報を含まないタイムスタンプデータを格納するデータ型を timestamp
使用しますtimestamp
。この timestamp
データ型は、add_timestamps
またはtimestamps
メソッドを呼び出すことで使用されます。
また、Railsは:datetime
データ型をtimestamp
。
使用例:
# timestamps
create_table :users do |t|
t.timestamps
end
# add_timestamps
def up
add_timestamps :users
end
# :datetime
def up
add_column :users, :last_sign_in, :datetime
end
タイムゾーンを含むタイムスタンプを保存するには、これらのメソッドを使用する代わりに、以下のメソッドを使用する必要があります:
add_timestamps_with_timezone
timestamps_with_timezone
datetime_with_timezone
これにより、すべてのタイムスタンプにタイムゾーンが指定されることになります。 これにより、システムのタイムゾーンが変更されたときに、既存のタイムスタンプが突然異なるタイムゾーンを使用することがなくなります。 また、最初に使用されたタイムゾーンが明確になります。
データベースへのJSONの保存
Rails 5はネイティブでJSONB
(binary JSON)カラムタイプをサポートしています。 このカラムを追加するマイグレーション例:
class AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
add_column :ci_builds_metadata, :config_options, :jsonb
end
end
変換レイヤーを提供するためにシリアライザーを使用する必要があります:
class BuildMetadata
serialize :config_options, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
end
JSONB
カラムを使用する場合は、JsonSchemaValidatorを使用して、時間経過とともに挿入されるデータの制御を維持します。
class BuildMetadata
validates :config_options, json_schema: { filename: 'build_metadata_config_option' }
end
テスト
TestingRailsmigrationsスタイルガイドを参照してください。
データ移行
通常のActiveRecord構文よりも、ArelやプレーンSQLを使用してください。 プレーンSQLを使用する場合は、quote_string
ヘルパーを使用してすべての入力を手動で引用符で囲む必要があります。
アレルの例:
users = Arel::Table.new(:users)
users.group(users[:user_id]).having(users[:id].count.gt(5))
#update other tables with these results
プレーンな SQL とquote_string
ヘルパーを使った例:
select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(id) > 1").each do |tag|
tag_name = quote_string(tag["name"])
duplicate_ids = select_all("SELECT id FROM tags WHERE name = '#{tag_name}'").map{|tag| tag["id"]}
origin_tag_id = duplicate_ids.first
duplicate_ids.delete origin_tag_id
execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end
より複雑なロジックが必要な場合は、マイグレーションにローカルなモデルを定義して使用することができます。 たとえば、次のようになります:
class MyMigration < ActiveRecord::Migration[4.2]
class Project < ActiveRecord::Base
self.table_name = 'projects'
end
end
その際には、クラス名や名前空間から派生しないように、モデルのテーブル名を明示的に設定するようにしてください。
予約パスの名前の変更
プロジェクトの新しいルートが導入されると、既存のレコードと衝突する可能性があります。 これらのレコードのパス名を変更し、関連するデータをディスク上に移動する必要があります。
すでに数回この作業をしなければならなかったので、現在ではこの作業を手伝ってくれるヘルパーがいます。
これを使用するには、Gitlab::Database::RenameReservedPathsMigration::V1
をマイグレーションに含めます。これは3つのメソッドを提供し、拒否する必要のあるパスを1つ以上渡すことができます。
rename_root_paths
これは、parent_id
を持たない、指定された名前のすべての_名前_空間のパスをリネームします。
rename_child_paths
これは、parent_id
を持つ、指定された名前のすべての_名前_空間のパスをリネームします。
rename_wildcard_paths
すべての_プロジェクトと_、project_id
を持つすべての_名前空間の_パス名を変更します。
これらの行のpath
列の名前は、以前の値の後に整数が付いたものに変更されます。 例えば、users
は次のようになります。users0