InfoGrab DocsInfoGrab Docs

날짜 범위 파티셔닝

요약

GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝이며, 테이블의 각 파티션이 한 달치 데이터를 담습니다. 더 구체적인 예로 audit_events 테이블을 살펴봅니다. 더 자세히 보기 위해 단순화한 audit_events 스키마를 가정합니다.

설명#

GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝이며, 테이블의 각 파티션이 한 달치 데이터를 담습니다. 이 경우 파티셔닝 키는 타임스탬프나 날짜 칼럼이어야 합니다. 이 방식의 파티셔닝이 제대로 동작하려면 대부분의 쿼리가 특정 날짜 범위의 데이터에 접근해야 합니다.

더 구체적인 예로 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);
Note

파티셔닝된 테이블의 기본 키 정의에는 파티션 키가 포함되어야 합니다.

그리고 이 테이블의 파티션 목록은 다음과 같을 수 있습니다.

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

주별 전략 (:weekly)#

주별 전략은 한 주에 파티션 하나를 만듭니다. 주는 월요일에 시작합니다.

partitioned_by :created_at, strategy: :weekly, retain_for: 4.weeks

월별 전략 (:monthly)#

월별 전략은 한 달에 파티션 하나를 만듭니다.

partitioned_by :created_at, strategy: :monthly, retain_for: 3.months, analyze_interval: 3.days

구성 옵션#

  • column: 파티셔닝에 사용할 칼럼입니다(필수이며, 타임스탬프나 날짜 칼럼이어야 합니다)
  • strategy: :daily, :weekly, :monthly 중 하나입니다(필수)
  • retain_for: 파티션 보존 기간으로, 기간 값 또는 :ever 입니다(필수, 보존 참고)
  • analyze_interval: 새 파티션에 ANALYZE를 실행하는 주기입니다(선택 사항)
  • retain_detached_partitions_for: 분리된 파티션을 삭제하기 전까지 유지하는 기간으로, 기간 값입니다(선택 사항, 분리 후 보존 참고)

세분화된 파티셔닝이 필요한 대용량 테이블에는 :daily를, 중간 수준의 세분화에는 :weekly를, 일별 파티셔닝이 과한 중간 규모 데이터 테이블에는 :monthly를 선택합니다.

보존#

시간 기반 전략은 테이블마다 보존 정책을 의식적으로 결정하도록 retain_for를 요구합니다. 보존 설정이 없으면 테이블은 모든 파티션을 영구히 유지하므로 스토리지 비용이 들고 파티션 키를 제한하지 않는 쿼리가 느려집니다.

오래된 파티션을 자동으로 정리하려면 기간 값을 전달합니다.

partitioned_by :created_at, strategy: :monthly, retain_for: 6.months

모든 파티션을 유지하려면 retain_for: :ever와 그 이유를 설명하는 주석으로 명시적으로 제외합니다.

# Audit events are retained indefinitely for compliance.
partitioned_by :created_at, strategy: :monthly, retain_for: :ever

retain_for는 기간 값이나 :ever 여야 합니다. 이를 생략하거나 nil 같은 다른 값을 전달하면 ArgumentError가 발생합니다.

분리 후 보존#

파티션 매니저가 파티션을 분리한 뒤에는 데이터가 아직 필요할 수 있으므로, 삭제하기 전까지 유예 기간 동안 분리된 테이블을 남겨 둡니다. PartitionManager는 모든 파티셔닝된 테이블에 대해 이 유예 기간의 기본값을 RETAIN_DETACHED_PARTITIONS_FOR(1주)로 둡니다.

특정 테이블에 더 짧거나 긴 유예 기간을 사용하려면 retain_detached_partitions_for를 전달합니다.

partitioned_by :created_at, strategy: :daily, retain_for: 14.days, retain_detached_partitions_for: 2.days

retain_detached_partitions_for는 기간 값이어야 합니다. 설정하지 않으면 테이블은 기본 유예 기간을 사용합니다.

예시#

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, retain_for: :ever
    }
  ]
)

이 예시에는 다음이 포함됩니다.

  • table_name: 임시 파티셔닝 테이블의 이름입니다 (예: audit_events_b8088ecbd2).
  • partitioned_column: 파티셔닝에 사용하는 칼럼입니다.
  • strategy: :daily, :weekly, :monthly 중 하나입니다.
  • retain_for: 이 등록에서는 :ever로 설정합니다(다음 경고 참고).
Warning

이 등록에는 retain_for: :ever를 설정합니다. 시간 기반 전략은 retain_for를 요구하지만, 데이터를 일정 기간 뒤에 삭제해야 하더라도 여기에 실제 기간 값을 설정해서는 안 됩니다. 백필 중에 retain_for가 기간 값으로 설정되어 있으면 파티션 매니저가 오래된 파티션을 분리할 수 있고, 그러면 분리된 파티션으로 데이터를 복사하려다 백필이 실패합니다. 실제 retain_for는 테이블 교체가 끝난 뒤(4단계)에만 모델에 설정합니다.

