Anfang dieses Jahres haben wir mit dem größten Projekt unseres Unternehmens begonnen
Natürlich haben wir uns für Timescale entschieden, unsere ausgereifte Cloud-Plattform mit TimescaleDB als Kernstück. Wir sind es gewohnt, mit PostgreSQL zu arbeiten, und wir haben TimescaleDB entwickelt, um PostgreSQL schneller und skalierbarer zu machen – was gibt es Schöneres, als nach unserem eigenen Beispiel zu leben?
Der einfachste Weg, dieses Dogfooding-Experiment zu beschreiben, sind die Zahlen, die helfen, sein Ausmaß zu quantifizieren. Um Insights zu erstellen, mussten wir Abfrageinformationen in unserer Flotte kontinuierlich laufender Produktionsdatenbanken sammeln. Wir haben schnell mehr als eine Billion Datensätze zu einzelnen (bereinigten) Anfragen auf der Plattform gesammelt.
Da Insights jetzt in Produktion ist, nehmen wir täglich über 10 Milliarden neue Datensätze auf. Der von einem einzelnen Timescale-Dienst bereitgestellte Datensatz wächst täglich um etwa 3 TB und beläuft sich derzeit auf über 350 TB , und derselbe Datenbankdienst ermöglicht Echtzeit-Dashboards für alle unsere Kunden.
Dieser Blogbeitrag bietet einen Blick hinter die Kulissen des Prozesses der Erstellung von Insights. Um in dieser Größenordnung zu arbeiten, mussten wir die Grenzen eines einzelnen Timescale-Dienstes erweitern und nicht nur PostgreSQL, sondern auch unser Einfühlungsvermögen als Entwickler skalieren. Wir fanden, dass Timescale dieser Aufgabe mehr als gewachsen ist, aber es gibt auch Bereiche, die wir verbessern möchten!
Um Insights zu ermöglichen, mussten wir unseren Datenbankadministrator-Hut aufsetzen 🤠 und einige technische Herausforderungen meistern, um PostgreSQL auf viele Terabytes an Daten zu skalieren. Wir wollten einen Timescale-Dienst als zentrale Datenbank nutzen, der auf unserer Plattform ohne „spezielle“ Infrastruktur gehostet wird. Das bedeutete Folgendes:
Wir mussten eine Pipeline aufbauen, die in der Lage ist, Milliarden von Datensätzen pro Tag in einen einzigen Timescale-Dienst aufzunehmen. Timescale kann mit hohen Aufnahmeraten umgehen und tut dies regelmäßig für unsere Kunden, aber dieser Grad der Skalierung unter Produktionsabfragelasten sorgt immer für Aufsehen.
Unsere Kunden mussten in der Lage sein, diese Datenbank so flexibel abzufragen, dass sie alle von Insights angebotenen Analysen ermöglichen, und wir wollten nicht, dass sie Minuten auf eine Antwort warten!
Wir mussten Hunderte von TBs in einem einzigen Timescale-Dienst speichern, da wir jeden Tag mehrere TBs hinzufügen. Ältere Daten (d. h. älter als ein paar Wochen) mussten zugänglich sein, aber nicht unbedingt schnell abzufragen.
Was die Datenerfassung betrifft, haben wir die Architektur der Timescale-Plattform genutzt. Timescale läuft auf Kubernetes (k8s) und wir haben mehrere k8s- Cluster, die in verschiedenen geografischen Regionen laufen. Diese Cluster verfügen über Knoten, die einen oder mehrere Kundendatenbankdienste enthalten. Um die Abfrageausführungen für alle diese Datenbanken zu sammeln, sprudeln wir von dieser Datenbank auf die regionale Ebene und verfügen dann über einen regionalen Writer, der Datensatzstapel im Timescale-Datenbankdienst speichert, der Insights unterstützt.
Verzeihen Sie die Handbewegung, die einige blutige Details auf niedriger Ebene vermeidet, aber im Großen und Ganzen funktionieren die Dinge so: Jede Datenbank, die in der gesamten Flotte ausgeführt wird, ist so instrumentiert, dass sie nach jeder Abfrage, einschließlich der, einen Datensatz erstellt (aus Datenschutz- und Sicherheitsgründen bereinigt). Abfrage selbst und Statistiken, die uns wichtig sind.
Diese Datensätze werden auf Knotenebene gesammelt, mit Etiketten versehen, um sie dem Datenbankdienst zuzuordnen, von dem sie stammen, und in Stapeln zusammengefasst, um sie an den regionalen Autor zu senden. Der regionale Schreibdienst wird nach Bedarf repliziert, um die Auslastung in jeder Region zu bewältigen. Jeder Writer sammelt Stapel von den Knoten in jedem Cluster und erstellt noch größere Stapel.
Diese großen Stapel werden dann zunächst mit „COPY“ in eine temporäre Tabelle geschrieben (keine Write-Ahead-Protokollierung = schnell). Die Einträge in dieser temporären Tabelle werden dann verwendet, um die erforderlichen Tabellen zu aktualisieren (siehe unten). Mit der temporären Tabelle können wir „COPY“ verwenden, ohne uns Gedanken über Duplikate machen zu müssen, die durch nachfolgende Vorgänge verarbeitet werden, bei denen die Datensätze aus der temporären Tabelle gelöscht werden.
Zusammenfassend:
Werfen wir einen Blick auf die Datenbank , die Insights antreibt. Wir führen Insights in einem „standardmäßigen“ Timescale-Dienst mit einem aus
Die Datenbank, auf der Insights basiert, besteht aus mehreren Teilen, aber wir werden versuchen, die wichtigsten hervorzuheben.
Erstens haben wir zwei reguläre PostgreSQL-Tabellen, die als „Referenztabellen“ dienen. Diese Tabellen enthalten Informationsdatenbank-Metadaten und Abfragezeichenfolgen-Metadaten. Hier sind ihre (Pseudo-)Schemata:
Datenbank-Metadaten
Table "insights.cloud_db" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- id | bigint | | not null | nextval('cloud_db_id_seq'::regclass) service_id | text | | not null | project_id | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "cloud_db_pkey" PRIMARY KEY, btree (id) "cloud_db_project_id_service_id_key" UNIQUE CONSTRAINT, btree (project_id, service_id)
Metadaten abfragen
Table "insights.queries" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- hash | text | | not null | normalized_query | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "queries_pkey" PRIMARY KEY, btree (hash)
Jedes Mal, wenn für eine neue Datenbank Abfragen ausgeführt werden, wird sie zu „insights.cloud_db“ hinzugefügt. Jedes Mal, wenn eine neue normalisierte Abfrage ausgeführt wird, wird sie zu „insights.queries“ hinzugefügt.
(Was ist eine normalisierte Abfrage? Es handelt sich um eine Abfrage, bei der alle Konstanten durch Platzhalter ersetzt wurden: $1 für die erste, $2 für die zweite usw., sodass wir nur die „Form“ der Abfrage sehen, nicht ihre Werte .)
Bisher verwenden wir nur normales Postgres ohne Timescale-Geheimsoße. Aber die anderen wichtigen Objekte in der Datenbank sind einzigartig für TimescaleDB und helfen dabei, PostgreSQL auf eine andere Ebene zu skalieren. Hier passiert die Magie: Hypertabellen und kontinuierliche Aggregate.
Hypertables sind die automatisch partitionierten Tabellen von Timescale. Sie unterteilen die Daten während der Aufnahme automatisch nach einer Dimension, was die Skalierung von PostgreSQL-Tabellen auf große Skalen erheblich erleichtert. Hypertabellen sind die Bausteine von Timescale. Wir speichern unsere Abfragestatistikmetriken in einer riesigen Hypertabelle, wie wir später sehen werden.
Kontinuierliche Aggregate sind Timescales verbesserte Version der materialisierten PostgreSQL-Ansichten, die eine inkrementelle und automatische Materialisierung ermöglichen, was sich beim Aufbau von Insights als sehr nützlich erwiesen hat.
Sehen wir uns an, wie wir diese Funktionen genutzt haben, um schnelle analytische Abfragen auf Benutzerseite zu ermöglichen.
Wie bereits erwähnt, verwenden wir eine große Hypertabelle, um Informationen zu jeder Abfrageausführung zu speichern. Diese Hypertabelle ist unsere Haupttabelle, in der die bereinigten Rohmetriken gespeichert sind. Es sieht in etwa wie folgt aus und ist so konfiguriert, dass die Zeitstempelspalte ( created
) verwendet wird, um die Daten bei der Aufnahme automatisch zu partitionieren.
Table "insights.records" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time | bigint | | | rows | bigint | | | ...
Wir haben für dieses Beispiel eine Reihe von Statistiken weggelassen, aber Sie verstehen, worauf es ankommt.
Jetzt müssen wir schnelle Abfragen von der Benutzerseite zulassen – aber diese Tabelle ist riesig. Um die Dinge zu beschleunigen, haben wir uns stark auf kontinuierliche Aggregate verlassen (mit
Kontinuierliche Aggregate machen in einem Produkt, das benutzerorientierte Echtzeitanalysen wie Insights bietet, sehr viel Sinn. Um Benutzern umsetzbare Informationen bereitzustellen, müssen wir Metriken aggregieren: Wir zeigen Benutzern kein Protokoll jeder von ihnen ausgeführten Abfrage mit Statistiken daneben – einige Datenbanken führen Tausende von Abfragen pro Sekunde durch, daher wäre es ein Albtraum, es zu finden irgendetwas Nützliches. Stattdessen stellen wir Benutzern Aggregate zur Verfügung.
Wir könnten also genauso gut die Tatsache ausnutzen, dass wir den Benutzern keine rohen Einzeldatensätze zeigen, und das Ergebnis behalten
Wir hätten materialisierte PostgreSQL-Ansichten verwenden können, aber die kontinuierlichen Aggregate von Timescale haben mehrere Vorteile, die für uns besonders nützlich waren. Wir aktualisieren die Ansichten häufig, und kontinuierliche Aggregate verfügen über integrierte Richtlinien für automatische Aktualisierungen und werden inkrementell aktualisiert.
Wir aktualisieren Ansichten alle fünf Minuten. Anstatt also alle fünf Minuten die gesamten materialisierten Informationen neu zu generieren, aktualisieren kontinuierliche Aggregate die Ansicht inkrementell, indem sie Änderungen in der Originaltabelle verfolgen. Bei dem von uns betriebenen Maßstab können wir es uns einfach nicht leisten, unsere Haupthypertabelle alle fünf Minuten von oben nach unten zu scannen, daher war diese Funktionalität kontinuierlicher Aggregate eine grundlegende „Freischaltung“ für uns.
In diesen kontinuierlichen Aggregaten, die Einblicke hinter die Kulissen ermöglichen, fassen wir auch die meisten interessanten Statistiken zusammen
Dennoch begann die Datenbank ab einem bestimmten Punkt viel Arbeit zu leisten, um alle diese Rohdatensätze einzufügen und sie dann für die Bereitstellung zu materialisieren. Wir stießen auf einige Einschränkungen, wie viel wir aufnehmen und mithalten konnten.
Um unsere Aufnahmerate weiter auf das von uns benötigte Niveau zu erhöhen, haben wir die UDDSketch-Generierung von der Datenbank auf die Regionsschreiber verlagert. Jetzt speichern wir immer noch einen Teil der Datensätze als „Rohdatensätze“, aber den Rest übertragen wir auch in vorgenerierte Skizzen, die wir in der Datenbank speichern:
Table "insights.sketches" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time_dist | uddsketch | | | rows_dist | uddsketch | | | ...
Das Beste an UDDSketchs ist, dass es sehr einfach ist, die Skizzen kontinuierlich „aufzurollen“, um größere Zeitbereiche zu unterstützen. Mithilfe eines solchen Rollups können Skizzen, die engere Zeitbereiche abdecken, zu einer Skizze aggregiert werden, die einen weiten Zeitbereich abdeckt, sowohl beim Aufbau eines hierarchischen kontinuierlichen Aggregats als auch zur Abfragezeit.
Ein weiteres Tool, das wir genutzt haben, um sowohl eine schnelle Aufnahme als auch schnelle Abfragen sicherzustellen, sind Lesereplikate. Die Verwendung der Replikation ist in unserem Fall sowohl für die hohe Verfügbarkeit als auch für die Leistung von größter Bedeutung, da Insights eine wichtige, kundenorientierte Funktion für die Timescale-Plattform darstellt.
Unsere Hauptdatenbankinstanz ist ziemlich beschäftigt mit Massenarbeit, dem Schreiben von Daten, der Materialisierung der kontinuierlichen Aggregate, der Ausführung der Komprimierung und vielem mehr. (Mehr zur Komprimierung gleich.) Um die Belastung etwas zu verringern, lassen wir den Kunden des Replikatdienstes Anfragen von der Insights-Konsole lesen.
Schließlich mussten wir Hunderte von TB bequem in einen einzigen Timescale-Dienst unterbringen. Die Insights-Datenbank skaliert schnell: Als wir anfingen, lag sie in der Größenordnung von 100 TB, mittlerweile sind es über 350 TB (Tendenz steigend).
Um so viele Daten effizient zu speichern, haben wir es aktiviert
Wir erleben auf unserem Haupt-Hypertisch eine Komprimierungsrate von mehr als dem 20-fachen .
Ein weiterer großer Vorteil bei der Verwaltung einer sehr großen Hypertabelle war die Schemaveränderbarkeit komprimierter Daten. Wir haben unser ungefähres Schema in einem vorherigen Abschnitt beschrieben, aber wie Sie sich vorstellen können, ändern wir es häufig, um mehr Statistiken usw. hinzuzufügen – es ist sehr nützlich, dies direkt in der komprimierten Hypertabelle tun zu können.
Wir sind auch starke Nutzer des Daten-Tierings von Timescale. Diese Funktion wurde Anfang des Jahres in den Early Access eingeführt (warten Sie bald auf GA-News 🔥) und ermöglicht es uns, Hunderte von TBs über unsere Timescale-Datenbank zugänglich zu halten. Auch das Daten-Tiering hat sich als sehr effizient erwiesen: Auch hier sehen wir erstaunliche Komprimierungsraten, wobei 130 TB auf äußerst ressourceneffiziente 5 TB schrumpfen.
Der Prozess der Entwicklung von Insights hat uns gezeigt, wie weit unser Produkt tatsächlich gehen kann, aber das Beste war, ein paar Meilen in der Haut unserer Kunden zu laufen. Wir haben viel über die Benutzererfahrung bei der Skalierung von PostgreSQL mit Timescale gelernt und als Ingenieure hinter dem Produkt einige Dinge zu unserer To-Do-Liste hinzugefügt.
Lassen Sie uns alles durchgehen: das Gute und das Mittelmäßige.
Verzeihen Sie unsere Unbescheidenheit, aber wir waren manchmal ziemlich stolz auf unser Produkt. Das tägliche Einspeisen von Dutzenden Milliarden Datensätzen in eine einzige PostgreSQL-Datenbank mit bereits Hunderten von TB ist nicht zu verachten . Wir haben ein paar Wochen damit verbracht, die Datenbank zu optimieren, als sie hochgefahren wurde, aber jetzt funktioniert sie einfach , ohne Babysitting oder ständige Überwachung. (Beachten Sie, dass dies etwas anderes ist als nicht überwacht, es wird definitiv überwacht!)
Unser
Die Kompression hat bei uns sehr gut funktioniert. Wie wir im vorherigen Abschnitt berichteten, erzielten wir mit einer einfachen einzelnen „Segmentby“-Option beeindruckende Komprimierungsraten (20x!). Für uns war die Einrichtung und Anpassung der Richtlinie nicht schwer – obwohl wir diese Funktion natürlich eingebaut haben … man könnte sagen, dass wir einen leichten Vorteil haben. 🙂 Darüber hinaus hat die Möglichkeit, komprimierten Daten nahtlos neue Spalten hinzuzufügen, die Flexibilität und Anpassungsfähigkeit unserer Datenbank weiter erhöht. Wir haben diese Möglichkeit ohne Komplikationen genutzt.
Kontinuierliche Aggregate vereinfachten die Logik bei der Konstruktion verschiedener Zeiträume und rationalisierten die Datenanalyse und -verarbeitung. Wir haben Unmengen hierarchischer kontinuierlicher Aggregate verwendet.
Die in den Hyperfunktionen von Timecale enthaltenen Näherungsalgorithmen vereinfachten unsere Implementierung und skalierten unsere Analyse erheblich. Die Möglichkeit, Skizzen einfach zusammenzufassen, war auch entscheidend für die effiziente Unterstützung verschiedener Zeitbereiche und Zeitfenstergranularitäten in unseren kundenorientierten Insights-Dashboards.
Der „unendliche“ warme Speicher, der einer Timescale-Datenbank über Daten-Tiering zur Verfügung steht, war entscheidend für die Skalierung auf Hunderte von TB, mit viel Spielraum für Wachstum. Unsere aktuelle __ Daten-Tiering-Richtlinie __behält Datensätze drei Wochen lang im Hot Storage.
Schließlich nutzten wir die Möglichkeit, benutzerdefinierte Jobs zu erstellen, um die Beobachtbarkeit zu verbessern (z. B. die Überwachung des Jobverlaufs) und experimentelle Aktualisierungsstrategien zu implementieren.
Nachdem wir Ihnen all die großartigen Dinge erzählt haben, ist es an der Zeit, die weniger großartigen anzuerkennen. Nichts ist perfekt, auch Timescale nicht. Bei der Implementierung unserer Pipeline standen wir vor einigen Herausforderungen, die wir nicht als Missstände verstehen:
Die Beobachtbarkeit der Datenbank könnte in der Timescale-Plattform verbessert werden, insbesondere im Hinblick auf Jobs und die Leistung der Materialisierung kontinuierlicher Aggregate.
TimescaleDB bietet hauptsächlich Snapshot-basierte Ansichten, was es schwierig macht, Leistung und Trends im Zeitverlauf zu verstehen. Beispielsweise ist keine vorkonfigurierte Tabelle „Auftragsverlauf“ verfügbar. Schon früh bemerkten wir, dass die inkrementelle Materialisierung unserer kontinuierlichen Aggregate scheinbar immer länger dauerte – was schließlich zur Entdeckung eines Fehlers führte –, aber wir hatten keine Möglichkeit, den Umfang zu bestätigen oder zu quantifizieren.
Wie bereits erwähnt, ermöglichte uns die Möglichkeit, benutzerdefinierte Jobs zu definieren und sie innerhalb des Job-Frameworks von Timescale auszuführen, eine „ausreichend gute“ Version davon zu erstellen. Wir haben kontinuierlich Ansichten abgefragt, die wir im Laufe der Zeit überwachen wollten, und alle Änderungen in eine Hypertabelle eingefügt. Dies funktioniert vorerst für Insights, aber wir arbeiten auch daran, einige dieser Dinge in integrierte Funktionen umzuwandeln, weil wir glauben, dass sie von entscheidender Bedeutung sind, wenn man Timescale über den Punkt „Alles ist immer schnell“ hinaus skaliert .
Kontinuierliche Aggregate können schwierig zu erstellen sein, wenn die zugrunde liegenden Daten groß sind .
Die Verwendung der Option „__ WITH NO DATA“ beim Erstellen kontinuierlicher Aggregate ist ein Lebensretter. Es ist außerdem wichtig, mit Ihren Offsets für die Aktualisierungsrichtlinie umsichtig umzugehen, damit die Datenmenge, die Sie inkrementell aktualisieren, nicht versehentlich zu groß wird.
Selbst wenn Sie diesen Rat befolgen, könnten Sie dennoch ein kontinuierliches Aggregat erhalten, dessen Aktualisierung länger dauert als die Datenmenge, die Sie materialisieren möchten, z. B. 30 Minuten, um 15 Minuten Daten zu materialisieren. Dies liegt daran, dass die zugrunde liegende kontinuierliche Aggregataufgabe manchmal zu groß ist, um in den Speicher zu passen, und auf die Festplatte überläuft.
Wir sind auf dieses Problem gestoßen, das durch einen von uns gefundenen (jetzt behobenen) Off-by-One-Fehler verschärft wurde, der dazu führte, dass zusätzliche Blöcke in den Abfrageplan aufgenommen wurden, selbst wenn sie letztendlich keine Daten zur Materialisierung beisteuerten. Das Finden dieses Fehlers war eigentlich ein Fall von „Dogfoodception“: Wir haben dieses Leistungsproblem entdeckt, als wir Insights verwendeten, während wir es erstellten 🤯. Die Timing-Informationen, die wir in Insights sahen, deuteten darauf hin, dass hier etwas nicht stimmte, und wir haben das Problem mithilfe von EXPLAIN und einem Blick auf die Pläne aufgedeckt. Wir können Ihnen also sagen, dass es funktioniert!
Um die Materialisierung zu beschleunigen, haben wir letztendlich eine benutzerdefinierte Richtlinie für inkrementelle Aktualisierungen erstellt, die die Größe der zu aktualisierenden Inkremente begrenzt. Wir arbeiten daran herauszufinden, ob wir dies wieder ordnungsgemäß in TimescaleDB verallgemeinern können.
Veränderungen sind im großen Maßstab schwierig .
Sobald Ihre Daten eine bestimmte Größe erreicht haben, können einige DDL-Vorgänge (Schema-Änderung) in TimescaleDB länger als ideal dauern. Wir haben dies bereits in mehrfacher Hinsicht erlebt.
Beispielsweise wird das Hinzufügen neuer Indizes zu großen Hypertabellen zu einer Zeitübung. Da TimescaleDB derzeit die Verwendung von „CONCURRENTLY“ mit „CREATE INDEX“ derzeit nicht unterstützt, besteht die nächstbeste Option darin, die integrierte Methode zu verwenden, um den Index Stück für Stück zu erstellen. In unserem Fall müssen wir sofort damit beginnen, nachdem ein neuer Block erstellt wurde, sodass die Sperrung des „aktiven“ Blocks minimal ist. Das heißt, das Erstellen eines Index, wenn ein Block neu ist, bedeutet, dass er (fast) leer ist und daher schnell abgeschlossen werden kann und neue Einfügungen nicht blockiert.
Eine weitere Schwierigkeit besteht darin, kontinuierliche Aggregate zu aktualisieren, um neue Metriken (Spalten) hinzuzufügen. Kontinuierliche Aggregate unterstützen „ALTER“ derzeit nicht. Wenn wir Benutzern also eine neue Metrik zur Verfügung stellen möchten, erstellen wir eine völlig neue „Version“ des kontinuierlichen Aggregats, d. h. für das kontinuierliche Aggregat „foo“ hätten wir dann „foo_v2“, „foo_v3“ usw. Das ist nicht ideal, funktioniert aber derzeit.
Schließlich ist das Ändern der Komprimierungseinstellungen im großen Maßstab recht schwierig. Tatsächlich ist dies für uns derzeit praktisch nicht möglich, da dazu alle komprimierten Blöcke dekomprimiert, die Einstellungen geändert und dann erneut komprimiert werden müssten, was bei unserem aktuellen Maßstab nicht machbar ist.
Wir arbeiten weiterhin mit unseren Kollegen zusammen, um für all diese Dinge praktikable Lösungen zu finden. Nicht nur für uns, sondern für alle Timescale-Benutzer.
Das waren ziemlich viele Informationen, die man in einem Beitrag zusammenfassen konnte. Aber wenn Sie eine brauchen
Building Insights war eine tiefgreifende Erfahrung für unser Team. Wir haben aus erster Hand gesehen, wie weit wir Timescale bringen und beeindruckende Skalenwerte erreichen können. Die Schmerzpunkte, auf die wir im Laufe des Prozesses gestoßen sind, haben uns so viel Einfühlungsvermögen gegenüber den Kunden vermittelt – das ist das Schöne am Dogfooding.
Nächstes Jahr hoffe ich, einen weiteren Blogbeitrag darüber schreiben zu können, wie wir eine weitere Größenordnung mehr Datenbanken überwachen und wie wir die Erfahrung bei der Arbeit mit Timescale im großen Maßstab weiter verbessert haben.
Bis dann! 👋
Auch hier veröffentlicht.