Есть ли какие-либо Ruby ORMs, которые используют курсоры или smart fetch?

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

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

 posts.each { |p| puts p }

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

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

    пост.find_in_batches { |batch| batch.каждый {/p / puts p } }

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

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


обновление:

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

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

5 ответов


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

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

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

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

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

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

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

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

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

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

Post.dataset = Post.dataset.use_cursor

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


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

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

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end

этот код работает быстрее, чем 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

может быть, вы можете рассмотреть ом на основе Redis хранилище NoSQL.