🤸

Sidekiqをredis-namespaceから脱却した話

2023/12/12に公開

【アドカレ2023】Sidekiqのredis-namespace gemからの脱却

これは 株式会社TimeTree Advent Calendar 2023 の12日目の記事です。
https://qiita.com/advent-calendar/2023/timetree
11日目は @gonseeカレンダー開発の怖い話: 週番号 の記事 でした。

こんにちは。今年の4月からTimeTreeに入社し、プロダクトDivというチームに所属しています。
社内では Dashというニックネームで働いています。
この記事では、 redis-namespacegemからの脱却までに行なった手法などを紹介していこうと思います。

※この記事に使用しているバージョンはそれぞれ下記の通りです。

  • Ruby on Rails:7.0.8
  • ElasticCache for Redis:7.0.7
  • Sidekiq Pro:5.5.8
  • redisgem:4.8.1
  • redis-namespacegem:1.11.0

背景

Sidekiq 6以前までのバージョンでは redis-namespaceを採用しており、TimeTreeもそれに遵守してきました。
しかし、Sidekiq 7のバージョンアップによってredis-namespacegemの利用が廃止されることで、Redisサーバーのキーからnamespaceを削除する必要があります。

# For example
# 移行前(namespaceあり)
||------ Redis Server ------||
|| 1) sidekiq:queues        ||
|| 2) sidekiq:queue:default ||
|| 3) sidekiq:schedule      ||
|| 4) sidekiq:retry         ||
||--------------------------||
             ↓
             ↓
# 移行後(namespaceなし)
||------ Redis Server ------||
|| 1) queues                ||
|| 2) queue:default         ||
|| 3) schedule              ||
|| 4) retry                 ||
||--------------------------||

https://github.com/sidekiq/sidekiq/blob/main/docs/7.0-Upgrade.md#redis-namespace

TimeTreeの構成

現在、TimeTreeではRuby on Railsを採用しており、バックグラウンドジョブの処理にSidekiq Proを利用しています。またRedisはElasticCache for Redisサーバーを使用しております。
TimeTreeのAPIサーバーからSidekiq用のRedis(以降、Sidekiq Redis)にJobを格納し、Sidekiq ServerはSidekiq RedisからJobを読み込み&処理しています。
Redisにはデフォルトで複数のデータベースが内包されていますが、TimeTreeでは1つのデータベースのみを使用し、namespaceを付与することで管理していました。
今回は1台のRedisサーバーでdatabase indexを分けることによって、 keyが混在しないように移行を行うことにしています。
また、TimeTreeは世界中で使われているサービスのため昼夜問わずユーザーがアプリケーションを利用してきます。そのため、**Migrating from redis-namespace**の記事のようにダウンタイムを発生させずに、稼働したまま移行するために今回の手法を採用しました。

移行イメージ

移行前のSidekiqの構成としては、右の画像のようにAPIサーバーからSidekiq Redisに対してJobの追加を行なっていました。

1️⃣ 移行中はAPIサーバーからindex 1データベースにJobを追加していき、
2️⃣ index 0データベースのJobが処理し終えるのを待ちます。

3️⃣ index 0データベース上のすべてJobが処理し終わったタイミングでSidekiqサーバー側もindex 1データベースに接続し直します。

※ただし、上記の方法ではScheduledに設定されているJobは移行されないため、後述するスクリプトなどによって新しいデータベースに移行する必要があります。

前段で記載した内容を以降でより詳細に解説していきます。

ステップ0:【事前準備】新旧Redisに格納されているそれぞれのキューの状態を監視できるようにする

Sidekiqにはモニタリング用の画面が提供されており、Sidekiq ProにはShardingの設定を追加することができます。
このSharding機能を利用することで、今回のような複数のdatabaseをモニタリングすることができます。
https://github.com/sidekiq/sidekiq/wiki/Pro-Web-UI#sharding

今回は下記のようにパスを分けて管理するようにしました。

  • /sidekiq → これまでのRedisデータベース監視用
  • /new_sidekiq → 新しいRedisデータベース監視用
new_redis_pool = ConnectionPool.new { Redis.new(..., db: 1) }
old_redis_pool = ConnectionPool.new do
  Redis::Namespace.new("sidekiq", redis: Redis.new(..., db: 0))
end

require "sidekiq/pro/web"
mount Sidekiq::Pro::Web.with(redis_pool: old_redis_pool), at: "/sidekiq", as: "sidekiq" # with namespace
mount Sidekiq::Pro::Web.with(redis_pool: new_redis_pool), at: "/new_sidekiq", as: "new_sidekiq" # without namespace

ステップ1:Sidekiq Clientをindex 1データベースに接続する

Sidekiqのconfigファイルで configure_client 側のみindex 1データベースに接続します。
(dbプロパティのデフォルト値は 0ですが、今回は明示的に定義しています)

old_redis_conn = proc
  Redis::Namespace.new("sidekiq", redis: Redis.new(..., db: 0))
end
new_redis_conn = proc
  Redis.new(..., db: 1)
end

Sidekiq.configure_server do |config|
	# Sidekiq Server側から接続するRedis
  config.redis = ConnectionPool.new(size: 5, &old_redis_conn)
end

Sidekiq.configure_client do |config|
	# Sidekiq Client側で接続するRedis
  config.redis = ConnectionPool.new(size: 5, &new_redis_conn)
end

さらに、Scheduled Jobに関しては以下のようなスクリプトを実行し、index 0データベースからindex 1へScheduled Jobを移行します。
Redis#zrandmemberでscheduleのJobの内容と実行までの時間を取得することができるので、その情報を使って、index 1のschedule Keyへ追加していきます。

class RedisNamespaceMigrateBatch
  NAMESPACE = "sidekiq"

	def run
    key = "schedule"
		@count = old_redis.zcard(key)
		old_redis.zrandmember(
		  "#{NAMESPACE}:#{key}",
		  @count,
		  with_scores: true,
		).each do |job, score|
		  new_redis.zadd(key, score, job)
		end
	end
	
	def old_redis
	  @old_redis ||= Redis.new(..., db: 0)
	end
	
	def new_redis
	  @new_redis ||= Redis.new(..., db: 1)
	end
end

ステップ2:index 0データベースのEnqueued Jobが全て完了するまで待つ

/sidekiq側の下記画像のオレンジの枠で囲っている Enqueuedが0になるのを待ちます。
また、同時に /new_sidekiq側のEnqueued Jobが増えていくのを確認します。
/new_sidekiq側のEnqueued Jobが増えない場合は、正常にJobの追加が行えていない可能性が高いため、Sidekiq Client側の設定を見直す必要がありそうです。

ステップ3:Sidekiq Serverをindex 1データベースに切り替える

ステップ1でSidekiq ClientのReidsサーバーを設定したように、Sidekiq Server側のRedisもindex 1データベースに切り替えます。
再起動後正常にJobが捌けていくことが確認できたら、無事移行完了です✨

最後に

世界中で使われるサービスであるTimeTreeならではの事情などあり、今回の移行作業はとても良い経験になりました!
TimeTreeはまだ入社して日が浅い状態でもさまざまなチャレンジをさせてくれる良い会社です!

この記事を読んでTimeTreeのサービスや世界中で使われるサービスのBackend開発などに興味のある方はぜひカジュアル面談などでお話しできると嬉しいです!

https://open.talentio.com/r/1/c/timetree/pages/29064

TimeTree Tech Blog

Discussion