업데이트 간 Sidekiq 호환성
GitLab v19.1Sidekiq job의 인수는 실행이 예약된 동안 큐에 저장됩니다. 애플리케이션의 이전 버전이 job을 게시하고, 업그레이드된 Sidekiq 노드가 이를 실행합니다. job이 업그레이드 전에 큐에 추가되었지만, 업그레이드 후에 실행됩니다.
Sidekiq job의 인수는 실행이 예약된 동안 큐에 저장됩니다. 온라인 업데이트 중에는 다음과 같은 여러 상황이 발생할 수 있습니다:
-
애플리케이션의 이전 버전이 job을 게시하고, 업그레이드된 Sidekiq 노드가 이를 실행합니다.
-
job이 업그레이드 전에 큐에 추가되었지만, 업그레이드 후에 실행됩니다.
-
job이 최신 버전의 애플리케이션을 실행하는 노드에 의해 큐에 추가되었지만, 이전 버전의 애플리케이션을 실행하는 노드에서 실행됩니다.
새로운 워커 추가#
GitLab.com에서는 현재 카나리 Stage에 Sidekiq 배포가 없습니다. 이는 HTTP 엔드포인트에서 예약할 수 있는 새로운 워커가 카나리에서 예약될 수 있지만, 전체 프로덕션 배포가 완료될 때까지 Sidekiq에서 실행되지 않을 수 있음을 의미합니다. 이는 job 예약보다 몇 시간 늦을 수 있습니다. 일부 워커의 경우 이것이 문제가 되지 않지만, 특히 지연 민감 job의 경우에는 사용자 경험이 저하될 수 있습니다.
이것은 새로운 워커 클래스가 처음 도입될 때만 적용됩니다. 일반적인 개발 프로세스로 기능 플래그 사용을 권장하므로, 새로운 Sidekiq 워커 예약을 포함한 전체 변경 사항을 기능 플래그로 제어하는 것이 좋습니다.
워커의 인수 변경#
job은 연속된 버전의 애플리케이션 간에 하위 호환성과 상위 호환성을 모두 갖춰야 합니다. 인수를 추가하거나 제거하면 문제가 발생할 수 있습니다.
모든 배포 중에는 일부 애플리케이션 노드는 업데이트되었지만 그렇지 않은 노드도 있는 기간이 있습니다. 업데이트된 노드가 새 인수로 job을 큐에 추가하지만, 이전 Sidekiq 노드가 이를 처리하면 인수 불일치로 인해 job이 실패합니다.
GitLab.com의 경우, 동일한 마일스톤에 여러 번의 배포가 있을 경우 이런 상황이 발생할 수 있습니다. 대부분의 Self-managed 배포는 각 릴리즈마다 단일 배포 사이클에서 모든 노드를 순차적으로 업데이트하므로, 여러 릴리즈에 걸쳐 변경 사항을 분산시켜야 합니다.
인수 폐기 및 제거#
perform_async 및 perform 메서드에서 인수를 제거하기 전에, 먼저 폐기 처리하세요.
다음 예제는 perform_async 메서드에서 arg2를 폐기 처리한 후 제거하는 방법을 보여줍니다:
- 기본값(보통
nil)을 제공하고, 주석을 사용하여 다음 마이너 릴리즈에서 인수를 폐기 처리한다고 표시합니다. (릴리즈 M)
class ExampleWorker
# Keep arg2 parameter for backwards compatibility.
def perform(object_id, arg1, arg2 = nil)
# ...
end
end
- 한 마이너 릴리즈 후,
perform_async에서 인수 사용을 중단합니다. (릴리즈 M+1)
ExampleWorker.perform_async(object_id, arg1)
- 다음 메이저 릴리즈에서, 워커 클래스에서 값을 제거합니다. (다음 메이저 릴리즈)
class ExampleWorker
def perform(object_id, arg1)
# ...
end
end
인수 추가#
Sidekiq 워커에 새 인수를 안전하게 추가하는 두 가지 옵션이 있습니다:
-
먼저 워커에 새 인수를 추가하는 다단계 릴리즈를 설정합니다. 향후 유연성을 위해 파라미터 해시 사용을 고려하세요.
-
워커가 이미 추가 인수에 파라미터 해시를 사용하고 있다면, 해시에 새 인수를 전달합니다. 아직 파라미터 해시를 사용하지 않는 워커는 먼저 추가하기 위해 다단계 릴리즈를 거쳐야 합니다.
다단계 릴리즈#
이 접근 방식은 여러 릴리즈가 필요합니다.
- 기본값을 가진 인수를 워커에 추가합니다. (릴리즈 M)
class ExampleWorker
def perform(object_id, new_arg = nil)
# ...
end
end
- 워커의 모든 호출에 새 인수를 추가합니다. (릴리즈 M+1)
ExampleWorker.perform_async(object_id, new_arg)
- 기본값을 제거합니다. (릴리즈 M+2)
class ExampleWorker
def perform(object_id, new_arg)
# ...
end
end
파라미터 해시#
이 접근 방식은 기존 워커가 이미 파라미터 해시를 사용하는 경우 여러 릴리즈가 필요하지 않습니다.
- 워커에서 파라미터 해시를 사용하여 향후 유연성을 허용합니다.
class ExampleWorker
def perform(object_id, params = {})
# ...
end
end
워커 클래스 제거#
워커 클래스를 제거하려면 세 개의 마이너 릴리즈에 걸쳐 다음 단계를 따르세요:
마이너 릴리즈 M에서#
-
job을 큐에 추가하는 코드를 모두 제거합니다.
예를 들어, 사용자가 상호작용할 수 있는 UI 컴포넌트나 API 엔드포인트가 있어 워커 인스턴스가 큐에 추가되는 경우, 해당 인터페이스를 제거하거나 워커 인스턴스가 더 이상 큐에 추가되지 않도록 업데이트하세요.
이렇게 하면 워커 클래스와 관련된 인스턴스가 더 이상 큐에 추가되지 않습니다.
-
프론트엔드와 백엔드 코드 모두 워커가 이전에 수행하던 작업에 더 이상 의존하지 않는지 확인합니다.
-
관련 워커 클래스에서
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
이 no-op을 구현함으로써, 아직 큐에 남아 있는 폐기된 job이 결국 처리될 때 불필요한 사이클을 방지할 수 있습니다.
M+1 릴리즈에서#
sidekiq_remove_jobs를 사용하는 마이그레이션(포스트 배포 마이그레이션이 아닌)을 추가합니다:
class RemoveMyDeprecatedWorkersJobInstances < Gitlab::Database::Migration[2.1]
# Always use `disable_ddl_transaction!` while using the `sidekiq_remove_jobs` method,
# as we had multiple production incidents due to `idle-in-transaction` timeout.
disable_ddl_transaction!
DEPRECATED_JOB_CLASSES = %w[
MyDeprecatedWorkerOne
MyDeprecatedWorkerTwo
]
def up
Gitlab::SidekiqSharding::Validator.allow_unrouted_sidekiq_calls do
# If the job has been scheduled via `sidekiq-cron`, we must also remove
# it from the scheduled worker set using the key used to define the cron
# schedule in config/schedule.yml or ee/config/schedule.yml.
job_to_remove = Sidekiq::Cron::Job.find('my_deprecated_worker')
# The job may be removed entirely:
job_to_remove.destroy if job_to_remove
# The job may be disabled:
job_to_remove.disable! if job_to_remove
end
# Removes scheduled instances from Sidekiq queues
sidekiq_remove_jobs(job_klasses: DEPRECATED_JOB_CLASSES)
end
def down
# This migration removes any instances of deprecated workers and cannot be undone.
end
end
M+2 릴리즈에서#
워커 클래스 파일을 삭제하고, Sidekiq 큐 문서의 안내에 따라 Rake 태스크를 실행하여 관련 파일을 재생성/업데이트하세요.
큐 이름 변경#
워커 제거가 위험한 것과 같은 이유로, 큐 이름을 변경할 때도 주의가 필요합니다.
큐 이름을 변경할 때는 포스트 배포 마이그레이션에서 sidekiq_queue_migrate 헬퍼 마이그레이션 메서드를 사용하세요:
class MigrateTheRenamedSidekiqQueue < Gitlab::Database::Migration[2.1]
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
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
포스트 배포 마이그레이션이 아닌 표준 마이그레이션에서 큐 이름을 변경하면 안 됩니다. 그렇지 않으면 해당 job을 예약하는 모든 워커가 실행을 중단하기 전에 너무 일찍 실행됩니다. 다른 예시도 참조하세요.
워커 클래스 이름 변경#
새로운 워커를 추가하는 것과 유사하게 처리해야 합니다. 즉, Sidekiq 배포가 완료된 후에만 새로운 이름의 워커 예약을 시작합니다.
애플리케이션의 연속된 버전 간 하위 호환성과 상위 호환성을 보장하려면 세 개의 마이너 릴리즈에 걸쳐 다음 단계를 따르세요:
-
새로운 이름의 워커를 만들고, 이전 워커가 새 워커의
#perform메서드를 호출하도록 합니다. 새 워커 예약을 시작할 시기를 제어하기 위해 기능 플래그를 도입합니다. (릴리즈 M)아직 큐에 있는 이전 워커 job은 새 워커에 위임됩니다. 이 버전이 배포되면, 어떤 버전의 job이 예약되었거나 어떤 Sidekiq가 처리하는지는 더 이상 중요하지 않습니다. 이전 Sidekiq는 이전 워커의 전체 구현을 사용하고, 새 Sidekiq는 새 워커에 위임합니다.
-
GitLab.com에서 기능 플래그를 활성화하고, 그 후 기본적으로 활성화하기 위한 머지 리퀘스트를 준비합니다. (릴리즈 M+1)
-
이전 워커 클래스와 기능 플래그를 제거합니다. (릴리즈 M+2)