데이터베이스 인덱스 추가

인덱스는 데이터베이스 쿼리의 실행 속도를 높일 수 있지만, 언제 새 인덱스를 추가해야 할까요? 이 질문에 대한 전통적인 답변은 데이터 필터링이나 조인에 사용되는 매 열(Column)에 대해 인덱스를 추가하는 것입니다. 예를 들어, 다음 쿼리를 고려해보십시오.

SELECT *
FROM projects
WHERE user_id = 2;

여기서 우리는 user_id 열을 기준으로 필터링하고 있으므로, 개발자는 이 열에 대해 인덱싱할 수 있다고 판단할 수 있습니다.

일부 경우에는 위의 방법을 사용하여 열에 대해 인덱싱하는 것이 합리적일 수 있지만, 실제로는 부정적인 영향을 끼칠 수도 있습니다. 테이블에 데이터를 기록할 때 기존 인덱스도 업데이트되어야 합니다. 인덱스가 많을수록 이 작업이 느려질 수 있습니다. 또한, 데이터양과 인덱스 유형에 따라 디스크 공간을 상당히 차지할 수도 있습니다. 예를 들어, PostgreSQL은 표준 B-tree 인덱스로 색인화할 수 없는 특정 데이터 유형을 색인화할 수 있는 GIN 인덱스를 제공합니다. 그러나 일반적으로 이러한 인덱스는 더 많은 데이터를 차지하고 B-tree 인덱스에 비해 업데이트 속도가 느립니다.

이 모든 이유로 새로운 인덱스를 추가할 때 다음 사항을 고려하는 것이 중요합니다. 1. 새로운 쿼리가 가능한 한 많은 기존 인덱스를 재사용하는가? 2. 쿼리 실행 속도가 테이블의 행을 반복하는 것보다 인덱스를 사용하는 것이 빠른 데이터가 충분한가? 3. 인덱스를 유지하는 과정에서 발생하는 부하가 쿼리 실행 시간 감소에 상응하는가?

일부 상황에서는 인덱스가 필요하지 않을 수도 있습니다.

  • 테이블이 작고(1,000개 이하의 레코드) 규모가 급격히 증가하지 않을 것으로 예상될 때
  • 기존 인덱스가 충분한 행을 필터링할 때
  • 새로운 인덱스를 추가한 후의 쿼리 실행 시간 감소가 미미할 때

게다가, 큰 인덱스가 쿼리의 모든 필터 조건과 일치할 필요는 없습니다. 인덱스 조회의 선택도를 충분히 줄일 수 있도록 충분한 열을 커버하기만 하면 됩니다.

쿼리 재사용

첫 번째 단계는 쿼리가 가능한 한 많은 기존 인덱스를 재사용하는지 확인하는 것입니다. 예를 들어, 다음 쿼리를 고려해보십시오.

SELECT *
FROM todos
WHERE user_id = 123
AND state = 'open';

이제 우리는 user_id 열에 이미 인덱스가 있지만 state 열에는 없다고 가정해봅니다. “state”가 인덱싱되지 않아서 이 쿼리가 성능이 나쁠 것이라고 생각할 수도 있지만, 실제로는 user_id에 대한 인덱스가 충분한 행을 걸러낼 수 있으므로 쿼리가 잘 수행될 수 있습니다.

인덱스를 재사용하는지 여부를 확인하는 가장 좋은 방법은 쿼리를 EXPLAIN ANALYZE를 사용하여 실행하는 것입니다. 조인된 테이블과 필터링에 사용되는 열에 따라 추가된 인덱스가 미미하거나 아예 영향을 끼치지 않을 수 있습니다.

간단히 말해서, - 가능한 한 많은 기존 인덱스를 재사용하는 쿼리를 작성해보십시오. - 쿼리를 EXPLAIN ANALYZE를 사용하여 실행한 후 출력 결과를 연구하여 최적의 쿼리를 찾아보십시오.

데이터 크기

특히 작은 테이블의 경우 보통 정렬된 일련의 스캔이 빠를 수 있으므로 데이터베이스는 레코드를 필터링할 때에도 인덱스를 사용하지 않을 수 있습니다.

