Sidekiq worker attributes

워커 클래스는 행동을 제어하고 메타데이터를 추가하기 위해 특정 속성을 정의할 수 있습니다.

기타 워커로부터 상속된 자식 클래스는 이러한 속성을 상속받기 때문에, 값을 재정의하려면 그들을 다시 정의해야 합니다.

작업의 긴급성

작업에는 :high, :low, 또는 :throttled로 설정할 수 있는 urgency 속성이 있습니다. 아래의 타겟이 있습니다:

긴급성 큐 스케줄링 타겟 실행 지연 요구 사항
:high 10 초 10 초
:low (기본값) 1 분 5 분
:throttled 없음 5 분

작업의 긴급성을 설정하려면 urgency 클래스 메서드를 사용하세요:

class HighUrgencyWorker
  include ApplicationWorker
  
  urgency :high
  
  # ...
end

지연에 민감한 작업

한 번에 많은 백그라운드 작업이 예약되면, 작업이 작업 노드가 사용 가능해질 때까지 대기하는 대기열이 발생할 수 있습니다. 이것은 표준적이며, 트래픽의 급증을 유연하게 처리하여 시스템에 탄력성을 부여합니다. 그러나 일부 작업은 다른 작업보다 실행 지연에 민감합니다.

일반적으로, 지연에 민감한 작업은 사용자가 동기적으로 발생할 것으로 합리적으로 기대되는 작업을 비동기적으로 하는 것보다 수행합니다. 일반적인 예로는 작업에 따라 브랜치로 푸시한 후에 Merge Request을 업데이트하는 작업이 있습니다. 이러한 작업에는 다음과 같은 예가 있습니다:

  1. 브랜치로 푸시한 후에 Merge Request을 업데이트하는 작업.
  2. 프로젝트의 알려진 브랜치 캐시를 무효화하는 작업.
  3. 사용자의 권한 변경 후 사용자가 볼 수 있는 그룹 및 프로젝트를 재계산하는 작업.
  4. CI 파이프라인에서 작업 상태 변경 후 CI 파이프라인의 상태를 업데이트하는 작업.

이러한 작업이 지연되면 사용자는 해당 지연을 버그로 인식할 수 있습니다. 예를 들어, 브랜치를 푸시하고 해당 브랜치에 대한 Merge Request을 생성하려고 하지만 UI에서 브랜치가 존재하지 않는다는 메시지를 받을 수 있습니다. 이러한 작업은 urgency :high로 간주됩니다.

이러한 작업이 매우 짧은 시간 내에 시작되도록 보장하기 위해 추가 노력이 기울여집니다. 그러나 처리량을 보장하기 위해 이러한 작업에는 매우 엄격한 실행 기간 요구 사항도 있습니다:

  1. 중앙값 작업 실행 시간은 1초 미만이어야 합니다.
  2. 작업의 99%는 10초 내에 완료되어야 합니다.

작업이 기대치를 충족시킬 수 없다면, 해당 작업은 urgency :high 워커로 간주할 수 없습니다. 워커를 재설계하거나 urgency :high 코드가 빠르게 실행되는 하나와 실행 지연 요구 사항이 없는 urgency :low로 구분하는 방법을 고려하세요.

큐의 긴급성 변경

GitLab.com에서는 Sidekiq을 여러 셔드로 실행하며, 각각이 특정 유형의 작업량을 나타냅니다.

큐의 긴급성을 변경하거나 새 큐를 추가할 때는 새 셔드에 대한 예상 작업 증가량을 고려해야 합니다. 기존 큐를 변경하는 경우 기존 셔드에도 영향이 있지만, 이는 항상 작업을 줄입니다.

이를 위해 새로운 셔드에 대한 총 실행 시간 및 RPS(처리량)의 예상 증가량을 계산하려고 합니다. 이러한 값을 다음에서 얻을 수 있습니다:

  • Queue Detail 대시보드는 큐 자체의 값이 있습니다. 새 큐의 경우, 유사한 패턴을 갖거나 유사한 상황에서 예약된 큐를 찾을 수 있습니다.
  • Shard Detail 대시보드에는 총 실행 시간 및 처리량 (RPS)이 있습니다. 셔드 사용량 패널은 현재 해당 셔드에 대한 잔여 용량이 있는지를 표시해줍니다.

