InfoGrab DocsInfoGrab Docs

백그라운드 작업(Background operations)

요약

이 프레임워크는 초기 배포 단계에 있습니다. 백그라운드 작업은 GitLab 데이터베이스에서 대규모 데이터 작업을 수행하기 위한 프레임워크를 제공합니다. 릴리스에 묶인 일회성 데이터 마이그레이션에는 배치 백그라운드 마이그레이션을 대신 사용합니다.

Warning

이 프레임워크는 초기 배포 단계에 있습니다. 도입할 때는 #g_database_architecture Slack 채널로 문의합니다.

백그라운드 작업은 GitLab 데이터베이스에서 대규모 데이터 작업을 수행하기 위한 프레임워크를 제공합니다. 업그레이드 중에 한 번 실행되어 완료되는 배치 백그라운드 마이그레이션(BBM)과 달리, 백그라운드 작업은 크론으로 반복 예약된 실행과 .enqueue API를 통한 온디맨드 프로그래밍 방식 실행을 모두 지원합니다.

릴리스에 묶인 일회성 데이터 마이그레이션에는 배치 백그라운드 마이그레이션을 대신 사용합니다.

백그라운드 작업을 사용하는 경우#

단일 실행 구간 안에 끝낼 수 없는 대규모 테이블의 데이터 작업을 수행해야 할 때 백그라운드 작업을 사용합니다.

백그라운드 작업이 적합한 경우는 다음과 같습니다.

  • 반복 일정에 따라 행을 삭제하거나 업데이트하는 경우(예: 오래된 데이터 정리).
  • 업그레이드 중에만이 아니라 지속적으로 실행해야 하는 데이터 정비를 수행하는 경우.
  • 애플리케이션 코드에서 일회성 대규모 데이터 작업을 프로그래밍 방식으로 트리거하는 경우.
  • 한 번의 처리로는 안전한 실행 시간을 초과하는 고트래픽 테이블을 대상으로 하는 경우.

스키마 변경이나 일반 마이그레이션 시간 제한 안에 완료할 수 있는 작업에는 백그라운드 작업을 사용하지 않습니다.

의사 결정 트리#

Mermaid 다이어그램 (18줄)
소스 코드 보기
flowchart TD
    A["Need to perform a data operation"] --> B{"Does it take more than 20 minutes?"}
    B -->|No| C["Use a regular or post-deployment Rails migration"]
    B -->|Yes| D{"What is the job urgency?"}
    D -->|High urgency| E["Use a Sidekiq worker with high urgency"]
    D -->|Low urgency| F{"Do you need full control over completion, tied to a release?"}
    F -->|Yes| G["Use a batched background migration"]
    F -->|No| S{"What is the 'gitlab_schema' of the 'target' table?"}
    S -->|Cell-local| I["Use background_operation_workers_cell_local"]
    S -->|Organization-based| T{"Is the operation a recurring cron job?"}
    T -->|Yes| V["Use background_operation_workers_cell_local"]
    T -->|No, on-demand| U["Use background_operation_workers.<br>Do not use background_operation_workers_cell_local: the enqueued row is not migrated when the organization moves cells &mdash; data loss."]
click C "query_performance.md" "Query performance guidelines"
click E "../sidekiq/worker_attributes.md#job-urgency" "Job urgency"
click G "batched_background_migrations.md" "Batched background migrations"
click U "#organization-specific"
click V "#cell-local"</code></pre></details></div>

백그라운드 작업의 동작 방식#

백그라운드 작업은 perform 메서드를 정의하는 Gitlab::BackgroundOperation::BaseOperationWorker의 서브클래스입니다. 작업을 예약하는 방법은 두 가지입니다.

  • 크론 기반: 크론 job(Database::BackgroundOperation::CronEnqueueWorker)이 구성된 일정에 따라 작업을 트리거합니다.
  • 온디맨드: 애플리케이션 코드가 Worker.enqueue를 호출해 프로그래밍 방식으로 작업을 생성하고 실행합니다.

각 호출은 커서 기반 키셋 반복으로 행 배치를 처리하고, 마지막 실행이 멈춘 지점부터 이어가며, 하위 배치를 사용자 정의 로직에 전달합니다.

모든 작업 클래스는 Gitlab::BackgroundOperation 네임스페이스에 정의해야 합니다. 파일은 lib/gitlab/background_operation/ 디렉터리에 둡니다.

실행 메커니즘#

