SELECT na kolumnach jsonb

0

Hej

Szukam porad dotyczących wydajności operacji SELECT na kolumnach jsonb(postgres).

Postgres 11, ale moge zmienic.
Liczba rekordow w tabeli: 50mln
Liczba kolumn: 250 typu jsonb + 10 typu int uzywanych glownie do optymalizowania wyszukiwania w bazie. Opcjonalnie moge podzielic kolumny np. na 5 tabel po 50 kolumn jsonb przy zachowaniu 50mln rekordow na tabele.
Rozmiar tabeli: 20TB

W kazdym polu kolumny jsonb chce przechowywać dane w opisanym poniżej formacie.

poziom 0 - stringkey1, stringkey2... stringkey5. 4-5 kluczy w kazdym polu. Mozliwe ze ten poziom zostanie wyrzucony, co zmniejszy rozmiar pola kolumn jsonb 3-4 razy.
poziom 1 - liczby od 1 do 100 (niekiedy bedzie ich 10 a niekiedy 85)
poziom 2 - klucze sa takie same dla kazdego poziomu, jest to ok 50 kluczy int/float z max 1 miejscem po kropce. Np: 0.1, 1, 1.4, 2, 2.3, 2.9, 3, 3.2 itd. Kazda kolumna jsonb ma swoj zestaw kluczy.
poziom 3 - klucze sa takie same dla kazdego poziomu i sa to: x, y, z

    {
    "stringkey1": {"1": {
                        "0.1": {
                            "x": 4,
                            "y": 2,
                            "z": 1
                        },
                        "1": {
                            "x": 20,
                            "y": 30,
                            "z": 15
                        },
                        "1.4": {
                            "x": 205,
                            "y": 22,
                            "z": 11
                        },
                        ... 
                    },
                    
                    "2": {
                        "0.1": {
                            "x": 84,
                            "y": 45,
                            "z": 66
                        },
                        "1": {
                            "x": 30,
                            "y": 10,
                            "z": 99
                        },
                        "1.4": {
                            "x": 66,
                            "y": 199,
                            "z": 15
                        },
                        ... 
                    },
                    
                    .....
    },
    "stringkey2": {
        ... ta sama strukture
    }

Chcialbym pobierac wartosci kluczy x, y lub z.
W 1 zapytaniu potrzebuje pobrac 4-10 parametrow z 5-20 kolumn jsonb.
W 1 zapytaniu pobierane sa dane z 2000-5000 rekordow.

Przyklad zapytania:


    SELECT
      
      jsonb_column_1 -> 'stringkey1' -> '1' -> '0.1' ->> 'x',
      jsonb_column_1 -> 'stringkey2' -> '1' -> '1.4' ->> 'z',
      jsonb_column_2 -> 'stringkey2' -> '80' -> '60' ->> 'x',
      jsonb_column_105 -> 'stringkey1' -> '28' -> '20.4' ->> 'y'
      jsonb_column_84 -> 'stringkey4' -> '19' -> '3.9' ->> 'z'
      ... w ten sposob pobieramy 4 do 10 parametrow
    
    WHERE
    
      int_column_1 = 1 AND
      int_column_2 = 100
        

Pytania:

  1. Jak zrobic indexy na danych w kolumnach jsonb dla tej konkretnej struktury? (prosiłbym o realny przykład dla zaprezentowanej struktury)

  2. Czy jesli w warunkach WHERE nie bedzie odwolan do kolumn jsonb to indexy (na kluczach w danych json) maja sens?

  3. Czy warto rozwazac calkowita zmiane struktury bazy?
    Np zamiast kolumn jsonb stworzyc 250 tabel w ktorych kolumny beda mialy nazwy takie jak klucze opisane w poziomie 2 tj. 1,2,3,4,5 ...100 a rekordow bedzie w kazdej tabel > 500mln. Może ktoś ma doświadczenie jak to inaczej zaprojektować aby uzyskać możliwie najlepszą wydajnośc?

Nie posiadam rzetelnych statystyk poniewaz baza jest trakcie projektowania/wypelniania. Testy ktore robilem nie sa miarodajne i nie byly wykonywane na docelowym serwerze. Sprzetowo zakladam serwer/serwery z dyskami nvme, 60+ cores, 512GB ram tak aby spelnic ponizsze wymagania.
Chcialbym moc obslugiwac do 200 rownoleglych zapytan SELECT ktore wykonają się w czasie < 0.5 s.

2

Jeśli ma to być szybkie, to wywaliłbym te jsony i szedł w inną organizację danych.

20TB przy 50M rekordów daje jakieś 500kb per rekord, z czego zwracasz 5k rekordów per zapytanie. 5k*0.5kb => 2.5MB, które baza ma jeszcze parsować.

Możesz napisać coś n/t tych jsonb:

  • Jaki jest max. rozmiar takiego obiektu?
  • Jak te jsonb pojawiają się w bazie?
  • Czy są modyfikowane?
  • Jak długo żyją?
  • Czym są te int_column1/column2 ? Jak się rozkładają wartości.
  • Skąd się bierze te 2k..5k rekordów per zapytanie ? (Dlaczego to jest 5k, a nie 100/200k?)

Może sensowniej byłoby je zamieniać na model relacyjny przy insertowaniu i mieć organizację np.

int_column_1
int_column_2
level0
level1
level2
level3
object_value 

Rozumiem, że możliwych wartości level0,...level3 jest skończona/przewidywalna ilość? Jeśli tak, to możne pójść w kierunku Key-Value.

Zesłownikować kombinacje: level0,level1,level2,level3 -> int

Dane trzymać jako:

int_column_1,
int_column_2,
key_id,
object_value

Mało wygodny model w pisaniu zapytań z palca, ale kto by tam chciał ręcznie klepać, skoro można wygenerować.

0

Jaki jest max. rozmiar takiego obiektu?

Wspomagając się strukturą JSONa z 1 posta.

Analizujac obiekt od elementu najbardziej "na zewnatrz":

-> max 5 tablic "stringkey1"
-> stringkeyX ma w sobie maksymalnie 100 tablic w ktorych klucze sa liczba calkowita od 1 do 100. W 95% przypadkow ta liczba jest <= 50
-> glebiej mamy maksymalnie 50 tablic w ktorych klucze sa liczba lub floatem. Wszystkie klucze sa znane.
-> glebiej mamy juz prosty key->value, max 3 klucze opisane jako x, y, z i one sa liczbami, niekiedy jest to float.

W jednym polu jest wiec maksymalnie 5000 tablic takich jak ta:

"1": {
"x": 20,
"y": 30,
"z": 15
},

To jest zalozenie z bardzo duza rezerwa. Teoretycznie moze byc 5000, ale w praktyce nie znalazlem rekordu majacego > 2000 takich tablic.

Jak te jsonb pojawiają się w bazie?

Dane sa wynikiem przetwarzania innych danych. Na tym etapie moge dowolnie zmodyfikować to jak będą zapisywane.

Czy są modyfikowane?
Nie

Jak długo żyją?
Kilka lat. Codziennie dodawane/usuwane jest 20k rekordow

Czym są te int_column1/column2 ? Jak się rozkładają wartości.

Sa to dane umozliwiajace filtrowanie, wczesniej byly to dane wyzszych poziomow JSONow ktorych schemat podałem w 1 poście. Klucze bedace stringiem zamienilem na liczby. Np 1 to odpowiednik klucza raw_results, 2 - processed_results itd.

Skąd się bierze te 2k..5k rekordów per zapytanie ? (Dlaczego to jest 5k, a nie 100/200k?)

Tyle rekordów zostaje po wyfiltrowaniu.

WHERE 
    int_column_1 = 1  AND
    int_column_2 =6 AND
    int_column_3 = 85 AND
    int_column_4 >= 5 AND int_column_4 <= 10

int_column_1 -> w tej kolumnie moze byc liczba od 1 do 3
int_column_2 -> w tej kolumnie moze byc liczba od 1 do 100
int_column_3 -> w tej kolumnie moze byc liczba od 1 do 100
int_column_4 -> w tej kolumnie moze byc liczba od 1 do 20000

Rozumiem, że możliwych wartości level0,...level3 jest skończona/przewidywalna ilość? Jeśli tak, to możne pójść w kierunku Key-Value.
Tak, ciekawy pomysł którego nie brałem pod uwage. Musze przetestować ile zajmować będzie odwrócenie procesu po pobraniu danych z bazy ponieważ musze je mieć w tablicy mającej określony format aby nie przepisywać innego dobrze zoptymalizowanego kodu.

0
yarel napisał(a):

Rozumiem, że możliwych wartości level0,...level3 jest skończona/przewidywalna ilość? Jeśli tak, to możne pójść w kierunku Key-Value.

Zesłownikować kombinacje: level0,level1,level2,level3 -> int

Dane trzymać jako:

int_column_1,
int_column_2,
key_id,
object_value

Mało wygodny model w pisaniu zapytań z palca, ale kto by tam chciał ręcznie klepać, skoro można wygenerować.

Po szybkiej kalkulacji wychodzi słownik mający 350 mln rekordów. W wyliczeniach uwzględniłem jednak przeniesienie do słownika 3 kolumn.
Nie przenoszac ich moge zmniejszyć ilość kombinacji do: 175, 58 lub 20 mln.
Bliżej której z tych liczb sugerowałbyś utrzymać wielkość takiego słownika?

0
yarel napisał(a):

Jeśli ma to być szybkie, to wywaliłbym te jsony i szedł w inną organizację danych.

A koledzy chcieli dobrze, doradzając JSONy w bazie ...

1

@getstarted: czyli klucze "stringkey1" są znane z góry? I ile ich jest?

Z tego do czytam to skłaniałbym się do takiego modelu wyjściowego, który trzymałby w każdym wierszu (JSONColumnN/sciezka/wartoscXYZ)

int_column_1,
int_column_2,
int_column_3,
int_column_4,
json_column,
level0,
level1,
level2
x,
y,
z

Czyli dla jsonb_column_105 -> 'stringkey1' -> '28' -> '20.4' ->> 'y'

  • json_column=105
  • level0='stringkey1'
  • level1=28
  • level2=20.4
  • x=<wartosc "x">
  • y=<wartosc "y">
  • z=<wartosc "z">

Tę tabelę należałoby partycjonować i utworzyć indeksy per partycja. Po czym, trudno mi ocenić po czym najlepiej, bo trzeba by wiedzieć po czym będą te zapytania faktycznie będą robione, by uzyskać efekt "walę w partycję i tam czytam umiarkowanej wielkości indeks".

Zapytania postaci:

SELECT
      
      jsonb_column_1 -> 'stringkey1' -> '1' -> '0.1' ->> 'x',
      jsonb_column_1 -> 'stringkey2' -> '1' -> '1.4' ->> 'z',
      jsonb_column_2 -> 'stringkey2' -> '80' -> '60' ->> 'x',
      jsonb_column_105 -> 'stringkey1' -> '28' -> '20.4' ->> 'y'
      jsonb_column_84 -> 'stringkey4' -> '19' -> '3.9' ->> 'z'
      ... w ten sposob pobieramy 4 do 10 parametrow
    
    WHERE
    
      int_column_1 = 1 AND
      int_column_2 = 100
      ...

trzeba by zastąpić union all (od 4 do 10) i finalnie zrobić pivot zbioru danych, tak by wynik był jak w formie jak wyżej.

select
 x
from tabelka where 
  int_column_1=1 and int_column_2=100 and int_column_3=100 and int_column4>
  and json_column=1 
  and level0='stringkey1'
  and level1=1
  and level2=0.1  /* jsonb_column_1 -> 'stringkey1' -> 1 -> '0.1' ->> 'x' */
union all 
select
 z
from tabelka where 
  int_column_1=1 and int_column_2=100 and int_column_3=100 and int_column4>
  and json_column=1 
  and level0='stringkey2'
  and level1=1
  and level2=1.4  /* jsonb_column_1 -> 'stringkey1' -> 1 -> '0.1' ->> 'z' */
 ...

Jaki ten postgres? Opensource czy z jakimiś dodatkami typu kompresja danych? Wówczas kosztem CPU miałbyś możliwość lepszego wykorzystania RAM.

Dlaczego te dane chcesz trzymać w bazie?

2

Jeszcze raz przyglądam się liczbom i coś mi się nie zgadza ;-)

  • 50 mln rekordów w tabeli
  • 250 pól typu JSONB
  • w pojedynczym JSONB pesymistycznie siedzi:
    poziom0=5 (stringkey1..5)
    poziom1=100 (kluczy w ramach stringkeyX)
    poziom2=50 (kluczy w ramach poziom1)
    poziom3=3 wartości typu float