그런 다음 변경하는 큐에 대한 RPS * 평균 런타임 (새 작업에 대한 추정값)을 계산하여 새로운 셔드에서 기대하는 RPS 및 실행 시간 증가량을 확인할 수 있습니다:

new_queue_consumption = queue_rps * queue_duration_avg
shard_consumption = shard_rps * shard_duration_avg

(new_queue_consumption / shard_consumption) * 100

예상 증가량이 5% 미만이라면, 추가 조치가 필요하지 않습니다.

그렇지 않으면, Merge Request에서 @gitlab-org/scalability를 ping하고 리뷰를 요청하세요.

외부 의존성을 갖는 작업

GitLab 애플리케이션의 대부분의 백그라운드 작업은 다른 GitLab 서비스와 통신합니다. 예를 들어, PostgreSQL, Redis, Gitaly 및 오브젝트 스토리지입니다. 이러한 작업은 “내부” 의존성으로 간주됩니다.

그러나, 일부 작업은 외부 서비스에 종속되어 성공적으로 완료되어야 합니다. 일부 예시는 다음과 같습니다:

  1. 사용자가 구성한 웹훅을 호출하는 작업.
  2. 사용자가 구성한 쿠버네티스 클러스터에 애플리케이션을 배포하는 작업.

이러한 작업은 “외부 의존성”을 갖습니다. 이는 백그라운드 처리 클러스터의 작동에 중요한 영향을 미칩니다:

  1. 대부분의 외부 의존성(예: 웹훅)은 SLO를 제공하지 않으므로, 이러한 작업에 대한 실행 지연 시간을 보장할 수 없습니다. 실행 지연 시간을 보장할 수 없기 때문에, 고트래픽 환경에서는 외부 의존성을 갖는 작업이 긴급성 있는 작업과 분리되어 처리량이 보장되어야 합니다.
  2. 외부 의존성을 갖는 작업의 오류는 경고 임계값이 더 높습니다. 오류의 원인이 외부에 있을 가능성이 있기 때문입니다.
class ExternalDependencyWorker
  include ApplicationWorker
  
  # 성공적으로 완료하기 위해
  # 외부 기관, 외부 서비스에 종속됨을 선언합니다.
  worker_has_external_dependencies!
  
  # ...
end

작업은 고긴급성이면서 외부 의존성을 갖을 수 없습니다.

CPU 바운드 및 메모리 바운드 워커

CPU 또는 메모리 자원 제한으로 제한되는 워커는 worker_resource_boundary 메서드로 표시해야 합니다.

대부분의 워커는 주로 Redis, PostgreSQL 및 Gitaly와 같은 다른 서비스로부터의 네트워크 응답 대기를 차단하여 대기하는 시간을 보냅니다. Sidekiq은 멀티 스레드 환경이므로 이러한 작업은 높은 동시성으로 예약될 수 있습니다.

그러나 일부 워커는 Ruby에서 로직을 실행하는 데 많은 시간을 소비합니다. Ruby MRI는 진정한 멀티 스레딩을 지원하지 않으며, 프로세스당 어느 정도의 코어가 호스팅되었더라도 한 번에 한 번에 하나의 Ruby 코드 섹션만 실행할 수 있도록 GIL을 사용하여 애플리케이션 개발을 단순화합니다. IO 바운드 워커의 경우 이는 문제가 되지 않습니다. 왜냐하면 대부분의 스레드가 GIL 외부의 내부 라이브러리에서 차단되기 때문입니다.

만약 많은 스레드가 동시에 Ruby 코드를 실행하려고 하면 이는 모든 프로세스를 느리게 만드는 GIL의 경합을 초래합니다.

고트래픽 환경에서 워커가 CPU에 바운드되어있는 경우 이를 알고 있으면 낮은 동시성을 갖는 다른 fleet에서 이를 실행시킬 수 있습니다. 이를 통해 최적의 성능을 보장할 수 있습니다.

마찬가지로, 워커가 대량의 메모리를 사용하는 경우 이를 고성능 메모리 fleet에 실행시킬 수 있습니다.

메모리 바운드 워커는 GC 작업을 생성하여 10-50ms의 일시 중지를 만듭니다. 이는 워커의 지연 요구 사항에 영향을 미칩니다. 이러한 이유로 memory 바운드, urgency :high 작업은 허용되지 않으며 CI에 실패합니다. 일반적으로 memory 바운드 워커는 권장되지 않으며, 작업을 처리하는 대체 방법을 고려해야 합니다.

만약 워커가 대량으로 메모리와 CPU 시간이 필요하다면, 위에서 설명한 urgency :high 메모리 바운드 워커의 제한 때문에 memory 바운드로 표시해야 합니다.

CPU 바운드 작업으로 선언하기

이 예제는 작업을 CPU 바운드로 선언하는 방법을 보여줍니다.

class CPUIntensiveWorker
  include ApplicationWorker
  
  # 이 워커가 CPU에서 많은 계산을 수행할 것이라고 선언합니다.
  worker_resource_boundary :cpu
  
  # ...
end

워커가 CPU 바운드인지 결정하기

다음 접근 방식을 사용하여 워커가 CPU 바운드인지를 결정합니다:

  • Sidekiq 구조화된 JSON 로그에서 워커 durationcpu_s 필드를 집계합니다.
  • duration은 작업 실행 총 시간(초)을 나타냅니다.
  • cpu_sProcess::CLOCK_THREAD_CPUTIME_ID 카운터에서 파생되며, 작업이 CPU에서 사용한 시간을 메트릭합니다.
  • cpu_sduration으로 나누어 CPU에서 사용하는 시간의 백분율을 구합니다.
  • 이 비율이 33%를 초과하면, 해당 워커는 CPU 바운드로 간주되어 표시되어야 합니다.
  • 이러한 값들은 작은 샘플 크기보다는 상당한 수집값을 기준으로 해야 합니다.

기능 카테고리

모든 Sidekiq 워커는 알려진 기능 카테고리를 정의해야 합니다.

작업 데이터 일관성 전략

GitLab 13.11 및 이전 버전에서, Sidekiq 워커는 읽기 및 쓰기 모두를 주 데이터베이스 노드에 보냈습니다. 이것은 단일 노드 시나리오에서 복제본이 자신의 쓰기에 대해 볼 수 없기 때문에 데이터 무결성이 보장되고 즉시 보장되었습니다. 그러나 워커가 주에 쓰고, 복제본에서 읽는 경우 복제본이 주보다 뒤떨어지기 때문에 stale record를 읽을 수 있는 가능성은 0이 아닙니다.

데이터베이스에 의존하는 작업의 수가 증가함에 따라 즉시 데이터 일관성을 보장하는 것은 주 데이터베이스 서버에 부담을 줄 수 있습니다. 따라서 Sidekiq 워커에 대해 데이터베이스 부하 분산 기능을 추가했습니다. 워커의 data_consistency 필드를 구성함으로써 스케줄러가 다음에 설명된 여러 전략을 사용하여 복제본을 타겟팅할 수 있게 됩니다.

기본 부하를 줄이고 즉시 일관된 읽기 보장

Sidekiq 워커가 모든 읽기 및 쓰기에 대해 주 데이터베이스 노드를 사용해야 하는지, 아니면 읽기가 복제본에서 제공될 수 있는지에 대한 명시적인 결정을 내리도록 요구합니다. 이것은 data_consistency 필드가 설정되었는지 확인하는 RuboCop 규칙에 의해 강제됩니다.

이 필드를 설정할 때, 다음 교환 관계를 고려하세요:

  • 즉각적으로 일관된 읽기를 보장하지만, 주 데이터베이스에 부하를 추가합니다.
  • 주 데이터베이스에 부담을 덜 주기 위해 복제본을 선호하지만, 재시도해야 하는 stale reads 가능성을 높입니다.

이 필드가 도입되기 전과 동일한 동작을 유지하려면, :always로 설정하여 새로운 값을 추가하는 것이 좋습니다. 이 작업이 필요한 이유는 대부분 또는 전혀 읽기를 수행하지 않는 워커나 자신의 쓰기를 읽는 워커에서 stale record가 복제본에서 다시 읽힐 경우 데이터 일관성 문제에 직면할 수 있기 때문입니다. 이러한 시나리오를 피하십시오. :always는 예외로 고려되어야 하므로.

복제본에서 읽기가 가능하도록 하려면, 두 가지 추가 일관성 모드 :sticky:delayed를 추가했습니다. RuboCop 규칙을 사용하여 :always 데이터 일관성 모드가 사용될 때 개발자에게 알립니다. 워커가 주 데이터베이스를 필요로 하는 경우 규칙을 인라인으로 비활성화할 수 있습니다.

:sticky 또는 :delayed 일관성을 선언하면, 워커는 데이터베이스 부하 분산에 이용할 수 있습니다.

두 경우 모두, 복제본이 최신 상태가 아니거나 복제가 지연되었을 때 최소 지연 간격(0.8초)이하로 예약된 작업이 실행됩니다. 이는 복제 과정이 끝나기를 기다립니다. 차이점은 지연 후에 여전히 복제 지연이 발생할 때: sticky 워커는 즉시 주 데이터베이스로 전환되고, delayed 워커는 빠르게 실패하고 한 번 다시 시도됩니다. 워커가 여전히 복제 지연을 만나면, 그들은 주로 전환됩니다. 만약 당신의 워커가 어떤 쓰기도 수행하지 않는다면, 주 데이터베이스 노드에 의존하지 않아도 되는 워커로 강하게 권장됩니다.

아래 표는 data_consistency 속성과 선호하는 복제본을 위한 값들을 복제본이 따라잡을 때까지 기다리는 정도에 따라 정렬하여 보여줍니다:

데이터 일관성 설명 지침
:always 작업이 주 데이터베이스를 사용해야 합니다 (기본값). 주로 쓰기를 수행하거나 자신의 쓰기를 읽을 때 데이터 일관성에 엄격한 요구사항이 있는 워커 또는 cron 작업에 사용해야 합니다.
:sticky 작업이 복제본을 선호하지만 쓰기가 있는 경우나 복제 지연이 발생하면 주 데이터베이스로 전환합니다. 가능한 한 빨리 실행되어야 하지만 작은 초기 대기 지연을 견딜 수 있는 작업에 사용해야 합니다.
:delayed 작업이 복제본을 선호하지만 쓰기가 있는 경우 주 데이터베이스로 전환합니다. 작업 시작 전에 복제 지연이 발생하면 작업이 한 번 다시 시도됩니다. 다음 재시도에서 복제본이 여전히 최신 상태가 아니면 주 데이터베이스로 전환됩니다. 캐시 만료 또는 웹 후크 실행과 같은 실행을 지연하는 것이 중요하지 않은 작업에 사용해야 합니다.

모든 경우에 워커는 완전히 따라잡은 복제본 또는 주 노드에서 읽어들이므로 데이터 일관성이 항상 보장됩니다.

워커의 데이터 일관성을 설정하려면 data_consistency 클래스 메서드를 사용하세요:

class DelayedWorker
  include ApplicationWorker
  
  data_consistency :delayed
  
  # ...
end

feature_flag 속성

feature_flag 속성을 사용하면 특정 작업의 data_consistency를 토글할 수 있어서 특정 작업에 대한 안전한 부하 분산 기능을 활성화 또는 비활성화할 수 있습니다. feature_flag가 비활성화된 경우, 작업은 기본적으로 :always를 사용하므로 작업은 항상 주 데이터베이스를 사용합니다.

feature_flag 속성은 기능 게이트 기반의 시스템을 사용하는 것을 허용하지 않습니다. 즉, 피처 플래그는 특정 프로젝트, 그룹 또는 사용자를 위해서만 토글할 수 없지만, 시간의 백분율 롤아웃을 안전하게 사용할 수 있습니다. 우리는 Sidekiq 클라이언트와 서버에서 모두 피처 플래그를 확인하므로, 10%를 롤아웃하는 것은 (0.1 [클라이언트에서]*0.1 [서버에서])로 결과될 수 있는 1%의 효과적인 작업을 복제본에서 사용합니다.

예시:

