Elasticsearch 6.x索引預排序分析

本文翻譯自https://www.elastic.co/blog/index-sorting-elasticsearch-6-0,侵刪

Elasticsearch 從6.0版本開始,引入了一個索引預排序(index sorting)的功能。使用這個功能,用戶可以在文檔寫入的階段,按指定的字段規則對文檔進行排序。這是一個令人激動的新功能,它將極大的提高Elasticsearch在某些場景下的性能!

本文內容涉及如下幾個方面:

  • Lucene 索引預排序功能的實現
  • 幾個索引預排序功能提升查詢性能的例子
  • 在時序數據中開啓索引預排序的注意事項
  • 性能考量

索引預排序在 Lucene 中的實現

Lucene 離線排序工具 IndexSorter

初期,Lucene 曾引入了一個離線的排序工具——IndexSorter。IndexSorter 把需要排序的索引完全複製了一份,將新的複製索引中的文檔按用戶指定的順序重新排序。因爲排序後的索引是一個新的索引,每次源索引中有新的數據更新,不得不重新執行一遍這個工具。IndexSorter 工具是第一次在索引寫入階段而不是查詢階段對文檔進行排序的嘗試。

針對索引預排序,社區提出了一個新的概念“early termination”。假設你要遍歷出前N個文檔,並且文檔是按 date 字段排序的。如果索引存儲在磁盤上時已經是有序的了,那麼我們遍歷出前N個文檔就可以直接返回,而不需要遍歷所有的文檔。這就是我們所說的“early termination”。提早的返回查詢結果,可以明顯的縮短查詢響應時間,特別是含有排序的查詢。剛纔介紹的離線排序的方案不能滿足有大量文檔更新的場景,這也是爲什麼最終離線排序方案會被其他方案取代。爲了替換離線排序的方案,我們提出了一個新的解決方案,在文檔的merge階段進行排序。

Lucene 所做的改進

正常情況下,Lucene 按文檔的接收順序寫入,並且分配一個自增的文檔id。在segment中的第一個文檔的文檔ID爲0,依次遞增。在查詢階段,segment中的文檔是按文檔id的順序遍歷的。如果某個查詢需要遍歷符合條件文檔的 TOP N,Lucene 需要訪問所有符合條件的文檔,並建立最大(小)堆進行過濾。在文檔數量爲百萬級別的場景中,這樣的排序取前N的場景是非常耗時間的。

每當刷新(refresh)操作被觸發,Lucene 會爲索引創建一個新的段。新的段包含上一次刷新後的所有新加入的文檔。刷新操作之後,新加入的文檔才能被搜索。因爲刷新操作發生的頻率是恆定的,所以 segment 的數量會爆炸式的增長。segment 合併操作會在後臺觸發以限制 segment 的數量。merge 操作基於某種合適的策略被觸發,幾個小的 segment 會合併爲一個更大的 segment。segment 合併的時候默認還是以文檔id爲序的。爲了取代靜態的離線合併工具(如上面提到的 IndexSorter ),引入了一種新的segment合併策略,允許在 segment 合併的時候,按用戶指定的字段對文檔重新排序。這個新的設計方案,在正確的方向上前進了一大步,允許索引在寫入的過程中排序並且只用了 segment 的一些基本信息。如果一些 segment 已經被排序,另外一些新創建的 segment 還沒有被排序。所以在合併的階段,未排序的 segment 會首先進行排序,然後再與其它已經排序的segment進行合併。

這個新的 segment 合併策略已經出現在了 IndexWriterConfig 這個模塊配置中的最外層的位置,成爲了最重要的合併策略。

然而,一些 benchmark 測試顯示,在合併階段進行排序的性能會以指數遞減:

es1.png

https://home.apache.org/~mikemccand/lucenebench/sparseResults.html#index_throughput

造成索引寫入性能衰減的原因很簡單:重新排序 segment 中的文檔,將導致合併操作時間和jvm佔用大幅增加。

如上所述,重新排序多個 segment 的耗時很長,我們決定將排序提前到生成索引的階段。我們把排序的操作提前到新 segment 刷盤的階段,而不是等到 merge 階段才排序多個 segment :LUCENE-7579。顯然,如果所有 segment 已經是排好序的了,那麼 merge 階段只需要執行一次快速的 merge sort 排序。這個新的算法首次在 Lucene 6.5 被引入,將壓測的吞吐指標提升了65%左右。

索引預排序在 Lucene 中有那麼長的歷史,然後直到最近才被引入到 Elasticsearch 中。 感謝開源社區在這個功能上做的大量的優化和努力,我們終於在 Elasticsearch 6.x 開始解鎖了這個功能, 並且期待這個新功能的發佈能極大的優化你的使用!

索引預排序實踐

儘早返回查詢語句的結果

在日常應用中,返回按某個字段排序的 TOP N 是非常常見的。 大多數的情況下,除非對整個數據集遍歷並排序,否則 Elasticsearch 不能快速的獲得 TOP N 的值。儘管 Doc values 的列式存儲可以加快遍歷的速度,但是在數據集量級非常大的場景下,效果就不是特別的好了。

有了索引預排序的功能之後,我們現在能指定磁盤上存儲文檔的順序,允許 Elasticsearch 儘快的返回查詢結果。這裏舉一個例子,如果我們創建了一個電腦遊戲的排行榜,返回成績最好的前三個玩家。我們可以使用 Elasticsearch 來存儲玩家的分數,並且保證數據以分數的維度排序。