테이블의 규모가 크게 증가할 것으로 예상되고 쿼리가 많은 행을 필터링해야 할 때 인덱스를 추가하는 것을 고려해볼 수 있습니다. 그러나 테이블의 크기가 작고(<1,000개의 레코드) 기존 인덱스가 이미 충분한 행을 필터링해내는 경우, 인덱스를 추가할 필요가 없을 수도 있습니다.

유지보수 부하

인덱스는 각각의 테이블 쓰기 작업을 업데이트해야 합니다. PostgreSQL의 경우, 테이블에 데이터를 작성할 때마다 모든 기존 인덱스가 업데이트됩니다. 그 결과, 동일한 테이블에 많은 인덱스가 있다면 쓰기 작업이 느려질 수 있습니다. 따라서 추가적인 인덱스를 유지하는 부하와 쿼리 성능을 균형 있게 유지하는 것이 중요합니다.

예를 들어, 인덱스를 추가하면 SELECT 시간이 5밀리초 감소하고 INSERT/UPDATE/DELETE 시간이 10밀리초 증가한다고 가정해봅시다. 이 경우 새로운 인덱스가 가치가 없을 수 있습니다. SELECT 시간이 줄어들고 INSERT/UPDATE/DELETE 시간이 영향을 받지 않을 때에 새로운 인덱스가 더 가치 있을 것입니다.

인덱스 추가 및 응용 프로그램 코드 동시 변경

불필요한 인덱스를 생성하는 위험을 최소화하기 위해 가능한 경우 동일한 Merge Request에서 다음 작업을 수행하십시오.

  • 응용 프로그램 코드 변경 사항
  • 인덱스 추가 또는 제거

인덱스를 생성하는 마이그레이션은 보통 짧은 시간이 소요되며 Merge Request의 크기를 크게 늘리지 않습니다. 이를 통해 백엔드 및 데이터베이스 리뷰어가 Merge Request이나 커밋 간의 컨텍스트를 전환하지 않고도 효율적으로 검토할 수 있습니다.

사용할 마이그레이션 타입

궁금할 때는 마이그레이션 스타일 가이드를 참고하십시오.

다음은 일반적인 시나리오와 간단한 참고 사항 디렉터리입니다.

기존 쿼리의 성능을 향상시키기 위해 인덱스 추가

테스트 성공 후 배포 마이그레이션을 사용하십시오. 기존 쿼리가 새로 추가된 인덱스 없이 이미 작동하고 있으며, 응용 프로그램 운영에 중요하지 않을 때 사용합니다.

인덱싱하는 데 많은 시간이 소요된다면 (배포 마이그레이션은 10분 이내에 완료되어야 합니다) 비동기적으로 인덱싱을 고려하십시오.

새로운 또는 업데이트된 쿼리를 지원하기 위해 인덱스 추가

새로 추가하거나 업데이트된 쿼리의 쿼리 계획을 항상 확인하십시오. 먼저 타임아웃되지 않거나 권장된 쿼리 실행 시간을 상당히 초과하지 않음을 확인하십시오.

쿼리가 타임아웃되지 않고 쿼리 실행 시간을 크게 초과하지 않는 경우:

  • 새로 추가된 쿼리의 성능을 향상시키기 위해 추가된 인덱스를 사용할 필요가 응용 프로그램 운영에 중요하지 않은 경우 배포 마이그레이션을 사용하여 인덱스를 생성하십시오.
  • 동일한 Merge Request에서 새로 추가된 쿼리를 생성하고 사용하는 응용 프로그램 코드 변경 사항을 제공하십시오.

쿼리가 타임아웃되거나 쿼리 실행 시간을 초과하는 경우, 규모가 가장 큰 GitLab 설치 중 어느 곳에서 그렇든 별도의 인덱스가 필요합니다.

GitLab.com에서 새로운 또는 업데이트된 쿼리의 성능이 느린 경우

Merge Request을 두 번 사용하여 배포 마이그레이션에서 인덱스를 생성하고 응용 프로그램 코드 변경을 수행하십시오:

  • 첫 번째 MR은 배포 마이그레이션을 사용하여 인덱스를 생성합니다.
  • 두 번째 MR은 응용 프로그램 코드 변경을 수행합니다. 이 MR은 첫 번째 MR에서의 배포 마이그레이션 실행 후에만 Merge되어야 합니다.
note
피처 플래그를 사용할 수 있다면, 피처 플래그 뒤에 있는 코드 변경 사항을 쉽게 만드는 단일 MR을 사용할 수 있을 것입니다. 동시에 배포 마이그레이션이 포함됩니다. 배포 마이그레이션 실행 후에 피처 플래그를 활성화할 수 있습니다.

GitLab.com에서는 연속 통합을 통해 발매 중 배포 방식으로 배포 마이그레이션을 실행합니다:

  • t에서, 통합되고 배포할 준비가 된 MR 그룹이 있습니다.
  • t+1에서, 그룹의 일반적인 마이그레이션은 GitLab.com의 스테이징 및 프로덕션 데이터베이스에서 실행됩니다.
  • t+2에서, 그룹의 응용 프로그램 코드 변경이 롤링 방식으로 배포를 시작합니다.

응용 프로그램 코드 변경이 완전히 배포된 후, 발매 관리자는 GitLab.com 가용성 대기 시간에 따라 배포 마이그레이션을 자유롭게 실행할 수 있습니다. 발매 관리자는 두 번째 MR을 Merge하기 전에 첫 번째 MR에서의 배포 마이그레이션 실행 여부를 확인해야 합니다.

대규모 GitLab 인스턴스에서 새로운 또는 업데이트된 쿼리의 성능이 느린 경우

Self-Managed형 인스턴스에서 쿼리 성능을 직접 확인하는 것이 불가능합니다. PostgreSQL은 데이터 분포에 기반하여 실행 계획을 생성하므로 쿼리 성능을 추측하는 것은 어려운 과제입니다.

Self-Managed형 인스턴스에서 쿼리 성능에 대한 우려가 있다면 이 인덱스가 필요하다고 판단된 경우, 다음 권장사항을 따르십시오.

  • 다운타임 없는 업그레이드를 따르는 Self-Managed형 인스턴스에서는, 응용 프로그램 코드 배포 후 업그레이드를 수행할 때 배포 마이그레이션이 실행됩니다.
  • 제로 다운타임 업그레이드를 따르지 않는 Self-Managed형 인스턴스의 경우, 관리자는 정규 마이그레이션 직후에 원하는 시점에 첫 번째 MR에 포함된 배포 마이그레이션을 실행하기로 선택할 것입니다. 응용 프로그램 코드 배포는 업그레이드될 때 실행됩니다.

이유로, 응용 프로그램은 배포 마이그레이션에서 추가된 인덱스가 해당 발매에 포함되었다고 가정해서는 안됩니다. 응용 프로그램 코드는 해당 배포에 해당 인덱스가 추가되지 않았어도 계속 작동해야 합니다.

생성해야 할 인덱스가 아주 빠르게 만들어질 수 있는 경우(일반적으로 테이블이 새로운 경우 또는 매우 작을 경우) 정규 마이그레이션에서 인덱스를 생성하고 동일한 MR 미일스톤에서 응용 프로그램 코드 변경을 배포하십시오.

필요한 인덱스가 만들어지기까지 시간이 걸리는 경우, 다음 배포에서 응용 프로그램 코드 변경을 수행하기 전에 먼저 인덱스를 PDM(배포 후 마이그레이션)에서 생성해야 합니다.

기존 테이블에 제약 조건으로 작용하는 고유 인덱스 추가

PostgreSQL의 고유 인덱스는 제약 조건으로 작용합니다. 기존 테이블에 하나를 추가하는 것은 까다로울 수 있습니다.

테이블이 GitLab.com 및 Self-Managed 인스턴스에서 절대적으로 작아야 한다는 것을 보장할 수 없는 경우, 다음을 위해 여러 배포 후 이동을 사용해야 할 수 있습니다:

  • 중복 레코드 제거 및(또는) 수정합니다.
  • 기존 열을 제약하는 고유 인덱스 도입합니다.

기존 열이 포함된 주기에 대한 자세한 내용은 기존 열에 NOT NULL 제약 조건 추가 섹션을 참조하세요.

PostgreSQL의 고유 인덱스는 일반 제약 조건과 달리 유효하지 않은 상태로 도입할 수 없습니다. 매핑 및 수정이 진행 중일 때, 새로운 및 업데이트된 레코드에 대한 원하는 고유성을 적용하기 위해 PostgreSQL의 부분 고유 인덱스 및 애플리케이션 유효성을 사용해야 합니다.

