DynamoDB nach Datum abfragen

102

Ich komme aus einem relationalen Datenbankhintergrund und versuche, mit Amazon DynamoDB zu arbeiten

Ich habe eine Tabelle mit einem Hash-Schlüssel "DataID" und einem Bereich "CreatedAt" und einer Reihe von Elementen darin.

Ich versuche, alle Elemente abzurufen, die nach einem bestimmten Datum erstellt und nach Datum sortiert wurden. Was in einer relationalen Datenbank ziemlich einfach ist.

In DynamoDB ist das Nächste, was ich finden kann, eine Abfrage und die Verwendung des Bereichsschlüssels größer als Filter. Das einzige Problem ist, dass ich zum Ausführen einer Abfrage einen Hash-Schlüssel benötige, der den Zweck zunichte macht.

Also, was mache ich falsch? Ist mein Tabellenschema falsch, sollte der Hash-Schlüssel nicht eindeutig sein? oder gibt es eine andere Möglichkeit zum Abfragen?

Apfeltuch
quelle

Antworten:

36

Aktualisierte Antwort:

DynamoDB ermöglicht die Angabe von Sekundärindizes, um diese Art von Abfrage zu unterstützen. Sekundärindizes können entweder global sein, was bedeutet, dass der Index die gesamte Tabelle über Hash-Schlüssel erstreckt, oder lokal, was bedeutet, dass der Index in jeder Hash-Schlüsselpartition vorhanden ist, sodass der Hash-Schlüssel auch bei der Abfrage angegeben werden muss.

Für den Anwendungsfall in dieser Frage möchten Sie einen globalen Sekundärindex für das Feld "CreatedAt" verwenden.

Weitere Informationen zu DynamoDB-Sekundärindizes finden Sie in der Dokumentation zum Sekundärindex

Ursprüngliche Antwort:

DynamoDB erlaubt keine indizierten Suchvorgänge nur für den Bereichsschlüssel. Der Hash-Schlüssel ist erforderlich, damit der Dienst weiß, in welcher Partition er suchen muss, um die Daten zu finden.

Sie können natürlich einen Scanvorgang ausführen, um nach dem Datumswert zu filtern. Dies würde jedoch einen vollständigen Tabellenscan erfordern, sodass dies nicht ideal ist.

Wenn Sie eine indizierte Suche von Datensätzen nach Zeit über mehrere Primärschlüssel hinweg durchführen müssen, ist DynamoDB möglicherweise nicht der ideale Dienst für Sie, oder Sie müssen möglicherweise eine separate Tabelle (entweder in DynamoDB oder einem relationalen Speicher) zum Speichern von Elementen verwenden Metadaten, für die Sie eine indizierte Suche durchführen können.

Mike Brant
quelle
13
Siehe die Kommentare zur Antwort unten; Es gibt derzeit keine Möglichkeiten, damit umzugehen, zumindest nicht für das, was das OP verlangt hat. Bei GSIs müssen Sie weiterhin einen Hash-Schlüssel angeben, sodass Sie nicht alle Datensätze mit CreatedAtmehr als einem bestimmten Punkt abfragen können .
pkaeding
4
@pkaeding ist richtig. Sie können Datensätze mit Scan älter als ein bestimmtes Datum abrufen , aber nicht in sortierter Reihenfolge. GSI wird Ihnen in diesem Fall nicht helfen. Es ist weder möglich, Partitionsschlüssel zu sortieren , noch nur Bereichsschlüssel abzufragen .
Gkiko
14
Für diejenigen von euch verwirrt. Diese Antwort ist falsch. Seine ursprüngliche Antwort ist richtig, seine aktualisierte Antwort jedoch nicht. Lesen Sie die Antwort von Warren Parad weiter unten. Es ist richtig.
Ryan Shillington
1
@MikeBrant Ich möchte eine Tabelle auf dem GSI-Hash-Schlüssel (CreatedAt) einer Tabelle mit dem Symbol größer als abfragen (nicht scannen, wodurch jedes Element in der Tabelle betrachtet wird, was es sehr ineffizient und kostspielig macht). Soweit ich weiß, ist dies nicht möglich.
Aziz Javed
4
Das Problem, das Sie wahrscheinlich bei der Verwendung eines Datums als primäre Partition bekommen, besteht darin, dass Sie möglicherweise einen Hotspot auf einigen oder einem der Peers erstellen, da in den meisten Datenspeichern neue Daten häufiger abgefragt werden als alte Daten.
Wissen
52

