Обобщающий вопрос в одном предложении: что может привести к увеличению размера массива Arel::Table
@aliases
при каждом поиске, выполненном с использованием ActiveRecord?
Я написал простое веб-приложение с использованием Rails 5. Когда я тестировал его под нагрузкой, использование памяти увеличивалось на неопределенный срок. После 2,2 миллиона запросов размер резидентной памяти процесса увеличился примерно до 1 ГБ.
Я провел исследование, получив дампы кучи до, сразу после и через 10 минут после выполнения нагрузочного теста со 100 000 запросов. Я проанализировал дампы кучи, используя инструмент для сравнения дампов кучи инструмент находится здесь. В нем говорится, что было около 398 000 просочившихся объектов, созданных Arel::Table#alias()
.
Это утверждение кажется виновником:
@aliases << node
Я подтвердил, что массив @aliases
Arel::Table
является источником утечки памяти, добавив вызов uniq!
в моей установленной версии Arel::Table#alias()
:
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 ищет RadiusVsa
на основе входных параметров для Group.dn
и RadiusDevice.ipv4_address
. Вот задействованные вызовы 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)