롱 폴링

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

기본적으로 GitLab Runner는 주기적으로 GitLab 인스턴스를 폴링하여 새 CI/CD 작업을 수행합니다. 실제 폴링 간격은 check_interval 및 러너 구성 파일에 구성된 러너 수에 따라 달라집니다.

많은 러너를 처리하는 서버의 경우, 이러한 폴링은 다음과 같은 여러 성능 문제를 야기할 수 있습니다.

  • 대기 시간이 길어집니다.
  • GitLab 인스턴스에서 CPU 사용량이 증가합니다.

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

전제 조건:

  • 관리자여야 합니다.

롱 폴링 활성화

러너로부터의 작업 요청을 GitLab 인스턴스가 새 작업이 준비될 때까지 오랫동안 보유하도록 GitLab 인스턴스를 구성할 수 있습니다.

이를 위해 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 채널에서 수신한 총 바이트 수  

이러한 메트릭으로 어떤 사용자가 롱 폴링과 관련된 문제를 발견한 예제를 확인할 수 있습니다.

롱 폴링 워크플로우

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

sequenceDiagram 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: 미지의 W->>+R: POST /api/v4/jobs/request R->>+Redis: 러너 A: last_update = X R->>W: 204 작업 없음, X-GitLab-Last-Update = X W->>C: 204 작업 없음, 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 서버에 POST 요청(/api/v4/jobs/request)을 보내며 작업은 먼저 Workhorse에서 처리됩니다.

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

Rails은 작업 대기열을 확인합니다. 러너에 대해 사용 가능한 작업이 없으면, Rails는 204 No job을 반환하고 last_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단계).

롱 폴링을 사용하면 새 작업이 준비되자마자 러너에게 즉시 알림을 받게됩니다. 이는 작업 대기 시간을 줄이는 데 도움이 되는 것뿐만 아니라 새 작업이 있는 경우에만 작업 요청이 Rails에 도달하기 때문에 서버 부담도 줄어듭니다.

문제 해결

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

작업 수행이 느림

특정 러너 구성에서 롱 폴링이 기본적으로 활성화되어 있지 않아서 작업 수행이 제때에 이루어지지 않을 수 있습니다. 이슈 27709을 참조하세요.

이 문제는 러너 config.tomlconcurrent 설정이 정의된 러너 수보다 낮은 값으로 설정된 경우에 발생할 수 있습니다. 이 문제를 해결하려면 concurrent 값이 정의된 러너 수와 동일하거나 그 이상임을 확인하세요.

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

롱 폴링이 활성화된 경우, 러너는 다음을 수행합니다.

  1. concurrent 개수의 고루틴을 실행합니다.
  2. 롱 폴링 후에 고루틴의 반환을 기다립니다.
  3. 다시 일괄 요청을 실행합니다.

예를 들어, 단일 config.toml에서 다음과 같이 구성된 경우:

  • 프로젝트 A에 대해 3개의 러너.
  • 프로젝트 B에 대해 1개의 러너.
  • concurrent가 3으로 설정된 경우.

이 예에서 러너는 첫 3개 프로젝트를 위해 고루틴을 실행합니다. 최악의 경우, 프로젝트 A에 대해 전체 롱 폴 간격 동안 러너가 기다려야할 수 있습니다. 프로젝트 B의 작업을 요청하기 전까지요.