![](/img/trans.png)
[英]Can we always use allowDiskUse:true in MongoDB aggregation query
[英]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的元類來做到這一點的方法。 我們開始做吧..:-)
在服務中編寫標准之前,請存儲builder()
方法的原始方法引用:
MetaMethod originalMethod = AggregationOptions.metaClass.static.getMetaMethod("builder", [] as Class[])
然后,替換builder方法以提供您的實現。
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
提取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
的代碼)
***主要要點在這里。 因此,只要您在域類中編寫任何條件(不使用投影和mongo),執行條件代碼后,Grails / gmongo都不會立即從數據庫中獲取記錄,除非您調用諸如toString()
,'size() or
在它們上轉儲()。
現在,當您將each
實例應用於該實例列表時,實際上並不會將所有實例加載到內存中,而是在幕后和MongoDB中遍歷Mongo Cursor,游標使用批處理從數據庫中提取記錄,這是非常安全的內存。 因此,可以安全地直接在條件結果上調用每個方法,這不會破壞JVM,除非您調用了觸發從數據庫加載所有記錄的任何方法。
您甚至可以在代碼中確認此行為: https : //github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm-mongodb/src/main/groovy/org/grails/datastore/映射/mongo/query/MongoQuery.java#L1775
每當您編寫任何沒有投影的條件時,您都會獲得MongoResultList
的實例,並且有一個名為initializeFully()
的方法正在toString()
和其他方法上調用。 但是,您可以看到MongoResultList
正在實現迭代器,該迭代器又調用MongoDB游標方法來迭代大型集合,這又是內存安全的。
希望這可以幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.