업데이트 간 Sidekiq 호환성

Sidekiq 작업의 인수는 실행이 예약될 때 대기열에 저장됩니다. 온라인 업데이트 중에는 다음과 같은 여러 가능한 상황이 발생할 수 있습니다.

  1. 이전 버전의 애플리케이션이 작업을 발행하고, 업그레이드된 Sidekiq 노드에서 실행됩니다.
  2. 업그레이드 전에 대기열에 작업이 추가되지만, 업그레이드 이후에 실행됩니다.
  3. 새 버전의 애플리케이션을 실행하는 노드에서 대기열에 작업이 추가되지만, 이전 버전의 애플리케이션을 실행하는 노드에서 실행됩니다.

새로운 워커 추가

GitLab.com에서는 현재 카나리 스테이지에 Sidekiq 배포가 없습니다. 이는 HTTP 엔드포인트에서 예약할 수 있는 새로운 워커가 카나리에서 예약되지만 전체 프로덕션 배포가 완료될 때까지 Sidekiq에서 실행되지 않을 수 있음을 의미합니다. 이는 작업을 예약하는 것보다 몇 시간 후에 발생할 수 있습니다. 일부 워커에는 문제가 없겠지만 특히 대기 시간에 민감한 작업 같은 경우 사용자 경험을 저해시킬 수 있습니다.

이는 새로운 워커 클래스가 처음 도입될 때에만 해당됩니다. 일반적인 개발 프로세스로 피처 플래그를 사용하는 것을 권장하므로, 새로운 Sidekiq 워커의 예약을 포함해 전체 변경사항을 피처 플래그로 관리하는 것이 가장 좋습니다.

워커의 인수 변경

작업은 애플리케이션의 연속 버전 사이에서 전후 호환되어야 합니다. 인수를 추가하거나 제거하면 모든 Rails 및 Sidekiq 노드가 업데이트된 코드를 보유하기 전에 배포 중 문제가 발생할 수 있습니다.

인수의 사용을 중지하고 제거

perform_asyncperform 메서드에서 인수를 제거하기 전에는 폐기하십시오. 다음 예제는 perform_async 메서드에서 arg2를 폐기한 후 제거하는 방법을 보여줍니다:

  1. 기본값(일반적으로 nil)을 제공하고 주석을 사용하여 해당 인수가 다음 마이너 릴리스에서 폐기될 것임을 표시합니다. (릴리스 M)

    class ExampleWorker
      # 백워드 호환성을 위해 arg2 매개변수 유지.
      def perform(object_id, arg1, arg2 = nil)
        # ...
      end
    end
    
  2. 한 마이너 릴리스 이후에 perform_async에서 해당 인수를 사용하지 않도록 합니다. (릴리스 M+1)

    ExampleWorker.perform_async(object_id, arg1)
    
  3. 다음 주요 릴리스에서 워커 클래스에서 값을 제거합니다. (다음 주요 릴리스)

    class ExampleWorker
      def perform(object_id, arg1)
        # ...
      end
    end
    

인수 추가

Sidekiq 워커에 안전하게 새로운 인수를 추가하는 두 가지 옵션이 있습니다:

  1. 새로운 인수를 먼저 워커에 추가하는 다단계 배포 설정.
  2. 추가적인 인수에 대한 매개변수 해시 사용. 이것이 가장 유연한 옵션일 수 있습니다.

다단계 배포

이 방법은 여러 릴리스가 필요합니다.

  1. 기본값을 사용하여 워커에 새로운 인수를 추가합니다 (릴리스 M).

    class ExampleWorker
      def perform(object_id, new_arg = nil)
        # ...
      end
    
  2. 모든 워커 호출에 새로운 인수를 추가합니다 (릴리스 M+1).

    ExampleWorker.perform_async(object_id, new_arg)
    
  3. 기본값을 제거합니다 (릴리스 M+2).

    class ExampleWorker
      def perform(object_id, new_arg)
        # ...
      end
    

매개변수 해시

기존 워커가 이미 매개변수 해시를 사용하는 경우 여러 릴리스가 필요하지 않습니다.

  1. 워커에서 매개변수 해시를 사용하여 미래에 유연성을 허용합니다.

    class ExampleWorker
      def perform(object_id, params = {})
        # ...
      end
    

워커 클래스 제거

