Zu diesem Buch – sowie zu vielen weiteren O’Reilly-Büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei oreilly.plus+: www.oreilly.plus |
Konzepte für zuverlässige,
skalierbare und wartbare Systeme
Martin Kleppmann
Lektorat: Alexandra Follenius
Übersetzung: Frank Langenau
Korrektorat: Claudia Lötschert, www.richtiger-text.de
Satz: III-satz, www.drei-satz.de
Herstellung: Stefanie Weidner
Umschlaggestaltung: Karen Montgomery, Michael Oréal, www.oreal.de
Bibliografische Information Der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
ISBN:
Print 978-3-96009-075-5
PDF 978-3-96010-183-3
ePub 978-3-96010-184-0
mobi 978-3-96010-185-7
Dieses Buch erscheint in Kooperation mit O’Reilly Media, Inc. unter dem Imprint »O’REILLY«. O’REILLY ist ein Markenzeichen und eine eingetragene Marke von O’Reilly Media, Inc. und wird mit Einwilligung des Eigentümers verwendet.
1. Auflage 2019
Copyright © 2019 dpunkt.verlag GmbH
Wieblinger Weg 17
69123 Heidelberg
Authorized German translation of the English edition of Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems, ISBN 978-1-449-37332-0 © 2017 Martin Kleppmann. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same.
Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.
Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.
Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen.
5 4 3 2 1 0
Technologie übt in unserer Gesellschaft eine große Macht aus. Daten, Software und Kommunikation können missbraucht werden: um ungerechte Machtstrukturen tiefer zu verankern, Menschenrechte auszuhöhlen und Eigeninteressen zu schützen. Aber sie können auch für gute Zwecke genutzt werden: um Minderheiten Gehör zu verschaffen, Chancen für alle zu eröffnen und Katastrophen abzuwenden. Dieses Buch ist jedem gewidmet, der sich für das Gute einsetzt.
Einleitung
Teil I: Grundlagen von Datensystemen
1Zuverlässige, skalierbare und wartbare Anwendungen
Gedanken zu Datensystemen
Zuverlässigkeit
Hardwarefehler
Softwarefehler
Menschliche Fehler
Wie wichtig ist Zuverlässigkeit?
Skalierbarkeit
Lasten beschreiben
Performance beschreiben
Konzepte zur Bewältigung von Belastungen
Wartbarkeit
Betriebsfähigkeit: Den Betrieb erleichtern
Einfachheit: Komplexität im Griff
Evolvierbarkeit: Änderungen erleichtern
Zusammenfassung
2Datenmodelle und Abfragesprachen
Relationales Modell vs. Dokumentmodell
Die Geburt von NoSQL
Die objektrelationale Unverträglichkeit
n:1- und n:n-Beziehungen
Wiederholen Dokumentdatenbanken die Geschichte?
Heutige relationale Datenbanken vs. Dokumentdatenbanken
Abfragesprachen für Daten
Deklarative Abfragen im Web
MapReduce-Abfragen
Graphen-ähnliche Datenmodelle
Property-Graphen
Die Abfragesprache Cypher
Graph-Abfragen in SQL
Triple-Stores und SPARQL
Das Fundament: Datalog
Zusammenfassung
3Speichern und Abrufen
Datenstrukturen, auf denen Ihre Datenbank beruht
Hash-Indizes
SSTables und LSM-Bäume
B-Bäume
B-Bäume und LSM-Bäume im Vergleich
Andere Indizierungsstrukturen
Transaktionsverarbeitung oder Datenanalyse?
Data-Warehousing
Sterne und Schneeflocken: Schemas für die Analytik
Spaltenorientierte Speicherung
Spaltenkomprimierung
Sortierreihenfolge in spaltenorientierten Datenbanken
In spaltenorientierte Datenbanken schreiben
Aggregation: Datenwürfel und materialisierte Sichten
Zusammenfassung
4Codierung und Evolution
Formate für das Codieren von Daten
Sprachspezifische Formate
JSON, XML und binäre Varianten
Thrift und Protocol Buffers
Avro
Die Vorzüge von Schemas
Datenflussmodi
Datenfluss über Datenbanken
Datenfluss über Dienste: REST und RPC
Datenfluss beim Nachrichtenaustausch
Zusammenfassung
Teil II: Verteilte Daten
5Replikation
Leader und Follower
Synchrone und asynchrone Replikation
Neue Follower einrichten
Knotenausfälle behandeln
Implementierung von Replikationsprotokollen
Probleme mit der Replikationsverzögerung
Die eigenen Schreiboperationen lesen
Monotones Lesen
Präfixkonsistenz
Lösungen für Replikationsverzögerung
Multi-Leader-Replikation
Einsatzfälle für Multi-Leader-Replikation
Schreibkonflikte behandeln
Topologien für Multi-Leader-Replikation
Replikation ohne Leader
In die Datenbank schreiben, wenn ein Knoten ausgefallen ist
Grenzen der Quorumkonsistenz
Sloppy Quoren und Hinted Handoff
Parallele Schreibvorgänge erkennen
Zusammenfassung
6Partitionierung
Partitionierung und Replikation
Partitionierung von Schlüssel-Wert-Daten
Partitionierung nach Schlüsselbereich
Nach dem Hashwert des Schlüssels partitionieren
Schiefe Arbeitslasten und Entlastung von Hotspots
Partitionierung und Sekundärindizes
Sekundärindizes nach Dokument partitionieren
Sekundärindizes nach Begriff partitionieren
Rebalancing – Partitionen gleichmäßig belasten
Strategien für Rebalancing
Operationen: Automatisches oder manuelles Rebalancing
Anfragen weiterleiten
Parallele Abfrageausführung
Zusammenfassung
7Transaktionen
Das schwammige Konzept einer Transaktion
Die Bedeutung von ACID
Einzelobjekt- und Multiobjektoperationen
Schwache Isolationsstufen
Read Committed
Snapshot-Isolation und Repeatable Read
Verlorene Updates verhindern
Schreibversatz und Phantome
Serialisierbarkeit
Tatsächliche serielle Ausführung
Zwei-Phasen-Sperrverfahren (2PL)
Serialisierbare Snapshot-Isolation (SSI)
Zusammenfassung
8Die Probleme mit verteilten Systemen
Fehler und Teilausfälle
Cloud-Computing und Supercomputing
Unzuverlässige Netzwerke
Netzwerkfehler in der Praxis
Fehler erkennen
Timeouts und unbeschränkte Verzögerungen
Synchrone und asynchrone Netzwerke
Unzuverlässige Uhren
Monotone Uhren und Echtzeituhren
Uhrensynchronisierung und Genauigkeit
Sich auf synchronisierte Uhren verlassen
Prozesspausen
Wissen, Wahrheit und Lügen
Die Wahrheit wird von der Mehrheit definiert
Byzantinische Fehler
Systemmodell und Realität
Zusammenfassung
9Konsistenz und Konsens
Konsistenzgarantien
Linearisierbarkeit
Was macht ein System linearisierbar?
Auf Linearisierbarkeit setzen
Linearisierbare Systeme implementieren
Die Kosten der Linearisierbarkeit
Ordnungsgarantien
Ordnung und Kausalität
Ordnung nach Sequenznummern
Total geordneter Broadcast
Verteilte Transaktionen und Konsens
Atomarer Commit und Zwei-Phasen-Commit (2PC)
Verteilte Transaktionen in der Praxis
Fehlertoleranter Konsens
Mitgliedschafts- und Koordinationsdienste
Zusammenfassung
Teil III: Abgeleitete Daten
10Stapelverarbeitung
Stapelverarbeitung mit Unix-Tools
Einfache Protokollanalyse
Die Unix-Philosophie
MapReduce und verteilte Dateisysteme
MapReduce-Jobausführung
Reduce-seitige Verknüpfungen und Gruppierungen
Map-seitige Verknüpfungen
Die Ausgabe von Stapel-Workflows
Hadoop im Vergleich mit verteilten Datenbanken
Jenseits von MapReduce
Zwischenzustände materialisieren
Graphen und iterative Verarbeitung
Höhere APIs und Sprachen
Zusammenfassung
11Stream-Verarbeitung
Ereignisströme übertragen
Nachrichtensysteme
Partitionierte Protokolle
Datenbanken und Streams
Systeme synchron halten
Erfassen von Datenänderungen
Event Sourcing
Zustand, Streams und Unveränderlichkeit
Streams verarbeiten
Anwendungen der Stream-Verarbeitung
Überlegungen zur Zeit
Stream-Joins
Fehlertoleranz
Zusammenfassung
12Die Zukunft von Datensystemen
Datenintegration
Spezialisierte Tools durch Ableiten von Daten kombinieren
Batch- und Stream-Verarbeitung
Die Entflechtung von Datenbanken
Zusammenstellung verschiedener Datenspeichertechniken
Anwendungen datenflussorientiert entwickeln
Abgeleitete Zustände beobachten
Auf der Suche nach Korrektheit
Das Ende-zu-Ende-Argument für Datenbanken
Durchsetzung von Einschränkungen
Zeitnähe und Integrität
Vertrauen ist gut, Kontrolle ist besser
Das Richtige tun
Prädiktive Analytik
Datenschutz und Nachverfolgung
Zusammenfassung
13Glossar
Index
Computing ist Popkultur. […] Die Popkultur verachtet die Geschichte. In der Popkultur dreht sich alles um Identität und das Gefühl mitzumachen. Sie hat nichts mit Zusammenarbeit, Vergangenheit oder Zukunft zu tun – es geht um das Leben in der Gegenwart. Ich denke, das Gleiche gilt für die meisten Leute, die Code für Geld schreiben. Sie haben keine Ahnung, woher [ihre Kultur kommt].
– Alan Kay, im Interview mit Dr Dobb’s Journal (2012)
Wenn Sie in den letzten Jahren in der Softwareentwicklung gearbeitet haben und hier vor allem mit Server- und Backendsystemen, dann sind Sie wahrscheinlich mit einer Fülle von Schlagwörtern rund um die Speicherung und Verarbeitung von Daten bombardiert worden. NoSQL! Big Data! Skalierbarkeit! Sharding! Konsistenz! ACID! CAP-Theorem! Cloud-Dienste! MapReduce! Echtzeit!
Das letzte Jahrzehnt hat viele interessante Entwicklungen hervorgebracht, und zwar bei Datenbanken, in verteilten Systemen und in der Art und Weise, wie wir Anwendungen darauf aufbauen. Für diese Entwicklungen gibt es vielfältige Triebkräfte:
Datenintensive Anwendungen erweitern die Grenzen des Machbaren, indem sie diese technologischen Entwicklungen nutzen. Eine Anwendung bezeichnen wir als datenintensiv, wenn sie vorrangig Probleme in Bezug auf Daten – die Menge der Daten, ihre Komplexität oder die Geschwindigkeit, mit der sie sich verändern – zu lösen hat, im Unterschied zu rechenintensiv, wenn die CPU-Takte einen Engpass darstellen.
Die Werkzeuge und Techniken, mit denen datenintensive Anwendungen Daten speichern und verarbeiten, haben sich diesen Änderungen schnell angepasst. Neue Arten von Datenbanksystemen (»NoSQL«) haben viel Aufmerksamkeit erregt, doch auch Nachrichtenwarteschlangen, Caches, Suchindizes, Frameworks für Batch- und Stream-Verarbeitung sowie verwandte Techniken sind auch sehr wichtig. Viele Anwendungen kombinieren diese Techniken.
Die diesbezüglichen Schlagwörter kennzeichnen die Begeisterung für die neuen Möglichkeiten, was prinzipiell zu begrüßen ist. Allerdings brauchen wir als Softwareentwickler und -architekten ein technisch genaues und präzises Verständnis für die verschiedenen Techniken und deren Abwägungen, wenn wir gute Anwendungen erstellen wollen. Für dieses Verständnis dürfen wir uns nicht mit Schlagwörtern begnügen, sondern müssen tiefer eintauchen.
Glücklicherweise stehen hinter diesen rasanten technischen Veränderungen dauerhafte Prinzipien, die ihre Gültigkeit behalten, unabhängig davon, welche Version eines bestimmten Tools Sie verwenden. Wenn Sie diese Prinzipien verstehen, können Sie sehen, wozu jedes Werkzeug passt, wie Sie es zweckmäßig einsetzen und wie Sie seine Fallstricke vermeiden. Hier kommt dieses Buch ins Spiel.
Es soll Ihnen helfen, sich in der vielfältigen und sich schnell verändernden Landschaft der Techniken zum Verarbeiten und Speichern von Daten zurechtzufinden. Dabei ist das Buch weder ein Tutorial für ein bestimmtes Tool noch ein Lehrbuch voller trockener Theorie. Vielmehr sehen wir uns Beispiele erfolgreicher Datensysteme an: Techniken, die die Grundlage vieler bekannter Anwendungen bilden und die täglich den Anforderungen an Skalierbarkeit, Leistung und Zuverlässigkeit in der Produktion gerecht werden müssen.
Wir dringen in die Interna dieser Systeme ein, nehmen ihre Schlüsselalgorithmen auseinander und diskutieren ihre Prinzipien und Abwägungen, die sie treffen müssen. Dabei streben wir nach zweckmäßigen Methoden, um über Datensysteme nachzudenken – nicht nur darüber, wie sie funktionieren, sondern auch, warum sie auf diese Weise funktionieren und welche Fragen wir stellen müssen.
Nachdem Sie dieses Buch gelesen haben, befinden Sie sich in einer komfortablen Position, um zu entscheiden, welche Technik für welchen Zweck geeignet ist, und um zu verstehen, wie sich die Tools kombinieren lassen, um die Grundlage für eine gute Anwendungsarchitektur zu bilden. Zwar werden Sie Ihre eigene Datenbank noch nicht von Grund auf neu erstellen können, doch zum Glück ist das auch kaum notwendig. Allerdings werden Sie ein gutes Gespür dafür entwickeln, was Ihre Systeme im Verborgenen tun, sodass Sie über ihr Verhalten nachdenken, gute Entwurfsentscheidungen treffen und eventuelle Probleme aufspüren können.
Wenn Sie Anwendungen entwickeln, die Daten auf einer Art Server oder Backend speichern oder verarbeiten, und Ihre Anwendungen das Internet nutzen (zum Beispiel Webanwendungen, mobile Apps oder Sensoren mit Internetanschluss), dann ist dieses Buch genau richtig für Sie.
Die Zielgruppe dieses Buchs sind Softwareentwickler, Softwarearchitekten und technische Führungskräfte, die gerne programmieren. Es ist vor allem relevant, wenn Sie Entscheidungen über die Architektur der Systeme treffen müssen, an denen Sie arbeiten – wenn Sie zum Beispiel Tools für ein bestimmtes Problem auswählen und herausfinden müssen, wie Sie sie am besten anwenden. Doch selbst wenn Sie keinen Einfluss auf die Auswahl Ihrer Tools haben, hilft Ihnen das Buch, deren Stärken und Schwächen besser zu verstehen.
Sie sollten einige Erfahrungen im Erstellen von webbasierten Anwendungen oder Netzwerkdiensten mitbringen und mit relationalen Datenbanken und SQL vertraut sein. Wenn Sie nichtrelationale Datenbanken und andere datenbezogene Tools kennen, ist das prinzipiell von Vorteil, aber nicht unbedingt erforderlich. Hilfreich ist ein allgemeines Verständnis der gängigen Netzwerkprotokolle wie zum Beispiel TCP und HTTP. Welche Programmiersprache oder welches Framework Sie wählen, ist für dieses Buch nicht entscheidend.
Wenn einer der folgenden Punkte auf Sie zutrifft, wird Ihnen dieses Buch nützlich sein:
Wenn es um skalierbare Datensysteme geht, hört man manchmal Meinungen wie »Du bist nicht Google oder Amazon. Mach dir keine Gedanken um die Skalierbarkeit und nimm einfach eine relationale Datenbank.« In dieser Aussage liegt etwas Wahres: Ein System zu erstellen für eine Skalierung, die Sie noch nicht brauchen, ist unnützer Aufwand und kann Sie in ein unflexibles Design zwängen. Letztlich handelt es sich dabei um eine Form von vorschneller Optimierung. Allerdings ist es auch wichtig, das richtige Tool für den Job auszuwählen, und verschiedene Techniken zeichnen sich jeweils durch ihre eigenen Stärken und Schwächen aus. Wie wir sehen werden, sind relationale Datenbanken zwar wichtig, aber kein Allheilmittel für den Umgang mit Daten.
Dieses Buch liefert keine detaillierten Anleitungen, wie Sie bestimmte Softwarepakete oder APIs installieren oder verwenden. Hierfür gibt es genügend Dokumentationen. Stattdessen erörtern wir die verschiedenen Prinzipien und Abwägungen, die für Datensysteme maßgeblich sind, und wir analysieren die verschiedenen Entwurfsentscheidungen, die für verschiedene Produkte typisch sind.
In den E-Book-Ausgaben sind Links zum vollständigen Text von Onlinequellen enthalten. Zwar wurden alle Links zeitnah zur Veröffentlichung überprüft, doch leider liegt es in der Natur des Webs, dass Links häufig ungültig werden. Falls Sie einen defekten Link finden oder die gedruckte Ausgabe dieses Buchs lesen, können Sie mit einer Suchmaschine nach den Quellen suchen. Bei akademischen Arbeiten können Sie nach dem Titel in Google Scholar nach frei zugänglichen PDFDateien suchen. Alternativ finden Sie alle Quellen unter https://github.com/ept/ddia-references, wo wir aktuelle Links pflegen.
Wir betrachten vor allem die Architektur von Datensystemen und ihre Integration in datenintensive Anwendungen. Der Platz im Buch reicht nicht aus, um Bereitstellung, Betrieb, Sicherheit, Verwaltung und andere Bereiche zu behandeln – solchen komplexen und wichtigen Themen könnten wir nicht gerecht werden, wenn wir sie zu oberflächlichen Randbemerkungen in diesem Buch machen. Sie verdienen eigene Bücher.
Viele der im Buch beschriebenen Techniken lassen sich dem Schlagwort Big Data zuordnen. Allerdings ist der Begriff »Big Data« so überstrapaziert und ziemlich schwammig definiert, dass er für eine ernsthafte technische Diskussion nicht viel taugt. Dieses Buch verwendet weniger zweideutige Begriffe, wie zum Beispiel Einzelknotensysteme vs. verteilte Systeme oder online/interaktiv vs. offline/Batch-Verarbeitungssysteme.
Dieses Buch betont Freie Software und Open-Source-Software, denn wenn man Quellcode selbst lesen, ändern und ausführen kann, versteht man viel besser, wie etwas im Detail funktioniert. Bei offenen Plattformen ist auch die Gefahr einer Anbieterabhängigkeit geringer. Wo es passend erscheint, diskutieren wir aber auch proprietäre Software (Closed-Source-Software, Software as a Service oder firmeninterne Software, die lediglich in der Literatur beschrieben, aber nicht als Code veröffentlicht wird).
Dieses Buch ist in drei Teile gegliedert:
Das meiste von dem, was wir in diesem Buch besprechen, ist bereits an anderer Stelle in der einen oder anderen Form gesagt worden – auf Konferenzen, in Forschungsarbeiten, Blog-Posts, Code, Bugtracker-Berichten, Mailinglisten und dem mündlich überlieferten technischen Mythos. Dieses Buch fasst die wichtigsten Ideen aus vielen verschiedenen Quellen zusammen und gibt im gesamten Text Verweise auf die Originalliteratur an. Die Verweise am Ende jedes Kapitels bieten sich an, wenn Sie in einem Themenbereich weiter recherchieren wollen. Zudem sind die meisten davon online frei verfügbar.
Nachdem die englische Originalfassung dieses Buchs viele begeisterte Leser gefunden hat, war ich sehr erfreut von den Plänen zu erfahren, Designing Data-Intensive Applications in mehrere andere Sprachen zu übersetzen. Zu den meisten Übersetzungen, zum Beispiel auf Koreanisch, Russisch und Polnisch, habe ich allerdings keinen Beitrag leisten können, weil ich in Sprachen leider nicht so bewandert bin.
Die deutsche Übersetzung ist jedoch ein Ausnahmefall: Zwar wohne ich inzwischen seit 15 Jahren in Großbritannien, aber da ich in Deutschland aufgewachsen bin, ist mein Deutsch immerhin gut genug, um beim Korrekturlesen der deutschen Übersetzung zu helfen. Ich war von Frank Langenaus Übersetzung von Anfang an beeindruckt: Er hat den beabsichtigten Sinn präzise erfasst und zugleich gute, idiomatische Ausdrucksweisen gefunden – viel besser, als ich es selbst geschafft hätte.
Das vorliegende Buch habe ich komplett überprüft und bei der Übersetzung begleitet; dabei haben wir auch den Inhalt auf den neuesten Stand gebracht. Wir sind bei der Übersetzung auf einige Details gestoßen, die im englischen Original unklar formuliert waren: Diese haben wir nicht nur in der deutschen Fassung korrigiert, sondern auch als Verbesserungen in die nächste Aktualisierung der englischen Ausgabe übernommen. Somit ist die Übersetzung dem Buch doppelt zugutegekommen!
Mein großer Dank gilt also Frank Langenau für seine hervorragende Übersetzung des Buchs. Ebenfalls danken möchte ich Alexandra Follenius und dem Team bei O’Reilly Deutschland.
Dieses Buch verschmilzt und systematisiert viele Ideen und Erkenntnisse anderer Menschen, wobei es Erfahrungen sowohl aus der akademischen Forschung als auch der industriellen Praxis zusammenbringt. In der Datenverarbeitung fühlen wir uns angezogen von neuen und modischen Dingen, doch meiner Ansicht nach können wir noch jede Menge lernen aus den Dingen, die in der Vergangenheit getan wurden. Im Buch finden Sie über 800 Verweise auf Artikel, Blogposts, Vorträge, Dokumentationen und mehr, die auch für mich wertvolle Lernunterlagen waren. Ich bin den Autoren dieses Materials sehr dankbar, dass sie ihr Wissen teilen.
Auch aus persönlichen Gesprächen habe ich viel mitgenommen, dank einer großen Anzahl von Menschen, die sich die Zeit genommen haben, Ideen zu diskutieren oder mir Sachverhalte geduldig zu erklären. Insbesondere möchte ich mich bedanken bei Joe Adler, Ross Anderson, Peter Bailis, Márton Balassi, Alastair Beresford, Mark Callaghan, Mat Clayton, Patrick Collison, Sean Cribbs, Shirshanka Das, Niklas Ekström, Stephan Ewen, Alan Fekete, Gyula Fóra, Camille Fournier, Andres Freund, John Garbutt, Seth Gilbert, Tom Haggett, Pat Helland, Joe Hellerstein, Jakob Homan, Heidi Howard, John Hugg, Julian Hyde, Conrad Irwin, Evan Jones, Flavio Junqueira, Jessica Kerr, Kyle Kingsbury, Jay Kreps, Carl Lerche, Nicolas Liochon, Steve Loughran, Lee Mallabone, Nathan Marz, Caitie McCaffrey, Josie McLellan, Christopher Meiklejohn, Ian Meyers, Neha Narkhede, Neha Narula, Cathy O’Neil, Onora O’Neill, Ludovic Orban, Zoran Perkov, Julia Powles, Chris Riccomini, Henry Robinson, David Rosenthal, Jennifer Rullmann, Matthew Sackman, Martin Scholl, Amit Sela, Gwen Shapira, Greg Spurrier, Sam Stokes, Ben Stopford, Tom Stuart, Diana Vasile, Rahul Vohra, Pete Warden und Brett Wooldridge.
Weitere Personen haben beim Schreiben dieses Buchs wertvolle Hilfe geleistet, indem sie Entwürfe begutachtet und Feedback gegeben haben. Für diese Beiträge danke ich Raul Agepati, Tyler Akidau, Mattias Andersson, Sasha Baranov, Veena Basavaraj, David Beyer, Jim Brikman, Paul Carey, Raul Castro Fernandez, Joseph Chow, Derek Elkins, Sam Elliott, Alexander Gallego, Mark Grover, Stu Halloway, Heidi Howard, Nicola Kleppmann, Stefan Kruppa, Bjorn Madsen, Sander Mak, Stefan Podkowinski, Phil Potter, Hamid Ramazani, Sam Stokes, and Ben Summers. Selbstverständlich übernehme ich die gesamte Verantwortung für alle verbliebenen Fehler oder nicht akzeptable Meinungen in diesem Buch.
Für die Unterstützung, dass dieses Buch Wirklichkeit werden konnte, und für ihre Geduld mit meinem langsamen Schreiben und den ausgefallenen Anfragen danke ich meinen Redakteuren Marie Beaugureau, Mike Loukides, Ann Spencer und dem ganzen Team bei O’Reilly. Bedanken möchte ich mich auch bei Rachel Head für Ihre Hilfe, die richtigen Worte zu finden. Dafür, dass sie mir die Zeit und Freiheit gegeben haben, trotz anderweitiger beruflicher Verpflichtungen zu schreiben, danke ich Alastair Beresford, Susan Goodhue, Neha Narkhede und Kevin Scott. Ein ganz besonderer Dank geht an Shabbir Diwan und Edie Freedman, die mit großer Sorgfalt die Karten zu den Kapiteln illustriert haben. Es ist wunderbar, dass sie die unkonventionelle Idee aufgegriffen haben, Landkarten zu erstellen, und dass sie sie so schön und ansprechend gestaltet haben.
Schließlich gilt meine Liebe meiner Familie und meinen Freunden, ohne die ich das Schreiben, das fast vier Jahre gedauert hat, nicht hätte durchstehen können. Ihr seid die Besten.
Die ersten vier Kapitel beschäftigen sich mit den fundamentalen Ideen, die auf alle Datensysteme anwendbar sind, egal, ob sie auf einem einzelnen Computer laufen oder über einen Cluster von Computern verteilt sind:
Später wendet sich Teil II den speziellen Fragen verteilter Datensysteme zu.
Das Internet wurde so gut gemacht, dass die meisten Menschen es als eine natürliche Ressource wie den Pazifischen Ozean betrachten, und nicht als etwas, das vom Menschen geschaffen wurde. Wann war das letzte Mal eine Technologie in einer solchen Größenordnung so fehlerfrei?
– Alan Kay im Interview mit Dr Dobb’s Journal (2012)
Viele Anwendungen sind heutzutage datenintensiv im Gegensatz zu rechenintensiv. Die CPU-Leistung an sich ist für diese Anwendungen kaum ein begrenzender Faktor – größere Probleme ergeben sich üblicherweise aus dem Umfang der Daten, ihrer Komplexität und der Geschwindigkeit, mit der sie sich verändern.
Eine datenintensive Anwendung besteht normalerweise aus Standardbausteinen, die häufig benötigte Funktionalität bereitstellen. Zum Beispiel müssen viele Anwendungen
Sollte das zu offensichtlich klingen, dann nur, weil diese Datensysteme eine so erfolgreiche Abstraktion sind: Wir verwenden sie die ganze Zeit, ohne groß darüber nachzudenken. Wenn ein Entwickler eine Anwendung erstellt, wird er kaum davon träumen, ein neues Speichermodul von Grund auf neu zu schreiben, denn für diese Aufgabe sind Datenbanken prädestiniert.
Die Realität sieht aber nicht so einfach aus. Es existieren viele Datenbanksysteme mit unterschiedlichen Eigenschaften, weil verschiedene Anwendungen unterschiedliche Anforderungen stellen. Für das Zwischenspeichern gibt es verschiedene Methoden, das Gleiche gilt für das Erstellen von Indizes usw. Wenn wir eine Anwendung erstellen, müssen wir immer noch herausfinden, welche Werkzeuge und welche Ansätze für die konkrete Aufgabe am besten geeignet sind. Und falls ein einzelnes Werkzeug diese Aufgabe nicht allein bewältigen kann, ist es mitunter schwierig, passende Tools zu kombinieren.
Dieses Buch führt Sie sowohl durch die Prinzipien als auch die praktischen Aspekte von Datensystemen und zeigt, wie Sie damit datenintensive Anwendungen erstellen können. Wir untersuchen, was verschiedene Werkzeuge gemeinsam haben, was sie unterscheidet und wie sie zu ihren Eigenschaften kommen.
In diesem Kapitel untersuchen wir zunächst die Grundlagen für das, was wir erreichen wollen: zuverlässige, skalierbare und wartbare Datensysteme. Wir machen deutlich, was diese Dinge bedeuten, umreißen Methoden, sie zu analysieren, und wenden uns den Basics zu, die für die späteren Kapitel erforderlich sind. In den folgenden Kapiteln fahren wir mit den einzelnen Ebenen nacheinander fort und sehen uns dabei die verschiedenen Entwurfsentscheidungen an, die beim Arbeiten an einer datenintensiven Anwendung betrachtet werden müssen.
In der Regel stellen wir uns Datenbanken, Warteschlangen, Caches usw. als vollkommen verschiedene Kategorien von Werkzeugen vor. Obwohl eine Datenbank und eine Nachrichtenwarteschlange einige oberflächliche Berührungspunkte aufweisen – beide speichern Daten für einen gewissen Zeitraum –, unterscheiden sie sich in ihren Zugriffsmustern. Das bedeutet verschiedene Leistungscharakteristika und somit sehr verschiedene Implementierungen.
Warum sollten wir sie alle unter einem Sammelbegriff wie Datensysteme zusammenfassen?
1In den letzten Jahren sind zahlreiche neue Tools für das Speichern und Verarbeiten von Daten entstanden. Optimiert für eine breite Vielfalt von Einsatzfällen lassen sie sich nicht mehr streng den herkömmlichen Kategorien zuordnen [1]. So gibt es zum Beispiel Datenspeicher, die auch als Nachrichtenwarteschlangen dienen (Redis), und Nachrichtenwarteschlangen mit datenbankähnlichen Beständigkeitsgarantien (Apache Kafka). Die Grenzen zwischen den Kategorien verschwimmen immer weiter.
Zweitens stellen heute immer mehr Anwendungen so anspruchsvolle oder breit gefächerte Anforderungen, dass ein einzelnes Tool nicht mehr sämtliche Ansprüche an die Verarbeitung und Speicherung der Daten realisieren kann. Stattdessen teilt man die Arbeit in Aufgaben auf, die ein einzelnes Tool effizient durchführen kann, und der Anwendungscode verknüpft diese verschiedenen Tools.
Haben Sie zum Beispiel eine von der Anwendung verwaltete Caching-Ebene (etwa mit einem Cache-Server wie Memcached) oder einen Server für die Volltextsuche (wie zum Beispiel Elasticsearch oder Solr) eingerichtet, die von Ihrer Hauptdatenbank getrennt sind, ist normalerweise der Code der Anwendung dafür zuständig, diese Caches und Indizes mit der Hauptdatenbank synchron zu halten. Abbildung 1-1 veranschaulicht dieses Prinzip (mehr Einzelheiten folgen in späteren Kapiteln).
Wenn Sie mehrere Tools kombinieren, um einen Dienst zu realisieren, verbirgt die Oberfläche des Diensts oder die API1 normalerweise diese Implementierungsdetails vor den Clients. Praktisch haben Sie nun ein neues spezialisiertes Datensystem aus kleineren, universellen Komponenten erzeugt. Das zusammengesetzte Datensystem kann bestimmte Garantien bieten: beispielsweise, dass der Cache korrekt ungültig gemacht oder bei Schreibvorgängen aktualisiert wird, sodass externe Clients konsistente Ergebnisse sehen. Jetzt sind Sie nicht nur Anwendungsentwickler, sondern auch Datensystemdesigner.
Beim Entwurf eines Datensystems oder eines Diensts tauchen viele knifflige Fragen auf. Wie stellen Sie sicher, dass die Daten korrekt und vollständig bleiben, selbst wenn intern etwas schiefläuft? Wie bieten Sie den Clients eine konstant gute Performance, selbst wenn Teile Ihres Systems ausfallen? Wie skalieren Sie, um einer wachsenden Belastung gerecht zu werden? Wie sieht eine gute API für den Dienst aus?
Viele Faktoren können das Design eines Datensystems beeinflussen, unter anderem die Fertigkeiten und Erfahrungen der beteiligten Entwickler, Abhängigkeiten von einem Legacysystem, die Lieferzeit, die Toleranz Ihres Unternehmens gegenüber verschiedenen Arten von Risiken, regulatorische Beschränkungen usw. Derartige Faktoren hängen stark von der jeweiligen Situation ab.
In diesem Buch konzentrieren wir uns auf drei Aspekte, die in den meisten Softwaresystemen wichtig sind:
Zuverlässigkeit
Das System sollte auch bei Widrigkeiten (Hardware- oder Softwarefehlern und sogar menschlichem Versagen) weiterhin korrekt arbeiten (die richtige Funktion auf dem gewünschten Leistungsniveau ausführen). Siehe Abschnitt »Zuverlässigkeit« unten.
Skalierbarkeit
Wenn das System wächst (in Bezug auf Datenvolumen, Verkehrsaufkommen oder Komplexität), sollte es vernünftige Maßnahmen geben, um mit diesem Wachstum umzugehen. Siehe Abschnitt »Skalierbarkeit« auf Seite 11.
Wartbarkeit
Im Laufe der Zeit arbeiten verschiedene Leute am System (Techniker und Betreiber, die sowohl das aktuelle Verhalten sicherstellen als auch das System an neue Einsatzfälle anpassen), und sie alle sollten daran produktiv arbeiten können. Siehe Abschnitt »Wartbarkeit« auf Seite 20.
Diese Begriffe werden oftmals in den Raum geworfen, ohne überhaupt ihre Bedeutung genau verstanden zu haben. Im Sinne einer nachvollziehbaren Herangehensweise werden wir im Rest dieses Kapitels Möglichkeiten untersuchen, um Überlegungen zu Zuverlässigkeit, Skalierbarkeit und Wartbarkeit anzustellen. In den darauffolgenden Kapiteln sehen wir uns dann die verwendeten Techniken, Architekturen und Algorithmen an, mit denen sich diese Ziele erreichen lassen.
Wohl jeder hat eine intuitive Vorstellung davon, was zuverlässig oder unzuverlässig bedeutet. Zu den typischen Erwartungen an Software gehören:
Wenn all dies zusammengenommen »korrekt arbeiten« bedeutet, dann können wir Zuverlässigkeit ganz grob verstehen als »weiterhin korrekt arbeiten, auch wenn etwas schiefläuft«.
Die Dinge, die schiefgehen, bezeichnet man als Fehler, und Systeme, die Fehler einkalkulieren und bewältigen können, heißen fehlertolerant oder robust. Der erste Begriff ist etwas irreführend, suggeriert er doch, dass wir ein System gegenüber jeder Art von Fehlern tolerant machen könnten, was in der Praxis aber nicht realisierbar ist. Wenn man damit rechnet, dass ein schwarzes Loch den gesamten Planeten Erde (und alle Server auf ihm) verschluckt, müsste Webhosting im Weltraum stattfinden, um Fehlertoleranz für dieses Ereignis zu bieten – viel Erfolg dabei, diesen Haushaltsposten genehmigt zu bekommen. Es ist demnach nur sinnvoll, von der Toleranz gegenüber bestimmten Fehlerarten zu sprechen.
Beachten Sie, dass ein Fehler nicht dasselbe ist wie ein Ausfall [2]. Entsprechend der üblichen Definition ist ein Fehler eine Komponente des Systems, die von ihrer Spezifikation abweicht, während bei einem Ausfall das System als Ganzes aufhört, dem Benutzer den gewünschten Dienst bereitzustellen. Da es nicht möglich ist, die Wahrscheinlichkeit eines Fehlers auf null zu verringern, ist es normalerweise am besten, Fehlertoleranzmechanismen zu entwickeln, die verhindern, dass Fehler zu Ausfällen führen. In diesem Buch behandeln wir verschiedene Techniken, um zuverlässige Systeme aus unzuverlässigen Bestandteilen aufzubauen.
Auch wenn es der Intuition widerspricht, kann es in derartigen fehlertoleranten Systemen sinnvoll sein, die Fehlerrate zu erhöhen, indem man Fehler bewusst auslöst – zum Beispiel durch zufälliges Beenden einzelner Prozesse ohne Warnung. Viele kritische Bugs gehen auf eine schlechte Fehlerbehandlung zurück [3]. Indem Sie Fehler bewusst herbeiführen, stellen Sie sicher, dass der Fehlertoleranzmechanismus laufend beansprucht und getestet wird. Das kann Ihr Vertrauen stärken, dass Fehler ordnungsgemäß behandelt werden, wenn sie im regulären Betrieb auftreten. Ein Beispiel für dieses Konzept ist das Tool Chaos Monkey [4] von Netflix.
Obwohl wir im Allgemeinen Fehler lieber tolerieren als verhindern, gibt es Fälle, in denen Vorbeugen besser ist als Heilen (zum Beispiel, weil es keine Heilung gibt). Das ist unter anderem bei Sicherheitsfragen der Fall: Wenn ein Angreifer ein System gehackt und Zugriff auf vertrauliche Daten erlangt hat, lässt sich ein solches Ereignis nicht mehr rückgängig machen. Allerdings beschäftigt sich dieses Buch vorrangig mit solchen Fehlern, die sich beheben lassen, wie die folgenden Abschnitte beschreiben.
Denkt man an die Ursachen für Systemausfälle, kommen einem schnell Hardwarefehler in den Sinn: ein Festplattencrash tritt auf, RAM-Zellen verlieren Speicherinhalte, das Stromnetz bricht kurzzeitig zusammen, ein falsches Netzwerkkabel wurde eingesteckt. Jeder, der schon einmal mit großen Datencentern gearbeitet hat, wird bestätigen, dass diese Dinge ständig passieren, wenn nur genügend Computer vorhanden sind.
Bei Festplatten beträgt die mittlere Betriebszeit bis zum Ausfall (Mean Time To Failure, MTTF) etwa 10 bis 50 Jahre [5, 6]. Folglich ist in einem Speichercluster mit 10.000 Festplatten im Durchschnitt ein Festplattenausfall pro Tag zu erwarten.
Unsere erste Reaktion darauf ist üblicherweise, die einzelnen Hardwarekomponenten mit mehr Redundanz auszustatten, um die Ausfallrate des Systems zu verringern. Festplatten lassen sich in einer RAID-Konfiguration betreiben, Server sind mit doppelten Stromversorgungen und Hot-Swap-fähigen CPUs ausgerüstet, und in Rechenzentren sichern Batterien und Dieselgeneratoren die Notstromversorgung ab.
Wenn eine Komponente kaputtgeht, kann die redundante Komponente ihren Platz einnehmen, während die defekte Komponente ersetzt wird. Zwar lässt sich mit diesem Konzept nicht komplett verhindern, dass Hardwareprobleme zu Ausfällen führen, doch es ist praktikabel und sorgt oftmals dafür, dass ein Computer jahrelang ununterbrochen läuft.
Bis vor Kurzem genügten redundante Hardwarekomponenten für die meisten Anwendungen, da dank dieser Maßnahme einzelne Computer nur ziemlich selten komplett ausfallen. Sofern Sie recht schnell eine Sicherung auf einem neuen Computer wiederherstellen können, ist die Ausfallzeit bei den meisten Anwendungen nicht dramatisch. Somit ist eine Redundanz mit mehreren Computern nur für eine kleine Anzahl von Anwendungen erforderlich, bei denen Hochverfügbarkeit an vorderster Stelle steht.
Wegen größerer Datenmengen und gestiegener rechentechnischer Anforderungen geht man jedoch bei immer mehr Anwendungen dazu über, eine größere Anzahl von Computern zu nutzen, wodurch die Rate der Hardwarefehler proportional zunimmt. Darüber hinaus kommt es bei manchen Cloudplattformen wie zum Beispiel Amazon Web Services (AWS) durchaus vor, dass Instanzen virtueller Computer ohne Vorwarnung unverfügbar werden [7], weil die Plattformen dafür konzipiert sind, Flexibilität und Elastizität2 über die Zuverlässigkeit einzelner Computer zu priorisieren.
Folglich gibt es eine Verschiebung hin zu Systemen, die den Verlust ganzer Computer tolerieren können, indem sie softwareseitige Fehlertoleranztechniken bevorzugen oder ergänzend zur Hardwareredundanz einsetzen. Solche Systeme bieten auch operative Vorteile: Bei einem Einzelserversystem müssen Sie die Stillstandszeiten planen, falls Sie den Computer neu starten müssen (um zum Beispiel Sicherheitspatches des Betriebssystems zu installieren), während sich ein System, das den Ausfall eines Computers tolerieren kann, knotenweise mit Patches versorgen lässt, ohne dass das gesamte System stillsteht (rollendes Upgrade; siehe Kapitel 4).