InfoGrab DocsInfoGrab Docs

Int range 파티셔닝

요약

Int range 파티셔닝은 정수 칼럼을 기반으로 대형 테이블을 더 작고 관리하기 쉬운 청크로 분할하는 기법입니다. 더 자세히 살펴보기 위해, 간소화된 merge_request_diff_files 스키마를 상상해 보겠습니다:

History

설명#

Int range 파티셔닝은 정수 칼럼을 기반으로 대형 테이블을 더 작고 관리하기 쉬운 청크로 분할하는 기법입니다. 각 파티션에는 특정 범위의 정수가 포함됩니다. 이 기법은 행 수가 많은 테이블에 특히 유용하며, 쿼리 성능을 크게 향상시키고 스토리지 요구사항을 줄이며 유지 관리 작업을 단순화할 수 있습니다. 이 유형의 파티셔닝이 잘 작동하려면 대부분의 쿼리가 특정 int 범위 내의 데이터에 접근해야 합니다.

더 자세히 살펴보기 위해, 간소화된 merge_request_diff_files 스키마를 상상해 보겠습니다:

CREATE TABLE merge_request_diff_files (
  merge_request_diff_id INT NOT NULL,
  relative_order INT NOT NULL,
  PRIMARY KEY (merge_request_diff_id, relative_order));

이제 UI에서의 일반적인 쿼리가 특정 int 범위 내의 데이터를 표시한다고 가정해 보겠습니다:

SELECT *
FROM merge_request_diff_files
WHERE merge_request_diff_id > 1 AND merge_request_diff_id < 10
LIMIT 100

테이블이 merge_request_diff_id 칼럼으로 파티셔닝된다면 기본 테이블은 다음과 같습니다:

CREATE TABLE merge_request_diff_files (
  merge_request_diff_id INT NOT NULL,
  relative_order INT NOT NULL,
  PRIMARY KEY (merge_request_diff_id, relative_order))
PARTITION BY RANGE(merge_request_diff_id);

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

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

merge_request_diff_files_1 FOR VALUES FROM (1) TO (20)
merge_request_diff_files_20 FOR VALUES FROM (20) TO (40)
merge_request_diff_files_40 FOR VALUES FROM (40) TO (60)

각 파티션은 기본 merge_request_diff_files 테이블과 동일한 구조를 가진 별도의 물리적 테이블이지만, 파티션 키가 지정된 범위에 속하는 행의 데이터만 포함합니다. 예를 들어, 파티션 merge_request_diff_files_1에는 merge_request_diff_id 칼럼이 1 이상이고 20 미만인 행이 포함됩니다.

이제 이전 예시 쿼리를 다시 살펴보면, 데이터베이스는 WHERE 조건을 사용하여 모든 일치하는 행이 merge_request_diff_files_1 파티션에 있음을 인식할 수 있습니다. 모든 파티션의 모든 데이터를 검색하는 대신에 말이죠. 대형 테이블에서 이는 데이터베이스가 접근해야 하는 데이터 양을 극적으로 줄일 수 있습니다.

새 파티셔닝된 테이블 생성#

새 테이블이므로 1개의 머지 리퀘스트(MR)로 수행할 수 있습니다.

ActiveRecord 모델 수정#

ActiveRecord 모델에서 아래 예시와 유사하게 partitioned_by 호출을 추가하세요. 이는 PartitionManager에게 파티션을 동적으로 생성하는 방법을 알려주는 지침을 제공하기 위해 필요합니다.

  PARTITION_SIZE = 2_000_000
  partitioned_by :namespace_id, strategy: :int_range, partition_size: PARTITION_SIZE

파티션 크기는 테이블의 예상 성장량을 기반으로 결정할 수 있습니다.

config/initializers/postgres_partitioning.rb 파일의 파티션 매니저에 모델을 등록하는 것을 잊지 마세요.

파티셔닝된 테이블의 시퀀스로 지원되지 않는 칼럼으로 테이블을 파티셔닝하는 경우, 칼럼의 sequence_name을 정의하세요. 이는 파티션 매니저가 새 파티션을 생성할 때 올바른 min_id를 가져오는 데 필요합니다.

partitioned_by :project_id, strategy: :int_range, partition_size: 2_000_000, sequence_name: 'projects_id_seq'

데이터베이스 마이그레이션 추가#

테이블을 생성하는 데이터베이스 마이그레이션에서, 특히 기존 테이블을 참조하는 외래 키로 테이블을 파티셔닝하는 경우 초기 파티션도 생성해야 할 수 있습니다.

이 예시에서는 namespaces 테이블을 참조합니다. 참조된 테이블에 맞게 수정해야 합니다. 다음 샘플 데이터베이스 마이그레이션에서 create_partitions 메서드의 구현을 참조하세요:

class AddKnowledgeGraphEnabledNamespaces < Gitlab::Database::Migration[2.3]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!
  milestone "fill_the_current_milestone"

  TABLE_NAME = :p_knowledge_graph_enabled_namespaces
  PARTITION_SIZE = 2_000_000
  MIN_ID = Namespace.connection
    .select_value("select min_value from pg_sequences where sequencename = 'namespaces_id_seq'") || 1

  def up
    with_lock_retries do
      create_table TABLE_NAME,
        options: 'PARTITION BY RANGE (namespace_id)',
        primary_key: [:id, :namespace_id], if_not_exists: true do |t|
        t.bigserial :id, null: false
        t.bigint :namespace_id, null: false
        t.timestamps_with_timezone null: false
        t.integer :state, null: false, default: 0, limit: 2, index: true
        t.index :namespace_id, unique: true
      end
    end

    create_partitions
  end

  def down
    drop_table TABLE_NAME
  end

  private

  def create_partitions
    max_id = Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
      Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed do
        define_batchable_model('namespaces', connection: connection).maximum(:id) || MIN_ID
      end
    end

    create_int_range_partitions(TABLE_NAME, PARTITION_SIZE, MIN_ID, max_id)
  end
end

테이블 시퀀스가 1부터 시작한다고 가정할 수 없다는 점에 주목할 가치가 있습니다. 이는 Cells에서 각 Cell이 데이터베이스 테이블에 대한 시퀀스 범위를 할당하기 때문입니다.

기존 테이블을 파티셔닝된 테이블로 변환#

1단계: 파티셔닝된 복사본 생성 (Release N)#

첫 번째 단계는 원본 테이블의 파티셔닝된 복사본을 생성하는 마이그레이션을 추가하는 것입니다. 이 마이그레이션은 원본 테이블의 데이터를 기반으로 적절한 파티션을 생성하고, 원본 테이블에서 파티셔닝된 복사본으로 쓰기를 동기화하는 트리거를 설치합니다.

merge_request_diff_commits 테이블을 merge_request_diff_id 칼럼으로 파티셔닝하는 마이그레이션 예시는 다음과 같습니다:

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

  disable_ddl_transaction!

  def up
    partition_table_by_int_range(
      'merge_request_diff_commits',
      'merge_request_diff_id',
      partition_size: 10_000_000,
      primary_key: %w[merge_request_diff_id relative_order]
    )
  end

  def down
    drop_partitioned_table_for('merge_request_diff_commits')
  end
end

이 마이그레이션이 실행된 후, 원본 테이블에서의 모든 삽입, 업데이트 또는 삭제 작업이 새 테이블에도 복제됩니다. 업데이트 및 삭제의 경우, 파티셔닝된 테이블에 해당 행이 존재하는 경우에만 작업이 적용됩니다.

2단계: 파티셔닝된 복사본 백필 (Release N)#

두 번째 단계는 원본 테이블에서 파티셔닝된 복사본으로 기존 데이터를 백필하는 백그라운드 job을 스케줄링하는 배포 후 마이그레이션을 추가하는 것입니다.

위 예시를 이어서, 마이그레이션은 다음과 같습니다:

class BackfillPartitionMergeRequestDiffCommits < Gitlab::Database::Migration[2.2]
  include Gitlab::Database::PartitioningMigrationHelpers

  milestone '16.10'

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    enqueue_partitioning_data_migration :merge_request_diff_commits
  end

  def down
    cleanup_partitioning_data_migration :merge_request_diff_commits
  end
end

이 단계는 내부적으로 BATCH_SIZE와 SUB_BATCH_SIZE를 각각 50,0002,500으로 하는 배치 백그라운드 마이그레이션을 큐에 추가합니다. 자세한 내용은 배치 백그라운드 마이그레이션 가이드를 참조하세요.

3단계: 백필 후 정리 (Release N+1)#

이 단계는 2단계를 포함하는 릴리즈 이후 최소 한 릴리즈 후에 수행되어야 합니다. 이는 백그라운드 마이그레이션이 GitLab Self-Managed 인스턴스에서 제대로 실행될 시간을 줍니다. 이 단계에서는 백그라운드 마이그레이션 후 정리하는 또 다른 배포 후 마이그레이션을 추가합니다. 여기에는 남은 job을 강제 실행하고, 삭제되거나 실패한 job으로 인해 누락된 데이터를 복사하는 것이 포함됩니다.

