簡體   English   中英

在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的元類來做到這一點的方法。 我們開始做吧..:-)

在服務中編寫標准之前,請存儲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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM