ClickHouse hat sich in den letzten Jahren als eine der führenden analytischen Spalten-Datenbanken etabliert, die besonders für schnelle Lese- und Schreiboperationen bei großen Datenmengen bekannt ist. Trotz ihrer vielen Vorteile steht die Datenbank bei der Verarbeitung extrem großer Datenmengen vor Herausforderungen, insbesondere bei Masseneinfügungen von Milliarden Datensätzen. CloudQuery, ein innovatives Cloud-Governance-Unternehmen, hat eine wegweisende Methode entwickelt, um dieses Problem erfolgreich zu lösen: das Insert-Splitter-Algorithmus mit UUID-Range-Bucketing. Dieses Verfahren stellt sicher, dass ClickHouse nicht an seine Speichergrenzen stößt und die Verarbeitung in kontrollierbaren, parallelen Schritten erfolgt. In diesem Beitrag beleuchten wir die technischen Hintergründe, die Herausforderungen und Lösungen sowie die beeindruckenden Produktiv-Ergebnisse dieser Technik.
ClickHouse und die Herausforderung großer Datenmengen ClickHouse beeindruckt durch seine Architektur für analytische Workloads mit extrem schnellem Zugang zu großen Datenvolumen. Die Datenbank arbeitet spaltenorientiert und nutzt minimalistische Indizierungen, was schnelle Abfragen auch über Billionen von Datenzeilen ermöglicht. Doch gerade bei sehr großen Bulk-Insert-Operationen zeigt sich eine Schwäche: ClickHouse lädt den gesamten Arbeitssatz im Speicher, bevor er ausgelagert wird. Diese Strategie führt bei enormen Datenvolumen zu erheblichen Speicherengpässen und im schlimmsten Fall zu Abbrüchen der Abfragen durch den OvercommitTracker. Diese Speicherproblematik entsteht insbesondere bei Operationen wie GROUP BY, ORDER BY oder bei der Verarbeitung von sehr großen Insert-Blöcken.
Standardmäßig versucht ClickHouse alles zunächst im RAM zu halten und erst bei Überschreiten bestimmter Grenzwerte wird auf Festplattenspeicher zurückgegriffen. Allerdings schaltet das Auslagern meist zu spät ein, sodass der Arbeitsspeicher schnell erschöpft ist. CloudQuery steht vor der Aufgabe, monatlich etwa sechs Milliarden Zeilen aus mehr als 2.500 Cloud-Konten und hunderten Kubernetes-Clustern zu verarbeiten. Dabei fallen bis zu vier Terabyte neue Daten pro Monat an.
Diese immense Menge von Echtzeit- und Konfigurationsdaten muss schnell in ClickHouse geladen und für Abfragen aufbereitet werden. Verteilte Bulk-Inserts – das Kernproblem Der ClickHouse-Client bündelt die Daten zeilenweise in sogenannten Blocks, die als Ganzes an den Server übertragen werden. Standardmäßig können hierbei bis zu einer Million Zeilen pro Block enthalten sein. Diese großen Blocks führen zu einem initialen Speicherverbrauch, der bei Milliarden von Zeilen schnell explodiert. Speziell bei zufällig verteilten Daten wie UUIDs ist es schwer, die Einfügungen so zu orchestrieren, dass die Speicherbelastung kontrolliert bleibt.
Zahlreiche Entwickler berichten von Memory-Limit-Fehlern, bei denen ClickHouse Abfragen abbricht, wenn der Speicherbedarf des laufenden INSERT die erlaubte Obergrenze überschreitet. Anpassungen an den Einstellungen wie max_bytes_before_external_group_by oder max_bytes_before_external_sort helfen nur mäßig, da ClickHouse vor dem Auslagern den kompletten Arbeitsdatensatz im Speicher halten möchte. Zudem schlägt selbst das Asynchronous Insert nicht die Brücke zu dauerhaft niedrigerem Speicherverbrauch, denn die Daten werden zunächst im RAM gepuffert. Strategien zur Lösung – was funktioniert und was nicht Es gibt eine Reihe von möglichen Lösungsansätzen, die CloudQuery getestet hat. Dazu zählen das Erhöhen des maximal verfügbaren Arbeitsspeichers, das Splitten großer Inserts in kleinere Zeitfenster, das Einführen von Messaging-Systemen wie Kafka als Zwischenspeicher sowie grundlegende Schemaänderungen und Partitionierungen.
Letztere sind zwar sehr effektiv, erfordern aber aufwendige Refaktorierungen und Backfill-Prozesse. Asynchrone Inserts sind eine empfohlene Praxis, reichen aber bei hohen Volumina nicht aus, da der RAM-Verbrauch erst beim tatsächlichen Absenden signifikant sinkt und anfängliche Spitzen nicht verhindert werden. Systeme mit Kafka und Materialized Views erhöhen die Infrastrukturkomplexität und bringen zusätzliche Wartungslasten mit sich. Schemaänderungen sind aus Flexibilitätsgründen oft nicht gewollt, da CloudQuery hunderte unterschiedliche Cloud Resources mit variierenden Datenstrukturen unterstützen muss. Insert-Splitter mit UUID-Range-Bucketing als Schlüsselinnovation CloudQuery entschied sich für eine deterministische, anwendungsseitige Lösung: den Insert-Splitter-Algorithmus, welcher UUID-Bereiche als Buckets für die Datenpartitionierung verwendet.
Die Idee dahinter ist, einen großen Einfügeprozess in mehrere kleine, überschaubare Teile zu zerlegen, die nacheinander oder parallel abgearbeitet werden können. Mit UUIDs als Schlüssel lassen sich die Datensätze effizient und gleichmäßig auf diese Buckets verteilen. Der Algorithmus funktioniert so, dass zunächst die Gesamtzahl der zu verarbeitenden Zeilen geschätzt wird. Daraus berechnet sich die benötigte Anzahl von Buckets als Potenz von zwei, sodass jeder Bucket ungefähr gleich viele Zeilen erhält. Anschließend werden die UUID-Ranges für diese Buckets definiert.
Getreu der Eigenheiten von ClickHouse, welches UUIDs anhand ihres zweiten Abschnitts sortiert, wird bei der Segmentierung vorsichtig vorgegangen, um eine gleichmäßige Verteilung sicherzustellen. Die Einfügungen erfolgen dann jeweils mit einem WHERE-Filter, der nur die UUIDs innerhalb eines bestimmten Buckets einschließt. Auf diese Weise werden kleinere Inserts erzeugt, die deutlich weniger Speicher beanspruchen und keine Memory-Spitzen mehr verursachen. Wichtig ist dabei, dass der Filter direkt in der innersten Selektionsabfrage genutzt wird, um ein vorzeitiges Materialisieren großer Resultate zu verhindern. Herausforderungen bei der Umsetzung UUIDs sind keine triviale Größe zum Sortieren oder Partitionieren.
ClickHouse sortiert UUIDs nur teilweise intuitiv, indem es eher den hinteren Teil für die Reihenfolge nimmt. Außerdem beeinflussen UUID-Versionbits (bei UUIDv4 beispielsweise die Bits mit 8, 9, A oder B im vierten Segment) die Verteilung. CloudQuery entwickelte eine genaue Logik, die diese technischen Besonderheiten berücksichtigt und dabei sowohl die Grenzen der einzelnen Buckets korrekt setzt als auch eine optimale Auslastung der Buckets garantiert. Zur Qualitätssicherung wurde eine Validierungsabfrage in ClickHouse umgesetzt, bei der eine Million synthetische UUIDs auf bis zu 1.024 Buckets verteilt wurden.
Das Ergebnis zeigte eine maximale Abweichung von weniger als 0,5 % zwischen den Bucket-Größen, womit eine extrem gleichmäßige Lastverteilung gewährleistet werden konnte. Produktive Ergebnisse und Performancegewinne Nach der Einführung der Insert-Splitter-Technik zeigten sich deutlich verbesserte Speicherwerte. Wo zuvor ein einziger Bulk-Insert 22 Sekunden dauerte und bis zu 8,5 GB RAM beanspruchte, konnte die gleiche Menge durch vier parallel oder sequenziell ausgeführte Teilinserts verarbeitet werden. Diese dauerten jeweils circa 5,8 Sekunden, beanspruchten nur rund 2,1 GB RAM und erreichten zusammen insgesamt eine vergleichbare Performance wie der Einzel-Insert, waren dabei aber viel zuverlässiger und stabiler. Noch wichtiger als die reine Performance war der enorme Zugewinn an Systemstabilität.
Memory-Limit-Fehler und Kills durch den OvercommitTracker traten nach Einführung der Technik praktisch nicht mehr auf. Die deterministische Verteilung nach UUID-Buckets sorgte für gleichmäßige Auslastung und ermöglichte sogar parallele Verarbeitung ohne Speicherüberlastung. CloudQuery profitierte so von einem wartungsarmen, verlässlichen Mechanismus, der ohne Änderungen am Schema, ohne zusätzliche Infrastruktur oder komplexe Konfigurationen funktioniert. Wichtige Erkenntnisse und Best Practices Die Methode ist kein Allheilmittel für jede ClickHouse-Situation, aber ein mächtiges Werkzeug im Werkzeugkasten für den Umgang mit großen Bulk-Inserts. Wichtig bleibt eine saubere Schema-Gestaltung, sinnvolle Sort-Keys und Partitionierungsstrategien sowie die Kontrolle der Parts-Anzahl und Merges, um Speicherverbrauch und Performance dauerhaft zu optimieren.
Operationen wie JOINs, GROUP BYs oder ORDERs bleiben weiterhin speicherintensiv und müssen separat betrachtet werden. Grundsätzlich gilt aber: Das Zerlegen großer Pipelines in handhabbare Chakren verhindert Memory-Spitzen und bietet bessere Steuerungsmöglichkeiten. CloudQuery rät, kleine Skripte und Tests vorab einzusetzen, um die Gültigkeit der Bucketing-Strategie zu prüfen, insbesondere wenn Datenstrukturen oder UUID-Generierung variieren. Ein tieferes Verständnis der Interna von ClickHouse und dem Umgang mit UUIDs ist dabei hilfreich. Fazit Der Insert-Splitter-Algorithmus mit UUID-Range-Bucketing stellt eine effektive und pragmatische Lösung dar, um die Speicherprobleme bei Milliarden-Zeilen-Insert-Operationen in ClickHouse zu bewältigen.