Nel modulo precedente, hai impostato l'accesso alle tue entità essenziali in DynamoDB. La struttura della chiave primaria ha risposto ad alcuni dei nostri principali pattern di accesso. Ciò include tutti i modelli di lettura o scrittura di una singola entità, nonché i pattern per recuperare più entità correlate, come tutte le foto che appartengono a un determinato utente.

In questo modulo imparerai a utilizzare un indice invertito, un pattern di progettazione comune per DynamoDB.

Gli indici secondari sono strumenti di creazione di modelli di dati fondamentali in DynamoDB. Ti consentono di rimodellare i tuoi dati per permettere pattern di query alternativi.

Un indice invertito è un pattern di progettazione di indice secondario comune per DynamoDB. Con un indice inverso, crei un indice secondario contrario alla chiave primaria per la tua tabella. La chiave HASH per la tua tabella diventa la chiave RANGE nel tuo indice, e la chiave RANGE per la tua tabella diventa la chiave primaria per il tuo indice.

Un indice invertito è utile in due situazioni. In primo luogo, un indice invertito è utile per query sul lato "opposto" di una relazione molti a molti. Questo è il caso della tua entità Amicizia. Con la struttura della chiave primaria, puoi eseguire una query su tutti i follower per un determinato utente tramite una query sulla chiave primaria della tabella. Quando aggiungi un indice invertito, sarai in grado di trovare gli utenti che un utente sta seguendo (i "seguiti") eseguendo una query sull'indice invertito.

In secondo luogo, un indice invertito è utile anche per query a relazioni uno a molti per un'entità che è essa stessa oggetto di una relazione uno a molti. Puoi vederlo con l'entità Reazione nella tua tabella. Su una singola foto possono esserci più reazioni e ogni reazione includerà la foto a cui si applica la reazione, il nome utente dell'utente che reagisce e il tipo di reazione. Tuttavia, poiché un utente può avere molte foto, l'identificatore principale di una foto è nella sua chiave RANGE -- PHOTO#<USERNAME>#<TIMESTAMP>. Per questo motivo, non è possibile utilizzare la chiave primaria per correlare le reazioni alle foto.

Per soddisfare il pattern di accesso "Visualizza foto e reazioni", il modello di dati posiziona l'identificatore di foto per un'entità Reazione nella chiave RANGE. Ora, quando si esegue una query sull'indice invertito, è possibile utilizzare l'identificatore di foto per selezionare la foto e tutte le sue reazioni in una singola richiesta. Questo procedimento è mostrato in seguito nella fase 2.

