【问题标题】:Working with a large data object between ruby processes在 ruby​​ 进程之间处理大型数据对象
【发布时间】:2011-02-23 23:49:04
【问题描述】:

如果使用 Marshal.dump 将其写入文件,我有一个大约 10 兆字节的 Ruby 哈希。 gzip 压缩后大约为 500 KB。

在 ruby​​ 中迭代和更改此哈希非常快(毫秒的分数)。即使复制它也非常快。

问题是我需要在 Ruby on Rails 进程之间共享此哈希中的数据。为了使用 Rails 缓存(file_store 或 memcached)执行此操作,我需要首先 Marshal.dump 文件,但这会在序列化文件时产生 1000 毫秒的延迟,在序列化文件时会产生 400 毫秒的延迟。

理想情况下,我希望能够在 100 毫秒内从每个进程中保存和加载此哈希。

一个想法是产生一个新的 Ruby 进程来保存这个哈希,它为其他进程提供一个 API 来修改或处理其中的数据,但我想避免这样做,除非我确定没有其他进程快速共享此对象的方法。

有没有一种方法可以更直接地在进程之间共享此哈希,而无需对其进行序列化或反序列化?

这是我用来生成类似于我正在使用的哈希的代码:

@a = []
0.upto(500) do |r|
  @a[r] = []
  0.upto(10_000) do |c|
    if rand(10) == 0 
      @a[r][c] = 1 # 10% chance of being 1
    else
      @a[r][c] = 0
    end
  end
end

@c = Marshal.dump(@a) # 1000 milliseconds
Marshal.load(@c) # 400 milliseconds

更新:

由于我最初的问题没有收到很多回复,我假设没有像我希望的那样简单的解决方案。

目前我正在考虑两种选择:

  1. 创建一个 Sinatra 应用程序来存储这个哈希值,并使用一个 API 来修改/访问它。
  2. 创建一个 C 应用程序以执行与 #1 相同的操作,但要快得多。

我的问题范围扩大了,以至于哈希可能比我原来的例子大。所以#2可能是必要的。但我不知道从哪里开始编写公开适当 API 的 C 应用程序。

如何最好地实施 #1 或 #2 的良好演练可能会获得最佳答案。

更新 2

我最终将它实现为一个用 Ruby 1.9 编写的独立应用程序,它有一个 DRb 接口来与应用程序实例通信。我使用 Daemons gem 在 Web 服务器启动时生成 DRb 实例。启动时,DRb 应用程序从数据库加载必要的数据,然后与客户端通信以返回结果并保持最新状态。它现在在生产中运行得很好。感谢您的帮助!

【问题讨论】:

  • 您能展示一下您使用哈希的方式吗?您展示了如何创建它,但是如何使用它?您提到了迭代、更改和复制,但您最常如何访问此哈希?
  • 我正在使用哈希来计算用户之间的共性。每个值实际上代表一种兴趣,并设置为 0 或 1 来表示用户是否有兴趣。我需要能够非常快速地找到与用户最有共同兴趣的 20 个人。您可以在我对这个问题的回答中看到我正在使用的代码示例:stackoverflow.com/questions/2878429/…

标签: ruby-on-rails c ruby performance serialization


【解决方案1】:

sinatra 应用可以运行,但与 DRb 服务相比,{un}序列化和 HTML 解析可能会影响性能。

这是一个示例,基于您在相关问题中的示例。我使用的是哈希而不是数组,因此您可以使用用户 ID 作为索引。这样就不需要在服务器上同时保存一个兴趣表和一个用户 ID 表。请注意,与您的示例相比,兴趣表是“转置”的,无论如何这是您想要的方式,因此可以在一次调用中更新它。

# server.rb
require 'drb'

class InterestServer < Hash
  include DRbUndumped # don't send the data over!

  def closest(cur_user_id)
    cur_interests = fetch(cur_user_id)
    selected_interests = cur_interests.each_index.select{|i| cur_interests[i]}

    scores = map do |user_id, interests|
      nb_match = selected_interests.count{|i| interests[i] }
      [nb_match, user_id]
    end
    scores.sort!
  end
end

DRb.start_service nil, InterestServer.new
puts DRb.uri

DRb.thread.join


# client.rb

uri = ARGV.shift
require 'drb'
DRb.start_service
interest_server = DRbObject.new nil, uri


USERS_COUNT = 10_000
INTERESTS_COUNT = 500

# Mock users
users = Array.new(USERS_COUNT) { {:id => rand(100000)+100000} }

# Initial send over user interests
users.each do |user|
  interest_server[user[:id]] = Array.new(INTERESTS_COUNT) { rand(10) == 0 }
