Im vorherigen Modul haben Sie den Zugriff auf Ihre Kernentitäten in DynamoDB eingerichtet. Die Primärschlüsselstruktur hat eine Reihe unserer Hauptzugriffsmuster gelöst. Dies umfasst alle Muster zum Lesen oder Schreiben einer einzelnen Entität sowie Muster zum Abrufen mehrerer verwandter Entitäten, z. B. aller Fotos, die einem bestimmten Benutzer gehören.

In diesem Modul erfahren Sie, wie Sie einen invertierten Index verwenden, ein gängiges Entwurfsmuster für DynamoDB.

Sekundärindizes sind wichtige Datenmodellierungswerkzeuge in DynamoDB. Sie ermöglichen es Ihnen, Ihre Daten umzuformen, um alternative Abfragemuster zu ermöglichen.

Bei einem invertierten Index handelt es sich um ein gängiges Entwurfsmuster für Sekundäre Indizes bei DynamoDB. Bei einem invertierten Index erstellen Sie einen sekundären Index, der die Umkehrung des Primärschlüssels für Ihre Tabelle darstellt. Der HASH-Schlüssel Ihrer Tabelle wird zum RANGE-Schlüssel in Ihrem Index, und der RANGE-Schlüssel Ihrer Tabelle wird zum Primärschlüssel für Ihren Index.

Ein invertierter Index ist in zwei Szenarien hilfreich. Erstens ist ein invertierter Index dafür nützlich, die „andere“ Seite einer Viele-zu-Viele-Beziehung abzufragen. Dies ist bei Ihrer Freundschafts-Entität der Fall. Mit Ihrer Primärschlüsselstruktur können Sie alle Follower für einen bestimmten Benutzer mit einer Abfrage der Tabelle nach dem Primärschlüssel abfragen. Wenn Sie einen invertierten Index hinzufügen, können Sie alle Benutzer finden, denen ein bestimmter Benutzer folgt (die "gefolgten Benutzer"), indem Sie den invertierten Index abfragen.

Ein invertierter Index ist auch dafür nützlich, eine Eins-zu-Viele-Beziehung für eine Entität abzufragen, die selbst Gegenstand einer Eins-zu-Viele-Beziehung ist. Sie können dies bei der Reaktions-Entität in Ihrer Tabelle sehen. Es kann mehrere Reaktionen auf ein einzelnes Foto geben, und jede Reaktion enthält das Foto, auf das die Reaktion angewendet wird, den Benutzernamen des reagierenden Benutzers sowie den Reaktionstyp. Da ein Benutzer jedoch viele Fotos haben kann, befindet sich die Hauptkennung für ein Foto in dessen RANGE-Schlüssel -- PHOTO#<USERNAME>#<TIMESTAMP>. Aus diesem Grund können Sie Ihren Primärschlüssel nicht verwenden, um Reaktionen mit Fotos zu verknüpfen.

Um das Zugriffsmuster „Foto und Reaktionen anzeigen“ zu erfüllen, platziert das Datenmodell die Fotokennung für eine Reaktionsentität im RANGE-Schlüssel. Wenn Sie jetzt den invertierten Index abfragen, können Sie die Foto-ID verwenden, um das Foto und alle seine Reaktionen in einer einzigen Anforderung auszuwählen. Dies wird in Schritt 2 unten gezeigt.

