DB des Messengers (Teil 2): 'live' partitionieren

Wir haben die Struktur unserer PostgreSQL-Datenbank zum Speichern von Korrespondenz erfolgreich entworfen. Ein Jahr ist vergangen, und die Benutzer fĂŒllen sie aktiv – inzwischen gibt es Millionen von EintrĂ€gen, und
 alles hat angefangen, ein wenig zu stocken.

DB des Messengers (Teil 2): 'live' partitionieren
Das Problem ist, dass Mit dem Wachstum der TabellengrĂ¶ĂŸe wĂ€chst auch die „Tiefe“ der Indizes – auch wenn nur logarithmisch. Aber mit der Zeit zwingt es den Server dazu, fĂŒr die gleichen Lese-/Schreibaufgaben deutlich mehr DatenblĂ€tter zu verarbeiten, als zu Beginn.

Hier kommt die Partitionierung.

in Hilfe, wobei ich anmerken möchte, dass es nicht um Sharding geht, also um die Verteilung von Daten auf verschiedene Datenbanken oder Server. Denn selbst wenn Sie die Daten auf mehrere Server aufteilen, werden Sie das Problem der „AufblĂ€hung“ der Indizes im Laufe der Zeit nicht los. Es ist klar, dass, wenn Sie sich tĂ€glich einen neuen Server leisten können, Ihre Probleme bereits auf einer ganz anderen Ebene liegen werden.

Wir werden nicht spezifische Skripte fĂŒr die Implementierung der Partitionierung „in der Hardware“ betrachten, sondern den Ansatz selbst – was und wie man „in Scheiben schneiden“ sollte und worauf dieses Verlangen hinauslĂ€uft.

Konzept

Lassen Sie uns unser Ziel noch einmal definieren: Wir wollen sicherstellen, dass die Menge der gelesenen PostgreSQL-Daten bei jeder Lese-/Schreiboperation heute, morgen und in einem Jahr ungefÀhr gleich bleibt.

FĂŒr alle chronologisch gesammelten Daten (Nachrichten, Dokumente, Protokolle, Archive, 
) ist das natĂŒrliche Wahlkriterium fĂŒr die Partitionierung das Datum/Uhrzeit des Ereignisses. In unserem Fall ist ein solches Ereignis der Zeitpunkt des Versendens einer Nachricht.

Wir stellen fest, dass die Benutzer praktisch immer nur mit den „neueren“ solchen Daten arbeiten – sie lesen die letzten Nachrichten, analysieren die letzten Protokolle,
 Sicher, sie können auch weiter in der Zeit zurĂŒckblĂ€ttern, aber das tun sie sehr selten.

Aus diesen EinschrĂ€nkungen wird offensichtlich, dass die optimale Lösung fĂŒr Nachrichten sein wird, „tĂ€gliche“ Partitionen – denn fast immer wird unser Benutzer das lesen, was ihm „heute“ oder „gestern“ zugegangen ist.

Wenn wir im Laufe des Tages praktisch nur in einer Partition schreiben und lesen, fĂŒhrt dies auch zu einer effizienteren Nutzung von Speicher und Festplatte – da alle Indizes der Partition leicht in den Arbeitsspeicher passen, im Gegensatz zu „großen und fetten“ ĂŒber die gesamte Tabelle.

Schritt-fĂŒr-Schritt

Insgesamt klingt alles, was oben gesagt wurde, wie ein einziger großer Gewinn. Und er ist erreichbar, aber dafĂŒr mĂŒssen wir uns anstrengen – denn die Entscheidung, eine der EntitĂ€ten zu sektionieren, fĂŒhrt zum Bedarf, auch die damit verbundenen.

Nachrichten, ihre Eigenschaften und Projektionen

Da wir uns entschieden haben, Nachrichten nach Datumsangaben zu schneiden, ist es auch sinnvoll, die davon abhÀngigen EntitÀten-Eigenschaften (AnhÀnge, Adressatenliste) ebenfalls zu trennen, und zwar nach dem Datum der Nachricht.

Da eine unserer typischen Aufgaben gerade die Ansicht der Nachrichteregister (ungelesene, eingehende, alle) ist, macht es auch Sinn, diese in die Datumssektionierung der Nachrichten einzubeziehen.

DB des Messengers (Teil 2): 'live' partitionieren

Wir fĂŒgen den SektionierungsschlĂŒssel (das Datum der Nachricht) in alle Tabellen ein: Adressaten, Datei, Register. In der Nachricht selbst kann man diesen nicht hinzufĂŒgen, sondern das bestehende Datum und die Uhrzeit verwenden.

Themen

Da das Thema ĂŒber mehrere Nachrichten hinweg geht, kann man es in diesem Modell nicht einfach teilen, wir mĂŒssen uns auf etwas anderes stĂŒtzen. In unserem Fall passt das Datum der ersten Nachricht in der Korrespondenz also der Zeitpunkt der Erstellung des eigentlichen Themas.

DB des Messengers (Teil 2): 'live' partitionieren

Wir fĂŒgen den SektionierungsschlĂŒssel (das Datum des Themas) in alle Tabellen ein: Thema, Teilnehmer.

Aber nun stehen wir sofort vor zwei Problemen:

  • In welcher Sektion soll man nach Nachrichten zum Thema suchen?
  • In welcher Sektion soll man das Thema aus der Nachricht suchen?

NatĂŒrlich könnte man weiterhin in allen Sektionen suchen, aber das wĂ€re sehr unglĂŒcklich und wĂŒrde all unsere Gewinne zunichte machen. Deshalb, um zu wissen, wo genau man suchen soll, erstellen wir logische Links/Referenzen zu den Sektionen:

  • Wir fĂŒgen in der Nachricht hinzu ein Feld mit dem Datum des Themas
  • zu dem Thema fĂŒgen wir hinzu eine Reihe von Nachrichtendaten dieser Korrespondenz (es kann eine separate Tabelle sein, oder auch ein Array von Daten)

DB des Messengers (Teil 2): 'live' partitionieren

Da es nur wenige Modifikationen der Liste von Nachrichtendaten fĂŒr jede einzelne Korrespondenz geben wird (denn fast alle Nachrichten fallen in 1-2 benachbarte Tage), werde ich mich genau auf diese Variante konzentrieren.

Insgesamt hat die Struktur unserer Datenbank unter BerĂŒcksichtigung der Sektionierung folgende Form angenommen:

Tabellen: RU, bei Abneigung gegen Kyrillisch in den Namen von Tabellen/Feldern besser nicht schauen

-- Abschnitte nach dem Datum der Nachricht
CREATE TABLE "Nachricht_YYYYMMDD"(
  "Nachricht"
    uuid
      PRIMARY KEY
, "Thema"
    uuid
, "DatumThema"
    date
, "Autor"
    uuid
, "DatumUhrzeit" -- nutzen wir als Datum
    timestamp
, "Text"
    text
);

CREATE TABLE "EmpfÀnger_YYYYMMDD"(
  "DatumNachricht"
    date
, "Nachricht"
    uuid
, "Person"
    uuid
, PRIMARY KEY("Nachricht", "Person")
);

CREATE TABLE "Datei_YYYYMMDD"(
  "DatumNachricht"
    date
, "Datei"
    uuid
      PRIMARY KEY
, "Nachricht"
    uuid
, "BLOB"
    uuid
, "Name"
    text
);

CREATE TABLE "RegisterNachrichten_YYYYMMDD"(
  "DatumNachricht"
    date
, "Inhaber"
    uuid
, "TypRegister"
    smallint
, "DatumUhrzeit"
    timestamp
, "Nachricht"
    uuid
, PRIMARY KEY("Inhaber", "TypRegister", "Nachricht")
);
CREATE INDEX ON "RegisterNachrichten_YYYYMMDD"("Inhaber", "TypRegister", "DatumUhrzeit" DESC);

-- Abschnitte nach dem Datum des Themas
CREATE TABLE "Thema_YYYYMMDD"(
  "DatumThema"
    date
, "Thema"
    uuid
      PRIMARY KEY
, "Dokument"
    uuid
, "Titel"
    text
);

CREATE TABLE "TeilnehmerThema_YYYYMMDD"(
  "DatumThema"
    date
, "Thema"
    uuid
, "Person"
    uuid
, PRIMARY KEY("Thema", "Person")
);

CREATE TABLE "DatenNachrichtenThema_YYYYMMDD"(
  "DatumThema"
    date
, "Thema"
    uuid
      PRIMARY KEY
, "Datum"
    date
);

Wir sparen ein wenig Geld

Nun, was ist, wenn wir nicht die klassische Variante der Partitionierung basierend auf der Verteilung der Feldwerte (ĂŒber Trigger und Vererbung oder PARTITION BY) verwenden, sondern „manuell“ auf Anwendungsseite, dann kann man feststellen, dass der Wert des PartitionierungsschlĂŒssels bereits im Namen der Tabelle selbst gespeichert ist.

Deshalb, wenn Sie so sehr besorgt sind ĂŒber das Volumen der gespeicherten Daten, dann kann man sich von diesen â€žĂŒberflĂŒssigen“ Feldern befreien und direkt auf bestimmte Tabellen zugreifen. Allerdings mĂŒssen in diesem Fall alle Abfragen ĂŒber mehrere Partitionen auf die Anwendungsseite ausgelagert werden.

Quelle: habr.com

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster