
Ich begrüße Sie, habr.
Wenn jemand das System nutzt und auf ein Speicherleistungsproblem gestoßen ist (IO, verbrauchter Speicherplatz), dann sollte die Wahrscheinlichkeit, dass ein Blick auf ClickHouse als Alternative geworfen wurde, gegen eins streben. Diese Aussage impliziert, dass bereits eine Drittanbieterimplementierung als Metrik-Daemon verwendet wird, zum Beispiel oder .
ClickHouse löst die beschriebenen Probleme gut. Zum Beispiel passten nach dem Übertragen von 2TiB Daten aus whisper 300GiB hinein. Ich werde nicht näher auf den Vergleich eingehen, es gibt genügend Artikel zu diesem Thema. Außerdem war unser ClickHouse-Speicher bis vor kurzem nicht perfekt.
Probleme mit dem verbrauchten Speicherplatz
Auf den ersten Blick sollte alles gut funktionieren. Wir folgen , erstellen eine Konfiguration für das Metrikspeicher-Schema (siehe retention), und erstellen dann eine Tabelle gemäß den Empfehlungen des gewählten Backends für graphite-web: + oder , je nachdem, welcher Stack verwendet wird. Und… da wird eine Zeitbombe aktiviert.
Um zu verstehen, welche, muss man wissen, wie Einfügungen und der weitere Lebenszyklus von Daten in Tabellen der *MergeTree ClickHouse (Diagramme stammen aus der von Alexey Zatelepin):
- Ein Block
von Daten wird eingefügt. In unserem Fall sind das die eingehenden Metriken.Jeder solcher Block wird vor der Speicherung auf der Festplatte nach dem Schlüssel

