Concurrency limiting
GitLab v19.5요약
Gitaly를 실행하는 서버에 과부하가 걸리지 않도록 다음 항목의 동시성을 제한할 수 있습니다: 이러한 제한은 고정값으로 두거나 적응형(adaptive)으로 설정할 수 있습니다. 환경에 제한을 적용할 때는 신중해야 하며, 예기치 않은 트래픽에 대비하는 등 일부 상황에서만 적용합니다.
Gitaly를 실행하는 서버에 과부하가 걸리지 않도록 다음 항목의 동시성을 제한할 수 있습니다:
- RPC.
- 팩 오브젝트(pack-objects).
이러한 제한은 고정값으로 두거나 적응형(adaptive)으로 설정할 수 있습니다.
환경에 제한을 적용할 때는 신중해야 하며, 예기치 않은 트래픽에 대비하는 등 일부 상황에서만 적용합니다. 제한에 도달하면 연결이 끊어져 사용자에게 부정적인 영향을 줍니다. 일관되고 안정적인 성능을 위해서는 노드 사양 조정, 대규모 리포지터리 검토, 워크로드 점검 등 다른 방안을 먼저 살펴봅니다.
RPC 동시성 제한#
리포지터리를 클론하거나 풀할 때 백그라운드에서 여러 RPC가 실행됩니다. 그중 Git 팩 RPC는 다음과 같습니다:
SSHUploadPackWithSidechannel(Git SSH 용).PostUploadPackWithSidechannel(Git HTTP 용).
이러한 RPC는 많은 리소스를 소비하므로 다음과 같은 상황에서 상당한 영향을 줄 수 있습니다:
- 예기치 않게 트래픽이 급증한 경우.
- 권장 사례를 따르지 않는 대규모 리포지터리를 대상으로 실행하는 경우.
이러한 상황에서는 Gitaly 구성 파일의 동시성 제한을 사용해 이 프로세스가 Gitaly 서버에 과부하를 일으키지 않도록 제한할 수 있습니다. 예를 들면 다음과 같습니다:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
concurrency: [
{
rpc: '/gitaly.SmartHTTPService/PostUploadPackWithSidechannel',
max_per_repo: 20,
max_queue_wait: '1s',
max_queue_size: 10,
},
{
rpc: '/gitaly.SSHService/SSHUploadPackWithSidechannel',
max_per_repo: 20,
max_queue_wait: '1s',
max_queue_size: 10,
},
],
}
rpc는 리포지터리별 동시성 제한을 설정할 RPC의 이름입니다.max_per_repo는 리포지터리별로 해당 RPC에서 동시에 처리 중인 RPC 호출의 최대 개수입니다.max_queue_wait은 요청이 Gitaly에 의해 처리되기까지 동시성 큐에서 대기할 수 있는 최대 시간입니다.max_queue_size는 Gitaly가 요청을 거부하기 전까지 동시성 큐(RPC 메서드별)가 커질 수 있는 최대 크기입니다.
이 설정은 해당 RPC에서 동시에 처리 중인 RPC 호출 수를 제한합니다. 제한은 리포지터리별로 적용됩니다. 위 예시에서는 다음과 같이 동작합니다:
- Gitaly 서버가 제공하는 각 리포지터리는
PostUploadPackWithSidechannel과SSHUploadPackWithSidechannelRPC 호출을 최대 20 개까지 동시에 처리할 수 있습니다. - 20 개 슬롯을 모두 사용한 리포지터리에 다른 요청이 들어오면 그 요청은 큐에 들어갑니다.
- 요청이 큐에서 1 초를 초과해 대기하면 오류와 함께 거부됩니다.
- 큐가 10을 초과해 늘어나면 이후 요청은 오류와 함께 거부됩니다.
이러한 제한에 도달하면 사용자의 연결이 끊어집니다.
Gitaly 로그와 Prometheus로 이 큐의 동작을 관찰할 수 있습니다. 자세한 내용은 관련 문서를 참고합니다.
인증되지 않은 요청에 대한 별도 제한#
히스토리
기본적으로 RPC 동시성 제한은 인증 상태와 무관하게 모든 요청에 적용됩니다. 다만 익명 트래픽으로 인한 오용이나 리소스 고갈로부터 Gitaly 서버를 보호하기 위해 인증되지 않은 요청에는 더 엄격한 별도 제한을 구성할 수 있습니다.
RPC에 unauthenticated 필드를 구성하면 Gitaly는 다음과 같이 별도의
제한기를 사용합니다:
- 인증된 요청은 주 동시성 제한(RPC 구성의 최상위 수준에 설정한 값)을 사용합니다.
- 인증되지 않은 요청은
unauthenticated필드에 지정한 제한을 사용합니다.
이렇게 분리하면 다음을 할 수 있습니다:
- 인증된 사용자에게는 더 높은 처리량을 유지하면서 인증되지 않은 트래픽에는 더 엄격한 제한을 적용합니다.
- 익명 클론이나 풀로 인한 서비스 거부 상황을 방지합니다.
- 인증된 사용자가 Gitaly 리소스에 우선적으로 접근하도록 보장합니다.
unauthenticated 필드를 구성하지 않으면 인증된 요청과 인증되지 않은 요청을 포함한
모든 요청이 동일한 동시성 제한을 공유합니다.
별도의 인증되지 않은 요청 제한을 사용하는 시점#
다음과 같은 경우 별도의 인증되지 않은 요청 제한 구성을 고려합니다:
- GitLab 인스턴스가 공개 리포지터리 접근을 허용하고 익명 트래픽이 많습니다.
- 부하가 높은 기간에 인증된 사용자를 우선하려고 합니다.
- 인증되지 않은 출처의 오용 가능성에 대비해야 합니다.
- 인증된 요청과 인증되지 않은 요청 사이에 리소스 경합이 관찰됩니다.
인증되지 않은 요청에 대한 정적 제한 구성#
다음 예시는 인증된 요청과 인증되지 않은 요청에 대해 별도의 정적 제한을 구성하는 방법을 보여 줍니다:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
concurrency: [
{
rpc: '/gitaly.SmartHTTPService/PostUploadPackWithSidechannel',
# Limits for authenticated requests
max_per_repo: 20,
max_queue_wait: '1s',
max_queue_size: 10,
# Separate limits for unauthenticated requests
unauthenticated: {
max_per_repo: 5,
max_queue_wait: '500ms',
max_queue_size: 5,
},
},
],
}
이 예시에서는 다음과 같이 동작합니다:
- 인증된 요청은 리포지터리별로 최대 20 개의 작업을 동시에 수행할 수 있습니다.
- 인증되지 않은 요청은 리포지터리별로 동시 작업 5 개로 제한됩니다.
- 인증되지 않은 요청은 큐 대기 시간이 더 짧고(500ms 대 1s) 큐 크기도 더 작습니다(5 대 10).
인증되지 않은 요청에 대한 적응형 제한 구성#
unauthenticated 필드는 주 구성과 마찬가지로 정적 동시성 제한과 적응형 동시성
제한을 모두 지원합니다. 인증되지 않은 요청에 적응형 제한을
구성할 수 있습니다:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
concurrency: [
{
rpc: '/gitaly.SmartHTTPService/PostUploadPackWithSidechannel',
# Adaptive limits for authenticated requests
adaptive: true,
min_limit: 10,
initial_limit: 20,
max_limit: 40,
max_queue_wait: '1s',
max_queue_size: 10,
# Adaptive limits for unauthenticated requests
unauthenticated: {
adaptive: true,
min_limit: 2,
initial_limit: 5,
max_limit: 10,
max_queue_wait: '500ms',
max_queue_size: 5,
},
},
],
}
이 구성에서는 두 트래픽 유형의 분리를 유지하면서, 인증된 요청과 인증되지 않은 요청의 제한이 시스템 리소스 사용량에 따라 각각 독립적으로 조정됩니다.
pack-objects 동시성 제한#
Gitaly는 리포지터리를 클론하거나 풀하는 SSH 트래픽과 HTTPS 트래픽을 모두 처리할 때 git-pack-objects 프로세스를 실행합니다. 이 프로세스는 pack-file을 생성하며
특히 예기치 않은 트래픽 급증이나 대규모 리포지터리에서의 동시 풀과 같은 상황에서 상당한 리소스를 소비합니다. GitLab.com에서는 인터넷 연결이 느린 클라이언트에서
문제가 발생하는 사례도 확인됩니다.
Gitaly 구성 파일에 pack-objects 동시성 제한을 설정하면 이 프로세스가 Gitaly 서버에 과부하를 일으키지 않도록 제한할 수 있습니다. 이 설정은 원격 IP 주소별로 동시에 처리 중인 pack-object 프로세스 수를 제한합니다.
이러한 제한은 예기치 않은 트래픽에 대비하는 등 일부 상황에서만 신중하게 활성화합니다. 제한에 도달하면 이 설정은 사용자의 연결을 끊습니다. 일관되고 안정적인 성능을 위해서는 노드 사양 조정, 대규모 리포지터리 검토, 워크로드 점검 등 다른 방안을 먼저 살펴봅니다.
구성 예시:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
pack_objects_limiting: {
max_concurrency: 15,
max_queue_length: 200,
max_queue_wait: '60s',
},
}
max_concurrency는 키별로 동시에 처리 중인 pack-object 프로세스의 최대 개수입니다.max_queue_length는 Gitaly가 요청을 거부하기 전까지 동시성 큐(키별)가 커질 수 있는 최대 크기입니다.max_queue_wait은 요청이 Gitaly에 의해 처리되기까지 동시성 큐에서 대기할 수 있는 최대 시간입니다.
위 예시에서는 다음과 같이 동작합니다:
- 각 원격 IP는 Gitaly 노드에서 최대 15 개의 pack-object 프로세스를 동시에 처리할 수 있습니다.
- 15 개 슬롯을 모두 사용한 IP에서 다른 요청이 들어오면 그 요청은 큐에 들어갑니다.
- 요청이 큐에서 1 분을 초과해 대기하면 오류와 함께 거부됩니다.
- 큐가 200을 초과해 늘어나면 이후 요청은 오류와 함께 거부됩니다.
pack-object 캐시가 활성화된 경우 pack-objects 제한은 캐시가 적중하지 않을 때만 동작합니다. 자세한 내용은 Pack-objects 캐시를 참고합니다.
Gitaly 로그와 Prometheus로 이 큐의 동작을 관찰할 수 있습니다. 자세한 내용은 Gitaly pack-objects 동시성 제한 모니터링을 참고합니다.
동시성 제한 보정#
동시성 제한을 설정할 때는 각자의 워크로드 패턴에 맞는 값을 선택합니다. 이 절에서는 이러한 제한을 효과적으로 보정하는 방법을 안내합니다.
보정에 Prometheus 메트릭과 로그 활용#
Prometheus 메트릭은 사용 패턴과 각 RPC 유형이 Gitaly 노드 리소스에 미치는 영향을 정량적으로 보여 줍니다. 이 분석에는 다음 주요 메트릭이 특히 유용합니다:
- RPC 별 리소스 소비 메트릭입니다. Gitaly는 대부분의 무거운 작업을
git프로세스로 넘기므로, 셸로 실행되는 명령은 보통 Git 바이너리입니다. Gitaly는 이 명령에서 수집한 메트릭을 로그와 Prometheus 메트릭으로 노출합니다.gitaly_command_cpu_seconds_total- 셸 실행에 소비된 CPU 시간의 합계이며,grpc_service·grpc_method·cmd·subcmd레이블을 가집니다.gitaly_command_real_seconds_total- 셸 실행에 소비된 실제 시간의 합계이며, 비슷한 레이블을 가집니다.
- RPC 별 최근 제한 메트릭입니다:
gitaly_concurrency_limiting_in_progress- 동시에 처리 중인 요청 수입니다.gitaly_concurrency_limiting_queued- 특정 리포지터리의 RPC 요청 중 대기 상태인 요청 수입니다.gitaly_concurrency_limiting_acquiring_seconds- 요청이 처리되기 전에 동시성 제한 때문에 대기한 시간입니다.
이러한 메트릭은 특정 시점의 리소스 사용률을 개괄적으로 보여 줍니다. 그중 gitaly_command_cpu_seconds_total 메트릭은 CPU 리소스를 많이 소비하는 특정 RPC를
식별하는 데 효과적입니다. 더 자세한 분석에 사용할 수 있는 추가 메트릭은
Gitaly 모니터링에서 설명합니다.
메트릭은 전체 리소스 사용 패턴을 파악하는 데 쓰이지만, 리포지터리별 세부 내역은 제공하지 않는 것이 보통입니다. 따라서 로그가 보완 데이터로 쓰입니다. 로그는 다음과 같이 분석합니다:
- 영향이 큰 것으로 확인된 RPC로 로그를 필터링합니다.
- 필터링한 로그를 리포지터리 또는 프로젝트별로 집계합니다.
- 집계 결과를 시계열 그래프로 시각화합니다.
메트릭과 로그를 함께 사용하면 시스템 전체의 리소스 사용량과 리포지터리별 패턴을 모두 파악할 수 있습니다. Kibana와 같은 분석 도구나 유사한 로그 집계 플랫폼을 사용하면 이 과정이 수월해집니다.
제한 조정#
처음 설정한 제한이 충분히 효율적이지 않다면 조정이 필요할 수 있습니다. 적응형 제한을 사용하면 시스템이 리소스 사용량에 따라 자동으로 조정하므로 정확한 제한값의 중요도가 낮아집니다.
동시성 제한은 리포지터리 단위로 적용됩니다. 제한값 30은 리포지터리별로 동시에 처리 중인 요청을 최대 30 개까지 허용한다는 뜻입니다. 제한에 도달하면 요청은 큐에 들어가고, 큐가 가득 찼거나 최대 대기 시간에 도달한 경우에만 거부됩니다.
적응형 동시성 제한#
Gitaly는 두 가지 동시성 제한을 지원합니다:
- RPC 동시성 제한은 각 Gitaly RPC에서 동시에 처리 중인 요청의 최대 개수를 구성합니다. 제한은 RPC와 리포지터리 단위로 적용됩니다.
- Pack-objects 동시성 제한은 IP 별로 동시에 처리하는 Git 데이터 전송 요청 수를 제한합니다.
이 제한을 초과하면 다음 중 하나가 적용됩니다:
- 요청이 큐에 들어갑니다.
- 큐가 가득 찼거나 요청이 큐에 너무 오래 머무르면 요청이 거부됩니다.
두 동시성 제한은 모두 정적으로 구성할 수 있습니다. 정적 제한도 보호 효과를 낼 수 있지만 다음과 같은 단점이 있습니다:
- 정적 제한은 모든 사용 패턴에 맞지 않습니다. 모든 상황에 통하는 값은 없습니다. 제한이 너무 낮으면 대규모 리포지터리가 영향을 받고, 제한이 너무 높으면 보호 효과가 사실상 사라집니다.
- 특히 리포지터리별 워크로드가 시간이 지나면서 변하는 환경에서는 동시성 제한을 적정 값으로 유지하는 작업이 번거롭습니다.
- 제한값이 서버 부하를 반영하지 않기 때문에 서버가 유휴 상태여도 요청이 거부될 수 있습니다.
적응형 동시성 제한을 구성하면 이러한 단점을 모두 해소하면서 동시성 제한의 이점을 유지할 수 있습니다. 적응형 동시성 제한은 선택 사항이며 위 두 가지 동시성 제한 유형을 기반으로 동작합니다. 여기에는 Additive Increase/Multiplicative Decrease(AIMD) 알고리즘이 사용됩니다. 각 적응형 제한은 다음과 같이 동작합니다:
- 프로세스가 정상적으로 동작하는 동안 특정 상한까지 점진적으로 증가합니다.
- 호스트 머신에 리소스 문제가 발생하면 빠르게 감소합니다.
이 방식은 머신이 "숨 쉴" 여유를 확보하고 현재 처리 중인 요청의 속도를 높입니다.

