Zapytanie do DynamoDB według daty

107

Pochodzę z relacyjnej bazy danych i próbuję pracować z DynamoDB Amazona

Mam tabelę z kluczem skrótu „DataID” i zakresem „CreatedAt” oraz kilkoma elementami w nim.

Próbuję uzyskać wszystkie elementy, które zostały utworzone po określonej dacie i posortowane według daty. Co jest dość proste w relacyjnej bazie danych.

W DynamoDB najbliższą rzeczą, jaką mogłem znaleźć, jest zapytanie i użycie klucza zakresu większego niż filtr. Jedynym problemem jest to, że do wykonania zapytania potrzebuję klucza skrótu, który mija się z celem.

Więc co robię źle? Czy mój schemat tabeli jest nieprawidłowy, czy klucz skrótu nie powinien być unikalny? czy jest inny sposób zapytania?

applechief
źródło

Odpowiedzi:

34

Zaktualizowana odpowiedź:

DynamoDB pozwala na określenie indeksów pomocniczych, aby pomóc w tego rodzaju zapytaniach. Indeksy pomocnicze mogą być globalne, co oznacza, że ​​indeks obejmuje całą tabelę między kluczami skrótu, lub lokalne, co oznacza, że ​​indeks istniałby w każdej partycji z kluczem mieszającym, co oznacza, że ​​podczas wykonywania zapytania należy również podać klucz skrótu.

W przypadku użycia w tym pytaniu chciałbyś użyć globalnego indeksu pomocniczego w polu „CreatedAt”.

Więcej informacji na temat indeksów pomocniczych DynamoDB można znaleźć w dokumentacji indeksów pomocniczych

Oryginalna odpowiedź:

DynamoDB nie zezwala na indeksowane wyszukiwania tylko w kluczu zakresu. Klucz skrótu jest wymagany, aby usługa wiedziała, na której partycji ma szukać danych.

Możesz oczywiście wykonać operację skanowania, aby przefiltrować według wartości daty, jednak wymagałoby to pełnego skanowania tabeli, więc nie jest to idealne rozwiązanie.

Jeśli musisz przeprowadzić indeksowane wyszukiwanie rekordów według czasu w wielu kluczach podstawowych, DynamoDB może nie być idealną usługą do użycia lub może być konieczne użycie oddzielnej tabeli (w DynamoDB lub w sklepie relacyjnym) do przechowywania pozycji metadane, względem których można przeprowadzić wyszukiwanie indeksowane.

Mike Brant
źródło
15
Zobacz komentarze do odpowiedzi poniżej; nie ma teraz sposobów, żeby sobie z tym poradzić, przynajmniej nie o to, o co prosił PO. GSI nadal wymagają określenia klucza skrótu, więc nie można zapytać o wszystkie rekordy z CreatedAtpunktem większym niż określony.
data premiery
4
@pkaeding ma rację. Możesz uzyskać rekordy starsze niż określona data za pomocą skanowania , ale nie możesz uzyskać ich w posortowanej kolejności. GSI nie pomoże Ci w tym przypadku. Nie jest możliwe sortowanie klucza partycji ani zapytanie tylko o klucz zakresu .
gkiko
15
Dla tych z was, którzy są zdezorientowani. TA ODPOWIEDŹ JEST ZŁA. Jego pierwotna odpowiedź jest prawidłowa, ale zaktualizowana odpowiedź nie. Przeczytaj odpowiedź Warrena Parada poniżej. Jest prawidłowe.
Ryan Shillington
1
@MikeBrant Chcę zapytać (nie skanować, które sprawdza każdy element w tabeli, co czyni go bardzo nieefektywnym i kosztownym) tabeli w kluczu skrótu GSI tabeli (CreatedAt) przy użyciu symbolu większości. O ile wiem, nie da się tego zrobić.
azizj1
4
Problem, który prawdopodobnie napotkasz podczas używania daty jako partycji podstawowej, polega na tym, że możesz utworzyć punkt aktywny na niektórych lub jednym z rówieśników, ponieważ w większości magazynów danych nowe dane są odpytywane częściej niż stare dane.
Wiedza,
54

