Есть ли в Ruby ORM, которые используют курсоры или умную выборку? - PullRequest
19 голосов
/ 13 января 2012

Я ищу Ruby ORM для замены ActiveRecord.Я смотрел на сиквел и DataMapper.Они выглядят довольно неплохо, однако ни один из них, похоже, не выполняет основных задач: не загружает все в память, когда вам это не нужно.

Я имею в виду, что я попробовал следующее (или эквивалентное) на ActiveRecord и Sequel натаблица с множеством строк:

 posts.each { |p| puts p }

Они оба сходят с ума по памяти.Кажется, они загружают все в память, а не загружают что-либо, когда это необходимо.Я использовал find_in_batches в ActiveRecord, но это неприемлемое решение:

  1. ActiveRecord не является приемлемым решением, потому что у нас было слишком много проблем с ним.
  2. Почемумой код должен знать механизм подкачки?Я счастлив где-то настроить размер страницы, но это все.С find_in_batches вам нужно сделать что-то вроде:

    post.find_in_batches {| batch |batch.each {| p |ставит p}}

Но это должно быть прозрачно.

Так есть ли где-нибудь надежный Ruby ORM, который выполняет выборку правильно?


Обновление:

Как упоминал Серхио, в Rails 3 вы можете использовать find_each, что именно то, что я хочу.Однако, поскольку ActiveRecord не является опцией, за исключением случаев, когда кто-то действительно может убедить меня его использовать, возникают следующие вопросы:

  1. Какие ORM поддерживают эквивалент find_each?
  2. Как это сделать?
  3. Зачем нам нужен find_each, а find должен это делать, не так ли?

Ответы [ 5 ]

43 голосов
/ 13 января 2012

Сиквел Dataset#each выдаёт отдельные строки за раз, но большинство драйверов баз данных сначала загружают весь результат в память.

Если вы используете адаптер Sequel Postgres, вы можете использовать настоящие курсоры:

posts.use_cursor.each{|p| puts p}

Это выбирает 1000 строк за раз по умолчанию, но вы можете использовать опцию, чтобы указать количество строк, которые нужно захватить за выборку курсора:

posts.use_cursor(:rows_per_fetch=>100).each{|p| puts p}

Если вы не используете адаптер Sequel Postgres, вы можете использовать расширение нумерации Sequel:

Sequel.extension :pagination
posts.order(:id).each_page(1000){|ds| ds.each{|p| puts p}}

Однако, как и в ActiveRecord find_in_batches / find_each, он выполняет отдельные запросы, поэтому вам следует быть осторожным, если есть одновременные изменения в наборе данных, который вы извлекаете.

Причина, по которой это не значение по умолчанию в сиквеле, вероятно, та же самая причина, по которой это не значение по умолчанию в ActiveRecord, а именно то, что в общем случае это не очень хорошее значение по умолчанию. Только запросы с большими результирующими наборами действительно должны беспокоиться об этом, и большинство запросов не возвращают большие результирующие наборы.

По крайней мере, с поддержкой курсора адаптера Postgres, довольно просто сделать его по умолчанию для вашей модели:

Post.dataset = Post.dataset.use_cursor

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

4 голосов
/ 13 февраля 2012
Sequel.extension :pagination
posts.order(:id).each_page(1000) do |ds|
  ds.each { |p| puts p }
end

Это очень и очень медленно на больших таблицах!

Становится ясно, если посмотреть на тело метода: http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Dataset.html#method-i-paginate

# File lib/sequel/extensions/pagination.rb, line 11

def paginate(page_no, page_size, record_count=nil)
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
  paginated = limit(page_size, (page_no - 1) * page_size)
  paginated.extend(Pagination)
  paginated.set_pagination_info(page_no, page_size, record_count || count)
end
3 голосов
/ 13 января 2012

ActiveRecord на самом деле имеет почти прозрачный пакетный режим :

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end
2 голосов
/ 13 февраля 2012

Этот код работает быстрее, чем find_in_batches в ActiveRecord

id_max = table.get(:max[:id])
id_min = table.get(:min[:id])
n=1000
(0..(id_max-id_min)/n).map.each do |i|
    table.filter(:id >= id_min+n*i, :id < id_min+n*(i+1)).each {|row|}
end
0 голосов
/ 13 января 2012

Может быть, вы можете рассмотреть Ом , основанный на Redis NoSQL store.

...