对API响应数据集进行Ruby分页会导致内存峰值

当我浏览API返回的数据集时,我遇到了大量内存峰值的问题。 API返回约150k条记录,我一次请求10k条记录,并翻阅15页数据。 数据是一个散列数组,每个散列包含25个具有~50个字符的字符串值的键。 这个过程杀死了我的512mb Heroku dyno。

我有一个用于分页API响应数据集的方法。

def all_pages value_key = 'values', &block response = {} values = [] current_page = 1 total_pages = 1 offset = 0 begin response = yield offset #The following seems to be the culprit values += response[value_key] if response.key? value_key offset = response['offset'] total_pages = (response['totalResults'].to_f / response['limit'].to_f).ceil if response.key? 'totalResults' end while (current_page += 1) <= total_pages values end 

我这样称呼这个方法:

 all_pages("items") do |current_page| get "#{data_uri}/data", query: {offset: current_page, limit: 10000} end 

我知道这是引起问题的数组的串联,因为删除该行允许进程在没有内存问题的情况下运行。 我究竟做错了什么? 整个数据集可能不超过20mb – 如何消耗所有的dyno内存? 我该怎么做才能提高效率?

更新

响应如下: {"totalResults":208904,"offset":0,"count":1,"hasMore":true, limit:"10000","items":[...]}

更新2

运行report显示以下内容:

 [HTTParty] [2014-08-13 13:11:22 -0700] 200 "GET 29259/data" - Memory 171072KB [HTTParty] [2014-08-13 13:11:26 -0700] 200 "GET 29259/data" - Memory 211960KB ... removed for brevity ... [HTTParty] [2014-08-13 13:12:28 -0700] 200 "GET 29259/data" - Memory 875760KB [HTTParty] [2014-08-13 13:12:33 -0700] 200 "GET 29259/data" - Errno::ENOMEM: Cannot allocate memory - ps ax -o pid,rss | grep -E "^[[:space:]]*23137" 

更新3

我可以使用下面的基本脚本重新创建该问题。 该脚本被硬编码为仅提取100k记录,并且已在本地VM上消耗超过512MB的内存。

 #! /usr/bin/ruby require 'uri' require 'net/http' require 'json' uri = URI.parse("https://someapi.com/data") offset = 0 values = [] begin http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.set_debug_output($stdout) request = Net::HTTP::Get.new(uri.request_uri + "?limit=10000&offset=#{offset}") request.add_field("Content-Type", "application/json") request.add_field("Accept", "application/json") response = http.request(request) json_response = JSON.parse(response.body) values << json_response['items'] offset += 10000 end while offset < 100_000 values 

更新4

我做了一些改进似乎有所帮助,但没有完全缓解这个问题。

1)使用symbolize_keys结果消耗更少的内存。 这是因为每个哈希的键都是相同的,并且象征它们然后将它们解析为单独的字符串会更便宜。

2)切换到ruby-yajl以进行JSON解析也会消耗更少的内存。

处理200k记录的内存消耗:

JSON.parse(response.body) :861080KB(在完全耗尽内存之前)

JSON.parse(response.body, symbolize_keys: true) :573580KB

Yajl::Parser.parse(response.body) :357236KB

Yajl::Parser.parse(response.body, symbolize_keys: true) :264576KB

但这仍然是一个问题。

  • 为什么不超过20MB的数据集会占用那么多内存?
  • 处理像这样的大型数据集的“正确方法”是什么?
  • 当数据集变大10倍时,人们会怎么做? 大100倍?

我会为能彻底回答这三个问题的人买啤酒!

非常感谢提前。

您已经确定了在您的arrays中使用+=的问题。 因此,可能的解决方案是每次添加数据而不创建新数组。

values.push response[value_key] if response.key? value_key

或者使用<<

values << response[value_key] if response.key? value_key

如果你真的想要一个新数组,你应该只使用+= 。 看起来你实际上并不想要一个新的数组,但实际上只是想要一个数组中的所有元素。