在 DynamoDB 中使用全域次要索引 - Amazon DynamoDB

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

在 DynamoDB 中使用全域次要索引

某些應用程式可能需要執行多種查詢,並使用各種不同的屬性做為查詢條件。若要支援這些需求,您可以建立一或多個全域次要索引,並在 Amazon DynamoDB 中對這些索引發出 Query 請求。

藍本:使用全域次要索引

為了示範,假設有一份名為 GameScores 的資料表,會追蹤手機遊戲應用程式的使用者與分數。GameScores 中的每個項目都是按分割區索引鍵 (UserId) 和排序索引鍵 (GameTitle) 識別。下圖顯示這些項目在資料表中的組織方式。(並未顯示所有屬性。)

GameScores 包含使用者 ID、標題、分數、日期和勝/敗清單的資料表。

現在假設您想要撰寫排行榜應用程式來顯示每個遊戲的最高分。已指定索引鍵屬性 (UserIdGameTitle) 的查詢會很有效率。但若應用程式僅能根據 GameTitleGameScores 擷取資料,就需要使用 Scan 操作。當愈來愈多項目新增至資料表時,掃描所有資料會變得很慢且效率不彰。這會讓回答問題變得很困難,例如下列問題:

  • Meteor Blasters 遊戲曾記錄到的最高分為何?

  • 哪位使用者在 Galaxy Invaders 中得到最高分?

  • 最高的勝負比為何?

若要加速非索引鍵屬性的查詢,您可以建立全域次要索引。全域次要索引包含基礎資料表中的一系列屬性,但這些屬性會依與該資料表不同的主索引鍵組織。索引鍵不需要有任何來自資料表的索引鍵屬性。甚至不需要有和資料表相同的索引鍵結構描述。

例如,您可以建立名為 GameTitleIndex 的全域次要索引,其中分割區索引鍵為 GameTitle,排序索引鍵為 TopScore。因為基礎資料表的主索引鍵屬性一律會投影到索引,所以也會顯示 UserId 屬性。下圖說明 GameTitleIndex 索引可能的樣子。

GameTitleIndex 包含標題、分數和使用者 ID 清單的資料表。

您現在可以查詢 GameTitleIndex 並輕鬆取得 Meteor Blasters 的分數。結果會依排序索引鍵值 TopScore 排序。如果您將 ScanIndexForward 參數設定為 false,結果會依遞減順序傳回,因此最高分優先傳回。

每個全域次要索引都必須有分割區索引鍵,並可以有一個選用的排序索引鍵。索引鍵結構描述可以和基礎資料表結構描述不同。您可以擁有一個具有簡易主索引鍵 (分割區索引鍵) 的資料表,並使用複合主索引鍵 (分割區索引鍵和排序索引鍵) 建立全域次要索引,反之亦然。索引鍵屬性可包含來自基礎資料表的任何最上層 StringNumberBinary 屬性。不允許其他純量類型、文件類型和集合類型。

您可以視需要將其他基礎資料表屬性投影到索引。當您查詢索引時,DynamoDB 可有效率地擷取這些投影屬性。但是,全域次要索引查詢無法自基礎資料表擷取屬性。例如,如果您依上圖所示查詢 GameTitleIndex,則除 TopScore 外,此查詢不能存取任何非索引鍵屬性 (雖然會自動投影索引鍵屬性 GameTitleUserId)。

在 DynamoDB 資料表中,每個索引鍵值必須是唯一的。不過,全域次要索引中的索引鍵值不需要是唯一的。為了示範,假設有一款名為 Comet Quest 的遊戲特別難,許多新的使用者試過後均無法突破零分的成績。以下為可代表此遊戲的一些資料。

UserId GameTitle TopScore
123 Comet Quest 0
201 Comet Quest 0
301 Comet Quest 0

當此資料新增至 GameScores 資料表時,DynamoDB 會將其傳播至 GameTitleIndex。如果我們接著使用 GameTitle Comet Quest 與 TopScore 0 來查詢索引,即會傳回下列資料。

包含遊戲名稱、最高分和使用者 ID 清單的資料表。

回應中只會顯示具有指定索引鍵值的項目。在此資料集合中,項目未依特定順序排列。

全域次要索引只會追蹤索引鍵屬性確實存在的資料項目。例如,假設您在 GameScores 表中新增另一個新項目,但只提供必要的主索引鍵屬性。

UserId GameTitle
400 Comet Quest

因為您未指定 TopScore 屬性,所以 DynamoDB 不會將此項目傳播至 GameTitleIndex。因此,如已針對 GameScores 查詢所有 Comet Quest 項目,可能會取得下列四個項目。

包含 4 個遊戲名稱、最高分和使用者 ID 清單的資料表。

針對 GameTitleIndex 的類似查詢仍只會傳回三個項目,而不是四個。這是因為具有不存在 TopScore 的項目不會傳播至索引。

包含 3 個遊戲名稱、最高分和使用者 ID 清單的資料表。

屬性投影

投影是指從資料表複製到次要索引的屬性集合。資料表的分割區索引鍵和排序索引鍵一律會投影到索引中;您可以投影其他屬性來支援應用程式的查詢需求。查詢索引時,Amazon DynamoDB 可以存取投影中的任何屬性,就好像這些屬性在它們自己的資料表中一樣。

在建立次要索引時,您需要指定要投影到索引中的屬性。DynamoDB 為此提供三種不同的選項:

  • KEYS_ONLY – 索引中的每個項目僅包含資料表分割區索引鍵和排序索引鍵值,以及索引鍵值。KEYS_ONLY 選項會產生最小的可行次要索引。

  • INCLUDE – 除了 中所述的屬性之外KEYS_ONLY,次要索引還將包含您指定的其他非金鑰屬性。

  • ALL – 次要索引包含來源資料表中的所有屬性。因為索引中的所有資料表資料都會重複,所以 ALL 投影會產生最大的可行次要索引。

在上圖中,GameTitleIndex 只有一個投影的屬性:UserId。因此,雖然應用程式可以在查詢中使用 GameTitleTopScore 有效率地判斷每個遊戲最高分得主的 UserId,卻無法有效率地判斷最高分得主的最高勝負比。若要做到這一點,應用程式必須在基礎資料表執行額外的查詢,以擷取每個最高分得主的勝負比。支援查詢此資料更有效率的方法,便是將這些屬性從基礎資料表投影到全域次要索引,如下圖所示。

將非金鑰屬性投影到 GSI以支援高效查詢的描述。

由於非索引鍵屬性 WinsLosses 會投影到索引,因此應用程式可以判斷任何遊戲或任何遊戲與使用者 ID 組合的勝負比。

在選擇要投影到全域次要索引的屬性時,您必須權衡佈建輸送量成本和儲存成本:

  • 若您只需要存取少量的屬性,並且希望盡可能地降低延遲,建議您考慮只將那些屬性投影到全域次要索引。索引愈小,存放成本就愈低,您的寫入成本也愈低。

  • 如果應用程式會頻繁存取某些非索引鍵屬性,您應該考慮將這些屬性投影到全域次要索引。全域次要索引的額外儲存成本會抵銷頻繁執行資料表掃描的成本。

  • 如果需要頻繁存取大多數的非索引鍵屬性,您可以將這些屬性 (或整個基礎資料表) 投影到全域次要索引。這能讓您掌握最大的靈活性。但儲存成本可能會增加,甚至翻倍。

  • 如果您的應用程式不需頻繁查詢資料表,但必須對資料表中的資料執行許多寫入或更新,請考慮投影 KEYS_ONLY。全域次要索引的大小將會最小,但仍能在需要的時候進行查詢活動。

從全域次要索引讀取資料

您可以使用 QueryScan 操作從全域次要索引擷取項目。GetItemBatchGetItem 操作不能用於全域次要索引。

查詢全域次要索引

您可以使用 Query 操作存取全域次要索引中一或多個項目。詢必須指定基礎資料表名稱以及您要使用的索引名稱、要在查詢結果中傳回的屬性,以及您要套用的任何查詢條件。DynamoDB 可以依遞增或遞減順序傳回結果。

假設下列資料從請求排行榜應用程式遊戲資料的 Query 傳回。

{ "TableName": "GameScores", "IndexName": "GameTitleIndex", "KeyConditionExpression": "GameTitle = :v_title", "ExpressionAttributeValues": { ":v_title": {"S": "Meteor Blasters"} }, "ProjectionExpression": "UserId, TopScore", "ScanIndexForward": false }