백그라운드 작업은 BBM과 동일한 실행 파이프라인 (Scheduler → Orchestrator → Runner → Executor)을 따릅니다. 자세한 내용은 BBM 실행 메커니즘을 참고합니다. 핵심 차이는 백그라운드 작업이 기본 키 범위 배칭 대신 커서 기반 키셋 페이지네이션을 사용한다는 점입니다.

워커 테이블은 잠금 없는 동시 실행을 위해 리스트 파티셔닝되어 있습니다. 미완료 상태에 대한 부분 유니크 인덱스가 동일한 구성의 작업이 중복 실행되는 것을 막습니다.

오래된 파티션(14일 초과)은 해당 워커가 모두 완료되면 자동으로 삭제됩니다.

셀 호환성#

백그라운드 작업은 조직 격리에 적합한 샤딩 키를 갖도록 서로 다른 두 테이블에 저장됩니다.

조직 전용#

조직 전용 작업은 background_operation_workers에 큐잉해야 합니다. 이 테이블에는 organization_id와 user_id가 필요하며, 이 레코드는 non-admin 사용자가 온디맨드로만 생성합니다.

조직 샤딩 키는 큐잉 시 Current.organization으로 설정할 수 있습니다.

사용자가 자신의 조직 안에서 대규모 데이터 작업을 수행하는 동작이 여기에 적합합니다.

예시는 다음과 같습니다.

셀 로컬#

background_operation_workers_cell_local은 조직 컨텍스트가 없는 셀 로컬 작업을 저장합니다. 이 작업은 조직과 연결되지 않으므로 gitlab_shared_cell_local 스키마를 가지며, 조직을 새 셀로 이전할 때 함께 이동하지 않습니다.

이 레코드는 반복 크론 job으로만 생성됩니다.

조직 전반의 대규모 데이터를 다루는 작업(예: 반복 크론 job)이 셀 로컬 워커에 적합합니다.

예시는 다음과 같습니다.

같은 구분이 job 테이블(background_operation_jobs와 background_operation_jobs_cell_local)에도 적용됩니다.

이러한 BO 워커를 만드는 방법에 대한 자세한 내용은 방법 섹션을 참고합니다.

조직이 새 셀로 이전할 때 일어나는 일#

조직 전용:

  • 이전 대상 조직 전용 워커는 소스 셀에서 중지되고, 해당 샤딩 키가 있으므로 함께 이동한 뒤 대상 셀에서 다시 시작됩니다.
  • 또한 이 워커들은 조직이 유지 보수 모드인 동안 처리를 건너뜁니다. 이 동작은 조직 유지 보수 모드가 출시되면 구현됩니다.
  • 이 워커와 관련된 데이터는 대상 셀로 이전됩니다. 조직 이전이 완전히 끝나면 대상 셀에서 이 워커의 실행이 이어집니다.

셀 로컬:

조직이 유지 보수 모드에 들어가면 백그라운드 작업 스케줄러가 기능 플래그로 일시 중지되고 소스 셀에서 큐가 비워집니다. 이전이 끝나면 소스 셀과 대상 셀 양쪽에서 재개됩니다.

셀 로컬 워커는 반복 크론 job(work_items/603423)으로만 생성되므로, 이후 크론 job이 대상 셀에서 처리되지 않은 데이터를 처리합니다.

그룹을 조직으로 이전#

그룹이 조직으로 이전될 때 조직 전용 백그라운드 워커를 처리해야 합니다.

조직 전용 백그라운드 작업을 관리자가 아닌 사용자로만 제한하면 background_operation_workers에 non-admin 사용자만 남으며, TLG가 조직으로 이전될 때 해당 사용자들의 background_operation_workers는 새 organization_id로 업데이트됩니다.

참고: work_items/603315

이전 진행 상황에서 재개#

동일한 job_class_name, table_name, column_name, job_arguments로 새 작업이 큐잉되면(중복 감지 참고), 프레임워크는 테이블 전체를 처음부터 다시 스캔하지 않고 이전 작업이 멈춘 지점부터 자동으로 재개합니다.

실행할 때마다 테이블 처음부터 시작해야 하는 작업은 작업 클래스에 reset_cursor!를 선언할 수 있습니다.

class MyOperation < BaseOperationWorker
  operation_name :delete_all
  cursor :id
  reset_cursor!

  def perform
    each_sub_batch { |sub_batch| sub_batch.delete_all }
  end
end

또는 .enqueue를 호출할 때 min_cursor를 명시적으로 전달합니다.

