날짜 범위 파티셔닝
GitLab v19.1GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝(date-range partitioning)으로, 테이블의 각 파티션이 단일 월의 데이터를 포함합니다. 더 구체적인 예시로, audit_events 테이블 사용을 고려해 보겠습니다.
날짜 범위 파티셔닝#
설명#
GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝(date-range partitioning)으로, 테이블의 각 파티션이 단일 월의 데이터를 포함합니다. 이 경우 파티셔닝 키는 타임스탬프 또는 날짜 칼럼이어야 합니다. 이 유형의 파티셔닝이 제대로 작동하려면 대부분의 쿼리가 특정 날짜 범위의 데이터에 접근해야 합니다.
더 구체적인 예시로, audit_events 테이블 사용을 고려해 보겠습니다.
이 테이블은 애플리케이션 데이터베이스에서 파티셔닝된 최초의 테이블입니다.
이 테이블은 애플리케이션에서 발생하는 보안 이벤트의 감사 항목을 추적합니다.
거의 모든 경우에 사용자는 특정 시간대에 발생한 감사 활동을 확인하고자 합니다.
따라서 날짜 범위 파티셔닝은 데이터 접근 방식에 자연스럽게 적합했습니다.
이를 더 자세히 살펴보기 위해, 단순화된 audit_events 스키마를 상상해 보겠습니다:
CREATE TABLE audit_events (
id SERIAL NOT NULL PRIMARY KEY,
author_id INT NOT NULL,
details jsonb NOT NULL,
created_at timestamptz NOT NULL);
이제 UI의 일반적인 쿼리가 특정 날짜 범위(예: 단일 주)의 데이터를 표시한다고 가정해 보겠습니다:
SELECT *
FROM audit_events
WHERE created_at >= '2020-01-01 00:00:00'
AND created_at < '2020-01-08 00:00:00'
ORDER BY created_at DESC
LIMIT 100
테이블이 created_at 칼럼으로 파티셔닝되면 기본 테이블은 다음과 같습니다:
CREATE TABLE audit_events (
id SERIAL NOT NULL,
author_id INT NOT NULL,
details jsonb NOT NULL,
created_at timestamptz NOT NULL,
PRIMARY KEY (id, created_at))
PARTITION BY RANGE(created_at);
파티셔닝된 테이블의 기본 키는 기본 키 정의의 일부로 파티션 키를 포함해야 합니다.
그리고 테이블에 대한 파티션 목록은 다음과 같을 수 있습니다:
audit_events_202001 FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
audit_events_202002 FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
audit_events_202003 FOR VALUES FROM ('2020-03-01') TO ('2020-04-01')
각 파티션은 기본 audit_events 테이블과 동일한 구조를 가진 별도의 물리적 테이블이지만,
파티션 키가 지정된 범위에 속하는 행의 데이터만 포함합니다. 예를 들어,
audit_events_202001 파티션은 created_at 칼럼이 2020-01-01 이상이고
2020-02-01 미만인 행을 포함합니다.
이제 이전 예시 쿼리를 다시 살펴보면, 데이터베이스는 WHERE 절을 사용하여
모든 일치하는 행이 audit_events_202001 파티션에 있음을 인식할 수 있습니다.
모든 파티션의 전체 데이터를 검색하는 대신, 적절한 파티션에서 단일 월의 데이터만
검색할 수 있습니다. 대용량 테이블에서 이는 데이터베이스가 접근해야 하는 데이터 양을
크게 줄일 수 있습니다.
그러나 파티셔닝 키를 기반으로 필터링하지 않는 쿼리를 상상해 보겠습니다:
SELECT *
FROM audit_events
WHERE author_id = 123
ORDER BY created_at DESC
LIMIT 100
이 예시에서 데이터베이스는 일치하는 데이터가 어느 파티션에든 존재할 수 있으므로
검색에서 파티션을 제외할 수 없습니다. 따라서 각 파티션을 개별적으로 쿼리하고
행을 단일 결과 집합으로 집계해야 합니다. author_id는 인덱싱되어 있으므로
성능 영향은 허용 가능할 수 있지만, 더 복잡한 쿼리에서는 오버헤드가 상당할 수 있습니다.
파티셔닝은 데이터의 접근 패턴이 파티셔닝 전략을 지원하는 경우에만 활용해야 하며,
그렇지 않으면 성능이 저하됩니다.
시간 범위 파티셔닝 전략#
GitLab은 시간 범위 파티셔닝을 위한 두 가지 전략을 지원합니다:
-
일별 파티셔닝
-
월별 파티셔닝
시간 범위 파티셔닝 사용#
모델에서 시간 범위 파티셔닝을 사용하려면 PartitionedTable 모듈을 포함하고 파티션 설정을 구성합니다:
class WebHookLog < ApplicationRecord
include PartitionedTable
partitioned_by :created_at, strategy: :monthly, retain_for: 1.month
end
사용 가능한 전략#
일별 전략 (:daily)#
일별 전략은 하루에 하나의 파티션을 생성합니다:
partitioned_by :created_at, strategy: :daily, retain_for: 7.days
월별 전략 (:monthly)#
월별 전략은 한 달에 하나의 파티션을 생성합니다:
partitioned_by :created_at, strategy: :monthly, retain_for: 3.months, analyze_interval: 3.days
구성 옵션#
-
column: 파티셔닝할 칼럼 (필수, 타임스탬프 또는 날짜 칼럼이어야 함) -
strategy::daily또는:monthly중 하나 (필수) -
retain_for: 파티션 보존 기간 (선택 사항) -
analyze_interval: 새 파티션에서 ANALYZE를 실행하는 빈도 (선택 사항)
세분화된 파티셔닝이 필요한 대용량 테이블에는 :daily를, 일별 파티셔닝이 과도할 수 있는
중간 정도의 데이터 볼륨을 가진 테이블에는 :monthly를 선택합니다.
예시#
1단계: 파티셔닝된 복사본 생성 (릴리즈 N)#
첫 번째 단계는 원본 테이블의 파티셔닝된 복사본을 생성하는 마이그레이션을 추가하는 것입니다. 이 마이그레이션은 원본 테이블의 데이터를 기반으로 적절한 파티션을 생성하고, 원본 테이블에서 파티셔닝된 복사본으로 쓰기를 동기화하는 트리거를 설치합니다.
audit_events 테이블을 created_at 칼럼으로 파티셔닝하는 마이그레이션 예시는 다음과 같습니다:
class PartitionAuditEvents < Gitlab::Database::Migration[2.1]
include Gitlab::Database::PartitioningMigrationHelpers
def up
partition_table_by_date :audit_events, :created_at
end
def down
drop_partitioned_table_for :audit_events
end
end
다음으로, 임시 파티셔닝된 테이블을 config/initializers/postgres_partitioning.rb에 등록합니다.
이 등록을 통해 파티션 관리자가 트리거가 데이터를 동기화할 때 새 파티션을 생성할 수 있습니다. 예를 들어:
Gitlab::Database::Partitioning.register_tables(
[
{
limit_connection_names: %i[main],
table_name: 'audit_events_partitioned_table_name',
partitioned_column: :created_at, strategy: :monthly
}
]
)
이 예시에는 다음이 포함됩니다:
-
table_name: 임시 파티셔닝된 테이블의 이름 (예:audit_events_b8088ecbd2). -
partitioned_column: 파티셔닝에 사용되는 칼럼. -
strategy::daily또는:monthly중 하나.
데이터가 특정 기간 후에 삭제되어야 하더라도, 이 등록에 retain_for를 추가하지 마십시오.
백필(backfill) 중에 retain_for가 설정되면 파티션 관리자가 오래된 파티션을 분리할 수 있으며,
백필이 분리된 파티션에 데이터를 복사하려 할 때 실패할 수 있습니다.
테이블 교체(4단계)가 완료된 후에만 모델에 retain_for를 추가합니다.
테이블 교체(4단계)가 완료되면 이 등록을 제거할 수 있습니다.
마이그레이션이 실행된 후, 원본 테이블의 삽입, 업데이트 또는 삭제 작업은 새 테이블에도 복제됩니다. 업데이트와 삭제의 경우, 해당 행이 파티셔닝된 테이블에 존재하는 경우에만 작업이 적용됩니다.
2단계: 파티셔닝된 복사본 백필 (릴리즈 N)#
두 번째 단계는 원본 테이블에서 파티셔닝된 복사본으로 기존 데이터를 백필하는 백그라운드 job을 스케줄링하는 배포 후 마이그레이션을 추가하는 것입니다.
위의 예시를 계속 이어서, 마이그레이션은 다음과 같습니다:
class BackfillPartitionAuditEvents < Gitlab::Database::Migration[2.1]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
MIGRATION = 'BackfillPartitionedAuditEvents'
def up
enqueue_partitioning_data_migration :audit_events, MIGRATION
end
def down
cleanup_partitioning_data_migration :audit_events, MIGRATION
end
end
배치된 백그라운드 마이그레이션은 db/docs/batched_background_migrations/ 하위에서 추적되며
고유한 이름이 필요합니다. lib/gitlab/background_migration/에 BackfillPartitionedTable의
서브클래스를 생성하고 마이그레이션에서 참조합니다:
class BackfillPartitionedAuditEvents < BackfillPartitionedTable; end
이 단계는 내부적으로 BATCH_SIZE와 SUB_BATCH_SIZE를 각각 50,000과 2,500으로 설정하여
배치된 백그라운드 마이그레이션을 큐에 넣습니다.
자세한 내용은 배치된 백그라운드 마이그레이션 가이드를 참조하세요.
3단계: 백필 후 정리 (2단계 이후 필수 정지 다음 릴리즈)#
2단계에서 실행된 백그라운드 마이그레이션이 GitLab Self-Managed 인스턴스에서 성공적으로 완료될 수 있도록, 2단계와 3단계 사이에 필수 정지가 발생해야 합니다.
이 단계에서는 백그라운드 마이그레이션 후 정리를 수행하는 또 다른 배포 후 마이그레이션을 추가합니다. 여기에는 남아 있는 job을 강제로 실행하고, 삭제되거나 실패한 job으로 인해 누락될 수 있는 데이터를 복사하는 작업이 포함됩니다.
예시를 계속 이어서, 이 마이그레이션은 다음과 같습니다:
class CleanupPartitionedAuditEventsBackfill < Gitlab::Database::Migration[2.1]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
def up
finalize_backfilling_partitioned_table :audit_events
end
def down
# no op
end
end
이 마이그레이션이 완료된 후 원본 테이블과 파티셔닝된 테이블은 동일한 데이터를 포함해야 합니다. 원본 테이블에 설치된 트리거는 이후에도 데이터가 동기화된 상태를 유지하도록 보장합니다.
4단계: 파티셔닝된 테이블과 비파티셔닝된 테이블 교체 (릴리즈 N+1)#
이 단계는 비파티셔닝된 테이블을 파티셔닝된 복사본으로 교체합니다. 이 단계는 다른 모든 마이그레이션 단계가 성공적으로 완료된 후에만 사용해야 합니다.
이 방법의 일부 제한 사항은 교체 마이그레이션 이전이나 진행 중에 처리되어야 합니다:
-
보조 인덱스와 외래 키는 파티셔닝된 테이블에 자동으로 재생성되지 않습니다.
-
인덱스에 의존하는 일부 유형의 제약 조건(UNIQUE 및 EXCLUDE)은 기반이 되는 인덱스가 없으므로 파티셔닝된 테이블에 자동으로 재생성되지 않습니다.
-
원본 비파티셔닝된 테이블을 참조하는 외래 키는 파티셔닝된 테이블을 참조하도록 업데이트해야 합니다. 이는 PostgreSQL 11에서는 지원되지 않습니다.
-
원본 테이블을 참조하는 뷰는 파티셔닝된 테이블을 참조하도록 자동 업데이트되지 않습니다.
# frozen_string_literal: true
class SwapPartitionedAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::PartitioningMigrationHelpers
def up
replace_with_partitioned_table :audit_events
end
def down
rollback_replace_with_partitioned_table :audit_events
end
end
이 마이그레이션이 완료된 후:
-
파티셔닝된 테이블이 비파티셔닝된(원본) 테이블을 대체합니다.
-
이전에 생성된 동기화 트리거가 삭제됩니다.
이제 파티셔닝된 테이블이 애플리케이션에서 사용할 준비가 되었습니다.
(1단계에서 설명한 대로) config/initializers/postgres_partitioning.rb에 임시 파티셔닝된 테이블을
등록했다면, 교체 후에는 임시 테이블이 더 이상 존재하지 않으므로 해당 등록을 제거합니다.
특정 기간 동안만 데이터를 보존하려면 모델에 retain_for를 추가합니다:
class ProjectDailyStatistic < ApplicationRecord
include PartitionedTable
partitioned_by :date, strategy: :monthly, retain_for: 3.months
end
이렇게 하면 파티션 관리자가 오래된 파티션을 자동으로 삭제합니다.