Veranschlagte Zeit für das Modul: 40 Minuten


  • Schritt 1: Erstellen Sie einen spärlichen Sekundärindex

    Um einen Sekundärindex anzulegen, geben Sie den Primärschlüssel des Index an, genau wie beim vorherigen Anlegen einer Tabelle. Beachten Sie, dass der Primärschlüssel für einen globalen Sekundärindex nicht für jedes Element eindeutig sein muss. DynamoDB kopiert dann Elemente in den Index, basierend auf den angegebenen Attributen, und Sie können sie genau wie die Tabelle abfragen.

    Ein invertierter Index ist ein gängiges Muster in DynamoDB, bei dem Sie einen sekundären Index erstellen, der die Umkehrung des Primärschlüssels Ihrer Tabelle darstellt. Der HASH-Schlüssel für Ihre Tabelle wird als RANGE-Schlüssel in Ihrem sekundären Index angegeben, und der RANGE-Schlüssel für Ihre Tabelle wird als HASH-Schlüssel in Ihrem sekundären Index angegeben.

    Das Anlegen eines Sekundärindexes ist dem Anlegen einer Tabelle ähnlich. In dem Code, den Sie heruntergeladen haben, befindet sich eine Datei namens add_inverted_index.py im Verzeichnis scripts/. Der Inhalt dieser Datei lautet wie folgt:

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.update_table(
            TableName='quick-photos',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            GlobalSecondaryIndexUpdates=[
                {
                    "Create": {
                        "IndexName": "InvertedIndex",
                        "KeySchema": [
                            {
                                "AttributeName": "SK",
                                "KeyType": "HASH"
                            },
                            {
                                "AttributeName": "PK",
                                "KeyType": "RANGE"
                            }
                        ],
                        "Projection": {
                            "ProjectionType": "ALL"
                        },
                        "ProvisionedThroughput": {
                            "ReadCapacityUnits": 5,
                            "WriteCapacityUnits": 5
                        }
                    }
                }
            ],
        )
        print("Table updated successfully.")
    except Exception as e:
        print("Could not update table. Error:")
        print(e)
    

    Wann immer Attribute in einem Primärschlüssel für eine Tabelle oder einen Sekundärindex verwendet werden, müssen sie in AttributeDefinitions definiert werden. Dann erstellen wir einen neuen Sekundärindex in der Eigenschaft GlobalSecondaryIndexUpdates. Für diesen Sekundärindex geben wir den Indexnamen, das Schema des Primärschlüssels, den bereitgestellten Durchsatz und die Attribute an, die wir projizieren wollen.

    Beachten Sie, dass ein invertierter Index der Name eines Entwurfsmusters anstelle einer offiziellen Eigenschaft in DynamoDB ist. Das Erstellen eines invertierten Index funktioniert genau wie das Erstellen anderer sekundärer Indizes.

    Erstellen Sie Ihren invertierten Index, indem Sie den unten aufgeführten Befehl ausführen.

    python scripts/add_inverted_index.py

    In der Konsole sollten Sie folgende Meldung sehen: "Tabelle erfolgreich aktualisiert".

    Im nächsten Schritt zeigen wir, wie unser invertierter Index verwendet werden kann, um ein Foto zu finden.

  • Schritt 2: Abfrage des invertierten Index, um Fotoreaktionen zu finden

    Nachdem wir nun den Sekundärindex konfiguriert haben, wollen wir ihn nutzen, um einige der Zugriffsmuster zu befriedigen.

    Um einen Sekundärindex zu verwenden, stehen Ihnen zwei API-Aufrufe zur Verfügung: Abfrage und Scan. Bei Abfrage müssen Sie den HASH-Schlüssel angeben, und es liefert ein gezieltes Ergebnis. Bei Scan geben Sie keinen HASH-Schlüssel an, und der Vorgang läuft über Ihre gesamte Tabelle hinweg. Von Scans in DynamoDB wird außer unter bestimmten Umständen abgeraten, da sie auf jedes Element in Ihrer Datenbank zugreifen. Wenn Sie eine beträchtliche Menge an Daten in Ihrer Tabelle haben, kann das Scannen sehr lange dauern.

    Wir können die Abfrage-API für unseren sekundären Index verwenden, um alle Reaktionen auf ein bestimmtes Foto zu finden. Wie Sie im vorherigen Modul gesehen haben, können Sie mit dieser Abfrage zwei Arten von Entitäten mit einem einzigen Befehl abrufen. In dieser Abfrage können Sie sowohl ein Foto als auch seine Reaktionen abrufen.

    In dem Code, den Sie heruntergeladen haben, gibt es im Verzeichnis application/ eine Datei namens fetch_user_and_photos.py. Der Inhalt dieses Skripts ist unten dargestellt.

    import boto3
    
    from entities import Photo, Reaction
    
    dynamodb = boto3.client('dynamodb')
    
    USER = "david25"
    TIMESTAMP = '2019-03-02T09:11:30'
    
    
    def fetch_photo_and_reactions(username, timestamp):
        try:
            resp = dynamodb.query(
                TableName='quick-photos',
                IndexName='InvertedIndex',
                KeyConditionExpression="SK = :sk AND PK BETWEEN :reactions AND :user",
                ExpressionAttributeValues={
                    ":sk": { "S": "PHOTO#{}#{}".format(username, timestamp) },
                    ":user": { "S": "USER$" },
                    ":reactions": { "S": "REACTION#" },
                },
                ScanIndexForward=True
            )
        except Exception as e:
            print("Index is still backfilling. Please try again in a moment.")
            return False
    
        items = resp['Items']
        items.reverse()
    
        photo = Photo(items[0])
        photo.reactions = [Reaction(item) for item in items[1:]]
    
        return photo
    
    
    photo = fetch_photo_and_reactions(USER, TIMESTAMP)
    
    if photo:
        print(photo)
        for reaction in photo.reactions:
            print(reaction)
    

    Die Funktion fetch_photo_and_reactions ähnelt einer Funktion, die Sie in Ihrer Anwendung haben würden. Die Funktion akzeptiert einen Benutzernamen und einen Zeitstempel und fragt den InvertedIndex ab, um das Foto und die Reaktionen für das Foto zu finden. Anschließend werden die zurückgegebenen Elemente in einer Foto-Entität und mehreren Reaktions-Entitäten zusammengesetzt, die in Ihrer Anwendung verwendet werden können.

    python application/fetch_photo_and_reactions.py

    Sie sollten die Ausgabe eines Fotos und der fünf möglichen Reaktionen sehen.

    Photo<david25 -- 2019-03-02T09:11:30>
    Reaction<ylee -- PHOTO#david25#2019-03-02T09:11:30 -- smiley>
    Reaction<kennedyheather -- PHOTO#david25#2019-03-02T09:11:30 -- smiley>
    Reaction<jenniferharris -- PHOTO#david25#2019-03-02T09:11:30 -- +1>
    Reaction<geoffrey32 -- PHOTO#david25#2019-03-02T09:11:30 -- +1>
    Reaction<chasevang -- PHOTO#david25#2019-03-02T09:11:30 -- +1>

    Beachten Sie, dass das Auffüllen des sekundären Index einen Moment dauern wird. Möglicherweise wird eine Fehlermeldung angezeigt, die darauf hinweist, dass ein Verfüllen durchgeführt wird. Bitte versuchen Sie es in ein paar Minuten erneut.

    Im nächsten Schritt erfahren Sie, wie Sie mithilfe des invertierten Index alle Benutzer abrufen, denen ein bestimmter Benutzer folgt.

  • Schritt 3: Gefolgte Benutzer finden

    Im vorherigen Schritt haben Sie gesehen, wie Sie mithilfe eines invertierten Index eine Eins-zu-Viele-Beziehung für eine Entität abrufen, die selbst Gegenstand einer Eins-zu-Viele-Beziehung war. In diesem Schritt verwenden Sie den invertierten Index, um die „andere“ Seite einer Viele-zu-Viele-Beziehung abzurufen.

    Mit dem Primärschlüssel in der Tabelle können Sie alle Follower eines bestimmten Benutzers finden, jedoch nicht alle Benutzer, denen eine bestimmte Person folgt. Beim invertierten Index ist es umgekehrt: Sie können alle Benutzer finden, denen ein bestimmter Benutzer folgt.

    In dem Code, den Sie heruntergeladen haben, gibt es im Verzeichnis application/ eine Datei namens fetch_user_and_photos.py. Der Inhalt dieses Skripts folgt.

    import boto3
    
    from entities import Friendship
    
    dynamodb = boto3.client('dynamodb')
    
    USERNAME = "haroldwatkins"
    
    
    def find_following_for_user(username):
        resp = dynamodb.query(
            TableName='quick-photos',
            IndexName='InvertedIndex',
            KeyConditionExpression="SK = :sk",
            ExpressionAttributeValues={
                ":sk": { "S": "#FRIEND#{}".format(username) }
            },
            ScanIndexForward=True
        )
    
        return [Friendship(item) for item in resp['Items']]
    
    
    
    follows = find_following_for_user(USERNAME)
    
    print("Users followed by {}:".format(USERNAME))
    for follow in follows:
        print(follow)
    

    Die Funktion find_following_for_user ähnelt einer Funktion, die Sie in Ihrer Anwendung haben würden. Die Funktion akzeptiert einen Benutzernamen, für den Sie die gefolgten Benutzer suchen möchten. Die Funktion fragt dann den invertierten Index ab, um alle Freundschafts-Entitäten zu finden, bei denen der folgende Benutzer der angegebene Benutzername ist.

    Führen Sie dieses Skript aus, indem Sie den folgenden Befehl in Ihrem Terminal ausführen.

    python application/find_following_for_user.py

    Ihre Konsole sollte nun eine Liste der Benutzer gefolgt vom angegebenen Benutzernamen ausgeben:

    Users followed by haroldwatkins:
    Friendship<chasevang -- haroldwatkins>
    Friendship<david25 -- haroldwatkins>
    Friendship<frankhall -- haroldwatkins>
    Friendship<geoffrey32 -- haroldwatkins>
    Friendship<jacksonjason -- haroldwatkins>
    Friendship<natasha87 -- haroldwatkins>
    Friendship<nmitchell -- haroldwatkins>
    Friendship<ppierce -- haroldwatkins>
    Friendship<tmartinez -- haroldwatkins>
    Friendship<vpadilla -- haroldwatkins>

    Beachten Sie, dass dies zwar alle Freundschafts-Entitäten für einen Benutzer zurückgibt, die Informationen innerhalb einer Freundschafts-Entität jedoch ziemlich spärlich sind. Es enthält nur den Benutzernamen des gefolgten Benutzers, nicht jedoch das vollständige Benutzerprofil. Im nächsten Modul werden wir erläutern, wie die partielle Normalisierung dazu verwendet werden kann, Situationen wie diese effizient zu bewältigen.

  • Fazit

    In diesem Modul haben wir unserer Tabelle einen sekundären Index unter Verwendung des invertierten Indexmusters hinzugefügt. Damit wurden zwei zusätzliche Zugangsmuster erfüllt:

    • Foto und Reaktionen anzeigen (Lesevorgang)
    • Gefolgte Benutzer für Benutzer anzeigen (Lesevorgang)

    Beim Abrufen aller gefolgten Benutzer für einen bestimmten Benutzer ist ein Problem aufgetreten, bei dem jeder Freundschafts-Entität Informationen über den verfolgten Benutzer fehlten. Im nächsten Modul werden wir sehen, wie die partielle Normalisierung verwendet wird, um bei diesem Zugriffsmuster zu helfen.