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 * 평균 실행 시간(새 작업에 대해 추정함)보다 낮은지 계산합니다:

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(Servive Level Objective)을 제공하지 않으며, 따라서 이러한 작업의 실행 지연을 보장할 수 없습니다. 실행 지연을 보장할 수 없기 때문에 우리는 처리량을 보장할 수 없으며 따라서 고트래픽 환경에서는 외부 종속성을 갖는 작업을 높은 긴급도 작업에서 분리하여 해당 큐의 처리량을 보장해야 합니다.
  2. 외부 종속성을 갖는 작업의 오류는 경고 임계값이 더 높습니다. 외부 종속성이 오류의 원인일 가능성이 있기 때문입니다.
class ExternalDependencyWorker
  include ApplicationWorker

  # 성공적으로 완료하려면
  # 타사, 외부 서비스에 의존한다는 것을 선언합니다
  worker_has_external_dependencies!

  # ...
end

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

CPU-bound과 Memory-bound Workers

CPU 또는 메모리 리소스 제한으로 인해 제약을 받는 워커는 worker_resource_boundary 메소드로 주석을 다는 것이 좋습니다.

대부분의 워커는 주로 기다림 상태로 시간을 보내며, Redis, PostgreSQL 및 Gitaly와 같은 다른 서비스로부터의 네트워크 응답을 기다립니다. Sidekiq는 멀티 스레드 환경이기 때문에 이러한 작업은 높은 동시성으로 예약될 수 있습니다.

그러나 일부 워커는 Ruby에서 CPU 상에서 로직을 실행하는 데 많은 시간을 소비합니다. Ruby MRI는 실제 다중 스레딩을 지원하지 않으며, 프로세스에 있는 루비 코드 섹션 하나만 어떤 코어 수에 관계없이 실행될 수 있도록 하는 GIL을 의존합니다. IO 바운드 워커의 경우 이것은 문제가 되지 않지만, 대부분의 스레드가 GIL 외부에서 차단된 라이브러리에 있기 때문입니다.

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

고트래픽 환경에서 워커가 CPU-bound임을 알면 더 낮은 동시성을 갖는 다른 플릿에서 실행하여 최적의 성능을 보장할 수 있습니다.

마찬가지로, 워커가 큰 양의 메모리를 사용하는 경우, 이를 특별 제작된 저 동시성, 고 메모리 플릿에서 실행할 수 있습니다.

메모리-bound 워커는 가벼운 GC 작업을 생성하여 10-50ms의 지연을 야기합니다. 이것은 워커의 대기 시간 요구 사항에 영향을 미칩니다. 따라서 ‘메모리’ 바운드, ‘긴급 :높음’ 작업은 허용되지 않고 CI에서 실패합니다. 일반적으로 ‘메모리’ 바운드 워커는 권장되지 않으며, 작업 처리에 대한 대안적인 방법을 고려해야 합니다.

워커가 많은 양의 메모리와 CPU 시간을 필요로 하는 경우, 위에서 설명한 고 메모리 바운드 워커에 대한 제약 때문에 메모리-bound로 표시해야 합니다.

Job을 CPU-bound로 선언

다음 예제는 작업을 CPU-bound로 선언하는 방법을 보여줍니다.

class CPUIntensiveWorker
  include ApplicationWorker

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

  # ...
end

워커가 CPU-bound인지 확인

워커가 CPU-bound인지 확인하는 데 다음 접근 방식을 사용합니다.

  • Sidekiq 구조화된 JSON 로그에서 워커 “지속 시간” 및 “cpu_s” 필드를 집계합니다.
  • “지속 시간”은 작업 실행 시간(초)을 나타냅니다.
  • “cpu_s”는 Process::CLOCK_THREAD_CPUTIME_ID 카운터에서 파생되며, 작업이 CPU에서 소요한 시간을 나타냅니다.
  • “cpu_s”를 “duration”으로 나누어 CPU에서 소요한 시간의 백분율을 얻습니다.
  • 이 비율이 33%를 초과하면 워커가 CPU-bound로 간주되고 그에 맞게 주석이 달아져야 합니다.
  • 이 값들은 작은 샘플 크기보다는 상당히 큰 집계에서 사용해야 합니다.

기능 카테고리

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

작업 데이터 일관성 전략