작업의 세부 정보는 다양할 수 있으며 다른 방법이 필요할 수 있습니다. 작업을 계획하려면 데이터베이스 팀, 리뷰어 또는 유지 관리자와 상담하세요.

사용되지 않는 인덱스 찾기

사용되지 않는 모든 인덱스를 확인하려면 다음 쿼리를 실행할 수 있습니다:

SELECT relname as table_name, indexrelname as index_name, idx_scan, idx_tup_read, idx_tup_fetch, pg_size_pretty(pg_relation_size(indexrelname::regclass))
FROM pg_stat_all_indexes
WHERE schemaname = 'public'
AND idx_scan = 0
AND idx_tup_read = 0
AND idx_tup_fetch = 0
ORDER BY pg_relation_size(indexrelname::regclass) desc;

이 쿼리는 결코 사용되지 않는 모든 인덱스를 포함하는 디렉터리을 출력하고 인덱스 크기를 내림차순으로 정렬합니다. 이 쿼리는 기존 인덱스가 여전히 필요한지를 결정하는 데 도움이 됩니다. 다양한 열의 의미에 대한 자세한 정보는 다음에서 확인할 수 있습니다: https://www.postgresql.org/docs/current/monitoring-stats.html.

인덱스가 프로덕션에서 여전히 사용 중인지 확인하려면 Thanos를 사용하세요:

sum by (type)(rate(pg_stat_user_indexes_idx_scan{env="gprd", indexrelname="INSERT INDEX NAME HERE"}[30d]))

쿼리 출력은 실제 데이터베이스 사용에 의존하므로 다음과 같은 요소에 영향을 받을 수 있습니다:

  • 일부 쿼리가 결코 실행되지 않아 특정 인덱스를 사용할 수 없는 경우.
  • 일부 테이블에 데이터가 적어 인덱스 스캔 대신에 일련의 스캔을 사용하는 PostgreSQL 결과.

이 데이터는 자주 사용되는 데이터베이스에 대해서만 신뢰할 수 있으며 가능한 많은 GitLab 기능을 사용합니다.

인덱스명 요구 사항

복잡한 정의를 갖는 인덱스는 이주의 암묵적인 명명 동작에 의존하는 대신 명시적으로 명명되어야 합니다. 간단히 말해 다음 중 하나 이상을 사용하여 생성된 인덱스에 명시적 이름 인수를 제공해야 합니다.

  • where
  • using
  • order
  • length
  • type
  • opclass

인덱스명 고려 사항

우리의 제약 조건 명명 규칙 페이지를 확인하세요.

명시적인 이름이 필요한 이유

Rails는 데이터베이스에 독립적이므로 모든 인덱스의 필수 옵션(테이블 이름 및 열 이름)에서만 인덱스 이름을 생성합니다. 예를 들어, 다음과 같이 마이그레이션에서 두 개의 인덱스가 생성된 것을 상상해보세요:

def up
  add_index :my_table, :my_column
  
  add_index :my_table, :my_column, where: 'my_column IS NOT NULL'
end

두 번째 인덱스를 생성하려고 하면 Rails는 두 인덱스에 대해 동일한 이름을 생성합니다.

이 명명 문제는 index_exists? 메서드의 동작에 의해 더욱 복잡해집니다. 인덱스의 유일성 사양을 고려할 때, index_exists? 메서드는 인덱스 이름, 열 이름 및 고유성 유무만을 고려합니다. 예를 들어:

def up
  unless index_exists?(:my_table, :my_column, where: 'my_column IS NOT NULL')
    add_index :my_table, :my_column, where: 'my_column IS NOT NULL'
  end
end

index_exists?의 호출은 :my_table:my_column어떠한 인덱스가 있는지만을 확인하고 인덱스 생성이 우회됩니다.

add_concurrent_index 도우미는 팝데이트 테이블에 인덱스를 생성해야 하는 경우가 요구됩니다. 트랜잭션 마이그레이션 내에서 사용할 수 없으므로 이미 존재하는 인덱스를 제대로 생성하지 못하는 경우에 대비한 내장 체크가 있습니다. 특정 유형의 인덱스에 항상 이름을 요구함으로써 Rails는 index_exists?에 대해 잘못된 양성을 반환할 수 있는 기회를 크게 줄일 수 있습니다.

