날짜 범위 분할

설명

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가 인덱싱될 것이므로 성능 영향은 수용할 수 있을 것으로 생각되지만 더 복잡한 쿼리의 경우 오버헤드가 상당할 수 있습니다. 성능 저하를 방지하기 위해 데이터 액세스 패턴이 분할 전략을 지원하는 경우에만 분할을 활용해야 합니다.

예시

단계 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)를 포함하는 릴리스 후 적어도 한 번 발생해야 합니다. 이렇게 함으로써 Self-Managed형 설치에서 백그라운드 마이그레이션이 제대로 실행될 시간을 제공합니다. 이 단계에서는 백그라운드 마이그레이션 후 정리를 수행하는 배포 후 마이그레이션을 추가합니다. 이는 남아 있는 작업을 실행하고 누락된 데이터를 복사하는 것을 포함합니다. 이는 삭제된 또는 실패한 작업으로 인해 누락될 수 있는 데이터를 복사하는 것을 포함합니다.

caution
단계 2와 3 사이에 필수 중지가 발생하여 단계 2의 백그라운드 마이그레이션이 성공적으로 완료되도록해야 합니다.

다시 한번, 위 예시를 이어서, 이 마이그레이션은 다음과 같을 것입니다:

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
    # 무효
  end
end

이 마이그레이션이 완료된 후에 원본 테이블과 분할된 테이블은 동일한 데이터를 포함해야 합니다. 원본 테이블에 설치된 트리거는 앞으로 데이터가 동기화될 것을 보장합니다.

단계 4: 파티션된 테이블과 파티션되지 않은 테이블 교체 (릴리스 N+1)

이 단계에서는 파티션되지 않은 테이블을 분할된 사본으로 교체합니다. 이 단계는 다른 모든 마이그레이션 단계가 성공적으로 완료된 후에만 사용해야 합니다.

이 방법의 몇 가지 제한 사항은 다음과 같이 처리되어야 합니다:

  • 보조 인덱스 및 외래 키는 자동으로 분할된 테이블에 다시 생성되지 않습니다.
  • 일부 유형의 제약 조건 (고유 및 제외)은 해당하는 인덱스에 의존하는 경우 분할된 테이블에 자동으로 다시 생성되지 않습니다.
  • 원본, 분할되지 않은 테이블을 참조하는 외래 키는 분할된 테이블을 참조하도록 업데이트해야 합니다. 이는 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

이 마이그레이션이 완료되면:

  • 분할된 테이블이 분할되지 않은 (원본) 테이블을 대체합니다.
  • 이전에 생성된 동기화 트리거가 삭제됩니다.

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