Sidekiq v6.0.6 + sidekiq-cron + fakeredis で Redis に接続しようとして CI のテストが失敗する

2020/04/06 追記: この問題は sidekiq-cron v1.2.0 で解消されました 👏

2020/04/01 追記: はじめは Sidekiq 単体の問題かと思っていましたが、sidekiq-cron と組み合わせている場合に発生するようなので、全体的に更新しました。

Sidekiq v6.0.6 がリリースされました。

Sidekiq v6.0.6 + sidekiq-cron v1.1.0 + fakeredis の組み合わせで、CircleCI で DB のマイグレーションを行う際にテストが失敗するようになり、対応したメモです。

僕が参加しているプロジェクトでは、RSpec を実行するときは、fakeredis という Redis をシミュレートする Gem を使用しています。

以下が CI のテストが失敗したときのログです。 DB のマイグレーション後、本来は Redis に接続しないはずの処理なのに、127.0.0.1:6379 の Redis に接続できないとエラーが発生しています。

bin/rails db:create db:migrate

# ...

Traceback (most recent call last):

# ...
	26: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq/launcher.rb:103:in `flush_stats'
	25: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq.rb:94:in `redis'
	24: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:61:in `with'
	23: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:61:in `handle_interrupt'
	22: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:64:in `block in with'
	21: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:64:in `handle_interrupt'
	20: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/connection_pool-2.2.2/lib/connection_pool.rb:65:in `block (2 levels) in with'
	19: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq.rb:97:in `block in redis'
	18: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/sidekiq-6.0.6/lib/sidekiq/launcher.rb:104:in `block in flush_stats'
	17: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:2411:in `pipelined'
	16: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:52:in `synchronize'
	15: from /usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
	14: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:52:in `block in synchronize'
	13: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis.rb:2416:in `block in pipelined'
	12: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:162:in `call_pipeline'
	11: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:306:in `with_reconnect'
	10: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:164:in `block in call_pipeline'
	 9: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:196:in `call_pipelined'
	 8: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:230:in `process'
	 7: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:319:in `logging'
	 6: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:231:in `block in process'
	 5: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:381:in `ensure_connected'
	 4: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:105:in `connect'
	 3: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:306:in `with_reconnect'
	 2: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:106:in `block in connect'
	 1: from /home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:343:in `establish_connection'
/home/circleci/repo/vendor/bundle/ruby/2.6.0/gems/redis-4.1.3/lib/redis/client.rb:362:in `rescue in establish_connection': Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED) (Redis::CannotConnectError)

対応方法

上記の Issue によると、Sidekiq の問題ではなく、sidekiq-cron が sidekiq/launcherrequire しているためだ、とあります。 エラーログのバックトレースに sidekiq-cron が出てこなかったので疑っていませんでしたが、試しに Gemfile の sidekiq-cron をコメントアウトすると、問題が発生しませんでした。

従って、暫定対応になりますが、Gemfile では sidekiq-cron を require: false にして、Sidekiq の initializer で sidekiq-cron を require するようにしました。

# Gemfile
gem "sidekiq-cron", require: false
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  # ...

  require "sidekiq-cron"
  schedule_file = "#{Rails.root}/config/schedule.yml"
  Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end

なぜなのか

Ruby インタプリタを終了する際に Sidekiq の統計情報を Redis に書き込む変更が、v6.0.6 で追加されました。

sidekiq-cron を require すると、内部で sidekiq/launcherrequire されるので、インタプリタを終了する際に上記の処理が実行され、Redis にアクセスできずにエラーになるようです。 fakeredis を使用しておらず、Redis にアクセスできる状態なら、今回の問題は発生しないものと考えられます。

最後に

今回の問題に対する Pull Request も送られているようなので、今後のアップデートで解消されるかもしれません。 その際は追記します。

有償ですが Sidekiq Enterprise に cron 機能があるので、可能ならそれを使う方が安心できるんじゃないかなぁと思う、今日このごろです。

fakeredis は今回あまり関係がありませんでしたが、テストのパフォーマンスが大きく劣化することがなければ、fake ではない本物の Redis を使った方がいいな、と感じました。

© 2023 暇人じゃない. All rights reserved.