class DelayedWorker
  include ApplicationWorker
  
  data_consistency :delayed, feature_flag: :load_balancing_for_delayed_worker
  
  # ...
end

해당 작업을 위한 데이터 일관성

sticky 또는 delayed 데이터 일관성을 선언하는 멱등작업에 대해, 멱등작업을 유지하면서 최신 WAL 위치를 보존합니다. 이는 완전히 따라잡은 복제본에서 읽기를 보장합니다.

작업 일시 중지 제어

pause_control 속성을 사용하여 작업 처리를 조건부로 일시 중지시킬 수 있습니다. 해당 전략이 활성화된 경우, 작업은 별도의 ZSET에 저장되고 해당 전략이 비활성화되면 재입열됩니다. PauseControl::ResumeWorker는 일시 중지된 작업을 다시 시작해야 하는지를 확인하는 cron 워커입니다.

pause_control을 사용하려면 다음과 같이 할 수 있습니다:

  • lib/gitlab/sidekiq_middleware/pause_control/strategies/에 정의된 전략 중 하나를 사용합니다.
  • 사용자 정의 전략을 lib/gitlab/sidekiq_middleware/pause_control/strategies/에 정의하고 해당 전략을 lib/gitlab/sidekiq_middleware/pause_control.rb에 추가합니다.

예시:

module Gitlab
  module SidekiqMiddleware
    module PauseControl
      module Strategies
        class CustomStrategy < Base
          def should_pause?
            ApplicationSetting.current.elasticsearch_pause_indexing?
          end
        end
      end
    end
  end
end
class PausedWorker
  include ApplicationWorker
  
  pause_control :custom_strategy
  
  # ...
end

동시성 제한

concurrency_limit 속성을 사용하여 worker의 동시성을 제한할 수 있습니다. 이는 이 제한을 초과하는 작업을 별도의 LIST에 넣고, 제한 이하로 떨어졌을 때 다시 대기열에 넣는 것입니다. ConcurrencyLimit::ResumeWorker는 제한된 작업이 다시 대기열에 들어가야 하는지 확인하는 cron worker입니다.

정의된 동시성 제한을 초과하는 첫 번째 작업은 해당 클래스의 다른 모든 작업에 대한 제한 프로세스를 시작합니다. 이 일이 발생하기 전까지는 작업은 보통대로 예약되고 실행됩니다.

제한 프로세스가 시작되면 새로 예약되고 실행된 작업은 실행 순서가 유지되도록 LIST의 끝에 추가됩니다. LIST가 다시 비게 되면 제한 프로세스가 종료됩니다.

caution
제한을 초과하는 지속적인 작업이 있다면, LIST는 제한이 해제되거나 작업 부하가 제한 이하로 떨어질 때까지 계속 커질 것입니다.

제한을 정의하는 데에는 람다를 사용해야 합니다. 람다가 nil, 0, 또는 음수 값을 반환하면 제한이 적용되지 않습니다.

class LimitedWorker
  include ApplicationWorker
  
  concurrency_limit -> { 60 }
  
  # ...
end
class LimitedWorker
  include ApplicationWorker
  
  concurrency_limit -> { ApplicationSetting.current.elasticsearch_concurrent_sidekiq_jobs }
  
  # ...
end

Geo 보조에서의 worker 실행 건너뛰기

Geo 보조 사이트에서는 데이터베이스 쓰기가 비활성화됩니다. 따라서 Geo 보조 사이트에 대기열에 들어간 경우 데이터베이스 쓰기를 시도하는 작업을 실행하면 안 됩니다. 편리하게도, 대부분의 작업은 대부분의 비-GET HTTP 요청이 Geo 주 사이트로 프록시됨대부분의 Sidekiq-Cron 작업이 비활성화됨 으로 인해 Geo 보조 사이트에 대기열에 들어가는 경우가 대부분 아닙니다. 자세한 내용은 Geo 엔지니어에게 문의하십시오. 실행을 건너뛰려면 worker 클래스에 ::Geo::SkipSecondary 모듈을 먼저 추가하십시오.

class DummyWorker
  include ApplicationWorker
  prepend ::Geo::SkipSecondary
  
  # ...
end