중복 감지#

미완료 상태(queued, active, on_hold)에 대한 부분 유니크 인덱스가 동일한 구성의 작업이 동시에 실행되는 것을 막습니다. 같은 테이블과 칼럼 범위에서 중복 작업을 실행하면 불필요한 작업이 생기고 데이터베이스 부하가 늘며, 같은 행에 대한 동시 변경으로 데이터 정합성 문제가 생길 위험이 있기 때문입니다.

.enqueue를 사용하면 프레임워크가 동일한 구성(job_class_name, table_name, column_name, job_arguments)의 미완료 작업이 있는지 확인합니다. 중복이 발견되면 큐잉을 건너뛰고 경고를 기록합니다. finished 또는 failed 상태의 작업은 새 큐잉을 막지 않습니다.

멱등성#

백그라운드 작업 워커는 Sidekiq 안에서 실행됩니다. job은 멱등이어야 합니다. 같은 행을 다시 처리해도 같은 결과가 나오도록 perform 메서드를 설계합니다.

스로틀링과 격리#

백그라운드 작업은 BBM과 동일한 데이터베이스 상태 점검과 격리 제약을 공유합니다.

방법#

크론으로 예약(반복 작업)#

고정된 간격으로 무기한 실행해야 하는 작업에는 크론 예약을 사용합니다. 예를 들어 만료된 데이터를 매시간 정리하는 경우가 있습니다.

1. 작업 클래스 정의#

lib/gitlab/background_operation/에 파일을 만듭니다.

# frozen_string_literal: true

module Gitlab
  module BackgroundOperation
    class PurgeExpiredTokens < BaseOperationWorker
      operation_name :delete_all
      feature_category :system_access
      cursor :id

      def perform
        each_sub_batch do |sub_batch|
          sub_batch.where(revoked: true).delete_all
        end
      end
    end
  end
end

주요 DSL 메서드는 다음과 같습니다.

  • operation_name: SQL 작업을 설명하는 심벌(예: :delete_all, :update_all). 계측에 사용됩니다.
  • feature_category: 이 작업을 소유한 기능 카테고리.
  • cursor: 키셋 페이지네이션에 사용하는 하나 이상의 칼럼 이름. 테이블의 기본 키를 사용합니다. 복합 기본 키의 경우: cursor :partition_id, :id.
  • scope_to: 배칭 수준과 하위 배치 수준 양쪽에서 릴레이션을 필터링하는 람다. scope_to로 행 필터링을 참고합니다.
  • reset_cursor!: 이전 워커의 max_cursor부터 재개하는 대신 실행할 때마다 커서를 MIN(column)으로 초기화합니다. 시간에 의존하는 필터와 함께 사용합니다.

2. 크론 job 구성#

config/schedule.yml(FOSS) 또는 ee/config/schedule.yml(EE)에 항목을 추가합니다.

bbo_users_delete_unconfirmed_secondary:
  class: Database::BackgroundOperation::CronEnqueueWorker
  cron: "0 * * * *"
  args:
    job_class_name: UsersDeleteUnconfirmedSecondaryEmails
    table_name: emails
    column_name: id

구성 필드는 다음과 같습니다.

  • class: 항상 Database::BackgroundOperation::CronEnqueueWorker.
  • cron: 일정에 사용하는 표준 크론 표현식.
  • args: 다음 항목을 담은 해시:
    • job_class_name: 작업의 클래스 이름(Gitlab::BackgroundOperation:: 접두사 제외).
    • table_name: 반복할 데이터베이스 테이블.
    • column_name: 커서 기반 반복에 사용하는 칼럼.

enqueue로 예약(온디맨드 작업)#

프로그래밍 방식으로 트리거하는 작업에는 .enqueue를 사용합니다. 예를 들어 애플리케이션 로직이나 서비스가 시작하는 일괄 정리 작업이 있습니다.

Gitlab::Database::BackgroundOperation::Worker.enqueue(
  'MyOperationClass',
  'target_table',
  'id',
  job_arguments: %w[arg1 arg2],
  user: current_user,
  organization: Current.organization
)

파라미터는 다음과 같습니다.

  • job_class_name: 작업 클래스 이름.
  • table_name: 반복할 데이터베이스 테이블.
  • column_name: 커서 칼럼.
  • job_arguments(선택): 문자열 인수 배열. 기본값은 [].
  • min_cursor(선택): 시작 커서 위치를 지정하는 배열. 생략하면 프레임워크는 이전 작업의 마지막 커서부터 재개하거나 MIN(column)으로 대체합니다.
  • user: 작업을 시작한 사용자.
  • organization: 사용자가 트리거한 동작이므로 Current.organization을 이미 사용할 수 있으며 이를 함께 전달해야 합니다.

