고급 검색 마이그레이션 스타일 가이드

새로운 고급 검색 마이그레이션 만들기

참고: 이 기능은 GitLab 13.0 이후에 생성된 인덱스에서만 지원됩니다.

스크립트를 사용하여

scripts/elastic-migration을 실행하고 다음과 같이 프롬프트에 따라 만들어주세요:

  • 마이그레이션을 정의하는 마이그레이션 파일: ee/elastic/migrate/YYYYMMDDHHMMSS_migration_name.rb
  • 마이그레이션을 테스트하는 스펙 파일: ee/spec/elastic/migrate/YYYYMMDDHHMMSS_migration_name_spec.rb
  • 마이그레이션을 식별하는 사전 파일: ee/elastic/docs/YYYYMMDDHHMMSS_migration_name.yml

수동으로

ee/elastic/migrate/ 폴더에서 파일 이름 형식인 YYYYMMDDHHMMSS_migration_name.rb 형식으로 새 파일을 만들어주세요. 이 형식은 Rails 데이터베이스 마이그레이션에도 동일하게 적용됩니다.

# frozen_string_literal: true

class MigrationName < Elastic::Migration
  # 중요: Elastic 인덱스 매핑에 대한 모든 업데이트는 다음에 복제되어야 합니다.
  #   - 주 인덱스의 경우 `Elastic::Latest::Config`
  #   - 독립된 인덱스의 경우 `Elastic::Latest::<Type>Config`

  def migrate
  end

  # 마이그레이션이 완료되었는지 확인합니다.
  # 완료된 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다.
  def completed?
  end
end

적용된 마이그레이션은 gitlab-#{RAILS_ENV}-migrations 인덱스에 저장됩니다. 실행되지 않은 모든 마이그레이션은 Elastic::MigrationWorker 크론 워커에 의해 순차적으로 적용됩니다.

Elastic 인덱스 매핑을 업데이트하려면, 구성을 각 파일에 적용해야 합니다:

마이그레이션은 재시도 한도와 실패한 것으로 표시 및 중지될 수 있는 능력으로 구축될 수 있습니다. 마이그레이션 재시도를 지원하기 위해 필요한 모든 데이터 또는 인덱스 정리는 마이그레이션에서 처리해야 합니다.

건너뛴 마이그레이션

skip_if 프록을 추가하여 마이그레이션을 건너뛸 수 있습니다. 이 프록은 true 또는 false를 평가합니다.

class MigrationName < Elastic::Migration
  skip_if ->() { true|false }

조건이 false인 경우에만 마이그레이션이 실행됩니다. 건너뛴 마이그레이션은 대기 중인 마이그레이션의 일부로 표시되지 않습니다.

건너뛴 마이그레이션은 쇠사탕으로 표시할 수 있지만, skip_if 조건을 유지하여 항상 해당 마이그레이션이 건너뜁니다. 한번 건너뛴 마이그레이션이 쇠사탕으로 표시되면 이 변경을 적용하는 유일한 방법은 처음부터 새로운 인덱스를 다시 생성하는 것입니다.

건너뛴 마이그레이션의 문서 파일을 다음 속성으로 업데이트해주세요:

skippable: true
skip_condition: '<설명>'

마이그레이션 도우미

다음 마이그레이션 도우미는 ee/app/workers/concerns/elastic/에서 사용할 수 있습니다:

Elastic::MigrationBackfillHelper

인덱스에서 특정 필드를 다시 채웁니다. 대부분의 경우, 필드에 대한 매핑은 이미 추가되어 있어야 합니다.

단일 필드를 다시 채우기 위해 field_name 메서드와 DOCUMENT_TYPE 상수가 필요합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationBackfillHelper

  DOCUMENT_TYPE = Issue

  private

  def field_name
    :schema_version
  end
end

하나 이상의 필드가 null인 경우, 여러 필드를 다시 채우기 위해 field_names 메서드와 DOCUMENT_TYPE 상수가 필요합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationBackfillHelper

  DOCUMENT_TYPE = Issue

  private

  def field_names
    %w[schema_version visibility_level]
  end
end

