Interrogazione di DynamoDB per data


102

Vengo da un database relazionale e cerco di lavorare con DynamoDB di Amazon

Ho una tabella con una chiave hash "DataID" e un intervallo "CreatedAt" e un mucchio di elementi in essa.

Sto cercando di ottenere tutti gli elementi che sono stati creati dopo una data specifica e ordinati per data. Che è abbastanza semplice in un database relazionale.

In DynamoDB la cosa più vicina che potrei trovare è una query e l'utilizzo della chiave di intervallo maggiore del filtro. L'unico problema è che per eseguire una query ho bisogno di una chiave hash che vanifica lo scopo.

Allora cosa sto facendo di sbagliato? Lo schema della mia tabella è sbagliato, la chiave hash non dovrebbe essere univoca? o c'è un altro modo per interrogare?

Risposte:


34

Risposta aggiornata:

DynamoDB consente la specifica di indici secondari per aiutare in questo tipo di query. Gli indici secondari possono essere globali, il che significa che l'indice si estende su tutta la tabella attraverso le chiavi hash, o locale, il che significa che l'indice esisterebbe all'interno di ogni partizione della chiave hash, richiedendo quindi che anche la chiave hash venga specificata quando si effettua la query.

Per il caso d'uso in questa domanda, si desidera utilizzare un indice secondario globale nel campo "CreatedAt".

Per ulteriori informazioni sugli indici secondari DynamoDB, vedere la documentazione dell'indice secondario

Risposta originale:

DynamoDB non consente ricerche indicizzate solo sulla chiave di intervallo. La chiave hash è necessaria in modo che il servizio sappia in quale partizione cercare i dati.

Ovviamente puoi eseguire un'operazione di scansione per filtrare in base al valore della data, tuttavia ciò richiederebbe una scansione completa della tabella, quindi non è l'ideale.

Se è necessario eseguire una ricerca indicizzata dei record in base al tempo su più chiavi primarie, DynamoDB potrebbe non essere il servizio ideale da utilizzare oppure potrebbe essere necessario utilizzare una tabella separata (in DynamoDB o in un negozio relazionale) per archiviare l'elemento metadati in base ai quali è possibile eseguire una ricerca indicizzata.


14
Vedere i commenti sulla risposta di seguito; ci sono non modi per gestire questo ora, almeno non per quello che l'OP ha chiesto. I GSI richiedono ancora di specificare una chiave hash, quindi non è possibile eseguire query per tutti i record con CreatedAtmaggiore di un certo punto.
pkaeding

4
@pkaeding ha ragione. Puoi ottenere record più vecchi di una data specifica utilizzando la scansione , ma non puoi ottenerli in ordine ordinato. GSI non ti aiuterà in questo caso. Non è possibile ordinare la chiave di partizione , né è possibile interrogare solo la chiave di intervallo .
gkiko

15
Per quelli di voi confusi. QUESTA RISPOSTA È SBAGLIATA. La sua risposta originale è giusta ma la sua risposta aggiornata non lo è. Leggi la risposta di Warren Parad di seguito. È corretto.
Ryan Shillington

1
@ MikeBrant Voglio interrogare (non scansionare, che guarda ogni elemento nella tabella, rendendolo molto inefficiente e costoso) una tabella sulla chiave hash GSI di una tabella (CreatedAt) utilizzando il simbolo maggiore di. Per quanto ne so, questo non può essere fatto.
Aziz Javed

4
Il problema che probabilmente si verifica utilizzando una data come partizione primaria è che si potrebbe creare un hotspot su alcuni o uno dei peer, poiché nella maggior parte degli archivi di dati vengono richiesti nuovi dati più spesso dei vecchi dati.
Conoscenza

53

Data l'attuale struttura della tabella, ciò non è attualmente possibile in DynamoDB. L'enorme sfida è capire che la chiave hash della tabella (partizione) dovrebbe essere trattata come la creazione di tabelle separate. In un certo senso questo è davvero potente (pensa alle chiavi di partizione come creare una nuova tabella per ogni utente o cliente, ecc ...).