프레임워크는 중복을 자동으로 확인하고 pg_class로 total_tuple_count를 추정하며, 기본 배치 파라미터를 설정하고 테이블의 gitlab_schema를 기준으로 올바른 데이터베이스 연결을 결정합니다.

조직 컨텍스트가 없는 작업에는 WorkerCellLocal을 사용합니다.

Gitlab::Database::BackgroundOperation::WorkerCellLocal.enqueue(
  'MyOperationClass',
  'target_table',
  'id'
)

scope_to로 행 필터링#

작업이 행의 일부만 대상으로 할 때는 scope_to를 사용합니다. 이 필터는 배치 경계 계산과 하위 배치 반복 양쪽에 적용되므로, 배칭 전략이 일치하지 않는 행을 아예 건너뜁니다.

scope_to가 없으면 필터링은 each_sub_batch 안에서 이루어지고 배치는 여전히 전체 기본 키 범위를 다룹니다.

Warning

범위를 지정하는 조건에는 인덱스가 뒷받침되어야 합니다. 이를 뒷받침하는 인덱스가 없으면 scope_to는 배칭 성능을 떨어뜨립니다.

scope_to ->(relation) { relation.where('expires_at < ?', Time.current) }

def perform
  each_sub_batch do |sub_batch|
    sub_batch.delete_all
  end
end

이 람다는 job 인스턴스의 컨텍스트에서 실행되므로 인스턴스 메서드와 job 인수에 접근할 수 있습니다.

Note

affected_rows 메트릭이 기록되려면 each_sub_batch 블록이 정수를 반환해야 합니다. delete_all 같은 메서드는 기본적으로 정수를 반환합니다. 삭제 후 로직을 추가한다면 개수를 명시적으로 반환합니다.

each_sub_batch do |sub_batch|
  deleted = sub_batch.delete_all
  do_something_else
  deleted
end

기본적으로 반복 작업은 이전 워커의 max_cursor부터 재개합니다. 대신 MIN(column)부터 시작하려면 reset_cursor!를 사용합니다. 이렇게 하면 실행 사이에 대상이 된 행을 건너뛰는 일을 방지할 수 있습니다 (예: created_at < 3.days.ago 같은 시간 의존 필터).

scope_to ->(relation) { relation.where('expires_at < ?', Time.current) }
reset_cursor!

모니터링#

백그라운드 작업은 관측 가능성을 위해 구조화된 로그와 Prometheus 메트릭을 내보냅니다.

구조화된 로그#

프레임워크는 상태 전환과 배치 크기 최적화 시 Gitlab::AppLogger에 이벤트를 기록합니다. 다음 message 값으로 필터링합니다.

  • background_operation_worker_transition_event: 작업의 상태가 바뀔 때 기록됩니다(예: queued → active, active → finished). job_class_name, table_name, previous_state, new_state를 포함합니다.
  • background_operation_job_transition_event: 개별 job의 상태가 바뀔 때 기록됩니다. 실패 시 attempts, exception_class, exception_message를 포함합니다.
  • background_operation_worker_optimization_event: 배치 크기가 조정될 때 기록됩니다. old_batch_size와 new_batch_size를 포함합니다.

Prometheus 메트릭#

다음 메트릭은 job이 실행될 때마다 내보내집니다.

메트릭 타입 설명
background_operation_job_batch_size Gauge 현재 배치 크기
background_operation_job_sub_batch_size Gauge 현재 하위 배치 크기
background_operation_job_interval_seconds Gauge 배치 간 간격
background_operation_job_duration_seconds Gauge 마지막 job의 실행 시간
background_operation_job_updated_tuples_total Counter 누적 처리 튜플 수
background_operation_job_query_duration_seconds Histogram 작업별 쿼리 소요 시간
background_operation_worker_migrated_tuples_total Gauge 지금까지 이전된 총 튜플 수
background_operation_worker_total_tuple_count Gauge 처리할 것으로 추정되는 총 튜플 수
background_operation_worker_last_update_time_seconds Gauge 마지막 업데이트의 Unix 타임스탬프

모든 메트릭에는 migration_id와 migration_identifier (job_class_name/table_name.column_name) 레이블이 붙습니다.

