고급 검색 개발 가이드
이 페이지에는 Elasticsearch를 사용한 개발 및 작업에 대한 정보가 포함되어 있습니다.
Elasticsearch를 활성화하고 초기 색인 작업을 수행하는 방법에 대한 정보는 Elasticsearch 통합 문서를 참조하십시오.
심층 연구
2019년 6월, Mario de la Ossa가 GitLab 팀 멤버만 참여 가능한 딥 다이브를 진행하여 향후 이 코드베이스의 일부를 작업할 수 있는 모든 사람들에게 도메인 특화 지식을 공유하기 위해 GitLab Elasticsearch 통합에 초점을 맞췄습니다. YouTube에서 녹화본 및 Google 슬라이드 및 PDF에서 찾을 수 있습니다. 이 심층 다이브에서 다룬 내용은 GitLab 12.0 기준으로 정확했으며, 특정 세부 정보가 변경되었을 수 있지만 여전히 좋은 소개 자료로 사용될 것입니다.
2020년 8월에는 다중 색인 지원을 위한 GitLab 특정 아키텍처에 초점을 맞춘 두 번째 딥 다이브가 진행되었습니다. YouTube 녹화본 및 슬라이드가 제공됩니다. 이 심층 다이브에서 다룬 내용은 GitLab 13.3 기준으로 정확했습니다.
지원되는 버전
버전 요구사항을 참조하십시오.
Elasticsearch 쿼리에 중대한 변경을 가하는 개발자는 모든 지원되는 버전에서 자신의 기능을 테스트해야 합니다.
개발 환경 설정
Elasticsearch GDK 설정 지침을 참조하십시오.
유용한 Rake 작업
-
gitlab:elastic:test:index_size
: 현재 색인이 사용하는 공간 및 색인에 있는 문서 수를 알려줍니다. -
gitlab:elastic:test:index_size_change
: 색인 크기를 출력하고 재색인하여 다시 색인 크기를 출력합니다. 색인 크기 개선을 테스트할 때 유용합니다.
또한, 테스트를 위해 큰 리포지터리나 여러 개의 포크가 필요한 경우 이 지침에 따르십시오
작동 방식
Elasticsearch 통합은 외부 인덱서에 의존합니다. 우리는 Go로 작성된 인덱서를 제공. 사용자는 초깃값 색인을 Rake 작업을 통해 트리거해야 하지만, 이 작업이 완료된 후에는 GitLab 자체가 /ee/app/models/concerns/elastic/application_versioned_search.rb
에서 상속된 after_
콜백을 통해 필요할 때 GitLab 스스로 다시 색인을 트리거합니다.
초기 색인이 완료된 후, 프로젝트를 제외한 모든 모델의 생성, 업데이트 및 삭제 작업은 Redis ZSET
에서 추적됩니다. 정기적으로 sidekiq-cron
ElasticIndexBulkCronWorker
가 이 큐를 처리하여 대량 요청 API를 사용하여 여러 Elasticsearch 문서를 한꺼번에 업데이트합니다.
검색 쿼리는 ee/app/models/concerns/elastic
에서 찾을 수 있는 concerns에 의해 생성됩니다. 이러한 concerns는 액세스 제어를 담당하고 있으며, 보안 버그의 역사적인 원인이 되었으므로 주의를 기울여야 합니다!
사용자 정의 라우팅
프로젝트와 연관된 문서 유형에 대해 Elasticsearch에서 사용자 정의 라우팅이 사용됩니다. 라우팅 형식은 project_<project_id>
입니다. 라우팅은 색인 작업 및 검색 작업 중에 설정됩니다. 사용자 정의 라우팅을 사용하는 데 장단점은 다음과 같습니다:
- 프로젝트 범위의 검색이 훨씬 빠릅니다.
- 글로벌 및 그룹 범위의 검색에는 라우팅이 사용되지 않습니다.
- 샤드 크기 불균형이 발생할 수 있습니다.
기존 분석기 및 토크나이저
다음 분석기 및 토크나이저가 ee/lib/elastic/latest/config.rb
에 정의되어 있습니다.
분석기
path_analyzer
블롭 경로를 색인에 사용할 때 사용됩니다. path_tokenizer
및 lowercase
및 asciifolding
필터를 사용합니다.
아래의 path_tokenizer
설명을 참조하십시오.
sha_analyzer
블롭 및 커밋에서 사용됩니다. sha_tokenizer
및 lowercase
및 asciifolding
필터를 사용합니다.
아래에서 나온 sha_tokenizer
설명을 참조하십시오.
code_analyzer
블롭의 파일 이름 및 내용을 색인할 때 사용됩니다. whitespace
토크나이저 및 word_delimiter_graph
, lowercase
, 및 asciifolding
필터를 사용합니다.
whitespace
토크나이저는 토큰을 어떻게 분할할지에 대한 더 많은 제어를 위해 선택되었습니다. 예를 들어, 문자열 Foo::bar(4)
는 Foo
및 bar(4)
와 같은 토큰을 올바르게 검색하려면 분할되어야 합니다.
토큰이 어떻게 분할되는지에 대한 설명은 code
필터를 참조하십시오.
토크나이저
sha_tokenizer
이것은 edgeNGram
tokenizer를 사용하여 SHAs를 5자 이상의 하위 집합으로 검색 가능하게 하는 사용자 정의 토크나이저입니다.
예시:
240c29dc7e
가 다음과 같이 변합니다:
240c2
240c29
240c29d
240c29dc
240c29dc7
240c29dc7e
path_tokenizer
이것은 입력으로 주어진 경로의 얼마든지 많거나 적은 부분으로 경로를 찾을 수 있도록 reverse: true
를 사용하는 path_hierarchy
tokenizer를 사용하는 사용자 정의 토크나이저입니다.
예시:
'/some/path/application.js'
가 다음과 같이 변합니다:
'/some/path/application.js'
'some/path/application.js'
'path/application.js'
'application.js'
주의사항
- 검색에는 자체 분석기가 있을 수 있습니다. 분석기를 편집할 때 확인하는 것을 잊지 마세요.
-
Character
필터(토큰 필터와 달리)는 항상 원래 문자를 대체합니다. 이러한 필터는 정확한 검색을 방해할 수 있습니다.
다수의 색인을 사용한 제로 다운타임 재인덱싱
현재 GitLab은 하나의 버전의 설정만 처리할 수 있습니다. 어떠한 설정/스키마 변경이 있을 경우 모든 것을 처음부터 다시 인덱싱해야 합니다. 재인덱싱은 오랜 시간이 걸릴 수 있기 때문에 이는 검색 기능의 다운타임을 초래할 수 있습니다.
다운타임을 피하기 위해 GitLab은 동시에 기능하는 여러 색인을 지원하도록 작업 중입니다. 스키마가 변경될 때마다 관리자는 새로운 색인을 생성하고 해당 색인으로 재인덱싱할 수 있으며, 검색은 계속하여 이전 안정적인 색인으로 이루어질 것입니다. 모든 데이터 업데이트는 두 색인으로 전달될 것입니다. 새로운 색인이 준비되면 관리자는 해당 색인을 활성화시킬 수 있으며, 이로써 모든 검색은 해당 색인으로 이루어지고 이전의 색인은 제거될 것입니다.
이는 AWS로의 이주와 같은 새로운 서버로의 마이그레이션에도 도움이 될 것입니다.
현재 우리는 이 새로운 설계로 이주 중입니다. 현재 모든 것은 하나의 단일 버전과 완전히 동작하도록 설정되어 있습니다.
아키텍처
elasticsearch-rails
가 제공하는 전통적인 설정은 내부 프록시 클래스를 통해 통신합니다. 개발자들은 모델별 논리를 해당 모델에 포함하기 위해 모듈에 모델별 논리를 작성할 것입니다 (예: SnippetsSearch
). __elasticsearch__
메서드는 다음과 같은 프록시 객체를 반환합니다:
-
Issue.__elasticsearch__
은Elasticsearch::Model::Proxy::ClassMethodsProxy
의 인스턴스를 반환합니다 -
Issue.first.__elasticsearch__
은Elasticsearch::Model::Proxy::InstanceMethodsProxy
의 인스턴스를 반환합니다.
이러한 프록시 객체들은 Elasticsearch 서버에 직접적으로 통신합니다 (다이어그램의 상단 부분 참조).
계획된 새로운 설계에서 각 모델은 해당 모델별 논리가 위치한 서브클래스화된 프록시 객체의 쌍을 가지게 될 것입니다. 예를 들어 Snippet
은 Elasticsearch::Model::Proxy::ClassMethodsProxy
의 서브클래스인 SnippetClassProxy
를 가지게 될 것입니다. Snippet
은 Elasticsearch::Model::Proxy::InstanceMethodsProxy
의 서브클래스인 SnippetInstanceProxy
를 가지게 될 것입니다.
__elasticsearch__
는 실제 프록시 객체를 추적하는 또 다른 프록시 객체의 한 층을 나타냅니다. 적절한 색인으로 메서드 호출을 전달할 것입니다. 예를 들어:
-
model.__elasticsearch__.search
는 읽기 작업이기 때문에 안정적인 색인으로 전달될 것입니다. -
model.__elasticsearch__.update_document
는 모든 색인으로 전달될 것이며, 모든 색인을 최신 상태로 유지할 것입니다.
버전별 전역 구성은 이제 Elastic::(Version)::Config
클래스에 있습니다. 여기에서 매핑을 변경할 수 있습니다.
새로운 스키마 버전 생성
ee/lib/elastic/v12p1
과 같은 폴더들은 서로 다른 버전의 검색 논리 스냅샷을 포함하고 있습니다. 연속적인 Git 히스토리를 유지하기 위해, 최신 버전은 ee/lib/elastic/latest
아래에 존재하지만 해당 클래스들은 실제 버전 (예: ee/lib/elastic/v12p3
)에 에일리어싱됩니다. 이러한 클래스들을 참조할 때는 Latest
네임스페이스를 직접적으로 사용하지말고 실제 버전 (예: V12p3
)을 사용해야 합니다.
버전 이름은 기본적으로 GitLab 릴리스 버전을 따릅니다. 스키마가 12.3에서 변경된 경우, 우리는 V12p3
(p는 “point”의 약자)이라는 새로운 네임스페이스를 생성할 것입니다. 버전을 다르게 명명해야하는 필요성이 있다면 이슈를 제기하세요.
현재 버전이 v12p1
이고 v12p3
에 대한 새로운 버전을 생성해야 하는 경우 다음 단계를 따릅니다:
-
v12p1
전체 폴더를v12p3
로 복사합니다. -
v12p3
폴더 아래의 파일들의 네임스페이스를V12p1
에서V12p3
로 변경합니다 (그러나 여전히Latest
에 에일리어싱됩니다). -
v12p1
폴더를 삭제합니다. -
latest
전체 폴더를v12p1
로 복사합니다. -
v12p1
폴더 아래의 파일들의 네임스페이스를Latest
에서V12p1
로 변경합니다. -
latest
폴더 아래의 파일들을 필요에 따라 변경합니다.
성능 모니터링
프로메테우스
GitLab은 모든 웹/API 요청 및 Sidekiq 작업에 대한 요청 수와 타이밍과 관련한 프로메테우스 메트릭를 내보냅니다. 이는 성능 트렌드를 진단하고 Elasticsearch 타이밍이 다른 작업에 비해 전반적인 성능에 미치는 영향을 비교하는 데 도움이 될 수 있습니다.
색인 대기열
GitLab은 또한 색인 대기열에 대한 프로메테우스 메트릭을 내보냅니다. 이는 성능 병목 현상을 진단하고 GitLab 인스턴스나 Elasticsearch 서버가 업데이트의 양에 대응할 수 있는지 여부를 결정하는 데 도움이 될 수 있습니다.
로그
모든 색인 작업은 Sidekiq에서 이루어지므로 Elasticsearch 통합을 위한 관련 로그의 많은 부분은 sidekiq.log
에서 찾을 수 있습니다. 특히, Elasticsearch에 요청을 하는 모든 Sidekiq 워커는 Elasticsearch에 대한 쿼리/쓰기의 요청 수와 소요 시간을 로깅할 것입니다. 이는 클러스터가 색인 작업에 따라 적절히 대응하고 있는지 이해하는 데 유용할 수 있습니다.
Elasticsearch 검색은 페이지 로드 또는 API 요청을 처리하는 일반적인 웹 워커를 통해 이루어집니다. Elasticsearch로의 요청을 하는 모든 페이지 로드나 API 요청은 production_json.log
에서 요청 수와 소요 시간을 로깅할 것입니다. 이 로그에는 데이터베이스 및 Gitaly 요청에 소요된 시간도 포함되어 있으며, 이는 어떤 부분의 검색이 성능이 나쁜지를 진단하는 데 도움이 될 수 있습니다.
성능 문제를 진단하는 데 도움이 될 수 있는 추가적인 Elasticsearch에 특화된 로그가 있는데, 이것들은 elasticsearch.log
에 전송됩니다.
성능 막대
Elasticsearch 요청은 성능 막대
에 표시되며, 이는 개발 환경에서도 배포된 GitLab 인스턴스에서도 사용할 수 있어 검색 성능이 나쁜 경우를 진단하는 데 유용합니다. 이를 통해 수행된 정확한 쿼리를 보여주어 왜 검색이 느릴 수 있는지 진단하는 데 도움이 됩니다.
상관 ID 및 X-Opaque-Id
우리의 상관 ID는 모든 요청에서 Rails에서 Elasticsearch로 X-Opaque-Id
헤더로 전달되어 클러스터의 모든 작업을 GitLab의 요청으로 추적할 수 있게 합니다.
문제 해결
flood stage disk watermark [95%] exceeded
획득하기
다음과 같은 오류가 발생할 수 있습니다.
[2018-10-31T15:54:19,762][WARN ][o.e.c.r.a.DiskThresholdMonitor] [pval5Ct]
flood stage disk watermark [95%] exceeded on
[pval5Ct7SieH90t5MykM5w][pval5Ct][/usr/local/var/lib/elasticsearch/nodes/0] free: 56.2gb[3%], this node will be marked read-only
기본 95% 임계값을 기반으로 디스크 공간 임계값을 초과했기 때문입니다. 또한 read_only_allow_delete
설정이 true
로 설정됩니다. 인덱싱을 차단하게 됩니다.
curl "http://localhost:9200/gitlab-development/_settings?pretty"
이를 elasticsearch.yml
파일에 추가하세요.
# 디스크 할당 비활성화
cluster.routing.allocation.disk.threshold_enabled: false
또는
# 고유한 제한 설정
cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.flood_stage: 5gb # ES 6.x only
cluster.routing.allocation.disk.watermark.low: 15gb
cluster.routing.allocation.disk.watermark.high: 10gb
Elasticsearch를 재시작하면 read_only_allow_delete
가 자동으로 해제됩니다.
_“Disk-based Shard Allocation | Elasticsearch Reference” 5.6 및 6.x에서 제공됨_ |
재해 복구/데이터 손실/백업
GitLab에서 Elasticsearch의 사용은 항상 보조 데이터 리포지터리로만 사용됩니다. 이는 Elasticsearch에 저장된 모든 데이터가 언제든지 다른 데이터 원본, 특히 PostgreSQL 및 Gitaly로부터 다시 유도될 수 있음을 의미합니다. 따라서 Elasticsearch 데이터 리포지터리가 무슨 이유로든 손상된 경우 모든 것을 처음부터 다시 인덱싱할 수 있습니다.
Elasticsearch 인덱스가 굉장히 크다면 처음부터 다시 인덱싱하는 데 너무 많은 시간이 걸리거나 지연이 너무 심할 수 있습니다. 자동으로 불일치를 찾아 재동기화하는 내장된 메커니즘은 없지만, 놓친 시간 범위의 업데이트를 전체 로그에서 찾아보는 것이 유용할 수 있는 도구가 있습니다. 이 정보는 매우 저수준이며 GitLab 코드베이스에 익숙한 운영자에게 유용합니다. 다른 사람에게 유용할 수 있으니 여기에 문서로 작성되었습니다. 이론적으로 어떤 것을 다시 재생해야 하는지 파악할 수 있을 수 있도록 사용될 수 있는 관련 로그는 다음과 같습니다.
- 모든 리포지터리 업데이트가 동기화된 비리포지터리 업데이트는
elasticsearch.log
에서track_items
을 검색하여 찾을 수 있으며, 이들은 다시::Elastic::ProcessBookkeepingService.track!
를 통해 전송하여 재생할 수 있습니다. - 발생한 모든 리포지터리 업데이트는
elasticsearch.log
에서indexing_commit_range
를 검색하여 찾을 수 있으며, 이를 재생하려면 로그에서 가장 오래된from_sha
를IndexStatus#last_commit/last_wiki_commit
에 재설정한 다음 다시 프로젝트를 인덱싱하도록 트리거하는ElasticCommitIndexerWorker
를 실행해야 합니다. - 발생한 모든 프로젝트 삭제는
sidekiq.log
에서ElasticDeleteProjectWorker
를 검색하여 찾을 수 있으며, 이 업데이트는 다시ElasticDeleteProjectWorker
를 트리거함으로써 재생할 수 있습니다.
위의 방법과 정기적인 Elasticsearch 스냅숏을 찍는 것으로 다양한 종류의 데이터 손실 문제에서 재해 복구할 수 있어야하며, 이를 통해 처음부터 모든 것을 인덱싱하는 것에 비해 상대적으로 짧은 시간 내에 데이터 손실 문제를 복구할 수 있어야 합니다.