Elastic::MigrationUpdateMappingsHelper

지정된 매핑을 호출하여 인덱스에서 매핑을 업데이트합니다.

new_mappings 메서드와 DOCUMENT_TYPE 상수가 필요합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationUpdateMappingsHelper

  DOCUMENT_TYPE = Issue

  private

  def new_mappings
    {
      schema_version: {
        type: 'short'
      }
    }
  end
end

Elastic::MigrationRemoveFieldsHelper

인덱스에서 지정된 필드를 제거합니다.

index_name, document_type 메서드가 필요합니다. 제거할 필드가 하나인 경우 field_to_remove 메서드를 추가하고, 그렇지 않으면 해당 필드의 배열로 fields_to_remove를 추가해주어야 합니다.

Elasticsearch에서 지정된 문서 유형과 해당 필드를 가진 문서가 있는지 일괄 처리합니다. 문서가 존재하는 경우 update_by_query를 수행하기 위해 Painless 스크립트를 사용합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationRemoveFieldsHelper

  batched!
  throttle_delay 1.minute

  private

  def index_name
    User.__elasticsearch__.index_name
  end

  def document_type
    'user'
  end

  def fields_to_remove
    %w[two_factor_enabled has_projects]
  end
end

기본 배치 크기는 10,000입니다. 이 값을 지정하여 BATCH_SIZE를 통해 이 값을 재정의할 수 있습니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationRemoveFieldsHelper

  batched!
  BATCH_SIZE = 100

  ...
end

Elastic::MigrationObsolete

이미 더 이상 필요하지 않은 마이그레이션을 표시합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationObsolete
end

건너뛴 마이그레이션을 쇠사탕으로 표시할 때 skip_if 조건을 유지해야 합니다.

Elastic::MigrationCreateIndex

새 인덱스를 생성합니다.

  • target_classdocument_type 메서드
  • 클래스의 매핑 및 인덱스 설정이 필요합니다.

경고: 동일한 단계에서 인덱스를 채우기 위해 후속 마이그레이션을 수행해야 합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationCreateIndex

  retry_on_failure

  def document_type
    :epic
  end

  def target_class
    Epic
  end
end

Search::Elastic::MigrationReindexBasedOnSchemaVersion

지정된 문서 유형을 저장하는 색인에서 모든 문서를 재색인하고 schema_version을 업데이트합니다.

DOCUMENT_TYPENEW_SCHEMA_VERSION 상수가 필요합니다. 색인 매핑에는 YYMM 형식의 schema_version 정수 필드가 있어야 합니다.

class MigrationName < Elastic::Migration
  include Search::Elastic::MigrationReindexBasedOnSchemaVersion

  batched!
  batch_size 9_000
  throttle_delay 1.minute

  DOCUMENT_TYPE = WorkItem
  NEW_SCHEMA_VERSION = 23_08
  UPDATE_BATCH_SIZE = 100
end

Search::Elastic::MigrationDeleteBasedOnSchemaVersion

지정된 문서 유형을 저장하는 색인에서 schema_version 값이 주어진 값보다 작은 모든 문서를 삭제합니다.

DOCUMENT_TYPE 상수 및 schema_version 메서드가 필요합니다. 색인 매핑에는 YYMM 형식의 schema_version 정수 필드가 있어야 합니다.

class MigrationName < Elastic::Migration
  include ::Search::Elastic::MigrationDeleteBasedOnSchemaVersion

  DOCUMENT_TYPE = Issue

  batch_size 10_000
  batched!
  throttle_delay 1.minute
  retry_on_failure

  def schema_version
    23_12
  end
end

Search::Elastic::MigrationDatabaseBackfillHelper

limited_indexing 설정을 존준해 데이터베이스의 모든 문서를 엘라스틱 서치 색인에 재색인합니다.

DOCUMENT_TYPE 상수와 respect_limited_indexing? 메서드가 필요합니다.

class MigrationName < Elastic::Migration
  include ::Search::Elastic::MigrationDatabaseBackfillHelper

  batch_size 10_000
  batched!
  throttle_delay 1.minute
  retry_on_failure

  DOCUMENT_TYPE = Issue

  def respect_limited_indexing?
    true
  end
