Jak zaprojektować bazę danych do przechowywania posortowanej listy?


42

Chcę przechowywać posortowaną listę w bazie danych. Chcę wydajnie wykonać następujące operacje.

  1. Wstaw (x) - Wstaw rekord x do tabeli
  2. Usuń (x) - Usuń rekord x z tabeli
  3. Przed (x, n) - zwraca rekordy „n” poprzedzające rekord x na posortowanej liście.
  4. Po (x, n) - zwraca rekordy „n” następujące po rekordzie x z posortowanej listy.
  5. Pierwszy (n) - Zwraca pierwsze rekordy „n” z posortowanej listy.
  6. Last (n) - Zwraca ostatnie „n” rekordy z posortowanej listy.
  7. Porównaj (x, y) - Biorąc pod uwagę dwa rekordy xiy z tabeli, sprawdź, czy x> y.

Prostą metodą, o której mógłbym pomyśleć, jest zapisanie w tabeli i zapytaniu jakiegoś atrybutu „ranga” poprzez sortowanie według tego atrybutu. Ale w tej metodzie wstawianie / modyfikowanie rekordu o randze staje się kosztowną operacją. Czy istnieje lepsza metoda?

W szczególności chcę zaimplementować tabelę za pomocą SimpleDB firmy Amazon. Ale ogólna odpowiedź na relacyjną bazę danych również powinna być pomocna.

Aktualizacja profilu obciążenia:

Ponieważ planuję to dla aplikacji internetowej, zależy to od liczby użytkowników korzystających z aplikacji.

Jeśli jest 100 000 aktywnych użytkowników (super optymizm: P), to mój bardzo przybliżony szacunek na dzień

500k wybiera, 100k wstawia i usuwa, 500k aktualizacji

Spodziewałbym się, że stół wzrośnie do 500 tys.

Chcę zoptymalizować operacje aktualizacji, wstawiania i porównywania. Ranga przedmiotów będzie się ciągle zmieniać i muszę aktualizować tabelę.


Opracuj trochę swojego oczekiwanego profilu obciążenia. Ile wyborów / wkładek / aktualizacji dziennie? Dla jakich operacji najbardziej chcesz zoptymalizować? O ile spodziewacie się, że stół będzie się powiększał w ciągu dnia lub w sumie?
— Nick Chammas,

Czy to dla tablicy rankingów graczy? W każdym razie zaktualizowałem moją odpowiedź poniżej, przekazując informacje zwrotne na podstawie twojego prognozowanego profilu obciążenia.
— Nick Chammas,

nie, to nie jest tablica rankingów graczy.
— chitti,

Jakie podejście wykorzystałeś?
— Nick Chammas,

Nie jestem nawet pewien, o co tu pytano ani czego nie trzeba robić z listy rzeczy do zrobienia.
— Evan Carroll

Odpowiedzi:


22

Jeśli ranga nie jest całkowicie arbitralna, ale można ją wyprowadzić z innej własności (np. Imię, wynik gracza itp.), Przyjrzyj się odpowiedzi Joela .

Jeśli jest to dowolna właściwość twoich danych, to powinna być przechowywana jako kolumna w twojej tabeli rekordów. Zakładając, że Amazon SimpleDB jest podobny do typowego RDBMS, możesz następnie zindeksować tę kolumnę i szybko zaspokoić wszystkie powyższe zapytania za pomocą odpowiedniej strategii indeksowania. Jest to normalne w przypadku RDBMS.

