NOT NULL
제약 조건
- GitLab 13.0에서 도입되었습니다.
값으로 NULL
을 가져서는 안 되는 모든 속성은 데이터베이스의 NOT NULL
열로 정의해야 합니다.
애플리케이션 논리에 따라 NOT NULL
열은 해당 모델에 presence: true
유효성 검증이 정의되거나 데이터베이스 정의의 일부로 기본값이 있어야 합니다.
예를 들어, bool 속성의 경우 응용 프로그램에서 매번 강제할 필요가 없지만 항상 NULL
이 아닌 값을 가져야 하는 미리 정의된 기본값(예: active=true
)이 있을 수 있습니다.
NOT NULL
열을 포함하는 새 테이블 만들기
새로운 테이블을 추가할 때, 모든 NOT NULL
열은 create_table
내에서 직접 정의되어야 합니다.
예를 들어, 두 개의 NOT NULL
열이 있는 테이블을 생성하는 마이그레이션을 고려해보세요. db/migrate/20200401000001_create_db_guides.rb
:
class CreateDbGuides < Gitlab::Database::Migration[2.1]
def change
create_table :db_guides do |t|
t.bigint :stars, default: 0, null: false
t.bigint :guide, null: false
end
end
end
기존 테이블에 NOT NULL
열 추가하기
GitLab 13.0 이후에 PostgreSQL 11이 최소 버전으로 지정되어 NULL
및/또는 기본값이 포함된 열을 추가하는 것이 훨씬 쉬워졌으며 모든 경우에 표준 add_column
도우미를 사용해야 합니다.
예를 들어, db_guides
테이블에 active
라는 새로운 NOT NULL
열을 추가하는 마이그레이션을 고려해보세요. db/migrate/20200501000001_add_active_to_db_guides.rb
:
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]
def change
add_column :db_guides, :active, :boolean, default: true, null: false
end
end
기존 열에 NOT NULL
제약 추가
기존 데이터베이스 열에 NOT NULL
을 추가하는 것은 일반적으로 적어도 두 가지 다른 릴리스로 나뉘어 여러 단계로 수행해야 합니다.
테이블이 충분히 작아서 백그라운드 마이그레이션을 사용할 필요가 없는 경우, 모든 단계를 동일한 합병 요청에 포함시킬 수 있습니다. 트랜잭션 기간을 줄이기 위해 별도의 마이그레이션을 사용하는 것이 좋습니다.
다음과 같은 단계가 필요합니다:
-
릴리스
N.M
(현재 릴리스)- $ATTRIBUTE 값이 응용 프로그램 수준에서 설정되는지 확인하세요.
- 속성에 기본값이 있는 경우, 기본값을 모델에 추가하여 새 레코드에 기본값이 설정되도록 합니다.
- 새 레코드와 기존 레코드를 위해 (있는 경우) 속성이
nil
로 설정될 수 있는 코드 위치를 모두 업데이트하세요. 일부 프로세스는before_save
및before_validation
과 같은 ActiveRecord 콜백을 건너뛰므로,update_column
,update_columns
,insert_all
및update_all
과 같은 방법을 확인해야 합니다.
- 기존 레코드를 수정하기 위한 배포 후 마이그레이션을 추가합니다.
테이블의 크기에 따라 다음 릴리스에서는 백그라운드 마이그레이션이 필요할 수 있습니다. 자세한 정보는대형 테이블의 'NOT NULL' 제약 조건
섹션을 참조하세요. - $ATTRIBUTE 값이 응용 프로그램 수준에서 설정되는지 확인하세요.
-
릴리스
N.M+1
(다음 릴리스)- GitLab.com의 모든 기존 레코드에 속성이 설정되었는지 확인하세요. 그렇지 않으면 릴리스
N.M
의 단계 1부터 다시 시작하세요. - 단계 1이 정상적으로 보이고 릴리스
N.M
에서 배치형 백그라운드 마이그레이션을 통해 백필이 끝났다면 백그라운드 마이그레이션 최종화를 추가합니다. - 모든 기존 및 새 레코드가 유효해야 하므로 모델에 속성에 대한 유효성 검증을 추가하세요.
-
NOT NULL
제약을 추가하기 위한 배포 후 마이그레이션을 추가하세요.
- GitLab.com의 모든 기존 레코드에 속성이 설정되었는지 확인하세요. 그렇지 않으면 릴리스
예
13.0과 같은 특정 릴리스 마일스톤을 고려해봅시다.
프로덕션 데이터베이스를 확인한 후 NULL
설명이 있는 epics
가 있음을 알고 있으므로 제약을 추가하고 유효성을 검사할 수 없습니다.
NULL
설명이 없더라도 다른 GitLab 인스턴스에 해당 레코드가 있을 수 있으므로 이 둘 다가 경우 모두 같은 과정을 따릅니다.새로운 유효하지 않은 레코드 방지 (현재 릴리스)
새로운 및 기존 레코드에 nil
속성을 설정하는 모든 코드 경로를 업데이트하여 속성이 nil
이 아닌 값을 가지도록 합니다.
Rails 속성 API를 사용하여 기본값을 설정하기 위해 epic.rb
에 속성이 추가되었습니다:
class Epic < ApplicationRecord
attribute :description, default: 'No description'
end
기존 레코드 수정을 위한 데이터 마이그레이션 (현재 릴리스)
여기서 사용할 접근 방식은 데이터 양과 정리 전략에 따라 다릅니다. GitLab.com에서 수정해야 할 레코드 수는 후속 마이그레이션 또는 백그라운드 데이터 마이그레이션을 사용하는 데 도움이 되는 좋은 지표입니다.
- 데이터 양이 1000
레코드보다 적으면 데이터 마이그레이션을 게시 마이그레이션 내에서 실행할 수 있습니다.
- 데이터 양이 1000
레코드보다 많으면 백그라운드 마이그레이션을 만드는 것이 좋습니다.
어느 옵션을 사용해야 하는지 확신이 없을 때는 데이터베이스 팀에 상의하세요.
다시 예를 들어, epics
테이블은 크지 않고 자주 액세스되지 않으므로 13.0 마일스톤(현재)을 위해 배포 후 마이그레이션을 추가합니다. db/post_migrate/20200501000002_cleanup_epics_with_null_description.rb
:
class CleanupEpicsWithNullDescription < Gitlab::Database::Migration[2.1]
# BATCH_SIZE=1000이고 GitLab.com의 epics.count=29500이므로
# - 평균 30번의 반복이 실행됩니다
# - 각각 평균 ~150ms가 걸립니다
# 예상 총 실행 시간: ~5초
BATCH_SIZE = 1000
disable_ddl_transaction!
class Epic < ActiveRecord::Base
include EachBatch
self.table_name = 'epics'
end
def up
Epic.each_batch(of: BATCH_SIZE) do |relation|
relation.
where('description IS NULL').
update_all(description: 'No description')
end
end
def down
# no-op : `NOT NULL` 제약 조건을 먼저 삭제하지 않고 `NULL`로 되돌아갈 수 없습니다
end
end
모든 레코드가 수정되었는지 확인(다음 릴리스)
프로덕션 데이터베이스의 얇은 클론을 만들고 postgres.ai를 사용하여 GitLab.com의 모든 레코드에 속성이 설정되어 있는지 확인하세요. 속성이 설정되어 있지 않다면 새로운 잘못된 레코드를 방지 단계로 돌아가 코드에서 해당 속성이 명시적으로 nil
로 설정된 위치를 찾은 다음 코드 경로를 수정한 다음 기존 레코드를 수정하기 위해 마이그레이션을 재예약하고 다음 릴리스에서 다음 단계를 수행하세요.
백그라운드 마이그레이션 완료하기(다음 릴리스)
만약 백그라운드 마이그레이션을 사용하여 마이그레이션이 완료되었다면 마이그레이션을 완료하세요.
모델에 유효성 추가하기(다음 릴리스)
모델에 속성에 대한 유효성을 추가하여 nil
속성이 있는 레코드를 방지하세요. 이제 모든 기존 및 새로운 레코드가 유효해야 합니다.
class Epic < ApplicationRecord
validates :description, presence: true
end
NOT NULL
제약 추가하기(다음 릴리스)
NOT NULL
제약을 추가하면 전체 테이블을 스캔하여 각 레코드가 올바른지 확인합니다.
여전히 예시로, 13.1 릴리스에서 (다음) add_not_null_constraint
마이그레이션 도우미를 사용하여 최종 배포 후 마이그레이션에서 실행합니다:
class AddNotNullConstraintToEpicsDescription < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
# `NOT NULL` 제약을 추가하고 유효성을 검증합니다
add_not_null_constraint :epics, :description
end
def down
# `add_not_null_constraint`는 되돌릴 수 없으므로 `down`이 필요합니다
remove_not_null_constraint :epics, :description
end
end
큰 테이블에 대한 NOT NULL
제약
널이 허용된 열을 정리해야 하는 경우(고트래픽 테이블의 예로 ci_builds
의 artifacts
), 백그라운드 마이그레이션이 얼마나 지속되는지에 따라 추가적인 일괄 백그라운드 마이그레이션 정리가 필요합니다.
이 경우 레코드가 마이그레이션하는 데 필요한 시간에 따라 릴리스 수가 달라집니다. 정리 작업은 백그라운드 마이그레이션이 완료된 후 스케줄되며, 이는 제약이 추가된 후 여러 릴리스 후에 발생할 수 있습니다.
- 릴리스
N.M
:-
기존 레코드를 수정하기 위해 백그라운드 마이그레이션 추가:
# db/post_migrate/ class QueueBackfillMergeRequestDiffsProjectId < Gitlab::Database::Migration[2.2] milestone '16.7' restrict_gitlab_migration gitlab_schema: :gitlab_main MIGRATION = 'BackfillMergeRequestDiffsProjectId' DELAY_INTERVAL = 2.minutes def up queue_batched_background_migration( MIGRATION, :merge_request_diffs, :id, job_interval: DELAY_INTERVAL ) end def down delete_batched_background_migration(MIGRATION, :merge_request_diffs, :id, []) end end
-
- 릴리스
N.M+X
, 여기서X
는 마이그레이션이 실행된 릴리스 수입니다:-
백그라운드 마이그레이션 정리:
# db/post_migrate/ class FinalizeMergeRequestDiffsProjectIdBackfill < Gitlab::Database::Migration[2.2] disable_ddl_transaction! milestone '16.10' restrict_gitlab_migration gitlab_schema: :gitlab_main MIGRATION = 'BackfillMergeRequestDiffsProjectId' def up ensure_batched_background_migration_is_finished( job_class_name: MIGRATION, table_name: :merge_request_diffs, column_name: :id, job_arguments: [], finalize: true ) end def down # 무시 end end
-
NOT NULL
제약 추가:# db/post_migrate/ class AddMergeRequestDiffsProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2] disable_ddl_transaction! milestone '16.7' def up add_not_null_constraint :merge_request_diffs, :project_id end def down remove_not_null_constraint :merge_request_diffs, :project_id end end
-
선택사항. 매우 큰 테이블에 대해 잘못된
NOT NULL
제약을 추가하고 비동기적으로 유효성을 검증할 경우:# db/post_migrate/ class AddMergeRequestDiffsProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2] disable_ddl_transaction! milestone '16.7' def up add_not_null_constraint :merge_request_diffs, :project_id, validate: false end def down remove_not_null_constraint :merge_request_diffs, :project_id end end
# db/post_migrate/ class PrepareMergeRequestDiffsProjectIdNotNullValidation < Gitlab::Database::Migration[2.2] milestone '16.10' CONSTRAINT_NAME = 'check_11c5f029ad' def up prepare_async_check_constraint_validation :merge_request_diffs, name: CONSTRAINT_NAME end def down unprepare_async_check_constraint_validation :merge_request_diffs, name: CONSTRAINT_NAME end end
-
선택사항. 제약을 비동기적으로 검증했다면 검증이 완료된 후
NOT NULL
제약을 유효성 검증합니다:# db/post_migrate/ class ValidateMergeRequestDiffsProjectIdNullConstraint < Gitlab::Database::Migration[2.2] milestone '16.10' def up validate_not_null_constraint :merge_request_diffs, :project_id end def down # 무시 end end
이러한 경우에는 업데이트 주기 초기에 데이터베이스 팀과 상의하세요. NOT NULL
제약이 필요하지 않을 수도 있고, 실제로 크거나 자주 액세스되는 테이블에 영향을 주지 않는 다른 옵션이 존재할 수도 있습니다.