Tempo necessario per completare il modulo: 40 minuti


  • Fase 1: Crea un indice secondario

    Per creare un indice secondario, specifica la chiave primaria dell'indice, proprio come quando hai creato la tabella. Ricorda che la chiave primaria per un indice secondario globale non deve essere univoca. Poi, DynamoDB copia gli elementi nell'indice sulla base degli attributi specificati, quindi puoi effettuarvi le query proprio come nella tabella.

    Un indice invertito è un pattern comune in DynamoDB dove crei un indice secondario che è l'inverso della chiave primaria della tua tabella. La chiave HASH per la tabella viene specificata come chiave RANGE nell'indice secondario e la chiave RANGE per la tabella viene specificata come chiave HASH nell'indice secondario.

    La creazione di un indice secondario è simile alla creazione di una tabella. Nel codice che hai scaricato, nella directory scripts/ è presente un file denominato add_inverted_index.py. Tale file conterrà gli elementi riportati di seguito.

    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)
    

    Quando gli attributi vengono utilizzati in una chiave primaria per una tabella o un indice secondario, devono essere definiti in AttributeDefinitions. Poi, dobbiamo creare un nuovo indice secondario nella proprietà GlobalSecondaryIndexUpdates. Per questo indice secondario, specifichiamo il nome dell'indice, lo schema della chiave primaria, il throughput su cui si è effettuato il provisioning e gli attributi che vogliamo mostrare.

    Ricorda che un indice invertito è un nome di un pattern di progettazione e non una proprietà ufficiale in DynamoDB. Creare un indice invertito è come creare qualsiasi altro indice secondario.

    Crea il tuo indice invertito eseguendo il seguente comando.

    python scripts/add_inverted_index.py

    Dovresti visualizzare nella console il messaggio: "Tabella aggiornata con successo".

    Nella fase successiva, mostreremo come utilizzare il nostro indice invertito per trovare una foto.

  • Fase 2: Esegui una query dell'indice invertito per trovare le reazioni a una foto

    Adesso che abbiamo configurato l'indice secondario, lo utilizzeremo per soddisfare alcuni pattern di accesso.

    Per utilizzare un indice secondario, hai due chiamate API disponibili: Query e Scan. Con Query, devi specificare la chiave HASH e otterrai il risultato fissato. Con Scan, non devi specificare una chiave HASH e l'operazione verrà eseguita sull'intera tabella. Le chiamate Scan sono sconsigliate in DynamoDB tranne in circostanze specifiche perché accedono a tutti gli elementi nel tuo database. Se hai un gran numero di dati nella tua tabella, l'operazione di scansione può richiedere molto tempo

    Possiamo utilizzare l'API Query sul nostro indice secondario per trovare tutte le reazioni a una determinata foto. Come visto nel modulo precedente, puoi utilizzare questa query per recuperare due tipi di entità in un singolo comando. Con questa query puoi recuperare una foto e le rispettive reazioni.

    Nel codice che hai scaricato, nella directory application/ è presente un file denominato fetch_photo_and_reactions.py. I contenuti di questo script sono mostrati di seguito.

    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)
    

    La funzione fetch_photo_and_reactions è simile a qualunque funzione della tua applicazione. La funzione accetta un nome utente e un timestamp, e invia una query a InvertedIndex per trovare la foto e le rispettive reazioni. Quindi assembla le voci restituite in un'entità Foto e in più entità Reazione che possono essere utilizzate nella tua applicazione.

    python application/fetch_photo_and_reactions.py

    Dovresti vedere un risultato foto e le sue cinque reazioni.

    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>

    Ricorda che l'indice secondario richiede qualche istante per il riempimento. È possibile che venga visualizzato un messaggio di errore che indica che il riempimento è in corso. Se del caso, riprova dopo qualche minuto.

    Nella fase successiva, vedremo come utilizzare l'indice invertito per recuperare tutti gli utenti che un determinato utente sta seguendo.

  • Fase 3: Cerca gli utenti seguiti

    Nella fase precedente, hai visto come utilizzare un indice invertito per recuperare una relazione uno a molti per un'entità che era essa stessa oggetto di una relazione uno a molti. In questa fase, utilizzerai l'indice invertito per recuperare il lato "opposto" di una relazione molti a molti.

    La chiave primaria nella tabella ti consente di trovare tutti i follower di un determinato utente, ma non ti permette di trovare tutti gli utenti che qualcuno sta seguendo. Con l'indice invertito, tutto è al contrario: puoi trovare tutti gli utenti seguiti da un determinato utente.

    Nel codice che hai scaricato, nella directory application/ è presente un file denominato find_following_for_user.py. Seguono i contenuti di questo script.

    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)
    

    La funzione find_following_for_user è simile a qualunque funzione della tua applicazione. La funzione accetta un nome utente per il quale desideri cercare gli utenti seguiti. La funzione quindi esegue una query dell'indice invertito per trovare tutte le entità di Amicizia in cui il seguente utente è il nome utente indicato.

    Lancia lo script eseguendo il seguente comando nel tuo terminale.

    python application/find_following_for_user.py

    La tua console dovrebbe generare un elenco di utenti seguito dal nome utente indicato:

    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>

    Ricorda che mentre questo restituisce tutte le entità Amicizia per un utente, le informazioni in un'entità Amicizia sono piuttosto sparse. Include solo il nome utente dell'utente seguito ma non il profilo utente completo. Nel modulo successivo, esamineremo come utilizzare la normalizzazione parziale per gestire in modo efficiente situazioni come queste.

  • Conclusioni

    In questo modulo, abbiamo aggiunto un indice secondario alla nostra tabella usando il pattern di indice invertito. Questo ha soddisfatto due ulteriori pattern di accesso:

    • Visualizza foto e reazioni (Leggi)
    • Visualizza seguiti per utente (Leggi)

    Durante il recupero di tutti gli utenti seguiti per un determinato utente, abbiamo riscontrato un problema in base al quale a ogni entità Amicizia mancavano informazioni sull'utente seguito. Nel modulo successivo, vedremo come utilizzare la normalizzazione parziale a supporto di questo pattern di accesso.