인덱스 존재 여부 테스트하기

이름으로 인덱스의 존재 여부를 테스트하는 가장 간단한 방법은 index_name_exists? 메서드를 사용하는 것입니다. 하지만 index_exists? 메서드는 이름 옵션과 함께 사용할 수도 있습니다. 예:

class MyMigration < Gitlab::Database::Migration[2.1]
  INDEX_NAME = 'index_name'
  
  def up
    # 스키마 불일치로 인해 조건부로 인덱스를 만들어야 할 경우
    unless index_exists?(:table_name, :column_name, name: INDEX_NAME)
      add_index :table_name, :column_name, name: INDEX_NAME
    end
  end
  
  def down
    # 아무 동작하지 않음
  end
end

add_concurrent_index, remove_concurrent_index, remove_concurrent_index_by_name과 같은 동시 인덱스 도우미는 이미 기존에 사용될 수 있는 인덱스를 내부적으로 수행합니다.

임시 인덱스

가끔 인덱스가 일시적으로 필요할 수 있습니다.

예를 들어, 마이그레이션에서 테이블의 열을 조건부로 업데이트해야 하는 경우 쿼리 성능 지침에 필요한 인덱스가 아닐 수도 있는데 이에 대한 쿼리를 수행하려면 인덱스가 필요합니다.

이러한 경우 임시 인덱스를 고려해보세요. 임시 인덱스를 지정하려면:

  1. 인덱스 이름 앞에 tmp_를 추가하고 명명 규칙을 따르세요.
  2. 다음(또는 미래) 마일스톤에서 인덱스를 삭제하기 위한 후속 이슈를 만드세요.
  3. 마이그레이션에 삭제 문제를 언급하는 주석을 추가하세요.

임시 마이그레이션은 다음과 같습니다:

INDEX_NAME = 'tmp_index_projects_on_owner_where_emails_disabled'

def up
  # 13.9에서 제거될 임시 인덱스 https://gitlab.com/gitlab-org/gitlab/-/issues/1234
  add_concurrent_index :projects, :creator_id, where: 'emails_disabled = false', name: INDEX_NAME
end

def down
  remove_concurrent_index_by_name :projects, INDEX_NAME
end

배치된 백그라운드 마이그레이션 이전에 새로운 인덱스 분석하기

가끔은 배치된 백그라운드 마이그레이션을 지원하기 위해 새로운 인덱스가 필요할 수 있습니다. 이는 두 개의 포스트 디플로이먼트 마이그레이션을 생성함으로써 일반적으로 수행됩니다:

  1. 새로운 인덱스 추가, 일반적으로 임시 인덱스.
  2. 배치된 백그라운드 마이그레이션을 큐에 추가합니다.

대부분의 경우 추가적인 작업이 필요하지 않습니다. 새로운 인덱스는 생성되고 예상대로 사용됩니다.

그러나 표현식 인덱스는 생성 시 새로운 인덱스에 대한 통계를 생성하지 않습니다. Autovacuum은 결국 ANALYZE를 실행하고 통계를 업데이트하여 새로운 인덱스가 사용됩니다. 전후 그 배경 마이그레이션 시나리오에서 새로운 인덱스를 생성한 후에 오직 ANALYZE만 실행하는 경우에만 ANALYZE를 실행합니다.

인덱스 생성 마이그레이션을 분석하여 테이블을 분석합니다.

# in db/post_migrate/

INDEX_NAME = 'tmp_index_projects_on_owner_and_lower_name_where_emails_disabled'
TABLE = :projects

disable_ddl_transaction!

def up
  add_concurrent_index TABLE, '(creator_id, lower(name))', where: 'emails_disabled = false', name: INDEX_NAME
  
  connection.execute("ANALYZE #{TABLE}")
end

ANALYZE는 포스트 디플로이먼트 마이그레이션에서만 실행해야 하며 큰 테이블에 적용해서는 안 됩니다. 이러한 동작이 큰 테이블에서 필요한 경우 #database Slack 채널에서 도움을 요청하세요.

파티션된 테이블에 대한 인덱스

