마이그레이션에서 다운타임 방지하기
GitLab v19.1데이터베이스를 다루다 보면 일부 작업은 다운타임을 필요로 할 수 있습니다. 칼럼을 제거하는 것은 까다롭습니다. 이는 칼럼이 명시적으로 무시(ignore) 처리되지 않은 경우 발생합니다. 또한, 해당 칼럼을 참조하는 데이터베이스 뷰도 고려해야 합니다.
데이터베이스를 다루다 보면 일부 작업은 다운타임을 필요로 할 수 있습니다. 마이그레이션에서 다운타임을 허용할 수 없으므로, 다운타임 없이 동일한 결과를 얻기 위한 일련의 단계를 사용해야 합니다. 이 가이드는 다운타임이 필요할 것처럼 보이는 다양한 작업, 그 영향, 그리고 다운타임 없이 수행하는 방법을 설명합니다.
칼럼 삭제하기#
칼럼을 제거하는 것은 까다롭습니다. 실행 중인 GitLab 프로세스는 해당 칼럼이 존재한다고 가정하기 때문입니다. ActiveRecord는 칼럼이 참조되지 않더라도 부팅 시 테이블 스키마를 캐시합니다.
이는 칼럼이 명시적으로 무시(ignore) 처리되지 않은 경우 발생합니다.
또한, 해당 칼럼을 참조하는 데이터베이스 뷰도 고려해야 합니다.
이를 안전하게 해결하려면 세 번의 릴리즈에 걸쳐 세 단계를 수행해야 합니다:
-
칼럼 무시하기 (릴리즈 M)
-
칼럼 삭제하기 (릴리즈 M+1)
-
무시 규칙 제거하기 (릴리즈 M+2)
세 번의 릴리즈에 걸쳐 분산시키는 이유는 칼럼 삭제가 쉽게 롤백할 수 없는 파괴적인 작업이기 때문입니다.
이 절차를 따르면 GitLab.com 배포 및 GitLab Self-Managed 인스턴스의 업그레이드 과정에서 이 단계들이 한꺼번에 묶이지 않도록 보장할 수 있습니다.
칼럼 무시하기 (릴리즈 M)#
첫 번째 단계는 애플리케이션 코드에서 칼럼을 무시 처리하고 모델 유효성 검사를 포함하여 해당 칼럼에 대한 모든 코드 참조를 제거하는 것입니다.
이 단계가 필요한 이유는 Rails가 칼럼을 캐시하고 다양한 곳에서 이 캐시를 재사용하기 때문입니다.
무시할 칼럼을 정의하는 방식으로 이 작업을 수행할 수 있습니다. 예를 들어, 릴리즈 12.5에서 User 모델의 updated_at을 무시하려면 다음을 사용합니다:
class User < ApplicationRecord
ignore_column :updated_at, remove_with: '12.7', remove_after: '2019-12-22'
end
여러 칼럼을 동시에 무시할 수도 있습니다:
ignore_columns %i[updated_at created_at], remove_with: '12.7', remove_after: '2019-12-22'
모델이 CE와 EE에 모두 존재하는 경우, 칼럼은 CE 모델에서 무시 처리해야 합니다. 모델이 EE에만 존재하는 경우에는 EE에 추가해야 합니다.
칼럼 무시 규칙을 언제 제거해도 안전한지 다음과 같이 표시해야 합니다:
-
remove_with: 칼럼 무시를 추가한 후 보통 두 릴리즈(M+2) 뒤의 GitLab 릴리즈로 설정합니다(예시에서는12.7). -
remove_after: 칼럼 무시를 제거해도 안전하다고 판단하는 날짜로 설정합니다. 일반적으로 M+1 릴리즈 날짜 이후, M+2 개발 사이클 중에 해당합니다. 예를 들어,12.7의 개발 사이클이2019-12-18~2020-01-17사이이고,12.6이 칼럼을 삭제하는 릴리즈라면,12.6의 릴리즈 날짜인2019-12-22로 날짜를 설정하는 것이 안전합니다.
이 정보를 통해 칼럼 무시에 대해 더 명확하게 판단할 수 있으며, 일반 릴리즈와 GitLab.com 배포 모두에서 칼럼 무시를 너무 일찍 제거하지 않도록 보장합니다. 예를 들어, 칼럼을 무시하는 변경 사항과 칼럼 무시를 제거하는 변경 사항을 함께 배포하는 상황(다운타임을 유발할 수 있는)을 방지합니다.
이 예시에서 칼럼 무시 변경 사항은 릴리즈 12.5에 포함되었습니다.
칼럼 무시와 칼럼 삭제는 같은 릴리즈에서 동시에 발생해서는 안 됩니다. 모델에서 올바르게 무시 처리하기 전에 칼럼을 삭제하면 제로 다운타임 마이그레이션에서 문제가 발생할 수 있습니다. 실행 중인 인스턴스가 Rails 스키마 캐시가 만료될 때까지 제거된 칼럼을 조회하려고 시도하여 실패할 수 있습니다. 이는 제로 다운타임 업그레이드를 따르려는 Self-managed 고객에게 문제가 될 수 있으며, 업데이트된 스키마를 다시 로드하기 위해 실행 중인 모든 GitLab 인스턴스를 명시적으로 재시작해야 합니다. 이 시나리오를 피하려면, 먼저 칼럼을 무시(릴리즈 M)하고 그 다음 릴리즈(릴리즈 M+1)에서 삭제합니다.
데이터베이스 뷰에서 참조되는 칼럼 무시하기#
칼럼이 다음 예시처럼 데이터베이스 뷰에서도 참조되는 경우:
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username, updated_at
FROM users
WHERE updated_at > now() - interval '30 day'
해당 모델 클래스에도 ignore_columns 지시문을 포함해야 합니다:
class RecentlyUpdatedUsersView < ApplicationRecord
self.table_name = 'recently_updated_users_view'
ignore_columns :updated_at
end
칼럼 삭제하기 (릴리즈 M+1)#
예시를 계속하면, 칼럼 삭제는 릴리즈 12.6의 post-deployment 마이그레이션에 포함됩니다:
post-deployment migration을 생성하는 것부터 시작합니다:
bundle exec rails g post_deployment_migration remove_users_updated_at_column
칼럼을 제거하는 마이그레이션을 작성할 때 다음 시나리오를 고려해야 합니다:
제거할 칼럼에 속하는 인덱스나 제약 조건이 없는 경우#
이 경우에는 트랜잭션 마이그레이션을 사용할 수 있습니다:
class RemoveUsersUpdatedAtColumn < Gitlab::Database::Migration[2.1]
def up
remove_column :users, :updated_at
end
def down
add_column :users, :updated_at, :datetime
end
end
제거할 칼럼에 속하는 인덱스나 제약 조건이 있는 경우#
down 메서드에서 삭제된 인덱스나 제약 조건을 다시 추가해야 하는 경우, 트랜잭션 마이그레이션에서는 이를 수행할 수 없습니다.
마이그레이션은 다음과 같이 작성합니다:
class RemoveUsersUpdatedAtColumn < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
remove_column :users, :updated_at
end
def down
add_column(:users, :updated_at, :datetime, if_not_exists: true)
# Make sure to add back any indexes or constraints,
# that were dropped in the `up` method. For example:
add_concurrent_index(:users, :updated_at)
end
end
down 메서드에서 칼럼을 다시 추가하기 전에 이미 존재하는지 확인합니다.
마이그레이션이 비트랜잭션 방식이므로 실행 중에 실패했을 수 있기 때문입니다.
disable_ddl_transaction!은 전체 마이그레이션을 감싸는 트랜잭션을 비활성화하는 데 사용됩니다.
데이터베이스 마이그레이션에 대한 자세한 내용은 마이그레이션 스타일 가이드 페이지를 참조하세요.
제거할 칼럼이 데이터베이스 뷰에서 참조되는 경우#
칼럼이 데이터베이스 뷰에서 참조되는 경우, 해당 칼럼에 제약 조건이 붙어 있는 것처럼 동작하므로 칼럼을 삭제하기 전에 먼저 뷰를 업데이트해야 합니다:
-
해당 칼럼을 제외하여 뷰를 재생성합니다.
-
원본 테이블에서 칼럼을 삭제합니다.
down 메서드는 칼럼이 뷰에서 참조되기 전에 존재해야 하므로 역순으로 작업을 수행해야 합니다:
-
원본 테이블에 칼럼을 다시 추가합니다.
-
칼럼을 포함하여 뷰를 다시 재생성합니다.
마이그레이션은 다음과 같이 작성합니다:
class RemoveUsersUpdatedAtColumn < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username) AS
SELECT id, username
FROM users;
SQL
remove_column :users, :updated_at
end
def down
add_column :users, :updated_at, :datetime
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username, updated_at
FROM users
WHERE updated_at > now() - interval '30 day'
SQL
end
end
무시 규칙 제거하기 (릴리즈 M+2)#
다음 릴리즈, 이 예시에서는 12.7에서, 무시 규칙을 제거하기 위한 또 다른 머지 리퀘스트를 설정합니다.
이렇게 하면 ignore_column 줄이 제거되고, 더 이상 필요하지 않은 경우 IgnoreableColumns 포함도 제거됩니다.
이는 remove_with에 표시된 릴리즈와 함께, remove_after 날짜가 지난 후에만 병합되어야 합니다.
칼럼 이름 변경하기#
아래 절차는 소규모 테이블에만 적합합니다. 이 절차는 일반 마이그레이션에서 한 칼럼의 모든 데이터를 다른 칼럼으로 복사하는데, 이는 대규모 테이블에서는 너무 오래 걸릴 수 있습니다. 대규모 테이블의 경우 데이터를 복사하고 여러 마일스톤에 걸쳐 이름 변경을 수행하기 위해 배치 백그라운드 마이그레이션을 사용하는 방법을 검토해야 합니다.
칼럼 이름을 표준 방식으로 변경하면 데이터베이스 마이그레이션 중이나 이후에 애플리케이션이 계속 이전 칼럼 이름을 사용할 수 있기 때문에 다운타임이 필요합니다. 다운타임 없이 칼럼 이름을 변경하려면 두 개의 마이그레이션이 필요합니다: 일반 마이그레이션과 post-deployment 마이그레이션. 두 마이그레이션 모두 같은 릴리즈에 포함될 수 있습니다. 단계:
-
일반 마이그레이션 추가하기 (릴리즈 M)
-
칼럼 무시하기 (릴리즈 M)
-
post-deployment 마이그레이션 추가하기 (릴리즈 M)
-
무시 규칙 제거하기 (릴리즈 M+1)
데이터베이스 뷰에서 참조되는 칼럼의 이름을 일반적인 방식으로 변경할 때는 SELECT 부분을 그대로 유지하면서 새 칼럼 이름으로 뷰가 업데이트되므로 추가 단계가 필요하지 않습니다.
다운타임 없이 수행할 때는 위의 단계에서 언급된 추가 고려 사항이 있습니다.
일반 마이그레이션 추가하기 (릴리즈 M)#
먼저 일반 마이그레이션을 생성해야 합니다. 이 마이그레이션은 이름 변경을 수행하기 위해
Gitlab::Database::MigrationHelpers#rename_column_concurrently를 사용해야 합니다. 예를 들어:
# A regular migration in db/migrate
class RenameUsersUpdatedAtToUpdatedAtTimestamp < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
rename_column_concurrently :users, :updated_at, :updated_at_timestamp
end
def down
undo_rename_column_concurrently :users, :updated_at, :updated_at_timestamp
end
end
이렇게 하면 칼럼 이름 변경, 데이터 동기화 유지, 인덱스 및 외래 키 복사가 처리됩니다.
칼럼에 원본 칼럼 이름이 포함되지 않은 인덱스가 하나 이상 있는 경우, 앞서 설명한 절차가 실패합니다. 이 경우 해당 인덱스의 이름을 변경해야 합니다.
칼럼이 데이터베이스 뷰에서 참조되는 경우, 뷰를 재생성하고 새 칼럼을 가리키도록 해야 합니다.
down 작업은 undo_rename_column_concurrently를 실행하기 전에 이를 복원해야 합니다:
# A regular migration in db/migrate including database view recreation
class RenameUsersUpdatedAtToUpdatedAtTimestamp < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
rename_column_concurrently :users, :updated_at, :updated_at_timestamp
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username, updated_at_timestamp
FROM users
WHERE updated_at > now() - interval '30 day'
SQL
end
def down
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username, updated_at
FROM users
WHERE updated_at > now() - interval '30 day'
SQL
undo_rename_column_concurrently :users, :updated_at, :updated_at_timestamp
end
end
칼럼 무시하기 (릴리즈 M)#
다음 단계는 애플리케이션 코드에서 칼럼을 무시 처리하고 사용되지 않도록 하는 것입니다. Rails가 칼럼을 캐시하고 다양한 곳에서 이 캐시를 재사용하기 때문에 이 단계가 필요합니다. 이 단계는 칼럼을 삭제할 때의 첫 번째 단계와 유사하며, 동일한 요구 사항이 적용됩니다.
class User < ApplicationRecord
ignore_column :updated_at, remove_with: '12.7', remove_after: '2019-12-22'
end
post-deployment 마이그레이션 추가하기 (릴리즈 M)#
이름 변경 절차는 post-deployment 마이그레이션에서 일부 정리 작업이 필요합니다.
Gitlab::Database::MigrationHelpers#cleanup_concurrent_column_rename을 사용하여 이 정리 작업을 수행할 수 있습니다:
# A post-deployment migration in db/post_migrate
class CleanupUsersUpdatedAtRename < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp
end
def down
undo_cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp
end
end
대규모 테이블의 이름을 변경하는 경우, 첫 번째 마이그레이션은 실행되었지만 두 번째 정리 마이그레이션은 아직 실행되지 않은 상태를 신중하게 고려해야 합니다. Canary를 사용하면 시스템이 상당한 시간 동안 이 상태로 운영될 수 있습니다.
무시 규칙 제거하기 (릴리즈 M+1)#
칼럼을 삭제할 때와 동일하게, 이름 변경이 완료된 후 다음 릴리즈에서 무시 규칙을 제거해야 합니다.
칼럼 제약 조건 변경하기#
NOT NULL 절(또는 다른 제약 조건)을 추가하거나 제거하는 것은 일반적으로 다운타임 없이 수행할 수 있습니다.
NOT NULL 제약 조건을 추가하려면 애플리케이션 변경 사항이 먼저 배포되어야 하므로, post-deployment 마이그레이션에서 수행해야 합니다.
반대로 NOT NULL 제약 조건을 제거하는 것은 일반 마이그레이션에서 수행해야 합니다.
이렇게 하면 NULL 값을 삽입하는 코드가 해당 칼럼에서 안전하게 실행될 수 있습니다.
전체 칼럼 타입을 재정의하는 비효율적인 쿼리를 생성하므로 change_column 사용을 피하세요.
각 특정 사용 사례에 대해서는 다음 가이드를 참조하세요:
칼럼 타입 변경하기#
칼럼 타입은 Gitlab::Database::MigrationHelpers#change_column_type_concurrently를 사용하여 변경할 수 있습니다.
이 메서드는 rename_column_concurrently와 유사하게 동작합니다. 예를 들어,
users.username의 타입을 string에서 text로 변경하려는 경우:
데이터베이스 뷰에서 참조되는 칼럼의 타입을 변경할 때는 프로세스의 일부로 뷰를 재생성해야 합니다.
일반 마이그레이션 생성하기#
일반 마이그레이션은 임시 이름으로 새 칼럼을 생성하고 데이터를 동기화 상태로 유지하기 위한 트리거를 설정하는 데 사용됩니다. 이러한 마이그레이션은 다음과 같이 작성합니다:
# A regular migration in db/migrate
class ChangeUsersUsernameStringToText < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
change_column_type_concurrently :users, :username, :text
end
def down
undo_change_column_type_concurrently :users, :username
end
end
칼럼이 데이터베이스 뷰에서 참조되는 경우, 뷰를 재생성하고 새 임시 칼럼을 가리키도록 해야 합니다.
이후 단계에서 임시 칼럼의 이름이 원래 이름으로 다시 변경될 때, 뷰는 내부적으로 자동으로 업데이트되므로 다른 변경이 필요하지 않습니다:
# A regular migration in db/migrate including database view recreation
class ChangeUsersUsernameStringToText < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
change_column_type_concurrently :users, :username, :text
# temporary column name follows this pattern: `"#{column}_for_type_change"`
# so the column named `username` becomes `username_for_type_change`
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username_for_type_change, updated_at
FROM users
WHERE updated_at > now() - interval '30 day'
SQL
end
def down
execute <<-SQL
DROP VIEW IF EXISTS recently_updated_users_view;
CREATE VIEW recently_updated_users_view(id, username, updated_at) AS
SELECT id, username, updated_at_timestamp
FROM users
WHERE updated_at > now() - interval '30 day'
SQL
undo_change_column_type_concurrently :users, :username
end
end
post-deployment 마이그레이션 생성하기#
다음으로 post-deployment 마이그레이션을 사용하여 변경 사항을 정리해야 합니다:
# A post-deployment migration in db/post_migrate
class ChangeUsersUsernameStringToTextCleanup < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
cleanup_concurrent_column_type_change :users, :username
end
def down
undo_cleanup_concurrent_column_type_change :users, :username, :string
end
end
이것으로 완료됩니다!
새 타입으로 데이터 캐스팅하기#
일부 타입 변경은 데이터를 새 타입으로 캐스팅해야 합니다. 예를 들어 text에서 jsonb로 변경하는 경우입니다.
이 경우 type_cast_function 옵션을 사용합니다.
잘못된 데이터가 없고 캐스팅이 항상 성공하는지 확인하세요. 캐스팅 오류를 처리하는 커스텀 함수를 제공할 수도 있습니다.
마이그레이션 예시:
def up
change_column_type_concurrently :users, :settings, :jsonb, type_cast_function: 'jsonb'
end
칼럼 기본값 변경하기#
칼럼 기본값 변경은 Rails가 기본값과 동일한 값을 처리하는 방식 때문에 어렵습니다.
Rails는 partial_inserts 설정이 활성화된 경우 레코드를 삽입할 때 기본값을 PostgreSQL에 전송하지 않습니다. 이 작업은 데이터베이스에 맡깁니다. 마이그레이션이 칼럼의 기본값을 변경할 때, 실행 중인 애플리케이션은 스키마 캐시로 인해 이 변경 사항을 알지 못합니다. 특히 데이터베이스 마이그레이션을 실행한 후 한참 뒤에 새 버전의 코드를 배포할 때 애플리케이션이 잘못된 데이터를 데이터베이스에 실수로 쓸 위험이 있습니다.
실행 중인 코드가 칼럼의 이전 기본값을 명시적으로 쓰는 경우, 이전 기본값을 명시적으로 지정하는 INSERT 쿼리에서 Rails가 이전 기본값을 새 기본값으로 대체하지 않도록 다단계 프로세스를 따라야 합니다.
이를 위해 두 개의 마이너 릴리즈에 걸친 단계가 필요합니다:
-
모델에
SafelyChangeColumnDefaultconcern 추가하기와 post-migration에서 기본값 변경하기.
Self-managed 릴리즈는 전체 마이너 릴리즈를 단일 제로 다운타임 배포로 번들링하기 때문에
SafelyChangeColumnDefault를 정리하기 전에 마이너 릴리즈 하나를 기다려야 합니다.
모델에 SafelyChangeColumnDefault concern 추가하고 post-migration에서 기본값 변경하기#
첫 번째 단계는 애플리케이션 코드에서 칼럼을 안전하게 변경 가능으로 표시하는 것입니다.
class Ci::Build < ApplicationRecord
include SafelyChangeColumnDefault
columns_changing_default :partition_id
end
그런 다음 기본값을 변경하기 위한 post-deployment migration을 생성합니다:
bundle exec rails g post_deployment_migration change_ci_builds_default
class ChangeCiBuildsDefault < Gitlab::Database::Migration[2.1]
def change
change_column_default('ci_builds', 'partition_id', from: 100, to: 101)
end
end
다음 마이너 릴리즈에서 SafelyChangeColumnDefault concern 정리하기#
다음 마이너 릴리즈에서 columns_changing_default 호출을 제거하는 새 머지 리퀘스트를 생성합니다.
다른 칼럼에 필요하지 않은 경우 SafelyChangeColumnDefault 포함도 제거합니다.
대규모 테이블의 스키마 변경하기#
change_column_type_concurrently와 rename_column_concurrently는 다운타임 없이 테이블 스키마를 변경하는 데 사용할 수 있지만,
대규모 테이블에서는 잘 동작하지 않습니다. 모든 작업이 순차적으로 수행되기 때문에 마이그레이션이 완료되는 데 매우 오랜 시간이 걸려 배포가 진행되지 못할 수 있습니다.
또한 순차적으로 많은 행을 빠르게 업데이트하므로 데이터베이스에 상당한 압력을 줄 수 있습니다.
데이터베이스 압력을 줄이기 위해서는 대규모 테이블(예: issues)의 칼럼을 마이그레이션할 때 백그라운드 마이그레이션을 사용해야 합니다.
백그라운드 마이그레이션은 배포를 늦추지 않고 작업/부하를 더 긴 시간에 걸쳐 분산합니다.
자세한 내용은 배치 백그라운드 마이그레이션 정리에 관한 문서를 참조하세요.
인덱스 추가하기#
add_concurrent_index를 사용하면 인덱스를 추가하는 데 다운타임이 필요하지 않습니다.
자세한 내용은 마이그레이션 스타일 가이드를 참조하세요.
인덱스 삭제하기#
인덱스를 삭제하는 데는 다운타임이 필요하지 않습니다.
테이블 추가하기#
이 작업은 해당 테이블을 사용하는 코드가 아직 없으므로 안전합니다.
테이블 삭제하기#
테이블을 삭제할 때는 다운타임을 피하기 위해 여러 릴리즈에 걸친 프로세스가 필요합니다:
-
릴리즈 M: 테이블을 사용하는 모든 애플리케이션 코드를 제거합니다.
-
릴리즈 M+1: 각각 별도의 post-deployment 마이그레이션으로, 외래 키를 제거한 후 테이블을 삭제합니다.
데이터베이스 딕셔너리에 설명된 프로세스를 사용하여 테이블을 db/docs/deleted_tables에 추가합니다.
테이블이 삭제되더라도 데이터베이스 마이그레이션에서 여전히 참조됩니다.
테이블 삭제 전 외래 키 제거하기#
테이블에 외래 키가 있는 경우, 테이블을 삭제하는 마이그레이션 전에 각 외래 키를 자체 post-deployment 마이그레이션에서 제거합니다.
다른 테이블을 여전히 참조하는 테이블을 삭제하면 참조된 테이블도 잠깁니다. 트래픽이 많은 테이블에 대한 외래 키가 있으면 DROP TABLE이 타임아웃될 수 있습니다.
외래 키가 트래픽이 많은 테이블(예: users, projects, namespaces)을 참조하는 경우,
해당 테이블에 ACCESS EXCLUSIVE 잠금이 필요합니다. 잠금 재시도 없이는 이 잠금이 타임아웃되어 수동 재시도가 필요할 수 있습니다.
with_lock_retries로 제거를 래핑합니다. 그러면 마이그레이션이 외래 키를 제거하기 전에 재시도하면서 먼저 참조된 테이블을 잠급니다:
class RemoveUploadsArchivedToUsersForeignKey < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '19.1'
SOURCE_TABLE_NAME = :uploads_archived
COLUMN = :uploaded_by_user_id
FOREIGN_KEY_NAME = :fk_b94f059d73
def up
with_lock_retries do
remove_foreign_key_if_exists SOURCE_TABLE_NAME, column: COLUMN, name: FOREIGN_KEY_NAME
end
end
def down
add_concurrent_foreign_key SOURCE_TABLE_NAME, :users,
column: COLUMN, on_delete: :nullify, name: FOREIGN_KEY_NAME
end
end
with_lock_retries는 짧은 lock_timeout으로 트랜잭션 내에서 제거를 실행하고, 타임아웃될 때까지 대기하는 대신 잠금을 획득하지 못하면 재시도합니다.
remove_foreign_key_if_exists는 기본적으로 reverse_lock_order: true를 사용하여 동시 애플리케이션 트랜잭션과의 교착 상태를 방지하기 위해 소스 테이블보다 먼저 참조된 테이블을 잠급니다.
자세한 내용은 reverse_lock_order를 참조하세요.
모든 외래 키가 제거된 후, 별도의 post-deployment 마이그레이션에서 테이블을 삭제합니다.
테이블 이름 변경하기#
테이블 이름을 변경하면 데이터베이스 마이그레이션 중이나 이후에 애플리케이션이 계속 이전 테이블 이름을 사용할 수 있기 때문에 다운타임이 필요합니다.
테이블과 ActiveRecord 모델이 아직 사용 중이 아닌 경우, 이전 테이블을 제거하고 새 테이블을 생성하는 것이 테이블을 "이름 변경"하는 선호되는 방법입니다.
여러 릴리즈에 걸친 테이블 이름 변경 프로세스를 따르면 다운타임 없이 테이블 이름을 변경할 수 있습니다.
외래 키 추가하기#
외래 키 추가는 잠재적으로 다운타임을 유발할 수 있습니다. 자세한 내용은 FK: 다운타임 및 마이그레이션 실패 방지 문서를 참조하세요.
정수형 기본 키를 bigint로 마이그레이션하기#
integer 기본 키(PK)가 있는 일부 테이블의 오버플로우 위험을 방지하기 위해,
해당 테이블의 PK를 bigint로 마이그레이션해야 합니다. 다운타임 없이, 데이터베이스에 과도한 부하를 주지 않으면서 이를 수행하는 프로세스는 아래에 설명되어 있습니다.
변환 초기화 및 기존 데이터 마이그레이션 시작 (릴리즈 N)#
프로세스를 시작하려면 새 bigint 칼럼을 생성하는 일반 마이그레이션을 추가합니다. 제공된
initialize_conversion_of_integer_to_bigint 헬퍼를 사용합니다. 이 헬퍼는 새 레코드에 대해 두 칼럼을 동기화 상태로 유지하기 위한 데이터베이스 트리거도 생성합니다(코드):
# frozen_string_literal: true
class InitializeConversionOfMergeRequestMetricsToBigint < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
TABLE = :merge_request_metrics
COLUMNS = %i[id]
def up
initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
def down
revert_initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
end
새 bigint 칼럼을 무시합니다:
# frozen_string_literal: true
class MergeRequest::Metrics < ApplicationRecord
ignore_column :id_convert_to_bigint, remove_with: '16.0', remove_after: '2023-05-22'
end
기존 데이터를 마이그레이션하기 위한 배치 백그라운드 마이그레이션을 대기열에 추가합니다(코드):
# frozen_string_literal: true
class BackfillMergeRequestMetricsForBigintConversion < Gitlab::Database::Migration[2.1]
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
TABLE = :merge_request_metrics
COLUMNS = %i[id]
def up
backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS, sub_batch_size: 200)
end
def down
revert_backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
end
-
Issue#438124로 인해 새 인스턴스는 모든 ID 칼럼이 bigint입니다. 이전 인스턴스(GitLab.com 포함)에서 아직 bigint로 변환되지 않은 ID 목록은
db/integer_ids_not_yet_initialized_to_bigint.yml에 유지됩니다. 이 파일을 수동으로 편집하지 마세요 - 정리 프로세스 중에 자동으로 업데이트됩니다. -
스키마 파일에는 이미 모든 ID가
bigint로 되어 있으므로,db/structure.sql에 변경 사항을 푸시하지 마세요.
백그라운드 마이그레이션 모니터링하기#
마이그레이션이 실행되는 동안 성능을 확인합니다. 아래에 여러 방법이 설명되어 있습니다.
배치 백그라운드 마이그레이션의 상위 수준 상태#
배치 백그라운드 마이그레이션 상태 확인 방법을 참조하세요.
데이터베이스 쿼리하기#
관련 데이터베이스 테이블을 직접 쿼리할 수 있습니다. 읽기 전용 복제본에 대한 액세스가 필요합니다. 쿼리 예시:
-- Get details for batched background migration for given table
SELECT * FROM batched_background_migrations WHERE table_name = 'namespaces'\gx
-- Get count of batched background migration jobs by status for given table
SELECT
batched_background_migrations.id, batched_background_migration_jobs.status, COUNT(*)
FROM
batched_background_migrations
JOIN batched_background_migration_jobs ON batched_background_migrations.id = batched_background_migration_jobs.batched_background_migration_id
WHERE
table_name = 'namespaces'
GROUP BY
batched_background_migrations.id, batched_background_migration_jobs.status;
-- Batched background migration progress for given table (based on estimated total number of tuples)
SELECT
m.table_name,
LEAST(100 * sum(j.batch_size) / pg_class.reltuples, 100) AS percentage_complete
FROM
batched_background_migrations m
JOIN batched_background_migration_jobs j ON j.batched_background_migration_id = m.id
JOIN pg_class ON pg_class.relname = m.table_name
WHERE
j.status = 3 AND m.table_name = 'namespaces'
GROUP BY m.id, pg_class.reltuples;
Sidekiq 로그#
배치 백그라운드 마이그레이션을 실행하는 워커를 모니터링하기 위해 Sidekiq 로그를 사용할 수도 있습니다:
-
@gitlab.com이메일 주소로 Kibana에 로그인합니다. -
인덱스 패턴을
pubsub-sidekiq-inf-gprd*로 변경합니다. -
json.queue: cronjob:database_batched_background_migration필터를 추가합니다.
PostgreSQL 느린 쿼리 로그#
느린 쿼리 로그는 실행하는 데 1초 이상 걸린 쿼리를 추적합니다. 배치 백그라운드 마이그레이션에 대한 쿼리를 확인하려면:
-
@gitlab.com이메일 주소로 Kibana에 로그인합니다. -
인덱스 패턴을
pubsub-postgres-inf-gprd*로 변경합니다. -
json.endpoint_id.keyword: Database::BatchedBackgroundMigrationWorker필터를 추가합니다. -
선택 사항. 업데이트만 보려면
json.command_tag.keyword: UPDATE필터를 추가합니다. -
선택 사항. 실패한 구문만 보려면
json.error_severity.keyword: ERROR필터를 추가합니다. -
선택 사항. 테이블 이름으로 필터를 추가합니다.
Grafana 대시보드#
데이터베이스 상태를 모니터링하려면 다음 추가 메트릭을 사용합니다:
-
PostgreSQL Tuple Statistics: 활발하게 변환 중인 테이블에 대한 높은 업데이트 비율이나 이 테이블의 증가하는 데드 튜플 비율이 보이면,
autovacuum이 따라가지 못하고 있을 수 있습니다. -
PostgreSQL Overview: 기본 데이터베이스 서버에서 높은 시스템 사용률이나 초당 트랜잭션(TPS)이 보이면, 마이그레이션이 문제를 일으키고 있을 수 있습니다.
Prometheus 메트릭#
각 배치 백그라운드 마이그레이션의 메트릭 수가 Prometheus에 게시됩니다. 이 메트릭은 Grafana에서 검색하고 시각화할 수 있습니다(예시 보기).
칼럼 교체하기 (릴리즈 N + 1)#
백그라운드 마이그레이션이 완료되고 새 bigint 칼럼이 모든 레코드에 대해 채워지면,
칼럼을 교체할 수 있습니다. 교체는 post-deployment 마이그레이션으로 수행됩니다. 정확한 프로세스는 변환되는 테이블에 따라 다르지만, 일반적으로 다음 단계로 수행됩니다:
제공된 ensure_backfill_conversion_of_integer_to_bigint_is_finished 헬퍼를 사용하여 배치 마이그레이션이 완료되었는지 확인합니다.
마이그레이션이 완료되지 않은 경우, 후속 단계는 어차피 실패합니다. 미리 확인함으로써 더 유용한 오류 메시지를 제공할 수 있습니다.
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
def up
ensure_backfill_conversion_of_integer_to_bigint_is_finished(
:ci_builds,
%i[
project_id
runner_id
user_id
],
# optional. Only needed when there is no primary key, for example, like schema_migrations.
primary_key: :id
)
end
def down; end
Gitlab::Database::MigrationHelpers::ConvertToBigint 모듈의 add_bigint_column_indexes 헬퍼 메서드를 사용하여
integer 칼럼을 사용하는 기존 인덱스와 일치하는 bigint 칼럼으로 인덱스를 생성합니다.
헬퍼 메서드는 필요한 모든 bigint 인덱스를 생성할 것으로 예상되지만, 기존 인덱스 중 누락된 것이 없는지 다시 확인하는 것이 좋습니다. 헬퍼에 대한 자세한 내용은
머지 리퀘스트 135781에서 확인할 수 있습니다.
기존 FK와 일치하는 bigint 칼럼을 사용하는 외래 키(FK)를 생성합니다. 이는 다른 테이블을 참조하는 FK와 마이그레이션 중인 테이블을 참조하는 FK 모두에 해당합니다(예시 보기).
트랜잭션 내에서 칼럼을 교체합니다:
관련된 테이블을 잠급니다. 교착 상태에 빠질 가능성을 줄이기 위해 부모에서 자식 순서로 수행하는 것이 권장됩니다(예시 보기).
-
칼럼 이름을 교체합니다(예시 보기)
-
트리거 함수를 재설정합니다(예시 보기).
-
기본값을 교체합니다(예시 보기).
-
PK 제약 조건을 교체합니다(해당하는 경우)(예시 보기).
-
이전 인덱스를 제거하고 새 인덱스의 이름을 변경합니다(예시 보기).
add_bigint_column_indexes 헬퍼를 사용하여 생성된 bigint 인덱스의 이름은 Gitlab::Database::MigrationHelpers::ConvertToBigint 모듈의 bigint_index_name을 호출하여 검색할 수 있습니다.
- 이전 외래 키를 제거하고(아직 있는 경우) 새 외래 키의 이름을 변경합니다(예시 보기).
트리거 및 이전 정수형 칼럼 제거하기 (릴리즈 N + 2)#
post-deployment 마이그레이션과 제공된 cleanup_conversion_of_integer_to_bigint 헬퍼를 사용하여
데이터베이스 트리거와 이전 integer 칼럼을 삭제합니다(예시 보기).
무시 규칙 제거하기 (릴리즈 N + 3)#
칼럼이 삭제된 다음 릴리즈에서, 더 이상 필요하지 않으므로 무시 규칙을 제거합니다(예시 보기).
데이터베이스 뷰#
GitLab은 데이터베이스 뷰를 가볍게 사용합니다. 뷰는 마이그레이션 처리 시 복잡성을 추가할 수 있기 때문입니다.
현재 뷰가 사용되는 두 가지 상황이 있습니다:
-
Unified Backup CLI를 위한 제한된 읽기 전용 데이터 노출
Postgres 내부 메트릭#
Postgres 내부 메트릭은 Gitlab::Database::Postgres* 모델(lib/gitlab/database에 있음)을 통해 접근 가능하며,
Gitlab::Database::SharedModel 클래스에 의존합니다.
Unified Backup CLI#
Unified Backup CLI는 여러 리포지터리 타입에 대해 gitaly-backup을 트리거하는 데 필요한 제한된 정보를 검색하기 위해 몇 가지 뷰에 의존합니다.
뷰는 Gitlab::Backup::Cli::Models::*(gems/gitlab-backup-cli/lib/gitlab/backup/cli/models에 있음)를 통해 접근 가능하며
연결을 처리하기 위해 Gitlab::Backup::Cli::Models::Base 클래스에 의존합니다.
Unified Backup CLI 코드가 별도의 gem에 있기 때문에, 메인 코드베이스에는 필요한 뷰가 도구에 필요한 정보를 반환하는지 확인하는 스펙이 포함되어 있습니다. 이를 통해 두 코드베이스 간의 "계약"을 보장합니다.
이 뷰에 필요한 칼럼 중 하나가 변경되어야 하는 경우, 다음 단계를 따릅니다:
- 칼럼을 삭제하는 경우
Durability 팀(Unified Backup 담당)과 Gitaly(gitaly-backup 담당)와 조율합니다.
- 칼럼 이름을 변경하는 경우
뷰별 고려 사항을 포함하여 칼럼 이름 변경하기를 따릅니다.
- 칼럼 타입을 변경하는 경우
뷰별 고려 사항을 포함하여 칼럼 타입 변경하기를 따릅니다.
데이터 마이그레이션#
데이터 마이그레이션은 까다로울 수 있습니다. 데이터를 마이그레이션하는 일반적인 접근 방법은 3단계 방식을 취하는 것입니다:
-
초기 데이터 배치를 마이그레이션합니다.
-
애플리케이션 코드를 배포합니다.
-
나머지 데이터를 마이그레이션합니다.
보통 이 방법이 효과적이지만 항상 그런 것은 아닙니다. 예를 들어, 필드의 형식을 JSON에서 다른 형식으로 변경해야 하는 경우 약간의 문제가 있습니다. 애플리케이션 코드를 배포하기 전에 기존 데이터를 변경하면 오류가 발생할 가능성이 높습니다. 반대로, 애플리케이션 코드를 배포한 후에 마이그레이션하면 동일한 문제가 발생할 수 있습니다.
단순히 일부 잘못된 데이터를 수정해야 하는 경우, 일반적으로 post-deployment 마이그레이션으로 충분합니다. 데이터 형식을 변경해야 하는 경우(예: JSON에서 다른 형식으로), 일반적으로 새 데이터 형식을 위한 새 칼럼을 추가하고 애플리케이션이 그것을 사용하도록 하는 것이 가장 좋습니다. 이 경우 절차는 다음과 같습니다:
-
새 형식으로 새 칼럼을 추가합니다.
-
기존 데이터를 이 새 칼럼으로 복사합니다.
-
애플리케이션 코드를 배포합니다.
-
post-deployment 마이그레이션에서 나머지 데이터를 복사합니다.
일반적으로 모든 상황에 맞는 단일 해결책은 없으므로, 이러한 마이그레이션을 머지 리퀘스트에서 논의하여 최선의 방법으로 구현하는 것이 좋습니다.
스키마 유효성 검사를 사용하는 JSON/JSONB 칼럼 변경하기#
JsonSchemaValidator로 유효성 검사를 하는 JSON 및 JSONB 칼럼은 JSON 스키마가
additionalProperties: false를 사용하는 경우 배포 중 실패를 피하기 위해 다단계 프로세스가 필요합니다.
추가 속성을 허용하는 스키마는 영향을 받지 않습니다.
이 유효성 검사는 다음과 같은 모델 선언에 의해 트리거됩니다:
validates :onboarding_status, json_schema: { filename: 'user_detail_onboarding_status' }
filename은 app/validators/json_schemas/ 또는
ee/app/validators/json_schemas/의 JSON 스키마 파일에 매핑됩니다.
제로 다운타임 배포 중에 서로 다른 애플리케이션 인스턴스가 일시적으로 다른 코드 버전을 실행할 수 있습니다. 스키마 변경 사항과 코드 변경 사항이 함께 배포되면, 이전 코드 버전을 실행하는 인스턴스가 인식되지 않는 속성이 있는 데이터를 처리할 때 유효성 검사가 실패합니다.
속성 추가하기#
새 속성을 추가하려면 두 단계가 필요합니다:
-
app/validators/json_schemas/또는ee/app/validators/json_schemas/의 JSON 스키마 파일에 속성을 추가합니다. 이 단계에서는 새 속성을required목록에 추가하지 마세요. 이전 코드 버전을 실행하는 인스턴스는 새 속성을 채우지 않으며, 이를 필수로 표시하면 해당 인스턴스에서 유효성 검사 실패가 발생합니다. -
스키마 변경이 완전히 배포된 후, 새 속성을 사용하는 코드를 추가합니다. 속성이 필수여야 하는 경우, 모든 인스턴스가 해당 속성을 채운 후 이 단계 또는 이후 단계에서
required목록에 추가합니다.
속성 제거하기#
속성을 제거하려면 최소 세 단계가 필요합니다:
-
속성을 사용하는 모든 코드를 제거합니다.
-
코드 변경이 완전히 배포된 후, 기존 데이터베이스 레코드에서 속성 값을 제거합니다.
-
데이터 마이그레이션이 완료된 후, JSON 스키마 파일에서 속성을 제거합니다.
배포 타이밍#
GitLab.com 배포에서만 사용되는 속성의 경우, 다음 단계를 병합하기 전에 각 변경 사항이 완전히 배포될 때까지 기다립니다. 새 릴리즈를 기다릴 필요는 없습니다.
다른 모든 속성의 경우, 제로 다운타임 업그레이드 절차를 따르는 고객이 유효성 검사 실패를 겪지 않도록 각 단계가 별도의 릴리즈에 있어야 합니다.