롱 폴링

Tier: Free, Premium, Ultimate Offering: GitLab.com, Self-managed, GitLab Dedicated

기본적으로 GitLab Runner는 주기적으로 GitLab 인스턴스를 폴링하여 새 CI/CD 작업을 가져옵니다. 실제 폴링 간격은 런너 구성 파일의 check_interval 및 설정된 러너 수에 따라 다릅니다.

많은 러너를 처리하는 서버에서 이 폴링은 여러 성능 문제로 이어질 수 있습니다:

  • 대기 시간이 길어짐.
  • GitLab 인스턴스의 CPU 사용량이 높아짐.

이러한 문제를 완화하기 위해 롱 폴링을 활성화해야 합니다.

사전 요구 사항:

  • 관리자여야 합니다.

롱 폴링 활성화

GitLab 인스턴스를 구성하여 러너의 작업 요청을 새 작업이 준비될 때까지 롱 폴링하는 방식으로 할 수 있습니다.

이를 위해 GitLab Workhorse 롱 폴링 기간(apiCiLongPollingDuration)을 구성하여 롱 폴링을 활성화합니다:

Linux package (Omnibus)
  1. /etc/gitlab/gitlab.rb 파일 편집:

    gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
    
  2. 파일 저장 및 GitLab 재구성:

    sudo gitlab-ctl reconfigure
    
Helm chart (Kubernetes)

gitlab.webservice.workhorse.extraArgs 설정으로 롱 폴링을 활성화합니다.

  1. Helm 값 내보내기:

    helm get values gitlab > gitlab_values.yaml
    
  2. gitlab_values.yaml 편집:

    gitlab:
      webservice:
        workhorse:
          extraArgs: "-apiCiLongPollingDuration 50s"
    
  3. 파일 저장 및 새 값 적용:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab
    
Docker
  1. 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"
    
  2. 파일 저장 및 GitLab 재시작:

    docker compose up -d
    

메트릭

롱 폴링이 활성화되면, GitLab Workhorse는 Redis PubSub 채널에 구독하고 알림을 기다립니다. 러너 키가 변경되거나 apiCiLongPollingDuration이 도달되면 롱 폴에서 작업 요청이 해제됩니다.

모니터링할 수 있는 여러 Prometheus 메트릭이 있습니다:

메트릭 타입 설명 레이블
gitlab_workhorse_keywatcher_keywatchers 게이지 GitLab Workhorse가 감시 중인 키의 수  
gitlab_workhorse_keywatcher_redis_subscriptions 게이지 Redis PubSub 구독 수  
gitlab_workhorse_keywatcher_total_messages 카운터 PubSub 채널에서 수신한 총 메시지 수  
gitlab_workhorse_keywatcher_actions_total 카운터 다양한 키 감시 동작 수 action
gitlab_workhorse_keywatcher_received_bytes_total 카운터 PubSub 채널에서 받은 총 바이트 수  

이러한 메트릭을 사용하여 어떤 사용자가 이러한 메트릭을 사용하여 롱 폴링과 관련된 문제를 발견했던 사례를 확인할 수 있습니다.

롱 폴링 워크플로우

다음 다이어그램은 롱 폴링이 활성화된 상태에서 단일 러너가 작업을 수행하는 과정을 보여줍니다:

%%{init: { "fontFamily": "GitLab Sans" }}%% sequenceDiagram accTitle: 롱 폴링 워크플로우 accDescr: 롱 폴링이 활성화된 상태에서 단일 러너가 작업을 가져오는 흐름 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: 러너 A에 대한 새 작업? Redis->>+W: Unknown W->>+R: POST /api/v4/jobs/request R->>+Redis: 러너 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: last_update 변경 시 알림 Note over W: 롱 폴에서 요청 보류 Note over S: CI 작업 생성 Note over S, Redis: 등록된 모든 러너 업데이트 S->>Redis: 러너 A: last_update = Z Redis->>W: 러너: last_update 변경 Note over W: 롱 폴에서 요청 해제 W->>Rails: POST /api/v4/jobs/request Rails->>W: 201 작업 예약됨 W->>C: 201 작업 예약됨

1단계에서 러너가 새 작업을 요청하면, GitLab 서버에 /api/v4/jobs/requestPOST 요청을 보내어 먼저 이를 Workhorse가 처리합니다.

Workhorse는 X-GitLab-Last-Update HTTP 헤더에서 러너 토큰 및 값, 키를 생성하고 해당 키로 Redis PubSub 채널을 구독합니다. 키에 대한 값이 없는 경우, Workhorse는 즉시 요청을 Rails로 전달합니다 (3단계 및 4단계).

Rails는 작업 대기열을 확인합니다. 러너에 대해 사용 가능한 작업이 없는 경우, Rails는 러너에게 204 No joblast_update 토큰으로 응답합니다 (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단계).

롱 폴링을 통해 러너는 새 작업이 가능해지자마자 즉시 알림을 받습니다. 이는 작업 대기 시간을 줄이고, 새로운 작업이 있을 때에만 리소스를 사용하도록 함으로써 서버 부하를 줄이는 데 도움이 됩니다.

문제 해결

긴 폴링을 사용할 때 다음과 같은 문제가 발생할 수 있습니다.

작업 픽업이 느림

일부 러너 구성에서는 러너가 적시에 작업을 수행하지 않기 때문에 긴 폴링이 기본적으로 활성화되지 않습니다. 이슈 27709를 확인하세요.

러너 config.tomlconcurrent 설정이 정의된 러너 수보다 낮게 설정된 경우에 발생할 수 있습니다. 이 문제를 해결하려면 concurrent의 값이 러너의 수와 동일하거나 그 이상으로 설정되어 있는지 확인하십시오.

예를 들어, config.toml에 3개의 [[runners]] 항목이 있는 경우, concurrent가 적어도 3으로 설정되어 있는지 확인하십시오.

긴 폴링이 활성화되면 러너는 다음과 같이 동작합니다.

  1. concurrent 개수의 고루틴을 시작합니다.
  2. 긴 폴링 후에 고루틴이 반환될 때까지 기다립니다.
  3. 다른 일괄 요청을 실행합니다.

예를 들어, 단일 config.toml이 다음과 같이 구성된 경우를 생각해보겠습니다.

  • 프로젝트 A의 러너가 3개입니다.
  • 프로젝트 B의 러너가 1개입니다.
  • concurrent가 3으로 설정되어 있습니다.

이 예에서 러너는 처음 3개 프로젝트에 대해 고루틴을 시작합니다. 최악의 경우, 러너는 프로젝트 A의 작업을 요청하기 전에 프로젝트 B의 긴 폴링 간격 전체를 기다리게 됩니다.