파티션된 테이블에서 동시에 인덱스를 생성할 수 없습니다. 그러나 비동시적으로 인덱스를 생성하면 인덱스를 생성 중인 테이블에 쓰기 잠금이 걸립니다. 따라서 핫 시스템에서 서비스 중단을 피하기 위해 인덱스를 생성할 때 CONCURRENTLY를 사용해야 합니다.

대책으로, 데이터베이스 팀은 add_concurrent_partitioned_index를 제공했습니다. 이 도우미는 쓰기 잠금을 유지하지 않고 파티션된 테이블에 인덱스를 생성합니다.

add_concurrent_partitioned_index의 내부 동작:

  1. CONCURRENTLY를 사용하여 각 파티션에 대해 인덱스를 생성합니다.
  2. 부모 테이블에 대한 인덱스를 생성합니다.

Rails 마이그레이션 예시:

# in db/post_migrate/

class AddIndexToPartitionedTable < Gitlab::Database::Migration[2.1]
  include Gitlab::Database::PartitioningMigrationHelpers
  
  disable_ddl_transaction!
  
  TABLE_NAME = :table_name
  COLUMN_NAMES = [:partition_id, :id]
  INDEX_NAME = :index_name
  
  def up
    add_concurrent_partitioned_index(TABLE_NAME, COLUMN_NAMES, name: INDEX_NAME)
  end
  
  def down
    remove_concurrent_partitioned_index_by_name(TABLE_NAME, INDEX_NAME)
  end
end

인덱스를 비동기적으로 생성

매우 큰 테이블의 경우, 인덱스 생성은 관리가 어려울 수 있습니다. add_concurrent_index는 보통 트래픽을 차단하지 않고 인덱스를 생성하지만, 인덱스 생성이 오랜 시간 동안 실행될 경우 여전히 문제가 될 수 있습니다. autovacuum과 같은 필수 데이터베이스 작업을 실행할 수 없으며, GitLab.com에서는 배포 프로세스가 인덱스 생성이 완료될 때까지 차단됩니다.

GitLab.com에 영향을 제한하기 위해 저희는 주말에 비동기적으로 인덱스를 생성하는 프로세스를 사용합니다. 일반적으로 트래픽이 적고 배포가 적기 때문에, 인덱스 생성은 낮은 수준의 리스크에서 진행될 수 있습니다.

저영향 시간에 인덱스 생성 일정화

  1. 인덱스를 생성하도록 일정화.
  2. MR이 배포되었는지 및 인덱스가 프로덕션에 존재하는지 확인.
  3. 인덱스를 동기적으로 생성하는 마이그레이션을 추가.

인덱스를 생성하도록 일정화

  1. 인덱스를 비동기적으로 생성하기 위해 후발 마이그레이션을 포함하는 MR을 생성합니다. 이는 인덱스를 비동기적으로 생성하는 후발 마이그레이션을 준비합니다.
  2. 이후 이슈를 생성합니다, 이는 인덱스를 동기적으로 생성하는 마이그레이션을 추가하는 것입니다.
  3. 비동기식 인덱스를 준비하는 Merge Request에, 후발 이슈를 언급하는 주석을 추가합니다.

비동기 인덱스 도우미를 사용하여 인덱스를 생성하는 예시는 다음과 같습니다. 이 마이그레이션은 postgres_async_indexes 테이블에 인덱스 이름과 정의를 입력합니다. 주말에 실행되는 프로세스가 이 테이블에서 인덱스를 가져와 생성을 시도합니다.

# in db/post_migrate/

INDEX_NAME = 'index_ci_builds_on_some_column'

# TODO: https://gitlab-issue-url 에서 동기식으로 생성할 인덱스
def up
  prepare_async_index :ci_builds, :some_column, name: INDEX_NAME
end

def down
  unprepare_async_index :ci_builds, :some_column, name: INDEX_NAME
end

MR이 배포되었는지 및 인덱스가 프로덕션에 존재하는지 확인

  1. ChatOps를 사용하여 GitLab.com에서 후발배포 마이그레이션이 실행되었는지 확인합니다. /chatops run auto_deploy status <merge_sha> 명령을 사용합니다. 출력이 db/gprd로 표시되면, 후발배포 마이그레이션이 프로덕션 데이터베이스에서 실행된 것입니다. 자세한 내용은 How to determine if a post-deploy migration has been executed on GitLab.com을 참조하십시오.
  2. 인덱스가 비동기적으로 생성된 경우, 다음 주말까지 인덱스가 생성되도록 기다립니다.
  3. Database Lab을 사용하여 생성이 성공적인지 확인합니다. 결과에서 인덱스가 invalid로 표시되지 않았는지 확인합니다.