end

Elastic::MigrationHelper

기존 예제에 맞지 않는 마이그레이션이 있는 경우 사용할 수 있는 메서드를 포함합니다.

class MigrationName < Elastic::Migration
  include Elastic::MigrationHelper

  def migrate
  ...
  end

  def completed?
  ...
  end
end

Elastic::MigrationWorker가 지원하는 마이그레이션 옵션

Elastic::MigrationWorker는 다음 마이그레이션 옵션을 지원합니다:

  • batched! - 마이그레이션을 일괄로 실행하도록 허용합니다. 설정하면 Elastic::MigrationWorkerthrottle_delay 옵션을 사용하여 자체를 다시 대기열에 넣습니다. 일괄 처리는 migrate 메서드에서 처리되어야 합니다. 이 설정은 다시 대기열에 넣는 것만 제어합니다.

  • batch_size - batched! 마이그레이션 실행 중 수정된 문서 수를 설정합니다. 이 크기는 업데이트에 충분한 시간을 제공하는 값을 설정해야 합니다. 이 설정은 기본값이 1000개인 Elastic::MigrationBackfillHelper migrate 메서드에서 사용하거나 사용자 정의 migrate 메서드에서 조절할 수 있습니다.

  • throttle_delay - 일괄 실행 간의 대기 시간을 설정합니다. 각 마이그레이션 일괄 실행에 충분한 시간을 제공하기 위해 이 시간을 충분히 길게 설정해야 합니다. 또한 이 시간은 Elastic::MigrationWorker 크론 워커가 실행되는 주기인 5분보다 짧아야 합니다. 기본값은 3분입니다.

  • pause_indexing! - 마이그레이션이 실행되는 동안 색인을 일시 중지합니다. 이 설정은 마이그레이션이 완료된 후에 색인 설정을 이전 값으로 설정합니다.

  • space_requirements! - 마이그레이션이 실행되는 동안 클러스터에 충분한 여유 공간이 있는지 확인합니다. 이 설정은 마이그레이션이 실행될 때 필요한 저장 공간이 확보되지 않은 경우 마이그레이션을 중지합니다. 마이그레이션은 space_required_bytes 메서드를 통해 필요한 공간을 정의하여야 합니다.

  • retry_on_failure - 실패 시 재시도 기능을 활성화합니다. 기본적으로 마이그레이션은 30번 재시도합니다. 재시도 횟수를 사용자 정의하려면 max_attempts 인수를 전달하세요: retry_on_failure max_attempts: 10

# frozen_string_literal: true

class BatchedMigrationName < Elastic::Migration
  # 일괄로 실행해야 함을 선언합니다.
  batched!
  throttle_delay 10.minutes
  pause_indexing!
  space_requirements!
  retry_on_failure

  # ...
end

마이그레이션에서 다운타임 방지

마이그레이션 되돌리기

마이그레이션이 실패하거나 GitLab.com에서 중단된 경우 마이그레이션을 도입한 변경 사항을 되돌리는 것이 좋습니다. 이렇게 하면 자체 호스팅 고객이 손상된 마이그레이션을 받지 않게되며 백포트가 필요한 경우를 줄일 수 있습니다.

병합 시기

릴리스 후 1주일 이내에 마이그레이션을 병합하지 않는 것이 좋습니다. 이렇게 하면 마이그레이션이 실패하거나 예상대로 작동하지 않는 경우 되돌리기를 위한 시간을 확보할 수 있습니다. 릴리스의 마지막 주에 개발 중 또는 검토 중인 마이그레이션은 다음 마일스톤으로 넘겨야 합니다.

다중 버전 호환성

고급 검색 마이그레이션은 다른 GitLab 변경 사항과 마찬가지로 동시에 여러 버전의 애플리케이션이 실행되는 경우를 지원해야 합니다.

배포 순서에 따라 마이그레이션이 시작되거나 종료된 상태에서 여전히 마이그레이션을 시작한 후 또는 마이그레이션이 완료된 상태에서 이전 버전의 애플리케이션 코드를 실행하는 서버가 여전히 존재할 수 있습니다. 고급 검색 마이그레이션이 배포가 완료된 후 시작하도록 보장할 때까지는 이 사항을 고려해야 합니다.