Le query possono essere eseguite solo in una singola partizione. Questa è davvero la fine della storia. Ciò significa che se desideri eseguire una query per data (ti consigliamo di utilizzare msec dall'epoca), tutti gli elementi che desideri recuperare in una singola query devono avere lo stesso hash (chiave di partizione).

Dovrei qualificarlo. Puoi assolutamente scanfarlo in base al criterio che stai cercando, non è un problema, ma ciò significa che guarderai ogni singola riga della tua tabella e quindi controllerai se quella riga ha una data che corrisponde ai tuoi parametri. Questo è davvero costoso, soprattutto se ti occupi di archiviare gli eventi per data in primo luogo (cioè hai molte righe).

Potresti essere tentato di mettere tutti i dati in una singola partizione per risolvere il problema, e puoi assolutamente farlo, tuttavia il tuo throughput sarà dolorosamente basso, dato che ogni partizione riceve solo una frazione della quantità totale impostata.

La cosa migliore da fare è determinare le partizioni più utili da creare per salvare i dati:

  • Hai davvero bisogno di guardare tutte le righe o sono solo le righe di un utente specifico?

  • Sarebbe opportuno restringere prima l'elenco per mese ed eseguire più query (una per ogni mese)? O per anno?

  • Se stai facendo un'analisi delle serie temporali, ci sono un paio di opzioni, cambia la chiave di partizione in qualcosa di calcolato PUTper rendere il querypiù semplice, o usa un altro prodotto aws come kinesis che si presta alla registrazione di sola aggiunta.


4
Voglio sottolineare l'opzione che hai presentato nel tuo ultimo paragrafo sul considerare "per anno". Crea un attributo come yyyye hash su quello, ma crea anche una createddata che puoi usare come chiave di intervallo. Quindi ottieni 10 GB di dati all'anno (27 MB al giorno), il che probabilmente va bene per più circostanze. Significa che devi creare una query all'anno quando le query di data superano il limite dell'anno, ma almeno funzionerà ed è più sicuro che creare una chiave hash fittizia.
Ryan Shillington


1
come spiega il collegamento sopra, le chiavi di partizione strettamente basate sul tempo possono portare a punti caldi. se è necessario utilizzare chiavi di partizione basate sul tempo, è meglio aggiungere qualche altro elemento alla chiave di partizione per distribuire un periodo di tempo su più partizioni. Ho visto suggerimenti sull'utilizzo di un prefisso compreso tra 0-n dove n è il numero di partizioni ogni volta che il bucket dovrebbe essere distribuito.
dres

@RyanShillington Non esiste un limite di 10 GB per gli indici secondari globali . Tale limite si applica solo agli indici secondari locali .
Simon Forsberg,

18

L'approccio che ho seguito per risolvere questo problema è stato quello di creare un indice secondario globale come di seguito. Non sono sicuro che questo sia l'approccio migliore, ma si spera che sia utile a qualcuno.

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt

Limitazione imposta all'utente dell'API HTTP per specificare il numero di giorni per recuperare i dati, per impostazione predefinita 24 ore.

In questo modo, posso sempre specificare l'HashKey come giorno della data corrente e RangeKey può utilizzare gli operatori> e <durante il recupero. In questo modo i dati vengono anche distribuiti su più frammenti.


8

La tua chiave hash (primaria di ordinamento) deve essere unica (a meno che tu non abbia un intervallo come dichiarato da altri).

Nel tuo caso, per interrogare la tua tabella dovresti avere un indice secondario.

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |

La tua chiave hash è ID Il tuo indice secondario è definito come: DataID-Created-index (questo è il nome che DynamoDB utilizzerà)

Quindi, puoi eseguire una query come questa:

var params = {
    TableName: "Table",
    IndexName: "DataID-Created-index",
    KeyConditionExpression: "DataID = :v_ID AND Created > :v_created",
    ExpressionAttributeValues: {":v_ID": {S: "some_id"},
                                ":v_created": {N: "timestamp"}
    },
    ProjectionExpression: "ID, DataID, Created, Data"
};

ddb.query(params, function(err, data) {
    if (err) 
        console.log(err);
    else {
        data.Items.sort(function(a, b) {
            return parseFloat(a.Created.N) - parseFloat(b.Created.N);
        });
        // More code here
    }
});

Essenzialmente la tua query ha il seguente aspetto:

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;

L'indice secondario aumenterà le unità di capacità di lettura / scrittura richieste, quindi è necessario considerarlo. È ancora molto meglio che eseguire una scansione, che sarà costosa in lettura e in tempo (ed è limitata a 100 elementi credo).

Questo potrebbe non essere il modo migliore per farlo, ma per qualcuno abituato a RD (sono anche abituato a SQL) è il modo più veloce per diventare produttivo. Poiché non ci sono vincoli riguardo allo schema, puoi creare qualcosa che funzioni e una volta che hai la larghezza di banda per lavorare nel modo più efficiente, puoi cambiare le cose.


1
Dici che non ci sono vincoli, ma dovresti sapere che questo approccio significa che puoi salvare al massimo 10 GB di dati (il massimo di una singola partizione).
Ryan Shillington

Questo sarebbe stato l'approccio se DataID fosse noto. Ma qui abbiamo bisogno di ottenere ogni riga per la quale il creato è più di una certa data.
Yasith Prabuddhaka

3

Potresti rendere la chiave hash qualcosa sulla falsariga di un id di "categoria di prodotto", quindi la chiave di intervallo come combinazione di un timestamp con un ID univoco aggiunto alla fine. In questo modo conosci la chiave hash e puoi comunque interrogare la data con maggiore di.


1

Puoi avere più chiavi hash identiche; ma solo se hai una chiave di intervallo che varia. Pensalo come formati di file; puoi avere 2 file con lo stesso nome nella stessa cartella purché il loro formato sia diverso. Se il formato è lo stesso, il nome deve essere diverso. Lo stesso concetto si applica alle chiavi hash / range di DynamoDB; pensa solo all'hash come nome e all'intervallo come formato.

Inoltre, non ricordo se avessero questi al momento dell'OP (non credo che lo avessero fatto), ma ora offrono indici secondari locali.

La mia comprensione di questi è che ora dovrebbe consentire di eseguire le query desiderate senza dover eseguire una scansione completa. Lo svantaggio è che questi indici devono essere specificati al momento della creazione della tabella e inoltre (credo) non possono essere vuoti durante la creazione di un elemento. Inoltre, richiedono un throughput aggiuntivo (sebbene in genere non tanto quanto una scansione) e archiviazione, quindi non è una soluzione perfetta, ma un'alternativa praticabile, per alcuni.

Tuttavia, consiglio ancora la risposta di Mike Brant come metodo preferito per utilizzare DynamoDB; e utilizzare quel metodo io stesso. Nel mio caso, ho solo una tabella centrale con solo una chiave hash come ID, quindi tabelle secondarie che hanno un hash e un intervallo che possono essere interrogati, quindi l'elemento punta il codice all '"elemento di interesse" della tabella centrale, direttamente .

Ulteriori dati sugli indici secondari possono essere trovati nella documentazione DynamoDB di Amazon qui per coloro che sono interessati.

Ad ogni modo, si spera che questo aiuti chiunque altro accada su questo thread.


Ho provato a creare una tabella DynamoDB in cui c'era AWSDynamoDBKeySchemaElement 'createdAt' di tipo hash e di nuovo AWSDynamoDBKeySchemaElement 'createdAt' di tipo range e ho ricevuto un errore che diceva Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 "(null)" UserInfo = {__ type = com.amazon.coral.validate # ValidationException, message = Sia la chiave hash che l'elemento Range Key in KeySchema hanno lo stesso nome}. Quindi non penso che quello che stai dicendo sia corretto.
user1709076

