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
proposed @qmnguyen0711 devops enablement 2023-05-30

Gitaly 적응형 동시성 제한

요약

Gitaly, Git 서버는 사건 발생 가능성을 줄이기 위해 클라이언트에 밀어넣어야 합니다. 과거에는 각 RPC 동시성 제한과 pack-objects 동시성 제한을 소개했습니다. 두 시스템 모두 성공적이었지만 구성은 정적이어서 심각한 결점이 있었습니다. 이 청사진은 정적 제한의 단점을 극복하기 위해 적응형 동시성 제한 시스템을 제안합니다. 이 알고리즘은 주로 가산 증가/곱셈 감소 방법을 사용하여 일반 처리 중에 제한을 점진적으로 증가시키고 사건 발생 시에 빠르게 감소시키는 방식으로 작동합니다. 이 알고리즘은 Gitaly가 리소스 부족과 심각한 대기 시간 저하를 판별하는 기준에 중점을 둡니다.

동기

사건 발생 가능성을 줄이고 자체를 보호하기 위해 Gitaly는 어떤 제한이 도달했음을 결정하면 클라이언트에 밀어넣어야 합니다. 이전 시도에서 우리는 백프레셔의 기초를 마련하고 각 RPC 동시성 제한과 pack-objects 동시성 제한을 소개했습니다.

각 RPC 동시성 제한을 사용하면 동시에 처리할 수 있는 최대 요청량을 설정할 수 있습니다. 이는 RPC 및 리포지터리에 따라 제한을 설정합니다. pack-objects 동시성 제한은 IP별로 연속된 Git 데이터 전송 요청을 제한합니다. 한 가지 언급할 점은 pack-objects 동시성 제한이 캐시 적중(cache misses)의 경우에만 적용됩니다. 이 제한을 초과하는 경우 요청은 대기열에 들어가거나 대기열이 가득 찬 경우 거부됩니다. 요청이 대기열에 너무 오래 남아 있는 경우 또한 거부됩니다.

두 시스템 모두 GitLab.com에서 약속한 결과를 냈지만 특히 동시성 제한의 값과 같은 정적 구성에는 일부 결점이 있습니다:

  • 동시성 제한의 합리적인 값 유지가 번거롭습니다. 이 운영 구성을 살펴보면 각 제한이 다른 소스의 단서에 따라 심각하게 보정되었습니다. 전반적인 상황이 변화하면 다시 조정해야 합니다.
  • 정적 제한은 모든 사용 패턴에 적합하지 않습니다. 만능 값을 선택하는 것은 현실적이지 않습니다. 제한이 너무 낮으면 대형 사용자에게 영향을 미칠 것입니다. 값이 너무 넉넉하면 보호 효과가 상실됩니다.
  • 요청이 서버가 유휴 상태일지라도 거부될 수 있습니다. 외래율은 서버에 가중된 부하의 지표가 아니기 때문입니다.

이러한 모든 결점을 극복하면서도 동시성 제한의 이점을 유지하기 위한 유망한 솔루션 중 하나는 현재 사용 가능한 노드의 처리 용량에 적응할 수 있도록 동시성 제한을 만드는 것입니다. 이에 제안된 새로운 모드를 “적응형 동시성 제한”이라고 합니다.

목표

  • Gitaly의 신뢰성과 탄력성을 향상시키기 위해 Gitaly가 과부하 상태일 때 트래픽을 밀어낼 수 있도록 만듭니다.
  • Gitaly 포화 사건의 발생 빈도를 최소화합니다.
  • 클라이언트가 동시성 제한에 부정확하게 도달하는 가능성을 줄여서 ResourceExhausted 오류율을 감소시킵니다.
  • 동시성 제한의 원활한 또는 완전 자동 보정을 촉진합니다.

비목표

  • 사용자 또는 관리자의 시스템의 작업량 또는 복잡성을 늘릴 것이 아닙니다. 여기에서 제안된 적응성은 정반대를 목표로 합니다.