在此查詢中:

  • DynamoDB GameTitleIndex使用GameTitle分割區索引鍵來尋找 Meteor Blasters 的索引項目。所有具有此索引鍵的索引項目都會相鄰存放,以利快速擷取。

  • 在此遊戲中,DynamoDB 會使用 索引來存取此遊戲的所有使用者IDs和最高分數。

  • 結果會傳回並遞減排序,因為 ScanIndexForward 參數設定為 false。

掃描全域次要索引

您可以使用 Scan 操作從全域次要索引檢索所有資料。您必須在請求中提供基礎資料表名稱及索引名稱。使用 Scan,DynamoDB 會讀取索引中的所有資料,並將其傳回應用程式。您也可以請求只傳回一部分的資料,並捨棄其他剩餘的資料。若要執行此作業,請使用 FilterExpression 操作的 Scan 參數。如需詳細資訊,請參閱 掃描的篩選條件表達式

資料表與全域次要索引之間的資料同步

DynamoDB 會自動將每個全域次要索引與其基礎資料表同步。當應用程式寫入或刪除資料表中的項目時,該資料表的任何全域次要索引都會使用最終一致模型非同步更新。應用程式永遠不會直接寫入索引。然而,了解 DynamoDB 維持這些索引之方式的含義很重要。

全域次要索引會從基礎資料表繼承讀取/寫入容量模式。如需詳細資訊,請參閱在 DynamoDB 中切換容量模式時的考量

在建立全域次要索引時,您要指定一或多個索引鍵屬性及其資料類型。這表示每次您將項目寫入基礎資料表時,這些屬性的資料類型都必須符合索引鍵結構描述的資料類型。在 GameTitleIndex 案例中,索引的 GameTitle 分割區索引鍵是定義為 String 資料類型。索引的 TopScore 排序索引鍵類型為 Number。如果您嘗試在 GameScores 表中新增項目,並為 GameTitleTopScore 指定不同的資料類型,DynamoDB 會因資料類型不符而傳回 ValidationException

當您在資料表中放置或刪除項目時,該資料表的全域次要索引會以最終一致的方式更新。在正常的情況下,資料表的資料變更會在瞬間傳播至全域次要索引。不過,在某些不太可能發生的失敗情況下,可能發生較久的傳播延遲。因此,您的應用程式必須能夠預期及處理針對全域次要索引查詢,卻傳回非最新結果的情況。

如果將項目寫入資料表,您不需指定任何全域次要索引排序索引鍵的屬性。以 GameTitleIndex 為例,您不需指定 TopScore 屬性的值,也能將新的項目寫入 GameScores 表。在此案例中,DynamoDB 不會將任何資料寫入此特定項目的索引。

相較於索引較少的資料表,全域次要索引較多的資料表會有較高的寫入活動成本。如需詳細資訊,請參閱全域次要索引的佈建輸送量考量

具有全域次要索引的資料表類別

全域次要索引永遠會使用相同的資料表類別做為其基礎資料表。每當新增資料表的全域次要索引時,新索引都會使用與其基礎資料表相同的資料表類別。更新資料表的資料表類別時,也會更新所有相關聯的全域次要索引。

全域次要索引的佈建輸送量考量

當您對佈建模式資料表建立全域次要索引時,必須為該索引的預期工作負載指定讀取與寫入容量單位。全域次要索引的佈建輸送量設定與其基礎資料表的佈建輸送量設定無關。對全域次要索引執行 Query 操作會使用索引 (而不是基礎資料表) 的讀取容量單位。當您在資料表中放置、更新或刪除項目時,也會更新該資料表中的全域次要索引。這些索引更新會使用來自索引的寫入容量單位,而不是基礎資料表的單位。

例如,如果對全域次要索引執行 Query 操作,並超出其佈建讀取容量,您的請求就會經過調節。如果您對資料表執行大量寫入活動,但該資料表全域次要索引的寫入容量不足,即會調節該資料表的寫入活動。

重要

若要避免調節問題產生,全域次要索引的佈建寫入容量必須大於等於基礎資料表的寫入容量,因為新的更新會同時寫入基礎資料表及全域次要索引。

若要檢視全域次要索引的佈建輸送量設定,請使用 DescribeTable 操作。會傳回所有資料表全域次要索引的詳細資訊。

讀取容量單位

全域次要索引支援最終一致讀取,每個讀取均使用一半的讀取容量單位。這表示單一全域次要索引查詢每個讀取容量單位最多可擷取 2 × 4 KB = 8 KB。

在全域次要索引查詢方面,DynamoDB 會使用為資料表查詢計算佈建讀取活動相同的方式,計算佈建讀取活動。唯一的差別在於計算方式是以索引項目的大小為基礎,而非基礎資料表中項目的大小。讀取容量單位數為所有傳回項目中,所有投影屬性大小的總和。然後,結果四捨五入至下一個 4 KB 界限。如需 DynamoDB 如何計算佈建輸送用量的詳細資訊,請參閱 DynamoDB 佈建容量模式

Query 操作傳回的結果大小上限是 1 MB。這包含所有傳回項目之所有屬性名稱與值的大小。

例如,假設有一個全域次要索引,其每個項目均包含 2,000 個位元組的資料。現在假設您 Query 此索引,而此查詢的 KeyConditionExpression 會傳回 8 個項目。相符項目的總大小為 2,000 位元組 × 8 個項目 = 16,000 位元組。然後,此結果四捨五入至最近的 4 KB 界限。因為全域次要索引查詢為最終一致,所以總成本為 0.5 × (16 KB/4 KB) 或 2 個讀取容量單位。

寫入容量單位

當新增、更新或刪除資料表項目,並影響到全域次要索引時,全域次要索引會使用該操作的佈建寫入容量單位。寫入的總佈建輸送量成本包含寫入基礎資料表使用的寫入容量單位加上更新全域次要索引使用的寫入容量單位。如果資料表的寫入作業不需更新全域次要索引,就不會使用該索引的寫入容量。

為成功寫入資料表,資料表及其所有全域次要索引的佈建輸送量設定皆必須具有足以容納寫入的寫入容量。否則資料表的寫入會受到調節。

將項目寫入全域次要索引的成本取決於幾個因素:

  • 若您將項目寫入定義索引屬性的資料表,或是您將現有的項目更新為先前未定義的索引屬性,則將該項目寫入索引需要進行一次寫入操作。

  • 若資料表的更新會變更索引鍵屬性的值 (從 A 到 B),則需要兩次寫入:一次是從索引刪除先前的項目,第二次則是將新的項目寫入索引。 

  • 若項目存在於索引中,但寫入資料表致使索引屬性遭到刪除,則需要進行一次寫入,從索引刪除舊項目的投影。

  • 若項目在更新之前或之後並不存在於索引中,則該索引將不會有任何額外的寫入成本。

  • 如果資料表的更新只變更索引鍵結構描述中投影屬性的值,但並不變更任何索引鍵屬性的值,則需執行一個寫入將投影屬性的值更新至索引中。

所有這些因素都假設索引中每個項目的大小都小於或等於 1 KB 項目大小 (用於計算寫入容量單位)。較大的索引項目需要額外的寫入容量單位。您可以考慮您的查詢所需要傳回的屬性,並只將那些屬性投影到索引中,來將寫入成本降至最小。

全域次要索引的儲存考量

當應用程式將項目寫入資料表時,DynamoDB 會自動將正確的部分屬性複製到應顯示這些屬性的任何全域次要索引中。 AWS 您的帳戶會支付儲存基礎資料表中項目的費用,以及儲存該資料表上任何全域次要索引中屬性的費用。

索引項目使用的空間數為下列項目的總和:

  • 基礎資料表主索引鍵 (分割區索引鍵和排序索引鍵) 的大小 (位元組)

  • 索引鍵屬性的大小 (位元組)

  • 投影屬性 (若有的話) 的大小 (位元組)

  • 每個索引項目 100 位元組的額外負荷

若要估算全域次要索引的儲存需求,您可以估算索引中項目的平均大小,再乘以基礎資料表中具有全域次要索引鍵屬性的項目數。

如果資料表包含特定屬性未經定義的項目,但該屬性卻已定義為索引的分割區索引鍵或排序索引鍵時,DynamoDB 不會將該項目的任何資料寫入索引。