GitLab 13.11 및 이전 버전에서, Sidekiq 워커는 읽기와 쓰기 모두를 주 데이터베이스 노드에 보냈습니다. 이는 단일 노드 시나리오에서 자체 작업을 읽는 워커조차도 오래된 읽기 문제를 만날 수 없도록 보장했습니다. 그러나 작업자가 주 노드에 쓰고, 대체로 복제본에서 읽는 경우, 복제본이 기본과의 동기화가 뒤처질 수 있기 때문에 오래된 기록을 읽을 수 있는 가능성이 있습니다.

데이터베이스에 의존하는 작업의 수가 증가함에 따라 즉시 데이터 일관성을 보장하는 것은 주 데이터베이스 서버에 지나치게 부담을 줄 수 있습니다. 따라서 Sidekiq 워커를 위한 데이터베이스 부하 분산 기능을 추가했습니다. 작업자의 data_consistency 필드를 구성함으로써 스케줄러가 아래에 설명된 여러 전략에 따라 읽기 복제본에 대상화할 수 있습니다.

기본 부하 감소를 위한 즉시 교환

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

이 필드를 설정할 때 다음 교환이 고려되어야 합니다.

  • 즉시 일관된 읽기를 보장하되, 주 데이터베이스에 부담을 줄이기.
  • 기본에 완전히 있어 다시 시도해야 할 오래된 읽기의 가능성을 증가시키되, 복제본을 선호.

이 필드가 도입되기 전과 비슷한 행동을 유지하기 위해서는 :always로 설정하여 작업자가 주로 또는 배타적으로 쓰기를 수행하거나 자체 기록을 읽는 경우에도 빠져나 는 데이터 일관성이 요구될 수 있는 작업에 사용해야 합니다. 이러한 시나리오를 피하려고 노력하십시오. 왜냐하면 :always는 규칙이 아닌 예외로 간주되어야 하기 때문입니다.

본딩된 데이터 일관성 모드

언제든지 작업자가 중요할 수 있는 주 데이터베이스 노드를 사용해야 하는지, 아니면 읽기를 복제본에서 수행할 수 있는지를 나타내는 :sticky:delayed 두 개의 추가적인 일관성 모드를 추가했습니다. RuboCop 규칙은 :always 데이터 일관성 모드가 사용될 때 개발자에게 상기시킵니다. 작업자가 주 데이터베이스를 필요로 하는 경우, 줄에 있는 규칙을 비활성화할 수 있습니다.

: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%를 롤아웃하면 대부분의 작업이 레플리카를 사용하는 1%(0.1[클라이언트 기준]*0.1[서버 기준])의 효과적인 작업이 발생할 것으로 예상됩니다.

예시:

class DelayedWorker
  include ApplicationWorker

  data_consistency :delayed, feature_flag: :load_balancing_for_delayed_worker

  # ...
end

멱등 작업과 데이터 일관성

idempotent jobs을 선언하는 :sticky 또는 :delayed 데이터 일관성을 가진 작업의 경우, 멱등 작업을 위한 최신 WAL 위치를 보존하여 중복을 제거하면서 완전히 catch-up된 레플리카에서 읽도록 보장합니다.

작업 일시 중지 제어

pause_control 속성을 사용하면 작업 처리를 조건부로 일시 중지할 수 있습니다. 전략이 활성화된 경우 작업은 별도의 ZSET에 저장되며 전략이 비활성화되면 재진열됩니다. PauseControl::ResumeWorker는 일시 중지된 작업을 다시 시작해아하는 cron 작업입니다.

pause_control을 사용하려면 다음을 수행할 수 있습니다:

  • lib/gitlab/sidekiq_middleware/pause_control/에 정의된 전략 중 하나를 사용합니다.
  • 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가 다시 비워지면 스로틀링 프로세스가 종료됩니다.

경고: 제한을 초과하는 지속적인 작업이 있으면 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 복제본 사이트에 대기열에 들어가지 않습니다. 왜냐하면 대부분의 GET이 아닌 HTTP 요청은 Geo 주 사이트로 프록시됩니다. 그리고 Geo 복제본 사이트에서는 대부분의 Sidekiq-Cron 작업이 비활성화됩니다. 확실하지 않다면 Geo 엔지니어에게 문의하십시오. 실행을 건너뛰려면 worker 클래스에 ::Geo::SkipSecondary 모듈을 먼저 추가하십시오.

class DummyWorker
  include ApplicationWorker
  prepend ::Geo::SkipSecondary

 # ...
end