고위험 마이그레이션

Elasticsearch는 트랜잭션을 지원하지 않기 때문에 애플리케이션 코드가 마이그레이션이 시작되거나 완료된 후 되돌아가는 상황을 수용할 수 있도록 마이그레이션을 설계해야 합니다.

이러한 이유로 일반적으로 중요한 데이터 손실이 발생할 위험이 있다면 (예: 데이터 이동 후 삭제) 마이그레이션이 성공적으로 완료된 후에 삭제를 하도록 하는 것이 안전합니다. 자체 호스팅 고객을 위해, 중요한 데이터 손실이 발생할 위험이 있는 경우 다음 릴리스로 미루는 것이 좋습니다.

마이그레이션 실행 시간 계산

GitLab.com에서 마이그레이션이 실행되는 데 얼마나 걸릴지 이해하는 것이 중요합니다. 마이그레이션에서 처리될 문서 수를 유도하세요. 이 번호는 데이터베이스를 쿼리하거나 기존 Elasticsearch 색인에서 얻을 수 있습니다. 다음 공식을 사용하여 실행 시간을 계산하세요:

> batch_size = 9_000
=> 9000
> throttle_delay = 1.minute
=> 1 minute
> number_of_documents = 15_536_906
=> 15536906
> (number_of_documents / batch_size) * throttle_delay
=> 1726 minutes
> (number_of_documents / batch_size) * throttle_delay / 1.hour
=> 28

고급 검색 마이그레이션의 모범 사례

최상의 결과를 얻기 위해 다음의 모범 사례를 따르세요:

  • 각 문서 유형의 모든 마이그레이션을 순서대로 정렬하여, Elastic::MigrationUpdateMappingsHelper를 사용하는 모든 마이그레이션은 Elastic::MigrationBackfillHelper를 사용하는 마이그레이션보다 먼저 실행됩니다. 모든 마이그레이션이 미적용된 경우 동일한 문서를 여러 번 다시 색인하는 것을 피하고 백필 시간을 줄일 수 있습니다.
  • 일괄 처리에서 배치 크기를 9,000 문서 미만으로 유지하세요. 대량 색인기는 1분마다 실행되고 10,000 문서의 배치를 처리하도록 설정되어 있습니다. 이렇게 하면 다른 마이그레이션 배치가 시도되기 전에 대량 색인기가 레코드를 처리할 시간이 생깁니다.
  • 마이그레이션이 완료되었는지 확인하기 전에 인덱스를 새로 고쳐야 하는지 확인하세요.
  • 각 마이그레이션에 로깅 문을 추가하세요. 마이그레이션이 시작될 때, 완료 확인이 발생했을 때, 마이그레이션이 완료될 때 이 로그는 마이그레이션 문제를 디버깅하는 데 도움이 됩니다.
  • Elasticsearch Reindex API 작업을 사용하는 경우 인덱싱을 일시 중지하세요.
  • 마이그레이션이 실패할 수 있는 경우 재시도 제한을 추가하세요. 이로써 문제가 발생할 경우 마이그레이션을 중단할 수 있습니다.

고급 검색 마이그레이션 정리

고급 검색 마이그레이션은 보통 긴 기간 동안 여러 코드 경로를 지원해야 하므로 가능한 경우 정리하는 것이 중요합니다.

GitLab 필수 정지를 사용하여 완전히 마이그레이션되지 않은 색인의 역호환성을 제거하는 안전한 시간에 이를 제거하기로 결정합니다. 이를 업그레이드 문서에 문서화했습니다.

