遍历数据库中的所有记录时,我们首先想到的是Model.all.each。但是,当数据量很大的时候(数万?),这就不怎么合适了,因为Model.all.each会一次性加载所有记录,并将其实例化成 Model 对象,这显然会增加内存负担,甚至耗尽内存。

对于ActiveRecord 而言,有个find_each专门解决此类问题。find_each底层依赖于find_in_batches,会分批加载记录,默认每批为1000。

Mongoid而言,话说可以直接用Person.all.each,它会自动利用游标(cursor)帮你分批加载的。不过有个问题得留意一下:cursor有个10分钟超时限制。这就意味着遍历时长超过10分钟就危险了,很可能在中途遭遇no cursor错误。

# gems/mongo-2.2.4/lib/mongo/collection.rb:218
# @option options [ true, false ] :no_cursor_timeout The server normally times out idle cursors
# after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.

虽然可以这样来绕过它 Model.all.no_timeout.each,不过不建议这样做。另外默认的batch_size不一定合适你,可以这样指定Model.all.no_timeout.batch_size(500).each。不过 mongodb 的默认batch_size看起来比较复杂,得谨慎。(The MongoDB server returns the query results in batches. Batch size will not exceed the maximum BSON document size. For most queries, the first batch returns 101 documents or just enough documents to exceed 1 megabyte. Subsequent batch size is 4 megabytes. To override the default size of the batch, see batchSize() and limit().

For queries that include a sort operation without an index, the server must load all the documents in memory to perform the sort before returning any results.

As you iterate through the cursor and reach the end of the returned batch, if there are more results, cursor.next() will perform a getmore operation to retrieve the next batch. )

Model.all.each { print '.' } 将得到类似的查询:

类似的方案应是利用skiplimit,类似于这样Model.all.skip(m).limit(n)。很不幸的是,数据量过大时,这并不好使,因为随着 skip 值变大,会越来越慢(The cursor.skip() method is often expensive because it requires the server to walk from the beginning of the collection or index to get the offset or skip position before beginning to return results. As the offset (e.g. pageNumber above) increases, cursor.skip() will become slower and more CPU intensive. With larger collections, cursor.skip() may become IO bound.)。这让我想起了曾经看过的帖子will_paginate 分页过多 (大概 10000 页),点击最后几页的时候,速度明显变慢,大致原因就是分页底层用到了offset,也是因为offset 越大查询就会越慢。

让我们再次回到ActiveRecordfind_each上来。Rails 考虑得很周全,它底层没利用offset,而是将每批查询的最后一条记录的id作为下批查询的primary_key_offset

# gems/activerecord-4.2.5.1/lib/active_record/relation/batches.rb:98
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size) relation = self
start = options[:start]
batch_size = options[:batch_size] || 1000 unless block_given?
return to_enum(:find_in_batches, options) do
total = start ? where(table[primary_key].gteq(start)).size : size
(total - 1).div(batch_size) + 1
end
end if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end relation = relation.reorder(batch_order).limit(batch_size)
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a while records.any?
records_size = records.size
primary_key_offset = records.last.id
raise "Primary key not included in the custom select clause" unless primary_key_offset yield records break if records_size < batch_size records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
end
end

前面的Model.all.no_timeout.batch_size(1000).each是 server 端的批量查询,我们也可模仿出client 端的批量查询,即Mongoid版的find_each:

# /config/initializers/mongoid_batches.rb
module Mongoid
module Batches
def find_each(batch_size = 1000)
return to_enum(:find_each, batch_size) unless block_given? find_in_batches(batch_size) do |documents|
documents.each { |document| yield document }
end
end def find_in_batches(batch_size = 1000)
return to_enum(:find_in_batches, batch_size) unless block_given? documents = self.limit(batch_size).asc(:id).to_a
while documents.any?
documents_size = documents.size
primary_key_offset = documents.last.id yield documents break if documents_size < batch_size
documents = self.gt(id: primary_key_offset).limit(batch_size).asc(:id).to_a
end
end
end
end Mongoid::Criteria.include Mongoid::Batches

最后对于耗时操作,还可考虑引入并行计算,类似于这样:

Model.find_each { ... }

