Sidekiq 워커 속성

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

기존 워커를 상속한 자식 클래스들은 이러한 속성을 상속받기 때문에, 값을 재정의하고 싶지 않은 한 재정의할 필요가 없습니다.

작업 긴급도

작업에는 설정된 urgency 속성이 있을 수 있으며, :high, :low, 또는 :throttled가 될 수 있습니다. 이들은 아래의 대상을 갖습니다:

긴급도 큐 스케줄링 대상 수행 지연 시간 요구조건
:high 10초 10초
:low (기본값) 1분 5분
:throttled 없음 5분

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

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

지연에 민감한 작업

대량의 백그라운드 작업이 동시에 예약되면, 작업이 대기할 때 워커 노드가 사용 가능해질 때까지 대기하는 큐잉이 발생할 수 있습니다. 이것은 표준적이며, 시스템이 트래픽 급증을 우아하게 처리함으로써 복원력을 제공합니다. 그러나 몇몇 작업은 다른 작업에 비해 수행 지연 시간에 민감합니다.

일반적으로 지연에 민감한 작업은 사용자가 동기적으로 발생했을 것으로 합리적으로 기대할 수 있는 동작을 비동기적으로 수행하기보다는 동기적으로 수행합니다. 일반적인 예시로는 동작 이후에 쓰기를 하는 작업 등이 있습니다. 이러한 작업의 예시로는 다음과 같은 것들이 있습니다:

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

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

이러한 작업은 예정된 후 매우 짧은 시간 내에 시작되도록 노력합니다. 그러나 처리량을 보장하기 위해 이러한 작업은 매우 엄격한 실행 기간 요구 조건을 갖습니다:

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

만약 워커가 이러한 기대치를 충족시킬 수 없다면, 이는 urgency :high 워커로 취급할 수 없습니다: 워커를 재설계하거나 urgency :high 코드를 빠르게 실행하는 워커와 실행 지연 시간 요구 조건이 없는 urgency :low를 가진 워커로 분리하는 고려를 하세요.

큐의 긴급도 변경

GitLab.com에서 우리는 Sidekiq를 여러 분할 에서 실행하며, 각각은 특정 유형의 작업 부하를 나타냅니다.

큐의 긴급도를 변경하거나 새로운 큐를 추가할 때는, 새로운 분할의 예상 작업 적용을 고려해야 합니다. 기존 큐를 변경하는 경우 기존 분할에도 영향을 주지만, 그것은 항상 작업 부하를 줄입니다.

이를 위해 새로운 분할에서 총 실행 시간과 RPS(처리량)의 예상 증가를 계산해야 합니다. 우리는 이러한 값들을 다음에서 얻을 수 있습니다:

  • 큐 상세 대시보드에는 해당 큐에 대한 값이 있습니다. 새로운 큐의 경우, 유사한 패턴을 갖거나 유사한 상황에서 예약된 큐를 찾을 수 있습니다.
  • 분할 상세 대시보드에는 총 실행 시간과 처리량(RPS)이 있습니다. 분할 활용률 패널은 현재 이 분할에 대한 여분의 용량이 있는지 여부를 표시합니다.

그런 다음, 새로운 큐를 위한 추정 실행 시간을 위해 RPS * 평균 실행 시간(신규 작업에 대해 추정)을 계산하여 새 분할에서 예상되는 RPS 및 실행 시간 증가를 알아봅니다:

new_queue_consumption = _RPS * _기간_평균
shard_consumption = 분할_RPS * 분할_기간_평균

(new_queue_consumption / shard_consumption) * 100

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

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

외부 의존성을 갖는 작업

GitLab 애플리케이션의 대부분의 백그라운드 작업은 다른 GitLab 서비스와 통신합니다. 예를 들어, PostgreSQL, Redis, Gitaly, 객체 리포지터리 등이 있습니다. 이러한 것들은 작업에 대한 “내부” 의존성으로 간주됩니다.

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

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

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

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

작업은 동시에 높은 긴급도를 갖고 외부 의존성을 가질 수 없습니다.

