【发布时间】:2017-01-20 11:36:58
【问题描述】:
一句话总结问题:什么会导致Arel::Table的@aliases数组在每次使用ActiveRecord完成的搜索中都变大?
我使用 Rails 5 编写了一个简单的 Web 应用程序。当我对其进行负载测试时,内存使用量无限增加。在 220 万次请求之后,进程的常驻内存大小达到了大约 1GB。
我通过在执行 100,000 个请求的负载测试之前、之后和 10 分钟后获取堆转储来进行调查。我使用the heap dump diffing tool found here 分析了堆转储。它说Arel::Table#alias() 创建了大约 398,000 个泄露的对象。
这句话似乎是罪魁祸首:
@aliases << node
通过在我安装的Arel::Table#alias() 版本中添加对uniq! 的调用,我确认Arel::Table 的@aliases 数组是内存泄漏的根源:
def alias name = "#{self.name}_2"
Nodes::TableAlias.new(self, name).tap do |node|
@aliases << node
end
@aliases.uniq! # locally added this line
end
通过对 Arel 的此修改,我的应用的内存使用量在负载测试期间保持不变。
据我所知,@aliases 会随着对我的应用程序的每个请求而增长,并且会随着相同的对象而增长。我想知道这是否是 Arel 中的错误,或者我在我的应用程序中做了一些坏事,导致这个数组在没有被清除或垃圾收集的情况下增长。
该应用有四个模型:
模型
class DeviceVendor < ApplicationRecord
end
class Group < ApplicationRecord
end
class RadiusDevice < ApplicationRecord
belongs_to :device_vendor
validates :ipv4_address, :ip => {format: :v4}
end
class RadiusVsa < ApplicationRecord
belongs_to :group
belongs_to :device_vendor
end
迁移
class CreateGroups < ActiveRecord::Migration[5.0]
def change
create_table :groups do |t|
t.string :dn
t.integer :rank
t.timestamps
end
end
end
class CreateDeviceVendors < ActiveRecord::Migration[5.0]
def change
create_table :device_vendors do |t|
t.string :name
t.timestamps
end
end
end
class CreateRadiusDevices < ActiveRecord::Migration[5.0]
def change
create_table :radius_devices do |t|
t.string :ipv4_address
t.string :model_number
t.belongs_to :device_vendor, index: true, foreign_key: true
t.timestamps
end
end
end
class CreateRadiusVsas < ActiveRecord::Migration[5.0]
def change
create_table :radius_vsas do |t|
t.string :radius_attributes
t.belongs_to :device_vendor, index: true, foreign_key: true
t.belongs_to :group, index: true, foreign_key: true
t.timestamps
end
end
end
我唯一的 HTTP 端点根据 Group.dn 和 RadiusDevice.ipv4_address 的输入参数搜索 RadiusVsa。以下是所涉及的 ActiveRecord 调用:
# groups param value is like: ['ou=foo,cn=bar', 'ou=baz,cn=qux']
group = Group.order(rank: :desc).find_by!(dn: params.require('groups'))
# source param value is like: '10.0.0.1'
radius_device = RadiusDevice.find_by!(ipv4_address: params.require('source'))
# RadiusVsa.find_by! is the call that causes Arel::Table#alias() to be invoked
vendor_attributes = RadiusVsa.find_by!(group: group, device_vendor: radius_device.device_vendor)
【问题讨论】:
标签: ruby-on-rails ruby memory-leaks arel