롱 폴링

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

기본적으로 GitLab 러너는 GitLab 인스턴스에 대해 주기적으로 새 CI/CD 작업을 폴링합니다. 실제 폴링 간격은 러너(runner) 구성 파일에서 check_interval 및 구성된 러너 수에 따라 다릅니다.

많은 러너를 처리하는 서버에서이 폴링은 다음과 같은 여러 성능 문제를 야기할 수 있습니다:

  • 대기 시간이 길어짐.
  • GitLab 인스턴스의 CPU 사용량이 증가함.

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

전제 조건:

  • 관리자이어야 합니다.

롱 폴링 활성화

러너가 새 작업이 준비될 때까지 러너에서 작업 요청을 지속적으로 폴링하도록 GitLab 인스턴스를 구성할 수 있습니다.

이를 위해 GitLab Workhorse 롱 폴링 기간인 apiCiLongPollingDuration을 구성하여 롱 폴링을 활성화하세요:

Linux 패키지 (Omnibus)
  1. /etc/gitlab/gitlab.rb 파일을 편집합니다:

    gitlab_workhorse['api_ci_long_polling_duration'] = "50s"
    
  2. 파일을 저장하고 GitLab을 다시 구성합니다:

    sudo gitlab-ctl reconfigure
    
Helm 차트 (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 카운터 Redis PubSub 채널에서 수신 된 총 메시지 수  
gitlab_workhorse_keywatcher_actions_total 카운터 다양한 키 감시자 작업 수 action
gitlab_workhorse_keywatcher_received_bytes_total 카운터 PubSub 채널에서 수신된 총 바이트 수  

이러한 메트릭으로 롱 폴링에 문제가 있는 것을 한 사용자가 어떻게 발견했는지 예제를 확인할 수 있습니다.

롱 폴링 워크플로우

다음 다이어그램은 롱 폴링이 활성화된 단일 러너가 작업을 받는 방법을 보여줍니다:

시퀀스다이어그램 autonumber 참가자 C as Runner 참가자 W as Workhorse 참가자 Redis, R as Redis 참가자 R as Rails 참가자 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는 last_update 토큰과 함께 204 작업 없음을 러너에게 반환합니다 (5단계에서 7단계).

러너는 해당 last_update 토큰을 사용하여 새로운 작업을 요청하고, 이번에는 X-GitLab-Last-Update HTTP 헤더를 해당 토큰으로 채웁니다. 이 시간에는 Workhorse가 러너의 last_update 토큰이 변경되었는지 확인합니다. 변경되지 않았다면, Workhorse는 apiCiLongPollingDuration에서 지정한 기간 동안 요청을 유지합니다.

사용자가 새 파이프라인 또는 작업을 트리거하는 경우, Sidekiq의 백그라운드 작업에서 모든 사용 가능한 작업에 대해 last_update 값을 업데이트합니다. 러너는 프로젝트, 그룹 및/또는 인스턴스에 등록할 수 있습니다.

10단계 및 11단계의 이 “틱”은 Workhorse의 롱 폴링 대기열에서 작업 요청을 해제하며 요청이 Rails로 전송됩니다 (12단계).

롱 폴링을 사용하면 새로운 작업이 사용 가능한 즉시 러너에게 알림이 전송됩니다. 이는 작업 대기 시간을 줄이고 새로운 작업이 있는 경우에만 Rails에 작업 요청이 도달하여 서버 부하를 줄이는 데 도움이 됩니다.

문제 해결

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

작업 픽업이 느림

일부 실행 환경에서는 기본적으로 긴 폴링이 활성화되어 있지 않습니다. 이는 실행 환경에 따라 실행자가 적시에 작업을 가져가지 않을 수 있기 때문입니다. 이슈 27709을 참조하세요.

이 문제는 실행자의 config.toml에서 concurrent 설정이 정의된 실행자의 숫자보다 작은 값으로 설정되었을 때 발생할 수 있습니다. 이 문제를 해결하려면 concurrent 값이 실행자의 숫자와 동일하거나 이보다 큰지 확인하세요.

예를 들어, config.toml에 3개의 [[runners]] 항목이 있다면, concurrent가 최소한 3으로 설정되어 있는지 확인하세요.

긴 폴링이 활성화되어 있는 경우, 실행자는 다음을 수행합니다:

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

예를 들어, 단일 config.toml에서 다음과 같이 설정된 경우를 고려해 보겠습니다:

  • 프로젝트 A에 대해 3개의 실행자.
  • 프로젝트 B에 대해 1개의 실행자.
  • concurrent가 3으로 설정됨.

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