上周,我花了很多時間嘗試使用實時系統中的數據來優化大約20個執行失敗的Cypher查詢(36866ms至155575ms)。 經過一番嘗試和錯誤,以及來自Michael的大量投入,我能夠大致確定對查詢進行哪些操作才能使它們性能更好-最后,性能最差的查詢降至521ms冷圖和1GB堆空間(并且該查詢具有可選路徑-不確定如何改進),其余都在50ms以下-與原始數字相比有很大改進。
希望這可能對其他人有所幫助,這就是我所做的事情(大多數是猜測工作,并且在很大程度上不科學)-也許Michael可以幫助解釋內部原理,并根據某些假設對我進行糾正。 我要做的第一件事是確保每個密碼查詢都使用參數-如Neo4j文檔中所述 ,這有助于緩存執行計劃。
其次,我遇到了Neo4j郵件列表中的一則帖子,其中Michael提到不要重新實例化ExecutionEngine,以便上述參數化查詢實際上可以緩存。 對于許多人來說,這似乎很明顯,但這是一個容易被忽視的事實,因為我有一個名為QueryExecutor的類,該類包含一個執行帶有參數映射的查詢的方法,并且該方法為每個查詢創建了一個新的ExecutionEngine。 一旦編寫并多次使用了此方法,就很容易忘記。 但是,這是影響整體性能的一個非常重要的因素(文檔中的提及將非常有幫助),它解釋了為什么即使在參數化的情況下,我的查詢通常也需要花費相同的時間來執行。 將其更改為使用緩存的ExecutionEngine,可以看到我的查詢時間工作表的下半部分在緩存后下降了0到1毫秒,這是非常出色的進步。
現在,從最壞的情況開始進入每個查詢。 我決定在本地計算機上進行優化,僅分配1GB的堆空間,并在一個冷圖上進行優化。 因此,我忽略了緩存后查詢執行的改進-我認為這是確保進度的一種更好的方法-如果第一個查詢命中沒有改善,那么您確實沒有對其進行優化。 這樣,如果它在有限的硬件上確實能很好地工作,我對它在生產中可以更好地工作充滿信心。
除了在代碼中安排查詢時間之外,我還使用webadmin控制臺進行了優化。 該查詢糟糕的指標是它不會返回并且控制臺將掛起。 優化以使其不會掛起本身就是一個重大改進。 高度不科學,但我建議這樣做。
我的第一個查詢平均大約76558毫秒(該時間是通過在引擎execute方法周圍添加開始時間和結束時間而獲得的)。 第一次優化后,它減少到466ms。 這是原始查詢: https : //gist.github.com/4436272
這是經過優化的一個: https ://gist.github.com/4436281無需執行此大型匹配只是為了基于a的alertDate屬性過濾出結果,因此我減少了匹配以返回最小的集合首先可以過濾的數據,即 通往的道路
如果要在第一次匹配之前執行錯誤查詢,則會看到返回2萬行之類的信息。 過濾后,它們只有450個奇數行。 可以想象,第二個查詢Swift減少了您可能使用的結果數量。
第二個變化是我那天從邁克爾那里學到的東西,當時我問做大型比賽還是繼續修剪子查詢是否有意義。 他的回答是:“ 問題是,您是在使用match來通過描述模式來實際擴展結果集,還是只是想確定某些關系存在(或不存在),即過濾。 如果是后者,則可能要在where子句中使用path-expression語法。
WHERE a-[:FOO]->b
or WHERE NOT(a-[:FOO]->b)'
這花了一點時間來適應,因為我編寫MATCH子句的方式恰恰是我腦海中的想法,但是現在我可以區分出所需結果的匹配項與過濾器的匹配項了。 在上面的查詢中,我的結果中需要(ir),因此無需在匹配中包含(a)-[:alert_for_inspection]->(i); 我可以在WHERE中使用它來確保a確實與i有關。
這是另一個示例: https : //gist.github.com/4436293
立刻,您會看到我們按日期過濾了cm關系-如果它們不在日期范圍內,則無需我們先進行匹配。 因此,查詢的這一部分可以重寫為:
start c=node:companies(id={id})
match c <-[parent_company*0..]-(n)with n
match n-[:defines_business_process]->(bp)-[:has_cycle]->(cm)
where cm.measureDate>={measureStartDate} and cm.measureDate<={measureEndDate}
在那之后,下一個過濾器處于相同的原理:
with cm
match (cm)-[:cycle_metric] ->m-[:metric_activity] ->ma-[:metric_unit] -> (u)-[:alert_for_unit]-(a) where a.alertDate=cm.measureDate and a.fromEntityType={type}
這進一步修剪了我們的結果集。 最后,將連接添加到r(以得到我們的結果),確保不會導致r產生但必不可少的路徑進入WHERE子句:
with a,ma
match (r) < - [:for_inspection_result]-a-[:alert_for_inspection]- > i
where (i) < -[:metric_inspection]-(ma)
return a.id as alertId, r.value as resultValue
這是完整的查詢: https ://gist.github.com/4436328原始時間-33360毫秒,經過優化-246毫秒。
至少對我來說,我的大多數查詢都屬于這種模式,因此,到第二天,我就能夠非常快速地重構它們。 有趣的是,在此之后,我仍然感到響應緩慢,但是在日志中打印的查詢時間很小。 消除之后,我發現我的代碼在執行查詢之后(由executionEngine.execute)實際上停留了很長時間,但在結果集的第一次迭代中。我假設結果不一定在execute()方法期間收集,但在結果集的迭代中比較懶惰-我不了解Cypher內部結構,因此我可能完全錯了。 但是定時迭代本身可以指出更差的查詢。
其他點點滴滴-ORDER BY增加了很多時間。 如果沒有它,您應該放下第一件事。 DISTINCT還增加了時間,但是在我的許多情況下,很難刪除它。
如果要檢查是否缺少可選路徑,通常在哪里進行MATCH(u)-[r?:has_a]-(a)WHERE NOT(r為null),而是改寫為MATCH(u)-[ other_stuff]-.. WHERE NOT(u-[:has_a] -a)的效果要好得多。 但是,在我有諸如MATCH X- [o?:optional] -Y WHERE(存在o,將Y匹配到A和B)或OR(不存在o,將X匹配到c和d)的可選路徑的地方,我無法簡化與沒有可選路徑的其他查詢相比,這些查詢仍需要一些時間。
最終-該問題被發現得太晚了,因為測試數據從未與實時數據非常接近。 圖的結構起著很大的作用-一些節點是緊密連接的,而其他節點則不是很多-并且涉及那些緊密連接的節點的查詢是對我們最大的傷害。 盡可能嘗試使用生產質量數據進行性能測試,或者嘗試創建與之類似的測試數據。
因此,總結一下:
- 始終參數化您的查詢
- 緩存ExecutionEngine
- 找出要過濾的內容,并盡早在涉及的匹配最少的情況下應用該過濾器,以便在進一步查詢時結果集逐漸變小。 繼續測量每個子查詢中返回的時間和結果,以便您可以確定過濾器不明顯時先執行的操作
- 檢查您的MATCH和RETURN子句-在MATCH中僅包括RETURN所需的那些部分。 剩下的將用于過濾結果的信息可以進入WHERE
- 如果您不需要ORDER BY,請昨天放下
- 如果您不需要DISTINCT,也要擺脫它
- 如果不需要基于可選路徑的檢查,可以將檢查是否存在可選路徑從MATCH移到WHERE
- 不僅要花費查詢的execute(),還要花費時間迭代結果
- 如果您的Webadmin控制臺掛起,則說明您做的不好。 刪除查詢的各個部分以找出有問題的部分。
- 嘗試盡可能使用實時數據
- 在資源不佳的冷圖上進行測試-當您看到生產中的圖形滑過您時,您會感覺好多了!
參考: Thought Bytes博客上來自我們JCG合作伙伴 Aldrin和Luanne Misquitta的優化Neo4j Cypher查詢 。
翻譯自: https://www.javacodegeeks.com/2013/01/optimizing-neo4j-cypher-queries.html