백그라운드 작업(Background operations)

GitLab v19.5
원문 보기

요약

이 프레임워크는 초기 배포 단계에 있습니다. 백그라운드 작업은 GitLab 데이터베이스에서 대규모 데이터 작업을 수행하기 위한 프레임워크를 제공합니다. 릴리스에 묶인 일회성 데이터 마이그레이션에는 배치 백그라운드 마이그레이션을 대신 사용합니다.

Warning

이 프레임워크는 초기 배포 단계에 있습니다. 도입할 때는 #g_database_architecture Slack 채널로 문의합니다.

백그라운드 작업은 GitLab 데이터베이스에서 대규모 데이터 작업을 수행하기 위한 프레임워크를 제공합니다. 업그레이드 중에 한 번 실행되어 완료되는 배치 백그라운드 마이그레이션(BBM)과 달리, 백그라운드 작업은 크론으로 반복 예약된 실행과 .enqueue API를 통한 온디맨드 프로그래밍 방식 실행을 모두 지원합니다.

릴리스에 묶인 일회성 데이터 마이그레이션에는 배치 백그라운드 마이그레이션을 대신 사용합니다.

백그라운드 작업을 사용하는 경우#

단일 실행 구간 안에 끝낼 수 없는 대규모 테이블의 데이터 작업을 수행해야 할 때 백그라운드 작업을 사용합니다.

백그라운드 작업이 적합한 경우는 다음과 같습니다.

  • 반복 일정에 따라 행을 삭제하거나 업데이트하는 경우(예: 오래된 데이터 정리).
  • 업그레이드 중에만이 아니라 지속적으로 실행해야 하는 데이터 정비를 수행하는 경우.
  • 애플리케이션 코드에서 일회성 대규모 데이터 작업을 프로그래밍 방식으로 트리거하는 경우.
  • 한 번의 처리로는 안전한 실행 시간을 초과하는 고트래픽 테이블을 대상으로 하는 경우.

스키마 변경이나 일반 마이그레이션 시간 제한 안에 완료할 수 있는 작업에는 백그라운드 작업을 사용하지 않습니다.

의사 결정 트리#

Mermaid 다이어그램 (18줄)
소스 코드 보기
flowchart TD
    A["Need to perform a data operation"] --> B{"Does it take more than 20 minutes?"}
    B -->|No| C["Use a regular or post-deployment Rails migration"]
    B -->|Yes| D{"What is the job urgency?"}
    D -->|High urgency| E["Use a Sidekiq worker with high urgency"]
    D -->|Low urgency| F{"Do you need full control over completion, tied to a release?"}
    F -->|Yes| G["Use a batched background migration"]
    F -->|No| S{"What is the 'gitlab_schema' of the 'target' table?"}
    S -->|Cell-local| I["Use background_operation_workers_cell_local"]
    S -->|Organization-based| T{"Is the operation a recurring cron job?"}
    T -->|Yes| V["Use background_operation_workers_cell_local"]
    T -->|No, on-demand| U["Use background_operation_workers.<br>Do not use background_operation_workers_cell_local: the enqueued row is not migrated when the organization moves cells &mdash; data loss."]
click C "query_performance.md" "Query performance guidelines"
click E "../sidekiq/worker_attributes.md#job-urgency" "Job urgency"
click G "batched_background_migrations.md" "Batched background migrations"
click U "#organization-specific"
click V "#cell-local"</code></pre></details></div>

백그라운드 작업의 동작 방식#

백그라운드 작업은 perform 메서드를 정의하는 Gitlab::BackgroundOperation::BaseOperationWorker의 서브클래스입니다. 작업을 예약하는 방법은 두 가지입니다.

  • 크론 기반: 크론 job(Database::BackgroundOperation::CronEnqueueWorker)이 구성된 일정에 따라 작업을 트리거합니다.
  • 온디맨드: 애플리케이션 코드가 Worker.enqueue를 호출해 프로그래밍 방식으로 작업을 생성하고 실행합니다.

각 호출은 커서 기반 키셋 반복으로 행 배치를 처리하고, 마지막 실행이 멈춘 지점부터 이어가며, 하위 배치를 사용자 정의 로직에 전달합니다.

모든 작업 클래스는 Gitlab::BackgroundOperation 네임스페이스에 정의해야 합니다. 파일은 lib/gitlab/background_operation/ 디렉터리에 둡니다.

실행 메커니즘#