es2.jpg
// Get the top 3 player scores (based on the number of points)
GET scores/score/_search
{
  "size": 3,
  "sort": [
      { "points": "desc" }
  ]
}

使用Elasticsearch 6.x 版本中的索引預排序,我們能更高效的存儲上面場景中的數據:

es3.png

上面的查詢依舊需要返回所有符合條件的文檔個數,這會多做很多操作。我們可以讓 track_total_hits 這個參數的值爲 false 來去掉這個操作:

// Get the top 3 player scores (based on the number of points)
GET scores/score/_search
{
  "size": 3,
  "track_total_hits" : false,
  "sort": [
      { "points": "desc" }
  ]
}

現在,我們應用索引預排序構造了一個非常高效的玩家分數積分榜的查詢。

指定索引與排序的字段順序

繼續我們上面玩家積分榜的例子,我們需要在索引寫入的時候告訴 Elasticsearch 如果對文檔進行排序。我們可以在索引的 settings 裏面進行設置:

PUT scores
{
    "settings" : {
        "index" : {
            "sort.field" : "points", 
            "sort.order" : "desc" 
        }
    },
    "mappings": {
        "score": {
            "properties": {
                "points": {
                  "type": "long"
                },
                "playerid": {
                  "type": "keyword"
                },
                "game" : {
                  "type" : "keyword"
                }
            }
        }
    }
}

如上面的例子,文檔在寫入磁盤時會按照 points 字段的遞減序進行排序。

聚合相似結構的文檔存儲

對相似類型的文檔進行排序有很多好處。舉例來說,一個名字爲“scores”的索引,某些分數來自於遊戲“Joust”,這個有些有一些自己特殊的字段,如“top-speed”和“farthest-jump”。另外一個遊戲“Dragon's Lair”,含有字段“sword-fight-score”和“goblins-killed”:

// Score for the game "Joust"
{
  "game" : "joust",
  "playerid" : "1234",
  "top-speed" : 212,
  "farthest-jump" : 49
}
// Score for the game "Joust"
{
  "game" : "joust",
  "playerid" : "1234",
  "top-speed" : 212,
  "farthest-jump" : 49
}

將文檔按 game 字段排序可以使相似的文檔存在一個 segment 。這樣做的好處可以加速查詢和壓縮的比率。 將相似結構的文檔存儲在一起確實有助於提高壓縮的比例,並且 Lucene 可以更高效的存儲偏移量信息:

PUT scores
{
    "settings" : {
        "index" : {
            "sort.field" : "game", 
            "sort.order" : "desc" 
        }
    }
}

更高效的 AND 連接查詢

使用索引預排序可以提高 AND 連接查詢的效率。

還是上面遊戲的例子,當一個新玩家加入了遊戲後,他應該能夠和相同地區,相似等級的其他玩家配對,以便可以開始一局新的遊戲。這裏有一個簡單的查詢例子,可以幫助查找相似的玩家,然後讓他們開始一局新的遊戲:

GET players/player/_search
{
  "size": 3,
  "track_total_hits" : false,
  "query" : { 
    "bool" : {
      "filter" : [
        { "term" : { "region" : "eu" } },
        { "term" : { "game" : "dragons-lair" } },
        { "term" : { "skill-rating" : 9 } },
        { "term" : { "map" : "castle" } } 
      ]
    }
  }
}

讓我們來展示下Elasticsearch是如何獲取結果的。

es4.png

然後我們配置一下這個索引的排序策略,看能否提高查詢效率:

PUT players
{
    "settings" : {
        "index" : {
            "sort.field" : ["region", "game", "skill-rating", "map"], 
            "sort.order" : ["asc", "asc", "asc", "asc"] 
        }
    },
    "mappings": {
        "player": {
            "properties": {
                "playerid": {
                  "type": "keyword"
                },
                "region": {
                  "type": "keyword"
                },
                "skill-rating" : {
                  "type" : "integer"
                },
                "game" : {
                  "type" : "keyword"
                },
                "map" : {
                  "type" : "keyword"
                }
            }
        }
    }
}

現在我們可以看到,所有相似條件的文檔都被被存儲到了一起:

es5.png

通過使用索引預排序的功能,我們能快速的定位到相似字段條件的文檔,是我們的玩家配對查詢能更快的得到結果。

索引預排序不適用的場景

開啓索引預排序功能後,會比不開啓這個功能耗費更多的索引生成時間。在某些用戶適用場景下,開啓索引預排序會有大約40%-50%的性能下降。基於這個問題,我們需要考慮好我們的業務更關注查詢的性能還是寫入的性能,這點是非常重要的。如果更關注寫性能的業務,開啓索引預排序不是一個很好的選擇。

下圖是一個是否開啓索引預排序時寫入吞吐的一個對比圖。這個壓測結果完全基於你的用戶使用場景。比如,“geonames”的壓測顯示索引預排序對寫入性能的影響是比較低的(深藍色的線):

es6.png

https://elasticsearch-benchmarks.elastic.co/index.html#tracks/geonames/nightly/30d

另外一個場景,"NYC Taxis"的壓測結果顯示寫入性能有大幅度的下降:

es7.png

https://elasticsearch-benchmarks.elastic.co/index.html#tracks/nyc-taxis/nightly/30d

在系統設計層面,我們必須仔細的考慮業務使用場景的方方面面,對是否開啓索引預排序這個功能進行慎重的權衡。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章