Utilizzo di indici secondari globali per query di aggregazione materializzate in DynamoDB - Amazon DynamoDB

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Utilizzo di indici secondari globali per query di aggregazione materializzate in DynamoDB

Il mantenimento di aggregazioni quasi in tempo reale e parametri chiave oltre a dati in rapida evoluzione sta acquisendo sempre più valore per permettere alle aziende di prendere decisioni in maniera veloce. Ad esempio, una libreria musicale potrebbe voler mostrare i brani più scaricati quasi in tempo reale, oppure una piattaforma di e-commerce potrebbe dover mostrare i prodotti di tendenza per categoria.

Poiché DynamoDB non supporta nativamente operazioni di aggregazione SUM simili COUNT o tra elementi, il calcolo di questi valori in fase di lettura richiederebbe la scansione di un gran numero di elementi, il che può essere lento e costoso. È invece possibile precalcolare le aggregazioni man mano che i dati cambiano e archiviare i risultati come normali elementi della tabella. Questo modello è chiamato aggregazione materializzata.

Scenario di esempio e modelli di accesso

Prendi in considerazione un'applicazione di libreria musicale con i seguenti requisiti:

  • L'applicazione registra i download di singoli brani ad alto volume (migliaia al secondo).

  • Gli utenti devono vedere i brani più scaricati in un determinato mese con una latenza di un millisecondo.

  • L'applicazione deve inoltre supportare query come «le 10 migliori canzoni del mese» e «tutte le canzoni scaricate in un determinato mese».

Calcolare il conteggio dei download in fase di lettura mediante la scansione di tutti i record di download può essere costoso su questa scala. È invece possibile mantenere un conteggio costante che si aggiorna a ogni download e archiviarlo in un modo che supporti l'esecuzione di query efficienti.

Perché precalcolare le aggregazioni

Esistono diversi approcci al calcolo delle aggregazioni. La tabella seguente mette a confronto le alternative più comuni e spiega perché l'aggregazione materializzata in DynamoDB è spesso la soluzione migliore per questo tipo di casi d'uso.

Approccio Compromessi Quando utilizzare
Scansiona e conta in fase di lettura Richiede la lettura di tutti i record di download per ogni query. La latenza aumenta con il volume dei dati e consuma una notevole capacità di lettura. Adatto solo per set di dati molto piccoli in cui la latenza non è un problema.
Archivio di aggregazione esterno (ad esempio, Amazon ElastiCache) Aggiunge complessità operativa con un servizio separato da gestire. Richiede la logica di sincronizzazione tra DynamoDB e la cache. Quando sono necessarie letture inferiori al millisecondo o una logica di aggregazione complessa che vada oltre i semplici conteggi.
Aggregazione a livello di applicazione in fase di scrittura Associa la logica di aggregazione al percorso di scrittura. Se l'applicazione fallisce dopo aver registrato il download ma prima di aggiornare il conteggio, l'aggregazione diventa incoerente. Quando è necessaria un'aggregazione sincrona e fortemente coerente e si può tollerare una maggiore latenza di scrittura.
Aggregazione materializzata con Streams e Lambda Disaccoppia l'aggregazione dal percorso di scrittura. L'aggregazione alla fine è coerente (in genere con pochi secondi di ritardo). Aggiunge i costi di chiamata Lambda. Quando hai bisogno di aggregazioni quasi in tempo reale con bassa latenza di lettura e puoi tollerare una eventuale coerenza. Questo è l'approccio descritto in questa pagina.

L'approccio di aggregazione materializzata semplifica il percorso di scrittura (basta registrare il download), trasferisce l'aggregazione su un processo asincrono e archivia il risultato in DynamoDB dove può essere interrogato con una latenza di un millisecondo.

Progettazione di tabelle

Questo design utilizza una singola tabella con due tipi di elementi che condividono la stessa chiave di partizione () ma utilizzano schemi di chiave di ordinamento diversi per distinguerli: songID

  • Record di download: eventi di download individuali. La chiave di ordinamento è la DownloadID (un identificatore univoco per ogni download).

  • Elementi di aggregazione mensili: conteggi di download precalcolati per brano al mese. La chiave di ordinamento è il YYYY-MM formato del mese (ad esempio,). 2018-01 Questi elementi contengono anche un DownloadCount attributo con il totale corrente.

Solo gli elementi di aggregazione mensili contengono l'Monthattributo. Questa distinzione è importante per la progettazione sparsa del GSI descritta più avanti.

Il diagramma seguente mostra il layout della tabella con entrambi i tipi di elementi:

Layout della tabella della libreria musicale che mostra i record di download e gli elementi di aggregazione mensili che condividono la stessa chiave di partizione (SongID).
Tipo di elemento Chiave di partizione (SongID) Chiave di ordinamento Attributi aggiuntivi
Scarica il record song1 download-abc123 UserID, Timestamp
Aggregazione mensile song1 2018-01 Month=2018-01, DownloadCount=1,746,992

Pipeline di aggregazione con Streams e AWS Lambda

La pipeline di aggregazione funziona come segue:

  1. Quando viene scaricato un brano, l'applicazione scrive un nuovo elemento nella tabella con Partition-Key=songID e. Sort-Key=DownloadID

  2. DynamoDB Streams acquisisce questa scrittura come record di flusso.

  3. Una funzione Lambda, collegata allo stream, elabora il nuovo record. Identifica il mese songID e il mese corrente, quindi aggiorna l'elemento di aggregazione mensile corrispondente incrementando l'attributo. DownloadCount

  4. L'elemento di aggregazione aggiornato è quindi disponibile per l'interrogazione tramite il GSI sparso.