Biorąc pod uwagę, że oczekujesz wysokiej aktywności wstawiania i aktualizacji, ale także stosunkowo wysokiej aktywności odczytu, zalecam wykonanie następujących czynności:

  • Zgromadź tabelę na poziomie, szczególnie jeśli zdecydowana większość twoich zapytań jest przeciwna randze. Jeśli nie, lub jeśli wybranie klucza klastrowania nie jest dostępne w SimpleDB, po prostu utwórz indeks z rangą jako wiodącą kolumną. Spełniałoby to zapytania 3-6.
  • Indeks najpierw rekordu, a następnie rangi (lub, w świecie SQL Server, po prostu zapisz i INCLUDE-ing rangę, lub po prostu zapisz, jeśli masz klastrowane rangi) spełniłby zapytanie 7.
  • Operacje 1 i 2 można zoptymalizować, odpowiednio rozdzielając dane (tj. Ustawiając FILLFACTORw SQL Server). Jest to szczególnie ważne, jeśli skupisz się na rankingu.
  • Wstawiając lub aktualizując rangi, zachowaj jak największą lukę między numerami rang, aby zminimalizować możliwość zmiany rangi istniejącego rekordu, aby uwzględnić wstawienie lub aktualizację rang. Na przykład, jeśli uszeregujesz swoje rekordy w krokach co 1000, pozostawisz wystarczająco dużo miejsca na około połowę tylu zmian i wkładek z minimalną szansą, będziesz musiał zmienić pozycję rekordu, który nie jest bezpośrednio zaangażowany w te zmiany.
  • Każdej nocy zmieniaj rangę wszystkich rekordów, aby wyzerować odstępy między nimi.
  • Możesz dostroić częstotliwość masowych ponownych rankingów, a także rozmiar odstępu w rankingu, aby uwzględnić oczekiwaną liczbę wstawek lub aktualizacji w stosunku do liczby istniejących rekordów. Więc jeśli masz 100 000 rekordów i spodziewasz się, że twoje wstawki i aktualizacje będą stanowić 10% tego, zostaw wystarczająco dużo miejsca na 10 000 nowych rang i zmieniaj rangę co noc.
  • Zmiana rankingu rekordów 500 000 jest kosztowną operacją, ale dla takich baz danych taka operacja powinna być odpowiednia raz dziennie lub w tygodniu poza godzinami pracy. To przesunięcie masy poza godzinami pracy, aby utrzymać luki w rankingach, pozwala zaoszczędzić Ci konieczności zmiany rankingu wielu rekordów dla każdej aktualizacji lub wstawienia rankingu w godzinach normalnych i szczytowych.

Jeśli spodziewasz się, że 100K + odczytów na stole o wielkości 100K + nie polecam podejścia z listą połączoną. Nie będzie dobrze skalować do tych rozmiarów.


Rangi można modyfikować. Oczekuję, że szeregi będą się ciągle zmieniać, a nowe rekordy będą ciągle wstawiane. Martwię się o przypadek, w którym wstawię nowy element z rangą, więc należy zmienić szeregi wszystkich rekordów poniżej nowego rekordu w kolejności sortowania. Czy to nie jest kosztowna operacja, gdy mam tysiące rekordów w bazie danych?
— chitti,

@chitti - Ach, to problem. Możesz rozdzielić swoje rankingi (np. 0, 1000, 2000, 3000, ...) i okresowo zmieniać kolejność wszystkich rekordów w miarę zapełniania się luk w rankingu. Nie będzie to jednak skalowane, jeśli spodziewasz się więcej niż kilkudziesięciu tysięcy rekordów.
— Nick Chammas,

1
@chitti - Właściwie to trochę zabawne. Właśnie z tym problemami radzą sobie mechanizmy baz danych podczas indeksowania danych, ponieważ zamawiają je i zmieniają w miarę dodawania lub zmieniania danych. Jeśli spojrzysz w górę FILLFACTOR, zobaczysz, że zasadniczo chodzi o stworzenie dodatkowej przestrzeni na rekordy w indeksie, tak jak opisywane luki rang tworzą przestrzeń dla zmian i wstawiania rang.
— Nick Chammas,

2
Dziękuję za zaktualizowaną odpowiedź. „Pozycja” jest arbitralną własnością moich danych. Jestem prawie przekonany, że potrzebuję niestandardowej kolumny indeksu. Sprawdź ten link SO z podobnym pytaniem. Najlepsza odpowiedź zawiera zalecenia dotyczące sposobu obsługi takiej kolumny rankingu.
— chitti,

@chitti - Akceptowana odpowiedź na to SO pytanie jest świetna. Sugeruje to samo podejście, które tu szczegółowo opisałem, z dodatkową sugestią używania liczb dziesiętnych zamiast liczb całkowitych, aby znacznie zwiększyć elastyczność w przypisywaniu i zmienianiu rang. Świetne znalezisko.
— Nick Chammas,

13

Ogólnie używam opisanej przez ciebie metody „rangi”. Zamiast kłopotać się aktualizowaniem wierszy, gdy trzeba było zmienić kolejność elementów, często mogłem uciec od usunięcia wszystkich rekordów z listy i ponownego wstawienia nowych elementów w odpowiedniej kolejności. Ta metoda jest wyraźnie zoptymalizowana do wyszukiwania.

Alternatywnym podejściem byłoby modelowanie rekordów jako połączonej listy przy użyciu kolumny tabeli klucza zwrotnego „poprzednik” w tabeli:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Możesz łatwo pobrać listę oraz dodawać i usuwać elementy przy niewielkim obciążeniu, ale uporządkowanie rekordów we właściwej kolejności będzie trudne. Być może istnieje sprytny sposób na zrobienie tego w jednym zapytaniu, prawdopodobnie z dużą ilością aliasowanych połączeń tabel.

Tego drugiego podejścia często używam, gdy modeluję relację typu drzewo (kategorie, foldery, zestawy i podzbiory). Generalnie miałem jakąś funkcję rekurencyjną, aby zrekonstruować pełne drzewo w mojej aplikacji.


2
Połączony model listy jest czysty. Aby pobrać taką hierarchię w SQL Server, należy użyć rekurencyjnej CTE .
— Nick Chammas,

Jednak zbudowanie tej hierarchii byłoby dość kosztowne dla wysokiego stołu. Zaletą jest to, że zmiany rang / wstawki / itp. Można łatwo wprowadzić. W zależności od oczekiwanego profilu obciążenia chitti może to być najlepsze podejście.
— Nick Chammas,

Opcja listy połączonej wygląda na najlepszy pomysł dla wszystkich operacji oprócz Porównaj. Masz pomysł, jak zaimplementować Porównaj bez konieczności śledzenia ścieżki między dwoma porównywanymi elementami?
— chitti,

Jeśli masz identyfikatory elementów, myślę, że porównanie () byłoby proste, chyba że źle zrozumiałem, co masz na myśli przez porównanie (). Kiedy powiedziałeś: „znajdź, jeśli x> y” miałeś na myśli „znajdź, czy x poprzedza y”? Nie widzę w tym łatwości bez niestandardowego indeksu lub procedury składowanej, która przechodziłaby listę (lub tej ciekawej funkcji CTE wspomnianej przez @Nick).
— bpanulla,

5
Ten typ rozwiązania jest również zbliżony do modelu danych grafowych ( en.wikipedia.org/wiki/Graph_theory ). System pamięci zoptymalizowany do przechowywania węzłów graficznych i krawędzi może być lepszym rozwiązaniem niż RDBMS. Sklepy z potrójnymi i poczwórnymi bazami danych i wykresami, takie jak Neo4J, są w tym całkiem dobre.
— bpanulla

6

Myślę, że należy przechowywać właściwość lub właściwości używane do obliczenia rangi, a następnie zbudować nad nimi indeks. Zamiast próbować zmusić bazę danych do fizycznego przechowywania danych w uporządkowanej kolejności lub za pomocą ręcznie zarządzanej połączonej listy, dlaczego nie pozwolić silnikowi bazy danych robić to, do czego został przeznaczony?


2
Co jeśli „właściwości używane do obliczania rangi” są arbitralne? Np .: Zestaw pozycji koszyka na zakupy, których kolejność zmienia się na podstawie dowolnych działań użytkownika.
— chitti

