Sidekiq 워커 속성

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

다른 워커를 상속받는 하위 클래스는 이러한 속성을 상속받기 때문에 값들을 재정의하려면 필요할 때만 재정의하면 됩니다.

작업 긴급도

작업에는 설정된 urgency(긴급도) 속성이 있을 수 있으며, :high(높음), :low(기본값), 또는 :throttled(쓸모없는)일 수 있습니다. 아래의 대상을 가지고 있습니다:

긴급도 큐 스케줄링 대상 실행 대기 시간 요구 사항
:high 10초 10초
:low (기본값) 1분 5분
:throttled 없음 5분

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

class HighUrgencyWorker
  include ApplicationWorker

  urgency :high

  # ...
end

대기 시간 민감한 작업

한 번에 많은 백그라운드 작업이 예약될 경우, 작업이 사용 가능한 워커 노드를 기다리는 동안 큐잉이 발생할 수 있습니다. 이것은 일반적인 것이며, 트래픽의 증가를 우아하게 처리하여 시스템의 내구성을 제공합니다. 그러나 일부 작업은 다른 것보다 대기 시간에 더 민감합니다.

일반적으로 대기 시간에 민감한 작업은 사용자가 비동기적으로 예상할 수 있는 작업을 동기적으로 수행합니다. 일반적인 예로는 작성 후에 작성을 따르는 쓰기 작업이 있습니다. 이러한 작업의 예로는 다음과 같은 것들이 있습니다:

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

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

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

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

작업이 이러한 기대치를 충족시키지 못하는 경우, 그것은 urgency :high 워커로 간주되지 않을 것입니다: 워커를 재설계하거나 빠르게 실행되는 urgency :high 코드를 가진 다른 워커와 실행 지연 요구 사항이 없는 urgency :low를 가진 워커 중 두 개로 작업을 분할하는 것을 고려하세요.

큐의 긴급도 변경

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

큐의 긴급도를 변경하거나 새로운 큐를 추가할 때, 새로운 쉬드의 예상 작업 부하를 고려해야 합니다. 기존 큐를 변경하는 경우, 이전 쉬드에도 영향을 미칩니다. 그러나 그것은 항상 작업 부하를 줄입니다.

이를 위해, 새 쉬드에서 총 실행 시간 및 RPS(처리량)의 예상 증가를 계산해야 합니다. 우리는 이러한 값들을 아래에서 얻을 수 있습니다:

  • 큐 상세 대시보드에는 큐 자체의 값들이 있습니다. 새 큐의 경우, 유사한 패턴이나 유사한 환경에서 예약된 큐를 찾을 수 있습니다.
  • 샤드 상세 대시보드에는 총 실행 시간과 처리량(RPS)이 있습니다. 쉬드 활용도 패널은 현재 이 쉬드에 대해 과도한 용량이 있는지 표시합니다.

그러면 우리는 새 큐 사용량을 계산하여 새 큐를 위한 RPS * 평균 실행 시간(새 작업에 대해 예상)을 변경하려는 큐의 RPS * 쉬드 실행 시간의 평균으로 나눈 다음 100을 곱하여 새 쉬드에 대해 기대하는 RPS 및 실행 시간 상대적 증가를 확인할 수 있습니다:

new_queue_consumption = queue_rps * queue_duration_avg
shard_consumption = shard_rps * shard_duration_avg

(new_queue_consumption / shard_consumption) * 100

만약 5% 미만으로 증가한다고 기대한다면, 추가 조치가 필요하지 않습니다.

그렇지 않은 경우에는, 병합 요청에 @gitlab-org/scalability에 알림을 보내어 리뷰를 요청하세요.

외부 종속성이 있는 작업

GitLab 응용 프로그램의 대부분의 백그라운드 작업은 다른 GitLab 서비스와 통신합니다. 예를 들어, PostgreSQL, Redis, Gitaly, 그리고 Object Storage 등이 있습니다. 이것들은 작업의 “내부” 종속성으로 간주됩니다.

그러나 일부 작업은 성공적으로 완료하기 위해 외부 서비스에 의존합니다. 예를 들어 다음과 같은 것들이 있습니다:

  1. 사용자가 구성한 웹 후크를 호출하는 작업.
  2. 사용자가 구성한 Kubernetes 클러스터에 응용 프로그램을 배포하는 작업.

이러한 작업은 “외부 종속성”이 있습니다. 이것은 백그라운드 처리 클러스터의 작동에 있어서 여러 가지 측면에서 중요합니다:

  1. 대부분의 외부 종속성(웹 후크와 같은)은 SLO를 제공하지 않습니다. 따라서 이러한 작업에 대한 실행 대기 시간을 보증할 수 없습니다. 실행 대기 시간을 보증할 수 없기 때문에 우리는 처리량을 보증할 수 없으며, 따라서 고 트래픽 환경에서 외부 종속성이 있는 작업이 고 긴급성 작업과 분리되어야 합니다.
  2. 외부 종속성이 있는 작업에서의 오류에는 더 높은 경고 문턱이 있습니다. 이는 오류의 원인이 외부에 있을 가능성이 높기 때문입니다.