인덱스를 동기적으로 생성하는 마이그레이션 추가

인덱스가 프로덕션 데이터베이스에 존재하는 것을 확인한 후, 이에 대한 두 번째 Merge Request을 생성하여 인덱스를 동기적으로 추가합니다. 스키마 변경사항은 두 번째 MR에 업데이트되고 structure.sql에 커밋되어야 합니다. 동기식 마이그레이션은 GitLab.com에 대해 비작업이지만, 다른 설치에 대해 예상대로 마이그레이션을 추가해야 합니다. 아래 블록은 이전 비동기식 예시에 대한 두 번째 마이그레이션을 생성하는 방법을 보여줍니다.

경고: 인덱스가 생성되기 전에 두 번째 마이그레이션이 배포되었는지 확인한 후 add_concurrent_index와 함께 두 번째 마이그레이션을 Merge하기 바랍니다. 두 번째 마이그레이션이 인덱스가 생성되기 전에 배포되면, 인덱스는 두 번째 마이그레이션이 실행될 때 동기적으로 생성됩니다.

# in db/post_migrate/

INDEX_NAME = 'index_ci_builds_on_some_column'

disable_ddl_transaction!

def up
  add_concurrent_index :ci_builds, :some_column, name: INDEX_NAME
end

def down
  remove_concurrent_index_by_name :ci_builds, INDEX_NAME
end

로컬에서 데이터베이스 인덱스 변경 테스트

Merge Request을 생성하기 전에 데이터베이스 인덱스 변경을 로컬에서 테스트해야 합니다.

비동기식으로 생성된 인덱스 확인

인덱스를 생성하는 변경 사항을 테스트하기 위해 로컬 환경에서 비동기 인덱스 도우미를 사용하여 변경 사항을 테스트해야 합니다.

  1. Rails 콘솔에서 Feature.enable(:database_async_index_creation)Feature.enable(:database_reindexing)를 실행하여 피처 플래그를 활성화합니다.
  2. bundle exec rails db:migrate 명령을 실행하여 postgres_async_indexes 테이블에 항목을 생성합니다.
  3. bundle exec rails gitlab:db:execute_async_index_operations:all 명령을 실행하여 모든 데이터베이스에서 인덱스가 비동기적으로 생성되도록 합니다.
  4. 인덱스를 확인하기 위해 PostgreSQL 콘솔을 열고 GDK 명령 gdk psql를 사용하여 명령 \d <index_name>를 실행하여 새로 생성된 인덱스가 있는지 확인합니다.

인덱스를 비동기적으로 제거

매우 큰 테이블의 경우, 인덱스 제거는 관리가 어려울 수 있습니다. remove_concurrent_index는 보통 트래픽을 차단하지 않고 인덱스를 제거하지만, 인덱스 제거가 오랜 시간 동안 실행될 경우 여전히 문제가 될 수 있습니다. autovacuum과 같은 필수 데이터베이스 작업을 실행할 수 없으며, GitLab.com의 배포 프로세스는 인덱스 제거가 완료될 때까지 차단됩니다.

GitLab.com에 영향을 제한하기 위해 다음과 같은 프로세스를 사용하여 주말에 인덱스를 비동기적으로 제거합니다. 일반적으로 트래픽이 적고 배포가 적기 때문에, 주말에 인덱스 제거는 낮은 수준의 리스크에서 진행될 수 있습니다.

  1. 인덱스를 제거하도록 일정화.
  2. MR이 배포되었는지 및 인덱스가 프로덕션에 더 이상 존재하지 않는지 확인.
  3. 인덱스를 동기적으로 제거하는 마이그레이션을 추가.

인덱스를 제거하도록 일정화

  1. 인덱스를 비동기적으로 제거하기 위한 후발 마이그레이션을 포함하는 MR을 생성합니다. 이는 인덱스를 비동기적으로 제거하는 후발 마이그레이션을 준비합니다.
  2. 이후 이슈를 생성합니다, 이는 인덱스를 동기적으로 제거하는 마이그레이션을 추가하는 것입니다.
  3. 비동기식 인덱스 제거를 준비하는 Merge Request에, 후발 이슈를 언급하는 주석을 추가합니다.

