在 Grails 和 MongoDB 插件的条件查询中使用 allowDiskUse?
Use allowDiskUse in criteria query with Grails and the MongoDB plugin?
为了使用 Grails (2.5.0) 和 MongoDB 插件 (3.0.2) 遍历 MongoDB (2.6.9) 集合中的所有文档,我创建了一个 forEach像这样:
class MyObjectService {
def forEach(Closure func) {
def criteria = MyObject.createCriteria()
def ids = criteria.list { projections { id() } }
ids.each { func(MyObject.get(it)) }
}
}
然后我这样做:
class AnalysisService{
def myObjectService
@Async
def analyze(){
MyObject.withStatelessSession {
myObjectService.forEach { myObject ->
doSomethingAwesome(myObject)
}
}
}
}
这很好用...直到我遇到一个很大的集合(>500K 文档),此时抛出 CommandFailureException,因为聚合结果的大小大于 16MB。
Caused by CommandFailureException: { "serverUsed" : "foo.bar.com:27017" , "errmsg" : "exception: aggregation result exceeds maximum document size (16MB)" , "code" : 16389 , "ok" : 0.0}
在阅读这篇文章时,我认为处理这种情况的一种方法是在 MongoDB 端运行的聚合函数中使用选项 allowDiskUse
,这样 16MB 的内存限制就赢了'申请,我可以获得更大的聚合结果。
如何将此选项传递给我的条件查询?我一直在阅读 Grails MongoDB 插件的文档和 Javadoc,但我似乎找不到它。是否有另一种方法来解决一般问题(遍历大量域对象的所有成员)?
MongoDB Grails 插件的当前实现无法做到这一点。 https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm-mongodb/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java#L957
如果您查看上面的行,您会看到默认选项用于构建 AggregationOptions 实例,因此没有提供选项的方法。
但是使用 Groovy 的 metaclass 还有另一种骇人听闻的方法。让我们开始吧..:-)
在您的服务中编写条件之前,存储 builder()
方法的原始方法参考:
MetaMethod originalMethod = AggregationOptions.metaClass.static.getMetaMethod("builder", [] as Class[])
然后,替换生成器方法以提供您的实现。
AggregationOptions.metaClass.static.builder = { ->
def builderInstance = new AggregationOptions.Builder()
builderInstance.allowDiskUse(true) // solution to your problem
return builderInstance
}
现在,您的服务方法将通过标准查询调用,并且不会导致您收到的聚合错误,因为我们尚未将 allowDiskUse
属性 设置为 true。
现在,重置原来的方法,这样它就不会影响任何其他调用(可选)。
AggregationOptions.metaClass.static.addMetaMethod(originalMethod)
希望对您有所帮助!
除此之外,为什么在forEach
方法中提取所有ID,然后使用get()
方法重新获取实例?您正在浪费会影响性能的数据库查询。此外,如果您按照此操作,则不必进行上述更改。
一个相同的例子:(UPDATED)
class MyObjectService {
void forEach(Closure func) {
List<MyObject> instanceList = MyObject.createCriteria().list {
// Your criteria code
eq("status", "ACTIVE") // an example
}
// Don't do any of this
// println(instanceList)
// println(instanceList.size())
// *** explained below
instanceList.each { myObjectInstance ->
func(myObjectInstance)
}
}
}
(我没有添加 AnalysisService
的代码,因为没有变化)
*** 重点就到这里了。所以每当你在域 class 中写入任何条件(没有投影并且在 mongo 中),执行条件代码后, Grails/gmongo 不会立即从数据库中获取记录,除非你调用一些方法就像 toString()
、'size()or
dump()` 一样。
现在,当您在该实例列表上应用 each
时,您实际上不会将所有实例加载到内存中,而是在幕后和 Mongo 中迭代 Mongo 光标DB,游标使用批处理从数据库中提取记录,这是非常内存安全的。因此,您可以安全地直接调用每个标准结果,这不会破坏 JVM,除非您调用任何触发从数据库加载所有记录的方法。
每当你写任何没有投影的条件时,你都会得到一个 MongoResultList
的实例,并且有一个名为 initializeFully()
的方法正在 toString()
和其他方法上调用。但是,您可以看到 MongoResultList
正在实现迭代器,它依次调用 MongoDB 游标方法来迭代大型集合,这又是内存安全的。
希望对您有所帮助!
为了使用 Grails (2.5.0) 和 MongoDB 插件 (3.0.2) 遍历 MongoDB (2.6.9) 集合中的所有文档,我创建了一个 forEach像这样:
class MyObjectService {
def forEach(Closure func) {
def criteria = MyObject.createCriteria()
def ids = criteria.list { projections { id() } }
ids.each { func(MyObject.get(it)) }
}
}
然后我这样做:
class AnalysisService{
def myObjectService
@Async
def analyze(){
MyObject.withStatelessSession {
myObjectService.forEach { myObject ->
doSomethingAwesome(myObject)
}
}
}
}
这很好用...直到我遇到一个很大的集合(>500K 文档),此时抛出 CommandFailureException,因为聚合结果的大小大于 16MB。
Caused by CommandFailureException: { "serverUsed" : "foo.bar.com:27017" , "errmsg" : "exception: aggregation result exceeds maximum document size (16MB)" , "code" : 16389 , "ok" : 0.0}
在阅读这篇文章时,我认为处理这种情况的一种方法是在 MongoDB 端运行的聚合函数中使用选项 allowDiskUse
,这样 16MB 的内存限制就赢了'申请,我可以获得更大的聚合结果。
如何将此选项传递给我的条件查询?我一直在阅读 Grails MongoDB 插件的文档和 Javadoc,但我似乎找不到它。是否有另一种方法来解决一般问题(遍历大量域对象的所有成员)?
MongoDB Grails 插件的当前实现无法做到这一点。 https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm-mongodb/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java#L957
如果您查看上面的行,您会看到默认选项用于构建 AggregationOptions 实例,因此没有提供选项的方法。
但是使用 Groovy 的 metaclass 还有另一种骇人听闻的方法。让我们开始吧..:-)
在您的服务中编写条件之前,存储 builder()
方法的原始方法参考:
MetaMethod originalMethod = AggregationOptions.metaClass.static.getMetaMethod("builder", [] as Class[])
然后,替换生成器方法以提供您的实现。
AggregationOptions.metaClass.static.builder = { ->
def builderInstance = new AggregationOptions.Builder()
builderInstance.allowDiskUse(true) // solution to your problem
return builderInstance
}
现在,您的服务方法将通过标准查询调用,并且不会导致您收到的聚合错误,因为我们尚未将 allowDiskUse
属性 设置为 true。
现在,重置原来的方法,这样它就不会影响任何其他调用(可选)。
AggregationOptions.metaClass.static.addMetaMethod(originalMethod)
希望对您有所帮助!
除此之外,为什么在forEach
方法中提取所有ID,然后使用get()
方法重新获取实例?您正在浪费会影响性能的数据库查询。此外,如果您按照此操作,则不必进行上述更改。
一个相同的例子:(UPDATED)
class MyObjectService {
void forEach(Closure func) {
List<MyObject> instanceList = MyObject.createCriteria().list {
// Your criteria code
eq("status", "ACTIVE") // an example
}
// Don't do any of this
// println(instanceList)
// println(instanceList.size())
// *** explained below
instanceList.each { myObjectInstance ->
func(myObjectInstance)
}
}
}
(我没有添加 AnalysisService
的代码,因为没有变化)
*** 重点就到这里了。所以每当你在域 class 中写入任何条件(没有投影并且在 mongo 中),执行条件代码后, Grails/gmongo 不会立即从数据库中获取记录,除非你调用一些方法就像 toString()
、'size()or
dump()` 一样。
现在,当您在该实例列表上应用 each
时,您实际上不会将所有实例加载到内存中,而是在幕后和 Mongo 中迭代 Mongo 光标DB,游标使用批处理从数据库中提取记录,这是非常内存安全的。因此,您可以安全地直接调用每个标准结果,这不会破坏 JVM,除非您调用任何触发从数据库加载所有记录的方法。
每当你写任何没有投影的条件时,你都会得到一个 MongoResultList
的实例,并且有一个名为 initializeFully()
的方法正在 toString()
和其他方法上调用。但是,您可以看到 MongoResultList
正在实现迭代器,它依次调用 MongoDB 游标方法来迭代大型集合,这又是内存安全的。
希望对您有所帮助!