end

# query at will
puts interest_server.closest(users.first[:id]).inspect

# update, say there's a new user:
new_user = {:id => 42}
users << new_user
# This guy is interested in everything!
interest_server[new_user[:id]] = Array.new(INTERESTS_COUNT) { true } 

puts interest_server.closest(users.first[:id])[-2,2].inspect
# Will output our first user and this new user which both match perfectly

要在终端中运行,请启动服务器并将输出作为参数提供给客户端:

$ ruby server.rb
druby://mal.lan:51630

$ ruby client.rb druby://mal.lan:51630
[[0, 100035], ...]

[[45, 42], [45, 178902]]

【讨论】:

  • 谢谢。实际上,我与一个连接到同一数据库以初始加载数据的 sinatra 应用程序相距甚远,然后有一个 REST api 来保持最新状态并在发送用户 ID 时获取普通用户。从你的例子看,DRb 看起来很优雅,所以我想我也会试一试。
【解决方案2】:

也许这太明显了,但是如果您牺牲一点对哈希成员的访问速度,传统数据库将为您提供更恒定的时间访问值。您可以从那里开始,然后添加缓存以查看是否可以从中获得足够的速度。这将比使用 Sinatra 或其他工具简单一些。

【讨论】:

  • 感谢您的回复。数据实际上存储在传统数据库中。我将它全部复制到散列中,因为使用 SQL 运行我需要的查询太慢了。通过查看以下问题,您可以更好地了解我的用途:stackoverflow.com/questions/2878429/…
【解决方案3】:

小心使用 memcache,它有一些对象大小限制(2mb 左右)

要尝试的一件事是使用 MongoDB 作为存储。它非常快,您可以将几乎任何数据结构映射到其中。

【讨论】:

  • 是的。我确实达到了 memcached 的限制。它是 1MB。在使用 Marshal.dump 之后,我可以通过 gzip 压缩对象来解决这个问题,它符合限制,但会进一步损害性能。
【解决方案4】:

如果将你的怪物散列包装在方法调用中是明智的,你可以简单地使用 DRb 来呈现它 - 启动一个小守护进程,该守护进程以散列作为前端对象启动一个 DRb 服务器 - 其他进程可以使用什么来查询它相当于 RPC。

更重要的是,还有其他方法可以解决您的问题吗?在不知道您要做什么的情况下,很难确定 - 但也许 trie 或 Bloom 过滤器会起作用?甚至一个接口很好的位域也可能会为您节省相当多的空间。

【讨论】:

  • 我有一个相关的问题,解释了存储什么样的数据以及为什么在这里:stackoverflow.com/questions/2878429/… DRb 听起来很有趣。我很犹豫是否要加入它,因为它似乎真的很旧(最后一次更新是 6 月 4 日),而且很多谷歌搜索结果都是关于它的错误讨论。一种可能性可能是一个 Sinatra 应用程序,它公开了一个 API 以作用于哈希(至少这可能会更简单和更好地理解)。
  • 我认为“最后更新”问题来自于它是 Ruby 标准库的一部分。我认为最好看的就是 ruby​​.org/stdlib 上的 ruby​​doc。
【解决方案5】:

您是否考虑过提高内存缓存最大对象大小?

大于 1.4.2 的版本

memcached -I 11m #giving yourself an extra MB in space

或在以前的版本中更改slabs.c 中POWER_BLOCK 的值并重新编译。

【讨论】:

  • 是的。但这不是问题。简单地将对象放入 memcached 并将其从 memcached 中拉出需要太长时间,因为对象必须经过序列化和反序列化过程。
【解决方案6】:

将数据存储在 Memcache 中而不是将 Hash 存储在 Memcache 中怎么样?使用上面的代码:

@a = []
0.upto(500) do |r|
  @a[r] = []
  0.upto(10_000) do |c|
    key = "#{r}:#{c}"
    if rand(10) == 0 
      Cache.set(key, 1) # 10% chance of being 1
    else 
      Cache.set(key, 0)
    end
  end
end

这将很快,您不必担心序列化,您的所有系统都可以访问它。我在关于访问数据的主要帖子的评论中询问,你必须要有创意,但这应该很容易做到。

【讨论】:

  • 感谢您的回复。不幸的是,我需要能够非常快速地遍历数据(基本上在 150 毫秒内读取整个对象)。如果我将它存储在哈希或数组中,这是可能的,但如果每个值都单独存储在 memcached 中,则绝对不可能。
猜你喜欢
  • 2010-10-14
  • 2013-07-21
  • 2014-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多