2단계와 3단계 사이에 백그라운드 마이그레이션이 성공적으로 완료될 수 있도록 반드시 필수 중단(required stop)이 있어야 합니다.

예시를 계속 이어서, 이 마이그레이션은 다음과 같습니다:

class CleanupPartitionMergeRequestDiffCommitsBackfill < 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 :merge_request_diff_commits
  end

  def down
    # no op
  end
end

이 마이그레이션이 완료된 후, 원본 테이블과 파티셔닝된 테이블에는 동일한 데이터가 포함되어야 합니다. 원본 테이블에 설치된 트리거는 이후에도 데이터가 동기화된 상태를 보장합니다.

4단계: 파티셔닝된 테이블과 비파티셔닝된 테이블 교체 (Release N+1)#

이 단계는 비파티셔닝된 테이블을 파티셔닝된 복사본으로 교체하며, 다른 모든 마이그레이션 단계가 성공적으로 완료된 후에만 사용해야 합니다.

이 방법에는 교체 마이그레이션 전에 또는 교체 마이그레이션 중에 반드시 처리해야 하는 몇 가지 제한 사항이 있습니다:

  • 보조 인덱스와 외래 키는 파티셔닝된 테이블에서 자동으로 재생성되지 않습니다.

  • 인덱스에 의존하는 일부 유형의 제약 조건(UNIQUE 및 EXCLUDE)은 파티셔닝된 테이블에서 자동으로 재생성되지 않습니다. 기본 인덱스가 존재하지 않기 때문입니다.

  • 원본 비파티셔닝된 테이블을 참조하는 외래 키는 파티셔닝된 테이블을 참조하도록 업데이트해야 합니다. 이는 PostgreSQL 11에서 지원되지 않습니다.

  • 원본 테이블을 참조하는 뷰는 파티셔닝된 테이블을 참조하도록 자동으로 업데이트되지 않습니다.

# frozen_string_literal: true

class SwapPartitionMergeRequestDiffCommits < 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

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

  • 파티셔닝된 테이블이 비파티셔닝된(원본) 테이블을 대체합니다.

  • 이전에 생성된 동기화 트리거가 삭제됩니다.

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

Int range 파티셔닝

GitLab v19.1
원문 보기
요약

Int range 파티셔닝은 정수 칼럼을 기반으로 대형 테이블을 더 작고 관리하기 쉬운 청크로 분할하는 기법입니다. 더 자세히 살펴보기 위해, 간소화된 merge_request_diff_files 스키마를 상상해 보겠습니다:

History

설명#

Int range 파티셔닝은 정수 칼럼을 기반으로 대형 테이블을 더 작고 관리하기 쉬운 청크로 분할하는 기법입니다. 각 파티션에는 특정 범위의 정수가 포함됩니다. 이 기법은 행 수가 많은 테이블에 특히 유용하며, 쿼리 성능을 크게 향상시키고 스토리지 요구사항을 줄이며 유지 관리 작업을 단순화할 수 있습니다. 이 유형의 파티셔닝이 잘 작동하려면 대부분의 쿼리가 특정 int 범위 내의 데이터에 접근해야 합니다.

더 자세히 살펴보기 위해, 간소화된 merge_request_diff_files 스키마를 상상해 보겠습니다:

CREATE TABLE merge_request_diff_files (
  merge_request_diff_id INT NOT NULL,
  relative_order INT NOT NULL,
  PRIMARY KEY (merge_request_diff_id, relative_order));

이제 UI에서의 일반적인 쿼리가 특정 int 범위 내의 데이터를 표시한다고 가정해 보겠습니다:

SELECT *
FROM merge_request_diff_files
WHERE merge_request_diff_id > 1 AND merge_request_diff_id < 10
LIMIT 100

테이블이 merge_request_diff_id 칼럼으로 파티셔닝된다면 기본 테이블은 다음과 같습니다:

CREATE TABLE merge_request_diff_files (
  merge_request_diff_id INT NOT NULL,
  relative_order INT NOT NULL,
  PRIMARY KEY (merge_request_diff_id, relative_order))
PARTITION BY RANGE(merge_request_diff_id);

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

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

merge_request_diff_files_1 FOR VALUES FROM (1) TO (20)
merge_request_diff_files_20 FOR VALUES FROM (20) TO (40)
merge_request_diff_files_40 FOR VALUES FROM (40) TO (60)

