You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 09:10:54