La funzione Lambda utilizza una UpdateItem chiamata con un'ADDespressione per incrementare atomicamente il numero di download. In questo modo si read-modify-write evitano le condizioni di gara:

import boto3 dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('MusicLibrary') def handler(event, context): for record in event['Records']: if record['eventName'] == 'INSERT': new_image = record['dynamodb']['NewImage'] song_id = new_image['songID']['S'] # Derive the month from the download timestamp timestamp = new_image['Timestamp']['S'] month = timestamp[:7] # Extract YYYY-MM table.update_item( Key={ 'songID': song_id, 'SK': month }, UpdateExpression='ADD DownloadCount :inc SET #m = :month', ExpressionAttributeNames={ '#m': 'Month' }, ExpressionAttributeValues={ ':inc': 1, ':month': month } )
Nota

Se un'esecuzione Lambda fallisce dopo aver scritto il valore di aggregazione aggiornato, è possibile ritentare il record dello stream. Poiché l'ADDoperazione incrementa il conteggio ogni volta che viene eseguita, un nuovo tentativo incrementerebbe il conteggio più di una volta per lo stesso download, lasciandoti con un valore approssimativo. Per la maggior parte dei casi d'uso relativi all'analisi e alla classificazione, questo piccolo margine di errore è accettabile. Se hai bisogno di conteggi esatti, prendi in considerazione l'aggiunta di una logica di idempotenza, ad esempio utilizzando un'espressione di condizione che verifichi se lo specifico è già stato elaborato. DownloadID

Design GSI sparso

Per interrogare in modo efficiente i risultati aggregati, crea un indice secondario globale con il seguente schema chiave:

  • Chiave di partizione GSI: Month (String)

  • Chiave di ordinamento GSI: DownloadCount (Numero)

Questo GSI è scarso perché solo gli elementi di aggregazione mensili contengono l'attributo. Month I singoli record di download non hanno questo attributo, quindi vengono automaticamente esclusi dall'indice. Ciò significa che il GSI contiene solo gli elementi di aggregazione precalcolati, una piccola frazione del totale degli elementi della tabella.

Un GSI scarso offre due vantaggi principali:

  • Costo inferiore: poiché nell'indice vengono replicati solo gli elementi di aggregazione, si consumano molta meno capacità di scrittura e storage rispetto a un indice che include tutti gli elementi della tabella.

  • Query più veloci: l'indice contiene solo i dati necessari per eseguire le query, quindi le letture sono efficienti e restituiscono risultati con una latenza di un millisecondo.

Per ulteriori informazioni sul funzionamento degli indici sparsi, consulta. Trai vantaggio degli indici di tipo sparse

Interrogazione del GSI

Con un GSI sparso, è possibile rispondere in modo efficiente a diversi tipi di domande:

Ottieni la canzone più scaricata in un determinato mese:

aws dynamodb query \ --table-name "MusicLibrary" \ --index-name "MonthDownloadsIndex" \ --key-condition-expression "#m = :month" \ --expression-attribute-names '{"#m": "Month"}' \ --expression-attribute-values '{":month": {"S": "2018-01"}}' \ --scan-index-forward false \ --limit 1

Se ScanIndexForward si imposta su, false i risultati vengono ordinati DownloadCount in ordine decrescente e viene Limit=1 restituito solo il brano migliore.

Visualizza le 10 migliori canzoni di un determinato mese:

aws dynamodb query \ --table-name "MusicLibrary" \ --index-name "MonthDownloadsIndex" \ --key-condition-expression "#m = :month" \ --expression-attribute-names '{"#m": "Month"}' \ --expression-attribute-values '{":month": {"S": "2018-01"}}' \ --scan-index-forward false \ --limit 10

Scarica tutti i brani in un determinato mese (ordinati per numero di download):

aws dynamodb query \ --table-name "MusicLibrary" \ --index-name "MonthDownloadsIndex" \ --key-condition-expression "#m = :month" \ --expression-attribute-names '{"#m": "Month"}' \ --expression-attribute-values '{":month": {"S": "2018-01"}}' \ --scan-index-forward false

Considerazioni

Tieni presente quanto segue quando implementi questo modello:

  • Coerenza finale: i valori di aggregazione vengono aggiornati in modo asincrono tramite DynamoDB Streams e Lambda. In genere c'è un ritardo di alcuni secondi tra la registrazione di un download e l'aggiornamento dell'aggregazione. Ciò significa che il GSI riflette dati quasi in tempo reale, non dati in tempo reale.

  • Concorrenza Lambda: se la tabella ha un volume di scrittura elevato, più chiamate Lambda possono tentare di aggiornare lo stesso elemento di aggregazione contemporaneamente. L'ADDoperazione atomica lo gestisce in modo sicuro, ma è necessario monitorare la concorrenza Lambda e le metriche di limitazione per garantire che la funzione possa tenere il passo con il flusso.

  • Capacità di scrittura GSI: poiché il GSI sparso contiene solo elementi di aggregazione, richiede una capacità di scrittura significativamente inferiore rispetto alla tabella base. Tuttavia, è comunque necessario fornire una capacità sufficiente (o utilizzare la modalità on-demand) per gestire la frequenza degli aggiornamenti di aggregazione.

  • Conteggi approssimativi: come notato in precedenza, i nuovi tentativi Lambda possono far sì che i conteggi vengano leggermente sovrastimati. Per i casi d'uso che richiedono conteggi esatti, implementa i controlli di idempotenza nella funzione Lambda.