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

时间:2014-08-13 05:29:04

标签: ruby out-of-memory

当我浏览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倍大?

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

提前多多感谢。

1 个答案:

答案 0 :(得分:0)

您已确定在您的阵列中使用+=的问题。因此,可能的解决方案是在不创建新数组的情况下添加数据。

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

或使用<<

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

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