Biorąc pod uwagę aktualną strukturę tabeli, nie jest to obecnie możliwe w DynamoDB. Ogromnym wyzwaniem jest zrozumienie, że klucz Hash tabeli (partycji) należy traktować jako tworzenie oddzielnych tabel. Pod pewnymi względami jest to naprawdę potężne (pomyśl o kluczach partycji jako o tworzeniu nowej tabeli dla każdego użytkownika lub klienta itp.).

Zapytania można wykonywać tylko w jednej partycji. To naprawdę koniec historii. Oznacza to, że jeśli chcesz zapytać według daty (będziesz chciał użyć ms od epoki), wówczas wszystkie elementy, które chcesz pobrać w pojedynczym zapytaniu, muszą mieć ten sam skrót (klucz partycji).

Powinienem to zakwalifikować. Absolutnie możesz scanwedług kryterium, którego szukasz, to nie jest problem, ale oznacza to, że będziesz patrzeć na każdy wiersz w swojej tabeli, a następnie sprawdzać, czy ten wiersz ma datę pasującą do twoich parametrów. Jest to naprawdę drogie, zwłaszcza jeśli zajmujesz się przechowywaniem wydarzeń według daty (tj. Masz dużo wierszy).

Możesz ulec pokusie umieszczenia wszystkich danych na jednej partycji, aby rozwiązać problem, i absolutnie możesz, jednak Twoja przepustowość będzie boleśnie niska, biorąc pod uwagę, że każda partycja otrzymuje tylko ułamek całkowitej ustawionej kwoty.

Najlepszą rzeczą do zrobienia jest określenie bardziej przydatnych partycji do utworzenia w celu zapisania danych:

  • Czy naprawdę musisz patrzeć na wszystkie wiersze, czy jest to tylko wiersze określonego użytkownika?

  • Czy byłoby dobrze najpierw zawęzić listę według miesiąca i wykonać wiele zapytań (jedno na każdy miesiąc)? Lub według roku?

  • Jeśli wykonujesz analizę szeregów czasowych, istnieje kilka opcji, zmień klucz partycji na coś obliczonego, PUTaby queryułatwić sobie pracę, lub użyj innego produktu aws, takiego jak kinezy, który nadaje się do rejestrowania tylko przez dołączanie.

Warren Parad
źródło
4
Chcę podkreślić opcję, którą przedstawiłeś w swoim ostatnim akapicie, dotyczącą rozważania „według roku”. Utwórz atrybut taki jak yyyyi hash na tym, ale także utwórz createddatę, której możesz użyć jako klucza zakresu. Następnie otrzymujesz 10 GB danych rocznie (27 MB dziennie), co prawdopodobnie jest w porządku w innych okolicznościach. Oznacza to, że musisz tworzyć kwerendę rocznie, gdy zapytania dotyczące dat przekraczają granicę roku, ale przynajmniej zadziała i jest bezpieczniejsze niż tworzenie fikcyjnego klucza mieszającego.
Ryan Shillington
1
Inna opcja: stackoverflow.com/questions/35963243/…
Ryan Shillington
1
Jak wyjaśnia powyższy link, klucze partycji ściśle oparte na czasie mogą prowadzić do hot spotów. jeśli musisz używać kluczy partycji opartych na czasie, lepiej jest dodać inny element do klucza partycji, aby rozłożyć przedział czasu na wiele partycji. Widziałem sugestie używania przedrostka między 0-n, gdzie n to liczba partycji za każdym razem, gdy należy rozłożyć zasobnik.
dres
@RyanShillington Nie ma limitu 10 GB na globalne indeksy pomocnicze. Ten limit dotyczy tylko lokalnych indeksów pomocniczych.
Simon Forsberg
18

Podejście, które zastosowałem, aby rozwiązać ten problem, polega na utworzeniu globalnego indeksu wtórnego, jak poniżej. Nie jestem pewien, czy to najlepsze podejście, ale miejmy nadzieję, że komuś przyda się.

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

Ograniczenie nałożone na użytkownika interfejsu API HTTP do określenia liczby dni pobierania danych, domyślnie 24 godziny.

W ten sposób zawsze mogę określić HashKey jako dzień bieżącej daty, a RangeKey może używać operatorów> i <podczas pobierania. W ten sposób dane są również rozproszone na wielu fragmentach.

Gireesh
źródło
8

Twój klucz Hash (główny rodzaj) musi być unikalny (chyba że masz zakres taki jak podany przez innych).

