Работа с большим объектом данных между процессами ruby - PullRequest
6 голосов
/ 26 мая 2010

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

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

Проблема в том, что мне нужно разделить данные в этом хеше между процессами 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 может быть необходимым. Но я понятия не имею, с чего начать с точки зрения написания приложения на C, предоставляющего соответствующий API.

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

Обновление 2

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

Ответы [ 6 ]

3 голосов
/ 05 июня 2010

Приложение 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]]
2 голосов
/ 05 июня 2010

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

0 голосов
/ 06 июня 2010

Как насчет хранения данных в 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 голосов
/ 03 июня 2010

Рассматривали ли вы увеличение максимального размера объекта memcache?

Версии больше, чем 1.4.2

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

или в предыдущих версиях изменив значение POWER_BLOCK в slabs.c и перекомпилировав.

0 голосов
/ 27 мая 2010

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

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

0 голосов
/ 26 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...