Aufgrund Ihrer aktuellen Tabellenstruktur ist dies derzeit in DynamoDB nicht möglich. Die große Herausforderung besteht darin zu verstehen, dass der Hash-Schlüssel der Tabelle (Partition) als Erstellen separater Tabellen behandelt werden sollte. In mancher Hinsicht ist dies sehr leistungsfähig (denken Sie an Partitionsschlüssel als Erstellen einer neuen Tabelle für jeden Benutzer oder Kunden usw.).

Abfragen können nur in einer einzelnen Partition durchgeführt werden. Das ist wirklich das Ende der Geschichte. Wenn Sie also nach Datum abfragen möchten (Sie möchten seit der Epoche ms verwenden), müssen alle Elemente, die Sie in einer einzelnen Abfrage abrufen möchten, denselben Hash (Partitionsschlüssel) haben.

Ich sollte das qualifizieren. Sie können absolut scannach dem Kriterium suchen, das Sie suchen, das ist kein Problem, aber das bedeutet, dass Sie jede einzelne Zeile in Ihrer Tabelle betrachten und dann prüfen, ob diese Zeile ein Datum hat, das Ihren Parametern entspricht. Dies ist sehr teuer, insbesondere wenn Sie in erster Linie Ereignisse nach Datum speichern möchten (dh Sie haben viele Zeilen).

Sie könnten versucht sein, alle Daten in einer einzigen Partition zu speichern, um das Problem zu lösen, und Sie können dies absolut, jedoch ist Ihr Durchsatz schmerzhaft niedrig, da jede Partition nur einen Bruchteil der insgesamt festgelegten Menge erhält.

Am besten ermitteln Sie nützlichere Partitionen, die zum Speichern der Daten erstellt werden sollen:

  • Müssen Sie wirklich alle Zeilen betrachten oder sind es nur die Zeilen eines bestimmten Benutzers?

  • Wäre es in Ordnung, die Liste zuerst nach Monat einzugrenzen und mehrere Abfragen durchzuführen (eine für jeden Monat)? Oder nach Jahr?

  • Wenn Sie eine Zeitreihenanalyse durchführen, gibt es mehrere Optionen. Ändern Sie den Partitionsschlüssel in einen berechneten Schlüssel, PUTum dies zu queryvereinfachen, oder verwenden Sie ein anderes aws-Produkt wie kinesis, das sich für die Protokollierung nur zum Anhängen eignet.

Warren Parad
quelle
4
Ich möchte die Option hervorheben, die Sie in Ihrem letzten Absatz zur Berücksichtigung von "nach Jahr" angesprochen haben. Erstellen Sie ein Attribut wie yyyyund einen Hash dafür, aber erstellen createdSie auch ein Datum, das Sie als Bereichsschlüssel verwenden können. Dann erhalten Sie 10 GB Daten pro Jahr (27 MB pro Tag), was für weitere Umstände wahrscheinlich in Ordnung ist. Es bedeutet zwar, dass Sie eine Abfrage pro Jahr erstellen müssen, wenn Datumsabfragen die Jahresgrenze überschreiten, aber zumindest funktioniert dies und es ist sicherer als das Erstellen eines Dummy-Hash-Schlüssels.
Ryan Shillington
1
Eine weitere Option: stackoverflow.com/questions/35963243/…
Ryan Shillington
1
Wie der obige Link erklärt, können streng zeitbasierte Partitionsschlüssel zu Hot Spots führen. Wenn Sie zeitbasierte Partitionsschlüssel verwenden müssen, ist es besser, dem Partitionsschlüssel ein anderes Element hinzuzufügen, um einen Zeitraum über mehrere Partitionen zu verteilen. Ich habe Vorschläge gesehen, nur ein Präfix zwischen 0 und n zu verwenden, wobei n die Anzahl der Partitionen ist, über die der Bucket jedes Mal verteilt werden soll.
dres
@ RyanShillington Es gibt keine 10 GB-Beschränkung für globale Sekundärindizes. Diese Grenze gilt nur für lokale Sekundärindizes.
Simon Forsberg
18

Der Ansatz, den ich zur Lösung dieses Problems verfolgt habe, besteht darin, einen globalen Sekundärindex wie folgt zu erstellen. Ich bin mir nicht sicher, ob dies der beste Ansatz ist, aber hoffentlich, ob er für jemanden nützlich ist.

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

Dem HTTP-API-Benutzer auferlegte Einschränkung, die Anzahl der Tage zum Abrufen von Daten anzugeben, standardmäßig 24 Stunden.

Auf diese Weise kann ich den HashKey immer als Tag des aktuellen Datums angeben und RangeKey kann beim Abrufen die Operatoren> und <verwenden. Auf diese Weise werden die Daten auch auf mehrere Shards verteilt.