class ExternalDependencyWorker
  include ApplicationWorker

  # 성공적으로 완료하기 위해
  # 제3 자, 외부 서비스에 의존하는
  # 것을 선언합니다
  worker_has_external_dependencies!

  # ...
end

작업은 긴급성이 높고 외부 종속성을 가질 수 없습니다.

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

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

대부분의 워커는 대부분의 시간을 다른 서비스(예: Redis, PostgreSQL 및 Gitaly)에서의 네트워크 응답을 기다리며 차단된 상태로 보냅니다. Sidekiq는 멀티 스레드 환경이기 때문에 이러한 작업들은 높은 병행성으로 예약될 수 있습니다.

그러나 일부 작업들은 Ruby에서 로직을 실행하면서 CPU에서 많은 시간을 보낼 수 있습니다. Ruby MRI는 진정한 멀티 스레딩을 지원하지 않습니다 - 프로세스에서 Ruby 코드의 한 섹션만 한 번에 실행할 수 있게 하는 GIL을 사용하여 개발을 크게 간단화합니다. IO 바운드 워커의 경우, 이것은 문제가 되지 않습니다. 이는 대부분의 스레드가 GIL 외부 라이브러리에서 차단되어 있기 때문입니다.

만약 많은 스레드가 동시에 Ruby 코드를 실행하려고 시도한다면, 이것은 모든 프로세스의 실행을 느리게 하는 효과가 있는 GIL에서 경합으로 이어집니다.

고 트래픽 환경에서 워커가 CPU 바운드임을 알기 때문에 저 병행성을 가진 다른 플릿에서 실행할 수 있습니다. 이러면 최적의 성능이 보장됩니다.

또한, 워커가 많은 양의 메모리를 사용하는 경우, 우리는 이를 독특한 저 병행성, 높은 메모리 플리트에서 실행할 수 있습니다.

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

워커가 많은 양의 메모리 및 CPU 시간을 필요로 하는 경우, 상기된 urgency :high 메모리 바운드 워커에 대한 제한으로 인해 이를 memory 바운드로 표시해야 합니다.

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

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

class CPUIntensiveWorker
  include ApplicationWorker

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

  # ...
end

Worker가 CPU 바운드인지 확인하기

Worker가 CPU 바운드인지 확인하는 방법은 다음과 같습니다:

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

기능 범주

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

작업 데이터 일관성 전략

GitLab 13.11 및 그 이전에는 Sidekiq worker가 읽기 및 쓰기 모두를 주 데이터베이스 노드로 보냈습니다. 이렇게 함으로써 데이터 무결성이 보장되고 즉시 보장되었으며, 단일 노드 시나리오에서 자신의 쓰기를 읽는 worker조차도 무효한 읽기를 만날 수 없습니다. 그러나 worker가 기본으로 쓰고 replica에서 읽는 경우, 복제본이 기본을 따라잡지 못할 가능성이 있으므로 무효한 레코드를 읽을 가능성은 0이 아닙니다.

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

기본 부하를 줄이기 위한 즉각성 교역

Sidekiq worker는 모든 읽기와 쓰기에 기본 데이터베이스 노드를 사용해야 하는지 여부에 대한 명시적인 결정을 내리도록 필요로 합니다. 이것은 RuboCop 규칙에 의해 강제되며, data_consistency 필드가 설정되도록 보장합니다.

data_consistency가 도입되기 전에 기본 동작은 :always와 같았습니다. 현재 작업은 현재 데이터베이스 LSN과 함께 대기열에 등록되므로, 레플리카( ‘sticky’ 또는 ‘delayed’의 경우)는 그 지점에 반드시 따라잡힐 것이고, 그게 아니면 작업은 재시도되거나 기본을 사용합니다. 이것은 작업이 대기열에 등록된 지점까지의 데이터가 일관성이 보장됨을 의미합니다.

아래 표는 data_consistency 속성과 그 값에 따라 읽기 레플리카에 우선권을 부여하고 레플리카가 따라잡을 때까지 기다리는 정도에 따라 정렬된 값들을 보여줍니다:

