将Rails应用程序从Mongoid(MongoDB)迁移到ActiveRecord(Postgres)时,如何获得恒定的内存使用率?

Qqw*_*qwy 6 postgresql activerecord ruby-on-rails mongodb mongoid

我最近开始咨询和帮助开发Rails应用程序,该应用程序使用MongoDB(以Mongoid作为其数据库客户端)来存储其所有模型实例。

在应用程序处于早期启动阶段时就很好了,但是随着应用程序拥有越来越多的客户端,并且开始需要越来越复杂的查询以在界面中显示适当的统计信息和其他信息,我们决定了唯一可行的解​​决方案前进的是对数据进行规范化,然后转移到结构化数据库。

因此,我们现在正在将表和数据从MongoDB(以Mongoid作为对象映射器)迁移到Postgres(以ActiveRecord作为对象映射器)。因为我们必须确保Mongo数据库中没有不正确的非标准化数据,所以我们必须在Rails-land内部运行这些数据迁移,以确保正在运行验证,回调和健全性检查。

一切都在开发中进行了“细化”,但现在我们正在带有实际生产数据库的登台服务器上运行迁移。事实证明,对于某些迁移,服务器的内存使用量会随着模型实例数量的增加而线性增加,一旦我们填满16 GB的RAM(以及另外16GB的交换空间...),就会导致迁移被终止。

由于我们逐个迁移模型实例,因此我们希望能够找到一种方法来确保内存使用保持不变(接近)。

当前可能导致这种情况的原因是(a)ActiveRecord或Mongoid保持对我们已经导入的对象实例的引用,以及(b)迁移在单个DB事务中运行,因此Postgres占用了越来越多的内存直到完成为止?

所以我的问题是:

  • 这种线性内存使用的可能原因是什么?
  • 我们如何减少呢?
  • 有没有办法使Mongoid和/或ActiveRecord放弃旧的引用?
  • 我们应该尝试手动调用Ruby GC吗?
  • 有没有办法将数据迁移拆分为多个数据库事务,这会有所帮助吗?

这些数据迁移具有以下格式:

class MigrateSomeThing < ActiveRecord::Migration[5.2]
  def up
    Mongodb::ModelName.all.each do |old_thing| # Mongoid's #.all.each works with batches, see /sf/ask/492885711/ 
      create_thing(old_thing, Postgres::ModelName.new)
    end
    raise "Not all rows could be imported" if MongoDB::ModelName.count != Postgres::ModelName.count
  end

  def down
    Postgres::ModelName.delete_all
  end

  def create_thing(old_thing, new_thing)
    attrs = old_thing.attributes
    # ... maybe alter the attributes slightly to fit Postgres depending on the thing.
    new_thing.attributes = attrs
    new_thing.save!
  end

end
Run Code Online (Sandbox Code Playgroud)

D. *_* SM 3

我建议通过执行所有读取但不执行任何模型创建/写入操作,并查看内存使用量是否仍在增长,将内存消耗范围缩小到读取或写入端(或者换句话说,Mongoid 与 AR)。

Mongoid 默认情况下会批量执行查找,这与 AR 不同,AR 必须通过find_in_batches.

由于 ActiveRecord 迁移默认包装在事务中,并且 AR 会执行属性值跟踪,以便在事务提交失败时将模型实例的属性恢复到之前的值,因此正在创建的所有 AR 模型很可能都保留在内存中,不能成为垃圾收集直到迁移完成。可能的解决方案是:

  1. 禁用相关迁移的隐式事务 ( https://apidock.com/rails/ActiveRecord/Migration ):

    禁用_ddl_事务!

  2. 通过直接插入创建数据,完全绕过模型实例化(这也将加快该过程)。最基本的方法是通过 SQL(Rails ActiveRecord:获取原始插入的 id),也有用于此目的的库(批量将记录插入 Active Record 表)。