Gireesh
quelle
8

Ihr Hash-Schlüssel (primär sortiert) muss eindeutig sein (es sei denn, Sie haben einen Bereich, wie er von anderen angegeben wurde).

In Ihrem Fall sollten Sie zum Abfragen Ihrer Tabelle einen Sekundärindex haben.

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

Ihr Hash-Schlüssel ist ID. Ihr Sekundärindex ist definiert als: DataID-Created-Index (das ist der Name, den DynamoDB verwendet)

Dann können Sie eine Abfrage wie folgt durchführen:

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
    }
});

Im Wesentlichen sieht Ihre Anfrage so aus:

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

Der Sekundärindex erhöht die erforderlichen Lese- / Schreibkapazitätseinheiten, sodass Sie dies berücksichtigen müssen. Es ist immer noch viel besser als ein Scan, der in Lese- und Zeitaufwand teuer ist (und meiner Meinung nach auf 100 Artikel begrenzt ist).

Dies ist vielleicht nicht der beste Weg, aber für jemanden, der an RD gewöhnt ist (ich bin auch an SQL gewöhnt), ist es der schnellste Weg, um produktiv zu werden. Da es keine Einschränkungen hinsichtlich des Schemas gibt, können Sie etwas erstellen, das funktioniert, und sobald Sie die Bandbreite haben, um auf die effizienteste Weise zu arbeiten, können Sie Änderungen vornehmen.

ET
quelle
1
Sie sagen, es gibt keine Einschränkungen, aber Sie sollten wissen, dass Sie mit diesem Ansatz höchstens 10 GB Daten speichern können (das Maximum einer einzelnen Partition).
Ryan Shillington
Dies wäre der Ansatz gewesen, wenn DataID bekannt wäre. Aber hier müssen wir jede Zeile abrufen, für die das erstellte mehr als ein Datum ist.
Yasith Prabuddhaka
3

Sie können den Hash-Schlüssel in Anlehnung an eine 'Produktkategorie'-ID und dann den Bereichsschlüssel als Kombination eines Zeitstempels mit einer eindeutigen ID am Ende festlegen. Auf diese Weise kennen Sie den Hash-Schlüssel und können das Datum immer noch mit größer als abfragen.

greg
quelle
1

Sie können mehrere identische Hash-Schlüssel haben. aber nur, wenn Sie eine Bereichstaste haben, die variiert. Stellen Sie es sich wie Dateiformate vor. Sie können 2 Dateien mit demselben Namen im selben Ordner haben, solange ihr Format unterschiedlich ist. Wenn ihr Format gleich ist, muss ihr Name unterschiedlich sein. Das gleiche Konzept gilt für die Hash- / Range-Schlüssel von DynamoDB. Stellen Sie sich den Hash als Namen und den Bereich als Format vor.

Ich erinnere mich auch nicht, ob sie diese zum Zeitpunkt des OP hatten (ich glaube nicht, dass sie es getan haben), aber sie bieten jetzt lokale Sekundärindizes an.

Nach meinem Verständnis sollten Sie jetzt die gewünschten Abfragen ausführen können, ohne einen vollständigen Scan durchführen zu müssen. Der Nachteil ist, dass diese Indizes bei der Tabellenerstellung angegeben werden müssen und auch (glaube ich) beim Erstellen eines Elements nicht leer sein dürfen. Darüber hinaus erfordern sie zusätzlichen Durchsatz (obwohl normalerweise nicht so viel wie ein Scan) und Speicherplatz, sodass dies für einige keine perfekte Lösung, sondern eine praktikable Alternative ist.

Ich empfehle jedoch immer noch die Antwort von Mike Brant als bevorzugte Methode zur Verwendung von DynamoDB. und benutze diese Methode selbst. In meinem Fall habe ich nur eine zentrale Tabelle mit nur einem Hash-Schlüssel als ID, dann sekundäre Tabellen mit einem Hash und einem Bereich, die abgefragt werden können. Dann zeigt das Element den Code direkt auf das "interessierende Element" der zentralen Tabelle .

Weitere Daten zu den Sekundärindizes finden Sie in der DynamoDB-Dokumentation von Amazon hier für Interessenten.

Wie auch immer, hoffentlich hilft dies allen anderen, die auf diesen Thread stoßen.

