Elasticsearch 優化排序查詢,更快獲得結果
請求按某個字段對結果進行排序是 Elasticsearch 極爲常用的操作。我們投入了大量時間和精力來優化排序查詢,以便爲用戶提供更快的排序查詢體驗。本篇博文將介紹我們對數值和日期字段進行的一些排序優化。
排序查詢的工作原理
當您想要找到匹配篩選條件的文檔,並請求按某個字段對結果進行排序時,Elasticsearch 會檢查該字段的文檔值,找出所有匹配篩選條件的文檔,並根據排在最前面的值選出前 K 個文檔。最複雜的情況莫過於篩選條件極其寬泛(例如 match_all query),此時,我們必須檢查並比較某個索引中所有文檔的文檔值。對於大的索引來說,這一過程可能需要相當長的時間。
優化特定字段排序查詢的方法之一是使用索引排序,並對該字段中的整個索引進行排序。如果索引按字段進行了排序,那麼其文檔值也會被排序。因此,要獲得按某個字段排序的前 K 個文檔,我們只需獲取前 K 個文檔,甚至不必檢查其餘的文檔,如此一來,排序查詢自然就非常快了。
某字段的文檔值 (左)和按該字段排序的索引中該字段的相同文檔值(右)。
索引排序是一個不錯的解決方案,但是您只能以單一方式進行索引排序。索引排序並不適用於具有下列特徵的排序查詢:採用不同的排序標準,例如降序與升序;採用不同的字段;或所用組合不同於索引排序定義中指定的組合。因此需要其他更靈活的方法來提高排序查詢的速度。
使用 distance_feature 查詢優化數值排序查詢
以前,我們通過爲每個文檔塊存儲最大影響因素(詞頻與文檔長度的組合)的方式,顯著提高了基於詞且按 _score 排序的查詢的速度。在查詢期間,我們可以通過查看文檔塊的最大影響因素來快速判斷其是否具有競爭力。如果某個塊不具有競爭力,我們可以跳過整個文檔塊,這將極大地加快查詢速度。
我們當時覺得有可能利用類似的方法來提高對數值或日期字段的排序查詢速度。結果證明,用 distance_feature 查詢代替排序是可行的。distance_feature 查詢是一種值得關注的查詢方式,它會返回最接近給定原點的前 K 個文檔。如果我們使用字段的最小值作爲原點,我們將得到按升序排序的前 K 個文檔。使用最大值作爲原點則會按降序爲我們提供前 K 個文檔。
對我們來說,distance_feature 查詢最具吸引力的特點在於,它可以有效跳過無競爭力的文檔塊。爲實現這一點,它需要依賴於 BKD 樹的屬性。BKD 樹在 Elasticsearch 中用於索引數值和日期字段。類似於將文本字段的倒排索引劃分爲文檔塊的方法,BKD 索引也被劃分爲單元格,並且每個單元格的最小值和最大值是已知的。因此,只需檢查單元格的最小值和最大值,distance_feature 查詢就可以有效跳過文檔無競爭力的單元格。要運行這種排序優化方法,數值或日期字段既要有索引又要有文檔值。
用 distance_feature 查詢代替對文檔值進行排序,使我們可以實現大幅提速(在某些數據集上高達 35 倍增益)。 我們已在 Elasticsearch 7.6 中引入了這種對日期和長字段的排序優化。
使用 search_after 優化排序查詢
我們爲所取得的這些提速成果感到欣慰,不過在使用 search_after 參數進行排序的問題上,我們還沒有找到一個令人滿意的解決方案。利用 search_after 進行排序十分普遍,因爲用戶通常不僅會關注搜索結果的首頁,也會對後續頁面感興趣。可以確定的是,相較於當前使用的在 Elasticsearch 中重寫排序查詢的方法,更好的解決方案是讓 Lucene 中的比較器和收集器進行這種排序優化,並跳過無競爭力的文檔。Lucene 中的比較器和收集器已支持 search_after,得益於此,我們苦於尋找解決方案的難題也迎刃而解了。我們將 distance_feature 查詢用於跳過無競爭力文檔塊的同一個 Elasticsearch 代碼添加到 Lucene 的數值比較器中。
在 Elasticsearch 7.16 中,我們已利用 search_after 參數引入了這種排序優化。 我們發現,在一些夜間性能基準測試中,查詢速度大幅提升(高達 10 倍),效果立竿見影:
使用 search_after 對具有多個段的索引進行排序優化(左),和強制合併到單個段的索引(右)。優化前 (2021-09-13),使用 search_after 的降序排序耗時 1400-1800 ms,優化後爲 200-300 ms。
優化跨多個段的排序
一個分片由多個段組成。Elasticsearch 在搜索時會按順序檢查段,因此確定具有前 K 個值的文檔,先從最有可能包含此類文檔的段開始處理會事半功倍。一旦收集了具有前 K 個值的文檔,我們就可以快速跳過僅含有排位靠後的值的其他段。
先從哪些段開始處理在很大程度上取決於用例。對於時間序列索引,最常見的請求是對時間戳字段的結果進行降序排序,因爲人們最關心的是查看最新事件。爲了優化時間序列索引的這種排序,我們首先對 @timestamp 字段進行降序排序,以便可以最先處理包含最新數據的段,理想情況下,我們可以按時間戳文檔在第一個段中收集最新的數據並跳過所有其他段。這一方法強有力地提高了時間戳字段的降序排序查詢速度。
當較小的段合併成更大的段時,我們不希望出現最新的段排在最後,而其中最新的文檔也位列末尾的情況。爲了更加均衡地合併段,我們引入了新舊段交叉的全新合併策略,即新舊段的文檔在全新的合併段中以混合順序排列。這也可以讓我們高效查找最新的文檔。
優化跨多個分片的排序
Elasticsearch 的強大之處在於它的分佈式搜索,如果忽略了分佈式的特點,任何優化都無法盡如人意。一些搜索可以跨越數百個分片(例如對時間序列索引的搜索),對此,從 “正確” 的分片集和不包含具有競爭力命中結果的快捷分片集開始操作將大有助益。該方法已得到全面落實。
從 Elasticsearch 7.6 開始,我們根據主排序字段的最大值 / 最小值對分片進行預排序,以便我們可以從這樣一個分片集開始分佈式搜索,即其中的分片最有可能包含排在最前面的值。從 Elasticsearch 7.7 開始,我們利用其他分片的結果實現快捷查詢階段,即一旦我們從第一個分片集收集了排在最前面的值,就完全可以跳過其他分片,因爲相較於前面分片中計算得到的最後面的排序值,這些分片中所有候選的值還要更加靠後。
在許多機器生成的時間序列索引中,文檔遵循索引生命週期策略,從性能優化的硬件開始,到成本優化的硬件結束,然後纔是刪除文檔。這種分片跳過機制通常意味着用戶可以發送廣泛的查詢,並享受由其性能優化的硬件所定義的查詢性能,因爲速度較慢和更經濟的硬件上的分片會被跳過(使可搜索快照的應用更加高效)。
對用戶的影響
作爲 Elasticsearch 的用戶,您如何才能利用這些排序優化呢?只有在無需跟蹤某個請求確切的總命中數且該請求不包含聚合時,這些排序優化纔會發揮作用。如果您需要知道確切的總命中數,我們就不能執行任何跳過操作,因爲我們需要計算所有匹配篩選條件的文檔。
track_total_hits 的默認值設置爲 10,000,也就是說,排序優化只有在收集了 10,000 個文檔時才能開始。如果將此值設置爲一個較小的數或 “false”,那麼 Elasticsearch 就可以更早地開始排序優化,換言之,您的請求響應速度也會更快。
最近 Kibana 也開始在 track_total_hits 被禁用的情況下發送請求,因此 Kibana 中的排序查詢也應該會更快。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Bd7RSMUgJFGS7sJToOGPeA