- 작업 긴급성
- 외부 의존성이 있는 작업
- CPU 제한 및 메모리 제한 워커
- 작업을 CPU 제한으로 선언하기
- 작업자가 CPU 바운드인지 결정하기
- 기능 범주
- 작업 데이터 일관성 전략
- 작업 일시 중지 제어
- 동시성 제한
- Geo 2차 사이트에서 작업자 실행 건너뛰기
사이드키크 작업자 속성
작업자 클래스는 동작을 제어하고 메타데이터를 추가하기 위해 특정 속성을 정의할 수 있습니다.
다른 작업자에서 상속된 자식 클래스는 이러한 속성도 상속받으므로, 값을 재정의하려는 경우에만 다시 정의하면 됩니다.
작업 긴급성
작업에는 urgency
속성이 설정될 수 있으며, 이는 :high
, :low
또는 :throttled
일 수 있습니다. 각각의 대상은 아래와 같습니다:
긴급성 | 큐 스케줄링 대상 | 실행 지연 요구사항 |
---|---|---|
:high |
10초 | 10초 |
:low (기본값) |
1분 | 5분 |
:throttled |
없음 | 5분 |
작업의 긴급성을 설정하려면 urgency
클래스 메서드를 사용하세요:
class HighUrgencyWorker
include ApplicationWorker
urgency :high
# ...
end
지연 민감 작업
많은 수의 백그라운드 작업이 동시에 스케줄링되면, 작업이 작업자 노드가 사용 가능해질 때까지 대기하는 동안 큐에 쌓일 수 있습니다. 이는 표준이며, 트래픽의 급증을 우아하게 처리할 수 있도록 시스템의 복원력을 제공합니다. 그러나 일부 작업은 다른 작업보다 지연에 더 민감합니다.
일반적으로 지연 민감 작업은 사용자가 동기식(동시에)으로 발생하기를 합리적으로 기대할 수 있는 작업을 수행하며, 백그라운드 작업에서 비동기식으로 발생하지 않습니다. 일반적인 예는 작업 후 쓰기입니다. 이러한 작업의 예는 다음과 같습니다:
- 브랜치로 푸시한 후 병합 요청을 업데이트하는 작업.
- 브랜치로 푸시한 후 프로젝트에 대한 알려진 브랜치의 캐시를 무효화하는 작업.
- 권한 변경 후 사용자가 볼 수 있는 그룹 및 프로젝트를 재계산하는 작업.
- 파이프라인의 작업 상태 변경 후 CI 파이프라인 상태를 업데이트하는 작업.
이러한 작업이 지연되면 사용자가 이를 버그로 인식할 수 있습니다: 예를 들어, 사용자가 브랜치를 푸시한 다음 해당 브랜치에 대한 병합 요청을 만들려고 할 때 UI에서 브랜치가 존재하지 않는다고 알릴 수 있습니다. 우리는 이러한 작업을 urgency :high
로 간주합니다.
이러한 작업이 스케줄링된 후 매우 짧은 시간 내에 시작되도록 추가적인 노력을 기울입니다. 그러나 처리량을 보장하기 위해 이러한 작업에는 매우 엄격한 실행 시간 요구사항도 있습니다:
- 중간 작업 실행 시간은 1초 미만이어야 합니다.
- 99%의 작업은 10초 이내에 완료되어야 합니다.
작업자가 이러한 기대치를 충족할 수 없다면, 해당 작업자는 urgency :high
작업자로 간주될 수 없습니다: 작업자를 재설계하거나, 하나는 urgency :high
코드가 빠르게 실행되는 작업자와 다른 하나는 실행 지연 요구사항이 없는 urgency :low
작업자 간에 작업을 분리하는 것을 고려하세요.
큐의 긴급성 변경
GitLab.com에서는 여러 샤드에서 Sidekiq를 실행하며, 각 샤드는 특정 유형의 작업량을 나타냅니다.
큐의 긴급성을 변경하거나 새로운 큐를 추가할 때는 새로운 샤드에서 예상 작업량을 고려해야 합니다. 기존 큐를 변경하는 경우에는 오래된 샤드에 영향을 미치지만, 그 경우에는 항상 작업이 줄어듭니다.
이를 위해, 우리는 새로운 샤드에 대한 총 실행 시간 및 RPS(처리량)의 예상 증가량을 계산하고자 합니다. 이 값은 다음에서 얻을 수 있습니다:
- 큐 세부정보 대시보드에는 큐 자체에 대한 값이 있습니다. 새로운 큐의 경우, 유사한 패턴을 가진 큐 또는 유사한 상황에서 스케줄링된 큐를 찾아볼 수 있습니다.
- 샤드 세부정보 대시보드에는 총 실행 시간과 처리량(RPS)이 있습니다. 샤드 사용률 패널은 현재 이 샤드에 여분의 용량이 있는지를 표시합니다.
그런 다음, 큐의 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
에 연락하여 검토를 요청하세요.
외부 의존성이 있는 작업
GitLab 애플리케이션의 대부분의 백그라운드 작업은 다른 GitLab 서비스와 통신합니다. 예를 들어, PostgreSQL, Redis, Gitaly 및 Object Storage입니다. 이러한 것들은 작업에 대해 “내부” 의존성으로 간주됩니다.
그러나 일부 작업은 성공적으로 완료하기 위해 외부 서비스에 의존합니다. 몇 가지 예는 다음과 같습니다:
- 사용자가 구성한 웹 훅을 호출하는 작업입니다.
- 사용자가 구성한 Kubernetes 클러스터에 애플리케이션을 배포하는 작업입니다.
이러한 작업은 “외부 의존성”을 가지고 있습니다. 이는 여러 가지 방법으로 백그라운드 처리 클러스터의 운영에 중요합니다:
-
대부분의 외부 의존성(예: 웹 훅)은 SLO를 제공하지 않으므로, 이러한 작업의 실행 지연 시간을 보장할 수 없습니다. 실행 지연 시간을 보장할 수 없기 때문에, 처리량을 보장할 수 없으며, 따라서 높은 트래픽 환경에서는 외부 의존성이 있는 작업과 높은 긴급 작업이 분리되어야 해당 큐의 처리량을 보장할 수 있습니다.
-
외부 의존성이 있는 작업에서의 오류는 오류의 원인이 외부일 가능성이 높기 때문에 경고 임계값이 더 높습니다.
class ExternalDependencyWorker
include ApplicationWorker
# 이 워커가 성공적으로 완료되기 위해
# 제3자 외부 서비스에 의존함을 선언합니다.
worker_has_external_dependencies!
# ...
end
작업은 높은 우선 순위를 가지면서 외부 의존성을 가질 수 없습니다.
CPU 제한 및 메모리 제한 워커
CPU 또는 메모리 자원 제한에 의해 제약을 받는 워커는
worker_resource_boundary
메서드로 주석을 달아야 합니다.
대부분의 워커는 Redis, PostgreSQL 및 Gitaly와 같은 다른 서비스의 네트워크 응답을 기다리며 대부분의 시간을 차단된 상태로 보냅니다. Sidekiq는 다중 스레드 환경이기 때문에, 이러한 작업은 높은 동시성으로 예약될 수 있습니다.
그러나 일부 워커는 Ruby에서 로직을 실행하며 많은 시간을 CPU에서 보냅니다.
Ruby MRI는 진정한 다중 스레딩을 지원하지 않으며,
GIL에 의존하여
한 번에 하나의 Ruby 코드 섹션만 프로세스에서 실행할 수 있도록 하여 애플리케이션 개발을
크게 단순화합니다. 프로세스를 호스팅하는 기계의 코어 수와 상관없이 말이죠. IO 제한 워커에게는
문제가 되지 않지만, 대부분의 스레드가 GIL 외부의 기본 라이브러리에서 차단됩니다.
많은 스레드가 동시에 Ruby 코드를 실행하려고 시도하면, GIL에서의 경쟁으로 인해 모든 프로세스가 느려지는 효과가 발생합니다.
높은 트래픽 환경에서 작업이 CPU 제한이라는 것을 아는 것은 이를 동시성이 낮은 다른 플릿에서 실행할 수 있게 해줍니다. 이는 최적의 성능을 보장합니다.
마찬가지로, 작업이 많은 메모리를 사용하는 경우, 우리는 이러한 작업을 맞춤형 낮은 동시성, 높은 메모리 플릿에서 실행할 수 있습니다.
메모리 제한 워커는 10-50ms의 중단과 함께 많은 GC 작업을 생성합니다.
이는 작업의 지연 요구 사항에 영향을 미칩니다. 이러한 이유로,
memory
제한, urgency :high
작업은 허용되지 않으며 CI에서 실패합니다.
일반적으로 memory
제한 워커는 권장되지 않으며, 작업을 처리하기 위한
대안적인 접근 방식이 고려되어야 합니다.
작업이 많은 메모리와 CPU 시간을 모두 필요로 하는 경우, 우선 순위가 높은 메모리 제한 워커의 위 제약 때문에 메모리 제한으로 표시해야 합니다.
작업을 CPU 제한으로 선언하기
이 예제는 작업을 CPU 제한으로 선언하는 방법을 보여줍니다.
class CPUIntensiveWorker
include ApplicationWorker
# 이 워커가 CPU에서 많은 계산을
# 수행할 것임을 선언합니다.
worker_resource_boundary :cpu
# ...
end
작업자가 CPU 바운드인지 결정하기
작업자가 CPU 바운드인지 결정하기 위해 다음 방법을 사용합니다:
-
Sidekiq 구조화된 JSON 로그에서 작업자의
duration
및cpu_s
필드를 집계합니다. -
duration
은 총 작업 실행 지속 시간(초)을 나타냅니다. -
cpu_s
는Process::CLOCK_THREAD_CPUTIME_ID
카운터에서 파생되며, CPU에서 작업에 소요된 시간을 측정합니다. -
cpu_s
를duration
으로 나누어 CPU에서 소요된 시간 비율을 계산합니다. -
이 비율이 33%를 초과하면, 작업자는 CPU 바운드로 간주되며 그렇게 주석을 달아야 합니다.
-
이러한 값은 작은 샘플 크기에서는 사용해서는 안 되며, 대신 상당히 큰 집계에 대해 사용해야 합니다.
기능 범주
모든 Sidekiq 작업자는 알려진 기능 범주를 정의해야 합니다.
작업 데이터 일관성 전략
GitLab 13.11 이전에 Sidekiq 작업자는 항상 쓰기 및 읽기 모두를 위한 데이터베이스 쿼리를 기본 데이터베이스 노드에 전송했습니다.
이렇게 하면 데이터 무결성이 보장되고 즉각적이므로, 단일 노드 시나리오에서는 자신이 작성한 내용을 읽는 작업자라도 오래된 읽기가 발생할 수 없습니다.
그러나 작업자가 기본에 쓰기를 하고 복제본에서 읽는 경우, 복제본이 기본보다 뒤처질 수 있으므로 오래된 레코드를 읽을 가능성이 0이 아닙니다.
데이터베이스에 의존하는 작업 수가 증가할 때 즉각적인 데이터 일관성을 보장하면 기본 데이터베이스 서버에 무리가 갈 수 있습니다.
따라서 Sidekiq 작업자를 위한 데이터베이스 로드 밸런싱을 사용할 수 있는 기능을 추가했습니다.
작업자의 data_consistency
필드를 설정함으로써, 스케줄러가 아래 설명된 여러 전략에 따라 읽기 복제본을 타겟팅할 수 있도록 허용할 수 있습니다.
즉각성을 줄여 기본 로드를 줄이는 전략
Sidekiq 작업자에게 모든 읽기 및 쓰기를 위해 기본 데이터베이스 노드를 사용해야 하는지, 아니면 읽기를 복제본에서 처리할 수 있는지를 명시적으로 결정하도록 요구합니다.
이것은 data_consistency
필드가 설정되도록 보장하는 RuboCop 규칙에 의해 시행됩니다.
data_consistency
가 도입되기 전에, 기본 동작은 :always
와 유사했습니다.
이제 작업은 현재 데이터베이스 LSN과 함께 큐에 추가되므로, 복제본(:sticky
또는 :delayed
)은 해당 시점까지 동기화되어 있거나, 작업이 재시도되거나 기본을 사용하게 됩니다.
즉, 데이터는 작업이 큐에 추가된 시점까지 일관성이 보장됩니다.
아래 표는 data_consistency
속성과 그 값을 보여 주며, 읽기 복제본을 선호하는 정도와 복제본이 따라잡을 시간을 기준으로 정렬되어 있습니다:
데이터 일관성 | 설명 | 지침 |
---|---|---|
:always |
작업은 모든 쿼리에 대해 기본 데이터베이스를 사용해야 합니다. (더 이상 사용되지 않음) | 더 이상 사용되지 않음 기본이 고착된 엣지 케이스를 겪는 작업에만 필요합니다. |
:sticky |
작업은 복제본을 선호하지만, 쓰기나 복제 지연이 발생할 경우 기본으로 전환합니다. (기본값) | 이 옵션은 기본값입니다. 작업이 최대한 빨리 실행되어야 하는 경우에 사용해야 합니다. 복제본은 Sidekiq에서 작업이 큐에 추가된 시점까지 동기화되어 있습니다. |
:delayed |
작업은 복제본을 선호하지만, 쓰기 위해 기본으로 전환합니다. 작업 시작 전에 복제 지연이 발생하면 작업이 한 번 재시도됩니다. 복제본이 여전히 최신 상태가 아닐 경우 다음 재시도에서 기본으로 전환합니다. | 실행 지연이 그렇게 중요하지 않은 작업(예: 캐시 만료 또는 웹 훅 실행)에 사용해야 합니다. 재시도가 비활성화된 작업(예: 크론 작업)에는 사용하지 말아야 합니다. |
모든 경우 작업자는 완전히 동기화된 복제본 또는 기본 노드에서 읽기 때문에 데이터 일관성은 항상 보장됩니다.
작업자의 데이터 일관성을 설정하려면 data_consistency
클래스 메서드를 사용하십시오:
class DelayedWorker
include ApplicationWorker
data_consistency :delayed
# ...
end
feature_flag
속성
feature_flag
속성은 작업의 data_consistency
를 전환할 수 있도록 하여, 특정 작업에 대한 로드 밸런싱 기능을 안전하게 전환할 수 있도록 합니다.
feature_flag
가 비활성화되면 작업은 기본적으로 :always
로 설정되며, 이는 작업이 항상 기본 데이터베이스를 사용함을 의미합니다.
feature_flag
속성은
actor 기반의 feature gates의 사용을 허용하지 않습니다.
이는 feature flag가 특정 프로젝트, 그룹 또는 사용자에 대해서만 전환될 수 없음을 의미하며, 대신 사용 비율 롤아웃을 안전하게 사용할 수 있습니다.
우리는 Sidekiq 클라이언트와 서버 모두에서 feature flag를 확인하므로, 10%의 롤아웃은 실제로 1%(0.1
[from client]*0.1
[from server]
)의 효과적인 작업이 복제본을 사용하는 결과를 초래할 가능성이 높습니다.
예시:
class DelayedWorker
include ApplicationWorker
data_consistency :delayed, feature_flag: :load_balancing_for_delayed_worker
# ...
end
우아한 작업을 위한 데이터 일관성
sticky
또는 delayed
데이터 일관성을 선언하는 우아한 작업에서는
우아한 작업을 위한 최신 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
경고:
작업에 대한 미들웨어를 제거하려는 경우, 전략을 :deprecated
로 설정하고 완전히 제거하기 전에 필요한 정지를 기다려야 합니다. 이렇게 하면 모든 일시 중지된 작업이 올바르게 다시 시작됩니다.
동시성 제한
concurrency_limit
속성을 사용하면 작업자의 동시성을 제한할 수 있습니다. 이 제한을 초과하는 작업은 별도의 LIST
에 저장되며 해당 작업의 수가 제한 이하로 떨어질 경우 재큐잉됩니다. ConcurrencyLimit::ResumeWorker
는 어떤 제한된 작업이 다시 재큐잉되어야 하는지 확인하는 크론 작업입니다.
정의된 동시성 제한을 초과하는 첫 번째 작업이 이 클래스의 모든 다른 작업에 대한 스로틀링 프로세스를 시작합니다.
이 과정이 시작될 때까지 작업은 정상적으로 예약되고 실행됩니다.
스로틀링이 시작되면, 새로 예약되고 실행된 작업은 LIST
의 끝에 추가되어 실행 순서가 유지되도록 보장합니다. LIST
가 다시 비게 되면 스로틀링 프로세스가 종료됩니다.
Prometheus 메트릭은 동시성 제한 미들웨어를 사용하여 작업자를 모니터링하기 위해 노출됩니다:
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 2차 사이트에서 작업자 실행 건너뛰기
Geo 2차 사이트에서는 데이터베이스 쓰기가 비활성화됩니다.
Geo 2차 사이트에서 데이터베이스 쓰기를 시도하는 작업자의 실행을 건너뛰어야 합니다.
만약 해당 작업자가 Geo 2차 사이트에서 큐에 추가된다면, 실행을 건너뛰어야 합니다.
편리하게도, 대부분의 작업자는 Geo 2차 사이트에 큐에 추가되지 않습니다.
왜냐하면 대부분의 비-GET HTTP 요청은 Geo 기본 사이트로 프록시되기 때문입니다,
그리고 Geo 2차 사이트는 대부분의 Sidekiq-Cron 작업을 비활성화합니다.
확신이 없다면 Geo 엔지니어에게 문의하세요.
실행을 건너뛰려면 ::Geo::SkipSecondary
모듈을 작업자 클래스에 선행(prepend)합니다.
class DummyWorker
include ApplicationWorker
prepend ::Geo::SkipSecondary
# ...
end