想象一下,我有一個 Reddit 型別的應用程式,我希望允許用戶查看最后一天、一周或一個月的帖子,按分數(降序)排序。
所以假設我有這個索引:
db.collection("posts").createIndex({
subreddit: 1, // string
creationTime: -1, // integer (millisecond timestamp)
score: -1, // decimal
});
我想要運行的查詢是這樣的:
db.collection("posts").find({
subreddit: "foo",
creationTime: { $gt: Date.now()-1000*60*60*24 }, // posts from last 24 hours
}).sort({ score: -1 });
這是我的問題:由于creationTime有許多可能的值,我想知道如果我改為創建一個類似的屬性,我是否會獲得明顯更好的性能(假設每個 subreddit 有數百萬個帖子)creationHour,這會將值的數量減少一個因子3600,這意味著每個時間增量會有很多帖子。為了我的目的,我不需要比每小時更多的時間解析度。
我不太了解 MongoDB 的索引是如何作業的,但我只是有一種模糊的直覺,即大量可能的值creationTime可能會使這種查詢顯著變慢。另一方面,我認為這是一種相當常見的操作,所以我希望這種查詢已經過優化,可以有效地運行。希望了解MongoDB索引的專業人士可以幫助我理解這一點。
uj5u.com熱心網友回復:
我相信對您的直接問題的簡短回答是,使用整數(表示時間戳)與按小時分桶對當前查詢和索引不應該有任何明顯的性能差異。
為什么?
您的問題實際上與 MongoDB 沒有任何關系,除了他們像大多數資料庫供應商一樣,使用 B 樹資料結構作為標準索引。所以問題實際上是關于如何遍歷 B 樹。
B 樹的一個特點是它們是一種有序的資料結構。因此,在給出的示例索引中,專案將按subreddit第一個(升序)排序,然后是creationTime下一個(降序),最后是score(降序)。當資料庫認識到它可以使用這個索引來執行查詢時,它會嘗試盡可能減少將要掃描的索引部分。我們可以看到,當我們explain使用此索引查看查詢的輸出時,報告的索引范圍是:
indexBounds: {
subreddit: [ '["foo", "foo"]' ],
creationTime: [ '[inf.0, 1666897635152.0)' ],
score: [ '[MaxKey, MinKey]' ]
}
執行此查詢時,對于索引資料和索引邊界,從這里creationTime到做什么會發生什么變化?creationHour對于前者,像1666897635152(representing 2022-10-27 19:07:15.152Z) 這樣的值會被截斷為像1666897200000(representing 2022-10-27 19:07:00.000Z) 這樣的值。由于壓縮,這可能會節省一點空間,但不會減少資料庫中的條目數。
對于后者,從邏輯上講,查詢仍然希望提取“相同”的結果。因此,查詢將針對時間發出完全相同的謂詞(根據小時數的截斷方式,包括或排除一些結果),或者首先對查詢本身的值進行四舍五入(類似地添加或洗掉條目在那個邊界時間取決于截斷)。在任何一種情況下,建議creationHour欄位的索引系結實際上與當前欄位的索引系結相同creationTime。也許是這樣的:
indexBounds: {
subreddit: [ '["foo", "foo"]' ],
creationTime: [ '[inf.0, 1666897200000.0)' ],
score: [ '[MaxKey, MinKey]' ]
}
您特別提到您提議的更改“會將值的數量減少 3600 倍,這意味著每個時間增量會有很多帖子。 ”確實,您正在減少資料庫和索引中捕獲的不同值的數量(因此可能節省空間)。但總體而言,資料庫正在處理的匹配檔案的數量以及處理它們的方式非常相似。因此,我希望性能也相似。
索引鍵排序
您可能會在上面的解釋片段中注意到一些有趣的東西,特別是索引范圍score是:
score: [ '[MaxKey, MinKey]' ]
為什么會這樣?這是什么意思?
您在查詢中使用的唯一位置score是排序。因此,我們可以對索引掃描的那部分應用沒有界限。將排序鍵放在索引中的那個位置的(唯一)好處是它允許資料庫在讀取索引之后和檢索完整檔案之前對結果進行排序。我們可以在解釋的結構中看到:
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'SORT',
sortPattern: { score: -1 },
...
inputStage: {
stage: 'IXSCAN',
使用這種查詢和索引結構,資料庫必須在識別所有結果后手動對結果進行排序。或者,資料庫可以 使用索引對查詢結果進行排序。但是,這樣做需要相對于查詢謂詞對索引鍵進行特定排序。另一種索引結構是提升score: -1到索引的第二個位置。這樣做允許資料庫以請求的排序順序遍歷索引,從而不必手動對結果進行排序:
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { subreddit: 1, score: -1, creationTime: -1 },
這種方法通常需要掃描更多的索引鍵。這是因為它現在是掃描期間不受限制的第二個索引鍵:
indexBounds: {
subreddit: [ '["foo", "foo"]' ],
score: [ '[MaxKey, MinKey]' ],
creationTime: [ '[inf.0, 1666899575532.0)' ]
}
隨著您的系統的增長,我希望您擁有的當前索引結構將優于此替代方案,盡管它必須手動執行排序。識別匹配結果的子集,尤其是最近 24 小時內的結果,可能比按預排序順序掃描大部分索引要快。
更多關于該主題的閱讀是在這里。
日期型別
creationTime作為一個小觀察,將 存盤為適當的 Date 型別可能是值得的。這可能會使將來的一些查詢更加方便。例如,聚合框架中有多種日期運算式運算子$dateTrunc,包括可用于按小時截斷日期的運算子。
概括
除了可能將creationTime整數從整數更改為適當的日期型別之外,您當前的方法似乎是合理的。從性能的角度來看,可能沒有令人信服的理由來調整creationTime場的解析度。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/523333.html