Kiedy mówisz, że ranga jest dowolna, co masz na myśli? Musi istnieć algorytm używany do obliczania rangi. Na przykład: „na podstawie wpisów w koszyku” - w jaki sposób? W bazie danych musi znajdować się coś, co jest sterownikiem do obliczania rangi. Może to być kombinacja kilku rzeczy, ale te rzeczy muszą jakoś być przechowywane w tabeli klienta lub w tabelach związanych z klientem. Jeśli jest w danych, możesz utworzyć funkcję, która je oblicza. Jeśli możesz to obliczyć, możesz go zapisać i zindeksować.
— Joel Brown,

Powiedzmy, że musimy utrzymać kolejność produktów w koszyku, a zamówienie może zostać „arbitralnie” zmienione przez użytkownika za pomocą interfejsu internetowego. Jak przechowujesz taką listę elementów w bazie danych i jak utrzymasz porządek sortowania?
— chitti,

Jeśli dobrze cię rozumiem, „arbitralnie zmieniając” kolejność elementów w koszyku masz na myśli, że użytkownik może przeciągać elementy w górę i w dół listy i upuszczać je tam, gdzie chcą. Wydaje mi się, że to trochę wymyślone. Dlaczego użytkownicy mieliby to robić? Gdyby mogli to zrobić, czy zrobiliby to dużo? Czy używanie prostej sekwencji elementów w koszyku tak naprawdę wiąże się z poważnymi problemami z wydajnością? Wydaje mi się, że kolejny numer od jednego do liczby przedmiotów w koszyku + FK do zamówienia dałby potrzebny indeks. Po prostu zaktualizuj przedmioty, gdy ktoś zostanie przeciągnięty.
— Joel Brown

3
Koszyk jest tylko przykładem, który podałem, aby pokazać, że istnieją przypadki, w których „pozycja” może być dowolna. Być może nie był to świetny przykład. Lepszym przykładem może być kolejka DVD Netflix. Dla samej argumentacji wyobraź sobie kolejkę z serwisem Netflix zawierającą 100 tys. Elementów, które użytkownik może dowolnie zmienić kolejność i robi to co minutę. Jak zaprojektowałbyś bazę danych do przechowywania uporządkowanej listy filmów w tej hipotetycznej aplikacji?
— chitti,

1

Są to ograniczenia nieobsługiwane przez RDBMS, takie jak simpleDB. Wymaganych funkcji nie można zaimplementować po stronie DB w simpleDB, należy je zaimplementować od strony programowania / aplikacji.

W przypadku RDBMS SQL serverwymagane funkcje są podstawowe dla indeksu klastrowego.

  • Wstaw (x) - Wstaw rekord x do tabeli> Prosta wstawka.
  • Usuń (x) - Usuń rekord x z tabeli> Proste usuwanie.
  • Przed (x, n) - zwraca rekordy „n” poprzedzające rekord x na posortowanej liście. > Wybierz najlepsze n wyników, gdzie x mniej niż wartość i uporządkuj według klauzuli.

  • Po (x, n) - zwraca rekordy „n” następujące po rekordzie x z posortowanej listy. > Wybierz najlepsze n wyników, gdzie x jest większe od wartości i uporządkuj według klauzuli.

  • Pierwszy (n) - Zwraca pierwsze rekordy „n” z posortowanej listy. > Wybierz najlepsze n wyników.

  • Last (n) - Zwraca ostatnie „n” rekordy z posortowanej listy. > Wybierz najlepsze n wyników po zamówieniu według opisu.

  • Porównaj (x, y) - Biorąc pod uwagę dwa rekordy xiy z tabeli, sprawdź, czy x> y. > Instrukcja IFSQL.

SimpleDB zapewnia automatyczne indeksowanie, sortowanie i podstawowy język zapytań . Mój problem pozostanie, nawet jeśli wybiorę RDBMS. Problem polega na tym, że ranking danych w mojej bazie danych zmienia się arbitralnie i nie można ich uchwycić jako pojedynczej właściwości (chyba że użyję niestandardowej kolumny rankingu), którą można zindeksować.
— chitti

0

Oto, czego użyłem, aby zmienić ranking mojej tabeli Postgres po każdej wstawce:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

W moim przypadku użycia wydajność nie jest problemem, ale pewność, że nigdy się nie złamie lub nie zadziała, jest ważna.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.