제안

제안된 적응형 동시성 제한 알고리즘은 주로 가산 증가/곱셈 감소(AIMD) 방법을 사용합니다. 이 방법은 일반적 처리 중에 제한을 점진적으로 증가시키고 사건(백오프 이벤트) 발생 시에 빠르게 줄이는 방식으로 작동합니다. Gitaly의 특정 제약 사항 및 동작 특징, cgroup 활용 및 업로드 팩 RPC 등을 고려하여 선택된 접근 방식입니다.

이 제안된 솔루션은 RPC 동시성pack objects 동시성에 대한 기존 제한을 대체하려는 것이 아니라 매개변수를 자동으로 조정합니다. 이는 대기열, 대기열 시간 초과, 대기열 길이, 분할 및 범위와 같은 다른 측면은 변경하지 않습니다. 제안된 솔루션은 현재 동시성 제한의 에만 중점을 둡니다.

설계 및 구현 세부 정보

AIMD 알고리즘

적응형 동시성 제한 알고리즘은 주로 가산 증가/곱셈 감소(AIMD) 방법을 사용합니다. 이 방법은 일반적 처리 중에 제한을 점진적으로 증가시키고 사건 발생 시에 빠르게 줄이는 방식으로 작동합니다.

초기화 중에 다음 매개변수를 구성합니다:

  • initialLimit: 시작할 동시성 제한. 이 값은 본질적으로 현재 정적 동시성 제한과 동일합니다.
  • maxLimit: 최대 동시성 제한.
  • minLimit: 프로세스가 기능으로 간주되기 위한 최소 동시성 제한. 0이면 모든 다가오는 요청을 거부합니다.
  • backoffFactor: 백오프 이벤트가 발생할 때 제한이 얼마나 빨리 감소하는가(0 < backoff < 1, 기본값은 0.75)

Gitaly 프로세스가 시작되면 limit = initialLimit를 설정하는데, 여기서 limit는 동시에 처리할 수 있는 최대 요청입니다.

정기적으로(15초마다 한 번씩) limit 값이 재보정됩니다:

  • 최근 보정 이후에 백오프 이벤트가 없는 경우 limit = limit + 1로 설정됩니다. 새 제한은 maxLimit를 초과할 수 없습니다.
  • 그렇지 않으면 limit = limit * backoffFactor로 설정됩니다. 새 제한은 minLimit보다 낮을 수 없습니다.

프로세스가 더 이상 요청을 처리할 수 없거나 곧 처리하지 못할 때를 백오프 이벤트로 지칭합니다. 이상적으로 효율적인 상태를 최대한 오래 유지하길 원합니다. 이것은 Gitaly가 최대 처리 능력에 도달했을 때의 상태입니다.

요청 처리 방법

동시성 제한은 한 번에 처리되는 요청의 총 개수를 제한합니다.

  • IFR < 제한 일 때, Gitaly는 새로운 요청을 기다리지 않고 즉시 처리합니다. 증가한 후에, 대기 중인 요청이 있다면 Gitaly는 이후의 요청을 즉시 처리합니다.
  • IFR = 제한 일 때, 제한에 도달합니다. 이후의 요청은 대기열에 들어가고 차례를 기다립니다. 대기열 길이가 구성된 제한에 도달하면, Gitaly는 새로운 요청을 즉시 거부합니다. 요청이 대기열에 오랫동안 머물 경우, Gitaly에의해 자동으로 삭제됩니다.
  • IRF > 제한 일 때, 이는 적합히 backoff 이벤트의 결과입니다. 이것은 Gitaly가 새롭게 지정된 제한보다 더 많은 요청을 처리한다는 것을 의미합니다. 위의 경우와 유사하게 다가오는 요청을 대기열에 넣는 것 외에도, 이 상황이 오랫동안 해결되지 않으면 Gitaly는 더 이상 현 상황을 처리하지 않도록 in-flight 요청들을 load shedding 할 수 있습니다.

