- 배경 정보
- 텍스트 열이 있는 새 테이블 생성
- 기존 테이블에 텍스트 열 추가
- 기존 열에 텍스트 제한 제약 조건 추가
- 기존 열의 텍스트 제한 제약 증가
- 대형 테이블의 텍스트 제한 제약
문자열과 텍스트 데이터 타입
문자열 또는 기타 텍스트 정보를 저장하기 위해 새로운 열을 추가할 때:
- 항상
string
데이터 타입 대신text
데이터 타입을 사용합니다. -
text
열은 항상 제한을 설정해야 하며, 테이블을 생성할 때create_table
과 함께#text ... limit: 100
헬퍼를 사용하거나, 기존 테이블을 변경할 때add_text_limit
을 사용합니다.
표준 Rails text
열 유형은 제한을 정의할 수 없지만, 우리는 create_table
을 확장하여
limit: 255
옵션을 추가합니다. create_table
외부에서는 add_text_limit
을 사용하여 이미 존재하는 열에 check constraint를 추가할 수 있습니다.
배경 정보
항상 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!
로 트랜잭션을 비활성화해야 합니다. 이는 마이그레이션이 이후에 실패할 경우 열 추가가 롤백되지 않음을 의미합니다. 마이그레이션을 다시 실행하려고 하면 이미 존재하는 열 때문에 오류가 발생합니다.
따라서 기존 테이블에 텍스트 열을 추가하는 방법은 다음 중 하나입니다:
별도의 마이그레이션에서 열 및 제한 추가
sprints
테이블에 새로운 텍스트 열 extended_title
을 추가하는 마이그레이션을 고려해보세요.
db/migrate/20200501000001_add_extended_title_to_sprints.rb
:
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.1]
# rubocop:disable Migration/AddLimitToTextColumns
# 제한은 20200501000002_add_text_limit_to_sprints_extended_title에서 추가됩니다.
def change
add_column :sprints, :extended_title, :text
end
# rubocop:enable Migration/AddLimitToTextColumns
end
두 번째 마이그레이션은 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
# 다운은 `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
와 함께 텍스트 열에 제한을 추가하는 배포 후 마이그레이션을 추가합니다. -
기존 레코드를 수정하기 위한 배포 후 마이그레이션을 추가합니다.
참고: 테이블의 크기에 따라 다음 릴리스에서 정리를 위한 백그라운드 마이그레이션이 필요할 수 있습니다. 자세한 내용은 대형 테이블의 텍스트 제한 제약 조건을 참조하세요.
- 텍스트 제한을 검증하기 위한 다음 마일스톤에 대한 이슈를 만듭니다.
-
-
릴리스
N.M+1
(다음 릴리스)- 배포 후 마이그레이션을 사용하여 텍스트 제한을 검증합니다.
예시
지정된 릴리스 마일스톤에 대해 issues.title_html
에 1024
제한을 추가하고 싶다고 가정해 보겠습니다.
예를 들어 13.0과 같은 릴리스 마일스톤에 대해 말입니다.
Issues 테이블은 2,500만 개 이상의 행을 가진 매우 분주하고 큰 테이블이므로 업데이트를 실행하는 동안 다른 모든 프로세스를 잠그고 싶지 않습니다.
또한, 우리의 프로덕션 데이터베이스를 확인한 결과, 제목이 1024자 제한보다 많은 문자를 포함하는 issues
가 있다는 것을 알고 있으므로 하나의 단계에서 제약 조건을 추가하고 검증할 수 없습니다.
참고: 주어진 제한보다 큰 제목을 가진 레코드가 없었다고 하더라도, 다른 GitLab 인스턴스가 그런 레코드를 가질 수 있으므로 우리는 어떤 경우든 동일한 프로세스를 따를 것입니다.
새로운 잘못된 레코드 방지 (현재 릴리스)
우리는 먼저 테이블에 NOT VALID
체크 제약 조건으로 제한을 추가하여 새로운 레코드가 삽입되거나 기존 레코드가 업데이트될 때 일관성을 보장합니다.
위의 예에서, 제목이 1024자가 넘는 기존 문제는 영향을 받지 않으며, 여전히 issues
테이블의 레코드를 업데이트할 수 있습니다. 그러나 1024자를 초과하는 제목으로 title_html
을 업데이트하려고 하면, 제약 조건이 데이터베이스 오류를 발생시킵니다.
기존 속성에 제약 조건을 추가하거나 제거하려면 모든 애플리케이션 변경 사항이 먼저 배포되어야 하며, 그렇지 않으면 구 버전의 애플리케이션 서버가 잘못된 값으로 속성을 업데이트하려고 시도할 수 있습니다. 이러한 이유로 add_text_limit
는 게시 후 마이그레이션에서 실행되어야 합니다.
여전히 우리의 예에서 13.0 마일스톤(현재)에서, 다음과 같은 유효성 검사가 모델 Issue
에 추가되었다고 가정합니다:
validates :title_html, length: { maximum: 1024 }
우리는 또한 13.0 마일스톤에서 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
레코드 이상이면 백그라운드 마이그레이션을 생성하는 것이 좋습니다.
어떤 옵션을 사용할지 확실하지 않은 경우 데이터베이스 팀에 조언을 요청하세요.
예제로 돌아가서, 문제 테이블은 상당히 크고 자주 접근되므로 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_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_1K')
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
- 텍스트 제한 검증.