데이터 일관성 설명 지침
:always 작업은 모든 쿼리에 기본 데이터베이스를 사용해야 함. (폐기 예정) 폐기 예정 기본 인스턴스 붙잡기 주변의 특이한 경우에 필요한 작업을 위해서만 필요합니다.
:sticky 작업은 레플리카를 선호하지만 쓰기나 복제 지연을 만날 때에는 기본으로 전환합니다. (기본값) 이것이 기본 옵션입니다. 가능한 빨리 실행되어야 하는 작업에 사용되어야 합니다. 레플리카는 Sidekiq에 대기열에 등록된 지점까지 따라잡힐 것입니다.
:delayed 작업은 레플리카를 선호하지만 쓰기에는 기본을 사용합니다. 작업 시작 전에 복제 지연을 만나면 작업은 한 번 재시도됩니다. 다음 재시도에서 복제본이 아직 최신 상태가 아니면 기본으로 전환합니다. 이것은 일반적으로 실행을 더 미루는 것이 중요하지 않은 작업에 사용되어야 합니다. 예를 들어 캐시 만료 또는 웹 후크 실행에 사용되어야 하며 cron 작업과 같이 재시도가 비활성화된 작업에는 사용되어서는 안 됩니다.

모든 경우에서 worker는 완전히 따라잡은 레플리카에서 또는 기본 노드에서 읽기 때문에 데이터 일관성은 항상 보장됩니다.

worker에 대한 데이터 일관성을 설정하려면 data_consistency 클래스 메서드를 사용하세요:

class DelayedWorker
  include ApplicationWorker

  data_consistency :delayed

  # ...
end

feature_flag 속성

feature_flag 속성을 사용하면 작업의 data_consistency를 토글하여 특정 작업의 데이터 로드 밸런싱 기능을 안전하게 토글할 수 있습니다. feature_flag가 비활성화된 경우 작업은 기본적으로 :always를 사용하며, 이는 작업이 항상 기본 데이터베이스를 사용함을 의미합니다.

feature_flag 속성은 액터에 기반한 기능 게이트의 사용을 허용하지 않습니다. 즉, 이 기능 게이트는 특정 프로젝트, 그룹 또는 사용자에 대해서만 토글할 수 없지만 대신 시간의 백분율 롤아웃을 안전하게 사용할 수 있습니다. 우리는 Sidekiq 클라이언트와 서버에서 모두 기능 게이트를 확인하기 때문에 10%의 시간 롤아웃을 실행하는 것은, 아마도 1%(0.1 [클라이언트에서]*0.1 [서버에서])의 효과적인 작업이 레플리카를 사용할 가능성이 있습니다.

예시:

class DelayedWorker
  include ApplicationWorker

  data_consistency :delayed, feature_flag: :load_balancing_for_delayed_worker

  # ...
end

멱등 작업과의 데이터 일관성

멱등 작업에 대해서 :sticky 또는 :delayed 데이터 일관성을 선언하는 경우, 멱등적 작업의 최신 WAL 위치를 저장함으로써 중복 확인을 하며 완전히 따라잡힌 레플리카에서 읽도록 하고 있습니다.

작업 일시 중지 제어

pause_control 속성을 사용하면 조건부로 job 처리를 일시 중지할 수 있습니다. 전략이 활성화되는 경우, 작업은 별도의 ZSET에 저장되며 전략이 비활성화될 때 작업이 다시 대기열에 들어갑니다. PauseControl::ResumeWorker는 일정 시간마다 일시 중지된 작업을 다시 시작해야 하는지 확인하는 cron worker입니다.

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

경고: 작업의 미들웨어를 제거하고 싶은 경우 전략을 :deprecated로 설정하여 비활성화하고 완전히 제거하기 전에 필요한 중지를 기다리십시오. 이렇게 하면 모든 일시 중지된 작업이 정확하게 다시 시작되도록 보장할 수 있습니다.

동시성 제한

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

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

쓰로틀링이 시작되면, 새롭게 예약되고 실행되는 작업은 실행 순서가 보장되도록 LIST의 끝에 추가됩니다. LIST가 다시 비어 있게 되면, 쓰로틀링 프로세스가 종료됩니다.

Prometheus 지표를 공개함으로써 동시성 제한 미들웨어를 사용하여 worker를 모니터링할 수 있습니다.

  • sidekiq_concurrency_limit_deferred_jobs_total
  • sidekiq_concurrency_limit_queue_jobs
  • sidekiq_concurrency_limit_max_concurrent_jobs

경고: 제한을 초과하는 지속적인 작업 부하가 발생할 경우, 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의 실행을 건너뛸 필요가 있습니다. Geo 보조 사이트에 대기열에 넣는 이러한 worker의 실행을 건너뛰어야 합니다. 편리하게도 대부분의 worker는 Geo 보조 사이트에 대기열에 들어가지 않습니다. 왜냐하면 대부분의 non-GET HTTP 요청이 Geo 주 사이트로 프록시됩니다니까입니다. 그리고 Geo 보조 사이트는 대부분의 Sidekiq-Cron 작업을 비활성화합니다 Ask a Geo engineer if you are unsure.. 실행을 건너뛰려면 worker 클래스에 ::Geo::SkipSecondary 모듈을 미리 추가하십시오.

class DummyWorker
  include ApplicationWorker
  prepend ::Geo::SkipSecondary

  # ...
end