Работа с большим объектом данных между рубиновыми процессами

У меня есть хеш Ruby, который достигает приблизительно 10 мегабайтов, если записано в файл с помощью Marshal.dump. После gzip сжатие это - приблизительно 500 килобайтов.

Итерация через и изменение этого хеша очень быстры в рубине (части миллисекунды). Даже копирование его чрезвычайно быстро.

Проблема состоит в том, что я должен совместно использовать данные в этом хеше между процессами Ruby on Rails. Чтобы сделать это использование кэша направляющих (file_store или memcached), мне нужен к Marshal.dump файл сначала, однако это подвергается 1 000 задержек миллисекунды при сериализации файла и 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 может быть необходимым. Но я понятия не имею, где запустить с точки зрения записи приложения C, которое выставляет соответствующий API.

Хорошая пошаговая демонстрация через, как лучше всего реализовать № 1 или № 2, может получить лучший кредит ответа.

Обновление 2

Я закончил тем, что реализовал это как отдельное приложение, записанное в Ruby 1.9, который имеет интерфейс DRb для общения с экземплярами приложения. Я использую драгоценный камень Демонов для порождения экземпляров DRb, когда веб-сервер запускает. На запуске загрузки приложения DRb в необходимых данных из базы данных, и затем это общается с клиентом, чтобы возвратить результаты и оставаться в курсе. Это работает вполне хорошо в производстве теперь. Спасибо за справку!

6
задан Gdeglin 30 June 2010 в 00:29
поделиться

6 ответов

Приложение sinatra будет работать, но {де} сериализация и анализ HTML могут повлиять на производительность по сравнению со службой DRb.

Вот пример, основанный на вашем примере в соответствующем вопросе. Я использую хэш вместо массива, чтобы вы могли использовать идентификаторы пользователей в качестве индексов. Таким образом, нет необходимости хранить на сервере как таблицу интересов, так и таблицу идентификаторов пользователей. Обратите внимание, что таблица процентов "транспонирована" по сравнению с вашим примером, и вы все равно хотите, чтобы она могла быть обновлена ​​за один вызов.

# 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]]
3
ответ дан 17 December 2019 в 04:43
поделиться

Если есть смысл заключить свой хэш-монстр в вызов метода, вы можете просто представить его с помощью DRb - запустите небольшой демон, который запускает сервер DRb с хешем в качестве переднего объекта - другие процессы могут запрашивать его, используя то, что составляет RPC.

Если говорить более конкретно, есть ли другой подход к вашей проблеме? Не зная, что вы пытаетесь сделать, трудно сказать наверняка - но, может быть, три или фильтр Блума подойдут? Или даже битовое поле с красивым интерфейсом, вероятно, сэкономит вам изрядное количество места.

0
ответ дан 17 December 2019 в 04:43
поделиться

будьте осторожны с memcache, он имеет некоторые ограничения на размер объекта (2 МБ или около того)

Одна вещь, которую нужно попробовать, - использовать MongoDB в качестве хранилища. Это довольно быстро, и вы можете отобразить в него практически любую структуру данных.

0
ответ дан 17 December 2019 в 04:43
поделиться

Рассматривали ли вы возможность повышать максимальный размер объекта memcache?

Версии выше 1.4.2

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

или более поздних версий, изменяющие значение POWER_BLOCK в слябах.c и перекомпиляции.

0
ответ дан 17 December 2019 в 04:43
поделиться

Может быть, это слишком очевидно, но если вы немного пожертвуете скоростью доступа к членам вашего хэша, традиционная база данных предоставит вам гораздо более постоянный доступ по времени к значениям. Вы можете начать с этого места, а затем добавить кеширование, чтобы увидеть, сможете ли вы получить от него достаточную скорость. Это будет немного проще, чем использовать Sinatra или какой-либо другой инструмент.

2
ответ дан 17 December 2019 в 04:43
поделиться

Как насчет хранения данных в Memcache вместо хранения хэша в 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

Это будет быстро, и вам не придется беспокоиться о сериализации, и все ваши системы будут иметь к ней доступ. Я спросил в комментарии к основному посту о доступе к данным, вам придется проявить творческий подход, но это должно быть легко сделать.

0
ответ дан 17 December 2019 в 04:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: