Baza danych komunikatora (cz.2): sekcjonujemy „na żywo”

Sukcesywnie zaprojektowaliśmy strukturę naszej bazy PostgreSQL do przechowywania korespondencji, minął rok, użytkownicy intensywnie ją zapełniają, a w niej już miliony rekordów, i... coś zaczęło zwalniać.

Baza danych komunikatora (cz.2): sekcjonujemy „na żywo”
Chodzi o to, że wraz ze wzrostem objętości tabeli rośnie także "głębokość" indeksów — choć w sposób logarytmiczny. Ale z czasem zmusza to serwer do przetwarzania znacznie większej ilości stron danych niż na początku.Tu z pomocą przychodzi

sekcjonowanie Zaznaczam, że mowa będzie nie o sharding, czyli rozdzielaniu danych między różne bazy lub serwery. Bo nawet, jeśli podzielisz dane na.

kilka serwerów, nie pozbędziesz się problemu "rozrostu" indeksów z czasem. Oczywiste jest, że jeśli możesz sobie pozwolić na codzienne wprowadzanie nowego serwera, to twoje problemy będą leżeć w zupełnie innej płaszczyźnie niż konkretna baza danych. Nie rozważymy jednak konkretnych skryptów do realizacji sekcjonowania "w sprzęcie", ale samą koncepcję — co i jak należy "pokroić na kawałki", i do czego takie pragnienie prowadzi.

Koncepcja

Ponownie określmy nasz cel: chcemy, aby zarówno dziś, jak i jutro, oraz za rok liczba odczytywanych danych PostgreSQL przy każdej operacji odczytu/zapisu pozostała mniej więcej taka sama.

Dla każdego

danych gromadzonych chronologicznie (wiadomości, dokumenty, logi, archiwa, …) naturalnym wyborem jako klucz do sekcjonowania jest data/godzina zdarzenia . W naszym przypadku takim zdarzeniem jestmoment wysłania wiadomości Zauważmy, że użytkownicy prawie zawsze.

pracują tylko z "najnowszymi" danymi — czytają ostatnie wiadomości, analizują ostatnie logi,… Nie, oczywiście, mogą przewijać wstecz w czasie, tylko robią to bardzo rzadko. Z tych ograniczeń staje się jasne, że optymalnym rozwiązaniem dla wiadomości będą

"codzienne" sekcje — bo niemal zawsze nasz użytkownik będzie czytać to, co dotarło do niego "dzisiaj" lub "wczoraj". Jeśli przez dzień piszemy i czytamy praktycznie tylko w jednej sekcji, to daje nam to także

bardziej efektywne wykorzystanie pamięci i dysku. bardziej efektywne wykorzystanie pamięci i dysku — ponieważ wszystkie indeksy sekcji mieszczą się łatwo w pamięci operacyjnej, w przeciwieństwie do „dużych i grubych” w całej tabeli.

krok po kroku

Generalnie, wszystko, co zostało powiedziane powyżej, brzmi jak jeden wielki zysk. I jest osiągalne, ale żeby to zrealizować, musimy się dobrze postarać — ponieważ decyzja o podziale jednej z encji prowadzi do konieczności 'cięcia' również powiązanych z nią.

Wiadomość, jej właściwości i projekcje

Skoro zdecydowaliśmy się na dzielenie wiadomości według dat, to również powiązane z nimi encje-właściwości (załączniki, lista odbiorców) są sensowne do podziału, a także według daty wiadomości..

Ponieważ jednym z typowych naszych zadań jest przegląd rejestrów wiadomości (nieprzeczytane, przychodzące, wszystkie), to również sensownie jest je 'wciągnąć' w podział według dat wiadomości.

Baza danych komunikatora (cz.2): sekcjonujemy „na żywo”

Dodajemy klucz podziału (datę wiadomości) do wszystkich tabel: odbiorcy, plik, rejestry. Do samej wiadomości nie trzeba go dodawać, można użyć istniejącego DataCzas.

Tematy

Ponieważ temat jest jeden dla wielu wiadomości, nie można go 'pokroić' w tym samym modelu, musimy opierać się na czymś innym. W naszym przypadku idealnie nadaje się data pierwszej wiadomości w korespondencji. — czyli moment utworzenia, samego tematu.