W twoim przypadku, aby wykonać zapytanie o twoją tabelę, powinieneś mieć dodatkowy indeks.

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

Twój klucz skrótu to ID Twój dodatkowy indeks jest zdefiniowany jako: DataID-Utworzony-indeks (to nazwa, której będzie używać DynamoDB)

Następnie możesz wykonać takie zapytanie:

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

Zasadniczo Twoje zapytanie wygląda następująco:

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

Indeks pomocniczy zwiększy wymagane jednostki pojemności odczytu / zapisu, więc musisz to wziąć pod uwagę. Nadal jest o wiele lepsze niż skanowanie, które będzie kosztowne w odczytach i czasie (i jest ograniczone do 100 pozycji, jak sądzę).

Może to nie jest najlepszy sposób na zrobienie tego, ale dla kogoś przyzwyczajonego do RD (jestem przyzwyczajony do SQL) jest to najszybszy sposób na uzyskanie produktywności. Ponieważ nie ma ograniczeń co do schematu, możesz uruchomić coś, co działa, a gdy masz przepustowość do pracy w najbardziej efektywny sposób, możesz wszystko zmienić.

ET
źródło
1
Mówisz, że nie ma żadnych ograniczeń, ale powinieneś wiedzieć, że takie podejście oznacza, że ​​możesz zapisać maksymalnie 10 GB danych (maksymalnie jedną partycję).
Ryan Shillington
Byłoby to podejście, gdyby znany był DataID. Ale tutaj musimy pobrać każdy wiersz, dla którego utworzono więcej niż jakąś datę.
Yasith Prabuddhaka
3

Możesz ustawić klucz Hash w podobny sposób jak identyfikator „kategorii produktu”, a następnie klucz zakresu jako kombinację znacznika czasu z unikalnym identyfikatorem dołączonym na końcu. W ten sposób znasz klucz skrótu i ​​nadal możesz zapytać o datę z większym niż.

greg
źródło
1

Możesz mieć wiele identycznych kluczy mieszających; ale tylko wtedy, gdy masz zmienny klucz zakresu. Pomyśl o tym jak o formatach plików; możesz mieć 2 pliki o tej samej nazwie w tym samym folderze, o ile ich format jest inny. Jeśli ich format jest taki sam, ich nazwa musi być inna. Ta sama koncepcja dotyczy kluczy mieszających / zakresowych DynamoDB; pomyśl tylko o haszu jako nazwie i zakresie jako formacie.

Nie pamiętam też, czy mieli je w czasie PO (nie sądzę, że mieli), ale teraz oferują lokalne indeksy wtórne.

Rozumiem, że powinno to teraz umożliwiać wykonywanie żądanych zapytań bez konieczności wykonywania pełnego skanowania. Wadą jest to, że te indeksy muszą być określone podczas tworzenia tabeli, a także (moim zdaniem) nie mogą być puste podczas tworzenia elementu. Ponadto wymagają dodatkowej przepustowości (choć zwykle nie tak dużej jak skanowanie) i pamięci, więc nie jest to idealne rozwiązanie, ale dla niektórych realna alternatywa.

Nadal jednak polecam odpowiedź Mike'a Branta jako preferowaną metodę korzystania z DynamoDB; i sam używam tej metody. W moim przypadku mam po prostu tabelę centralną z tylko kluczem mieszającym jako moim identyfikatorem, następnie tabele pomocnicze, które mają skrót i zakres, które można odpytać, a następnie element kieruje kod bezpośrednio do „interesującego elementu” w tabeli centralnej .

Dodatkowe dane dotyczące indeksów wtórnych można znaleźć w dokumentacji Amazon DynamoDB tutaj dla zainteresowanych.

W każdym razie miejmy nadzieję, że pomoże to każdemu, kto wydarzy się w tym wątku.

DGolberg
źródło
Próbowałem utworzyć tabelę DynamoDB, w której był AWSDynamoDBKeySchemaElement 'createdAt' typu hash i ponownie AWSDynamoDBKeySchemaElement 'createdAt' typu range i otrzymałem błąd, który mówił, że Error Domain = com.amazonaws.AWSDynamoDBErrorInfo Code = 0 "(User null Code) = {__ type = com.amazon.coral.validate # ValidationException, message = Zarówno klucz skrótu, jak i element klucza zakresu w schemacie klucza mają tę samą nazwę}. Więc nie sądzę, żeby to, co mówisz, było poprawne.
user1709076
Myślę, że źle zrozumiałeś (chociaż przypuszczam, że mój opis też nie był jasny). Nie możesz mieć 2 różnych atrybutów (kolumn) o tej samej nazwie w tabeli, ale kiedy tworzysz klucz skrótu z kluczem zakresu, możesz mieć wiele elementów, które używają tego samego skrótu, o ile ich zakres jest inny, i wzajemnie. Na przykład: Twój hash to „ID”, a zakres to „Date”, możesz mieć 2 wystąpienia identyfikatora „1234”, o ile ich data jest inna.
DGolberg
Ah DGoldberg! Teraz cię rozumiem. To wspaniale. Więc w moim przypadku, ponieważ tylko i zawsze będę chciał zapytać o wiadomości tekstowe „po dacie = x”. Wygląda na to, że mogę ustawić wszystkie wiadomości tekstowe tak, aby miały ten sam „fake_hash = 1”. Następnie wykonaj moje zapytanie.keyConditionExpression = @ "fake_hash = 1 i #Date>: val". Dziękuję Ci bardzo. Jeśli masz jakieś inne dane wejściowe, z przyjemnością go usłyszę, ponieważ wydaje się dziwne, że hash ma zawsze tę samą wartość?
user1709076
Musiałbym to sprawdzić ponownie, ale jestem prawie pewien, że możesz wykonać zapytanie na tabelach tylko z hashem ... chociaż jeśli używasz znacznika daty / czasu jako swojego skrótu, polecam nagrywanie do najkrótsza możliwa jednostka, taka jak milisekundy lub nano / mikrosekundy (niezależnie od najmniejszej jednostki czasu, jaką może zarejestrować kod), aby zmniejszyć prawdopodobieństwo nakładania się daty / czasu. Dodatkowo możesz dodać optymistyczne blokowanie, aby jeszcze bardziej zmniejszyć możliwość nakładania się: docs.aws.amazon.com/amazondynamodb/latest/developerguide/ ... Po prostu spróbuj ponownie innym razem, jeśli wystąpi konflikt.
DGolberg
-11

Zaktualizowana odpowiedź Nie ma wygodnego sposobu na zrobienie tego za pomocą Dynamo DB Queries z przewidywalną przepustowością. Jedną (nieoptymalną) opcją jest użycie GSI ze sztucznym HashKey & CreatedAt. Następnie zapytaj za pomocą samego HashKey i podaj ScanIndexForward, aby uporządkować wyniki. Jeśli możesz wymyślić naturalny HashKey (powiedz kategorię przedmiotu itp.), Ta metoda jest zwycięzcą. Z drugiej strony, jeśli zachowasz ten sam HashKey dla wszystkich elementów, wpłynie to na przepustowość głównie wtedy, gdy zestaw danych przekroczy 10 GB (jedna partycja)

Oryginalna odpowiedź: Możesz to zrobić teraz w DynamoDB za pomocą GSI. Ustaw pole „CreatedAt” jako GSI i wprowadź zapytania, takie jak (GT some_date). Przechowuj datę jako liczbę (ms od epoki) dla tego rodzaju zapytań.

Szczegóły są dostępne tutaj: Globalne indeksy wtórne - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

To bardzo potężna funkcja. Należy pamiętać, że zapytanie jest ograniczone do (EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN) Warunek - Amazon DynamoDB: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html

Sony Kadavan
źródło
32
Głosowałem w dół, ponieważ o ile wiem, twoja odpowiedź jest nieprawidłowa. Podobnie jak w przypadku klucza podstawowego tabeli, można zapytać o klucz skrótu GSI tylko za pomocą operatora EQ. Jeśli sugerowałeś, że CreatedAtpowinien to być klucz zakresu GSI, musisz wybrać klucz mieszający - i wrócisz do miejsca, w którym zacząłeś, ponieważ będziesz mógł odpytać GT CreatedAttylko o określoną wartość klucz skrótu.
PaF
Uzgodniono z PaF. Używanie GSI z kluczem mieszającym, ponieważ czas utworzenia nie pomaga w pytaniu zadawanym w PO.
4-8-15-16-23-42