DGolberg
quelle
Ich habe versucht, eine DynamoDB-Tabelle zu erstellen, in der AWSDynamoDBKeySchemaElement 'createdAt' vom Typ Hash und erneut AWSDynamoDBKeySchemaElement 'createdAt' vom Typbereich vorhanden war, und es wurde ein Fehler mit der Meldung Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0 "(null) angezeigt = {__ type = com.amazon.coral.validate # ValidationException, message = Sowohl der Hash-Schlüssel als auch das Range Key-Element im KeySchema haben denselben Namen}. Ich denke also nicht, dass das, was du sagst, richtig ist.
user1709076
Ich glaube, Sie haben es falsch verstanden (obwohl ich in meiner Beschreibung auch nicht sehr klar war). Sie können nicht zwei verschiedene Attribute (Spalten) mit demselben Namen in einer Tabelle haben. Wenn Sie jedoch einen Hash-Schlüssel mit einem Bereichsschlüssel erstellen, können Sie mehrere Elemente verwenden, die alle denselben Hash verwenden, solange ihr Bereich unterschiedlich ist umgekehrt. Beispiel: Ihr Hash ist "ID" und Ihr Bereich ist "Datum". Sie können 2 Instanzen der ID "1234" haben, solange ihr Datum unterschiedlich ist.
DGolberg
Ah DGoldberg! Ich hole dich jetzt. Das ist großartig. Für meinen Fall, da ich nur und immer nur nach Textnachrichten 'nach Datum = x' fragen möchte, könnte ich anscheinend alle Textnachrichten so einstellen, dass sie den gleichen 'fake_hash = 1' haben. Dann mache meine query.keyConditionExpression = @ "fake_hash = 1 und #Date>: val". Vielen Dank. Wenn Sie eine andere Eingabe haben, würde ich mich freuen, diese zu hören, da es seltsam erscheint, einen Hash zu haben, der immer den gleichen Wert hat?
user1709076
Ich müsste es noch einmal überprüfen, aber ich bin mir ziemlich sicher, dass Sie eine Abfrage für Nur-Hash-Tabellen durchführen können. Wenn Sie jedoch einen Datums- / Zeitstempel als Hash verwenden, würde ich empfehlen, bis zum kürzeste mögliche Einheit, wie Millisekunden oder Nano / Mikrosekunden (unabhängig von der kleinsten Zeiteinheit, die der Code aufzeichnen kann), um die Wahrscheinlichkeit einer Überlappung von Datum und Uhrzeit zu verringern. Darüber hinaus können Sie optimistische Sperren hinzufügen, um die Möglichkeit von Überlappungen weiter zu verringern: docs.aws.amazon.com/amazondynamodb/latest/developerguide/… Versuchen Sie es bei einem Konflikt einfach ein anderes Mal.
DGolberg
-10

Aktualisierte Antwort Es gibt keine bequeme Möglichkeit, dies mit Dynamo DB-Abfragen mit vorhersehbarem Durchsatz zu tun. Eine (suboptimale) Option ist die Verwendung einer GSI mit einem künstlichen HashKey & CreatedAt. Fragen Sie dann nur nach HashKey und erwähnen Sie ScanIndexForward, um die Ergebnisse zu ordnen. Wenn Sie einen natürlichen HashKey finden können (z. B. die Kategorie des Artikels usw.), ist diese Methode ein Gewinner. Wenn Sie andererseits für alle Elemente denselben HashKey beibehalten, wirkt sich dies hauptsächlich dann auf den Durchsatz aus, wenn Ihr Datensatz über 10 GB (eine Partition) hinauswächst.

Ursprüngliche Antwort: Sie können dies jetzt in DynamoDB mithilfe von GSI tun. Machen Sie das Feld "CreatedAt" als GSI und stellen Sie Abfragen wie (GT some_date). Speichern Sie das Datum als Zahl (ms seit der Epoche) für diese Art von Abfragen.

Details finden Sie hier: Globale Sekundärindizes - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

Dies ist eine sehr mächtige Funktion. Beachten Sie, dass die Abfrage auf (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Bedingung beschränkt ist - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html

Sony Kadavan
quelle
30
Ich habe abgelehnt, weil Ihre Antwort, soweit ich das beurteilen kann, falsch ist. Ähnlich wie beim Primärschlüssel einer Tabelle können Sie den Hash-Schlüssel einer GSI nur mit dem EQ-Operator abfragen. Wenn Sie angedeutet haben, dass CreatedAtdies der Bereichsschlüssel der GSI sein soll, müssen Sie einen Hash-Schlüssel auswählen - und dann sind Sie wieder da, wo Sie begonnen haben, da Sie GT CreatedAtnur für einen bestimmten Wert des abfragen können Hash-Schlüssel.
PaF
Einverstanden mit PaF. Die Verwendung einer GSI mit dem Hash-Schlüssel als Erstellungszeit hilft nicht bei Fragen, die im OP gestellt werden.
4-8-15-16-23-42