- 背景情報
- テキスト・カラムを持つ新しいテーブルを作成します。
- 既存のテーブルにテキストカラムを追加
- 既存のカラムにテキスト制限制約を追加
- 既存のカラムのテキスト制限制約を増やす
- 大きなテーブルのテキスト制限制約
文字列とTextデータ型
GitLab 13.0から導入されました。
文字列やその他のテキスト情報を格納するために新しいカラムを追加する場合:
- 私たちは常に、
string
データ型の代わりにtext
データ型を使用します。 -
text
テーブルを作成する際には#text ... limit: 100
ヘルパー (下記を参照ください) を使用してcreate_table
を使用し、 既存のテーブルを変更する際にはadd_text_limit
を使用します。
Railsの標準的なtext
列型には制限を定義できませんが、create_table
を拡張してlimit: 255
オプションを追加しました。create_table
以外では、add_text_limit
を使用して、すでに存在する列にチェック制約を追加することができます。
背景情報
text
カラムの上限を更新したい場合、ALTER TABLE ...
コマンドを実行しなければならないという欠点があるからstring
です string
。
制限が追加される間、ALTER TABLE ...
コマンドはテーブル上でEXCLUSIVE LOCK
を必要とします。 はカラムの更新処理中も、既存のすべてのレコードの検証中も保持され、大きなテーブルではこの処理に時間がかかります。
一方、テキストはPostgreSQLでは多かれ少なかれ文字列と同等ですが、既存の列に制限を追加したり、その制限を更新したりする際に、非常にコストのかかるEXCLUSIVE LOCK
。EXCLUSIVE LOCK
、列の宣言を更新するためだけに必要です。そして、VALIDATE CONSTRAINT
を使用して、後の段階で検証することができます。SHARE UPDATE EXCLUSIVE LOCK
を必要とするだけです(他の検証やインデックス作成と競合するだけで、読み書きは可能です)。
attr_encrypted
属性にテキストカラムを使用しないでください。代わりに:binary
。テキスト・カラムを持つ新しいテーブルを作成します。
新しいテーブルを追加するときは、テーブルの作成と同じマイグレーションですべてのテキストカラムの制限を追加する必要があります。Railsの#text
メソッドにlimit:
属性を追加し、このカラムの制限を追加できるようにします。
たとえば、db/migrate/20200401000001_create_db_guides.rb
という2つのテキストカラムを持つテーブルを作成するマイグレーションを考えてみましょう:
class CreateDbGuides < Gitlab::Database::Migration[2.1]
def change
create_table :db_guides do |t|
t.bigint :stars, default: 0, null: false
t.text :title, limit: 128
t.text :notes, limit: 1024
end
end
end
既存のテーブルにテキストカラムを追加
既存のテーブルにカラムを追加するには、そのテーブルの排他ロックが必要です。そのロックが保持される時間は短いとはいえ、add_column
が実行を完了するまでに必要な時間は、テーブルへのアクセス頻度によって異なります。例えば、非常に頻繁にアクセスされるテーブルの排他ロックの取得には、GitLab.com では数分かかることがあり、with_lock_retries
を使う必要があります。
テキスト制限を追加する際には、disable_ddl_transaction!
でトランザクションを無効にする必要があります。これは、マイグレーションが失敗してもカラムの追加がロールバックされないことを意味します。マイグレーションを再実行しようとすると、すでにカラムが存在するためにエラーが発生します。
このような理由から、既存のテーブルにテキストカラムを追加するには、以下のどちらかの方法があります:
カラムとリミットを別々のマイグレーションで追加します。
新しいテキストカラムextended_title
をテーブルsprints
,db/migrate/20200501000001_add_extended_title_to_sprints.rb
に追加するマイグレーションを考えます:
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
def change
add_column :sprints, :extended_title, :text
end
# rubocop:enable Migration/AddLimitToTextColumns
end
2回目のマイグレーションは、最初のマイグレーションに続いて、extended_title
,db/migrate/20200501000002_add_text_limit_to_sprints_extended_title.rb
に制限を追加する必要があります:
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_text_limit :sprints, :extended_title, 512
end
def down
# Down is required as `add_text_limit` is not reversible
remove_text_limit :sprints, :extended_title
end
end
カラムがすでに存在するかどうかをチェックしながら、1回のマイグレーションでカラムとリミットを追加します。
新しいテキストカラムextended_title
をテーブルsprints
,db/migrate/20200501000001_add_extended_title_to_sprints.rb
に追加するマイグレーションを考えます:
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
with_lock_retries do
add_column :sprints, :extended_title, :text, if_not_exists: true
end
add_text_limit :sprints, :extended_title, 512
end
def down
with_lock_retries do
remove_column :sprints, :extended_title, if_exists: true
end
end
end
既存のカラムにテキスト制限制約を追加
既存のデータベース列にテキスト制限を追加するには、少なくとも2つの異なるリリースに分かれた複数のステップが必要です:
-
リリース
N.M
(現在のリリース)- デプロイ後のマイグレーションを追加し、
validate: false
でテキスト列に制限を追加。 -
既存のレコードを修正するために、デプロイ後のマイグレーションを追加します。
テーブルのサイズによっては、次のリリースでクリーンアップのためのバックグラウンドマイグレーションが必要になる可能性があります。詳細は、大規模テーブルのテキスト制限制約を参照してください。 - 次のマイルストーンでテキスト制限を検証するためのイシューを作成してください。
- デプロイ後のマイグレーションを追加し、
-
リリース
N.M+1
(次のリリース)- デプロイ後のマイグレーションを使用してテキスト制限を検証します。
物件例
あるリリースのマイルストーン、例えば13.0について、issues.title_html
に1024
の制限を加えたいとします。
イシューは2,500万行を超えるかなり多忙で大きなテーブルなので、更新の実行中にアクセスしようとする他のすべてのプロセスをロックしたくありません。
また、実運用データベースを確認したところ、issues
、タイトルに1024文字の制限を超える文字が含まれていることがわかりました。
新しい無効なレコードを防ぐ (現在のリリース)
NOT VALID
新しいレコードが挿入された時や現在のレコードが更新された時に一貫性を強制します。
上記の例では、タイトルが1024文字を超える既存のイシューは影響を受けず、issues
テーブルのレコードを更新することができます。しかし、1024文字以上のタイトルを持つtitle_html
を更新しようとすると、制約によりデータベース・エラーが発生します。
既存の属性に制約を追加または削除するには、アプリケーションの変更を_最初に_デプロイする必要があります。このような理由から、add_text_limit
はデプロイ後のマイグレーションで実行する必要があります。
まだこの例では、13.0マイルストーン(現在)において、モデルIssue
に以下の検証が追加されていると考えてください:
validates :title_html, length: { maximum: 1024 }
デプロイ後のマイグレーションでvalidate: false
を使ってテキスト制限を追加することで、同じマイルストーンでデータベースを更新することもできますdb/post_migrate/20200501000001_add_text_limit_migration.rb
:
class AddTextLimitMigration < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
# This will add the constraint WITHOUT validating it
add_text_limit :issues, :title_html, 1024, validate: false
end
def down
# Down is required as `add_text_limit` is not reversible
remove_text_limit :issues, :title_html
end
end
既存レコードを修正するためのデータマイグレーション (現在のリリース)
ここでのアプローチは、データ量とクリーンアップ戦略に依存します。GitLab.comで修正しなければならないレコードの数は、デプロイ後のマイグレーションを使うかバックグラウンドのデータマイグレーションを使うかを決めるのに役立つ良い指標です:
- データ量が
1,000
レコード以下であれば、データマイグレーションはポストマイグレーション内で実行できます。 - データ量が
1,000
レコードより多い場合は、バックグラウンドマイグレーションを作成することをお勧めします。
どのオプションを使用するか不明な場合は、データベースチームにお問い合わせください。
例に戻ると、イシュー・テーブルはかなり大きく、頻繁にアクセスされるため、13.0マイルストーン(現在)のバックグラウンド・マイグレーションを追加します。db/post_migrate/20200501000002_schedule_cap_title_length_on_issues.rb
:
class ScheduleCapTitleLengthOnIssues < Gitlab::Database::Migration[2.1]
# Info on how many records will be affected on GitLab.com
# time each batch needs to run on average, etc ...
BATCH_SIZE = 5000
DELAY_INTERVAL = 2.minutes.to_i
# Background migration will update issues whose title is longer than 1024 limit
ISSUES_BACKGROUND_MIGRATION = 'CapTitleLengthOnIssues'.freeze
disable_ddl_transaction!
def up
queue_batched_background_migration(
ISSUES_BACKGROUND_MIGRATION,
:issues,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE
)
end
def down
delete_batched_background_migration(ISSUES_BACKGROUND_MIGRATION, :issues, :id, [])
end
end
このガイドを短くするために、バックグラウンドマイグレーションの定義を省略し、バッチのスケジューリングに使用されるデプロイ後のマイグレーションの高レベルの例のみを提供します。バッチ化されたバックグラウンドマイグレーションに関する詳細な情報は、ガイドを参照してください。
テキスト制限の検証(次のリリース)
テキスト制限の検証はテーブル全体をスキャンし、各レコードが正しいことを確認します。
まだこの例では、13.1マイルストーン(次)のために、最終的なデプロイ後のマイグレーションでvalidate_text_limit
マイグレーションヘルパーを実行します、db/post_migrate/20200601000001_validate_text_limit_migration.rb
:
class ValidateTextLimitMigration < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
validate_text_limit :issues, :title_html
end
def down
# no-op
end
end
既存のカラムのテキスト制限制約を増やす
既存のデータベースカラムのテキスト制限を増やすには、まず (別の名前で) 新しい制限を追加し、それから以前の制限を削除します:
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_text_limit :ci_runners, :maintainer_note, 1024, constraint_name: check_constraint_name(:ci_runners, :maintainer_note, 'max_length_1MB')
remove_text_limit :ci_runners, :maintainer_note, constraint_name: check_constraint_name(:ci_runners, :maintainer_note, 'max_length')
end
def down
# no-op: Danger of failing if there are records with length(maintainer_note) > 255
end
end
大きなテーブルのテキスト制限制約
非常に大きなテーブル(例えば、ci_builds
のartifacts
)のテキスト列をクリーンアップする必要がある場合、バックグラウンドマイグレーションがしばらく続き、データマイグレーションを追加した後のリリースで、追加のバッチバックグラウンドマイグレーションによるクリーンアップが必要になります。
このようなまれなケースでは、エンドツーエンドで3つのリリースが必要になります:
- リリース
N.M
- 既存のレコードを修正するために、テキスト制限とバックグラウンドマイグレーションを追加します。 - リリース
N.M+1
- バックグラウンド・マイグレーションをクリーンアップ。 - リリース
N.M+2
- テキスト制限を検証。