rails 4 中使用 activemodel 序列化程序的序列化性能较慢

Slow serialization permormance in rails 4 with activemodel serializers

删除了 N+1 个查询,但对我没有帮助。只有40个对象,耗时15秒

我怀疑有太多 Stock.with_translations(I18n.locale)Distributor.with_translations(I18n.locale) 数据库调用以至于序列化工作如此缓慢。我如何重构该数据库调用?

 class ShopsSerializer < ActiveModel::Serializer
    include ActionView::Helpers::SanitizeHelper

    attributes :id, :title, :description, :audio_sizes, :stocks_count, :image_sizes, :audio_count, :country
    has_many :images, serializer: ShopImageSerializer
    has_many :products, serializer: ProductSerializer

    def image_sizes
      total = 0.0

      stocks = Stock.with_translations(I18n.locale).includes(:images).where(city_id: object.id)
      stocks.each do |stock|
        sum = stock.images.inject(0){|sum, item| sum + item.image_size if item.image.present?} || 0
        total += sum
      end

      total.round(2)
    end

    def audio_sizes
      size = 0.0
      Stock.with_translations(I18n.locale).where(city_id: object.id).map{|s| size += s.audio.size if s.audio.present?}
      Distributor.with_translations(I18n.locale).where(city_id: object.id).map{|d| size += d.audio.size if d.audio.present?}
      size
    end

    def stocks_count
      Stock.with_translations(I18n.locale).where(city_id: object.id).count + Distributor.with_translations(I18n.locale).where(city_id: object.id).count
    end

    def audio_count
      count = 0
      Stock.with_translations(I18n.locale).where(city_id: object.id).map do |s|
        if s.audio.present?
          count += 1
        end
      end

      Distributor.with_translations(I18n.locale).where(city_id: object.id).map do |d|
        if d.audio.present?
          count += 1
        end
      end
      count
    end
  end

理想情况下,您应该在数据库级别移动您的计算,但我没有时间为您写这个。否则你仍然有 N + 1 问题,因为对于每个要序列化的对象,你查询东西。

无论如何,在你的情况下,一个胜利是至少查询一次,像这样记住它们:

 class ShopsSerializer < ActiveModel::Serializer
    include ActionView::Helpers::SanitizeHelper

    attributes :id, :title, :description, :audio_sizes, :stocks_count, :image_sizes, :audio_count, :country
    has_many :images, serializer: ShopImageSerializer
    has_many :products, serializer: ProductSerializer

    def image_sizes
      total = 0.0

      stocks.each do |stock|
        sum = stock.images.inject(0){|sum, item| sum + item.image_size if item.image.present?} || 0
        total += sum
      end

      total.round(2)
    end

    def audio_sizes
      size = 0.0
      stocks.map{|s| size += s.audio.size if s.audio.present?}
      distributors.map{|d| size += d.audio.size if d.audio.present?}
      size
    end

    def stocks_count
      stocks.count + distributors.count
    end

    def audio_count
      count = 0
      stocks.map do |s|
        if s.audio.present?
          count += 1
        end
      end

      distributors.map do |d|
        if d.audio.present?
          count += 1
        end
      end
      count
    end

    private

    def stocks
      @stocks ||= Stock.with_translations(I18n.locale).includes(:images).where(city_id: object.id)
    end

    def distributors
      @distributors||= Distributor.with_translations(I18n.locale).where(city_id: object.id)
    end
  end

使用 rails 缓存而不是 activemodel 序列化程序缓存解决了我的问题。 link

您没有说明您使用的是什么版本的 AMS,您如何使用序列化器,或者关于您的 ar 模型或关联序列化器的很多信息..

你也没有说出你尝试过什么,或者你读过什么文档,所以很难知道你在自己解决问题上的投入与让互联网为你完成工作相比。如果这听起来很刺耳,抱歉,这只是来自处理开源问题的经验。

也就是说,AMS 本身不会为您执行任何数据库操作。如果你想急切加载任何东西,那是你需要在你的应用程序中做的事情,这意味着阅读 rails 关于关联和查询的文档

在询问技术问题时没有提供足够的信息是一个常见问题。我建议你看看 https://www.chiark.greenend.org.uk/~sgtatham/bugs.html or https://github.com/rails-api/active_model_serializers/blob/f5ec8ed9d4624afa6ede9b39d51d145b53b1f344/CONTRIBUTING.md#filing-an-issue or https://github.com/norman/yourbugreportneedsmore.info/blob/master/index.html

引用最后一个:

你好!

您已被定向到此网站,因为您提交了错误报告至 一个开源项目,但您提供的信息太少 开发人员能够帮助你。这看起来很熟悉吗?

Hi, I'm getting a weird error when I use <program>, do you know what might be wrong?

调试软件很难,即使您手边有代码。 现在想象一下,尝试在别人的计算机上调试软件,而无需 对代码的任何访问权限,而不知道系统上的操作系统是什么 计算机,甚至是正在使用的软件版本。你的唯一 提示是 "there's a weird error" 并且你有 50 行中的 1 行 要使用的堆栈跟踪。听起来不可能?那是因为它是!

所以你需要帮助?

如果你想真正解决你的问题,这里是你可以 提交一份开发人员实际会回应的良好错误报告:

  • 有堆栈跟踪吗?发送整个东西 - 或者更好的是,发送一个 link 到 它粘贴在 Gist 或 Pastie 上。
  • 提供上下文,例如 Ruby 或 Python 或 COBOL 的版本或 无论您使用什么,以及导致问题的代码。 同样,Gist 和 Pastie 是您的朋友。
  • 更好的是,创建一个重现问题的小程序,然后把 它在 Github 上,或压缩并发送 一封电邮。
  • 更好的是,如果你能够,添加一个失败的测试用例 演示您遇到的问题,并将其作为拉取请求发送 或补丁。

信息太多?

如果您只记得一件事,请记住这一点:再现性是 key. 如果我不能重现你的问题,我就无法修复它。

信息不足?

有关正确报告错误的更长指南,请查看 Simon Tatham's excellent article

你还记得DRY吗?我会重构 Stock.with_translations(I18n.locale).where(city_id: object.id) Stock模型并使其成为一个或多个范围。它也可能对未来有用。

可能 Rails 会更好地缓存它。

可以通过向 stocks.city_id 添加索引来加快查询速度,除非您已经有了它。

您也可以通过 joining tables

查看效果

joins(:images) 而不是 includes(:images)

对于这 40 个项目中的每一个,您查询 Stock.with_translations(I18n.locale).includes(:images) 一次,Stock.with_translations(I18n.locale) 两次,Distributor.with_translations(I18n.locale) 一次。

这导致至少 40*2 + 40*2 + 40 次查询。

您显然需要一种方法来创建 ShopStockDistributor 之间的关联,可能与 has_many :through 之间的关联。但是当您尝试跨城市访问 StockDistributor 中的项目计数时,您可以查询它们并将它们与序列化程序中的选项一起传递,或者您可以记忆并 运行 这些查询一次正如@apneadiving 所建议的那样。