Rails项目Heroku环境下Sidekiq任务中临时文件丢失问题排查
问题排查与解决方案
核心原因
Heroku的dyno文件系统是独立且临时的:
- Web dyno接收上传后存在
tmp/uploads,但Sidekiq运行在单独的worker dyno,两个dyno的文件系统完全隔离,worker根本无法访问web dyno里的临时文件 - 即使是同一个dyno,Heroku会在请求结束后定期清理临时文件,Sidekiq异步执行时文件可能已被删除
页面能显示图片是因为当前web dyno的临时文件尚未被清理,但worker dyno完全访问不到该路径。
解决方案步骤
1. 替换本地临时存储为直接上传到Cloudinary
不再依赖Heroku的本地tmp目录,上传时直接把文件传到Cloudinary获取资源标识,将这些信息传给Sidekiq Job,而非本地文件路径:
# 控制器修改示例 def associate_photos # 直接上传到Cloudinary并收集资源信息 photo_data = params[:photos].map do |photo| upload_result = Cloudinary::Uploader.upload(photo.tempfile, folder: 'tmp_ingredients', use_filename: true) { public_id: upload_result['public_id'], url: upload_result['secure_url'] } end AssociatePhotosJob.perform_later(ingredient_id: params[:ingredient_id], photos: photo_data) redirect_to review_photos_path, notice: '照片关联任务已启动' end
2. 修改Sidekiq Job与Service,使用Cloudinary资源而非本地文件
不再读取本地tmp/uploads的文件,直接用Cloudinary的public_id完成食材关联:
# AssociatePhotosJob 修改示例 class AssociatePhotosJob < ApplicationJob queue_as :default def perform(ingredient_id:, photos:) ingredient = Ingredient.find(ingredient_id) IngredientPhotoAssociator.new(ingredient, photos).associate end end # Service 修改示例 class IngredientPhotoAssociator def initialize(ingredient, photos) @ingredient = ingredient @photos = photos end def associate @photos.each do |photo_data| # 直接用Cloudinary资源信息创建关联 @ingredient.photos.create!( cloudinary_public_id: photo_data[:public_id], url: photo_data[:url] ) end # 可选:清理Cloudinary临时目录的文件 Cloudinary::Api.delete_resources(photos.map { |p| p[:public_id] }) end end
3. 移除对Heroku本地tmp目录的依赖
删除代码中所有依赖tmp/uploads的逻辑,确保所有文件操作都通过Cloudinary完成。
4. 验证Heroku配置
- 确认Cloudinary环境变量
CLOUDINARY_URL已正确设置:heroku config:get CLOUDINARY_URL - 确保Sidekiq worker dyno已启动:
heroku ps:scale worker=1
额外注意事项
- 若必须保留临时存储步骤,可使用Heroku的共享存储插件(如AWS S3),但直接上传到Cloudinary是更轻量的方案
- 测试时查看Sidekiq日志确认错误消除:
heroku logs -t -s sidekiq
内容的提问来源于stack exchange,提问作者Quentin
相关产品推荐
相关产品推荐

