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. 다음 주요 릴리스에서 worker 클래스에서 해당 값을 제거합니다. (다음 주요 릴리스)

    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
    

    다음과 같이 노옵을 구현할 수 있습니다:

      class ExampleWorker
        def perform(object_id); end
    

    이 노옵을 구현함으로써, 여전히 대기열에 있는 모든 폐기된 작업이 처리되고 나면 불필요한 사이클을 피할 수 있습니다.

다음, 별도의 소규모 릴리스에서

  1. 워커 클래스 파일을 삭제하고 Sidekiq 대기열 문서의 지침을 따라 Rake 작업을 실행하여 관련 파일을 재생성/업데이트합니다.
  2. sidekiq_remove_jobs를 사용하는 마이그레이션(포스트-디플로이먼트 마이그레이션이 아님)을 추가합니다:

    class RemoveMyDeprecatedWorkersJobInstances < Gitlab::Database::Migration[2.1]
      DEPRECATED_JOB_CLASSES = %w[
        MyDeprecatedWorkerOne
        MyDeprecatedWorkerTwo
      ]
      # `sidekiq_remove_jobs` 메서드를 사용하는 동안 항상 `disable_ddl_transaction!`를 사용합니다. 'idle-in-transaction' 타임아웃으로 인해 여러 프로덕션 이슈가 발생했기 때문입니다.
      disable_ddl_transaction!
      def up
        # 작업이 `sidekiq-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
    

대기열 이름 변경

워커를 제거하는 것과 같은 이유로, 대기열 이름을 변경할 때 주의해야 합니다.

대기열 이름을 변경할 때는 포스트-디플로이먼트 마이그레이션에서 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

대기열을 표준 마이그레이션이 아닌 포스트-디플로이먼트 마이그레이션에서 이름을 변경해야 합니다. 그렇지 않으면 이러한 작업을 예약하는 모든 워커가 실행을 중단하기 전에 실행되기 때문입니다. 또한 기타 예시를 참조하십시오.

워커 클래스 이름 변경

이를 워커 추가와 유사하게 처리해야 합니다. 이는 Sidekiq 배포가 완료된 후에 새로운 이름의 워커를 예약하기 시작하는 것을 의미합니다.

애플리케이션의 연속 버전 간에 전후 호환성을 보장하기 위해 세 가지 소규모 릴리스에 걸쳐 다음 단계를 따릅니다:

  1. 새로운 이름의 워커를 생성하고 이전 워커가 새로운 워커의 #perform 메서드를 호출하도록 합니다. 새로운 워커를 예약하는 시기를 제어하기 위해 피처 플래그를 도입합니다. (릴리스 M)

    여전히 대기열에 있는 이전 워커 작업은 새로운 워커로 위임됩니다. 이 버전이 배포되면 예약된 작업의 버전이나 해당 작업을 처리하는 Sidekiq과는 더 이상 관련이 없어집니다. 오래된 Sidekiq는 이전 워커의 완전한 구현을 사용하고, 새로운 Sidekiq는 새 워커로 위임합니다.

  2. GitLab.com에서 피처 플래그를 활성화하고 이후에 기본적으로 활성화되도록 MR을 준비합니다. (릴리스 M+1)
  3. 이전 워커 클래스와 피처 플래그를 제거합니다. (릴리스 M+2)