예를 들어, 비동기식 인덱스 도우미를 사용하여 인덱스를 제거하는 방법은 다음과 같습니다.

# in db/post_migrate/

INDEX_NAME = 'index_ci_builds_on_some_column'

# TODO: https://gitlab-issue-url 에서 동기식으로 제거할 인덱스
def up
  prepare_async_index_removal :ci_builds, :some_column, name: INDEX_NAME
end

def down
  unprepare_async_index :ci_builds, :some_column, name: INDEX_NAME
end

이 마이그레이션은 postgres_async_indexes 테이블에 인덱스 이름과 정의를 입력합니다. 주말에 실행되는 프로세스가 이 테이블에서 인덱스를 가져와 제거를 시도합니다.

Merge Request을 생성하기 전에 로컬에서 데이터베이스 인덱스 변경을 테스트해야 합니다. Merge Request 설명에 테스트 결과를 포함해야 합니다.

배포되었는지 MR이 확인하고 인덱스가 프로덕션에 더는 존재하지 않는지 확인합니다

  1. /chatops run auto_deploy status <merge_sha>를 사용하여 GitLab.com에서 후 배포 마이그레이션이 실행되었는지 확인합니다. 출력이 db/gprd인 경우 후 배포 마이그레이션이 프로덕션 데이터베이스에서 실행되었습니다. 더 많은 정보는 GitLab.com에서 후 배포 마이그레이션이 실행되었는지 확인하는 방법를 참조하십시오.

  2. 비동기적으로 제거된 인덱스의 경우, 다음 주말까지 기다려 인덱스가 주말에 제거되도록 합니다.

  3. 삭제가 성공적으로 이루어졌는지 확인하려면 Database Lab을 사용합니다. Database Lab에서 삭제가 성공적으로 이루어졌는지 확인하는 방법을 참조하십시오. Database Lab에서 제거된 인덱스를 찾으려고 시도하면 오류가 보고되어야 합니다. 그렇지 않은 경우, 인덱스는 아직 존재할 수 있습니다.

인덱스를 동기적으로 제거하는 마이그레이션 추가

프로덕션 데이터베이스에 인덱스가 더는 존재하지 않는지 확인한 후, 인덱스를 동기적으로 제거하는 두 번째 MR을 생성합니다. 스키마 변경 내용은 이 두 번째 MR의 structure.sql에 업데이트되고 커밋되어야 합니다. 동기적 마이그레이션은 GitLab.com에는 무효작용을 일으키지만 다른 설치를 기대하는 대로 마이그레이션을 추가해야 합니다. 이전 비동기 예제에 대한 두 번째 마이그레이션을 생성하는 예시는 다음과 같습니다:

경고: remove_concurrent_index_by_name을 사용하여 두 번째 마이그레이션을 Merge하기 전에 프로덕션에 인덱스가 더는 존재하지 않는지 확인하십시오. 두 번째 마이그레이션이 인덱스가 파괴되기 전에 배포되면 두 번째 마이그레이션이 실행될 때 인덱스가 동기적으로 파괴됩니다.

# in db/post_migrate/

INDEX_NAME = 'index_ci_builds_on_some_column'

disable_ddl_transaction!

def up
  remove_concurrent_index_by_name :ci_builds, name: INDEX_NAME
end

def down
  add_concurrent_index :ci_builds, :some_column, name: INDEX_NAME
end

비동기적으로 제거된 인덱스 확인

인덱스를 제거하는 변경 사항을 테스트하려면 로컬 환경에서 비동기 인덱스 도우미를 사용합니다:

  1. Rails 콘솔에서 Feature.enable(:database_reindexing)을 실행하여 피처 플래그를 활성화합니다.
  2. bundle exec rails db:migrate를 실행하여 postgres_async_indexes 테이블에 항목을 만듭니다.
  3. 인덱스를 비동기적으로 제거합니다. bundle exec rails gitlab:db:reindex는 인덱스를 파괴해야 합니다.
  4. 파괴된 인덱스가 더 이상 존재하지 않는지 확인하려면 PostgreSQL 콘솔을 열고 GDK 명령인 gdk psql을 사용하여 \d <index_name>를 실행합니다.