ruby-on-rails - rails - 导出一个巨大的 CSV 文件会消耗生产中的所有 RAM
问题描述
所以我的应用程序导出了一个 11.5 MB 的 CSV 文件,并且基本上使用了所有永远不会被释放的 RAM。
CSV 的数据取自数据库,在上述情况下,整个数据都被导出。
我以下列方式使用 Ruby 2.4.1 标准 CSV 库:
export_helper.rb
:
CSV.open('full_report.csv', 'wb', encoding: UTF-8) do |file|
data = Model.scope1(param).scope2(param).includes(:model1, :model2)
data.each do |item|
file << [
item.method1,
item.method2,
item.methid3
]
end
# repeat for other models - approx. 5 other similar loops
end
然后在控制器中:
generator = ExportHelper::ReportGenerator.new
generator.full_report
respond_to do |format|
format.csv do
send_file(
"#{Rails.root}/full_report.csv",
filename: 'full_report.csv',
type: :csv,
disposition: :attachment
)
end
end
在单个请求之后,puma 进程加载了整个服务器 RAM 的 55% 并保持这种状态,直到最终完全耗尽内存。
例如,在本文中生成一个 75 MB 的 CSV 文件百万行只需要 1 MB 的 RAM。但是不涉及数据库查询。
服务器有 1015 MB RAM + 400 MB 交换内存。
所以我的问题是:
- 究竟是什么消耗了这么多内存?是 CSV 生成还是与数据库的通信?
- 我做错了什么并且错过了内存泄漏吗?或者它只是图书馆的工作方式?
- 有没有办法在不重新启动 puma 工人的情况下释放内存?
提前致谢!
解决方案
而不是each
您应该使用find_each
专门针对此类情况的 ,因为它将批量实例化模型并随后释放它们,而each
将立即实例化所有模型。
CSV.open('full_report.csv', 'wb', encoding: UTF-8) do |file|
Model.scope1(param).find_each do |item|
file << [
item.method1
]
end
end
此外,在将 CSV 发送到浏览器之前,您应该流式传输 CSV 而不是将其写入内存或磁盘:
format.csv do
headers["Content-Type"] = "text/csv"
headers["Content-disposition"] = "attachment; filename=\"full_report.csv\""
# streaming_headers
# nginx doc: Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications
headers['X-Accel-Buffering'] = 'no'
headers["Cache-Control"] ||= "no-cache"
# Rack::ETag 2.2.x no longer respects 'Cache-Control'
# https://github.com/rack/rack/commit/0371c69a0850e1b21448df96698e2926359f17fe#diff-1bc61e69628f29acd74010b83f44d041
headers["Last-Modified"] = Time.current.httpdate
headers.delete("Content-Length")
response.status = 200
header = ['Method 1', 'Method 2']
csv_options = { col_sep: ";" }
csv_enumerator = Enumerator.new do |y|
y << CSV::Row.new(header, header).to_s(csv_options)
Model.scope1(param).find_each do |item|
y << CSV::Row.new(header, [item.method1, item.method2]).to_s(csv_options)
end
end
# setting the body to an enumerator, rails will iterate this enumerator
self.response_body = csv_enumerator
end
推荐阅读
- database - 数据库,如何确定功能依赖关系以及是否在 BCNF 中?
- types - yaml中的整数类型转换
- python - TensorFlow ExportOutputs、PredictOuput 和指定 signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
- xamarin - 如何依次播放播放列表中的所有视频?
- php - wget它在使用php执行时不起作用
- javascript - java脚本中添加十进制值的问题
- python - Google Colab Error : Failed to get convolution algorithm.This is probably because cuDNN failed to initialize
- r - Replacing an element within a vector in a list
- sharepoint - 带有仅应用程序令牌的 Office 365 SharePoint API 返回 401
- c# - 在嵌入文档上应用过滤器后选择文档中的字段