Baza danych komunikatora (cz.2): sekcjonujemy „na żywo”

Dodajemy klucz podziału (datę tematu) do wszystkich tabel: temat, uczestnik.

Jednak teraz napotykamy od razu dwa problemy:

  • w której sekcji szukać wiadomości po temacie?
  • w której sekcji szukać tematu wiadomości?

Oczywiście można kontynuować szukanie we wszystkich sekcjach, ale to będzie bardzo smutne i zniweczy wszystkie nasze zyski. Dlatego, aby wiedzieć, gdzie dokładnie szukać, zrobimy logiczne linki/wskaźniki na sekcje:

  • dodajemy do wiadomości pole z datą tematu,
  • do tematu dodajemy zbiór dat wiadomości tej korespondencji (można osobną tabelą, a można również tablicą dat)

Baza danych komunikatora (cz.2): sekcjonujemy „na żywo”

Ponieważ modyfikacji listy dat wiadomości dla każdej konkretnej korespondencji nie będzie wiele (prawie wszystkie wiadomości wpadają w 1-2 sąsiednie dni), zatrzymam się właśnie na takim wariancie.

Podsumowując, struktura naszej bazy przyjęła następujący kształt z uwzględnieniem podziału:

Tabele: RU, przy odrzuceniu cyrylicy w nazwach tabel/pól lepiej nie patrzeć.

-- sekcje według daty wiadomości
CREATE TABLE "Wiadomość_YYYYMMDD"(
  "Wiadomość"
    uuid
      PRIMARY KEY
, "Temat"
    uuid
, "DataTematu"
    date
, "Autor"
    uuid
, "DataCzas" -- używamy jako daty
    timestamp
, "Tekst"
    text
);

CREATE TABLE "Adresat_YYYYMMDD"(
  "DataWiadomości"
    date
, "Wiadomość"
    uuid
, "Osoba"
    uuid
, PRIMARY KEY("Wiadomość", "Osoba")
);

CREATE TABLE "Plik_YYYYMMDD"(
  "DataWiadomości"
    date
, "Plik"
    uuid
      PRIMARY KEY
, "Wiadomość"
    uuid
, "BLOB"
    uuid
, "Nazwa"
    text
);

CREATE TABLE "RejestrWiadomości_YYYYMMDD"(
  "DataWiadomości"
    date
, "Właściciel"
    uuid
, "TypRejestru"
    smallint
, "DataCzas"
    timestamp
, "Wiadomość"
    uuid
, PRIMARY KEY("Właściciel", "TypRejestru", "Wiadomość")
);
CREATE INDEX ON "RejestrWiadomości_YYYYMMDD"("Właściciel", "TypRejestru", "DataCzas" DESC);

-- sekcje według daty tematu
CREATE TABLE "Temat_YYYYMMDD"(
  "DataTematu"
    date
, "Temat"
    uuid
      PRIMARY KEY
, "Dokument"
    uuid
, "Nazwa"
    text
);

CREATE TABLE "UczestnikTematu_YYYYMMDD"(
  "DataTematu"
    date
, "Temat"
    uuid
, "Osoba"
    uuid
, PRIMARY KEY("Temat", "Osoba")
);

CREATE TABLE "DatyWiadomościTematu_YYYYMMDD"(
  "DataTematu"
    date
, "Temat"
    uuid
      PRIMARY KEY
, "Data"
    date
);

Oszczędzamy grosz

A jeśli nie używamy klasycznej metody partycjonowania na podstawie rozkładu wartości pola (przez wyzwalacze i dziedziczenie lub PARTITION BY), ale „ręcznie” na poziomie aplikacji, można zauważyć, że wartość klucza partycjonowania jest już przechowywana w nazwie samej tabeli.

Dlatego jeśli tak bardzo martwisz się o objętość przechowywanych danych, to można zrezygnować z tych „zbędnych” pól i zwracać się bezpośrednio do konkretnych tabel. Prawda, że wszystkie zapytania z wielu sekcji w tym przypadku należy już zrealizować po stronie aplikacji.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster