在上一個單元中,我們定義了行動應用程式的存取模式。在此單元中,我們將設計 DynamoDB 資料表的主要金鑰並啟用核心存取模式。

完成單元的時間:40 分鐘


在為 DynamoDB 資料表設計主要金鑰時,請牢記以下最佳實務:

  • 從資料表中的不同實體開始。若您要在單一資料表中儲存多種不同類型的資料 (例如,員工、部門、客戶和訂單),請確保您的主要金鑰可以區分每個實體,並針對個別項目啟用核心動作。
  • 使用前綴來區分實體類型。使用前綴來區分實體類型可以防止衝突並有助於查詢。若您在相同資料表中同時有客戶和雇員,則客戶的主要金鑰可以是 CUSTOMER#<CUSTOMERID>,而雇員的主要金鑰可以是 EMPLOYEE#<EMPLOYEEID>
  • 首先關注單項操作,然後在可能的情況下新增多項操作。對於主要金鑰,務必使用單項 API 來滿足對單項的讀取和寫入選項:GetItemPutItemUpdateItemDeleteItem。若也可以使用查詢透過主要金鑰來滿足您的多項讀取模式,那就太好了。若不是,則可始終新增輔助索引來處理查詢使用案例。

考慮到這些最佳實務,我們來設計主要金鑰,並執行一些基本操作。


  • 步驟 1.設計主要金鑰

    我們來考慮一下前述介紹中建議的不同實體。在我們的行動應用程式中,我們具有以下實體:

    • 使用者
    • 相片
    • 回應
    • 朋友關係

    這些實體顯示三種不同類型的資料關係。

    首先,應用程式中的每個使用者都將具有單一使用者檔案,在資料表中由使用者實體表示。

    接著,使用者將在您的應用程式中顯示多張相片,並且一張相片將有多個回應。這些都是一對多關係。

    最後,朋友關係實體表示多對多關係。朋友關係實體表示一個使用者何時在您的應用程式中關注另一個使用者。這是多對多關係,因為一個使用者可以關注多個其他使用者,並且一個使用者可以具有多個關注者。

    通常,具有多對多映射表示您要滿足兩種查詢模式,而此應用程式也不例外。在朋友關係實體上,我們有一種需要尋找關注某個特定使用者的所有使用者的存取模式,以及一種需要尋找某個指定使用者關注的所有使用者的存取模式。

    因此,我們將使用具有雜湊範圍值的複合主要金鑰。複合主要金鑰將為我們提供雜湊金鑰上的查詢功能,可以滿足我們所需的一種查詢模式。在 DynamoDB API 規範中,分區金鑰稱為雜湊,排序金鑰稱為範圍,在本指南中,我們將互換使用 API 術語,在討論程式碼或 DynamoDB JSON Wire 格式時尤其如此。

    請注意,一對一實體 -- 使用者 -- 範圍值沒有自然屬性。由於它是一對一對應,因此存取模式將是基本的金鑰值查詢。由於您的資料表設計需要範圍屬性,因此可以為範圍金鑰提供填充值。

    考慮到這一點,讓我們對每種實體類型的雜湊和範圍值使用以下模式︰

    實體

    雜湊

    範圍

    使用者

    USER#<USERNAME>

    #METADATA#<USERNAME>

    相片

    USER#<USERNAME>

    PHOTO#<USERNAME>#<TIMESTAMP>

    回應

    REACTION#<USERNAME>#<TYPE>

    PHOTO#<USERNAME>#<TIMESTAMP>

    朋友關係

    USER#<USERNAME>

    #FRIEND#<FRIEND_USERNAME>

    我們在看看資料表。

    首先,對於使用者實體,雜湊值為 USER#<USERNAME>。請注意,您使用前綴來識別實體,並防止實體類型之間發生任何可能的衝突。

    對於使用者實體上的範圍值,我們使用的是 #METADATA# 的靜態前綴,後跟使用者名稱值。對於範圍值,具有一個已知值很重要,例如使用者名稱。這允許單項操作,例如 GetItemPutItemDeleteItem

    然而,若將此欄用作索引的雜湊金鑰,您還需要範圍值在不同的使用者實體中具有不同的值,以啟用 均勻分割。因此,請將使用者名附加至範圍金鑰。

    其次,相片實體是特定使用者實體的子實體。相片的主要存取方式是針對按日期排序的使用者擷取相片。當需要按特定屬性排序的內容時,您將需要範圍金鑰中包含該屬性以進行排序。對於相片實體,使用與使用者實體相同的雜湊金鑰,這讓您能夠在單一請求中同時擷取使用者檔案和使用者相片。對於範圍金鑰,使用 PHOTO#<USERNAME>#<TIMESTAMP> 以在資料表中唯一標識相片。

    第三,回應實體是特定相片實體的子實體。與相片實體存在一對多關係,因此將使用與相片實體類似的推理。在下一個單元中,您將看到如何使用次要索引在單一查詢中擷取相片及其所有回應。現在,請注意,回應實體的範圍金鑰與相片實體的範圍金鑰具有相同的模式。對於雜湊金鑰,我們使用建立回應的使用者名稱,以及所套用的類型。附加回應類型允許使用者將多種回應類型新增至單一相片中。

    最後,朋友關係實體使用與使用者實體相同的雜湊金鑰。如此您便可以在單一查詢中,擷取使用者的中繼資料以及該使用者的所有關注者。朋友關係實體的範圍金鑰是 #FRIEND#<FRIEND_USERNAME>。在下面的步驟 4 中,您將了解為什麼在朋友關係實體的範圍金鑰之前加上 “#”。

    在下一步中,我們使用此主要金鑰設計來建立資料表。

  • 步驟 2:建立資料表

    現在,我們已經設計了主要金鑰,接下來建立資料表。

    您在單元 1 步驟 3 中下載的程式碼在名稱為 create_table.pyscripts/ 目錄中包含一個 Python 指令碼。Python 指令碼的內容如下︰

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.create_table(
            TableName='quick-photos',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            KeySchema=[
                {
                    "AttributeName": "PK",
                    "KeyType": "HASH"
                },
                {
                    "AttributeName": "SK",
                    "KeyType": "RANGE"
                }
            ],
            ProvisionedThroughput={
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            }
        )
        print("Table created successfully.")
    except Exception as e:
        print("Could not create table. Error:")
        print(e)

    上述指令碼透過 Boto 3 (適用於 Python 的 AWS 開發套件) 來使用 CreateTable 操作。該操作聲明兩個屬性定義,它們是要在主要金鑰中使用的類型化屬性。雖然 DynamoDB 是無結構描述,但是您必須聲明用於主要金鑰的名稱和屬性類型。屬性必須包含在寫入資料表的每個項目中,因此在建立資料表時必須指定屬性。

    由於您將不同的實體儲存在單一資料表中,因此主要金鑰不能使用 UserId 之類的屬性名稱。該屬性根據儲存的實體類型而有所差異。例如,使用者的主要金鑰可能是其使用者名稱,而回應的主要金鑰可能是其類型。因此,我們對屬性使用通用名稱 -- PK (用於分割金鑰) 和 SK (用於排序金鑰)。

    在金鑰結構描述中設定屬性後,我們為資料表指定佈建輸送量。DynamoDB 具有兩種容量模式:佈建和隨需。在佈建容量模式下,您可以準確指定所需的讀寫輸送量。無論是否使用,都需要為此容量付費。

    在 DynamoDB 隨需容量模式下,您可以按請求付費。與完全使用佈建輸送量相比,按請求付費略高,但是您不必花時間進行容量規劃或擔心受到限制。隨需模式非常適合尖峰或不可預測的工作負載。我們在此實驗室中使用佈建容量模式,因為它適合 DynamoDB 免費方案。

    若要建立資料表,請使用以下命令執行 Python 指令碼。

    python scripts/create_table.py

    該指令碼應傳回以下訊息:「資料表建立成功。」

    在下一步中,我們將一些範例資料大量載入至資料表中。 

  • 步驟 3:將資料大量載入至資料表中

    在此步驟中,我們將一些資料大量載入至上一步中建立的 DynamoDB 資料表中。這意味著在後續步驟中,我們將會使用範例資料。

    scripts/ 目錄中,有一個名稱為 items.json 的檔案。該檔案包含 967 個為我們的專案隨機產生的範例項目。這些項目包括使用者相片朋友關係回應實體。若要查看某些範例資料,您可以開啟檔案。

    scripts/ 目錄中還有一個名稱為 bulk_load_table.py 的檔案,其讀取 items.json 中的項目,並將它們大量寫入 DynamoDB 資料表。該檔案的內容如下︰

    import json
    
    import boto3
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('quick-photos')
    
    items = []
    
    with open('scripts/items.json', 'r') as f:
        for row in f:
            items.append(json.loads(row))
    
    with table.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)

    在該指令碼中,我們沒有使用 Boto 3 中的低級別使用者端,而是使用了更高級別的 Resource objectResource 物件提供更簡單的界面來使用 AWS API 。Resource 物件在這種情況下很有用,因為它可以大量處理我們的請求。BatchWriteItem API 操作在單一請求中最多接受 25 個項目。 資源物件將為我們進行該大量處理,而不是讓我們將資料分為 25 個或更少項目的請求

    執行 bulk_load_table.py 指令碼,並透過在終端機中執行以下命令來向資料表載入資料。

    python scripts/bulk_load_table.py

    您可以透過執行掃描操作並傳回計數,來確保將所有資料已載入。

    執行以下命令以使用 AWS CLI 獲取計數:

    aws dynamodb scan \
     --table-name quick-photos \
     --select COUNT

    這應顯示以下結果。

    {
        "Count": 967, 
        "ScannedCount": 967, 
        "ConsumedCapacity": null
    }
    

    您應看到計數為 967,表明所有項目均已成功載入。

    在下一步中,我們將展示如何在單一請求中擷取多種實體類型,這可減少您在應用程式中的網路請求總數,並提高應用程式效能。

  • 步驟 4:在單一請求中擷取多種實體類型

    正如我們在上一單元中所述,您應針對 DynamoDB 資料表所接收的請求數量進行最佳化。我們還提到 DynamoDB 沒有關聯式資料庫所具備的聯接。而是,您可以設計資料表,以允許您的請求中具有類似聯接的行為。

    在此步驟中,我們將了解如何在單一請求中擷取多種實體類型。在我們的應用程式中,我們可能想要擷取有關使用者的資訊。其中包括使用者實體上使用者檔案中的所有資訊,以及使用者上傳的所有相片。

    該請求跨越兩種實體類型 -- 使用者實體和相片實體。但是,這並不意味著我們需要提出多個請求。

    在您下載的程式碼中,名稱為 fetch_user_and_photos.py 的檔案位於 application/ 目錄。該指令碼顯示了如何建構程式碼,以同時擷取使用者在單一請求中上傳的使用者實體和相片實體。

    以下程式碼包含 fetch_user_and_photos.py 指令碼

    import boto3
    
    from entities import User, Photo
    
    dynamodb = boto3.client('dynamodb')
    
    USER = "jacksonjason"
    
    
    def fetch_user_and_photos(username):
        resp = dynamodb.query(
            TableName='quick-photos',
            KeyConditionExpression="PK = :pk AND SK BETWEEN :metadata AND :photos",
            ExpressionAttributeValues={
                ":pk": { "S": "USER#{}".format(username) },
                ":metadata": { "S": "#METADATA#{}".format(username) },
                ":photos": { "S": "PHOTO$" },
            },
            ScanIndexForward=True
        )
    
        user = User(resp['Items'][0])
        user.photos = [Photo(item) for item in resp['Items'][1:]]
    
        return user
    
    
    user = fetch_user_and_photos(USER)
    
    print(user)
    for photo in user.photos:
        print(photo)

    首先,我們匯入 Boto 3 庫和一些簡單的類,來表示應用程式程式碼中的物件。若您有興趣,您可以在 application/entities.py 檔案中查看這些實體的定義。

    真正的工作發生在單元中定義的 fetch_user_and_photos 函數中。這類似於您在應用程式中定義的函數,用於此資料的所有端點。

    在此函數中,您首先向 DynamoDB 發出查詢請求。該查詢指定 USER#<Username>雜湊金鑰,以將傳回的項目隔離至特定使用者。

    然後,查詢指定 #METADATA#<Username>PHOTO$ 之間的範圍金鑰條件表達式。該查詢將傳回使用者實體,因為其排序金鑰為 #METADATA#<Username>,以及該使用者的所有相片實體,其排序金鑰以 PHOTO# 開頭。字串類型的排序金鑰按 ASCII 字元程式碼排序。美元符號 ($) 在 ASCII 中的英鎊符號 (#) 之後,因此這確保我們將獲取所有相片實體。

    收到回應後,我們則會將項目組合至應用程式已知的物件中。我們知道傳回的第一個項目將是我們的使用者實體,因此我們從該項目建立了一個使用者物件。對於其餘項目,我們為每個項目創建一個相片物件,然後將使用者陣列附加至使用者物件。

    指令碼的末尾顯示該函數的用法,並列印出結果物件。您可以在終端機中使用以下命令執行此指令碼。

    python application/fetch_user_and_photos.py

    它應將使用者物件和所有相片物件列印至主控台︰

    User<jacksonjason -- John Perry>
    Photo<jacksonjason -- 2018-05-30T15:42:38>
    Photo<jacksonjason -- 2018-06-09T13:49:13>
    Photo<jacksonjason -- 2018-06-26T03:59:33>
    Photo<jacksonjason -- 2018-07-14T10:21:01>
    Photo<jacksonjason -- 2018-10-06T22:29:39>
    Photo<jacksonjason -- 2018-11-13T08:23:00>
    Photo<jacksonjason -- 2018-11-18T15:37:05>
    Photo<jacksonjason -- 2018-11-26T22:27:44>
    Photo<jacksonjason -- 2019-01-02T05:09:04>
    Photo<jacksonjason -- 2019-01-23T12:43:33>
    Photo<jacksonjason -- 2019-03-03T02:00:01>
    Photo<jacksonjason -- 2019-03-03T18:20:10>
    Photo<jacksonjason -- 2019-03-11T15:18:22>
    Photo<jacksonjason -- 2019-03-30T02:28:42>
    Photo<jacksonjason -- 2019-04-14T21:52:36>

    此指令碼顯示如何在單一 DynamoDB 請求中對資料表建模,並編寫查詢以擷取多種實體類型。在關聯式資料庫中,您可以在單一請求中使用聯接,以從不同資料表中擷取多種實體類型。使用 DynamoDB,您可以專門為資料建模,以便將應當一起存取的實體在單一資料表中彼此相鄰。這種方法取代了在典型的關聯式資料庫中進行聯接的需求,並在您擴展應用程式時保持了高效能。


    在此單元中,我們設計了主要金鑰並建立了資料表。然後,我們將資料大量載入至資料表中,並了解了如何在單一請求中查詢多種實體類型。

    使用目前的主要金鑰設計,我們能夠滿足以下存取模式:

    • 建立使用者檔案 (寫入)
    • 更新使用者檔案 (寫入)
    • 取得使用者檔案 (讀取)
    • 上傳相片 (寫入)
    • 檢視使用者的相片 (讀取)
    • 檢視使用者的朋友 (讀取)

    在下一個單元中,我們將新增次要索引並了解反向索引技術。次要索引讓您能夠支援 DynamoDB 資料表上的其他存取模式。