CPU-bound 및 Memory-bound 워커

CPU 또는 메모리 리소스 제한으로 제약을 받는 워커는 worker_resource_boundary 메소드로 표시되어야합니다.

대부분의 워커들은 주로 다른 서비스(예: Redis, PostgreSQL, Gitaly)에서 네트워크 응답을 기다리는 블록된 상태를 많이 보입니다. Sidekiq는 멀티 스레드 환경이므로, 이러한 작업은 높은 동시성으로 예약될 수 있습니다.

그러나, 어떤 워커들은 루비에서 로직을 실행할 때 많은 시간을 소비합니다. 루비 MRI는 실제 멀티 스레드를 지원하지 않기 때문에 이는 모든 프로세스의 루비 코드 섹션이 한 번에 하나씩만 실행되도록 하는 GIL에 의존합니다. IO 바운드 워커의 경우에는 이는 문제가 되지 않으며, 대부분의 스레드가 GIL 외부의 라이브러리에서 블록되어 있기 때문입니다.

만약 많은 스레드가 동시에 루비 코드를 실행하려고 시도한다면, 이는 모든 프로세스를 느리게 만드는 GIL의 경합을 야기합니다.

고트래픽 환경에서 워커가 CPU-바운드임을 알고 있다면, 낮은 동시성을 갖는 다른 플릿에서 실행할 수 있습니다. 이로써 최적의 성능을 보장합니다.

마찬가지로, 워커가 많은 양의 메모리를 사용한다면, 우리는 이러한 작업을 특수한 낮은 동시성, 높은 메모리 플릿에서 실행할 수 있습니다.

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

만약 워커가 많은 양의 메모리와 CPU 시간을 필요로 한다면, 상기된 memory-bound로 표시되어야 합니다.

CPU-bound Job 선언

이 예시는 작업을 CPU 중심으로 선언하는 방법을 보여줍니다.

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

Worker가 CPU-bound인지 확인하기

다음 접근 방식을 사용하여 worker가 CPU-bound인지 확인합니다:

  • Sidekiq 구조화된 JSON 로그에서 worker의 durationcpu_s 필드를 집계합니다.
  • duration은 작업 실행 총 시간(초)을 나타냅니다.
  • cpu_sProcess::CLOCK_THREAD_CPUTIME_ID 카운터에서 파생되며, 작업이 CPU에서 쓴 시간을 나타냅니다.
  • cpu_sduration으로 나누어 CPU에서 사용된 시간의 백분율을 얻습니다.
  • 이 비율이 33%를 초과하는 경우, worker는 CPU-bound로 간주되어야 하며 그에 맞게 주석이 달려져 있어야 합니다.
  • 이러한 값은 적은 샘플 크기보다는 상당한 양의 집계에 사용되어야 합니다.

기능 범주

모든 Sidekiq worker는 알려진 기능 범주를 정의해야 합니다.

작업 데이터 일관성 전략

GitLab 13.11 및 이전 버전에서 Sidekiq workers는 읽기 및 쓰기 모두에 대해 항상 주 데이터베이스 노드에 대한 쿼리를 전송했습니다. 이는 단일 노드 시나리오에서조차 자신의 쓰기를 읽는 worker도 만나지 못하도록하여 데이터 무결성을 보장하고 즉시 보장했습니다. 그러나 worker가 기본에 쓰고 복제본에서 읽는 경우, 복제본이 기본에 뒤처지는 가능성이 있기 때문에 오래된 레코드를 읽을 가능성이 있습니다.

데이터베이스에 의존하는 작업 수가 증가함에 따라 즉각적인 데이터 일관성을 보장하면 주 데이터베이스 서버에 부담이 될 수 있습니다. 따라서 Sidekiq workers를 위한 데이터베이스 로드 밸런싱 기능을 추가했습니다. worker의 data_consistency 필드를 구성함으로써 스케줄러가 아래에 설명된 여러 전략에 따라 읽기 복제본을 대상으로 할 수 있게 했습니다.

주 데이터베이스 부하를 줄이려면 즉시 제약을 두기