백그라운드 작업은 BBM과 동일한 실행 파이프라인 (Scheduler → Orchestrator → Runner → Executor)을 따릅니다. 자세한 내용은 BBM 실행 메커니즘을 참고합니다. 핵심 차이는 백그라운드 작업이 기본 키 범위 배칭 대신 커서 기반 키셋 페이지네이션을 사용한다는 점입니다.

워커 테이블은 잠금 없는 동시 실행을 위해 리스트 파티셔닝되어 있습니다. 미완료 상태에 대한 부분 유니크 인덱스가 동일한 구성의 작업이 중복 실행되는 것을 막습니다.

오래된 파티션(14일 초과)은 해당 워커가 모두 완료되면 자동으로 삭제됩니다.

셀 호환성#

백그라운드 작업은 조직 격리에 적합한 샤딩 키를 갖도록 서로 다른 두 테이블에 저장됩니다.

조직 전용#

조직 전용 작업은 background_operation_workers에 큐잉해야 합니다. 이 테이블에는 organization_id와 user_id가 필요하며, 이 레코드는 non-admin 사용자가 온디맨드로만 생성합니다.

조직 샤딩 키는 큐잉 시 Current.organization으로 설정할 수 있습니다.

사용자가 자신의 조직 안에서 대규모 데이터 작업을 수행하는 동작이 여기에 적합합니다.

예시는 다음과 같습니다.

셀 로컬#

background_operation_workers_cell_local은 조직 컨텍스트가 없는 셀 로컬 작업을 저장합니다. 이 작업은 조직과 연결되지 않으므로 gitlab_shared_cell_local 스키마를 가지며, 조직을 새 셀로 이전할 때 함께 이동하지 않습니다.

이 레코드는 반복 크론 job으로만 생성됩니다.

조직 전반의 대규모 데이터를 다루는 작업(예: 반복 크론 job)이 셀 로컬 워커에 적합합니다.

예시는 다음과 같습니다.

같은 구분이 job 테이블(background_operation_jobs와 background_operation_jobs_cell_local)에도 적용됩니다.

이러한 BO 워커를 만드는 방법에 대한 자세한 내용은 방법 섹션을 참고합니다.

조직이 새 셀로 이전할 때 일어나는 일#

조직 전용:

  • 이전 대상 조직 전용 워커는 소스 셀에서 중지되고, 해당 샤딩 키가 있으므로 함께 이동한 뒤 대상 셀에서 다시 시작됩니다.
  • 또한 이 워커들은 조직이 유지 보수 모드인 동안 처리를 건너뜁니다. 이 동작은 조직 유지 보수 모드가 출시되면 구현됩니다.
  • 이 워커와 관련된 데이터는 대상 셀로 이전됩니다. 조직 이전이 완전히 끝나면 대상 셀에서 이 워커의 실행이 이어집니다.

셀 로컬:

조직이 유지 보수 모드에 들어가면 백그라운드 작업 스케줄러가 기능 플래그로 일시 중지되고 소스 셀에서 큐가 비워집니다. 이전이 끝나면 소스 셀과 대상 셀 양쪽에서 재개됩니다.

셀 로컬 워커는 반복 크론 job(work_items/603423)으로만 생성되므로, 이후 크론 job이 대상 셀에서 처리되지 않은 데이터를 처리합니다.

그룹을 조직으로 이전#

그룹이 조직으로 이전될 때 조직 전용 백그라운드 워커를 처리해야 합니다.

조직 전용 백그라운드 작업을 관리자가 아닌 사용자로만 제한하면 background_operation_workers에 non-admin 사용자만 남으며, TLG가 조직으로 이전될 때 해당 사용자들의 background_operation_workers는 새 organization_id로 업데이트됩니다.

참고: work_items/603315

이전 진행 상황에서 재개#

동일한 job_class_name, table_name, column_name, job_arguments로 새 작업이 큐잉되면(중복 감지 참고), 프레임워크는 테이블 전체를 처음부터 다시 스캔하지 않고 이전 작업이 멈춘 지점부터 자동으로 재개합니다.

실행할 때마다 테이블 처음부터 시작해야 하는 작업은 작업 클래스에 reset_cursor!를 선언할 수 있습니다.

class MyOperation < BaseOperationWorker
  operation_name :delete_all
  cursor :id
  reset_cursor!

  def perform
    each_sub_batch { |sub_batch| sub_batch.delete_all }
  end
end

또는 .enqueue를 호출할 때 min_cursor를 명시적으로 전달합니다.

