업데이트 간 Sidekiq 호환성

Sidekiq 작업에 대한 인수는 실행이 예약될 때 대기열에 저장됩니다. 온라인 업데이트 동안, 이로 인해 여러 가지 가능한 상황이 발생할 수 있습니다:

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

새 작업자 추가

GitLab.com에서는 현재 canary 단계의 Sidekiq 배포가 없습니다. 이는 HTTP 엔드포인트에서 예약할 수 있는 새 작업자가 canary에서 예약되지만 실제 production 배포가 완료될 때까지 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. 새 인수를 먼저 worker에 추가하는 다단계 배포를 설정합니다.
  2. 추가적인 인수에 대해 매개변수 해시를 사용합니다. 이것이 가장 유연한 옵션일 수 있습니다.

다단계 배포

이 접근 방식은 여러 릴리스를 필요로 합니다.

  1. 기본값을 사용하여 worker에 인수를 추가합니다 (릴리스 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
    

매개변수 해시

이 접근 방식은 기존 worker가 이미 매개변수 해시를 사용할 경우 여러 릴리스가 필요하지 않습니다.

  1. 미래 유연성을 위해 worker에서 매개변수 해시를 사용합니다.

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

작업자 클래스 제거

작업자 클래스를 제거하려면 두 소수릴리스 동안 다음 단계를 따릅니다:

첫 번째 소수릴리스에서

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

    예를 들어 사용자가 상호 작용하는 UI 구성 요소나 API 엔드포인트가 작업자 인스턴스가 더 이상 대기열에 추가되지 않도록 하거나 업데이트되도록하는지 확인합니다.

    이는 작업자 클래스와 관련된 인스턴스가 더 이상 대기열에 추가되지 않도록합니다.

  2. frontend 및 backend 코드가 더 이상 작업자가 실행했던 작업에 의존하지 않도록합니다.
  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` 메서드를 사용할 때는 항상 `disable_ddl_transaction!`을 사용하십시오. 여러 번의 프로덕션 이슈로 인해 'idle-in-transaction' 타임아웃이 발생했습니다.
      disable_ddl_transaction!
      def up
        # 만약 작업이 `sidekiq-cron`을 통해 예약되었다면, `config/initializers/1_settings.rb`에서 크론 스케줄을 정의하는 데 사용된 키를 사용하여 예정된 작업자 집합에서도 제거해야 합니다.
        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

큐를 변경하는 작업은 표준 마이그레이션에서가 아닌 포스트 배포 마이그레이션에서 수행해야 합니다. 그렇지 않으면 이 작업은 모든 해당 작업을 예약한 작업자가 실행을 중지하기 전에 너무 일찍 실행됩니다. 기타 예제도 참조하세요.

작업자 클래스 이름 변경

이를 새로운 작업자 추가와 유사하게 처리해야 합니다. 이는 Sidekiq 배포가 완료된 후에 새롭게 명명된 작업자만 예약을 시작한다는 것을 의미합니다.

애플리케이션의 연속 버전 간의 역방향 및 순방향 호환성을 보장하기 위해 세 개의 소수 버전 릴리스 동안 다음 단계를 따르세요:

  1. 새로운 이름의 작업자를 만들고, 이전 작업자가 새로운 작업자의 #perform 메서드를 호출하도록 합니다. 새로운 작업자를 예약하는 시점을 제어하기 위해 피쳐 플래그를 도입합니다. (릴리스 M)

    대기 중인 이전 작업자 작업은 새로운 작업자에게 위임됩니다. 이 버전이 배포되면 예약된 작업의 어떤 버전이 중요하지 않으며, 어떤 Sidekiq가 이를 처리하는지도 중요하지 않습니다. 이전-Sidekiq는 이전 작업자의 완전한 구현을 사용하고, 새로운-Sidekiq는 새로운 작업자에게 위임합니다.

  2. GitLab.com에 대한 피쳐 플래그를 활성화하고, 그 후에 기본적으로 활성화하도록 MR을 준비합니다. (릴리스 M+1)
  3. 이전 작업자 클래스와 피쳐 플래그를 제거합니다. (릴리스 M+2)