적응형 제한기는 30 초마다 제한을 보정하며 다음과 같이 동작합니다:
- 상한에 도달할 때까지 제한을 1 씩 늘립니다.
- 최상위 cgroup에서 쉽게 회수할 수 있는 페이지 캐시를 제외한 메모리 사용량이 90% 를 초과하거나, 관찰 시간의 50% 이상 CPU가 스로틀링된 경우 제한을 절반으로 줄입니다.
그 외의 경우에는 상한에 도달할 때까지 제한이 1 씩 증가합니다.
적응형 제한은 RPC 또는 pack-objects 캐시별로 각각 활성화합니다. 다만 제한 보정은 동시에 이루어집니다. 적응형 제한에는 다음 구성 항목이 있습니다:
adaptive는 적응형 동작의 활성화 여부를 설정합니다.max_limit은 최대 동시성 제한입니다. Gitaly는 현재 제한을 이 값에 도달할 때까지 늘립니다. 일반적인 조건에서 시스템이 충분히 감당할 수 있는 넉넉한 값으로 설정합니다.min_limit은 구성한 RPC의 최소 동시성 제한입니다. 호스트 머신에 리소스 문제가 발생하면 Gitaly는 이 값에 도달할 때까지 제한을 빠르게 줄입니다.min_limit을 0으로 설정하면 처리가 완전히 중단될 수 있으므로 일반적으로 권장되지 않습니다.initial_limit은 이 두 극단값 사이의 적절한 시작점을 제공합니다.
RPC 동시성에 적응형 동작 활성화#
사전 요구 사항:
- 적응형 제한은 컨트롤 그룹에 의존하므로, 적응형 제한을 사용하기 전에 컨트롤 그룹을 활성화해야 합니다.
다음은 RPC 동시성에 적응형 제한을 구성하는 예시입니다:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
cgroups: {
# Minimum required configuration to enable cgroups support.
repositories: {
count: 1
},
},
concurrency: [
{
rpc: '/gitaly.SmartHTTPService/PostUploadPackWithSidechannel',
max_queue_wait: '1s',
max_queue_size: 10,
adaptive: true,
min_limit: 10,
initial_limit: 20,
max_limit: 40
},
{
rpc: '/gitaly.SSHService/SSHUploadPackWithSidechannel',
max_queue_wait: '10s',
max_queue_size: 20,
adaptive: true,
min_limit: 10,
initial_limit: 50,
max_limit: 100
},
],
}
자세한 내용은 RPC 동시성을 참고합니다.
pack-objects 동시성에 적응형 동작 활성화#
사전 요구 사항:
- 적응형 제한은 컨트롤 그룹에 의존하므로, 적응형 제한을 사용하기 전에 컨트롤 그룹을 활성화해야 합니다.
다음은 pack-objects 동시성에 적응형 제한을 구성하는 예시입니다:
# in /etc/gitlab/gitlab.rb
gitaly['configuration'] = {
# ...
pack_objects_limiting: {
max_queue_length: 200,
max_queue_wait: '60s',
adaptive: true,
min_limit: 10,
initial_limit: 20,
max_limit: 40,
},
}
자세한 내용은 pack-objects 동시성을 참고합니다.
적응형 동시성 제한 보정#
적응형 동시성 제한은 GitLab 이 Gitaly 리소스를 보호하는 기존 방식과 크게 다릅니다. 지나치게 제한적이거나 지나치게 느슨할 수 있는 정적 임계값에 의존하지 않고, 적응형 제한은 실제 리소스 상태에 실시간으로 지능적으로 대응합니다.
이 방식에서는 동시성 제한 보정에서 설명한 것처럼 광범위한 보정을 거쳐 "완벽한" 임계값을 찾을 필요가 없습니다. 장애 상황에서 적응형 제한기는 제한을 지수적으로 줄이고(예: 60 → 30 → 15 → 10), 시스템이 안정되면 제한을 점진적으로 올려 자동으로 복구합니다.
적응형 제한을 보정할 때는 정확성보다 유연성을 우선할 수 있습니다.
RPC 분류 및 구성 예시#
보호가 필요한 비용이 큰 Gitaly RPC는 크게 두 가지 유형으로 분류할 수 있습니다:
- 순수 Git 데이터 작업.
- 시간에 민감한 RPC.
각 유형은 동시성 제한 구성 방식에 영향을 주는 서로 다른 특성을 가집니다. 다음 예시는 제한 구성의 근거를 보여 주며 시작점으로도 활용할 수 있습니다.
순수 Git 데이터 작업#
이 RPC는 Git pull·push·fetch 작업과 관련되며 다음과 같은 특성을 가집니다:
- 장시간 실행되는 프로세스입니다.
- 리소스 사용량이 많습니다.
- 연산 비용이 큽니다.
- 시간에 민감하지 않습니다. 지연 시간이 추가되어도 일반적으로 허용됩니다.
SmartHTTPService와 SSHService의 RPC는 순수 Git 데이터 작업 범주에 속합니다. 구성 예시는 다음과 같습니다:
{
rpc: "/gitaly.SmartHTTPService/PostUploadPackWithSidechannel", # or `/gitaly.SmartHTTPService/SSHUploadPackWithSidechannel`
adaptive: true,
min_limit: 10, # Minimum concurrency to maintain even under extreme load
initial_limit: 40, # Starting concurrency when service initializes
max_limit: 60, # Maximum concurrency under ideal conditions
max_queue_wait: "60s",
max_queue_size: 300
}
시간에 민감한 RPC#
이 RPC는 GitLab 자체와 다른 클라이언트를 대상으로 하며 다음과 같은 특성을 가집니다:
- 일반적으로 온라인 HTTP 요청이나 Sidekiq 백그라운드 job의 일부입니다.
- 지연 시간이 짧습니다.
- 일반적으로 리소스 사용량이 적습니다.
이 RPC에서는 GitLab의 타임아웃 구성을 기준으로 max_queue_wait 파라미터를 정합니다. 예를 들어 GitLab에서 get_tree_entries는 보통 30 초의 중간 타임아웃을 가집니다:
{
rpc: "/gitaly.CommitService/GetTreeEntries",
adaptive: true,
min_limit: 5, # Minimum throughput maintained under resource pressure
initial_limit: 10, # Initial concurrency setting
max_limit: 20, # Maximum concurrency under optimal conditions
max_queue_size: 50,
max_queue_wait: "30s"
}
적응형 제한 모니터링#
프로덕션 환경에서 적응형 제한이 어떻게 동작하는지 확인하려면 Gitaly 적응형 동시성 제한 모니터링에서 설명하는 모니터링 도구와 메트릭을 참고합니다. 적응형 제한의 동작을 관찰하면 제한이 리소스 압박에 올바르게 반응하며 예상대로 조정되는지 확인할 수 있습니다.