중복 감지#

미완료 상태(queued, active, on_hold)에 대한 부분 유니크 인덱스가 동일한 구성의 작업이 동시에 실행되는 것을 막습니다. 같은 테이블과 칼럼 범위에서 중복 작업을 실행하면 불필요한 작업이 생기고 데이터베이스 부하가 늘며, 같은 행에 대한 동시 변경으로 데이터 정합성 문제가 생길 위험이 있기 때문입니다.

.enqueue를 사용하면 프레임워크가 동일한 구성(job_class_name, table_name, column_name, job_arguments)의 미완료 작업이 있는지 확인합니다. 중복이 발견되면 큐잉을 건너뛰고 경고를 기록합니다. finished 또는 failed 상태의 작업은 새 큐잉을 막지 않습니다.

멱등성#

백그라운드 작업 워커는 Sidekiq 안에서 실행됩니다. job은 멱등이어야 합니다. 같은 행을 다시 처리해도 같은 결과가 나오도록 perform 메서드를 설계합니다.

스로틀링과 격리#

백그라운드 작업은 BBM과 동일한 데이터베이스 상태 점검과 격리 제약을 공유합니다.

방법#

크론으로 예약(반복 작업)#

고정된 간격으로 무기한 실행해야 하는 작업에는 크론 예약을 사용합니다. 예를 들어 만료된 데이터를 매시간 정리하는 경우가 있습니다.

1. 작업 클래스 정의#

lib/gitlab/background_operation/에 파일을 만듭니다.

# frozen_string_literal: true

module Gitlab
  module BackgroundOperation
    class PurgeExpiredTokens < BaseOperationWorker
      operation_name :delete_all
      feature_category :system_access
      cursor :id

      def perform
        each_sub_batch do |sub_batch|
          sub_batch.where(revoked: true).delete_all
        end
      end
    end
  end
end

주요 DSL 메서드는 다음과 같습니다.

  • operation_name: SQL 작업을 설명하는 심벌(예: :delete_all, :update_all). 계측에 사용됩니다.
  • feature_category: 이 작업을 소유한 기능 카테고리.
  • cursor: 키셋 페이지네이션에 사용하는 하나 이상의 칼럼 이름. 테이블의 기본 키를 사용합니다. 복합 기본 키의 경우: cursor :partition_id, :id.
  • scope_to: 배칭 수준과 하위 배치 수준 양쪽에서 릴레이션을 필터링하는 람다. scope_to로 행 필터링을 참고합니다.
  • reset_cursor!: 이전 워커의 max_cursor부터 재개하는 대신 실행할 때마다 커서를 MIN(column)으로 초기화합니다. 시간에 의존하는 필터와 함께 사용합니다.

2. 크론 job 구성#

config/schedule.yml(FOSS) 또는 ee/config/schedule.yml(EE)에 항목을 추가합니다.

bbo_users_delete_unconfirmed_secondary:
  class: Database::BackgroundOperation::CronEnqueueWorker
  cron: "0 * * * *"
  args:
    job_class_name: UsersDeleteUnconfirmedSecondaryEmails
    table_name: emails
    column_name: id

구성 필드는 다음과 같습니다.

  • class: 항상 Database::BackgroundOperation::CronEnqueueWorker.
  • cron: 일정에 사용하는 표준 크론 표현식.
  • args: 다음 항목을 담은 해시:
    • job_class_name: 작업의 클래스 이름(Gitlab::BackgroundOperation:: 접두사 제외).
    • table_name: 반복할 데이터베이스 테이블.
    • column_name: 커서 기반 반복에 사용하는 칼럼.

enqueue로 예약(온디맨드 작업)#

프로그래밍 방식으로 트리거하는 작업에는 .enqueue를 사용합니다. 예를 들어 애플리케이션 로직이나 서비스가 시작하는 일괄 정리 작업이 있습니다.

Gitlab::Database::BackgroundOperation::Worker.enqueue(
  'MyOperationClass',
  'target_table',
  'id',
  job_arguments: %w[arg1 arg2],
  user: current_user,
  organization: Current.organization
)

파라미터는 다음과 같습니다.

  • job_class_name: 작업 클래스 이름.
  • table_name: 반복할 데이터베이스 테이블.
  • column_name: 커서 칼럼.
  • job_arguments(선택): 문자열 인수 배열. 기본값은 [].
  • min_cursor(선택): 시작 커서 위치를 지정하는 배열. 생략하면 프레임워크는 이전 작업의 마지막 커서부터 재개하거나 MIN(column)으로 대체합니다.
  • user: 작업을 시작한 사용자.
  • organization: 사용자가 트리거한 동작이므로 Current.organization을 이미 사용할 수 있으며 이를 함께 전달해야 합니다.