여러 번 우리는 대기열 의미론을 변경할 것인지에 대해 토의한 적이 있습니다. 지금은 대기열(FIFO)의 처음에서 대기 중인 프로세스를 허용하고 있지만, 여러 차례 제안된 것처럼 대기열의 뒷부분(LIFO)에서 프로세스를 허용하는 것이 더 바람직할 수도 있습니다.

얼마든지 거부 이유에 상관없이 클라이언트는 나중에 백오프와 다시 시도하라는 신호로 ResourceExhausted 응답 코드를 받습니다. 대부분 Gitaly의 직접적인 클라이언트들은 내부적이므로(GitLab Shell과 Workhorse 등), 사용자는 사용자 친화적인 메시지를 받게 됩니다. Gitaly는 내부 클라이언트에게 백오프를 유도하기 위해 지수적 백프로 헤더를 첨부할 수 있습니다. 그러나 그것은 다소 무자비하며 예상하지 못한 결과로 이어질 수 있습니다. 나중에 고려할 수 있습니다.

백오프 이벤트

각 시스템은 고유한 신호 집합을 가지고 있는데, Gitaly의 경우에는 고려해야 할 두 가지 측면이 있습니다.

  • 자원의 부족, 특히 메모리와 CPU, 이는 git-pack-objects(1)와 같은 Git 프로세스를 처리하는 데 필수적입니다. 이러한 자원이 제한되거나 고갈되면 Gitaly가 더 많은 요청을 받는 것은 의미가 없습니다. 이럴 경우, Gitaly는 가장 효율적인 cgroup을 적용하여 이 문제를 해결합니다. 아래 섹션에서 cgroup을 사용하여 회계를 수행하는 방법에 대해 기술되어 있습니다.
  • 심각한 지연 감지. Gitaly는 git 데이터를 제공하기 위한 것 외에도 다양한 용도를 위한 여러 RPC를 제공합니다. 전체적인 지연이 심하게 나타나면 Gitaly는 더 이상의 요청을 받지 않아야 합니다. 아래 섹션에서 합당하게 지연을 감지하는 방법에 대해 설명합니다.

위의 신호 외에도 나중에 시스템을 더 똑똑하게 만들기 위해 추가적인 신호를 고려할 수 있습니다. 예를 들어 Go 가비지 컬렉터 통계, 네트워킹 통계, 파일 디스크립터 등이 있습니다. 몇몇 기업은 CPU 포화를 추정하기 위해 시간 드리프팅을 사용하는 것과 같이 똑똑한 요령들이 있습니다.

Upload Pack RPCs의 백오프 이벤트

Upload Pack RPCs와 해당 쌍둥이인 PackObjects RPC는 Gitaly에만 해당됩니다. 이것들은 가장 무거운 작업인데, 많은 양의 Git 데이터를 전송합니다. 각 작업은 분 또는 심지어 시간이 걸릴 수 있습니다. 각 작업의 시간은 여러 가지 요소에 따라 달라지며, 요청된 객체의 수 및 클라이언트의 인터넷 속도가 가장 중요합니다.

따라서 지연은 백오프 이벤트를 결정하는 데 부적절한 신호입니다. 이러한 유형의 RPC는 현재 단계에서는 리소스 회계에만 종속되어야 합니다.

다른 RPC의 백오프 이벤트

위에서 언급했듯이, Gitaly는 다양한 용도로 여러 RPC를 제공합니다. 허용 가능한 지연 및 지연 감지를 인식하는 시기에 따라 다양할 수 있습니다. 다행히도 현재의 RPC 동시성 제한 구현은 RPC 및 개별 리포지터리 별로 구성을 범위로 한정합니다. 이러한 설정에서 지연 신호는 의미가 있습니다.

지연 외에도 자원 사용 또한 중요한 역할을 합니다. 따라서 다른 RPC들은 지연 메트릭 및 리소스 회계 신호를 사용해야 합니다.