테이블 교체가 끝나면(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를 50,000, SUB_BATCH_SIZE를 2,500으로 하여 배치 백그라운드 마이그레이션을 큐에 넣습니다. 자세한 내용은 배치 백그라운드 마이그레이션 가이드를 참고합니다.

3단계: 백필 후 정리 (2단계 이후 필수 정지 다음 릴리스)#

Warning

GitLab Self-Managed 인스턴스에서 2단계의 백그라운드 마이그레이션이 성공적으로 완료되도록 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

이렇게 하면 파티션 매니저가 오래된 파티션을 자동으로 삭제합니다.

날짜 범위 파티셔닝

GitLab v19.4
원문 보기

요약

GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝이며, 테이블의 각 파티션이 한 달치 데이터를 담습니다. 더 구체적인 예로 audit_events 테이블을 살펴봅니다. 더 자세히 보기 위해 단순화한 audit_events 스키마를 가정합니다.

설명#

GitLab 마이그레이션 헬퍼가 가장 잘 지원하는 방식은 날짜 범위 파티셔닝이며, 테이블의 각 파티션이 한 달치 데이터를 담습니다. 이 경우 파티셔닝 키는 타임스탬프나 날짜 칼럼이어야 합니다. 이 방식의 파티셔닝이 제대로 동작하려면 대부분의 쿼리가 특정 날짜 범위의 데이터에 접근해야 합니다.

더 구체적인 예로 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);
Note

파티셔닝된 테이블의 기본 키 정의에는 파티션 키가 포함되어야 합니다.

그리고 이 테이블의 파티션 목록은 다음과 같을 수 있습니다.

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

주별 전략 (:weekly)#

주별 전략은 한 주에 파티션 하나를 만듭니다. 주는 월요일에 시작합니다.

partitioned_by :created_at, strategy: :weekly, retain_for: 4.weeks

월별 전략 (:monthly)#

월별 전략은 한 달에 파티션 하나를 만듭니다.

partitioned_by :created_at, strategy: :monthly, retain_for: 3.months, analyze_interval: 3.days

구성 옵션#

  • column: 파티셔닝에 사용할 칼럼입니다(필수이며, 타임스탬프나 날짜 칼럼이어야 합니다)
  • strategy: :daily, :weekly, :monthly 중 하나입니다(필수)
  • retain_for: 파티션 보존 기간으로, 기간 값 또는 :ever 입니다(필수, 보존 참고)
  • analyze_interval: 새 파티션에 ANALYZE를 실행하는 주기입니다(선택 사항)
  • retain_detached_partitions_for: 분리된 파티션을 삭제하기 전까지 유지하는 기간으로, 기간 값입니다(선택 사항, 분리 후 보존 참고)

세분화된 파티셔닝이 필요한 대용량 테이블에는 :daily를, 중간 수준의 세분화에는 :weekly를, 일별 파티셔닝이 과한 중간 규모 데이터 테이블에는 :monthly를 선택합니다.

보존#

시간 기반 전략은 테이블마다 보존 정책을 의식적으로 결정하도록 retain_for를 요구합니다. 보존 설정이 없으면 테이블은 모든 파티션을 영구히 유지하므로 스토리지 비용이 들고 파티션 키를 제한하지 않는 쿼리가 느려집니다.

오래된 파티션을 자동으로 정리하려면 기간 값을 전달합니다.

partitioned_by :created_at, strategy: :monthly, retain_for: 6.months

모든 파티션을 유지하려면 retain_for: :ever와 그 이유를 설명하는 주석으로 명시적으로 제외합니다.

# Audit events are retained indefinitely for compliance.
partitioned_by :created_at, strategy: :monthly, retain_for: :ever

retain_for는 기간 값이나 :ever 여야 합니다. 이를 생략하거나 nil 같은 다른 값을 전달하면 ArgumentError가 발생합니다.

분리 후 보존#

파티션 매니저가 파티션을 분리한 뒤에는 데이터가 아직 필요할 수 있으므로, 삭제하기 전까지 유예 기간 동안 분리된 테이블을 남겨 둡니다. PartitionManager는 모든 파티셔닝된 테이블에 대해 이 유예 기간의 기본값을 RETAIN_DETACHED_PARTITIONS_FOR(1주)로 둡니다.

특정 테이블에 더 짧거나 긴 유예 기간을 사용하려면 retain_detached_partitions_for를 전달합니다.

partitioned_by :created_at, strategy: :daily, retain_for: 14.days, retain_detached_partitions_for: 2.days

retain_detached_partitions_for는 기간 값이어야 합니다. 설정하지 않으면 테이블은 기본 유예 기간을 사용합니다.

예시#

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, retain_for: :ever
    }
  ]
)

이 예시에는 다음이 포함됩니다.

  • table_name: 임시 파티셔닝 테이블의 이름입니다 (예: audit_events_b8088ecbd2).
  • partitioned_column: 파티셔닝에 사용하는 칼럼입니다.
  • strategy: :daily, :weekly, :monthly 중 하나입니다.
  • retain_for: 이 등록에서는 :ever로 설정합니다(다음 경고 참고).
Warning

이 등록에는 retain_for: :ever를 설정합니다. 시간 기반 전략은 retain_for를 요구하지만, 데이터를 일정 기간 뒤에 삭제해야 하더라도 여기에 실제 기간 값을 설정해서는 안 됩니다. 백필 중에 retain_for가 기간 값으로 설정되어 있으면 파티션 매니저가 오래된 파티션을 분리할 수 있고, 그러면 분리된 파티션으로 데이터를 복사하려다 백필이 실패합니다. 실제 retain_for는 테이블 교체가 끝난 뒤(4단계)에만 모델에 설정합니다.

테이블 교체가 끝나면(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를 50,000, SUB_BATCH_SIZE를 2,500으로 하여 배치 백그라운드 마이그레이션을 큐에 넣습니다. 자세한 내용은 배치 백그라운드 마이그레이션 가이드를 참고합니다.

3단계: 백필 후 정리 (2단계 이후 필수 정지 다음 릴리스)#

Warning

GitLab Self-Managed 인스턴스에서 2단계의 백그라운드 마이그레이션이 성공적으로 완료되도록 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

이렇게 하면 파티션 매니저가 오래된 파티션을 자동으로 삭제합니다.