Sidekiqをredis-namespaceから脱却した話
【アドカレ2023】Sidekiqのredis-namespace gemからの脱却
これは 株式会社TimeTree Advent Calendar 2023 の12日目の記事です。 11日目は @gonsee の カレンダー開発の怖い話: 週番号 の記事 でした。
こんにちは。今年の4月からTimeTreeに入社し、プロダクトDivというチームに所属しています。
社内では Dash
というニックネームで働いています。
この記事では、 redis-namespace
gemからの脱却までに行なった手法などを紹介していこうと思います。
※この記事に使用しているバージョンはそれぞれ下記の通りです。
- Ruby on Rails:
7.0.8
- ElasticCache for Redis:
7.0.7
- Sidekiq Pro:
5.5.8
-
redis
gem:4.8.1
-
redis-namespace
gem:1.11.0
背景
Sidekiq 6以前までのバージョンでは redis-namespace
を採用しており、TimeTreeもそれに遵守してきました。
しかし、Sidekiq 7のバージョンアップによってredis-namespace
gemの利用が廃止されることで、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 ||
||--------------------------||
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をモニタリングすることができます。
今回は下記のようにパスを分けて管理するようにしました。
-
/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
index 1
データベースに接続する
ステップ1:Sidekiq Clientを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
index 0
データベースのEnqueued Jobが全て完了するまで待つ
ステップ2:/sidekiq
側の下記画像のオレンジの枠で囲っている Enqueued
が0になるのを待ちます。
また、同時に /new_sidekiq
側のEnqueued Jobが増えていくのを確認します。
/new_sidekiq
側のEnqueued Jobが増えない場合は、正常にJobの追加が行えていない可能性が高いため、Sidekiq Client側の設定を見直す必要がありそうです。
index 1
データベースに切り替える
ステップ3:Sidekiq Serverをステップ1でSidekiq ClientのReidsサーバーを設定したように、Sidekiq Server側のRedisもindex 1
データベースに切り替えます。
再起動後正常にJobが捌けていくことが確認できたら、無事移行完了です✨
最後に
世界中で使われるサービスであるTimeTreeならではの事情などあり、今回の移行作業はとても良い経験になりました!
TimeTreeはまだ入社して日が浅い状態でもさまざまなチャレンジをさせてくれる良い会社です!
この記事を読んでTimeTreeのサービスや世界中で使われるサービスのBackend開発などに興味のある方はぜひカジュアル面談などでお話しできると嬉しいです!
TimeTreeのエンジニアによる記事です。メンバーのインタビューはこちらで発信中! note.com/timetree_inc/m/m4735531db852
Discussion