Rails 6后台任务用只读副本时出现ActiveRecord::ConnectionTimeoutError
解决Rails 6 + Sidekiq使用只读副本时的连接超时问题
核心问题分析
你遇到的ActiveRecord::ConnectionTimeoutError本质是数据库连接池资源耗尽,结合配置与场景,主要排查方向集中在连接池配置不匹配、连接未正确释放、生产/开发环境差异这几点。
具体排查与解决方案
1. 确认连接池大小与Sidekiq并发数的匹配性
- 问题点:主库和只读副本的连接池是独立的,当前配置中
pool值取自RAILS_MAX_THREADS=12,但如果Sidekiq的并发数(concurrency)大于12,就会出现多线程争抢有限连接的情况。 - 排查步骤:
- 查看Sidekiq配置文件(
config/sidekiq.yml)中的concurrency值,或启动Sidekiq时的命令行参数。 - 在生产环境的Sidekiq任务中临时添加日志,打印实际连接池大小:
puts "Reading pool size: #{ApplicationRecord.connection_pool(:reading).size}" puts "Writing pool size: #{ApplicationRecord.connection_pool(:writing).size}"
- 查看Sidekiq配置文件(
- 解决方案:将副本库的
pool值调整为不小于Sidekiq的并发数。比如Sidekiq并发是20,修改database.yaml中primary_replica的配置:
然后在环境变量中设置primary_replica: # 其他配置不变 pool: <%= ENV["SIDEKIQ_DB_POOL"] || ENV['RAILS_MAX_THREADS'] || 5 %>SIDEKIQ_DB_POOL=20(根据实际并发数调整)。
2. 检查只读连接是否被正确释放
- 问题点:如果
connected_to(role: :reading)块内存在长时间运行的操作(比如外部API调用、文件处理),或异常导致连接未归还,会持续占用连接池资源。 - 排查与修复:
- 确保所有只读查询严格限制在
connected_to块内,块内不要包含非数据库操作的耗时逻辑:# 错误示例:块内包含耗时API调用 ActiveRecord::Base.connected_to(role: :reading) do data = User.all ExternalApi.call(data) # 耗时操作,占用连接 end # 正确示例:先获取数据,再执行耗时操作 data = ActiveRecord::Base.connected_to(role: :reading) { User.all } ExternalApi.call(data) - 避免在只读块内开启事务(副本库是只读的,事务不仅无效,还会锁定连接):
# 错误示例 ActiveRecord::Base.connected_to(role: :reading) do User.transaction do # 副本库不支持写操作,且会占用连接 # ... end end - 若块内可能抛出异常,可手动兜底确保连接释放(Rails的
connected_to块默认自动处理,极端场景下补充):conn = nil begin ActiveRecord::Base.connected_to(role: :reading) do conn = ActiveRecord::Base.connection # 执行查询 end ensure conn&.release_connection if conn end
- 确保所有只读查询严格限制在
3. 验证生产环境的环境变量配置
- 问题点:生产环境中可能存在
DB_POOL环境变量被意外设置为较小值(比如5),导致连接池大小未按RAILS_MAX_THREADS=12生效。 - 排查步骤:在生产环境执行
echo $DB_POOL和echo $RAILS_MAX_THREADS,确认实际生效的环境变量值。 - 解决方案:确保
DB_POOL未被设置,或设置为与RAILS_MAX_THREADS一致的数值。
4. 排查数据库层面的连接限制
- 问题点:PostgreSQL的
max_connections配置可能不足以支撑所有进程的连接总数(Web进程 + Sidekiq进程的连接池总和)。 - 排查步骤:登录生产环境的PostgreSQL,执行以下SQL查看当前连接数和最大限制:
SHOW max_connections; SELECT count(*) FROM pg_stat_activity; - 解决方案:如果当前连接数接近
max_connections,可适当调大PostgreSQL的max_connections配置,或减少单个进程的连接池大小(需权衡并发需求)。
开发环境复现问题的方法
开发环境无法复现的原因通常是并发量不足或任务执行过快,可通过以下方式模拟:
- 修改
config/sidekiq.yml,将concurrency设为大于连接池大小的值(比如20)。 - 创建包含长时间休眠的Sidekiq任务:
class SlowReadJob include Sidekiq::Worker def perform ActiveRecord::Base.connected_to(role: :reading) do User.all.to_a sleep 10 # 模拟耗时操作 end end end - 批量触发数十个该任务,观察是否出现连接超时错误。
内容的提问来源于stack exchange,提问作者hummmingbear
相关产品推荐
相关产品推荐

