Rails 7.1 ActiveStorage随机报错:找不到指定ID的Blob
问题分析
随机出现的ActiveRecord::RecordNotFound确实是竞态条件导致的,核心场景如下:
- 用户通过Google OAuth认证后,后台同步执行头像附着操作:下载Google头像、创建
ActiveStorage::Blob、关联ActiveStorage::Attachment到Avatar记录。 - 操作完成后立刻重定向到引导页,但在Fly.io的多实例分布式环境中,数据库写入可能存在同步延迟,或者浏览器请求提前到达其他实例,导致查询Blob时记录尚未持久化。
- 另外,
representation(:thumb)是惰性生成的,当请求该URL时,active_storage/representations/redirect#show会查询Blob,若此时Blob记录未完全写入,就会抛出异常。
解决方案
方案1:确保数据库操作原子性提交
用事务包裹头像附着的全流程,强制刷新关联记录,确保数据完全持久化后再重定向:
def set_avatar_from_auth(auth) if auth.dig(:info, :image).present? image_url = auth.dig(:info, :image) large_image_url = image_url.sub(/\bs\d{2,4}-c\b/, 's2500-c') attach_image_from_url(large_image_url) # 强制刷新关联,确保数据库记录已提交 profile.reload profile.avatar.reload if profile.avatar.present? else attach_default_avatar(profile.female?) end end def attach_image_from_url(image_url) begin # 原子性操作,避免部分记录写入 ActiveRecord::Base.transaction do avatar = profile.create_avatar avatar.image.attach( io: URI.open(image_url), filename: "#{SecureRandom.hex}.jpg", content_type: 'image/jpeg' ) avatar.reload end rescue => e Rails.logger.error("[User] - Error attaching image for User with id #{id} from URL: #{e.message}") attach_default_avatar(profile.female?) end end
方案2:视图层增加异常处理与降级
修改视图代码,捕获Blob不存在的异常,避免页面崩溃,自动降级到默认头像:
<%= picture_tag(class: "img-fluid shadow-lg border-0 p-0 align-self-center") do %> <% begin %> <% if current_profile.avatar&.image&.attached? %> <%= tag :source, srcset: url_for(current_profile.avatar.image.representation(:thumb)) %> <% end %> rescue ActiveRecord::RecordNotFound # 捕获异常,不渲染source标签,使用默认图 end %> <%= image_tag (current_profile.female? ? 'avatar-girl.png' : 'avatar-boy.png'), alt: current_profile.username, size: '340x340' %> <% end %>
或者更轻量的检查方式:
<%= picture_tag(class: "img-fluid shadow-lg border-0 p-0 align-self-center") do %> <% if current_profile.avatar&.image&.attached? && current_profile.avatar.image.blob.present? %> <%= tag :source, srcset: url_for(current_profile.avatar.image.representation(:thumb)) %> <% end %> <%= image_tag (current_profile.female? ? 'avatar-girl.png' : 'avatar-boy.png'), alt: current_profile.username, size: '340x340' %> <% end %>
方案3:预生成缩略图避免惰性竞争
在附着头像时同步生成缩略图,确保representation对应的Blob提前存在:
def attach_image_from_url(image_url) begin ActiveRecord::Base.transaction do avatar = profile.create_avatar avatar.image.attach( io: URI.open(image_url), filename: "#{SecureRandom.hex}.jpg", content_type: 'image/jpeg' ) # 强制生成并保存缩略图 avatar.image.representation(:thumb).processed end rescue => e Rails.logger.error("[User] - Error attaching image for User with id #{id} from URL: #{e.message}") attach_default_avatar(profile.female?) end end
注意:此方案会增加头像附着操作的耗时,需根据业务性能要求权衡使用。
方案4:优化Fly.io环境配置
- 确保应用实例全部连接数据库主节点,避免从节点的复制延迟导致查询不一致。
- 调整PostgreSQL同步级别(设置
synchronous_commit = on),确保写入操作完全持久化后再返回应用。
验证方法
由于开发环境是单实例+本地数据库,无网络延迟和多实例竞争,难以复现问题。可在生产环境通过attach_image_from_url方法末尾添加sleep 1模拟延迟,然后快速刷新引导页,验证修复效果。
内容的提问来源于stack exchange,提问作者Diego Charles
相关产品推荐
相关产品推荐

