Sidekiqの互換性とアップデート

Sidekiqジョブの引数は、実行スケジュール中にキューに保存されます。オンラインアップデートの間、これはいくつかの可能な状況につながる可能性があります:

  1. 古いバージョンのアプリケーションがジョブを発行し、アップグレードされたSidekiqノードによって実行されます。
  2. ジョブはアップグレード前にキューに入れられ、アップグレード後に実行されます。
  3. ジョブは新しいバージョンのアプリケーションを実行しているノードでキューに入れられますが、古いバージョンのアプリケーションを実行しているノードで実行されます。

新しいワーカーの追加

GitLab.comでは現在、Sidekiqのデプロイはカナリアステージでは行っていません。つまり、HTTPエンドポイントからスケジューリングできる新しいワーカーは、canaryからスケジューリングされるかもしれませんが、本番デプロイが完了するまでSidekiqでは実行されません。これはジョブのスケジューリングより数時間遅くなる可能性があります。ワーカーによっては、これは問題になりません。他のジョブ、特にレイテンシに敏感なジョブにとっては、ユーザーエクスペリエンスの低下につながります。

これは新しいワーカークラスが導入されたときにのみ適用されます。一般的な開発プロセスとして機能フラグを使うことを推奨しているので、機能フラグで変更全体(新しい Sidekiq ワーカーのスケジューリングを含む)を制御するのがベストです。

ワーカーの引数の変更

ジョブは、アプリケーションの連続するバージョン間で後方互換性と前方互換性を保つ必要があります。引数の追加や削除は、すべての Rails と Sidekiq ノードが更新されたコードを持つ前のデプロイ時に問題を引き起こす可能性があります。

引数の廃止と削除

** perform_async およびperform メソッドから引数を削除する前に、**引数を非推奨にします。以下の例では、perform_async メソッドからarg2 を非推奨にし、削除しています:

  1. デフォルト値 (通常はnil) を指定し、コメントを使用して、引数を今後のマイナーリリースで非推奨とマークします。(リリース M)

    class ExampleWorker
      # Keep arg2 parameter for backwards compatibility.
      def perform(object_id, arg1, arg2 = nil)
        # ...
      end
    end
    
  2. 1つ後のマイナーリリースで、perform_async の引数の使用を停止します。 (リリースM+1)

    ExampleWorker.perform_async(object_id, arg1)
    
  3. 次のメジャーリリースで、ワーカークラスから値を削除します。(次のメジャーリリース)

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

引数

Sidekiqワーカーに新しい引数を安全に追加するには、2つのオプションがあります:

  1. 新しい引数を最初にワーカーに追加するマルチステップデプロイを設定します。
  2. 追加の引数にはパラメータハッシュを使います。おそらくこれが最も柔軟なオプションです。

マルチステップデプロイ

このアプローチでは複数のリリースが必要です。

  1. デフォルト値 (Release M) でワーカーに引数を追加してください。

    class ExampleWorker
      def perform(object_id, new_arg = nil)
        # ...
      end
    end
    
  2. Worker のすべての呼び出しに新しい引数を追加します (リリース M+1)。

    ExampleWorker.perform_async(object_id, new_arg)
    
  3. デフォルト値を削除します (リリース M+2)。

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

パラメータ・ハッシュ

既存の Worker が既にパラメータハッシュを使用している場合、この方法では複数のリリースは必要ありません。

  1. 将来の柔軟性を考慮して、Worker でパラメータハッシュを使用してください。

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

ワーカークラスの削除

ワーカークラスを削除するには、2つのマイナーリリースにわたって以下の手順に従ってください:

最初のマイナーリリースでは

  1. ジョブをエンキューするコードをすべて削除しました。

    たとえば、ユーザーが操作できる UI コンポーネントや API エンドポイントがあり、その結果ワーカー インスタンスがエンキューされるのであれば、それらの表面領域を削除するか、ワーカー インスタンスがエンキューされないように更新してください。

    これにより、Worker クラスに関連するインスタンスがエンキューされなくなります。

  2. フロントエンドとバックエンドの両方のコードが、ワーカーによって行われていた作業に依存しないようにします。
  3. 関連する Worker クラスで、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を実装することで、キューに残っている非推奨ジョブが最終的に処理された後の不要なサイクルを避けることができます。

その後の別のマイナーリリースで

  1. ワーカークラスファイルを削除し、Sidekiqキュードキュメントのガイダンスに従ってRakeタスクを実行し、関連ファイルを再生成/更新してください。
  2. sidekiq_remove_jobs を使用するマイグレーション(デプロイ後のマイグレーションではない)を追加します:

    class RemoveMyDeprecatedWorkersJobInstances < Gitlab::Database::Migration[2.0]
      DEPRECATED_JOB_CLASSES = %w[
        MyDeprecatedWorkerOne
        MyDeprecatedWorkerTwo
      ]
      # 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!
      def up
        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
    

キューの名前の変更

ワーカーの削除が危険であるのと同じ理由で、キューの名前を変更する際には注意が必要です。

キューの名前を変更する際には、デプロイ後のマイグレーションで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

キュー名の変更は、標準的なマイグレーションではなく、デプロイ後のマイグレーションで行う必要があります。そうしないと、これらのジョブをスケジュールするワーカーがすべて停止する前に、キューの名前が変更されてしまいます。他の例も参照してください。