날짜 범위 파티셔닝

설명

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);

주의: 파티셔닝된 테이블의 기본 키는 기본 키 정의의 일부로 파티션 키를 포함해야 합니다.

그리고 테이블에 대한 파티션 목록이 있을 수 있습니다:

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_202001created_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가 인덱싱되기 때문에 성능 영향은 상당히 허용 가능할 수 있지만, 더 복잡한 쿼리에서는 오버헤드가 상당할 수 있습니다.

파티셔닝은 데이터의 액세스 패턴이 파티셔닝 전략을 지원하는 경우에만 활용해야 하며, 그렇지 않으면 성능이 저하됩니다.

예제

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

이 작업이 실행된 후, 원본 테이블에서의 모든 삽입, 업데이트 또는 삭제는 새 테이블에도 복제됩니다. 업데이트 및 삭제의 경우, 해당 행이 파티션된 테이블에 존재하는 경우에만 효과가 있습니다.

2단계: 파티션된 복사본에 데이터 추가하기 (릴리스 N)

두 번째 단계는 원본 테이블에서 파티션된 복사본으로 기존 데이터를 추가하는 백그라운드 작업을 예약하는 배포 후 마이그레이션을 추가하는 것입니다.

위 예제를 계속 이어가면, 마이그레이션은 다음과 같이 보입니다:

class BackfillPartitionAuditEvents < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  def up
    enqueue_partitioning_data_migration :audit_events
  end

  def down
    cleanup_partitioning_data_migration :audit_events
  end
end

이 단계는 배치 백그라운드 마이그레이션을 예약합니다. 내부적으로 BATCH_SIZE와 SUB_BATCH_SIZE는 각각 50,0002,500으로 설정됩니다. 더 많은 세부정보는 배치 백그라운드 마이그레이션 가이드를 참조하세요.

3단계: 백필 후 정리하기 (릴리스 N+1)

이 단계는 단계(2)가 포함된 릴리스 이후 최소 한 번의 릴리스에서 발생해야 합니다. 이렇게 하면 백그라운드 마이그레이션이 자가 관리 설치에서 제대로 실행될 수 있는 시간 여유를 제공합니다. 이 단계에서는 백그라운드 마이그레이션 후 정리하는 또 다른 배포 후 마이그레이션을 추가합니다. 여기에는 남은 작업을 강제로 실행하고, 드롭되거나 실패한 작업으로 인해 놓쳤을 수 있는 데이터를 복사하는 작업이 포함됩니다.

경고:
단계 2와 3 사이에는 백그라운드 마이그레이션이 성공적으로 완료될 수 있도록 필요한 중지가 있어야 합니다.

다시 예제를 이어가면, 이 마이그레이션은 다음과 같을 것입니다:

class CleanupPartitionedAuditEventsBackfill < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main

  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

이 마이그레이션이 완료된 후:

  • 파티션된 테이블이 비파티션된(원본) 테이블을 대체합니다.
  • 이전에 생성된 동기화 트리거가 삭제됩니다.

이제 파티션된 테이블은 애플리케이션에서 사용할 준비가 되었습니다.