Chcąc przechowywać tylko wartości x,y,z z jsonów w formie binarnej, przy max. napakowanych jsonach wychodzi mi:
50 000 000 (rekordów) * 250 (pól jsonb) * 5 (klucze level0) * 100 (klucze level1) * 50 (klucze level2) *3 (klucze level3) * 4 (rozmiar float w bajtach).

3750000000000000 bajtów/1024/1024/1024/1024 -> 3410 TB. Przy takim wolumenie danych indeks będzie duży, przyjmijmy że 10% danych. To daje 340TB. Ciężko wrzucić do cache na jednej maszynce.

Chcesz mieć odpowiedź w <1sek, tzn. że sam indeks powinien być do przeczytania (pesymistycznie zakładam full index scan) w <1 sekundę. Czyli jeśli jeden dysk NVME daje Ci 3500 MB/s, tzn. że do przeczytania 340TB w mniej niż 1 sekundę potrzebujesz by ten indeks był równo rozłożony na dyskach.

Zastanówmy się, 340*1024*1024 MB / 3500MB/s ~ 100 000 dysków NVME... (chyba, że gdzieś się rąbnąłem o rząd wielkości, czy dwa)

Możliwe, że te wartości poziom1=100, poziom2=50 są mocno zawyżone w stosunku do rzeczywistości i jakoś nie pasują mi do wspomnianych 20TB o których pisałeś.

Jeśli jednak nie, to jedna baza danych jest słabym pomysłem i trzeba iść w rozproszony model przetwarzania. Dane rozbite na wiele nodów i każdy przetwarza fragment.

Hasło do google: Massive Parallel Processing.

0

I. Wyjasnienie

Błędem z mojej strony było podawanie maksymalnych możliwych zakresów. W swoim 1 poście uprościłem zagadnienie, ponieważ mocno wierzyłem że moje marne doświadczenia w zarządzaniu indexami na kolumnach jsonb jest tutaj problemem.

Zacznijmy od początku i z większą ilością szczegółów.

Żeby uniknąć posługiwania się specjalistycznymi / technicznymi pojeciami przedstawie projekt w formie projektu medycznego aby nazwy kluczy JSON były powszechnie zrozumiałe. Polskie nazwy parametrow/kluczy mieszaja sie z angielskimi. Jest to celowe i tylko na potrzeby tego wpisu.

II. Dodawanie danych

  1. Do systemu trafiają dane z urządzeń pomiarowych i zachowywane sa w tabeli

  2. Po każdym dniu pomiarowym są przetwarzane i otrzymujemy:

    • patient_id
    • data_wykonania_obliczen
    • 180 kolumn JSONB (wcześniej pisałem o 250 bo zalozylem mozliwość rozbudowy). Nie tworzylem jeszcze tych 180 kolumn tylko dane z wszysktich 180 kolumn wrzucilem do jednej kolumny JSONB, zeby zobaczyc ile zajmuja dane.

data_wykonania_obliczen - to dzien, nie ma znaczenia dokladna godzina. Myslalem aby w celu zwiekszenia wydajnosci daty slownikowac. np 2000-01-01 -> 1.

III. Skąd wzieło mi się 20TB?

1 dnia wpadaja dane 1000 pacjentów (z bazy ponad 25k)
1 rekord opisany powyzej zajmuje srednio 20MB (lekko zawyzone dla bezpieczenstwa)
Dane z 1 dnia zajmuja 20GB
Dane z 1 miesiaca 600 GB
Dane z 1 roku 7TB
Dane z wymaganego okresu zajmuja 21 TB
Jeszcze tutaj doprecyzuje, zalezalo mi na tym aby dane z 1 dnia (wyjasnienie pozniej) pobraly sie z bazy w <0.5s.

IV. Przykladowa struktura JSONB dla pojedynczej kolumny

Aktualizacja: Wcześniej pisałem o 3 kolumnach int uzywanych w klauzuli WHERE zapytan SELECT. Mialem na mysli ze beda nimi 3 pierwsze poziomy ponizszej struktury JSON tj: period->place->activity.

JSONB dla kolumny AKTYWNOSC_KOMOREK_X001:

