递归 ruby 方法中的明显内存泄漏
Apparent memory leak in recursive ruby method
此脚本一运行,我就看到 CPU 使用率和服务器上的磁盘 IO 水平稳步上升,直到它最终被杀死。
这是一个通过从数据库中挑选未抓取的 url、抓取它并将其链接添加到数据库来递归抓取站点的脚本。
我假设函数内或函数与 ActiveRecord 交互的方式存在某种内存泄漏。有什么办法可以提高效率并堵住漏洞吗?
def self.site project, operate
@log = Logger.new(STDOUT)
recurse = ->() do
#
# Pick a from the database to crawl
unless ProjectData.where( status: 'unscraped', project_id: project[:id] ).exists?
@log.info "No pages to scrape"
return
end
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first
working_page.status = 'processing'
working_page.save
@log.info "Scraping #{working_page.url}"
#
# Scape it
data, links = OutriderTools::Scrape::page( working_page.url, operate)
unless links.nil?
links.each do |link|
# Check if link already exists
#if ProjectData.find_by(url: link.to_s).nil?
unless ProjectData.where( url: link.to_s, project_id: project[:id] ).exists?
ProjectData.create({
:url => link.to_s,
:status => 'unscraped',
:project_id => project[:id]
})
@log.info "Adding new url to database: #{link.to_s}"
else
@log.info "URL already exists in database: #{link.to_s}"
end
end
end
@log.info "Saving page data for url #{working_page.url}"
@log.info data[:status]
working_page.update( data ) unless data.nil?
recurse.call
end
recurse.call
end
您应该确保在完成时将状态设置为 unscraped 以外的状态
页面。对我来说,除非 data.nil,否则什么 working_page.update( data ) 是不清楚的?
做。我也认为使用递归没有意义。您可以使用无限循环并在没有更多页面时中断。使用递归可能会填满内存
很快。
大多数此类脚本都很慢,并且在由 Web 服务器执行时可能会导致超时。您应该 运行 将脚本作为某种预定作业。
首先让我向您指出 this article 我最近读到有关内存泄漏的内容,它是精彩的 Ruby 每周时事通讯的一部分。
那个 sead,它主要是高级的东西,大多数时候更传统的简单方法工作得更快。
在我看来,问题的最可能根源是递归,摆脱它。
您的代码的某些部分还可以更加精简。例如
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first
working_page.status = 'processing'
working_page.save
可能是
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first_or_create(status: 'processing')
同样的技巧
unless ProjectData.where( url: link.to_s, project_id: project[:id] ).exists?
ProjectData.create({
:url => link.to_s,
:status => 'unscraped',
:project_id => project[:id]
})
可能是(并且不要混合新旧哈希符号)
hash = {url: link.to_s, status: 'unscraped', project_id: project[:id]})
ProjectData.where(hash).first_or_create(hash.merge({status: 'unscraped'}))
您可以使用
去掉最后一个额外的关卡
return if links.nil?
你最好注释掉所有不是绝对必要的东西,例如日志记录甚至保存到数据库,从几行开始,看看它在不增加内存的情况下工作,然后通过删除来建立评论。
只是一个想法,不是答案:
我希望您知道,通过使用递归,您可以将所有收集到的数据和变量保存在内存中——在递归结束之前,它们永远不会被释放。
例如,working_page
和 links
变量都在内存中保持活动状态(连同 DB ActiveRecord class),而新的 working_page
和 links
变量在递归名内创建-space.
可能没有内存泄漏,只是设计问题。
除非您在递归之后再次需要该数据——您似乎不需要——最好使用 while
循环:
working_page = nil
while (working_page = ProjectData.where( status: 'unscraped', project_id: project[:id] ).first)
# ... do your thing...
end
(=
不是错误。它被用作赋值,整个赋值被审查以检查 working_page
是否存在并分配给它的对象)
此脚本一运行,我就看到 CPU 使用率和服务器上的磁盘 IO 水平稳步上升,直到它最终被杀死。
这是一个通过从数据库中挑选未抓取的 url、抓取它并将其链接添加到数据库来递归抓取站点的脚本。
我假设函数内或函数与 ActiveRecord 交互的方式存在某种内存泄漏。有什么办法可以提高效率并堵住漏洞吗?
def self.site project, operate
@log = Logger.new(STDOUT)
recurse = ->() do
#
# Pick a from the database to crawl
unless ProjectData.where( status: 'unscraped', project_id: project[:id] ).exists?
@log.info "No pages to scrape"
return
end
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first
working_page.status = 'processing'
working_page.save
@log.info "Scraping #{working_page.url}"
#
# Scape it
data, links = OutriderTools::Scrape::page( working_page.url, operate)
unless links.nil?
links.each do |link|
# Check if link already exists
#if ProjectData.find_by(url: link.to_s).nil?
unless ProjectData.where( url: link.to_s, project_id: project[:id] ).exists?
ProjectData.create({
:url => link.to_s,
:status => 'unscraped',
:project_id => project[:id]
})
@log.info "Adding new url to database: #{link.to_s}"
else
@log.info "URL already exists in database: #{link.to_s}"
end
end
end
@log.info "Saving page data for url #{working_page.url}"
@log.info data[:status]
working_page.update( data ) unless data.nil?
recurse.call
end
recurse.call
end
您应该确保在完成时将状态设置为 unscraped 以外的状态 页面。对我来说,除非 data.nil,否则什么 working_page.update( data ) 是不清楚的? 做。我也认为使用递归没有意义。您可以使用无限循环并在没有更多页面时中断。使用递归可能会填满内存 很快。 大多数此类脚本都很慢,并且在由 Web 服务器执行时可能会导致超时。您应该 运行 将脚本作为某种预定作业。
首先让我向您指出 this article 我最近读到有关内存泄漏的内容,它是精彩的 Ruby 每周时事通讯的一部分。
那个 sead,它主要是高级的东西,大多数时候更传统的简单方法工作得更快。
在我看来,问题的最可能根源是递归,摆脱它。
您的代码的某些部分还可以更加精简。例如
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first
working_page.status = 'processing'
working_page.save
可能是
working_page = ProjectData.where( status: 'unscraped', project_id: project[:id]).first_or_create(status: 'processing')
同样的技巧
unless ProjectData.where( url: link.to_s, project_id: project[:id] ).exists?
ProjectData.create({
:url => link.to_s,
:status => 'unscraped',
:project_id => project[:id]
})
可能是(并且不要混合新旧哈希符号)
hash = {url: link.to_s, status: 'unscraped', project_id: project[:id]})
ProjectData.where(hash).first_or_create(hash.merge({status: 'unscraped'}))
您可以使用
去掉最后一个额外的关卡return if links.nil?
你最好注释掉所有不是绝对必要的东西,例如日志记录甚至保存到数据库,从几行开始,看看它在不增加内存的情况下工作,然后通过删除来建立评论。
只是一个想法,不是答案:
我希望您知道,通过使用递归,您可以将所有收集到的数据和变量保存在内存中——在递归结束之前,它们永远不会被释放。
例如,working_page
和 links
变量都在内存中保持活动状态(连同 DB ActiveRecord class),而新的 working_page
和 links
变量在递归名内创建-space.
可能没有内存泄漏,只是设计问题。
除非您在递归之后再次需要该数据——您似乎不需要——最好使用 while
循环:
working_page = nil
while (working_page = ProjectData.where( status: 'unscraped', project_id: project[:id] ).first)
# ... do your thing...
end
(=
不是错误。它被用作赋值,整个赋值被审查以检查 working_page
是否存在并分配给它的对象)