롱 폴링
Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
기본적으로 GitLab Runner는 주기적으로 새로운 CI/CD 잡을 GitLab 인스턴스에 폴링합니다. 많은 러너를 처리하는 서버에서 이 폴링은 다음과 같은 성능 문제를 일으킬 수 있습니다: 이러한 문제를 완화하려면 롱 폴링을 활성화해야 합니다.
기본적으로 GitLab Runner는 주기적으로 새로운 CI/CD 잡을 GitLab 인스턴스에 폴링합니다. 실제 폴링 간격은 러너 구성 파일에 구성된 check_interval 및 러너 수에 따라 달라집니다.
많은 러너를 처리하는 서버에서 이 폴링은 다음과 같은 성능 문제를 일으킬 수 있습니다:
- 더 긴 대기 시간.
- GitLab 인스턴스에서 더 높은 CPU 사용량.
이러한 문제를 완화하려면 롱 폴링을 활성화해야 합니다.
사전 조건:
- 관리자여야 합니다.
롱 폴링 활성화#
새 잡이 준비될 때까지 롱 폴링에서 러너의 잡 요청을 유지하도록 GitLab 인스턴스를 구성할 수 있습니다.
이를 위해 GitLab Workhorse 롱 폴링 지속 시간(apiCiLongPollingDuration)을 구성하여 롱 폴링을 활성화합니다:
-
/etc/gitlab/gitlab.rb를 편집합니다:gitlab_workhorse['api_ci_long_polling_duration'] = "50s" -
파일을 저장하고 GitLab을 재구성합니다:
sudo gitlab-ctl reconfigure
gitlab.webservice.workhorse.extraArgs 설정으로 롱 폴링을 활성화합니다.
-
Helm 값을 내보냅니다:
helm get values gitlab > gitlab_values.yaml -
gitlab_values.yaml을 편집합니다:gitlab: webservice: workhorse: extraArgs: "-apiCiLongPollingDuration 50s" -
파일을 저장하고 새 값을 적용합니다:
helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
-
docker-compose.yml을 편집합니다:version: "3.6" services: gitlab: image: 'gitlab/gitlab-ee:latest' restart: always hostname: 'gitlab.example.com' environment: GITLAB_OMNIBUS_CONFIG: | gitlab_workhorse['api_ci_long_polling_duration'] = "50s" -
파일을 저장하고 GitLab을 다시 시작합니다:
docker compose up -d
메트릭#
롱 폴링이 활성화되면 GitLab Workhorse는 Redis PubSub 채널을 구독하고 알림을 기다립니다. 잡 요청은 러너 키가 변경되거나 apiCiLongPollingDuration에 도달하면 롱 폴링에서 릴리스됩니다. 모니터링할 수 있는 여러 Prometheus 메트릭이 있습니다:
| 메트릭 | 유형 | 설명 | 레이블 |
|---|---|---|---|
gitlab_workhorse_keywatcher_keywatchers |
Gauge | GitLab Workhorse에서 감시하는 키의 수 | |
gitlab_workhorse_keywatcher_redis_subscriptions |
Gauge | Redis PubSub 구독 수 | |
gitlab_workhorse_keywatcher_total_messages |
Counter | GitLab Workhorse가 PubSub 채널에서 받은 총 메시지 수 | |
gitlab_workhorse_keywatcher_actions_total |
Counter | 다양한 키 감시기 액션 수 | action |
gitlab_workhorse_keywatcher_received_bytes_total |
Counter | PubSub 채널에서 수신한 총 바이트 |
이러한 메트릭으로 롱 폴링 문제를 발견한 사용자 사례 예시를 볼 수 있습니다.
롱 폴링 워크플로우#
다이어그램은 롱 폴링이 활성화된 상태에서 단일 러너가 잡을 가져오는 방법을 보여줍니다:
소스 코드 보기
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Long polling workflow
accDescr: The flow of a single runner getting a job with long polling enabled
autonumber
participant C as Runner
participant W as Workhorse
participant Redis as Redis
participant R as Rails
participant S as Sidekiq
C->>+W: POST /api/v4/jobs/request
W->>+Redis: New job for runner A?
Redis->>+W: Unknown
W->>+R: POST /api/v4/jobs/request
R->>+Redis: Runner A: last_update = X
R->>W: 204 No job, X-GitLab-Last-Update = X
W->>C: 204 No job, X-GitLab-Last-Update = X
C->>W: POST /api/v4/jobs/request, X-GitLab-Last-Update: X
W->>Redis: Notify when last_update change
Note over W: Request held in long poll
Note over S: CI job created
Note over S, Redis: Update all registered runners
S->>Redis: Runner A: last_update = Z
Redis->>W: Runner: last_update changed
Note over W: Request released from long poll
W->>Rails: POST /api/v4/jobs/request
Rails->>W: 201 Job was scheduled
W->>C: 201 Job was scheduled</code></pre></details></div>
1단계에서 러너가 새 잡을 요청할 때 GitLab 서버에 POST 요청(/api/v4/jobs/request)을 발행하며 먼저 Workhorse에서 처리됩니다.
Workhorse는 X-GitLab-Last-Update HTTP 헤더에서 러너 토큰과 값을 읽고 키를 구성한 다음 해당 키로 Redis PubSub 채널을 구독합니다. 키에 대한 값이 없으면 Workhorse는 요청을 Rails로 즉시 전달합니다(3단계 및 4단계).
Rails는 잡 큐를 확인합니다. 러너에 사용 가능한 잡이 없으면 Rails는 last_update 토큰과 함께 204 No job을 러너에 반환합니다(5단계에서 7단계).
러너는 해당 last_update 토큰을 사용하여 잡에 대한 또 다른 요청을 발행하고 이 토큰으로 X-GitLab-Last-Update HTTP 헤더를 채웁니다. 이번에는 Workhorse가 러너의 last_update 토큰이 변경되었는지 확인합니다. 변경되지 않은 경우 Workhorse는 apiCiLongPollingDuration에서 지정한 기간까지 요청을 유지합니다.
사용자가 새 파이프라인이나 잡을 실행하도록 트리거하면 Sidekiq의 백그라운드 작업이 잡에 사용 가능한 모든 러너의 last_update 값을 업데이트합니다. 러너는 프로젝트, 그룹 및/또는 인스턴스에 등록될 수 있습니다.
10단계와 11단계의 이 "틱"은 Workhorse 롱 폴링 큐에서 잡 요청을 릴리스하고 요청이 Rails로 전송됩니다(12단계). Rails는 사용 가능한 잡을 찾고 해당 잡에 러너를 할당합니다(13단계 및 14단계).
롱 폴링을 사용하면 새 잡이 사용 가능해진 직후 러너에게 알림이 전달됩니다. 이는 잡 대기 시간을 줄이는 데 도움이 될 뿐만 아니라 새 작업이 있을 때만 잡 요청이 Rails에 도달하기 때문에 서버 오버헤드도 줄입니다.
문제 해결#
롱 폴링 작업 시 다음 문제가 발생할 수 있습니다.
느린 잡 픽업#
롱 폴링은 일부 러너 구성에서 러너가 잡을 제때 가져오지 못할 수 있기 때문에 기본적으로 활성화되지 않습니다.
이슈 27709를 참조하세요.
이는 러너 config.toml의 concurrent 설정이 정의된 러너 수보다 낮은 값으로 설정된 경우 발생할 수 있습니다. 이 문제를 해결하려면 concurrent 값이 러너 수 이상인지 확인하세요.
예를 들어 config.toml에 세 개의 [[runners]] 항목이 있는 경우 concurrent가 최소 3으로 설정되어 있는지 확인합니다.
롱 폴링이 활성화되면 러너는:
concurrent 수의 Goroutine을 시작합니다.
- 롱 폴링 후 Goroutine이 반환될 때까지 기다립니다.
- 또 다른 요청 배치를 실행합니다.
예를 들어 단일 config.toml에 다음이 구성된 경우를 고려합니다:
- 프로젝트 A에 대한 3개의 러너.
- 프로젝트 B에 대한 1개의 러너.
concurrent를 3으로 설정.
이 예시에서 러너는 처음 3개 프로젝트에 대한 Goroutine을 시작합니다.
최악의 경우 러너는 프로젝트 B의 잡을 요청하기 전에 프로젝트 A의 전체 롱 폴링 간격을 기다립니다.