{
"obecny_okres": { <-- period
                 
            "w_domu": { <-- place
            
                "podczas_aktywnosci": { <-- activity
                    
                        "1": { <-- days - ilosc dni pomiarowych ( dla wartosci 4 -> dane z ostatnich 4 dni pomiarowych) MAX 100. Nie każdy dzien w swiecie rzeczywstym to dzien pomiarowy
                            "0.1": { <-- wartość FLOAT_VALUE - kazda kolumna ma swoj zestaw wartosci FLOAT_VALUE
                                "o": 1, <-- dni powyzej wartosci FLOAT_VALUE
                                "u": 0, <-- dni ponizej wartosci FLOAT_VALUE
                                "e": 0 <-- dni z dokladnie wartosc FLOAT_VALUE
                            },
                            "1": {
                                "o": 1,
                                "u": 0,
                                "e": 0
                            },
                            "1.4": {
                                "o": 0,
                                "u": 1,
                                "e": 0
                            },
                            ... i tak max 60
                        },
                        
                        "2": {
                            "0.1": {
                                "o": 1,
                                "u": 0,
                                "e": 1
                            },
                            "1": {
                                "o": 1,
                                "u": 0,
                                "e": 1
                            },
                            "1.4": {
                                "o": 0,
                                "u": 2,
                                "e": 0
                            },
                            ... i tak max 60
                        },
                
                },  <-- END podczas_aktywnosci
                
                
                "podczas_odpoczynku": {
                     
                    taka sama struktura jak podczas_aktywnosci
                       
                },
                
                "podczas_snu": {
                    taka sama struktura jak podczas_aktywnosci
                },
                
                
            }, <-- END w_domu
            
            
            "poza_domem": {
                taka sama struktura jak w_domu
            },
            
            
            "gdziekolwiek": {
                taka sama struktura jak w_domu
            }, 
            
                .....
},
"wszystkie_okresy": {
    tutaj jest ta sama struktura ktora widnieje w obecny_okres

}

System zaklada istnienie innych wzorcow JSON ale zmiany jesli bedziemy tylko pobierac dane beda malo istotne. Ponizej lista modyfikacji we wzorcach:

  • np. w kolumnie AKTYWNOSC_KOMOREK_AVG001 trzymane sa srednie. Sciezka wiec wyglada tak: AKTYWNOSC_KOMOREK_AVG001 -> period -> place -> activity -> days -> WARTOSC AVG FLOAT.
  • zamiast o/u/e w niektorzych strukturach JSONB moze wystepowac y/n (yes/no)
    INFO: To logiczne, ale dla jasności inny format JSONB moze byc tylko w innej kolumnie w ktorym jest tylko ten inny format.

Pseudo mapa:

PERIOD
    obecny_okres
    wszystkie_okresy
        PLACE
            w_domu
            poza_domem
            gdziekolwiek
                ACTIVITY
                    podczas_aktywnosci
                    podczas_odpoczynku
                    podczas_snu
                        DAYS - MAX 100, w obecny_okres jest ich mniej, ok 30-40. W wszystkie_okresy srednio 55.
                            FLOAT_VALUE - MAX 60. W innych wzorcach JSON moga byc inne wartosci / nazwy kluczy FLOAT_VALUE ale nie bedzie ich wiecej niz 60 w kazdym DAY
                                O
                                U
                                E

period -> place -> activity zawsze mamy takie same. Ewentualnie ktoregos obiektu moze nie byc w jakims polu gdyz np. nie bylo danych.

V. Jak korzystamy z danych.

Dane JSONB uzywane sa do kolejnych operacji lub do filtrowania.

Przyklad jak to powinno funkcjonowac:

  1. z kilku kolumn JSONB pobieramy potrzebne dane dla wybranych pacjentow (patient_id) gdzie "data_wykonania_obliczen" miesci sie w załozonym okresie.

    Komentarz: Chcac miec obraz pacjenta na dzien 2000-01-10 szukamy obliczen ktore byly wykonane najblizej tej daty w przeszlosci, ale nie sa starsze niz np X dni. Jak nie ma nic z X dni to nas taki pacjent nie intresuje. Ilosc rekordow spelniajacych warunek bedzie wynosila 1-2k

  2. przekazujemy dane do workerow ktore cos z tymi danymi robia

    Komentarz: Najczesciej wymaganie jest takie aby przekazac do workerow dane z ostatnich 10-12 miesiecy.
    Myslalem wiec aby przekazywac do workerow dzien po dniu lub jakies wieksze partie, np z calego tygodnia czy miesiaca w zaleznosci od wydajnosci.
    Kolejne elementy systemu musza miec mozliwosc przetwarzania zbiorow danych z okreslonego data_wykonania_obliczen wiec takie przekazywanie danych dzien po dniu ma sens.
    W innym przyadku nalezaloby modyfikowac zapytania i na poziomie bazy danych filtrowac wartosci. Bardziej skomplikowane filtrowanie / operacje na danych chce przerzucic na inne serwery, a baze chce miec tylko do "wydania danych" do workerow.

Najczesciej potrzeba pobrac z jednego pola JSONB 4 wartosci ktorych pierwsze 5 elementow sciezki sa takie same:

AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> o
AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> e
AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> u
AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> e

VI. Przykład z dokładnym opisem tego co zamierzano osiągnąć

SELECT

  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> o
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> e
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> u
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> e
  
    chcemy wiedziec ile razy w okresie 2 dni pomiarowych od daty 2000-01-10 (klauzula WHERE) odnotowano pomiary >(o) lub =(e) od FLOAT_VALUE = 0.1 
    oraz ile razy w okresie 2 dni odnotowano pomiary <(u) lub =(e) od FLOAT_VALUE = 1.4
  
  AKTYWNOSC_KOMOREK_X025 -> obecny_okres -> gdziekolwiek -> podczas_snu -> 10 -> 3 -> o
  AKTYWNOSC_KOMOREK_X025 -> obecny_okres -> gdziekolwiek -> podczas_snu -> 10 -> 8 -> u

    chcemy wiedziec ile razy w okresie 10 dni pomiarowych od daty 2000-01-10 (klauzula WHERE) odnotowano pomiary >(o) od FLOAT_VALUE = 3
    oraz ile razy w okresie 10 dni odnotowano pomiary <(u) od FLOAT_VALUE = 8
    
    
WHERE 

data_wykonania_obliczen = 453 AND (tutaj dacie odpowiada wartosc ze slownika zeby szybciej przetwarzac)
patient_id IN (id,id,id)


ORDER BY data_wykonania_obliczen DESC <-- to zapytanie tego nie potrzebuje ale dodalem aby pamietac ze ta kolumna bedzie przeprowadzac taka operacje

Slownik dat:
453 -> 2000-01-10
460 -> 2000-01-17

Uwagi:

  • robiac SELECT zawsze mamy patient_id(lub array z id), konkretna data_wykonania_obliczen lub zakres

  • w powyzszym zapytaniu oczywiscie trzeba zrobic aby w przypadku WHERE data_wykonania_obliczen >= 453 AND data_wykonania_obliczen <= 460 pobieralo dane pacjenta tylko te najblizsze daty 2000-01-17. Jesli w przedziale bedzie wiecej rekordow (patient_id, data_wykonania_obliczen) to bierzemy tylko ten najnowszy.

  • dane pobierane sa najczesciej z nie wiecej niz 10 roznych kolumn JSONB (np z kolumn AKTYWNOSC_KOMOREK_X001, AKTYWNOSC_KOMOREK_X022, AKTYWNOSC_KOMOREK_X109 ... )

VII. Wczoraj wspominales o slownikowaniu + trzymaniu danych w formacie

int_col
int_col
level1
level2
...
value

I teraz policzmy:

period (2)
place (3)
activity (3)
days (50) - srednia dla obu period
FLOAT_VALUE (300) - jest ich po max 60 na jeden DAY ale w roznych wzorcach JSON moga byc inne wartosic klucza wiec przyjmijmy ze mamy ich roznych 300
o/u/e/y/n (5)
1.35mln rekordow w slowniku o ile czegos nie pominalem

Dla proponowanego formatu przy insercie tylko jednego zestawu danych(pacjent|data_obliczen|wszystkie_powiazania) do tabeli to jak dobrze licze masz 162 tys rekordow.
Dziennie wpada takich zestawow danych 1000 wiec mamy 162mln dziennie, 5mld miesiecznie, 60mld rocznie a musimy przechowyawc 3lata danych. Chyba ze Ciebie zle zrozumialem co do formatu przechowywania danych.

0
yarel napisał(a):

czyli klucze "stringkey1" są znane z góry? I ile ich jest?

Tak, szczegółowo wyjaśniłem to w wiadomości wyżej

yarel napisał(a):

trzeba by wiedzieć po czym będą te zapytania faktycznie robione

To również wyjaśniłem wyżej.

yarel napisał(a):

Jaki ten postgres? Opensource czy z jakimiś dodatkami typu kompresja danych? Wówczas kosztem CPU miałbyś możliwość lepszego wykorzystania RAM.

11 bez dodatkow, mysle ze na tym etapie mógłbym przejść na dowolną inna wersje.

yarel napisał(a):

Dlaczego te dane chcesz trzymać w bazie?

Jestem otwarty na dowolne inne rozwiązanie które zapewni dużą wydajność. Nigdy nie pracowałem z plikami json, z kolumnami jsonb też nie, ale naturalnie kierowałem się ku takiemu rozwiązaniu.

Gdybym poszedł w pliki i podzielił jsony na mniejsze to miałoby to szanse dawać zadowalajace wyniki?
Chetnie to sprawdzę choć mam pytania na które zapewne odpowiedza google i testy. Zadam je tutaj tez bo może masz z tym doświadczenie.

  • czy wraz ze wzrostem ilosci plikow beda coraz wieksze czasy ich przetwarzania? Jest jakaś liczba plików której lepiej nie przekraczać dla określonego sprzetu?
  • czy lepiej probowac dzialac na samych nazwach plikow czy tez sciezki do okreslonych plikow zapisywac w bazie?
  • czy reprezentatywne będzie takie środowisko testowe: 1mln plikow po 200KB = 200GB w 1 katalogu? Jeśli później zrobie 100 takich katalogow to czasy wykonywania pozostaną takie same?
  • czy jeśli bazowym językiem aplikacji jest php to operacje na plikach lepiej byłoby przeprowadzać wywołując skrypt np .py który ma prawdopodobnie lepsze zdolności parsowania plików?
0
yarel napisał(a):

Jeszcze raz przyglądam się liczbom i coś mi się nie zgadza ;-)

  • 50 mln rekordów w tabeli
  • 250 pól typu JSONB
  • w pojedynczym JSONB pesymistycznie siedzi:
    poziom0=5 (stringkey1..5)
    poziom1=100 (kluczy w ramach stringkeyX)
    poziom2=50 (kluczy w ramach poziom1)
    poziom3=3 wartości typu float

