Jak to zrobić najszybciej? Wyszukiwanie i obliczenia na dużej ilości rekordów.

0

Chciałbym poprosić o sugestie / pomysły jak można podejść do poniższego problemu. Chodzi o sugestie narzędzi, rozwiązań, algorytmów... Cokolwiek.
Problem jest "ogólnie łatwy" ale złożony obliczeniowo, a zależy mi na maksymalnej optymalizacji czasowej wykonywania tych obliczeń.

Załóżmy, że mamy przykładową tabelę TABLE o strukturze ( wszystkie pola to integer ):
screenshot-20220119234831.png

Z tabeli chcemy wyciągnąć pewne dane spełniające warunki wynikające z zapytania ( niekoniecznie zapytania SQL ):
Niech przykładową strukturę zapytania określa kilka poniższych zmiennych ( pseudokod PHP ):

  $D1 = 100 ( wartości od 0 to 10000 )
  $D2 = 250 ( wartości od 0 to 10000 )
  $PL = array[ 1, 3, 5, 7 ] // w tablicy może być od 1 do 56 elementów o wartościach 0 - 100
  $sumFrom = 2000 ( wartości od 0 to 1000000 )
  $sumTo = 5000 ( wartości od 0 to 1000000 )

Na potrzeby przykładu załóżmy, też że przykładowa tabela to klasyczna tabela w bazie SQL:
Aby uzyskać pożądany wynik najpierw wybieramy rekordy spełniające łatwiejszą część warunku:

Select * from TABLE whwre  D1 >= 100 and D2 <= 250 and CN in ( 1, 3, 5, 7 )

W wyniku wykonania zapytania pozostaną nam dwa rekordy o id=1,2
Teraz dla każdego z rekordów, które spełniają powyższy warunek musimy wykonać dodatkowe obliczenia, których wynik uzależniony jest od zawartości tablicy $PL i pewnej zależności wartości tablicy z kolumną TABLE.KA.

Te obliczenia mogą wyglądać np. tak:

function calcRow( $PL, $sumFrom, $sumTo, $tableRow ){

  if ( count ( $PL ) > tableRow.RA ) return -1 ;
  if ( count ( $PL ) <= tableRow.RA ) {
    $sum = 0 ;
    foreach ( $PL as item ){
      if ( item < tableRow.KA )
        $sum = $sum + tableRow.CA1 
      else
        $sum = $sum + tableRow.CC2
    }
  }
  if ( $sum < $sumFrom ) || ( $sum > $sumTo ) return -1 ;
  return ( $sum );
}

Gdzie leży problem? No kilka jest...

  1. W praktyce kolumn jest około 50 a baza ma 500 000 000 rekordów.
  2. Odpowiedzi na zadane pytania musimy uzyskiwać w czasie poniżej 100ms.
  3. Baza jest modyfikowana "na bieżąco" rekordy dochodzą, są modyfikowane lub kasowane.

Generalnie problem mam rozwiązany na 3 sposoby:
0. Wszystko na MySQL wyszukiwane SQL z funkcją składowaną. Działało to strasznie...

  1. Mix SQL + PHP ale to dział sensownie jedynie do 1000 000 rekordów ( nieco lepiej niż czysty SQL );
  2. Aplikacja (skompilowanwa), w której dane siedzą w RAM, wszystkie dane są poindeksowane posortowane itp..., a same obliczenia są liczone na bieżąco. Na 100 000 000 rekordów działa to fajnie i mieści się w wymaganych czasach ale pożera ponad 30GB RAM i jest czasochłonny problem z aktualizacją danych bo trzeba aktualizować indeksy i wykonywać ponowne sortowania.

Każdy ma jednak swoje wady. Jak można to zrobić inaczej?

1
  1. Spróbuj elasticsearch, brzmi jak Twoje rozwiązanie z pkt. 2, jednak elastic podejrzewam, że zrobi to lepiej. Możesz zrobić na nim kilka nodów jak danych faktycznie jest dużo i muszą być szybko, nie musi być to jeden serwer z 30GB RAM bo takie chyba dużo kosztują. Dodatkowo elastic będzie odkładał w pamięci tylko te zapytania które są często powtarzane.

  2. Możesz też wrzucić te dane do jakiegoś DB cloudowego np. bigquery google - ale to sporo kosztuje, jednak jak dobrze wszystko poindeksujesz/podzielisz na partycje to może w 100ms się ogarnie (on też będzie to odpalał na kilku nodach).

Co to za baza, że ma tyle kolumn i rekordów? Skąd to wymaganie dot. 100 ms?

1

