This page contains information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. The development, release, and timing of any products, features, or functionality may be subject to change or delay and remain at the sole discretion of GitLab Inc.
Status Authors Coach DRIs Owning Stage Created
ongoing @dgruzd @DylanGriffith @DylanGriffith @joshlambert @changzhengliu devops enablement 2022-12-28

Zoekt를 사용한 코드 검색

요약

우리는 GitLab에 추가적인 코드 검색 기능을 구현할 것입니다. 이 기능은 코드 검색을 위해 특별히 설계된 오픈 소스 검색 엔진인 Zoekt를 기반으로 합니다. Zoekt는 GitLab에서 API로 사용될 것이며 사용자 인터페이스는 Zoekt에서 제공하는 새로운 기능을 제외하고는 크게 변경되지 않을 것입니다.

이 기능은 시스템이 실제로 우리의 확장 및 비용 기대치를 충족하는지 확인하고 Elasticsearch에서 지원하는 코드 검색과 함께 실행되도록 단계적으로 구현될 것입니다. 첫 번째 단계는 내부적으로 gitlab-org에서 사용할 수 있도록 만든 후, 고객의 관심에 따라 고객을 기반으로 한 단계적인 확대로 전개될 것입니다.

동기

현재 GitLab 코드 검색 기능은 Elasticsearch를 기반으로 합니다. Elasticsearch는 다른 유형의 검색(이슈, Merge Request, 댓글 등)에서 유용하게 사용되었지만, 사용자가 일치하는 것을 정확하게(즉, 거짓 긍정이 없는) 예상하고 유연하게(예를 들어 부분 일치정규 표현식을 지원하는 코드 검색에는 적합하지 않은 설계입니다. 우리는 옵션을 조사하였고, Zoekt가 코드 검색에 적합한 유일한 잘 유지되는 오픈 소스 기술임을 확인하였습니다. 우리의 연구에 따르면, 우리가 직접 구현하려고 한다면 Zoekt의 기본 아키텍처가 우리가 다시 구현할 아키텍처와 유사할 것이라고 믿고 있습니다.

우리의 초기 벤치마킹에 따르면 Zoekt는 우리의 규모에서 실행 가능할 것으로 보이지만, 정확한 벤치마킹 노력보다 Zoekt와의 베타 통합 구축에 투자하고 GitLab.com에서 그룹별로 점진적으로 롤아웃하는 것이 확장성 및 비용에 대한 더 나은 통찰력을 제공할 것으로 확신합니다. 이러한 과정은 처음에는 내부적으로 롤아웃되고 후에 시행할 의사가 있는 고객들에게 롤아웃될 것이므로 비교적 낮은 위험을 가질 것입니다.

목표

이 통합의 주요 목표는 다음과 같은 매우 요청된 코드 검색 개선 사항을 구현하는 것입니다:

  1. 고급 검색에서 정확한 일치 (부분 일치) 코드 검색
  2. 고급 글로벌 검색에서 정규 표현식 지원
  3. 동일한 파일에서 여러 줄 일치 지원

롤아웃의 초기 단계는 가능한 한 빨리 확장이나 인프라 비용 문제를 발견하고 해결하기 위해 설계될 것입니다. 그렇게 함으로써, 이 기술이 적합하지 않은 경우에 너무 많은 투자를 하기 전에 조기에 전환할 수 있도록 할 것입니다.

비목표

초기에는 다음이 목표가 아닙니다. 그러나 이론적으로 이 솔루션 위에 구축될 수 있습니다.

  1. 많은 리포지터리에 대해 빠르게 정규식 스캔을 수행할 수 있는 보안 스캔 기능 개선
  2. 검색 인프라에서 비용 절감 - 추가적인 최적화로 가능할 수 있지만, 초기 추정치에 따르면 비용은 유사할 것입니다
  3. 사용자가 찾을 것으로 예측하는 검색에 사용되는 AI/ML 기능
  4. 코드 지능 및 네비게이션 - 구조화된 데이터에 기반을 둬야 할 것으로 예상되므로 코드 지능 및 네비게이션 기능은 확실히 코드 기반의 임시적인 해결책이 될 수 있으나, Zoekt를 사용한 정규식 기반 검색(구 ctags 사용)은 구축되었지만 ctags 심볼에 충분한 데이터가 포함되지 않거나 Zoekt가 프로젝트 간 탐색에 필요한 의존성을 이해하지 못할 수도 있기 때문에 초기에 적합하지 않을 것입니다.

제안

Zoekt 통합의 초기 구현이 Zoekt를 Elasticsearch 코드 검색의 대체제로 사용할 수 있는지의 실현 가능성을 보여주기 위해 만들어졌습니다. 이 설계안은 GitLab.com에서의 대규모 고객 롤아웃에 필요한 모든 세부 정보를 확장할 것입니다.

디자인 및 구현 세부사항

사용자 경험

Zoekt 롤아웃에 참여하는 그룹 또는 프로젝트에서 고급 검색을 수행하는 사용자는 UI 어딘가에 “정확한 검색”으로 전환하는 토글을 표시할 것입니다 (또는 다른 UX가 결정될 예정입니다). 초기 사용자 피드백은 사용자에게 이러한 선택지를 어떻게 제시할지를 평가하는 데 도움이 될 것이며, 결국 우리는 Zoekt가 적합한 장기적인 선택인 경우 Elasticsearch 옵션을 제거하려고 할 것입니다.

색인화

우리의 Elasticsearch 통합과 유사하게, GitLab은 리포지터리에 업데이트가 있을 때마다 Zoekt에게 알립니다. 우리는 gitlab-zoekt-indexer라는 새로운 인덱서를 도입하고 이를 통해 리포지터리를 복제해야 하는 레거시 인덱서를 대체할 것입니다. 새로운 인덱서는 리포지터리를 색인하는 데 필요한 모든 정보가 담긴 payload를 기대하며, 이를 통해 Gitaly에 연결할 때 필요한 모든 정보를 포함할 것입니다.

이 통합의 Rails 쪽은 리포지터리에 업데이트가 있을 때마다 예약된 Sidekiq 워커가 실행될 것이며, 이 워커는 간단히 Zoekt의 /indexer/index 엔드포인트를 호출할 것입니다. 이때 Gitaly에 Zoekt가 연결할 수 있도록 Gitaly 토큰을 보내야 할 것입니다.

우리는 이 새로운 인덱서를 활성화하기 전에 GitLab -> Zoekt HTTP 호출에 대한 인증 추가로 SSL로 연결을 암호화하고 기본 인증을 추가할 것입니다. 이는 GitLab에서 Gitaly 비밀을받기 때문에 중요합니다.

검색

검색은 Zoekt의 /api/search 기능을 사용하여 구현될 것입니다. 우리는 또한 이 엔드포인트를 고치기 위한 오픈 PR을 고려할 것이며, 이에 대한 해결책을 찾는 동안 이를 fork하여 작업할 수도 있습니다. GitLab은 모든 검색을 사용자의 검색 컨텍스트(그룹 또는 프로젝트)에 기반하여 적절한 필터로 시작할 것이며, Zoekt에 대해 이는 사용자가 검색하는 리포지터리에 일치하는 쿼리 문자열 정규식으로 구현될 것입니다.

Zoekt 인프라

각 Zoekt 노드에는 gitlab-zoekt-indexerzoekt-webserver가 실행되어야 합니다. 이 두 웹 서버는 서로 다른 책임을 갖고 있습니다. 실제 .zoekt 색인 파일은 빠른 검색을 위해 SSD에 저장될 것입니다. 이 웹 서버들은 동일한 파일에 액세스하기 때문에 동일한 노드에서 실행되어야 합니다. gitlab-zoekt-indexer.zoekt 색인 파일을 작성하는 것에 책임이 있고, zoekt-webserver는 이러한 .zoekt 색인 파일을 읽어 수행하는 검색에 응답하는 것에 책임이 있습니다.

전개 전략

초기에는 Zoekt 코드 검색이 gitlab-org에만 사용 가능할 것입니다. 그 이후로는 더 나은 코드 검색 경험을 요청한 특정 고객에게 전개를 시작할 것입니다. 규모 확장과 개선에 대해 배우면서 GitLab.com의 모든 라이선스 그룹에 점진적으로 전개할 것입니다. Zoekt가 색인된 그룹과 미색인 그룹을 추적하는 것에 대한 Elasticsearch와 유사한 방식을 사용할 것입니다. 새로운 테이블 zoekt_indexed_namespaces을 기반으로하며, 여기에는 namespace_id 참조가 있을 것입니다. 그룹 계층 구조의 모든 레이어를 확인하는 로직을 단순화하기 위해 최상위 네임스페이스로만 전개를 허용할 것입니다. 모든 라이선스 그룹에 전개한 후에는 자동으로 새로 라이선스를 받은 그룹을 등록하는 논리를 활성화할 것입니다. 이 테이블은 아래에서 설명하는 네임스페이스 별 샤딩 및 복제 데이터를 저장하는 장소일 수도 있습니다.

샤딩 및 복제 전략

Zoekt에는 내장된 샤딩이 없으며, GitLab의 모든 라이선스 고객에게 검색 기능을 제공하기 위해 여러 Zoekt 서버가 필요할 것으로 예상됩니다.

샤딩을 구현할 수 있는 명확한 방법이 2가지 있습니다:

  1. Zoekt 위에 또는 Zoekt 앞에 독립적인 컴포넌트로 샤딩을 구축합니다. Zoekt에 분산된 데이터베이스의 복잡성을 모두 구축하는 것은 프로젝트에 대한 좋은 방향이 아닐 가능성이 높기 때문에 이것은 아마도 독립적인 인프라 조각이 될 것입니다.
  2. GitLab 내에서 샤드를 관리합니다. 이는 GitLab 내의 응용 프로그램 계층으로, 적절한 샤드에 대해 인덱싱 및 검색 요청을 선택하는 것입니다.

마찬가지로 복제를 구현할 수 있는 몇 가지 방법이 있습니다:

  1. Zoekt 복제본이 다른 Zoekt 복제본을 인식하고 동기화를 유지하기 위해 서버 측에서
  2. 클라이언트 측 복제법으로 클라이언트가 모든 복제본에 색인 요청을 보내고 어떤 복제본에 대해서도 검색 요청을 보냅니다.

저희는 샤딩을 GitLab 응용 프로그램 내에서 구현할 계획이지만, 복제는 GitLab에서 복제본에 중복된 업데이트를 보내는 대신 Zoekt 서버의 파일 시스템 수준에서 처리하는 것이 좋을 것으로 예상합니다. 이것은 특정 디렉터리 내의 .zoekt 파일에 대한 변경 사항을 모니터링하고 이러한 업데이트를 복제본에 동기화하는 Zoekt 서버의 프로세스일 것입니다. 이는 파일이 계속 변경되고 동기화 중에 파일이 삭제될 수 있기 때문에 rsync보다 약간 더 정교하게 업데이트를 일괄적으로 동기화하는 것이 필요할 것입니다.

GitLab에서 샤딩을 구현함으로써 배포해야 할 추가적인 인프라 컴포넌트가 단순화되고 여러 고객에 대한 우리의 전개를 제어하는 더 많은 유연성을 제공합니다.

Zoekt 노드에서 기본 -> 복제본으로 동기화를 구현하는 것은 전체 리소스 사용을 최적화합니다. 우리는 복제본에서 색인 파일을 동기화하는 것만 필요하기 때문에 필요한 것만 동기화할 것입니다. 이는 다음을 절약합니다:

  1. 복제본의 디스크 공간
  2. 복제본의 CPU 사용량(색인을 다시 빌드할 필요가 없기 때문)
  3. 리포를 복제하거나 색인을 다시 빌드할 필요가 없기 때문에 Gitaly의 부하

이러한 고가용성 측면의 구현은 나중에 이행되도록 계획하고 있지만, 초기 계획에는 다음과 같습니다:

  1. GitLab에는 Zoekt 서버 풀이 구성됩니다.
  2. GitLab은 그룹에 임의로 Zoekt 기본 서버를 할당합니다.
  3. 복제본도 Zoekt 복제본이 있을 것입니다.
  4. 주기적으로 Zoekt 기본 서버는 .zoekt 색인 파일을 해당 복제본에 동기화하기 위해 동기화할 것입니다.
  5. 기본이 문제가 있다면 복제본을 기본으로 승격시키는 어떤 프로세스가 필요할 것입니다. 우리는 기본과 복제본 디렉터리을 추적하기 위해 Consul을 사용할 것입니다.
  6. 프로젝트를 색인화할 때 GitLab은 주요기에 색인을 업데이트하기 위해 Sidekiq 작업을 대기열에 넣을 것입니다.
  7. 검색 시에는 해당 그룹을 위해 Zoekt 기본 서버 또는 복제본 중 한 곳을 랜덤으로 선택할 것입니다. 우리는 “더 최신” 것이 무엇인지 신경 쓰지 않을 것이고 코드 검색이 “최종적으로 일관되게”될 것이기 때문에 모든 읽기는 약간 오래된 색인을 읽을 수 있습니다. 우리는 최대 지연시간 목표를 가지고 있으며 너무 오래된 노드는 회전에서 제외하는 것을 고려할 수 있습니다.
  8. 그룹 검색을 항상 하나의 Zoekt 서버를 검색할 수 있도록 모든 것을 최상위 그룹으로 샤딩할 것입니다. 이것이 중요한 일이라면 미래에 글로벌 검색을 위한 집계가 가능할 수도 있습니다. 작은 Self-Managed형 인스턴스는 하나의 Zoekt 서버를 사용하여 전체 검색을 작업을 해제하지 않고 gloabl 검색이 작동할 수 있게 할 수 있습니다. 가장 큰 그룹 크기 및 단일 노드 Zoekt 서버의 확장 제한에 따라 여러 샤드에 그룹을 할당할 수 있도록 구현 방식을 고려할 것입니다.

선택한 경로의 단점은 모든 이러한 Zoekt 서버를 GitLab에서 관리하는 추가 복잡성을 추가하는 것이 일부에 있습니다. 이 결정은 계속 진행 중인 작업으로 간주하고, GitLab에 너무 많은 복잡성을 추가하는 것으로 판명되면 재평가할 것입니다.

GitLab ::Zoekt::Shard 모델을 사용한 샤딩 제안

이미 ::Zoekt::IndexedNamespace로 구현되어 있으며, 네임스페이스와 샤드 간의 다대다 관계를 구현합니다.

Zoekt 노드의 자체 등록 샤딩 제안

본 제안은 대부분 GitLab Runner의 아키텍처에서 영감을 받은 것이지만, 통신은 상호 양방향입니다. 저희는 Zoekt 샤딩 및 복제의 토론을 거친 후 이것을 도달했습니다.

고려한 대안들

Zoekt 클러스터 상태를 어디에서 관리할 지에 대한 다양한 옵션을 고려해 본바 있습니다. Raft 및 Zoekt의 데이터베이스가 포함이지만, 전체 클러스터 상태를 Zoekt가 아닌 GitLab에서 관리하는 것에 대한 많은 이점이 있다고 판단하여 Zoekt 노드가 가능한 한 단순하게 유지하는 것으로 결정했습니다.

주요 이점은 다음과 같습니다:

  1. Zoekt와 비교하여 GitLab의 배포 주기가 빠르며, 많은 프로젝트에서 많은 버전 패치가 필요하지 않습니다.
  2. GitLab은 이미 우리가 이미 익숙한 많은 도구를 사용하여 상태를 관리할 수 있는 도구들과 동일한 상태를 관리하는 데 사용할 수 있는 많은 도구들이 있습니다. 여기에는 Postgres, Redis, Sidekiq 등이 포함될 것입니다.
  3. 이 프로젝트에 대부분 시간을 보내는 엔지니어들은 다른 검색 기능이 대부분 Rails 코드이기 때문에, 이 프로젝트에 대한 경험이 훨씬 더 많은 인력이 보유하고 있으며 Go 코드 작성에 소비하는 시간보다는 Rails 코드 작성에 더 많은 시간을 투자합니다.

이러한 일부 이점들은 다른 팀이 소유한 다른 프로젝트에서 볼 수 있는 것으로 보이며, 다른 프로젝트에게는 올바른 선택이 아닐 수 있습니다.

고수준 제안

  1. Zoekt 노드는 별도의 주소, 샤드 이름 및 GitLab URL 3가지 추가 인수와 함께 시작됩니다.
  2. 이로 인해 샤드 이름을 분리하여 샤드를 다른 주소로 마이그레이션할 수 있습니다.
  3. Zoekt가 k8s에서 실행 중인 경우, 주소에 대한 인수로 hostname --fqdn (예: gitlab-zoekt-1.gitlab-zoekt.default.svc.cluster.local)을 전달할 수 있을 것입니다. Zoekt를 bare-metal에서 실행하는 고객은 별도로 구성할 필요가 있을 것입니다.
  4. Zoekt는 아마도 내부 API를 사용하여 GitLab에 연결할 것입니다. 추가 트래픽 비용을 피하고 트래픽을 내부로 유지하기 위해 별도의 GitLab URL을 사용할 수도 있습니다.
  5. GitLab은 “마지막 시간” 및 샤드의 이름을 가진 조회 테이블을 유지할 것입니다 (우리는 ::Zoekt::Shard를 확장할 수 있을 것입니다). 또한 복제본과 기본 사이의 개념을 도입해야 할 것입니다.
  6. Zoekt 노드 (이 경우 인덱서)는 주기적으로 주소와 이름과 함께 구성된 GitLab URL로부터 새로운 작업을 얻기 위해 주기적인 요청을 보낼 것입니다. GitLab은 새로운 노드를 등록하거나 조회 테이블의 기존 레코드를 업데이트할 것입니다.
  7. 작업이 완료되면 zoekt-indexer는 작업이 완료되었음을 나타내기 위해 GitLab에 콜백을 보낼 것입니다.
  8. 지정된 시간 후에 GitLab이 요청을 받지 못하면 지정된 네임스페이스를 다른 샤드로 재할당하고 누락된 샤드를 사용 중지 상태로 표시할 것입니다.
  9. 검색을 실행할 때는 주요 서버와 복제본 사이를 라운드 로빈 방식으로 요청할 수 있습니다. 아마도 재시도를 구현할 수 있을 것입니다. 예를 들어, 주요 요청이 실패하면 복제본에 대한 또 다른 요청을 즉시 보내거나 그 반대의 경우가 있을 수 있습니다. 예를 들어: Zoekt 코드 검색에 대한 회로 차단기를 고려하십시오.
  10. 최초에는 효과적으로 샤드간 인덱스 파일을 이동 및 복사하는 기능을 구현할 때까지 복제를 건너뛰고 싶을 수도 있을 것입니다 (예: rsync).
  11. 리밸런싱은 아마도 상태 세트의 cron Sidekiq 워커에 발생할 것이며, 이는 색인화된 네임스페이스에 충분한 복제본 및 사용 가능한 스토리지가 있는지 고려할 것입니다.

k8s에서 실행 할 수 있는 명령 명령어의 예:

./gitlab-zoekt-indexer -index_dir=/data/index -shard_name=`hostname` -address=`hostname --fqdn`

상태 세트에 복제본을 추가할 때는 주소와 샤드 이름을 자동으로 처리할 것입니다. 예:

  • gitlab-zoekt-0 / gitlab-zoekt-0.gitlab-zoekt.default.svc.cluster.local
  • gitlab-zoekt-1 / gitlab-zoekt-1.gitlab-zoekt.default.svc.cluster.local
  • ..

인덱서가 받을 수 있는 가능한 작업:

  • index_repositories(ids: [1,2,3,4])
  • `delete_re

Consul을 사용한 복제 및 서비스 검색

만약 위에서 설명한 대로 Zoekt 노드 수준에서 복제할 계획이라면, 데이터 모델을 변경하여 zoekt_shards -> namespaces에서 one-to-many 관계를 사용해야 합니다. 이는 zoekt_indexed_namespacesnamespace_id 열을 고유하게 만드는 것을 의미합니다. 그런 다음 index_url이 항상 주요 Zoekt 노드를 가리키고, search_url은 N개의 복제본과 주요 사이에서의 DNS 레코드인 서비스 검색 접근 방식을 구현해야 합니다. 그런 다음 검색 시 search_url 레코드 중에서 임의로 선택합니다.

반복 작업

  1. gitlab-org에 사용 가능하게 만들기
  2. 모니터링 개선하기
  3. 성능 향상
  4. 일부 고객에게 사용 가능하게 만들기
  5. 샤딩 구현
  6. 복제 구현
  7. 라이선스가 있는 그룹에 대해 많이 사용 가능하게 만들기
  8. (재)샤딩된 샤드의 자동 균형 조정 구현
  9. 모든 라이선스가 있는 그룹에 롤아웃할 비용 추정하고, 더 최적화하거나 계획을 조정해야 하는지 결정하기
  10. 모든 라이선스가 있는 그룹에 롤아웃
  11. 성능 향상
  12. 비용 평가하고 모든 무료 고객에게 롤아웃할지 결정하기