cgroup을 사용한 리소스 회계

포화 문제는 보통 Gitaly 자체보다는 대부분의 작업을 처리하는 생성된 Git 프로세스들에 의해 일으키는 경우가 많습니다. 이러한 프로세스들은 cgroup 내에 포함되어 있으며, cgroup에 대한 버킷팅 알고리즘은 여기에서 찾을 수 있습니다. 일반적으로 Gitaly는 대상 리포지터리에 따라 요청에 적합한 cgroup을 선택합니다. 또한, 모든 리포지터리 레벨 cgroup이 속하는 상위 cgroup도 있습니다.

Cgroup 통계 정보는 널리 접근 가능합니다. Gitaly는 cgroup 제어 파일 내의 다음 정보를 통해 리소스 용량과 현재 리소스 사용량을 쉽게 가져올 수 있습니다:

  • memory.limit_in_bytes
  • memory.usage_in_bytes
  • cpu.cfs_period_us
  • cpu.cfs_quota_us
  • cpuacct.usage

이러한 통계 데이터를 가져오는 것은 약간의 오버헤드를 의미할 수 있습니다. 이를 실시간으로 유지할 필요는 없습니다. 따라서 제한 조정 주기에서 주기적으로 처리할 수 있습니다.

과거에는 cgroup가 생성된 프로세스가 제한을 초과하지 못하게 막는 데 믿을 수 있는 방법이었습니다. 일반적으로 cgroup가 생성한 제한을 신뢰하고 방해 없이 프로세스가 실행되는 것은 안전합니다. 그러나 cgroup가 설정한 제한(100%)에 도달하면 과부화가 발생할 수 있습니다. 이는 페이지 폴트 증가, 시스템 호출 지연, 메모리 할당 문제, 심지어 메모리 부족으로 이어질 수 있습니다. 이러한 사건의 결과는 이 예시에서 강조되어 있습니다. Inflight 요청이 크게 영향을 받아 수용할 수 없는 지연, 타임아웃 및 심지어 취소로 이어집니다.

또한, 과거의 여러 관측을 통해 git-pack-objects(1)와 같은 일부 Git 프로세스는 시간이 지남에 따라 메모리가 증가하는 경향이 있습니다. git-pull(1)의 여러 요청이 오면, 노드가 다양한 메모리를 필요로 하는 프로세스로 가득 차는 일이 쉽게 발생할 수 있습니다. 이러한 누적을 미리 막는 것이 훨씬 나을 것입니다.

그 결과, Gitaly는 메모리 용량의 75%와 CPU 용량의 90%만 사용하도록 하는 소프트 제한 집합을 사용하여 과부하를 피합니다. 이 전략은 노드가 잠재적인 과부화 사건을 처리할 머리막이 충분하도록 보장합니다.

이론적으로 cgroup 계층구조는 우리에게 개별적으로 과부하 상태를 결정할 수 있는 기회를 제공합니다. 따라서 Gitaly는 각 리포지터리에 대해 독립적으로 동시성 제한을 조정할 수 있습니다. 그러나 실제 사용에서 이런 접근은 불필요하게 복잡할 수 있습니다. 반면에, 나중에 운영자들이 혼란스러워 할 수 있습니다.

좋은 시작으로 Gitaly는 다음 중 어떠한 조건에서도 과부하 이벤트를 인식합니다:

  • 상위 cgroup의 소프트 제한이 도달되었을 때.
  • 어떠한 리포지터리 cgroup의 소프트 제한이 도달되었을 때.

두 번째 조건이 자리에 있는 것은 논리적입니다. 왜냐하면 리포지터리 cgroup의 용량 제한이 상위 cgroup의 용량에 상당한 영향을 미칠 수 있기 때문입니다. 이는 리포지터리 cgroup가 제한에 도달할 때, 다른 cgroup에게는 덜 자원을 사용할 수 있게 됩니다. 그 결과, 동시성 제한을 감소시킴으로써 과부하 발생 시기를 지연시킵니다.