Credo che tu abbia frainteso (anche se suppongo di non essere stato molto chiaro neanche nella mia descrizione). Non puoi avere 2 diversi attributi (colonne) con lo stesso nome, in una tabella, ma quando crei una chiave hash con una chiave di intervallo, puoi avere più elementi che usano tutti lo stesso hash purché il loro intervallo sia diverso e viceversa. Ad esempio: il tuo hash è "ID" e il tuo intervallo è "Data" potresti avere 2 istanze dell'ID "1234" purché la loro data sia diversa.
DGolberg

Ah DGoldberg! Ti prendo adesso. È fantastico. Quindi, nel mio caso, dal momento che voglio solo e sempre solo interrogare i messaggi di testo "dopo la data = x", sembra che potrei impostare tutti i messaggi di testo in modo che abbiano lo stesso "fake_hash = 1". Quindi esegui la mia query.keyConditionExpression = @ "fake_hash = 1 e #Date>: val". Grazie mille. Se hai qualche altro input sarei felice di sentirlo dato che sembra strano avere un hash che ha sempre lo stesso valore?
user1709076

Dovrei controllare di nuovo, ma sono abbastanza sicuro che tu possa fare una query su tabelle di soli hash ... anche se se stai usando un indicatore di data / ora come hash, ti consiglio di registrarlo nel unità più breve possibile, come millisecondi o nano / microsecondi (qualunque sia la più piccola unità di tempo che il codice può registrare), al fine di ridurre la possibilità di sovrapposizione di data / ora. Inoltre, puoi aggiungere un blocco ottimistico per ridurre ulteriormente la possibilità di sovrapposizioni: docs.aws.amazon.com/amazondynamodb/latest/developerguide/… Riprova semplicemente un'altra volta se c'è un conflitto.
DGolberg

-11

Risposta aggiornata Non esiste un modo conveniente per eseguire questa operazione utilizzando le query Dynamo DB con un throughput prevedibile. Un'opzione (non ottimale) è usare un GSI con un HashKey artificiale e CreatedAt. Quindi interroga solo HashKey e menziona ScanIndexForward per ordinare i risultati. Se riesci a trovare un HashKey naturale (ad esempio la categoria dell'elemento, ecc.) Allora questo metodo è vincente. D'altra parte, se mantieni lo stesso HashKey per tutti gli elementi, influenzerà il throughput principalmente quando il tuo set di dati supera i 10 GB (una partizione)

Risposta originale: puoi farlo ora in DynamoDB utilizzando GSI. Crea il campo "CreatedAt" come GSI e invia query come (GT some_date). Memorizza la data come numero (msec dall'epoca) per questo tipo di query.

I dettagli sono disponibili qui: Global Secondary Indexes - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

Questa è una caratteristica molto potente. Tieni presente che la query è limitata a (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Condizione - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html


31
Ho downvotato perché, per quanto ne so, la tua risposta non è corretta. Proprio come la chiave primaria di una tabella, puoi interrogare la chiave hash di un GSI solo con l'operatore EQ. Se stavi insinuando che CreatedAtdovrebbe essere la chiave di intervallo del GSI, dovrai scegliere una chiave hash e poi tornerai dove hai iniziato, perché sarai in grado di interrogare GT CreatedAtsolo per un valore specifico del tasto cancelletto.
PaF

Concordato con PaF. L'utilizzo di un GSI con la chiave hash come l'ora di creazione non aiuta con la domanda posta nell'OP.
4-8-15-16-23-42
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.