- 배경 정보
- 텍스트 열이 포함된 새 테이블 생성
- 기존 테이블에 텍스트 열 추가
- 기존 열에 텍스트 제한 제약 추가
- 기존 열의 텍스트 제한 제약 조건 증가
- 대규모 테이블의 텍스트 제한 제약 조건
문자열과 텍스트 데이터 유형
문자열 또는 기타 텍스트 정보를 저장하는 새로운 열을 추가할 때:
-
string
데이터 유형 대신에 항상text
데이터 유형을 사용합니다. -
text
열은 테이블을 생성할 때#text ... limit: 100
헬퍼(아래 참조)를 사용하거나 기존 테이블을 변경할 때add_text_limit
를 사용하여 항상 한도를 설정해야 합니다.
표준 Rails text
열 유형은 한도를 정의할 수 없지만, 우리는 create_table
을 확장하여 limit: 255
옵션을 추가합니다. create_table
이외에도 add_text_limit
를 사용하여 이미 존재하는 열에 검사 제약을 추가할 수 있습니다.
배경 정보
우리가 항상 string
대신 text
를 사용하길 원하는 이유는 string
열은 한도를 업데이트하려면 ALTER TABLE ...
명령을 실행해야 한다는 단점이 있기 때문입니다.
항목이 추가되는 동안 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
에 두 개의 텍스트 열이 있는 테이블을 생성하는 마이그레이션을 고려해보세요.
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
첫 번째 마이그레이션에 이어 두 번째 마이그레이션이 따라와야 합니다. extended_title
에 추가된 한도가 있는 sprints
에 대한 db/migrate/20200501000002_add_text_limit_to_sprints_extended_title
:
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_text_limit :sprints, :extended_title, 512
end
def down
# `add_text_limit`은 뒤로 돌릴 수 없기 때문에 다운이 필요합니다
remove_text_limit :sprints, :extended_title
end
end
열과 제한을 동시에 추가하되 이미 열이 존재하는 경우 확인
sprints
테이블에 새로운 텍스트 열 extended_title
을 추가하는 마이그레이션을 고려해보십시오.
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
기존 열에 텍스트 제한 제약 추가
기존 데이터베이스 열에 텍스트 제한을 추가하려면 적어도 두 가지 다른 릴리스로 나누어 여러 단계가 필요합니다:
-
릴리스
N.M
(현재 릴리스)-
validate: false
를 사용하여 텍스트 열에 제한을 추가하는 순방향 마이그레이션 추가 -
기존 레코드를 수정하는 순방향 마이그레이션 추가
참고: 테이블 크기에 따라, 다음 릴리스에서 정리를 위해 백그라운드 마이그레이션이 필요할 수 있습니다. 자세한 내용은 large tables에서 텍스트 제한 제약을 확인하십시오.
- 다음 마일스톤에 텍스트 제한을 확인하기 위한 이슈 생성
-
-
릴리스
N.M+1
(다음 릴리스)- 순방향 마이그레이션을 사용하여 텍스트 제한 확인
예제
특정 릴리스 마일스톤(예: 13.0)에 issues.title_html
에 1024
제한을 추가하고자 한다고 가정해봅시다.
issues
는 행이 2500만 개가 넘는 상당히 바쁘고 큰 테이블이므로 업데이트를 실행하는 동안 모든 다른 프로세스가 잠기지 않도록 합니다.
또한, 프로덕션 데이터베이스를 확인한 후 제공된 제한보다 더 많은 문자를 포함하는 issues
가 있는 것을 알기 때문에 제약을 추가하고 확인하는 것을 한 번에 할 수 없습니다.
참고: 제공된 제한보다 더 큰 제목을 가진 레코드가 없어도 다른 GitLab 인스턴스에는 해당 레코드가 있을 수 있으므로 어차피 동일한 프로세스를 따르게 됩니다.
새로운 유효하지 않은 레코드 방지 (현재 릴리스)
먼저, 테이블에 NOT VALID
검사 제약 조건으로 제한을 추가하여 새 레코드가 삽입되거나 현재 레코드가 업데이트될 때 일관성을 강제합니다.
위의 예에서 제공된 제한보다 더 큰 제목을 가진 기존 이슈에는 영향을 주지 않으며 issues
테이블의 레코드를 여전히 업데이트할 수 있습니다. 그러나 title_html
을 1024 문자보다 더 많은 제목으로 업데이트하려고 하면 제약으로 인해 데이터베이스 오류가 발생합니다.
기존 속성에 제약을 추가하거나 제거하려면, 모든 애플리케이션 변경 사항이 먼저 배포되어야 합니다. 그렇지 않으면 여전히 이전 버전의 애플리케이션 서버가 유효하지 않은 값으로 속성을 업데이트하려고 시도할 수 있습니다.
이러한 이유로 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
# 이는 확인하지 않고 제약을 추가합니다.
add_text_limit :issues, :title_html, 1024, validate: false
end
def down
# `add_text_limit`은 되돌릴 수 없으므로 다운이 필요합니다.
remove_text_limit :issues, :title_html
end
end
기존 레코드 수정을 위한 데이터 마이그레이션 (현재 릴리스)
여기서 접근 방법은 데이터 양과 정리 전략에 따라 다릅니다. GitLab.com에서 수정해야 하는 레코드 수는 백그라운드 데이터 마이그레이션을 사용해야 할 지 여부를 결정하는 데 도움이 되는 좋은 지표입니다.
- 데이터 양이
1,000
레코드보다 적으면 데이터 마이그레이션을 순방향 마이그레이션 내에서 실행할 수 있습니다. - 데이터 양이
1,000
레코드보다 많으면 백그라운드 마이그레이션을 생성하는 것이 좋습니다.
어떤 옵션을 사용해야 하는지 확신이 없을 때는 데이터베이스 팀에게 문의하십시오.
예시에 다시 돌아와서, issues 테이블은 상당히 크고 자주 액세스되므로 13.0 마일스톤(현재)에 백그라운드 마이그레이션을 추가할 것입니다.
db/post_migrate/20200501000002_schedule_cap_title_length_on_issues.rb
:
class ScheduleCapTitleLengthOnIssues < Gitlab::Database::Migration[2.1]
# GitLab.com에서 영향을 받는 레코드 수, 각 배치가 평균적으로 실행하는 데 필요한 시간 등 ...
BATCH_SIZE = 5000
DELAY_INTERVAL = 2.minutes.to_i
# 백그라운드 마이그레이션은 1024 제한보다 긴 제목을 가진 issues를 업데이트합니다
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
본 가이드를 간략하게 유지하기 위해, 백그라운드 마이그레이션의 정의를 건너뛰고 일괄 배포를 예약하는 데 사용되는 순방향 마이그레이션의 고수준 예제만 제공했습니다. 데이터 혹은 이슈 배포에 대한 자세한 내용은 batched background migrations을 참고해주시기 바랍니다.
텍스트 제한 유효성 검사 (다음 릴리스)
텍스트 제한을 유효성 검사하면 전체 테이블을 스캔하여 각 레코드가 올바른지 확인합니다.
여전히 예시에서, 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_1K')
remove_text_limit :ci_runners, :maintainer_note, constraint_name: check_constraint_name(:ci_runners, :maintainer_note, 'max_length')
end
def down
# no-op: 레코드가 길이(maintainer_note) > 255인 경우 실패할 수 있음
end
end
대규모 테이블의 텍스트 제한 제약 조건
실제로 대규모 테이블의 텍스트 열을 정리해야 하는 경우(ci_builds
의 artifacts
예시), 백그라운드 마이그레이션이 한동안 실행되어야 하며 데이터 마이그레이션 추가 후에 추가 일괄 백그라운드 마이그레이션 정리가 필요합니다.
그 특별한 경우에는 데이터 마이그레이션을 추가한 발매로 3개의 발매가 필요합니다:
- 발매
N.M
- 텍스트 제한과 기존 레코드를 수정하는 백그라운드 마이그레이션 추가. - 발매
N.M+1
- 백그라운드 마이그레이션 정리. - 발매
N.M+2
- 텍스트 제한을 유효성 검사함.