각 파티션은 기본 merge_request_diff_files 테이블과 동일한 구조를 가진 별도의 물리적 테이블이지만, 파티션 키가 지정된 범위에 속하는 행의 데이터만 포함합니다. 예를 들어, 파티션 merge_request_diff_files_1에는 merge_request_diff_id 칼럼이 1 이상이고 20 미만인 행이 포함됩니다.

이제 이전 예시 쿼리를 다시 살펴보면, 데이터베이스는 WHERE 조건을 사용하여 모든 일치하는 행이 merge_request_diff_files_1 파티션에 있음을 인식할 수 있습니다. 모든 파티션의 모든 데이터를 검색하는 대신에 말이죠. 대형 테이블에서 이는 데이터베이스가 접근해야 하는 데이터 양을 극적으로 줄일 수 있습니다.

새 파티셔닝된 테이블 생성#

새 테이블이므로 1개의 머지 리퀘스트(MR)로 수행할 수 있습니다.

ActiveRecord 모델 수정#

ActiveRecord 모델에서 아래 예시와 유사하게 partitioned_by 호출을 추가하세요. 이는 PartitionManager에게 파티션을 동적으로 생성하는 방법을 알려주는 지침을 제공하기 위해 필요합니다.

  PARTITION_SIZE = 2_000_000
  partitioned_by :namespace_id, strategy: :int_range, partition_size: PARTITION_SIZE

파티션 크기는 테이블의 예상 성장량을 기반으로 결정할 수 있습니다.

config/initializers/postgres_partitioning.rb 파일의 파티션 매니저에 모델을 등록하는 것을 잊지 마세요.

파티셔닝된 테이블의 시퀀스로 지원되지 않는 칼럼으로 테이블을 파티셔닝하는 경우, 칼럼의 sequence_name을 정의하세요. 이는 파티션 매니저가 새 파티션을 생성할 때 올바른 min_id를 가져오는 데 필요합니다.

partitioned_by :project_id, strategy: :int_range, partition_size: 2_000_000, sequence_name: 'projects_id_seq'

데이터베이스 마이그레이션 추가#

테이블을 생성하는 데이터베이스 마이그레이션에서, 특히 기존 테이블을 참조하는 외래 키로 테이블을 파티셔닝하는 경우 초기 파티션도 생성해야 할 수 있습니다.

이 예시에서는 namespaces 테이블을 참조합니다. 참조된 테이블에 맞게 수정해야 합니다. 다음 샘플 데이터베이스 마이그레이션에서 create_partitions 메서드의 구현을 참조하세요:

class AddKnowledgeGraphEnabledNamespaces < Gitlab::Database::Migration[2.3]
  include Gitlab::Database::PartitioningMigrationHelpers

  disable_ddl_transaction!
  milestone "fill_the_current_milestone"

  TABLE_NAME = :p_knowledge_graph_enabled_namespaces
  PARTITION_SIZE = 2_000_000
  MIN_ID = Namespace.connection
    .select_value("select min_value from pg_sequences where sequencename = 'namespaces_id_seq'") || 1

  def up
    with_lock_retries do
      create_table TABLE_NAME,
        options: 'PARTITION BY RANGE (namespace_id)',
        primary_key: [:id, :namespace_id], if_not_exists: true do |t|
        t.bigserial :id, null: false
        t.bigint :namespace_id, null: false
        t.timestamps_with_timezone null: false
        t.integer :state, null: false, default: 0, limit: 2, index: true
        t.index :namespace_id, unique: true
      end
    end

    create_partitions
  end

  def down
    drop_table TABLE_NAME
  end

  private

  def create_partitions
    max_id = Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
      Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed do
        define_batchable_model('namespaces', connection: connection).maximum(:id) || MIN_ID
      end
    end

    create_int_range_partitions(TABLE_NAME, PARTITION_SIZE, MIN_ID, max_id)
  end
end

테이블 시퀀스가 1부터 시작한다고 가정할 수 없다는 점에 주목할 가치가 있습니다. 이는 Cells에서 각 Cell이 데이터베이스 테이블에 대한 시퀀스 범위를 할당하기 때문입니다.

기존 테이블을 파티셔닝된 테이블로 변환#

1단계: 파티셔닝된 복사본 생성 (Release N)#

첫 번째 단계는 원본 테이블의 파티셔닝된 복사본을 생성하는 마이그레이션을 추가하는 것입니다. 이 마이그레이션은 원본 테이블의 데이터를 기반으로 적절한 파티션을 생성하고, 원본 테이블에서 파티셔닝된 복사본으로 쓰기를 동기화하는 트리거를 설치합니다.