우리는 Sidekiq workers가 모든 읽기 및 쓰기에 기본 데이터베이스 노드를 사용해야 하는지 여부에 대한 명시적인 결정을 내리도록 요구합니다. data_consistency 필드가 설정되었는지 확인하는 RuboCop 규칙을 강제하며, 이를 설정할 때 다음 사항을 고려하세요:

  • 즉시 일관된 읽기를 보장하지만 주 데이터베이스에 부하를 증가시킴.
  • 복제본을 선호하여 주 데이터베이스에 약간의 초기 대기 지연을 견딜 수 있는 작업에 사용. 메모장의 설정을 보류하기 때문에 후속 실패 및 복제본이 아직 뒤처진 상황에서의 처리가 뒤처질 가능성이 있으면 데이터 연기적 설정에 설자. 이러한 시나리오를 피할 수 없는 경우 .01 성능으로 사용 세분화된 시나리오를 사용하여 :always를 유지하기 위한 이유, 데이터 일관성 작업을 위해 제공.

    ::이제 핢습하실 때에는 :sticky하거나 :delayed일관성 설정을 신명하십니다. 이로서 worker는 주 데이터베이스 노드에 의존할 필요가 거의 없게 됩니다.

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

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

feature_flag 속성

feature_flag 속성을 사용하면 작업의 data_consistency를 전환할 수 있어 특정 작업에 대한 로드 밸런싱 기능을 안전하게 전환할 수 있습니다.

feature_flag가 비활성화되면 작업은 항상 주 데이터베이스를 사용하는 :always를 기본값으로 사용합니다.

feature_flag 속성은 작업자 기반 기능 누름을 통한 특집 봇이 제공되지 않았다는 것을 의미합니다. 이것은 특집 봇이 특정 프로젝트, 그룹 또는 사용자에 대해서만 특집이 헤제됩니다. 대신 시간 배포를 안전하게 사용할 수 있습니다. 우리는 Sidekiq client와 server 모두에서 특집 깃을 분석하므로 10% 시간된 롤링은 복제본을 사용하도록 결정된프 사례가 많이 발생합니다.

예시:

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

Idempotent jobs와 데이터 일관성

idempotent jobs에 대해 :sticky 또는 :delayed 데이터 일관성을 선언하는 경우, 우리는 중복을 제거하면서 최신 WAL 위치를 보존하고, 완전히 catch-up된 레플리카에서 읽는 것을 보장합니다. idempotent jobs를 위해 최신 WAL 위치를 보존

작업 일시 중지 제어

pause_control 속성을 사용하면 조건에 따라 작업 처리를 일시 중지할 수 있습니다. 전략이 활성화된 경우 작업은 별도의 ZSET에 저장되며, 전략이 비활성화되면 다시 대기열에 추가됩니다. PauseControl::ResumeWorker은 일시 중지된 작업을 다시 시작해야하는지 확인하는 크론 워커입니다.

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는 쿼터를 확인하여 쓰로틀된 작업이 있는지 확인하는 크론 워커입니다.

정의된 동시성 제한을 초과하는 첫 번째 작업이 다른 작업의 쓰로틀링 프로세스를 초기화합니다. 이 일이 발생할 때까지 작업은 일반적으로 예약 및 실행됩니다.

쓰로틀링이 시작되면 새롭게 예약되고 실행되는 작업은 실행 순서가 보존되도록 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 보조 사이트에서 데이터베이스 쓰기를 시도하는 worker 실행을 건너뛰어야 합니다. 대부분의 worker가 Geo 보조 사이트에 대기열에 들어가지 않습니다. 왜냐하면 대부분의 non-GET HTTP 요청이 Geo 주 사이트로 프록시됩니다, 그리고 Geo 보조 사이트에서 대부분의 Sidekiq-Cron 작업이 비활성화되기 때문입니다. 의심스러운 경우 Geo 엔지니어에게 문의하세요. 실행을 건너뛰려면, worker 클래스에 ::Geo::SkipSecondary 모듈을 선행시킵니다.

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