Możesz trzymać dane w cache typu Ignite i bazie danych jednocześnie, ewentualnie użyć jakiejś time series db typu KDB. Ogólnie MySQL i inne bazy oparte o wiersze są dosyć średnie do przetwarzania dużych ilości danych, lepiej poszukaj czegoś kolumnowego. Idealnie by było gdybyś dane trzymał np. w Parquet i korzystał z klastra Sparka do tego, wtedy przy odpowiedniej konfiguracji pewnie dałoby się zejść do sekund.

0
Markuz napisał(a):
  1. Spróbuj elasticsearch, brzmi jak Twoje rozwiązanie z pkt. 2, jednak elastic podejrzewam, że zrobi to lepiej. Możesz zrobić na nim kilka nodów jak danych faktycznie jest dużo i muszą być szybko, nie musi być to jeden serwer z 30GB RAM bo takie chyba dużo kosztują. Dodatkowo elastic będzie odkładał w pamięci tylko te zapytania które są często powtarzane.

Już kiedyś próbowałem go ugryźć ale do innych celów ( do analizy statystyk ruchu na serwerach ) i faktycznie nie zawiódł.
Pytanie czy takie ilości danych ktoś tam obrabiał?

  1. Możesz też wrzucić te dane do jakiegoś DB cloudowego np. bigquery google - ale to sporo kosztuje, jednak jak dobrze wszystko poindeksujesz/podzielisz na partycje to może w 100ms się ogarnie (on też będzie to odpalał na kilku nodach).

Tablice już partycjonowałem ale w ramach jednego serwera - przyniosło to korzyść ale to nadal przepaść do opcji z trzymaniem wszystkiego w RAM.

Co to za baza, że ma tyle kolumn i rekordów? Skąd to wymaganie dot. 100 ms?

0

Taka luźna uwaga, a może Redis?
https://redis.com/

1

Widzę 2 podejścia:

  1. Klaster do analityki - np. https://greenplum.org/, columnar store + partitioning i powinno spokojnie dać radę. W jednym z firmowych produktów mamy greenpluma i daje radę. U jednego z klientów przetwarza ruch generowany przez 15M użytkowników (obsługuje ~1 miliard rekordów/dzień - analityka real time na danych z urządzeń sieciowych, aplikacji operatora typu CRM, rekordy zdarzeń - połączenia, płatności itd.).

  2. Rozwiązanie szyte na miarę - tu podstawowy problem to skalowalność (w końcu miejsce na kości RAM w serwerze sie skończy i co wtedy?), ale może masz jakieś ograniczenia w postaci ilości rekordów ("nigdy" nie będzie więcej niż N, albo dane się da odsepartować i przetwarzać niezależnie w tym sensie, że dotyczą różnych klientów/obszarów i zawsze takie grupy są przetwarzane osobno), i drugi problem elastyczność ( czy struktura przetwarzania będzie ta sama? tzn. zawsze to samo zapytania z dokładnością do parametrów).

Jeśli struktura danych jest stabilna i problem też stabilny, to można szukać optymalizacji "na miarę". W takim podejściu starałbym się dane trzymać w pamięci kolumnowo (nie wszystkich kolumn potrzebujesz, więc po co generować page faulty dla przechowywania wierszami?)

Jesli potrzebujesz elastyczności, to nie szedłbym w customa, bo stworzysz coś w stylu klastra do analityki :-)

1
  1. Oddzieliłbym problem wyszukiwania od aktualizacji. Być może nawet fizycznie - osobne tabele.
  2. Dlaczego między rekordami są zależności 1 do wielu w jednej tabeli? Może warto to rozplątać?
  3. Ogólnie proces wygląda na bardzo podobny do patternu "wszystkomający arkusz" w którym w jednym arkuszu jakiś ekspert zawarł wszystkie obliczenia, przez co jemu jest wygodnie ale w pewnym momencie kończy się miejsce i inni ni w ząb nie rozumieją o co chodzi. Proponowałbym rozpisać proces na jakiś sensowny algorytm i może spróbować podzielić dane - raczej na fazy obliczeń niż na partycje.
0

Myślę że pierw problem należy rozwiązać na kartce, dokładnie przyjrzeć się danym, zapytaniom, i obliczeniom, w kompletnym oderwaniu od technologii. Przy 0,5 mld rekordów i czasach < 0,1s to obawiam się, że już będzie trzeba trochę pomyśleć, a nie liczyć że jakieś narzędzie w magiczny sposób rozwiążę problem za nas. Trzeba pomyśleć jak osiągnąć najniższą złożoność obliczeniową przy użyciu standardowych metod stosowanych w przetwarzaniu danych: dzielenia danych pionowo, poziomo, mamy mnóstwo indeksów do wyboru, możemy keszować wyniki pod problemów albo obliczać z wyprzedzeniem a nie dopiero w momencie żądania, zrównoleglać poszczególne części procesu. Jak już będziemy mieli ten nasz problem teoretycznie rozwiązany, to dopiero można zacząć się rozglądać za narzędziami które oferują to co potrzebujemy.