워커 클래스를 제거하려면 두 가지 마이너 릴리스 동안 다음 단계를 따릅니다:

첫 번째 마이너 릴리스에서

  1. 작업을 대기열에 추가하는 코드를 모두 제거합니다.

    예를 들어, 사용자가 상호 작용할 수 있는 UI 컴포넌트나 API 엔드포인트가 있고, 해당 컴포넌트들이 워커 인스턴스가 더는 대기열에 추가되지 않도록 업데이트되거나 제거되었는지 확인합니다.

    이렇게 함으로써 워커 클래스와 관련된 인스턴스가 더 이상 대기열에 추가되지 않도록 할 수 있습니다.

  2. 프론트엔드 및 백엔드 코드가 더 이상 해당 워커가 해야 했던 작업에 의존하지 않도록 확인합니다.
  3. 관련된 워커 클래스에서 perform 메서드의 내용을 무효화(no-op)시키되 모든 매개변수는 그대로 유지합니다.

    예를 들어, 다음과 같은 ExampleWorker를 다룬다면:

      class ExampleWorker
        def perform(object_id)
          SomeService.run!(object_id)
        end
    

    무효화(no-op)를 구현하는 것은 다음과 같을 수 있습니다:

      class ExampleWorker
        def perform(object_id); end
    

    이 무효화를 구현함으로써, 마이너 릴리스 이후에 아직 대기열에 추가된 폐기된 작업이 처리될 때 불필요한 사이클을 피할 수 있습니다.

분리된 이후의 다음 마이너 릴리스에서

  1. 워커 클래스 파일을 삭제하고 관련 파일을 다시 생성/업데이트하는 절차에 대한 지침을 우리의 Sidekiq 대기열 문서의 안내에 따릅니다.
  2. sidekiq_remove_jobs를 사용하는 마이그레이션(배포 후 마이그레이션은 아님)을 추가합니다:

    class RemoveMyDeprecatedWorkersJobInstances < Gitlab::Database::Migration[2.1]
      DEPRECATED_JOB_CLASSES = %w[
        MyDeprecatedWorkerOne
        MyDeprecatedWorkerTwo
      ]
      # `sidekiq_remove_jobs` 메서드를 사용할 때는 반드시 `idle-in-transaction` 타임아웃으로 인한 여러 프로덕션 이슈가 있었기 때문에 `disable_ddl_transaction!`을 항상 사용합니다.
      disable_ddl_transaction!
      def up
        # `sidekiq-cron`을 통해 작업이 예약되었을 경우, 설정 내에서 cron 일정을 정의한 키를 사용하여 예약된 작업 세트에서도 제거해야 합니다. 
        job_to_remove = Sidekiq::Cron::Job.find('my_deprecated_worker')
        # 작업이 완전히 제거될 수 있습니다:
        job_to_remove.destroy if job_to_remove
        # 작업이 비활성화될 수 있습니다:
        job_to_remove.disable! if job_to_remove
           
        # Sidekiq 대기열에서 예약된 인스턴스를 제거합니다.
        sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES)
      end
         
      def down
        # 이 마이그레이션은 폐기된 워커의 모든 인스턴스를 제거하며 되돌릴 수 없습니다.
      end
    end
    

대기열 이름 변경

작업자를 제거하는 것이 위험한 이유와 마찬가지로 대기열 이름을 변경할 때에도 주의를 기울여야 합니다.

대기열 이름을 변경할 때에는 배포 후 마이그레이션(post-deployment migration)에서 sidekiq_queue_migrate 헬퍼 마이그레이션 방법을 사용하세요.

class MigrateTheRenamedSidekiqQueue < Gitlab::Database::Migration[2.1]
  restrict_gitlab_migration gitlab_schema: :gitlab_main
  disable_ddl_transaction!
  
  def up
    sidekiq_queue_migrate 'old_queue_name', to: 'new_queue_name'
  end
  
  def down
    sidekiq_queue_migrate 'new_queue_name', to: 'old_queue_name'
  end
end

대기열 이름을 변경할 때 표준 마이그레이션(standard migration)이 아니라 배포 후 마이그레이션에서 해야 합니다. 그렇지 않으면, 모든 작업자가 해당 작업을 예약한 모든 작업이 중지되기 전에 실행되므로 너무 일찍 실행됩니다. 다른 예제도 참조하세요.