Chcąc przechowywać tylko wartości x,y,z z jsonów w formie binarnej, przy max. napakowanych jsonach wychodzi mi:
50 000 000 (rekordów) * 250 (pól jsonb) * 5 (klucze level0) * 100 (klucze level1) * 50 (klucze level2) *3 (klucze level3) * 4 (rozmiar float w bajtach).

3750000000000000 bajtów/1024/1024/1024/1024 -> 3410 TB. Przy takim wolumenie danych indeks będzie duży, przyjmijmy że 10% danych. To daje 340TB. Ciężko wrzucić do cache na jednej maszynce.

Masz racje, narobiłem pierwszym wpisem dużo zamieszania.

3 znaczne zawyżenia liczb były celowe (rozrastanie bazy) ale to nie Ty pomyliłeś się w rzędach wielkości tylko ja. Przed napisaniem wiadomości testowałem różne opcje i pomieszałem liczby.

W poście https://4programmers.net/Forum/Bazy_danych/364438-postgres_select_na_kolumnach_jsonb?p=1875956#id1875956 punkcie III. zawarłem częśc wyliczeń.

Tutaj uzupelnie je bazując na Twoim wyliczeniach przechowywania wartosci x, y, z w formie binarnej. W moich wyliczeniach będzie więcej poziomów, bo w w/w poście przedstawiłem nieco inną strukture JSON.

Podaje uśrednione wartości. W nawiasie podałem też maksymalne(jesli są inne) jakie moga wystąpić w niektórych sytuacjach:

pola json - 200 (max kiedys bedzie 250)
period - 2
place - 3
activity 3
days - 35 (max 100)
float_value - 25 (max 60)
o/u/e (wczesnie x y z ) - 3
no i wartosci - 4

1k rekordow dziennie, 30k rekordow miesiecznie, 360k rekordow rocznie. Dane z 3lat daja 1mln (tyle wystarczy w tej bazie). Tutaj był największy błąd, bo patrzyłem na inną tabele.

1 000 000 (rekordów) * 200 (pól jsonb) * 2 (klucze period) * 3 (klucze place) * 3 (klucze activity) * 35 (klucze days) * 25 (klucze float_value) * 3 (klucze o/u/e wczesniej x/y/z) * 4 (rozmiar float w bajtach)

37 800 000 000 000 bajtow / 1024 / 1024/ 1024 / 1024 -> 34TB
Realnych danych i tak bedzie mniej bo wygenerowalem spora liczbe losowych rekordów. Srednia waga rekordu id | 180 jsonow polaczonych w 1 kolumnie wyniosla ok 20MB.
Dla 1mln rekordow daje to 20TB. Nadmiarowe dane będą archiwizowane w innej bazie i nie będą wymagały szybkich czasów dostępu.

Najbardziej podoba mi się ta opcja z partycjonowaniem o której wspominałes https://4programmers.net/Forum/Bazy_danych/364438-postgres_select_na_kolumnach_jsonb?p=1875820#id1875820

2

Dzięki za to tło biznesowe. Teraz lepiej rozumiem co ma być przechowywane i jak ma wyglądać odpytywanie.

  1. Dla pacjenta zbierane są pomiary wykonane w różnych momentach czasu
  2. Zebrane dane składają się na historię pomiarów
  3. Chcesz znać wartości wybranych pomiarów per pacjent/okres czasu (lub per grupa pacjentów) (najczęściej za ostatni rok)

Wydaje mi się mało prawdopodobne, że to 200 zapytań równoległych będzie dla tych samych klientów, raczej będą to inni klienci, więc efekt używania RAMu jako cache dla bloków bazodanowych może być słaby w przypadku przechowywania danych "wierszami". W takim przypadku chcąc znać tylko tętno pacjenta, wymuszałbyś na bazie odczytanie całego wiersza, w którym będzie masa danych, których nie potrzebujesz.

Sensowniejsze wydaje mi się przechowywanie danych kolumnami per typ pomiaru. Wówczas jeśli zapytanie będzie per grupa pacjentów, będzie szansa na szybsze I/O, bo takie kolumnowe dane będą świeżo w pamięci.

np. "tętno"

25000 pacjentów x 365 dni x 3 lata x 100 pomiarów dziennie x [ 2 (sizeof(short int)) //najmniejszy typ wspierajacy wartosc pomiaru + 4 (sizeof(int)) // id pacjenta ] -> ~16gb

Przypuśćmy, że partycjonujesz po miesiącach -> 36 partycji , 435MB per partycja. 12 takich partycji śmiało wejdzie do RAMu i przy zapytaniach o grupę klientów będzie efekt buforowania I/O.

Jeśli bazy danych to:

  • powinna wspierać columnar storage
  • fajnie gdyby miała kompresję danych (wówczas ta partycja 435MB mogłaby się jeszcze zmniejszyć -> szybsze I/O kosztem CPU)
  • fajnie gdyby wspierała szeregi czasowe

https://db-engines.com/en/ranking/time+series+dbms

Jeśli nie bazy danych, to:

  • przechowywanie danych w formie kolumnowej (Apache Parquet?)
  • do tego jakiś spark sql
0
yarel napisał(a):

Dzięki za to tło biznesowe. Teraz lepiej rozumiem co ma być przechowywane i jak ma wyglądać odpytywanie.

  1. Dla pacjenta zbierane są pomiary wykonane w różnych momentach czasu
  2. Zebrane dane składają się na historię pomiarów
  3. Chcesz znać wartości wybranych pomiarów per pacjent/okres czasu (lub per grupa pacjentów) (najczęściej za ostatni rok)

Dane o których mówimy to nie surowe dane pomiarowe. Ty napsiales o danych opisanych SELECT na kolumnach jsonb w punkcie II. podpunkt 1, a ja mowie o II. podpunkt 2.

Surowe pomiary sa w tabeli pomiary.
Pomiary przetworzone maja byc w tabeli pomiary_przetworzone.

W kolumnach JSON sa przechowywane dane już przetworzone.
Np. AKTYWNOSC_KOMOREK_X001 nie zawiera danych jakiegos konkretnego pomiaru tylko jest wynikiem dzialania funkcji ktore np. z 10 parametrow wejsciowych (tętno, temperatura, saturacja ...) tworzą jakis parametr pomocniczy w tym wypadku AKTYWNOSC_KOMOREK_X001.

Te JSONY sa po to aby w szybki sposob miec dostep do danych potrzebnych w innych miejscach aplikacji.
99% zapytan ma taki cel:

SELECT

  patient_id,

  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> o
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> e
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> u
  AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 1.4 -> e	

  AKTYWNOSC_KOMOREK_X056 -> wszystkie_okresy-> gdziekolwiek -> podczas_aktywnosci -> 10 -> 6.2 -> o
  AKTYWNOSC_KOMOREK_X056 -> wszystkie_okresy-> gdziekolwiek -> podczas_aktywnosci -> 10 -> 10.3 -> u

  AKTYWNOSC_KOMOREK_X119 -> wszystkie_okresy-> poza_domem -> podczas_odpoczynku -> 6 -> 32 -> o
  AKTYWNOSC_KOMOREK_X119 -> wszystkie_okresy-> poza_domem -> podczas_odpoczynku -> 6 -> 32 -> u

  ... i tak w 99% select dotyczy nie wiecej niz 10 roznych kolumn AKTYWNOSC_KOMOREK_XYZ

FROM pomiary_przetworzone

WHERE

  data_wykonania_obliczen = 2000-12-31 AND
  patient_id IN (id, id, id)

I teraz chcemy miec to samo dla 365 kolejnych dat data_wykonania_oblicze(2000-12-30, 2000-12-29, 2000-12-28) tak zeby miec dane np z roku.
Najlepiej jakbysmy to wyciagali pogrupowane po data_wykonania_oblicze poniewaz pozniej workery przetwarzaja dane dniami.

Sens posiadania danych usystematyzowanych w ten sposob. Dla:

data_wykonania_obliczen = 2000-12-31.
AKTYWNOSC_KOMOREK_X056 -> wszystkie_okresy-> gdziekolwiek -> podczas_aktywnosci -> 10 -> 6.2 -> o = 8
AKTYWNOSC_KOMOREK_X056 -> wszystkie_okresy-> gdziekolwiek -> podczas_aktywnosci -> 10 -> 10.3 -> u = 9

Na ostatnich 10 pomiarów parametr AKTYWNOSC_KOMOREK_X056 byl 8 razy powyzej 6.2 i 9 razy ponizej 10.3

Po co nam taka wiedza? Bo np szukalismy pacjentow ktorzy spelniali warunek:
Szukaj pacjentow ktorzy na 10 ostatnich pomiarow mieli pseudoparametr AKTYWNOSC_KOMOREK_X056 w zakresie 6.2 do 10.3.
... + 5 innych warunków z innych kolumn.

Do zapytania nie dodaje WHERE wartosc BETWEEN 6.2 and 10.3 świadomie, poniewaz dane patient_id ktorzy nie spelniaja warunku tez sa potrzebne.

Najbardziej podoba mi się i jest dla mnie najłatwiejszy do wykonania kierunek SELECT na kolumnach jsonb ale chyba przy tej ilości rekordów to jest niemożliwe?

EDIT:

Jak oceniłbyś pomysł w którym w Postgresie robimy taką tabele:

patient_id,
data_wykonania_obliczen, jako INT ze slownika
period
place
activity
days
float_value
parameter_name (o/u/e)
200 kolumn AKTYWNOSC_KOMOREK_XYZ...

Przy założeniu takim jak mówiłem, gdy dziennie insertowanych będzie średnio 1000 zestawow danych do tablicy pomiary_przetworzone. Przeliczając na rekordy byloby to średnio:

2 (period) * 3 (place) * 3 (activity) * 35 (days) * 25 (float_value) * 3(parameter_name) = ok 48k rekordow dla 1 patient_id, 1 data_wykonania_obliczen
Dla 1000 pacjentow dziennie to 48mln rekordow dziennie, 1.44mld miesiecznie, 17.28mld rocznie.

Alternatywnie zrezygnowac z parameter_name i w 200 kolumnach (AKTYWNOSC_KOMOREK_XYZ...) trzymać i pobierac cały json {o/u/e}.
Wowczas bedzie 16mln dziennie, 480mln miesiecznie i ok 5.2mld rocznie

Moglibysmy zmniejszyc wielkosc tabeli do 1 roku. Operacje które muszą być realizowane szybko i tak dotyczą ostatnich 12mies a pozostałe dane miały pełnić funkcje kontrolne, więc mogą być procesowane wolniej, na innym serwerze.

1
  1. Dla problemu wydajnego przetwarzania danych, nie ma znaczenia, czy logicznie dane to są II. podpunkt 1 czy II. podpunkt 2
  2. Będziesz miał fizyczne odczyty z dysków i chodzi o to by je zminimalizować (odczyt z dysku, jest droższy niż sięgnięcie do cache, w którym część pliku już jest).

Organizacja, którą proponujesz będzie miała taki skutek, że dla patient_id dane będą składowane w 1 wierszu, tzn. chcąc sięgnąć do 1 rekordu patient_id to fizycznie baza będzie musiała odczytać wszystkie kolumny AKTYWNOSC_KOMOREK_XYZ... Będzie tak dlatego, że postgres przechowuje dane wierszami.

To o czym Ci piszę, to, że dla Twojego przypadku użycia, o wile lepiej nadaje się przechowywanie kolumnowe. Wówczas, jeśli sięgniesz do AKTYWNOSC_KOMOREK_XYZ_123, to silnik fizycznie będzie odczytywał tylko bloki danych tej kolumny, czyli na starcie organizacja fizyczna daje Ci x200 mniej danych do odczytania...

Zła wiadomość: goły postgres tego (jeszcze?) nie ma.

Dobra wiadomość, są rozwiązania bazujące na postgres, które mają kolumnowe przechowywanie danych, kompresję, rozproszone tabele, skalowalność na wiele nodów.

Doczytaj o "postgresach na streydach":

Mam takie przeczucie, że cała ta warstwa JSONów i agregacji może nie być potrzebna przy pójściu w TimescaleDB i korzystaniu hypertables. Nadal będziesz miał postgres user experience, ale dostaniesz też możliwość agregowania szeregów czasowych i skalowania rozwiązania na wiele nodów.

0

Dzięki za opowiedź.

I. Column Oriented Storage
Czytałem o tym gdy wspomniałeś pierwszy raz i znalazłem https://citusdata.github.io/cstore_fdw/
Będę zgłębiał zagadnienie.

II. Analizowalem użycie formatu Parquet, ale w połączeniu z PHP spada wydajność. Robienie protezy w postaci jakiegoś skryptu wywoływanego z php to na ten moment dla mnie ostateczność.

III. Jestem w trakcie rozpisywania różnych układów tabel oraz analizuje możliwości przebudowy struktury danych z JSONow po tym jak zasugerowałeś spojrzenie per column, a nie per row.
Rozważam jeszcze następujące scenariusze.

  1. Analizuje czy mógłbym wydawać od razu dane dla całego roku. Dane z roku musiałbym pogrupować znacznikiem czasowym data_wykonania_obliczen, czyli:

     patient_id -> AKTYWNOSC_KOMOREK_XYZ_123 -> sciezka(period->activity->days->float_value->o/u/e...)
     	-> data_wykonania_obliczen_1 -> wartosc
     	-> data_wykonania_obliczen_2 -> wartosc
     	-> data_wykonania_obliczen_3 -> wartosc
     	-> data_wykonania_obliczen_4 -> wartosc
     	.... i takich punktow w ciagu roku mamy z 60, moze z 70
    
  2. Zmniejszenie wielkości kolumny JSONB oraz podzielenie danych na tabele.

    opcja 1:
    - w pierwszej tabeli wrzucam: patient_id | period | activity | days | jsonb_row_id
    - w drugiej tabeli: id | 200 kolumn jsonb co nadal jest zlym rozwiazaniem ale ilosc danych na rekord dramatycznie sie zmniejsza

    opcja 2:
    - w pierwszej tabeli wrzucam: patient_id | period | activity | days | jsonb_row_id
    - tworze 200 tabel z nazwami AKTYWNOSC_KOMOREK_XYZ_123: id | jsonbdata

    Gdzies jeszcze musialbym dac data_wykonania_obliczen.
    W obu opcjach pobieram wszystkie jsonb_row_id i pytam o szczegolowe dane w tabeli lub tabelach po id lub po id i data_wykonania_obliczen.

Nie wiem tylko czy przy takim ukladzie cokolwiek zyskam. Czy ten kierunek ma jakikolwiek sens? Czy ostatecznie takie kombinacje porzucić?

IV. O TimescaleDB bede tez czytal wiec teraz sie nie wypowiem.

0
yarel napisał(a):

Doczytaj o "postgresach na streydach":

Mam takie przeczucie, że cała ta warstwa JSONów i agregacji może nie być potrzebna przy pójściu w TimescaleDB i korzystaniu hypertables. Nadal będziesz miał postgres user experience, ale dostaniesz też możliwość agregowania szeregów czasowych i skalowania rozwiązania na wiele nodów.

Poczytałem i to rozwiazanie wydaje sie najlepsze. Porównanie czystego Postgresa do TimescaleDB wygląda rewelacyjnie.
Zastanawiam się dlaczego ich ceny za cloud są tak wysokie w porównaniu z zainstalowaniem tego na własnym serwerze w oparciu o darmową licencje. Nie wiem czy coś przegapiłem czy po prostu kierują to do dużych korporacji które często na takie koszty nie patrzą.

Przeczytałem to co pisałeś w poprzednich wiadomościach o Column Oriented Storage, rozumiem dlaczego tak należy zrobić oraz jakie są tego korzyści. Nie wiem jednak jak miałaby wyglądać taka tabela i jak pytać w niej o dane. Rozumiem że nawet jeśli pójdę w TimescaleDB to nadal mam trzymać się przechowywania kolumnowego.

Jeśli znajdziesz troche czasu to mógłbyś mi doradzić jak miałaby wyglądać struktura tabeli.

Poniżej jeszcze finalne przeliczenie ilosci rekordow bez uwzglednienia przechowywania kolumnowego:

patient_id | data_wykonania_obliczen | data_name(x200) | period (x2) | place (x3) | activity (x3) | days(x35) | float_value(x25) | o | u | e

Ilość rekordów jakie musiałaby przechowywać baza to jeśli dobrze licze:

200 * 2 * 3 * 3 * 35 * 25 => 3.15mln na patient_id

1000 zestawów danych dziennie daje 3mld dziennie, 90mld miesiecznie i ponad 1000 mld rocznie. Troche dużo chociaż przeglądając informacje o technologii widziałem że ktoś na 1 serwerze upchnal 500mld rekordow przy zachowaniu dobrej wydajnosci. Szukam możliwości optymalizacji, istnieje potencjal na 30% redukcje ilosci danych.

Przypomne że przy 1 zapytaniu beda pobierane wartosci dla max 10 roznych data_name (czyli tych AKTYWNOSC_KOMOREK_XYZ).
Za kazdym razem potrzebne beda dane z calego roku grupowane po data_wykonania_obliczen(wyrazona w pelnym dniu).

Tak pobrane dane z calego roku moglbym przekazac do workera i pogrupowac po data_wykonania_obliczen juz w aplikacji. Mozna oczywiscie tez zastosowac ogranicznie, wyciagac miesiacami i te dane miesieczne przekazywac do workerow.

Czyli zapytanie bez ograniczenia czasowego mogloby w uproszczeniu wygladac malo skomplikowanie:

SELECT * FROM tabela
    WHERE
        data_name = :data_name AND
        period = :period AND
        place = :place AND
        activity = :activity AND
        days = :days AND
        float_value = :float_value
0

Po n-tym przeczytaniu tego co napsiałeś i sprawdzeniu kilku benchmarkow być może lepsze od timescaleDB na Postgresie byloby rozwiązanie które sugerowałeś już 2 krotnie, czyli silnik budowany od poczatku pod kolumnowe przechowywanie danych np. ClickHouse. ClickHouse wydaje się wykręcać jeszcze lepsze liczby przy operacjach INSERT, a timescale sami sugerują tego typu baze gdy mamy dużo kolumn.
Nie wiem tylko jak u nich z kompresją.

Pytanie tylko jak miałaby wyglądać tabela?
patient_id | data_wykonania_obliczen | period (x2) | place (x3) | activity (x3) | days(x35) | float_value(x25) | value_type(x3) | 200 kolumn z nazwami parametrow

47k na pacjenta, 47mln/dzien, 1.5mld miesiac, 18mld rok.

I w takim ukladzie pobieralibysmy wszystkie wartosci z 1 z 200 kolumn dla okreslonej sciezki period->place->activity->days->float_value->value_type

Czyli w 1 zapytaniu dostalibysmy dane z roku dla: AKTYWNOSC_KOMOREK_X001 -> obecny_okres -> w_domu -> podczas_snu -> 2 -> 0.1 -> o

Dobrze w końcu rozumiem idee?

0

Przeczytałem to co pisałeś w poprzednich wiadomościach o Column Oriented Storage, rozumiem dlaczego tak należy zrobić oraz jakie są tego korzyści.

Nie jestem pewien jak tę ideę kolumnowego przechowywania danych zrozumiałeś. Można na przynajmniej na 2 poziomach:

  • fizycznym (w jaki sposób dane przechowywane są na storage)
  • logicznym (jak wygląda model danych) - mam wrażenie, że tak to rozumiesz

W przypadku postgresa, z rozszerzeniem do kolumnowego przechowywania danych, tworzenie tabeli różni się detalem, który określa w jaki sposób silnik ma dane zapisywać i odczytywać;

create table my_tab (
a integer,
b integer,
c date,
d date
);

vs

create table my_tab (
a integer,
b integer,
c date,
d date
) USING COLUMNAR;

Zapytanie: select a,b from my_tab będzie generowało różną ilość operacji I/O, w zależności od fizycznej organizacji. Jak masz do odczytania 1 mln par (a,b), to

  • w 1. przypadku będzie to 1 mln * (4 bajty + 4 bajty + 4 bajty + 4 bajty) = 12 mln bajtów
  • w 2. przypadku będzie to 1 mln * (4bajty + 4 bajty) = 8 mln bajtów

(W uproszczeniu, ale powinno to pozwolić zobrazować jaki wpływ na I/O ma organizacja fizyczna.)

Teraz jak odwołujesz się do 5 z 250 jsonów, to odczytanie 1 wiersza oznacza

  • w 1. przypadku - odczytanie 250 kolumn typu json (bo na dysku leżą wierszami i przyjmując np. 1 json ~ 4kb, to daje 250 x 4kb ~ 1MB)
  • w 2. przypadku - silnik nie musi czytać wszystkiego, a jedynie te kolumny, które są potrzebne w zapytaniu ( 5x4kb -> 20kb per wiersz danych)

Teraz odczytujesz np. 1000 wierszy

  • w 1. przypadku 1MB * 1000 ~ 1GB
  • w 2. przypadku 20kb * 1000 -> 20000 kb ~ 20MB

Przy milionie wierszy masz już 1TB vs 20GB. Teraz jak dodasz funkcjonalność rozproszonego postgresa na np. 5 nodów, to każdy czyta 200GB vs 4GB.

Nie wiem jednak jak miałaby wyglądać taka tabela i jak pytać w niej o dane.

W przypadku postgresa + kolumnowego storage, mogłoby to być te 250 kolumn jsonowych, przy tabeli utworzone z wykorzystaniem klauzuli USING COLUMNAR.
Niewielki koszt, a profit I/O ogromny. Do tego partycjonowanie po czasie (również niewielki wysiłek).

Przy przejściu na dedykowany silnik (do szeregów czasowych) trzeba by się zapoznać z jego funkcjonalnościami i przerobić model w stopniu znacznym. Czyli praca mocno koncepcyjna :-)

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