GitLab Housekeeper를 사용하여 정리 프로세스를 자동화합니다. 이 프로세스에는 기존 마이그레이션을 폐기 처리하고 폐기된 마이그레이션을 삭제하는 것이 포함됩니다. 마이그레이션이 폐기 처리되면 마이그레이션 코드가 폐기된 마이그레이션 코드로 교체되고 테스트는 폐기된 마이그레이션 공유 예제로 교체됩니다. 이렇게 함으로써:

  • 고급 검색 마이그레이션에서 호출된 코드를 유지할 필요가 없습니다.
  • 더 이상 지원하지 않는 마이그레이션에 대한 테스트를 실행해서 CI 시간을 낭비하지 않습니다.
  • 이 마이그레이션을 실행하지 않은 오퍼레이터들이 타깃 버전으로 직접 업그레이드하게 되면 처음부터 다시 색인화하도록 하는 메시지가 표시됩니다.

더욱 안전을 위해, 마지막 필수 정지의 두 번째 이전 마이너 버전에서 생성된 마이그레이션은 정리하지 않습니다. 예를 들어, 마지막 필수 정지가 %14.0이라면, %13.12에서만 추가된 마이그레이션을 정리하지 않아야 합니다. 이 추가적인 안전장치를 통해 GitLab.com에서 며칠 동안 걸릴 수 있는 마이그레이션에 대비합니다. GitLab.com으로의 배포는 자동화되어 있으므로 이러한 정리를 방지하는 자동화된 확인이 없습니다. 거기에 더해, 자동화된 확인이 있다고 하더라도 고급 검색 마이그레이션을 기다리기 위해 GitLab.com 배포를 막고 싶지는 않을 것입니다. 왜냐하면 아직 한주가 남아 있는 경우가 있기 때문입니다.

폐기된 마이그레이션으로 표시하는 프로세스

Keeps::MarkOldAdvancedSearchMigrationsAsObsolete Keep를 수동으로 실행하여 마이그레이션을 폐기 처리합니다.

마지막 필수 정지의 두 버전 전에 만들어진 각 마이그레이션에 대해, 다음 단계를 수행합니다.

  1. 마이그레이션의 내용을 보존하고 아래에 prepend를 추가합니다:

     ClassName.prepend ::Elastic::MigrationObsolete
    
  2. 명세 파일 내용을 'a deprecated Advanced Search migration' 공유 예제로 교체합니다.
  3. Global Search 백앤드 엔지니어 중 한 명을 임의로 선택합니다.
  4. 마이그레이션을 폐기 처리했다는 것을 사전 파일에 업데이트합니다.

MR 담당자는 다음을 수행해야 합니다:

  1. 사전 파일이 올바른 marked_obsolete_by_urlmarked_obsolete_in_milestone를 가지고 있는지 확인합니다.
  2. .rubocop_todo/ 디렉토리에 마이그레이션이나 명세 파일에 대한 참조가 존재하지 않는지 확인합니다.
  3. 이 마이그레이션을 위해 역호환성을 처리하는 모든 로직을 제거합니다. 이를 위해 Elastic::DataMigrationService.migration_has_finished?(:migration_name_in_lowercase)을 찾아야 합니다.
  4. 필요한 변경 사항을 MR에 푸시합니다.

폐기된 마이그레이션을 삭제하는 프로세스

Keeps::DeleteObsoleteAdvancedSearchMigrations Keep를 수동으로 실행하여 폐기된 마이그레이션 및 명세를 삭제합니다. 이 Keep은 가장 최근의 폐기된 마이그레이션을 제외한 모든 폐기된 마이그레이션을 제거합니다.

  1. 마지막 필수 정지 이전에 폐기 처리된 폐기된 마이그레이션을 선택합니다.
  2. 첫 번째 단계가 모든 폐기된 마이그레이션을 포함하는 경우, unapplied migrations이 있는 고객을 위한 예방 장치로 한 개의 폐기된 마이그레이션을 유지합니다.
  3. 해당 마이그레이션 파일과 명세 파일을 삭제합니다.
  4. MR을 생성하고 Global Search 팀 멤버에게 할당합니다.

MR 담당자는 다음을 수행해야 합니다:

  1. 기본 브랜치에서 마이그레이션을 migration graveyard로 백업합니다.
  2. .rubocop_todo/ 디렉토리에 마이그레이션이나 명세 파일에 대한 참조가 존재하지 않는지 확인합니다.
  3. 필요한 변경 사항을 MR에 푸시합니다.