merge_request_diff_commits 테이블을 merge_request_diff_id 칼럼으로 파티셔닝하는 마이그레이션 예시는 다음과 같습니다:

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

  disable_ddl_transaction!

  def up
    partition_table_by_int_range(
      'merge_request_diff_commits',
      'merge_request_diff_id',
      partition_size: 10_000_000,
      primary_key: %w[merge_request_diff_id relative_order]
    )
  end

  def down
    drop_partitioned_table_for('merge_request_diff_commits')
  end
end

이 마이그레이션이 실행된 후, 원본 테이블에서의 모든 삽입, 업데이트 또는 삭제 작업이 새 테이블에도 복제됩니다. 업데이트 및 삭제의 경우, 파티셔닝된 테이블에 해당 행이 존재하는 경우에만 작업이 적용됩니다.

2단계: 파티셔닝된 복사본 백필 (Release N)#

두 번째 단계는 원본 테이블에서 파티셔닝된 복사본으로 기존 데이터를 백필하는 백그라운드 job을 스케줄링하는 배포 후 마이그레이션을 추가하는 것입니다.

위 예시를 이어서, 마이그레이션은 다음과 같습니다:

class BackfillPartitionMergeRequestDiffCommits < Gitlab::Database::Migration[2.2]
  include Gitlab::Database::PartitioningMigrationHelpers

  milestone '16.10'

  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    enqueue_partitioning_data_migration :merge_request_diff_commits
  end

  def down
    cleanup_partitioning_data_migration :merge_request_diff_commits
  end
end

이 단계는 내부적으로 BATCH_SIZE와 SUB_BATCH_SIZE를 각각 50,0002,500으로 하는 배치 백그라운드 마이그레이션을 큐에 추가합니다. 자세한 내용은 배치 백그라운드 마이그레이션 가이드를 참조하세요.

3단계: 백필 후 정리 (Release N+1)#

이 단계는 2단계를 포함하는 릴리즈 이후 최소 한 릴리즈 후에 수행되어야 합니다. 이는 백그라운드 마이그레이션이 GitLab Self-Managed 인스턴스에서 제대로 실행될 시간을 줍니다. 이 단계에서는 백그라운드 마이그레이션 후 정리하는 또 다른 배포 후 마이그레이션을 추가합니다. 여기에는 남은 job을 강제 실행하고, 삭제되거나 실패한 job으로 인해 누락된 데이터를 복사하는 것이 포함됩니다.

2단계와 3단계 사이에 백그라운드 마이그레이션이 성공적으로 완료될 수 있도록 반드시 필수 중단(required stop)이 있어야 합니다.

예시를 계속 이어서, 이 마이그레이션은 다음과 같습니다:

class CleanupPartitionMergeRequestDiffCommitsBackfill < 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 :merge_request_diff_commits
  end

  def down
    # no op
  end
end

이 마이그레이션이 완료된 후, 원본 테이블과 파티셔닝된 테이블에는 동일한 데이터가 포함되어야 합니다. 원본 테이블에 설치된 트리거는 이후에도 데이터가 동기화된 상태를 보장합니다.

4단계: 파티셔닝된 테이블과 비파티셔닝된 테이블 교체 (Release N+1)#

이 단계는 비파티셔닝된 테이블을 파티셔닝된 복사본으로 교체하며, 다른 모든 마이그레이션 단계가 성공적으로 완료된 후에만 사용해야 합니다.

이 방법에는 교체 마이그레이션 전에 또는 교체 마이그레이션 중에 반드시 처리해야 하는 몇 가지 제한 사항이 있습니다:

  • 보조 인덱스와 외래 키는 파티셔닝된 테이블에서 자동으로 재생성되지 않습니다.

  • 인덱스에 의존하는 일부 유형의 제약 조건(UNIQUE 및 EXCLUDE)은 파티셔닝된 테이블에서 자동으로 재생성되지 않습니다. 기본 인덱스가 존재하지 않기 때문입니다.

  • 원본 비파티셔닝된 테이블을 참조하는 외래 키는 파티셔닝된 테이블을 참조하도록 업데이트해야 합니다. 이는 PostgreSQL 11에서 지원되지 않습니다.

  • 원본 테이블을 참조하는 뷰는 파티셔닝된 테이블을 참조하도록 자동으로 업데이트되지 않습니다.

# frozen_string_literal: true

class SwapPartitionMergeRequestDiffCommits < 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

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

  • 파티셔닝된 테이블이 비파티셔닝된(원본) 테이블을 대체합니다.

  • 이전에 생성된 동기화 트리거가 삭제됩니다.

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