프레임워크는 중복을 자동으로 확인하고 pg_class로 total_tuple_count를 추정하며, 기본 배치 파라미터를 설정하고 테이블의 gitlab_schema를 기준으로 올바른 데이터베이스 연결을 결정합니다.

조직 컨텍스트가 없는 작업에는 WorkerCellLocal을 사용합니다.

Gitlab::Database::BackgroundOperation::WorkerCellLocal.enqueue(
  'MyOperationClass',
  'target_table',
  'id'
)

scope_to로 행 필터링#

작업이 행의 일부만 대상으로 할 때는 scope_to를 사용합니다. 이 필터는 배치 경계 계산과 하위 배치 반복 양쪽에 적용되므로, 배칭 전략이 일치하지 않는 행을 아예 건너뜁니다.

scope_to가 없으면 필터링은 each_sub_batch 안에서 이루어지고 배치는 여전히 전체 기본 키 범위를 다룹니다.

Warning

범위를 지정하는 조건에는 인덱스가 뒷받침되어야 합니다. 이를 뒷받침하는 인덱스가 없으면 scope_to는 배칭 성능을 떨어뜨립니다.

scope_to ->(relation) { relation.where('expires_at < ?', Time.current) }

def perform
  each_sub_batch do |sub_batch|
    sub_batch.delete_all
  end
end

이 람다는 job 인스턴스의 컨텍스트에서 실행되므로 인스턴스 메서드와 job 인수에 접근할 수 있습니다.

Note

affected_rows 메트릭이 기록되려면 each_sub_batch 블록이 정수를 반환해야 합니다. delete_all 같은 메서드는 기본적으로 정수를 반환합니다. 삭제 후 로직을 추가한다면 개수를 명시적으로 반환합니다.

each_sub_batch do |sub_batch|
  deleted = sub_batch.delete_all
  do_something_else
  deleted
end

기본적으로 반복 작업은 이전 워커의 max_cursor부터 재개합니다. 대신 MIN(column)부터 시작하려면 reset_cursor!를 사용합니다. 이렇게 하면 실행 사이에 대상이 된 행을 건너뛰는 일을 방지할 수 있습니다 (예: created_at < 3.days.ago 같은 시간 의존 필터).

scope_to ->(relation) { relation.where('expires_at < ?', Time.current) }
reset_cursor!

모니터링#

백그라운드 작업은 관측 가능성을 위해 구조화된 로그와 Prometheus 메트릭을 내보냅니다.

구조화된 로그#

프레임워크는 상태 전환과 배치 크기 최적화 시 Gitlab::AppLogger에 이벤트를 기록합니다. 다음 message 값으로 필터링합니다.

  • background_operation_worker_transition_event: 작업의 상태가 바뀔 때 기록됩니다(예: queued → active, active → finished). job_class_name, table_name, previous_state, new_state를 포함합니다.
  • background_operation_job_transition_event: 개별 job의 상태가 바뀔 때 기록됩니다. 실패 시 attempts, exception_class, exception_message를 포함합니다.
  • background_operation_worker_optimization_event: 배치 크기가 조정될 때 기록됩니다. old_batch_size와 new_batch_size를 포함합니다.

Prometheus 메트릭#

다음 메트릭은 job이 실행될 때마다 내보내집니다.

메트릭 타입 설명
background_operation_job_batch_size Gauge 현재 배치 크기
background_operation_job_sub_batch_size Gauge 현재 하위 배치 크기
background_operation_job_interval_seconds Gauge 배치 간 간격
background_operation_job_duration_seconds Gauge 마지막 job의 실행 시간
background_operation_job_updated_tuples_total Counter 누적 처리 튜플 수
background_operation_job_query_duration_seconds Histogram 작업별 쿼리 소요 시간
background_operation_worker_migrated_tuples_total Gauge 지금까지 이전된 총 튜플 수
background_operation_worker_total_tuple_count Gauge 처리할 것으로 추정되는 총 튜플 수
background_operation_worker_last_update_time_seconds Gauge 마지막 업데이트의 Unix 타임스탬프

모든 메트릭에는 migration_id와 migration_identifier (job_class_name/table_name.column_name) 레이블이 붙습니다.