지연 메트릭

동시성 한도를 재보정할 때, 업로드 팩 외의 RPC에 대한 지연 시간이 고려됩니다. 지연을 메트릭할 때 고려해야 할 두 가지 사항:

  • 지연 시간을 기록하는 방법
  • 지연 시간의 저하를 인식하는 방법

제대로된 gRPC 서버인 Gitaly와 같은 서버는 노드당 초당 많은 요청을 처리할 수 있습니다. 프로덕션 서버는 초당 수천 개의 요청을 처리할 수 있습니다. 반응 시간을 정확하게 추적하고 저장하는 것은 현실적으로 실용적이지 않습니다.

지연 시간의 저하 여부를 결정하는 휴리스틱은 흥미롭습니다. 가장 순진한 해결책은 정적 지연 임계값을 미리 정의하는 것입니다. 각 RPC마다 다른 임계값이 있을 수 있습니다. 불행하게도, 정적 동시성 제한과 유사하게, 합리적인 최신 값을 선택하는 것은 어려우며 지루할 수 있습니다.

다행히도, TCP 혼잡 제어 분야에서 적용되는 몇 가지 유명한 알고리즘이 있습니다:

이 두 알고리즘은 사전에 정의된 구성 없이도 지연 임계값을 자동으로 결정할 수 있습니다. 실제 시나리오에서 매우 효율적이고 통계적으로 신뢰할 수 있습니다. 내 의견으로는 두 알고리즘이 우리의 특정 사용 사례에 동등하게 적합합니다.

부하 분산

Gitaly가 과부화 상황에 갇힌 경우 두 가지 표시로 나타날 수 있습니다:

  • 일정량의 연이은 백오프 이벤트
  • 특정 수의 인-플라이트 요청이 동시성 제한을 초과

이러한 경우, 특정 cgroup 또는 전체 Gitaly 노드가 일시적으로 사용할 수 없게 될 수 있습니다. 인-플라이트 요청은 취소되거나 시간 초과될 가능성이 높습니다. GitLab.com 프로덕션에서 사고가 트리거되어 인간의 개입이 필요합니다. 우리는 이러한 상황을 부하 분산으로 개선할 수 있습니다.

이 메커니즘은 일부 인-플라이트 요청을 일부러 셀렉티브하게 제거하기 시작합니다. 주된 목적은 모든 인-플라이트 요청의 침투적인 실패를 방지하는 것입니다. 희망컨대, 그 중 몇 개가 제거된 후에 cgroup/노드가 인간의 개입 없이 빠르게 정상 상태로 복구될 수 있습니다. 결과적으로 이는 순수 가용성 및 내구성 향상으로 이어집니다.

어떤 요청을 취소할지 선택하는 것은 까다롭습니다. 많은 시스템에서 요청 중요도가 고려됩니다. 하류(from downstream)에서의 요청이 중요도 점을 할당받습니다. 점수가 낮은 요청이 먼저 대상이 됩니다. 불행하게도, GitLab에는 이와 유사한 시스템이 없습니다. 긴급성 시스템이 있지만 중요도보다는 응답 시간 약속에 사용됩니다.

대신, 시스템을 해치는 요청을 우선 처리할 수 있습니다. 고려해야 할 몇 가지 기준:

  • 상당한 메모리 소비 요청
  • 서버를 시간당 상당한 CPU를 사용하는 요청
  • 느린 클라이언트
  • 최근 교통을 지배하는 IP에서의 요청
  • 대기 중인 요청/초기 단계의 요청. 거의 완료된 요청을 거절하고 싶지는 않습니다.

시작할 때는 먼저 두 기준을 선택할 수 있습니다. 나중에 프로덕션에서 배우면 디렉터리을 강화할 수 있습니다.

참고 자료