Model.find_in_batches do |items|
Parallel.each items, in_processes: 4 do |item|
# ...
end
end

参考链接

  1. https://docs.mongodb.com/manual/core/cursors/
  2. https://docs.mongodb.com/master/reference/method/cursor.skip/
  3. https://ruby-china.org/topics/28659

Mongoid Paging and Iterating Over Large Collections的更多相关文章

  1. Learning part-based templates from large collections of 3D shapse CorrsTmplt Kim 代码调试

    平台: VMware上装的Ubuntu-15.10 环境准备工作:装Fortran, lapack, blas, cblas (理论上装好lapack后面两个应该是自动的),其他的有需要的随时安装就可 ...

  2. Java性能提示(全)

    http://www.onjava.com/pub/a/onjava/2001/05/30/optimization.htmlComparing the performance of LinkedLi ...

  3. EF 5 最佳实践白皮书

    Performance Considerations for Entity Framework 5 By David Obando, Eric Dettinger and others Publish ...

  4. Java 8 Stream API Example Tutorial

    Stream API Overview Before we look into Java 8 Stream API Examples, let’s see why it was required. S ...

  5. Unity 5 Game Optimization (Chris Dickinson 著)

    1. Detecting Performance Issues 2. Scripting Strategies 3. The Benefits of Batching 4. Kickstart You ...

  6. 【转】最佳Restful API 实践

    原文转自:https://bourgeois.me/rest/ REST APIs are a very common topic nowaday; they are part of almost e ...

  7. 【翻译十九】-java之执行器

    Executors In all of the previous examples, there's a close connection between the task being done by ...

  8. 【翻译十七】java-并发之高性能对象

    High Level Concurrency Objects So far, this lesson has focused on the low-level APIs that have been ...

  9. Understanding, Operating and Monitoring Apache Kafka

    Apache Kafka is an attractive service because it's conceptually simple and powerful. It's easy to un ...

随机推荐

  1. Django基础--4

    补充知识-路由系统(URL) URL传递额外的参数 在url.py里,除了默认会传一个request给处理函数,还可以传递额外的参数,把一个字典作为第三个参数传入,之后就可以在处理函数里取到对应的值: ...

  2. Vim插件推荐

    看下文时要知道我的<leader>键就是\. ctags C程序阅读辅助工具.在看C/C++代码的时候经常需要在文件之间跳来跳去,这是很麻烦的事,ctags就是解决这种问题的.ctags是 ...

  3. 复制windows CMD命令行中的内容

    标记文本后,按"回车",或鼠标"右键"为从CMD中复制文本. 在CMD中,按鼠标"右键",为在CMD中粘贴文本.

  4. InnoDB多版本(MVCC)实现简要分析

    转载自:http://hedengcheng.com/?p=148 基本知识 假设对于多版本(MVCC)的基础知识,有所了解.InnoDB为了实现多版本的一致读,采用的是基于回滚段的协议. 行结构 I ...

  5. nginx里面的rewrite配置

    哎,我需要静静,刚刚在去怎么优化dom层级,发现更新完代码,层级又蹭蹭蹭的往上涨,顿时没脾气了,还是把昨天的nginx配置总结下,增加点动力,昨天前天两天都在搞这个问题,也是搞的没脾气,网上查了很多资 ...

  6. caffe RandomOrderChannels

    就是将rgb图的3个channel随机打乱顺序,给定一个概率值是否执行这个操作,v系列模型的概率一般都设置的为0 void RandomOrderChannels(const cv::Mat& ...

  7. P2065 [TJOI2011]卡片

    真·会了网络流,忘了匈牙利 一共两种颜色,很容易想到二分图劈配 虽然这题数据水 但是,数据强的话肯定会卡掉的. 为什么? 我暴力质因数都过了 我们可以在两边的数与质因数相连.然后跑网络流. #incl ...

  8. 1、React-Native的基础入门

    React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安 ...

  9. POJ2409 Let it Bead(Polya定理)

    Let it Bead Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6443   Accepted: 4315 Descr ...

  10. html编写头部,mata的含义

    <meta name="viewport" content="width=device-width, initial-scale=1.0"> con ...