Wiem że to zbiór ogólników, ale ciężko napisać coś konkretnego jeśli nie zna się dokładnie problemu, a mam przeczucie że ten problem będzie wymagał bardzo customowego podejścia i żadne magiczne narzędzie nie pomoże, bez zastanowienia się jak teoretycznie najbardziej optymalnie przetwarzać te dane.

4
  1. Partycjonowanie, szczególnie te po tym PL których jest względnie mało, i pracowanie równolegle w każdej partycji.
  2. Żeby mieć czasy rzędu 100ms to nie ma innej drogi niż wszystko w RAMie a 30GB to jest nic. Mój 5-letni laptop ma więcej.
  3. Eventual consistency / CQRS -> nie rób updatów na tych samych danych gdzie szukasz, bo jakieś locki i inne duperele cię zjedzą. Zamiast tego lepiej mieć jakiś niezmienny model w którym szukasz i masz osobno model w którym asynchronicznie ogarniasz updaty i co jakiś czas "podmieniasz" model z którego czytasz (jakimś AtomicReference np.). W efekcie updaty nie wplywają w żaden sposób na szukanie kosztem np. opóźnienia o 30 czy 60 sekund (czy co ile będziesz podmieniać model) kiedy updaty stają się widoczne
  4. Obawiam się że cięzko będzie znaleźć jakąś gotową magiczną bazę która rozwiąże twój problem.
1

Jeśli możesz sobie pozwolić na jakiś margines błędu (eventual consistency), to może rozwiązania typu lambda/kappa architecture - wtedy 99% rzeczy masz już przeliczone offline. W sumie, gdybys asynchronicznie zasilał Elastica (rozpiętego na wielu shardach), to tez querasy byłyby szybkie.

A może rozwiązanie typu Apache Druid - tutaj absolutnie nie jestem ekspertem. Używaliśmy do analizy clickstreamów - tutaj problem wyglada podobnie z powodu dużego napływu danych.

0
katakrowa napisał(a):

( wszystkie pola to integer ):

Wcale nie, bo:

$D1 = 100 ( wartości od 0 to 10000 )
$D2 = 250 ( wartości od 0 to 10000 )

to short jest (uzycie odpowiednich typow spowoduje szybsze czytanie z dysku i mniejsze zapotrzebowanie na ram)

$PL = array[ 1, 3, 5, 7 ] // w tablicy może być od 1 do 56 elementów o wartościach 0 - 100

a to byte

$sumFrom = 2000 ( wartości od 0 to 1000000 )
$sumTo = 5000 ( wartości od 0 to 1000000 )

No ok, to juz int. Sumy latwo updatowac na zywo. Masz taka mozliwosc? Ale do zakresu inta sporo brakuje. Mozna by kilka pol polaczyc bitowo. Tyle ze rozlaczenie tez kosztuje. Przed optymalizacja warto by wlaczyc profiler i sie dowiedziec czy trzeba optymalizowac obliczenia, czytanie z dysku, a moze np. zdecydowanie za duzo przez siec idzie.
Za to pozostale wartosci na obrazku to maksymalnie short. A moze dane maja jakies wlasciowosci, ktore pozwola je przyciac do byte (np. CA1, CC1 z obrazka sa wielokrotnoscia 10 i < 2560).

Na 100 000 000 rekordów działa to fajnie i mieści się w wymaganych czasach ale pożera ponad 30GB RAM i jest czasochłonny problem z aktualizacją danych bo trzeba aktualizować indeksy i wykonywać ponowne sortowania.

Bez znajomosci danych ciezko zgadnac ale, szczegolnie jesli indeksowane sa glownie shorty, moze da sie wrzucic do tablicy (bez wzgledu na to czy ram czy baza) i dodac jeden bit oznaczajacy czy pole jest zapelnione.

0

A próbowałeś zwykłym in memory column store? http://shrenikp.blogspot.com/2018/05/oracle-in-memory-column-store-im-column.html
https://docs.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-overview?view=sql-server-ver15
500M wierszy nie wydaje się zbyt dużo, mieliśmy tabelę na column store z kilkoma miliardami wierszy (fact table) i działało to błyskawicznie na podobnych zapytaniach (do kostki OLAP)

1 użytkowników online, w tym zalogowanych: 0, gości: 1