업데이트 간 Sidekiq 호환성

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

  1. 이전 버전의 응용 프로그램에서 작업을 게시하고 업그레이드된 Sidekiq 노드에서 실행됩니다.
  2. 작업이 업그레이드 전에 대기열에 추가되지만 업그레이드 후 실행됩니다.
  3. 새 버전의 응용 프로그램을 실행 중인 노드에서 대기열에 작업을 추가하지만 이전 버전의 응용 프로그램을 실행 중인 노드에서 실행됩니다.

새 워커 추가

GitLab.com에서는 현재 카나리아 단계에 Sidekiq 배포가 없습니다. 이는 Canary에서 예약할 수 있는 새 워커가 있더라도 전체 프로덕션 배포가 완료될 때까지 Sidekiq에서 실행되지 않을 수 있다는 것을 의미합니다. 작업을 예약하는 것보다 수 시간 늦어질 수 있습니다. 일부 워커에는 문제가 되지 않겠지만 특히 지연에 민감한 작업의 경우 사용자 경험이 나빠질 수 있습니다.

이는 새로운 워커 클래스가 처음 도입될 때만 해당됩니다. 일반적인 개발 프로세스로 기능 플래그를 사용하는 것을 권장하므로 (새로운 Sidekiq 워커 예약 포함하여) 전체 변경을 플래그로 제어하는 것이 가장 좋습니다.

워커의 인수 변경

작업은 응용 프로그램의 연속 버전 간에 전후방호환되어야 합니다. 인수를 추가하거나 제거하면 모든 Rails 및 Sidekiq 노드에 업데이트된 코드가 배포되기 전에 문제가 발생할 수 있습니다.

인수를 폐지하고 제거

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

  1. 기본값(보통 nil)을 제공하고 주석을 사용하여 해당 인수가 다음 소수릴리스에서 폐기될 것이라고 표시하세요. (릴리스 M)

    class ExampleWorker
      # Backwards compatibility를 위해 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
    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
    end
    

워커 클래스 제거

워커 클래스를 제거하려면 두 가지 마이너 릴리스 동안 다음 단계를 수행하세요:

첫 번째 마이너 릴리스에서

  1. 작업을 대기열에 추가하는 모든 코드를 제거하세요.

    예를 들어, 사용자가 상호 작용할 수 있는 UI 구성 요소나 API 엔드포인트가 있어 해당 영역을 제거하거나 업데이트하여 워커 인스턴스가 더 이상 대기열에 추가되지 않도록 합니다.

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

  2. 전담과 백엔드 코드가 더 이상 워커가 수행하던 작업에 의존하지 않도록 합니다.
  3. 관련 있는 워커 클래스에서 perform 메서드의 내용을 노옵(no-op)으로 대체하되 인수는 그대로 유지합니다.

    예를 들어, 다음과 같은 ExampleWorker와 작업 중이라면:

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

    노옵(no-op)을 구현하는 방법은 다음과 같습니다:

      class ExampleWorker
        def perform(object_id); end
      end
    

    이렇게 하면 폐기된 작업이 결국 처리되어 불필요한 사이클을 피할 수 있습니다.

후속으로 별도의 소규모 릴리즈에서

  1. worker 클래스 파일을 삭제하고 Sidekiq 대기열 문서에서 안내된 내용을 따라 Rake 작업을 실행하여 관련 파일을 재생성/업데이트하세요.
  2. post-deployment 마이그레이션이 아닌 마이그레이션에 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`을 통해 작업이 예약된 경우에는 `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
    

대기열 이름 변경

워커를 제거하는 것이 위험한 이유와 동일한 이유로 대기열을 이름을 바꿀 때에는 주의가 필요합니다.

대기열을 이름을 바꿀 때에는 post-deployment 마이그레이션에서 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

대기열을 이름을 변경하는 경우 표준 마이그레이션이 아닌 post-deployment 마이그레이션에서 수행해야 합니다. 그렇지 않으면 이러한 작업을 예약하는 워커가 모두 실행을 중지하기 전에 실행되므로 너무 일찍 실행됩니다. 또한 다른 예시를 참조하세요.