- ORDER BY
, der beim Erstellen der Tabelle angegeben wurde, sortiert.Nach der Sortierung wird - ein Stück
(part) von Daten auf die Festplatte geschrieben.(partDer Server überwacht im Hintergrund, dass es nicht zu viele solcher Stücke gibt, und startet im Hintergrund

- Zusammenführungen
merge(, also Merge-Vorgänge.Der Server hört auf, selbst Merges zu starten, wenn keine Daten mehr aktiv in die


- Partition
partition() fließen, aber man kann den Vorgang manuell mit dem BefehlOPTIMIZEWenn in der Partition nur noch ein Stück übrig ist, kann man nicht mit einem normalen Befehl zusammenführen, man muss. - OPTIMIZE ... FINAL
So, die ersten Metriken kommen an. Und sie nehmen einen gewissen Platz in Anspruch. Nachfolgende Ereignisse können je nach vielen Faktoren variieren:
Der Partitionierungsschlüssel kann sowohl sehr klein (Tag) als auch sehr groß (mehrere Monate) sein.
- Der Partitionierungsschlüssel kann sowohl sehr klein (ein Tag) als auch sehr groß (mehrere Monate) sein.
- Die Retention-Konfiguration kann mehrere bedeutende Aggregationsschwellen innerhalb der aktiven Partition (wohin Metriken geschrieben werden) enthalten oder auch nicht.
- Wenn es sehr viele Daten gibt, werden die frühesten Teile, die durch Hintergrund-Merges bereits riesig sein können (bei der Auswahl eines suboptimalen Partitionierungsschlüssels), nicht mit den frischen kleinen Teilen zusammengeführt.
Und letztendlich endet alles gleich. Der Platz, den Metriken in ClickHouse einnehmen, wächst nur, wenn:
- nicht angewendet wird
So, die ersten Metriken kommen an. Und sie nehmen einen gewissen Platz in Anspruch. Nachfolgende Ereignisse können je nach vielen Faktoren variieren:manuell oder - keine Daten ständig in alle Partitionen eingefügt werden, um irgendwann einen Hintergrund-Merge zu starten.
Die zweite Methode scheint am einfachsten umsetzbar zu sein und ist daher wahrscheinlich falsch und wurde zuerst ausprobiert.
Ich habe ein ausreichend einfaches Skript in Python geschrieben, das gefälschte Metriken für jeden Tag der letzten 4 Jahre gesendet hat und stündlich über Cron ausgeführt wurde.
Da die gesamte Arbeit von ClickHouse DBMS darauf basiert, dass dieses System irgendwann die gesamte Hintergrundarbeit erledigen wird, aber unklar ist, wann, konnte ich nicht auf den Moment warten, an dem die alten riesigen Teile mit den neuen kleinen zu mergen begannen. Es wurde klar, dass ich einen Weg finden musste, um erzwungene Optimierungen zu automatisieren.

Informationen in den Systemtabellen von ClickHouse
Lassen Sie uns einen Blick auf die Struktur der Tabelle . Dies ist umfassende Informationen über jedes Teil aller Tabellen auf dem ClickHouse-Server. Sie enthält unter anderem folgende Spalten:
- Datenbankname (
database); - Tabellenname (
Tabelle); - Name und ID der Partition (
) fließen, aber man kann den Vorgang manuell mit dem Befehl&partition_id); - Wann das Stück erstellt wurde (
modification_time); - Minimales und maximales Datum im Stück (Partitionierung erfolgt nach Tagen) (
min_date&max_date);
Es gibt auch eine Tabelle , mit folgenden interessanten Feldern:
- Datenbankname (
Tables.database); - Tabellenname (
Tables.table); - Alter der Metrik, wann die nächste Aggregation angewendet werden soll (
age);
Also:
- Wir haben eine Tabelle der Teile und eine Tabelle der Aggregationsregeln.
- Wir kombinieren ihre Schnittmenge und erhalten alle Tabellen *GraphiteMergeTree.
- Wir suchen alle Partitionen, in denen:
- mehr als ein Stück
- oder der Zeitpunkt gekommen ist, die nächste Aggregationsregel anzuwenden, und
modification_timeälter als dieser Zeitpunkt ist.
Implementierung
Diese Abfrage
WÄHLEN
concat(p.database, '.', p.table) AS tabelle,
p.partition_id AS partition_id,
p.partition AS partition,
-- Die "älteste" Regel, die auf
-- eine Partition angewendet werden kann, aber nicht in der Zukunft, siehe (*)
max(g.age) AS alter,
-- Anzahl der Teile in der Partition
countDistinct(p.name) AS teile,
-- Die älteste Metrik in der Partition wird als 00:00:00 des folgenden Tages akzeptiert
toDateTime(max(p.max_date + 1)) AS max_time,
-- Wann die Partition optimiert werden sollte
max_time + alter AS rollup_time,
-- Wann das älteste Stück in der Partition aktualisiert wurde
min(p.modification_time) AS updated_at
VON system.parts AS p
INNER JOIN
(
-- Alle Regeln für alle Tabellen *GraphiteMergeTree
WÄHLEN
Tabellen.database AS database,
Tabellen.table AS tabelle,
alter
VON system.graphite_retentions
ARRAY JOIN Tabellen
GROUP BY
database,
tabelle,
alter
) AS g ON
(p.table = g.tabelle)
UND (p.database = g.database)
WO
-- Nur aktive Teile
p.active
-- (*) Und nur Zeilen, wo die Aggregationsregeln bereits angewendet werden sollten
UND ((toDateTime(p.max_date + 1) + g.age) < now())
GROUP BY
tabelle,
partition
HAVING
-- Nur Partitionen, die jünger sind als der Optimierungszeitpunkt
(updated_at 1)
ORDER BY
tabelle ASC,
partition ASC,
alter ASCgibt jede der Partitionen der Tabellen *GraphiteMergeTree zurück, deren Zusammenführung zu einer Freigabe von Speicherplatz führen sollte. Es bleibt nur noch, sie alle mit einer Anfrage zu durchlaufen. So, die ersten Metriken kommen an. Und sie nehmen einen gewissen Platz in Anspruch. Nachfolgende Ereignisse können je nach vielen Faktoren variieren:In der endgültigen Implementierung wurde auch berücksichtigt, dass Partitionen mit aktiven Aufzeichnungen nicht berührt werden müssen.
Genau das macht das Projekt . Ehemalige Kollegen von Yandex.Market haben es im Produkt getestet, das Ergebnis kann weiter unten gesehen werden.

Wenn das Programm auf einem Server mit ClickHouse gestartet wird, läuft es einfach im Dämonenmodus. Einmal pro Stunde wird eine Anfrage ausgeführt, um zu überprüfen, ob neue Partitionen älter als drei Tage aufgetaucht sind, die optimiert werden können.
In naher Zukunft – die Bereitstellung von mindestens deb-Paketen, und möglichst auch rpm.
Zum Abschluss
In den vergangenen neun Monaten habe ich in meiner Firma viel Zeit verbracht, indem ich an der Schnittstelle von ClickHouse und graphite-web gearbeitet habe. Das war eine gute Erfahrung, deren Ergebnis der bevorstehende Übergang von whisper zu ClickHouse als Metrik-Speicher ist. Ich hoffe, dass dieser Artikel eine Art Beginn eines Zyklus ist, wie wir verschiedene Teile dieses Stacks verbessert haben und was in Zukunft getan werden wird.
Für die Entwicklung der Anfrage wurden mehrere Liter Bier und Admin-Tage zusammen mit , wofür ich ihm meinen Dank aussprechen möchte. Und auch für das Durchsehen dieses Artikels.
Quelle: habr.com




