- Ubers Zahlungsplattform verwaltete seit 2017 angesammelte Ledger-Daten im Umfang von mehr als 1 Billion Einträgen und mehreren PB; deshalb wurde die gemischte Struktur aus DynamoDB, TerraBlob und LedgerStore zu einer LedgerStore-zentrierten Architektur migriert
- Weil die Kostenlast von DynamoDB hoch war und daher nur die jüngsten 12 Wochen an Daten vorgehalten wurden, wurde LedgerStore, ein append-only Ledger-Speicher, als langfristige Lösung gewählt
- Die Validierung kombinierte Shadow-Validierung, die den Produktions-Traffic spiegelt, mit einer Offline-Validierung durch Vergleich vollständiger Dumps, um Risiken bei aktuellen und kalten Daten getrennt zu prüfen
- Da Backfills eine größere Last als normaler Traffic erzeugen können, wurden sie schrittweise mit kleinen Batches, Idempotenz, Rate Control, Emergency Stop und dem Aussondern problematischer Records durchgeführt
- Die Umstellung wurde über mehrere Wochen konservativ ausgerollt und nach anfänglichem Fallback sowie einem Monat Datenhaltung in DynamoDB schließlich mit finalem Backup und Löschen der Tabellen ohne Downtime abgeschlossen
Migrationsziel und bisherige Speicherstruktur
- Ubers Zahlungsplattform Gulfstream nutzte bei ihrer Einführung 2017 DynamoDB als Speicher
- Als die Kosten von DynamoDB in Ubers Größenordnung stiegen, wurde die Speicherstruktur in drei Pfade aufgeteilt
- Aktuelle Hot Data der letzten 12 Wochen wurden in DynamoDB gespeichert
- Ältere Cold Data wurden in Ubers internem Blob-Store TerraBlob gespeichert
- In LedgerStore wurden bereits Daten geschrieben, und es wurde zum endgültigen Migrationsziel
- Der Umfang der Migration umfasste die Ledger-Daten des gesamten Uber-Geschäfts seit 2017
- Unveränderliche Records: 1.2 PB komprimiert
- Sekundärindizes: 0.5 PB unkomprimiert
- Ledger-Records können nach dem Schreiben praktisch nicht mehr geändert werden; wenn Korrekturen nötig sind, können die Daten der Sekundärindizes angepasst werden
Warum LedgerStore gewählt wurde
- LedgerStore ist eine append-only Datenbank im Ledger-Stil
- Das auf Zahlungsdaten ausgerichtete Design passte zu den Anforderungen von Gulfstream
- Verifizierbare Unveränderlichkeit, bei der sich per kryptografischer Signatur prüfen lässt, ob ein Record verändert wurde
- Hierarchischer Storage, der Hot Data und Cold Data passend zu Anfrageverarbeitung und Speicherkosten trennt
- Bessere Latenzeigenschaften für Sekundärindizes mit eventual consistency
- Die Reduzierung von drei Speichern auf einen vereinfachte den Storage-Zugriffscode von Gulfstream und das Design der Indexerstellung
- LedgerStore läuft On-Premise in Ubers Rechenzentren und kann dadurch geringere Netzwerklatenz bieten
- Die Migration zu LedgerStore brachte außerdem große wiederkehrende Kosteneinsparungen
Mit Shadow-Validierung geprüfte Stabilität des aktuellen Traffics
- Um zu beurteilen, ob das Backfill korrekt ist, wurden fünf Kriterien definiert
- Vollständigkeit: Alle Records wurden zurückgefüllt
- Korrektheit: Alle Records sind korrekt
- Last: LedgerStore kann die aktuelle Last verarbeiten
- Latenz: Die P99-Latenz von LedgerStore liegt im zulässigen Bereich
- Index-Latenz: Die Verzögerung bei der Hintergrunderstellung von Sekundärindizes liegt im zulässigen Bereich
- Die Shadow-Validierung verglich Antworten auf Basis des bisherigen Speichers mit Antworten, bei denen LedgerStore die Datenquelle war
- Ziel war es, Vollständigkeit und Korrektheit des Backfills nach dem Maßstab der Shadow-Validierung auf mindestens 99.99% zu bringen, mit einer Obergrenze von 99.9999%
- Eine Obergrenze war nötig, weil bei der Validierung großer Datenmengen die vollständige Untersuchung jedes Verdachtsfalls ein Projekt zum Stillstand bringen kann
- In historischen Datenmigrationen können fehlerhafte Writes aus frühen Entwicklungsphasen oder datenvolumenbedingte Beschädigungen enthalten sein
- Selbst wenn S3 11 Neunen an Haltbarkeit garantiert, sind bei 1 Billion Records 10 beschädigte Fälle zu erwarten
- Bei eventually consistent Indizes entstehen False Positives, wenn Records, die erst einige Sekunden später sichtbar werden, in der Shadow-Validierung als fehlend erscheinen
- Um 6 Neunen belastbar zu bestätigen, müssen 100 Millionen Vergleiche durchgeführt werden; bei 1.000 Vergleichen pro Sekunde ist dafür mehr als ein Tag Datensammlung nötig
- Für 7 Neunen müsste man unter denselben Bedingungen 12 Tage warten
- Durch das Spiegeln des Produktions-Traffics nach LedgerStore konnten gleichzeitig Last, Latenz, Index-Latenz und die Zuverlässigkeit des Zugriffscodes geprüft werden
- Während der Migration entdeckte Probleme bei Latenz und Index-Latenz führten zu mehreren Korrekturen
- Optimierung der Partition Keys, um die Verteilung der Indexdaten zu verbessern
- Behebung eines Indexproblems, das statt Point Lookups Record-Scans auslöste
- Live-Shadow-Validierung ist nützlich für aktuell genutzte Daten, bietet aber für die Gesamtheit historischer, kaum genutzter Daten nur eingeschränkte Garantien
Offline-Validierung und inkrementelles Backfill
- Die Offline-Validierung verglich die Gesamtdaten in LedgerStore mit Daten-Dumps aus DynamoDB
- Da Live-Traffic überwiegend auf aktuelle Daten zugreift, sind Probleme in Cold Data allein durch Shadow-Validierung schwer zu erkennen
- Records mit Datenproblemen mussten übersprungen werden, damit das Backfill weiterlaufen konnte; gleichzeitig musste auch die Möglichkeit von Bugs im Backfill-Job selbst berücksichtigt werden
- Der größte Validierungsjob umfasste 70 TB komprimierte Daten, geschätzt 300 TB unkomprimiert, und verglich in einem einzelnen Job 760 Milliarden Records
- Für einen Apache-Spark-Job in dieser Größenordnung war Data Shuffle erforderlich; dabei wurden Distributed Shuffle as a Service for Spark, Dynamic Resource Allocation und Speculative Execution gemeinsam genutzt
- Fehlende Records, die durch die Offline-Validierung gefunden wurden, dienten als Input für inkrementelle Backfills
- Durch wiederholte Validierung und Backfill wurde geprüft, ob wirklich alle Records geschrieben wurden
Betriebliche Probleme beim Backfill
- Backfills müssen in kleinem Maßstab beginnen und schrittweise bis an die Systemgrenzen skaliert werden
- Wer blind über die Grenze hinaus pusht, verursacht im eigenen System faktisch einen DDoS
- Erst müssen Bottlenecks gefunden und behoben werden, danach kann erneut skaliert werden
- Nach jedem Skalierungsschritt ist engmaschiges Monitoring nötig
- Wenn mehrere Jahre an Daten in wenigen Monaten zurückgefüllt werden, entsteht eine deutlich höhere Last als im normalen Traffic
- Verarbeitet die Produktion 1.000 Requests pro Sekunde, dann dauert ein Backfill von 100 Milliarden Records mit 10.000 Requests pro Sekunde 120 Tage
- Wenn ein laufender Backfill Störungen verursachen könnte, muss er sofort gestoppt werden
- Ein Backfill ist kein Job, der in einem Zug bis zum Ende läuft, sondern sollte in inkrementelle Batches aufgeteilt werden
- Jeder Batch sollte so klein sein, dass er in wenigen Minuten abgeschlossen werden kann
- Da ein Job mitten im Batch enden kann, muss er idempotent sein
- Nach Abschluss eines Batches werden Statistiken wie die Zahl gelesener und zurückgefüllter Records in Dateien geschrieben und aggregiert, um den Fortschritt zu verfolgen
- Ein sicheres Backfill braucht anpassbare Rate Control
- In Java/Scala kann dafür Guavas
RateLimiterverwendet werden - Wenn bei geringem Produktions-Traffic schneller gearbeitet werden kann, wird der Systemzustand überwacht und die RPS angepasst
- Uber passte die RPS per additive increase/multiplicative decrease an, behielt aus Sicherheitsgründen jedoch eine Obergrenze bei
- In Java/Scala kann dafür Guavas
- Bei Verdacht auf Fehler oder Überlastung muss sich ein Backfill schnell stoppen lassen
- Während eines Incidents sollte ein Backfill aus Vorsichtsgründen und zur Reduktion von Rauschen gestoppt werden
- Auch nach einem Incident kann die Wiederherstellung des Systems zusätzliche Last erzeugen
- Eine Emergency-Stop-Funktion hilft auch beim Debuggen größenbedingter Probleme
Große Dateien, Fehlertoleranz und Logging
- Für Daten-Dump-Dateien ist eine Größe von etwa 1 GB sinnvoll, mit ungefähr dem Zehnfachen Spielraum nach oben und unten
- Sind Dateien zu groß, können sie an MultiPart-Limits verschiedener Tools scheitern
- Sind Dateien zu klein, steigt ihre Zahl so stark, dass schon das Auflisten viel Zeit kostet
- Beim Ausführen von Shell-Befehlen kann man an ARGMAX-Grenzen stoßen
- Bei der Datentransformation im Backfill treten Datenqualitätsprobleme oder beschädigte Records zwangsläufig auf
- Problematische Records sind zufällig verteilt, daher kann der Job nicht jedes Mal gestoppt werden
- Gleichzeitig könnte es sich um einen Code-Bug handeln, daher dürfen sie auch nicht ignoriert werden
- Problem-Records werden separat gedumpt und ihre Statistiken überwacht
- Ist die Fehlerrate hoch, wird das Backfill manuell gestoppt, das Problem behoben und danach fortgesetzt
- Wegen RPC timeout können Writes von Records fehlschlagen
- Retries sind möglich, aber irgendwann muss unabhängig vom Grund aufgegeben und weitergemacht werden, damit der Gesamtjob vorankommt
- Auch wenn man für Debugging und Fortschrittskontrolle viele Logs schreiben möchte, kann dadurch erheblicher Druck auf die Logging-Infrastruktur entstehen
- Selbst wenn das Schreiben möglich ist, kann die aufzubewahrende Log-Menge übermäßig groß werden
- Für stark loggende Bereiche wird Rate Limiting angewendet
- Wenn Fehler selten auftreten, kann man auch alle Error-Logs aufzeichnen
Schrittweises Rollout und Entfernen des Fallbacks
- Die Umstellung auf LedgerStore senkte das Risiko nicht nur durch Validierung und Analyse der Backfill-Statistiken, sondern auch durch ein konservatives Rollout
- Das Rollout erfolgte über mehrere Wochen und in Abstimmung mit den On-Call-Engineers der wichtigsten aufrufenden Services
- Anfangs wurde ein Fallback genutzt, der Daten aus DynamoDB holte, wenn sie in LedgerStore nicht gefunden wurden
- Für jeden im Fallback-Log als fehlend markierten Record wurde erneut geprüft, ob er in LedgerStore tatsächlich fehlte
- Auch nach dem Entfernen des Fallbacks wurden die Daten in DynamoDB einen Monat lang beibehalten
- Danach wurden Writes nach DynamoDB gestoppt, ein finales Backup erstellt und die Tabellen gelöscht
- Die gesamte Migration dauerte zwei Jahre und wurde ohne Downtime oder Incident während oder nach der Migration abgeschlossen
1 Kommentare
Hacker-News-Kommentare
Ich frage mich, ob man 1,7 Petabyte Daten (1 Billion indexierte Datensätze) auf einen einzigen sehr leistungsstarken Bare-Metal-Server für höchstens ein paar Tausend Dollar im Monat packen und mit SQLite bereitstellen könnte.
Zum Beispiel so: https://use.expensify.com/blog/scaling-sqlite-to-4m-qps-on-a...
SQLite unterstützt eine Datenbank von maximal 281 Terabyte, vorausgesetzt, man findet eine Disk und ein Dateisystem, die eine Datei mit 281 Terabyte unterstützen. Trotzdem heißt es dort: Wenn absehbar ist, dass die Inhalte in den Terabyte-Bereich wachsen, sollte man eher eine zentralisierte Client/Server-Datenbank als SQLite in Betracht ziehen.
So viel Storage in einen einzelnen Server zu stecken, dürfte schwierig sein; ein Niveau von ein paar Tausend Dollar pro Monat erst recht. Auch SQLite passt für diesen Zweck nicht.
Ein Unternehmen mit genügend Kapital und organisatorischer Stärke kann eine eigene Cloud deutlich günstiger bauen, aber ein großer Teil der Rechnung besteht darin, Risiken auszulagern.
[0] https://www.sqlite.org/releaselog/3_33_0.html
[1] https://www.sqlite.org/limits.html (#12)
LedgerStore scheint nicht Open Source zu sein [1], und um Informationen dazu zu finden, muss man sich durch gegenseitig verlinkte Uber-Blogposts hangeln.
Der Beitrag von 2021 mit den meisten Informationen zu LedgerStore scheint dieser zu sein:
https://www.uber.com/en-US/blog/dynamodb-to-docstore-migrati...
[1]:https://github.com/uber
Insgesamt scheint Uber recht stark auf selbst bauen ausgerichtet zu sein. Sie kommen offenbar häufig zu dem Schluss, dass vorhandene Open-Source-Lösungen nicht ausreichen, und bauen dann lieber selbst. Das unterscheidet sich zum Beispiel von Facebooks Ansatz, MySQL mit MyRocks/RocksDB zu verbessern und als Open Source zu pflegen.
Wenn man den Artikel liest, wird ziemlich schnell klar, dass Uber DynamoDB falsch eingesetzt hat.
Für einige zentrale User Journeys brauchten sie offenbar starke Konsistenz, und für historische Transaktionen brauchten sie umfangreiches Data Warehousing.
Es ist seltsam, dass sie die DynamoDB-Struktur mit zwei Tabellen nicht zuerst in eine Architektur wie DynamoDB + Redshift umgebaut haben. Das ist ein ziemlich gängiges Pattern.
Um 2015 herum gab es eine Phase, in der coole Tech-Unternehmen wie Netflix, Spotify, SoundCloud und Uber viele Infrastruktur- und Datenbank-Tools gebaut haben.
Heutige Engineers sprechen oft in AWS-/Cloud-Begriffen.
Es wirkt erfrischend, dass es noch Organisationen gibt, die solche Tools selbst bauen.
Ich kenne die Wirtschaftlichkeit dieses konkreten Projekts nicht, aber DynamoDB ist wirklich teuer.
Eine Zeit lang dachte ich, alle anderen würden DynamoDB einfach falsch nutzen, indem sie Scans und Queries statt Point-Lookups auf vorberechneten Tabellen machen.
Aber selbst wenn man es wie eine verteilte Hash-Tabelle nutzt, zahlt man immer noch einen hohen Aufpreis.
Eine RCU liest bis zu 4 KB; um 100 MB zu lesen, braucht man also 100.000 RCU, das sind 30.000 Dollar pro Jahr bzw. 2.500 Dollar pro Monat. Wenn meine Rechnung stimmt, sehe ich preislich nichts Vergleichbares.
Ich frage mich, ob sie https://tigerbeetle.com geprüft haben.
Und Uber ist vermutlich eines der wenigen großen Unternehmen mit einem Supportvertrag mit der Zig Foundation.
Glückwunsch an die Beteiligten. Allerdings dürften allein die Kosten für den Betrieb dieses Teams ziemlich hoch sein und sich nicht allzu sehr von den 6 Millionen Dollar Einsparung unterscheiden; dazu kommt noch der Wartungsaufwand.
Es wirkt auch nicht sehr wahrscheinlich, dass ein Zahlungssystem eine langfristige Wette ist. Interessant, warum Teams solche Projekte übernehmen. Ist das eine Art Sunk Cost, weil man die Engineering-Teams ohnehin schon hat?
Dazwischen gibt es viel Raum für realistischere Kostenschätzungen.
Wenn Daten und Indizes 1,7 PB umfassen, kostet DynamoDB-Storage zum Listenpreis etwa 5,1 Millionen Dollar pro Jahr.
Das wirkt durchaus realistisch, um ein dediziertes System zu haben, das gut auf einen Kernbereich des Geschäfts zugeschnitten ist.
Das ist nicht so ein großes Unterfangen, wie manche sagen. Außerdem sind die Datensätze unveränderlich, was vieles deutlich einfacher macht.
Ich frage mich, ob das ein weiteres Ausnahmebeispiel dafür ist, dass es ab einer bestimmten Größenordnung vorteilhafter ist, selbst zu bauen. Die Größenordnung, die Uber bewältigen muss, ist ziemlich beeindruckend.
Aus dem Original geht nicht klar hervor, wie hoch die gesamten Betriebskosten des neu refaktorierten Dienstes sind. Müssen sie jetzt nicht eine eigene Datenbank und den Storage dahinter verwalten? Übersehe ich etwas?
Das Unternehmen verkaufte kein Endnutzerprodukt, sondern hatte ein verteiltes Dateisystem als Produkt.
Meiner Meinung nach haben die meisten Unternehmen nicht die Expertise, Systeme wie Datenbanken zu bauen; und selbst wenn es aus Kostensicht sinnvoll aussieht, haben sie in der Praxis Angst davor.
Ich sehe das als ein sehr gutes Beispiel dafür, wie teuer proprietäre cloudbasierte Datenspeicher sein können und dass eine Migration auf etwas anderes realistisch möglich ist.
Unter der Annahme rationalen Handelns scheint eine Abrechnung für Cloud-Service-Provider bevorzustehen.
Ich habe einmal als Contractor für ein kleines Unternehmen gearbeitet, das GCP Bigtable nutzte, um Reports über Daten aus einer 375-MB-MySQL-DB laufen zu lassen, und dafür über 11.000 Dollar im Monat zahlte.
Sie hatten einen Data Scientist direkt von der Uni eingestellt, der die Reports bauen sollte, und machten auf einem winzigen Datensatz etwas absurd Ineffizientes. Sie wollten, dass ich das am nächsten Tag für Kleingeld behebe, also habe ich abgelehnt.
Das ist einfach ein schlecht entworfenes System. Hätten sie eine On-Premises-Datenbank massiv überprovisioniert, um dasselbe zu tun, hätten sie genau dasselbe Problem gehabt.