在ruby进程之间使用大型数据对象

如果使用Marshal.dump写入文件,我有一个大约10兆字节的Ruby哈希。 在gzip压缩之后它大约是500千字节。

迭代和更改此哈希在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接口。 我在Web服务器启动时使用Daemons gem生成DRb实例。 启动时,DRb应用程序从数据库加载必要的数据,然后与客户端通信以返回结果并保持最新。 它现在在生产中运行得很好。 谢谢您的帮助!

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或其他工具要简单一些。

注意memcache,它有一些对象大小限制(2mb左右)

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

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

更重要的是,您的问题还有另一种解决方法吗? 在不知道你想要做什么的情况下,很难肯定地说 – 但是也许trie或Bloomfilter会起作用? 或者甚至一个很好的接口位域可能会为你节省相当多的空间。

您是否考虑过提高memcache的最大对象大小?

版本大于1.4.2

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

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

如何在Memcache中存储数据而不是在Memcache中存储Hash? 使用上面的代码:

 @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 

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