У меня есть хеш 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
Обновление:
Так как мой исходный вопрос не получил много ответов, я предполагаю, что нет никакого решения, столь же легкого, как я надеялся бы.
В настоящее время я рассматриваю две возможности:
Объем моей проблемы увеличился таким образом, что хеш может быть больше, чем мой исходный пример. Таким образом, № 2 может быть необходимым. Но я понятия не имею, где запустить с точки зрения записи приложения C, которое выставляет соответствующий API.
Хорошая пошаговая демонстрация через, как лучше всего реализовать № 1 или № 2, может получить лучший кредит ответа.
Обновление 2
Я закончил тем, что реализовал это как отдельное приложение, записанное в Ruby 1.9, который имеет интерфейс DRb для общения с экземплярами приложения. Я использую драгоценный камень Демонов для порождения экземпляров DRb, когда веб-сервер запускает. На запуске загрузки приложения DRb в необходимых данных из базы данных, и затем это общается с клиентом, чтобы возвратить результаты и оставаться в курсе. Это работает вполне хорошо в производстве теперь. Спасибо за справку!
Приложение 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]]
Если есть смысл заключить свой хэш-монстр в вызов метода, вы можете просто представить его с помощью DRb - запустите небольшой демон, который запускает сервер DRb с хешем в качестве переднего объекта - другие процессы могут запрашивать его, используя то, что составляет RPC.
Если говорить более конкретно, есть ли другой подход к вашей проблеме? Не зная, что вы пытаетесь сделать, трудно сказать наверняка - но, может быть, три или фильтр Блума подойдут? Или даже битовое поле с красивым интерфейсом, вероятно, сэкономит вам изрядное количество места.
будьте осторожны с memcache, он имеет некоторые ограничения на размер объекта (2 МБ или около того)
Одна вещь, которую нужно попробовать, - использовать MongoDB в качестве хранилища. Это довольно быстро, и вы можете отобразить в него практически любую структуру данных.
Рассматривали ли вы возможность повышать максимальный размер объекта memcache?
Версии выше 1.4.2
memcached -I 11m #giving yourself an extra MB in space
или более поздних версий, изменяющие значение POWER_BLOCK в слябах.c и перекомпиляции.
Может быть, это слишком очевидно, но если вы немного пожертвуете скоростью доступа к членам вашего хэша, традиционная база данных предоставит вам гораздо более постоянный доступ по времени к значениям. Вы можете начать с этого места, а затем добавить кеширование, чтобы увидеть, сможете ли вы получить от него достаточную скорость. Это будет немного проще, чем использовать Sinatra или какой-либо другой инструмент.
Как насчет хранения данных в 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
Это будет быстро, и вам не придется беспокоиться о сериализации, и все ваши системы будут иметь к ней доступ. Я спросил в комментарии к основному посту о доступе к данным, вам придется проявить творческий подход, но это должно быть легко сделать.