Die Skalierung einer Postgres- Datenbank ist ein Übergangsritus für wachsende Anwendungen. Wenn Sie sehen, dass Ihre Tabellen um Millionen oder sogar Milliarden Zeilen anwachsen, beginnen Ihre einstmals schnellen Abfragen zu verzögern, und die steigenden Infrastrukturkosten beginnen, einen langen Schatten auf Ihr Endergebnis zu werfen. Sie stecken in einem Rätsel: Sie möchten sich nicht von Ihrem geliebten PostgreSQL trennen, aber es scheint, dass Sie eine effektivere Möglichkeit benötigen, mit Ihren wachsenden Datensätzen umzugehen.
In diesem Artikel erzählen wir Ihnen, wie wir einen flexiblen, leistungsstarken spaltenbasierten Komprimierungsmechanismus für PostgreSQL entwickelt haben, um dessen Skalierbarkeit zu verbessern. Durch die Kombination von Spaltenspeicherung mit speziellen Komprimierungsalgorithmen können wir beeindruckende Komprimierungsraten erzielen, die in keiner anderen relationalen Datenbank zu finden sind (+95 %).
Durch die Komprimierung Ihres Datensatzes können Sie Ihre PostgreSQL-Datenbanken weiter vergrößern. Wie wir in diesem Artikel sehen werden, können Sie mit diesem äußerst effektiven Komprimierungsdesign die Größe Ihrer großen PostgreSQL-Tabellen um das 10- bis 20-fache reduzieren. Sie können viel mehr Daten auf kleineren Festplatten speichern (also Geld sparen) und gleichzeitig die Abfrageleistung verbessern. Die Zeitskalenkomprimierung ist außerdem vollständig veränderbar, was die Datenbankverwaltung und -operationen vereinfacht: Sie können Spalten in komprimierten Tabellen hinzufügen, ändern und löschen und Sie können Daten direkt EINFÜGEN, AKTUALISIEREN und LÖSCHEN.
Willkommen zu einem skalierbareren PostgreSQL!
segmentby
Spaltesegmentby
Spaltenorderby
Doch bevor wir näher auf die Art und Weise eingehen, wie wir die Komprimierung aufgebaut haben, wollen wir uns ein paar Minuten Zeit nehmen, um diese Frage zu beantworten: Warum ist es überhaupt notwendig, einen neuen Datenbankkomprimierungsmechanismus zu PostgreSQL hinzuzufügen?
Lassen Sie uns zunächst die Anforderungen moderner Anwendungen und ein wenig Softwaregeschichte verstehen.
Wir lieben Postgres: Wir glauben, dass es die beste Grundlage für die Erstellung von Anwendungen ist, da seine Kombination aus Zuverlässigkeit, Flexibilität und umfangreichem Ökosystem von keiner anderen Datenbank erreicht werden kann. Aber Postgres wurde vor Jahrzehnten geboren – diese Robustheit hat nicht ohne Nachteile.
Heutzutage verwenden Entwickler PostgreSQL für viel mehr als den traditionellen OLTP-Anwendungsfall (OnLine Transaction Processing), für den es am besten bekannt ist. Viele datenintensive, anspruchsvolle Anwendungen, die rund um die Uhr laufen und ständig wachsende Datenmengen verarbeiten, werden von PostgreSQL unterstützt:
PostgreSQL-Datenbanken werden verwendet, um große Mengen an Sensordaten zu erfassen, die von Verkehrsmanagementsystemen, Versorgungsnetzen und Überwachungsgeräten für die öffentliche Sicherheit gestreamt werden.
Energieunternehmen nutzen PostgreSQL, um Kennzahlen aus Smart Grids und erneuerbaren Energiequellen zu speichern und zu analysieren.
Im Finanzsektor ist PostgreSQL das Herzstück von Systemen, die Markttickdaten in Echtzeit verfolgen.
E-Commerce-Plattformen verwenden PostgreSQL, um durch Benutzerinteraktionen generierte Ereignisse zu verfolgen und zu analysieren.
Postgres wird sogar als Vektordatenbank verwendet, um die neue Welle von KI-Anwendungen voranzutreiben.
Postgres-Tabellen wachsen daher sehr schnell, und Tabellen, die Milliarden von Zeilen umfassen, sind in der Produktion die neue Normalität.
Leider ist PostgreSQL von Haus aus nicht für den Umgang mit dieser Datenmenge gerüstet: Die Abfrageleistung lässt nach und die Datenbankverwaltung wird mühsam. Um diese Einschränkungen zu beseitigen, haben wir gebaut
Der Aufbau eines hochleistungsfähigen Komprimierungsmechanismus für PostgreSQL war ein ähnlich wichtiger Schritt. Diese ständig wachsenden Datensätze stellen nicht nur eine Herausforderung für eine gute Leistung dar, sondern ihre Datenanhäufung führt auch zu immer größeren Festplatten und höheren Speicherkosten. PostgreSQL brauchte eine Lösung.
Aber was ist mit der bestehenden TOAST-Methode von PostgreSQL? Trotz seines erstaunlichen Namens 🍞😋,
TOAST ist der automatische Mechanismus, den PostgreSQL verwendet, um große Werte zu speichern und zu verwalten, die nicht in einzelne Datenbankseiten passen. Während TOAST Komprimierung als eine seiner Techniken einbezieht, um dies zu erreichen, besteht die Hauptaufgabe von TOAST nicht darin, den Speicherplatz auf breiter Front zu optimieren.
Wenn Sie beispielsweise über eine 1-TB-Datenbank verfügen, die aus kleinen Tupeln besteht, hilft Ihnen TOAST nicht dabei, diese 1 TB systematisch in 80 GB umzuwandeln, egal wie viel Feinabstimmung Sie versuchen. TOAST komprimiert automatisch übergroße Attribute in einer Zeile, wenn sie den Schwellenwert von 2 KB überschreiten. TOAST hilft jedoch nicht bei kleinen Werten (Tupeln) und Sie können auch keine erweiterten, vom Benutzer konfigurierbaren Konfigurationen anwenden, z. B. die Komprimierung aller Daten, die älter als einen Monat sind in einer bestimmten Tabelle. Die Komprimierung von TOAST basiert ausschließlich auf der Größe einzelner Spaltenwerte und nicht auf breiteren Tabellen- oder Datensatzmerkmalen.
TOAST kann auch zu einem erheblichen E/A-Overhead führen, insbesondere bei großen Tabellen mit häufig aufgerufenen übergroßen Spalten. In solchen Fällen muss PostgreSQL die Out-of-Line-Daten aus der TOAST-Tabelle abrufen, was eine vom Zugriff auf die Haupttabelle getrennte E/A-Operation darstellt, da PostgreSQL zum Lesen den Zeigern von der Haupttabelle auf die TOAST-Tabelle folgen muss Vollständige Daten. Dies führt typischerweise zu einer schlechteren Leistung.
Schließlich ist die Komprimierung von TOAST nicht darauf ausgelegt, besonders hohe Komprimierungsraten zu bieten, da sie einen Standardalgorithmus für alle Datentypen verwendet.
Diese kurze Erwähnung von TOAST hilft uns auch, die Einschränkungen von PostgreSQL bei der effektiven Datenkomprimierung zu verstehen. Wie wir gerade gesehen haben, verarbeitet die Komprimierung von TOAST Daten Zeile für Zeile, aber diese zeilenorientierte Architektur verstreut die Homogenität, auf der Komprimierungsalgorithmen basieren, und führt zu einer grundlegenden Obergrenze für die Einsatzfähigkeit einer Komprimierung. Dies ist ein wesentlicher Grund dafür, dass relationale Datenbanken (wie natives Postgres) bei der Speicheroptimierung oft nicht ausreichen.
Lassen Sie uns das aufschlüsseln. Traditionell fallen Datenbanken in eine von zwei Kategorien:
Zeilenorientierte Datenbanken organisieren Daten nach Zeilen, wobei jede Zeile alle Daten für einen bestimmten Datensatz enthält. Sie sind für die Transaktionsverarbeitung optimiert, bei der Datensätze häufig eingefügt, aktualisiert und gelöscht werden, und sie sind effizient für OLTP-Systeme, bei denen Vorgänge einzelne Datensätze oder kleine Teilmengen von Daten umfassen (z. B. das Abrufen aller Informationen über einen bestimmten Kunden).
Spaltenorientierte (auch „spaltenorientierte“) Datenbanken hingegen organisieren Daten nach Spalten. In jeder Spalte werden alle Daten für ein bestimmtes Attribut über mehrere Datensätze hinweg gespeichert. Sie sind in der Regel für OLAP-Systeme (OnLine Analytical Processing) optimiert, bei denen Abfragen häufig Aggregationen und Vorgänge über viele Datensätze hinweg umfassen.
Lassen Sie uns dies anhand eines Beispiels veranschaulichen. Angenommen, wir haben eine users
mit vier Spalten: user_id
, name
, logins
und last_login
. Wenn diese Tabelle die Daten für eine Million Benutzer speichert, verfügt sie effektiv über eine Million Zeilen und vier Spalten, wodurch die Daten jedes Benutzers (dh jede Zeile) physisch zusammenhängend auf der Festplatte gespeichert werden.
In diesem zeilenorientierten Setup wird die gesamte Zeile für user_id = 500.000 zusammenhängend gespeichert, was den Abruf beschleunigt. Dadurch werden flache und breite Abfragen in einem Zeilenspeicher schneller durchgeführt (z. B. „Alle Daten für Benutzer X abrufen“):
-- Create table CREATE TABLE users ( user_id SERIAL PRIMARY KEY, name VARCHAR(100), logins INT DEFAULT 0, last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Assume we have inserted 1M user records into the 'users' table -- Shallow-and-wide query example (faster in row store) SELECT * FROM users WHERE user_id = 500000;
Im Gegensatz dazu speichert ein Spaltenspeicher alle user_id
zusammen, alle Namen zusammen, alle Anmeldewerte zusammen usw., sodass die Daten jeder Spalte zusammenhängend auf der Festplatte gespeichert werden. Diese Datenbankarchitektur bevorzugt Deep-and-Narrow-Abfragen, z. B. „Berechnen Sie die durchschnittliche Anzahl der Anmeldungen für alle Benutzer“:
-- Deep-and-narrow query example (faster in column store) SELECT AVG(logins) FROM users;
Spaltenspeicher eignen sich besonders gut für enge Abfragen über große Datenmengen . In einer spaltenbasierten Datenbank müssen nur die Daten der logins
Spalte gelesen werden, um den Durchschnitt zu berechnen. Dies ist möglich, ohne dass der gesamte Datensatz für jeden Benutzer von der Festplatte geladen werden muss.
Wie Sie vielleicht schon erraten haben, hat das Speichern von Daten in Zeilen und Spalten auch einen Einfluss darauf, wie gut Daten komprimiert werden können. In einer spaltenorientierten Datenbank sind einzelne Datenspalten in der Regel vom gleichen Typ und stammen häufig aus einer begrenzteren Domäne oder einem begrenzteren Bereich.
Infolgedessen komprimieren spaltenorientierte Datenbanken in der Regel besser als zeilenorientierte Datenbanken. Beispielsweise war unsere logins
Spalte zuvor alle vom Typ Integer und bestand wahrscheinlich nur aus einem kleinen Bereich numerischer Werte (und hatte daher eine niedrige Entropie, die sich gut komprimieren lässt). Vergleichen Sie das mit einem zeilenorientierten Format, bei dem eine ganze breite Datenzeile viele verschiedene Datentypen und -bereiche umfasst.
Aber auch wenn sie Vorteile bei Abfragen im OLAP-Stil und bei der Komprimierbarkeit aufweisen, sind Spaltenspeicher nicht ohne Kompromisse:
Abfragen, die einzelne Zeilen abrufen, sind viel weniger leistungsfähig (manchmal sogar unmöglich auszuführen).
Ihre Architektur ist für herkömmliche ACID-Transaktionen nicht so gut geeignet.
Oftmals ist es nicht möglich, Updates in Columnar-Stores durchzuführen.
Für zeilenbasierte Geschäfte ist es einfacher, von einem zu profitieren
Mit einem Zeilenspeicher ist es einfacher, Ihren Datensatz zu normalisieren, sodass Sie zusammengehörige Datensätze effizienter in anderen Tabellen speichern können.
Was ist also besser: zeilenorientiert oder spaltenorientiert?
Traditionell würden Sie die Kompromisse zwischen beiden je nach Arbeitsbelastung abwägen. Wenn Sie einen typischen OLTP-Anwendungsfall ausführen würden, würden Sie wahrscheinlich eine zeilenorientierte relationale Datenbank wie PostgreSQL wählen; Wenn es sich bei Ihrem Anwendungsfall eindeutig um OLAP handelt, könnten Sie sich für einen Säulenshop wie ClickHouse entscheiden.
Aber was ist, wenn Ihr Arbeitspensum tatsächlich eine Mischung aus beidem ist?
Ihre Anwendungsabfragen können im Allgemeinen flach und umfassend sein, wobei eine einzelne Abfrage auf viele Datenspalten sowie auf Daten über viele verschiedene Geräte/Server/Elemente hinweg zugreift. Beispielsweise könnten Sie eine benutzerorientierte Visualisierung betreiben, die die Anzeige der zuletzt aufgezeichneten Temperatur und Luftfeuchtigkeit für alle Sensoren in einer bestimmten Produktionsanlage erfordert. Eine solche Abfrage müsste auf mehrere Spalten in allen Zeilen zugreifen, die den Erstellungskriterien entsprechen, und könnte sich möglicherweise über Tausende oder Millionen von Datensätzen erstrecken.
Einige Ihrer Abfragen können jedoch auch tiefgreifend sein, da eine einzelne Abfrage über einen längeren Zeitraum hinweg eine kleinere Anzahl von Spalten für einen bestimmten Sensor auswählt. Beispielsweise müssen Sie möglicherweise auch den Temperaturtrend für ein bestimmtes Gerät im letzten Monat analysieren, um Anomalien zu untersuchen. Diese Art von Abfrage würde sich auf eine einzelne Spalte (Temperatur) konzentrieren, müsste diese Informationen jedoch aus einer großen Anzahl von Zeilen abrufen, die jedem Zeitintervall im Zielzeitraum entsprechen.
Ihre Anwendung kann auch datenintensiv sein und viele Einfügungen (Anhänge) erfordern. Wie wir bereits besprochen haben, ist der Umgang mit Hunderttausenden Schreibvorgängen pro Sekunde die neue Normalität. Ihre Datensätze sind wahrscheinlich auch sehr granular, z. B. erfassen Sie möglicherweise jede Sekunde Daten. Um mit dem vorherigen Beispiel fortzufahren, müsste Ihre Datenbank diese umfangreichen Schreibvorgänge zusammen mit konstanten Lesevorgängen verarbeiten können, um Ihre benutzerorientierte Visualisierung in Echtzeit zu ermöglichen.
Ihre Daten werden größtenteils angehängt , aber nicht unbedingt nur angehängt . Möglicherweise müssen Sie gelegentlich alte Datensätze aktualisieren oder möglicherweise verspätete oder nicht in der Reihenfolge eintreffende Daten aufzeichnen.
Bei diesem Workload handelt es sich weder um OLTP noch um OLAP im herkömmlichen Sinne. Stattdessen enthält es Elemente von beidem. Was also tun?
Gehen Sie hybrid!
Um eine Arbeitslast wie im vorherigen Beispiel zu bedienen, müsste eine einzelne Datenbank Folgendes enthalten:
Die Fähigkeit, hohe Einfügungsraten aufrechtzuerhalten, die problemlos Hunderttausende Schreibvorgänge pro Sekunde erreichen können
Unterstützung für das Einfügen verspäteter oder nicht in der richtigen Reihenfolge befindlicher Daten sowie für das Ändern vorhandener Daten
Genügend Flexibilität, um sowohl flache und breite als auch tiefe und schmale Abfragen in einem großen Datensatz effizient zu verarbeiten
Ein Komprimierungsmechanismus, der die Datenbankgröße erheblich reduzieren kann, um die Speichereffizienz zu verbessern
Das ist es, was wir erreichen wollten, als wir TimescaleDB (und damit auch PostgreSQL) eine Spaltenkomprimierung hinzufügten.
Wie wir in einem vorherigen Abschnitt erwähnt haben, haben wir TimescaleDB entwickelt, um PostgreSQL mit mehr Leistung und Skalierbarkeit zu erweitern und es für anspruchsvolle Arbeitslasten wie z. B. geeignet zu machen
Theoretisch bedeutet dies, dass TimescaleDB auch an das zeilenorientierte Speicherformat von PostgreSQL mit seiner bescheidenen Komprimierbarkeit gebunden ist. In Wirklichkeit gibt es nichts, was ein wenig Technik nicht lösen könnte.
Zwei Beobachtungen. Erste,
Tatsächlich muss diese Zeilen-zu-Spalten-Transformation nicht auf Ihre gesamte Datenbank angewendet werden. Als Timescale-Benutzer können Sie Ihre PostgreSQL-Tabellen in hybride Zeilen-Spalten-Speicher umwandeln und dabei genau auswählen, welche Daten in Spaltenform komprimiert werden sollen
Lassen Sie uns anhand eines Beispiels veranschaulichen, wie das praktisch funktioniert. Stellen Sie sich ein Temperaturüberwachungssystem vor, das jede Sekunde Messwerte von mehreren Geräten erfasst und Daten wie Zeitstempel, Geräte-ID, Statuscode und Temperatur speichert.
Um effizient auf die neuesten Temperaturdaten zuzugreifen, insbesondere bei betrieblichen Abfragen, bei denen Sie möglicherweise die neuesten Messwerte von verschiedenen Geräten analysieren möchten, können Sie die neuesten Daten (z. B. die letzte Woche) in der herkömmlichen unkomprimierten, zeilenorientierten PostgreSQL-Struktur speichern . Dies unterstützt hohe Aufnahmeraten und eignet sich auch hervorragend für Punktabfragen zu aktuellen Daten:
-- Find the most recent data from a specific device SELECT * FROM temperature_data WHERE device_id = 'A' ORDER BY timestamp DESC LIMIT 1; -- Find all devices in the past hour that are above a temperature threshold SELECT DISTINCT device_id, MAX(temperature) FROM temperature WHERE timestamp > NOW() - INTERVAL '1 hour' AND temperature > 40.0;
Sobald diese Daten jedoch ein paar Tage alt sind, werden flache und breite Abfragen wie die vorherige nicht mehr häufig ausgeführt. Stattdessen werden eher tiefe und enge analytische Abfragen durchgeführt. Um die Speichereffizienz und die Abfrageleistung für diese Art von Abfragen zu verbessern, können Sie alle Daten, die älter als eine Woche sind, automatisch in ein stark komprimiertes Spaltenformat umwandeln. Dazu würden Sie in Timescale eine Komprimierungsrichtlinie wie diese definieren:
-- Add a compression policy to compress temperature data older than 1 week SELECT add_compression_policy('temperature_data', INTERVAL '7 days');
Sobald Ihre Daten komprimiert sind, würde die Ausführung detaillierter Analyseabfragen zu den Temperaturdaten (sei es auf einem bestimmten Gerät oder auf mehreren Geräten) eine optimale Abfrageleistung erzielen.
-- Find daily max temperature for a specific device across past year SELECT time_bucket('1 day', timestamp) AS day, MAX(temperature) FROM temperature_data WHERE timestamp > NOW() - INTERVAL '1 year' AND device_id = 'A' ORDER BY day; -- Find monthly average temperatures across all devices SELECT device_id, time_bucket('1 month', timestamp) AS month, AVG(temperature) FROM temperature_data WHERE timestamp < NOW() - INTERVAL '2 weeks' GROUP BY device_id, month ORDER BY month;
Wie stellen wir den „Umstieg“ vom Zeilen- zum Spaltenformat dar? Die Hypertabellen von Timescale dienen der Partitionierung von Daten in „Blöcke“ auf der Grundlage eines Partitionierungsschlüssels, beispielsweise eines Zeitstempels oder einer anderen Seriennummernspalte. Jeder Block speichert dann die Datensätze, die einem bestimmten Bereich von Zeitstempeln oder anderen Werten für diesen Partitionierungsschlüssel entsprechen. Im obigen Beispiel würden die Temperaturdaten nach Wochen aufgeteilt, sodass der letzte Teil im Zeilenformat verbleibt und alle älteren Wochen in das Spaltenformat konvertiert werden.
Diese hybride Zeilen-Spalten-Speicher-Engine ist ein unglaublich leistungsstarkes Tool zur Optimierung der Abfrageleistung in großen PostgreSQL-Datenbanken und gleichzeitig zur drastischen Reduzierung des Speicherbedarfs. Wie wir später in diesem Artikel sehen werden, können wir durch die Umwandlung von Daten in ein Spaltenformat und die Anwendung spezieller Komprimierungsmechanismen nicht nur Ihre analytischen Abfragen beschleunigen, sondern erreichen auch Komprimierungsraten von bis zu 98 %. Stellen Sie sich vor, welche Auswirkungen dies auf Ihre Lagerkosten hat!
Bevor wir uns mit den Details zur Abfrageleistung und Speichereinsparungen befassen, wollen wir uns zunächst damit befassen, wie dieser Mechanismus unter der Haube funktioniert: wie die Umwandlung von Zeilen in Spalten tatsächlich durchgeführt wird und wie die Komprimierung auf die Spaltendaten angewendet wird.
Wenn die Komprimierungsrichtlinie greift, wandelt sie im Wesentlichen die traditionell zahlreichen einzelnen Datensätze in der ursprünglichen PostgreSQL-Hypertabelle – stellen Sie sich 1.000 dicht gepackte Zeilen vor – in eine einzelne, kompaktere Zeilenstruktur um. Innerhalb dieser komprimierten Form speichert jedes Attribut oder jede Spalte nicht mehr einzelne Einträge aus jeder Zeile. Stattdessen kapselt es eine kontinuierliche, geordnete Folge aller entsprechenden Werte aus diesen 1.000 Zeilen. Bezeichnen wir diese 1.000 Zeilen als Batch .
Stellen wir uns zur Veranschaulichung eine Tabelle wie diese vor:
| Timestamp | Device ID | Status Code | Temperature | |-----------|-----------|-------------|-------------| | 12:00:01 | A | 0 | 70.11 | | 12:00:01 | B | 0 | 69.70 | | 12:00:02 | A | 0 | 70.12 | | 12:00:02 | B | 0 | 69.69 | | 12:00:03 | A | 0 | 70.14 | | 12:00:03 | B | 4 | 69.70 |
Um diese Daten für die Komprimierung vorzubereiten, würde Timescale diese tabellarischen Daten zunächst in einen spaltenorientierten Speicher umwandeln. Bei einem Datenstapel (ca. 1.000 Zeilen) werden die Daten jeder Spalte in einem Array zusammengefasst, wobei jedes Array-Element dem Wert aus einer der ursprünglichen Zeilen entspricht. Der Prozess führt zu einer einzelnen Zeile, wobei jede Spalte ein Array von Werten aus diesem Stapel speichert.
| Timestamp | Device ID | Status Code | Temperature | |------------------------------|--------------------|--------------------|-------------------------------| | [12:00:01, 12:00:01, 12...] | [A, B, A, B, A, B] | [0, 0, 0, 0, 0, 4] | [70.11, 69.70, 70.12, 69....] |
Noch vor der Anwendung von Komprimierungsalgorithmen spart dieses Format sofort Speicherplatz, indem es den internen Overhead von Timescale pro Zeile erheblich reduziert. PostgreSQL fügt normalerweise etwa 27 Byte Overhead pro Zeile hinzu (z. B. für Multi-Version Concurrency Control oder MVCC). Wenn also unser obiges Schema beispielsweise 32 Byte groß ist, benötigen die 1.000 Datenzeilen aus einem Stapel, der zuvor [1.000 * (32 + 27)] ~= 59 Kilobyte benötigte, jetzt [1.000 * 32 + 27] ohne Komprimierung ] ~= 32 Kilobyte in diesem Format.
[ Nebenbei : Diese Idee, eine größere Tabelle in kleinere Stapel zu „gruppieren“ und dann die Spalten jedes Stapels zusammenhängend (und nicht die der gesamten Tabelle) zu speichern, ist tatsächlich ein ähnlicher Ansatz wie die „Zeilengruppen“ im Apache Parquet-Dateiformat. Obwohl wir diese Ähnlichkeit erst im Nachhinein erkannten!]
Der große Vorteil dieser Transformation besteht jedoch darin, dass wir nun bei einem Format, in dem ähnliche Daten (Zeitstempel, Geräte-IDS, Temperaturmesswerte usw.) zusammenhängend gespeichert werden, typspezifische Komprimierungsalgorithmen anwenden können, sodass jedes Array separat komprimiert wird . Dadurch erreicht Timescale beeindruckende Komprimierungsraten.
Timescale verwendet automatisch die folgenden Komprimierungsalgorithmen. Alle diese Algorithmen sind „
Delta-of-Delta +
Wörterbuchkomprimierung für ganze Zeilen für Spalten mit einigen sich wiederholenden Werten (+ LZ-Komprimierung oben)
LZ-basierte Array-Komprimierung für alle anderen Typen
Wir haben Gorilla und Simple-8b erweitert, um die Dekomprimierung von Daten in umgekehrter Reihenfolge durchzuführen, sodass wir Abfragen beschleunigen können, die Rückwärtsscans verwenden.
Wir haben festgestellt, dass diese typspezifische Komprimierung sehr leistungsstark ist: Zusätzlich zur höheren Komprimierbarkeit können einige der Techniken wie Gorilla und Delta-of-Delta während der Dekodierung bis zu 40-mal schneller sein als die LZ-basierte Komprimierung, was zu einer deutlich verbesserten Abfrageleistung führt .
Beim Dekomprimieren von Daten kann Timescale diese einzelnen komprimierten Stapel verarbeiten und sie Stapel für Stapel und nur die angeforderten Spalten dekomprimieren. Wenn die Abfrage-Engine also feststellen kann, dass nur 20 Stapel (entsprechend 20.000 ursprünglichen Datenzeilen) aus einem Tabellenblock verarbeitet werden müssen, der ursprünglich eine Million Datenzeilen enthielt, kann die Abfrage viel schneller ausgeführt werden, da sie liest und dekomprimiert viel weniger Daten. Mal sehen, wie es das macht.
Das bisherige Array-basierte Format stellt eine Herausforderung dar: Welche Zeilen sollte die Datenbank abrufen und dekomprimieren, um eine Abfrage aufzulösen?
Nehmen wir noch einmal unser Beispiel für Temperaturdaten. Es tauchen immer wieder verschiedene natürliche Arten von Abfragen auf: das Auswählen und Sortieren von Daten nach Zeitbereichen oder das Auswählen von Daten basierend auf ihrer Geräte-ID (entweder in der WHERE-Klausel oder über ein GROUP BY). Wie können wir solche Anfragen effizient unterstützen?
Wenn wir nun Daten vom letzten Tag benötigen, muss die Abfrage durch Zeitstempeldaten navigieren, die jetzt Teil eines komprimierten Arrays sind. Sollte die Datenbank also ganze Blöcke (oder sogar die gesamte Hypertabelle) dekomprimieren, um die Daten für den letzten Tag zu finden?
Oder selbst wenn wir die einzelnen „Batches“ identifizieren könnten, die in einem komprimierten Array gruppiert sind (oben beschrieben), sind dann Daten von verschiedenen Geräten verstreut, sodass wir das gesamte Array dekomprimieren müssen, um herauszufinden, ob es Daten über ein bestimmtes Gerät enthält? Dieser einfachere Ansatz könnte zwar immer noch zu einer guten Komprimierbarkeit führen, wäre jedoch im Hinblick auf die Abfrageleistung bei weitem nicht so effizient.
Um die Herausforderung zu lösen, Daten für bestimmte Abfragen in ihrem Spaltenformat effizient zu finden und zu dekomprimieren,
segmentby
Spalte Denken Sie daran, dass Daten in Timescale zunächst Stück für Stück in eine komprimierte Spaltenform konvertiert werden. Um die Effizienz von Abfragen zu verbessern, die auf der Grundlage einer bestimmten Spalte filtern (z. B. häufige Abfragen nach device_id
), haben Sie die Möglichkeit, diese bestimmte Spalte als „
Diese segmentby
Spalten werden verwendet, um die Daten innerhalb jedes komprimierten Blocks logisch zu partitionieren. Anstatt wie oben gezeigt ein komprimiertes Array beliebiger Werte zu erstellen, gruppiert die Komprimierungs-Engine zunächst alle Werte, die denselben segmentby
Schlüssel haben.
Daher werden 1.000 Datenzeilen zu Geräte-ID A dicht gesichert, bevor sie in einer einzigen komprimierten Zeile gespeichert werden, 1.000 Datenzeilen zu Geräte-ID B usw. Wenn also device_id
als segmentby
Spalte ausgewählt wird, enthält jede komprimierte Zeile komprimierte Spaltendatenstapel zu einer bestimmten Geräte-ID, die unkomprimiert in dieser Zeile gespeichert werden. Timescale erstellt zusätzlich einen Index für diese Segmentby-Werte innerhalb des komprimierten Blocks.
| Device ID | Timestamp | Status Code | Temperature | |-----------|--------------------------------|-------------|-----------------------| | A | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 0] | [70.11, 70.12, 70.14] | | B | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 4] | [69.70, 69.69, 69.70] |
Diese zusammenhängende Speicherung von Daten erhöht die Effizienz von Abfragen, die durch die segmentby
Spalte gefiltert werden, erheblich. Wenn Sie eine nach device_id
gefilterte Abfrage ausführen, bei der device_id
die segmentby
Spalte ist, kann Timescale (über einen Index) schnell alle komprimierten Zeilen im Block auswählen, die die angegebene(n) Geräte-ID(s) haben, und Daten schnell überspringen (und eine Dekomprimierung vermeiden). ) Daten, die keinen Bezug zu den angeforderten Geräten haben.
In dieser Abfrage findet und verarbeitet Timescale beispielsweise effizient nur die komprimierten Zeilen, die Daten für die Geräte-ID A enthalten:
SELECT AVG(temperature) FROM sensor_data WHERE device_id = 'A' AND time >= '2023-01-01' AND time < '2023-02-01';
Darüber hinaus speichern Timescale-Hypertabellen mit jedem Block verknüpfte Metadaten, die den Wertebereich angeben, den der Block abdeckt. Wenn eine Hypertabelle also nach Wochen mit Zeitstempeln partitioniert ist, weiß der Abfrageplaner beim Ausführen der obigen Abfrage, dass er nur diese 4–5 Blöcke verarbeiten darf, die den Monat Januar abdecken, was die Abfrageleistung weiter verbessert.
segmentby
SpaltenSie können angeben, welche Spalten für segmentby verwendet werden sollen, wenn Sie die Komprimierung einer Hypertabelle zum ersten Mal aktivieren. Die Auswahl der zu verwendenden Spalte sollte darauf basieren, welche Spalte(n) in Ihren Abfragen häufig verwendet werden. Tatsächlich können Sie mehrere Spalten zum Segmentieren verwenden: Anstatt beispielsweise Stapel nach Geräte-ID zu gruppieren, können Sie (sagen wir) die Stapel, die sowohl dieselbe Mieter-ID als auch dieselbe Geräte-ID haben, zusammen gruppieren.
Achten Sie jedoch darauf, die Selektivität nicht zu übertreiben: Wenn Sie zu viele Segmentby-Spalten definieren, verringert sich die Effizienz der Komprimierung, da jede zusätzliche Segmentby-Spalte die Daten effektiv in immer kleinere Batches aufteilt.
Wenn Sie nicht mehr 1.000 Datensatzstapel mit Daten erstellen können, sondern stattdessen nur noch fünf Datensätze mit den angegebenen Segmentby-Schlüsseln in einem bestimmten Block haben, wird die Komprimierung überhaupt nicht gut funktionieren!
Sobald Sie jedoch identifiziert haben, nach welchen Spalten Sie segmentieren möchten, können Sie diese ganz einfach konfigurieren, wenn Sie die Komprimierung in Ihrer Hypertabelle aktivieren:
ALTER TABLE temperature_data SET ( timescaledb.compress, timescaledb.compress_segmentby = 'device_id' );
orderby
TimescaleDB verbessert die Abfrageleistung für komprimierte Daten durch strategische Datenreihenfolge innerhalb jedes Blocks, die durch den Parameter compress_orderby
vorgegeben wird. Während die Standardeinstellung der Sortierung nach Zeitstempel (dem typischen Partitionierungsschlüssel in Zeitreihendaten) für die meisten Szenarien geeignet ist, kann das Verständnis dieser Optimierung hilfreich sein. Lesen Sie weiter für eine noch tiefere technische Perspektive.
Betrachten Sie noch einmal das Beispiel wöchentlicher Blöcke und einer Abfrage, die nur Daten zu einem einzelnen Tag anfordert. In einer regulären Tabelle mit einem Zeitstempelindex könnte die Abfrage diesen Index effizient durchsuchen, um die Daten des Tages zu finden.
Bei komprimierten Daten ist die Situation jedoch anders: Zeitstempel sind komprimiert und können nicht aufgerufen werden, ohne ganze Stapel zu dekomprimieren. Das Erstellen eines Index für jeden einzelnen Zeitstempel wäre kontraproduktiv, da er die Vorteile der Komprimierung zunichte machen könnte, indem er übermäßig groß wird.
Timescale behebt dieses Problem, indem es die zu stapelnden Daten grundsätzlich nach ihrem Zeitstempel „sortiert“. Anschließend werden Metadaten zu den minimalen und maximalen Zeitstempeln für jeden Stapel aufgezeichnet. Wenn eine Abfrage ausgeführt wird, ermöglichen diese Metadaten der Abfrage-Engine, schnell zu identifizieren, welche komprimierten Zeilen (Batches) für den Zeitraum der Abfrage relevant sind, wodurch die Notwendigkeit einer vollständigen Dekomprimierung verringert wird.
Diese Methode lässt sich gut mit der Verwendung von Segmentby-Spalten kombinieren. Während des Komprimierungsprozesses werden die Daten zunächst nach der Spalte „Segmentby“ gruppiert, dann anhand des Parameters „Orderby“ geordnet und schließlich in kleinere, nach Zeitstempeln geordnete „Mini-Batches“ aufgeteilt, die jeweils bis zu 1.000 Zeilen enthalten.
Die Kombination aus TimescaleDB-Segmentierung und -Reihenfolge verbessert die Leistung gängiger Zeitreihen- und Analyseabfragen erheblich. Diese sowohl zeitliche (über orderby
) als auch räumliche (über segmentby
) Optimierung stellt sicher, dass TimescaleDB umfangreiche Zeitreihendaten effektiv verwaltet und abfragt und so ein optimiertes Gleichgewicht zwischen Komprimierung und Zugänglichkeit bietet.
Die erste Version unseres Komprimierungsdesigns wurde 2019 mit TimescaleDB 1.5 veröffentlicht. Viele Veröffentlichungen später hat die Timescale-Komprimierung einen langen Weg zurückgelegt.
Eine der Haupteinschränkungen unserer ersten Version bestand darin, dass wir nach der Komprimierung der Daten keine weiteren Änderungen an Daten (z. B. INSERTs, UPDATEs, DELETEs) zuließen, ohne zuvor den gesamten Hypertabellenblock, in dem sie sich befanden, manuell zu dekomprimieren.
Angesichts der Tatsache, dass wir für datenintensive Anwendungsfälle optimiert haben, die auf Analyse- und Zeitreihendaten basieren, die hauptsächlich einfügelastig und nicht aktualisierungsintensiv sind, stellte dies eine weitaus geringere Einschränkung dar, als dies bei einem herkömmlichen OLTP-Anwendungsfall der Fall gewesen wäre wo Daten häufig aktualisiert werden (z. B. eine Kundeninformationstabelle). Jedoch,
Eine weitere Einschränkung unserer ersten Komprimierungsversion bestand darin, dass wir keine Schemaänderungen in Tabellen, einschließlich komprimierter Daten, zuließen. Dies bedeutete, dass Entwickler ihre Datenstruktur nicht weiterentwickeln konnten, ohne die gesamte Tabelle zu dekomprimieren.
Heute sind alle diese Einschränkungen aufgehoben. Mit Timescale können Sie jetzt vollständige DML- (Data Manipulation Language) und DDL-Operationen (Data Definition Language) für komprimierte Daten ausführen:
Sie können UPDATEs, UPSERTs und DELETEs durchführen.
Sie können Spalten hinzufügen, auch mit Standardwerten.
Sie können Spalten umbenennen und löschen.
Um die Datenänderung an komprimierten Daten zu automatisieren (und sie für unsere Benutzer nahtlos zu gestalten), haben wir unseren Komprimierungsansatz geändert, indem wir einen „Staging-Bereich“ eingeführt haben – im Wesentlichen einen überlappenden Abschnitt, der unkomprimiert bleibt und in dem wir die unten aufgeführten Vorgänge „über unkomprimierte Daten“ ausführen die Haube.
Als Benutzer müssen Sie nichts manuell tun: Sie können Ihre Daten direkt ändern, während unsere Engine im Hintergrund alles automatisch erledigt. Die Möglichkeit, Änderungen an komprimierten Daten vorzunehmen, macht die hybride Zeilen-Spalten-Speicher-Engine von Timescale deutlich flexibler.
Dieses Design über den Staging-Bereich macht INSERTs so schnell wie das Einfügen in unkomprimierte Blöcke, da genau das passiert (wenn Sie in einen komprimierten Block einfügen, schreiben Sie jetzt in den Staging-Bereich). Außerdem konnten wir UPDATEs, UPSERTs und DELETEs direkt unterstützen: Wenn ein Wert geändert werden muss, verschiebt die Engine einen relevanten Teil der komprimierten Daten in den Staging-Bereich, dekomprimiert ihn, führt die Änderung durch und verschiebt ihn (asynchron) erneut in komprimierter Form in die Haupttabelle übertragen.
(Dieser Datenbereich funktioniert typischerweise im Maßstab der komprimierten „Mini-Batches“ von bis zu 1.000 Werten, die eine „Zeile“ im zugrunde liegenden PostgreSQL-Speicher bilden, um die Datenmenge zu minimieren, die zur Unterstützung von Änderungen dekomprimiert werden muss.)
Dieser „Staging-Bereich“ verfügt immer noch über eine reguläre Transaktionssemantik, und Ihre Abfragen sehen diese Werte, sobald sie darin eingefügt werden. Mit anderen Worten: Der Abfrageplaner ist intelligent genug, um zu verstehen, wie eine ordnungsgemäße Abfrage über diese zeilenbasierten „Staging“-Blöcke und den regulären Spaltenspeicher hinweg durchgeführt wird.
An diesem Punkt ist die nächste logische Frage: Was ist das Endergebnis? Wie wirkt sich die Komprimierung auf die Abfrageleistung aus und wie viel Speicherplatz kann ich dadurch einsparen?
Wie wir in diesem Artikel besprochen haben, eignen sich Spaltenspeicher im Allgemeinen nicht besonders gut für Abfragen, die einzelne Zeilen abrufen, aber sie schneiden bei analytischen Abfragen, die aggregierte Werte betrachten, tendenziell viel besser ab. Genau das sehen wir in Timescale: Deep-and-Narrow-Abfragen mit Durchschnittswerten erzielen bei Verwendung der Komprimierung erhebliche Leistungsverbesserungen.
Lassen Sie uns dies veranschaulichen, indem wir ein paar Abfragen ausführen
Betrachten Sie die folgende Abfrage, bei der Sie nach dem höchsten Fahrpreis aus einer Teilmenge des Taxidatensatzes innerhalb eines bestimmten Zeitraums fragen:
SELECT max(fare_amount) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01';
Bei Ausführung mit dem unkomprimierten Datensatz beträgt die Ausführungszeit der Abfrage 4,7 Sekunden. Wir verwenden einen kleinen, nicht optimierten Testdienst und fragen viele Millionen Zeilen ab, daher ist diese Leistung nicht die beste. Nach der Komprimierung der Daten sinkt die Antwortzeit jedoch auf 77,074 Millisekunden:
Lassen Sie uns ein weiteres Beispiel teilen. Diese Abfrage zählt die Anzahl der Fahrten mit einem bestimmten Preistyp innerhalb eines bestimmten Zeitraums:
SELECT COUNT(*) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01' AND "RatecodeID" = 99;
Wenn diese Abfrage für die unkomprimierten Daten ausgeführt würde, würde der Abschluss 1,6 Sekunden dauern. Dieselbe Abfrage, die für komprimierte Daten ausgeführt wird, ist in nur 18,953 Millisekunden abgeschlossen. Wieder einmal sehen wir eine sofortige Verbesserung! Dies sind nur kurze Beispiele, aber sie veranschaulichen, wie leistungsfähig die Komprimierung sein kann, um Ihre Abfragen zu beschleunigen.
Vergessen wir nicht, was uns überhaupt hierher geführt hat: Wir brauchten eine Taktik, die es uns ermöglichte, die Größe unserer großen PostgreSQL-Datenbanken zu reduzieren, damit wir PostgreSQL weiter skalieren konnten. Um zu zeigen, wie effektiv die Timescale-Komprimierung für diese Aufgabe sein kann, enthält die folgende Tabelle einige tatsächliche Beispiele für Komprimierungsraten, die bei Timescale-Kunden beobachtet wurden .
Diese Speichereinsparungen führen direkt zu Kosteneinsparungen:
Die letztendlich erreichte Komprimierungsrate hängt von mehreren Faktoren ab, einschließlich Ihres Datentyps und Ihrer Zugriffsmuster. Aber wie Sie sehen, kann die Timescale-Komprimierung äußerst effizient sein –
Unser Team kann Ihnen bei der Feinabstimmung der Komprimierung helfen, um so viel Geld wie möglich zu sparen.
„Mit der Komprimierung haben wir im Durchschnitt eine Reduzierung der Festplattengröße um 97 Prozent festgestellt.“
(Michael Gagliardo, Ndustrial)
„Wir haben festgestellt, dass das Komprimierungsverhältnis von Timescale absolut phänomenal ist! Wir haben derzeit ein Komprimierungsverhältnis von über 26, was den Speicherplatz, der zum Speichern aller unserer Daten erforderlich ist, drastisch reduziert.“
(Nicolas Quintin, Oktave)
„Die Komprimierung von Timescale war so gut wie beworben, was uns eine Einsparung von 90 % [Festplatten-]Speicherplatz in unserem zugrunde liegenden Hypertable ermöglichte.“
(Paolo Bergantino, METER-Gruppe)
Schließlich könnten wir diesen Artikel nicht abschließen, ohne Timescales Tiered Storage zu erwähnen.
Neben der Komprimierung steht Ihnen jetzt ein weiteres Tool zur Verfügung, mit dem Sie Ihre PostgreSQL-Datenbanken in der Timescale-Plattform noch weiter skalieren können: Sie können Ihre älteren, selten genutzten Daten auf eine kostengünstige Objektspeicherebene schichten und trotzdem weiterhin über den Standardzugriff darauf zugreifen SQL.
Diese kostengünstige Speicherstufe hat einen Pauschalpreis von 0,021 US-Dollar pro GB/Monat für Daten – günstiger als Amazon S3 – sodass Sie viele TBs in Ihrer PostgreSQL-Datenbank zu einem Bruchteil der Kosten behalten können.
So funktioniert unser Tiered-Storage-Backend auf der Timescale-Plattform und wie die Low-Storage-Stufe mit der Komprimierung zusammenspielt:
Ihre aktuellsten Daten werden in eine leistungsstarke Speicherebene geschrieben, die für schnelle Abfragen und hohe Aufnahmemengen optimiert ist. In dieser Stufe können Sie die spaltenbasierte Timescale-Komprimierung aktivieren, um die Größe Ihrer Datenbank zu verkleinern und Ihre analytischen Abfragen zu beschleunigen, wie wir in diesem Artikel besprochen haben. Sie können beispielsweise eine Komprimierungsrichtlinie definieren, die Ihre Daten nach einer Woche komprimiert.
Sobald Ihre Anwendung nicht mehr häufig auf diese Daten zugreift, können Sie sie durch die Einrichtung einer Tiering-Richtlinie automatisch auf eine kostengünstigere Objektspeicherschicht umstellen. Die Daten in der kostengünstigen Speicherebene bleiben vollständig in Ihrer Datenbank abfragbar und es gibt keine Begrenzung für die Datenmenge, die Sie speichern können – bis zu Hunderten von TB oder mehr. Sie können beispielsweise eine Tiering-Richtlinie definieren, die alle Ihre Daten, die älter als sechs Monate sind, auf die kostengünstige Speicherebene verschiebt.
Sobald Sie diese Daten nicht mehr in Ihrer Datenbank behalten müssen, können Sie sie über eine Aufbewahrungsrichtlinie löschen. Beispielsweise können Sie alle Daten nach fünf Jahren löschen.
Wir haben Postgres einen effektiven Datenbankkomprimierungsmechanismus gegeben, indem wir Funktionen zur Spaltenkomprimierung hinzugefügt haben. Dies ist eine wesentliche Funktion zur Skalierung von PostgreSQL-Datenbanken in der heutigen datenintensiven Welt: Die Komprimierung ermöglicht enorme Einsparungen bei der Festplattennutzung (Speicherung von mehr Daten zu einem günstigeren Preis) und Leistungsverbesserungen (Ausführung analytischer Abfragen über große Volumina in Millisekunden).
Das Komprimierungsdesign von Timescale erreicht beeindruckende Komprimierungsraten, indem es erstklassige Komprimierungsalgorithmen mit einer neuartigen Methode zur Erstellung hybrider Zeilen-/Spaltenspeicher in PostgreSQL kombiniert. Durch diese Funktion entspricht der Speicherbedarf von Timescale (und damit von PostgreSQL) dem von benutzerdefinierten, eingeschränkteren Spaltendatenbanken.
Aber im Gegensatz zu vielen Spalten-Engines unterstützt Timescale die ACID-Transaktionssemantik und direkte Unterstützung für Änderungen (INSERTs, UPDATEs, UPSERTs, DELETEs) an komprimierten Spaltendaten. Da das alte Modell „eine Datenbank für Transaktions-Workloads, eine andere für Analyse“ veraltet ist, führen viele moderne Anwendungen Workloads aus, die beiden Mustern entsprechen. Warum also zwei separate Datenbanken verwalten, wenn Sie alles in PostgreSQL erledigen können?
Mit Timescale können Sie mit PostgreSQL beginnen, mit PostgreSQL skalieren und bei PostgreSQL bleiben.
– Geschrieben von Carlota Soto und Mike Freedman .
Auch hier veröffentlicht.