Mehr Entwickler sollten dies über Datenbanken wissen

Notiz. übersetzen: Jaana Dogan ist eine erfahrene Ingenieurin bei Google, die derzeit an der Beobachtbarkeit der in Go geschriebenen Produktionsdienste des Unternehmens arbeitet. In diesem Artikel, der beim englischsprachigen Publikum große Popularität erlangte, sammelte sie in 17 Punkten wichtige technische Details zu DBMS (und manchmal zu verteilten Systemen im Allgemeinen), die für Entwickler großer/anspruchsvoller Anwendungen nützlich sind.

Mehr Entwickler sollten dies über Datenbanken wissen

Die überwiegende Mehrheit der Computersysteme verfolgt ihren Zustand und erfordert dementsprechend eine Art Datenspeichersystem. Ich habe über einen langen Zeitraum Wissen über Datenbanken gesammelt und dabei Designfehler gemacht, die zu Datenverlusten und Ausfällen führten. In Systemen, die große Informationsmengen verarbeiten, bilden Datenbanken das Herzstück der Systemarchitektur und sind ein Schlüsselelement bei der Auswahl der optimalen Lösung. Trotz der Tatsache, dass der Arbeit der Datenbank große Aufmerksamkeit geschenkt wird, sind die Probleme, die Anwendungsentwickler zu antizipieren versuchen, oft nur die Spitze des Eisbergs. In dieser Artikelserie teile ich einige Ideen, die für Entwickler nützlich sein werden, die nicht auf diesem Gebiet spezialisiert sind.

  1. Sie haben Glück, wenn das Netzwerk in 99,999 % der Fälle keine Probleme verursacht.
  2. ACID bedeutet viele verschiedene Dinge.
  3. Jede Datenbank verfügt über eigene Mechanismen zur Gewährleistung von Konsistenz und Isolation.
  4. Optimistisches Blockieren hilft, wenn es schwierig ist, das Übliche aufrechtzuerhalten.
  5. Neben Dirty Reads und Datenverlust gibt es noch weitere Anomalien.
  6. Nicht immer sind sich Datenbank und Benutzer über die Vorgehensweise einig.
  7. Sharding auf Anwendungsebene kann außerhalb der Anwendung verschoben werden.
  8. Autoinkrementierung kann gefährlich sein.
  9. Veraltete Daten können nützlich sein und müssen nicht gesperrt werden.
  10. Verzerrungen sind typisch für alle Zeitquellen.
  11. Verzögerung hat viele Bedeutungen.
  12. Leistungsanforderungen sollten für eine bestimmte Transaktion bewertet werden.
  13. Verschachtelte Transaktionen können gefährlich sein.
  14. Transaktionen sollten nicht an den Anwendungsstatus gebunden sein.
  15. Abfrageplaner können Ihnen viel über Datenbanken erzählen.
  16. Online-Migration ist schwierig, aber möglich.
  17. Eine deutliche Vergrößerung der Datenbasis bringt eine Erhöhung der Unvorhersehbarkeit mit sich.

Ich möchte Emmanuel Odeke, Rein Henrichs und anderen für ihr Feedback zu einer früheren Version dieses Artikels danken.

Sie haben Glück, wenn das Netzwerk in 99,999 % der Fälle keine Probleme verursacht.

Es bleibt die Frage, wie zuverlässig moderne Netzwerktechnologien sind und wie oft Systeme aufgrund von Netzwerkausfällen ausfallen. Informationen zu diesem Thema sind rar und die Forschung wird oft von großen Organisationen mit spezialisierten Netzwerken, Ausrüstung und Personal dominiert.

Mit einer Verfügbarkeitsrate von 99,999 % für Spanner (Googles weltweit verteilte Datenbank) behauptet Google dies nur 7,6% Probleme hängen mit dem Netzwerk zusammen. Gleichzeitig bezeichnet das Unternehmen sein spezialisiertes Netzwerk als „Hauptpfeiler“ der Hochverfügbarkeit. Studie Bailis und Kingsbury, durchgeführt im Jahr 2014, stellt eine der „Missverständnisse über verteiltes Rechnen", den Peter Deutsch 1994 formulierte. Ist das Netzwerk wirklich zuverlässig?

Umfassende Forschung außerhalb großer Unternehmen, die für das Internet im weiteren Sinne durchgeführt wird, gibt es einfach nicht. Auch liegen von den großen Playern nicht genügend Daten darüber vor, wie viel Prozent der Probleme ihrer Kunden netzwerkbedingt sind. Uns sind Ausfälle im Netzwerk-Stack großer Cloud-Anbieter bekannt, die einen ganzen Teil des Internets für mehrere Stunden lahmlegen können, einfach weil es sich um hochkarätige Ereignisse handelt, die eine große Anzahl von Menschen und Unternehmen betreffen. Netzwerkausfälle können in vielen weiteren Fällen zu Problemen führen, auch wenn nicht alle Fälle im Rampenlicht stehen. Auch Kunden von Cloud-Diensten wissen nichts über die Ursachen von Problemen. Kommt es zu einem Ausfall, ist es nahezu unmöglich, ihn auf einen Netzwerkfehler auf Seiten des Dienstanbieters zurückzuführen. Für sie sind Dienste Dritter Black Boxes. Es ist unmöglich, die Auswirkungen abzuschätzen, ohne ein großer Dienstleister zu sein.

Wenn man bedenkt, was die großen Player über ihre Systeme berichten, kann man mit Sicherheit sagen, dass man Glück hat, wenn Netzwerkprobleme nur einen kleinen Prozentsatz der potenziellen Ausfallzeiten ausmachen. Die Netzwerkkommunikation leidet immer noch unter so alltäglichen Dingen wie Hardwarefehlern, Topologieänderungen, administrativen Konfigurationsänderungen und Stromausfällen. Kürzlich war ich überrascht, als ich erfuhr, dass die Liste möglicher Probleme hinzugefügt wurde Haibisse (Ja, Sie haben richtig gehört).

ACID bedeutet viele verschiedene Dinge

Das Akronym ACID steht für Atomicity, Consistency, Isolation, Reliability. Diese Eigenschaften von Transaktionen sollen deren Gültigkeit bei Ausfällen, Fehlern, Hardwareausfällen etc. sicherstellen. Ohne ACID oder ähnliche Schemata wäre es für Anwendungsentwickler schwierig, zwischen dem, wofür sie verantwortlich sind, und dem, wofür die Datenbank verantwortlich ist, zu unterscheiden. Die meisten relationalen Transaktionsdatenbanken versuchen, ACID-kompatibel zu sein, aber neue Ansätze wie NoSQL haben zu vielen Datenbanken ohne ACID-Transaktionen geführt, da ihre Implementierung teuer ist.

Als ich zum ersten Mal in die Branche einstieg, sprach unser technischer Leiter darüber, wie relevant das ACID-Konzept sei. Fairerweise muss man sagen, dass ACID eher als grobe Beschreibung denn als strenger Implementierungsstandard betrachtet wird. Heute finde ich es vor allem deshalb nützlich, weil es eine bestimmte Kategorie von Problemen aufwirft (und eine Reihe möglicher Lösungen vorschlägt).

Nicht jedes DBMS ist ACID-kompatibel; Gleichzeitig verstehen Datenbankimplementierungen, die ACID unterstützen, die Anforderungen unterschiedlich. Einer der Gründe, warum ACID-Implementierungen lückenhaft sind, liegt in den vielen Kompromissen, die bei der Implementierung von ACID-Anforderungen eingegangen werden müssen. Ersteller stellen ihre Datenbanken möglicherweise als ACID-konform dar, die Interpretation von Randfällen kann jedoch erheblich variieren, ebenso wie der Mechanismus zur Behandlung „unwahrscheinlicher“ Ereignisse. Zumindest können Entwickler ein umfassendes Verständnis der Feinheiten von Basisimplementierungen erlangen, um ihr spezielles Verhalten und ihre Design-Kompromisse richtig zu verstehen.

Die Debatte darüber, ob MongoDB den ACID-Anforderungen entspricht, geht auch nach der Veröffentlichung von Version 4 weiter. MongoDB wurde schon lange nicht mehr unterstützt Protokollierung, obwohl die Daten standardmäßig nur alle 60 Sekunden auf die Festplatte übertragen wurden. Stellen Sie sich das folgende Szenario vor: Eine Anwendung veröffentlicht zwei Schreibvorgänge (w1 und w2). MongoDB speichert w1 erfolgreich, w2 geht jedoch aufgrund eines Hardwarefehlers verloren.

Mehr Entwickler sollten dies über Datenbanken wissen
Diagramm zur Veranschaulichung des Szenarios. MongoDB stürzt ab, bevor Daten auf die Festplatte geschrieben werden können

Das Festschreiben auf der Festplatte ist ein kostspieliger Prozess. Durch die Vermeidung häufiger Commits verbessern Entwickler die Aufzeichnungsleistung auf Kosten der Zuverlässigkeit. MongoDB unterstützt derzeit die Protokollierung, aber Dirty Writes können dennoch die Datenintegrität beeinträchtigen, da Protokolle standardmäßig alle 100 ms erfasst werden. Das heißt, ein ähnliches Szenario ist für Protokolle und die darin enthaltenen Änderungen immer noch möglich, obwohl das Risiko viel geringer ist.

Jede Datenbank verfügt über eigene Konsistenz- und Isolationsmechanismen

Von den ACID-Anforderungen weisen Konsistenz und Isolation die größte Anzahl unterschiedlicher Implementierungen auf, da die Bandbreite an Kompromissen größer ist. Es muss gesagt werden, dass Konsistenz und Isolation recht teure Funktionen sind. Sie erfordern Koordination und verschärfen den Wettbewerb um Datenkonsistenz. Die Komplexität des Problems erhöht sich erheblich, wenn es notwendig ist, die Datenbank horizontal über mehrere Rechenzentren hinweg zu skalieren (insbesondere wenn diese sich in verschiedenen geografischen Regionen befinden). Es ist sehr schwierig, ein hohes Maß an Konsistenz zu erreichen, da dadurch auch die Verfügbarkeit verringert und die Netzwerksegmentierung erhöht wird. Für eine allgemeinere Erklärung dieses Phänomens empfehle ich Ihnen, auf zu verweisen CAP-Theorem. Es ist auch erwähnenswert, dass Anwendungen mit kleinen Inkonsistenzen umgehen können und Programmierer die Nuancen des Problems gut genug verstehen können, um zusätzliche Logik in die Anwendung zu implementieren, um Inkonsistenzen zu bewältigen, ohne sich dabei stark auf die Datenbank verlassen zu müssen.

DBMS bieten häufig unterschiedliche Isolationsstufen. Anwendungsentwickler können basierend auf ihren Vorlieben die effektivste auswählen. Eine geringe Isolation ermöglicht eine höhere Geschwindigkeit, erhöht aber auch das Risiko eines Datenwettlaufs. Eine hohe Isolierung verringert diese Wahrscheinlichkeit, verlangsamt jedoch die Arbeit und kann zu Konkurrenz führen, die zu solchen Bremsen in der Basis führt, dass es zu Ausfällen kommt.

Mehr Entwickler sollten dies über Datenbanken wissen
Überprüfung bestehender Parallelitätsmodelle und Beziehungen zwischen ihnen

Der SQL-Standard definiert nur vier Isolationsstufen, obwohl es in Theorie und Praxis noch viel mehr gibt. Jepson.io bietet einen hervorragenden Überblick über bestehende Parallelitätsmodelle. Beispielsweise garantiert Google Spanner externe Serialisierbarkeit mit Taktsynchronisierung, und obwohl es sich hierbei um eine strengere Isolationsschicht handelt, ist sie in Standard-Isolationsschichten nicht definiert.

Der SQL-Standard erwähnt die folgenden Isolationsstufen:

  • Serialisierbar (am strengsten und teuersten): Die serialisierbare Ausführung hat den gleichen Effekt wie die Ausführung einer sequentiellen Transaktion. Sequentielle Ausführung bedeutet, dass jede nachfolgende Transaktion erst beginnt, nachdem die vorherige abgeschlossen ist. Es ist zu beachten, dass das Niveau Serialisierbar Aufgrund von Interpretationsunterschieden wird sie häufig als sogenannte Snapshot-Isolation (z. B. in Oracle) implementiert, obwohl die Snapshot-Isolation selbst im SQL-Standard nicht vertreten ist.
  • Wiederholbare Lesevorgänge: Nicht festgeschriebene Datensätze in der aktuellen Transaktion stehen der aktuellen Transaktion zur Verfügung, aber von anderen Transaktionen vorgenommene Änderungen (z. B. neue Zeilen) nicht sichtbar.
  • Lesen Sie engagiert: Nicht festgeschriebene Daten sind für Transaktionen nicht verfügbar. In diesem Fall können Transaktionen nur festgeschriebene Daten sehen und es kann zu Phantom-Lesevorgängen kommen. Wenn eine Transaktion neue Zeilen einfügt und festschreibt, kann die aktuelle Transaktion diese bei einer Abfrage sehen.
  • Ungebunden lesen (am wenigsten strenge und kostspielige Stufe): Dirty Reads sind zulässig, Transaktionen können nicht festgeschriebene Änderungen sehen, die von anderen Transaktionen vorgenommen wurden. In der Praxis kann diese Ebene für grobe Schätzungen, beispielsweise bei Abfragen, nützlich sein COUNT(*) auf dem Tisch.

Ebene Serialisierbar minimiert das Risiko von Datenwettläufen, ist gleichzeitig am teuersten in der Implementierung und führt zu der höchsten Wettbewerbsbelastung des Systems. Andere Isolationsstufen sind einfacher zu implementieren, erhöhen jedoch die Wahrscheinlichkeit von Datenwettläufen. Bei einigen DBMS können Sie eine benutzerdefinierte Isolationsstufe festlegen, andere haben strenge Präferenzen und nicht alle Stufen werden unterstützt.

Die Unterstützung von Isolationsstufen wird in einem bestimmten DBMS oft angekündigt, aber nur eine sorgfältige Untersuchung seines Verhaltens kann Aufschluss darüber geben, was tatsächlich passiert.

Mehr Entwickler sollten dies über Datenbanken wissen
Überprüfung von Parallelitätsanomalien auf verschiedenen Isolationsebenen für verschiedene DBMS

Martin Kleppmann in seinem Projekt Einsiedelei Vergleicht verschiedene Isolationsstufen, spricht über Parallelitätsanomalien und ob die Datenbank eine bestimmte Isolationsstufe einhalten kann. Kleppmanns Forschung zeigt, wie unterschiedlich Datenbankentwickler über Isolationsstufen denken.

Optimistisches Blockieren hilft, wenn es schwierig ist, das Übliche aufrechtzuerhalten.

Das Blockieren kann sehr teuer sein, nicht nur, weil es den Wettbewerb in der Datenbank erhöht, sondern auch, weil es erfordert, dass die Anwendungsserver ständig eine Verbindung zur Datenbank herstellen. Die Netzwerksegmentierung kann exklusive Sperrsituationen verschlimmern und zu Deadlocks führen, die schwer zu identifizieren und zu beheben sind. In Fällen, in denen eine exklusive Sperrung nicht geeignet ist, hilft eine optimistische Sperre.

Optimistisches Schloss ist eine Methode, bei der beim Lesen eines Strings dessen Version, Prüfsumme oder Zeitpunkt der letzten Änderung berücksichtigt wird. Dadurch können Sie sicherstellen, dass keine atomare Versionsänderung erfolgt, bevor Sie einen Eintrag ändern:

UPDATE products
SET name = 'Telegraph receiver', version = 2
WHERE id = 1 AND version = 1

In diesem Fall wird die Tabelle aktualisiert products wird nicht ausgeführt, wenn zuvor eine andere Operation Änderungen an dieser Zeile vorgenommen hat. Wenn in dieser Zeile keine weiteren Vorgänge durchgeführt wurden, erfolgt die Änderung für eine Zeile und wir können sagen, dass die Aktualisierung erfolgreich war.

Neben Dirty Reads und Datenverlust gibt es noch weitere Anomalien

Wenn es um Datenkonsistenz geht, liegt der Schwerpunkt auf der Möglichkeit von Race Conditions, die zu Dirty Reads und Datenverlust führen können. Datenanomalien hören hier jedoch nicht auf.

Ein Beispiel für solche Anomalien ist die Aufnahmeverzerrung (Schreiben Sie schräg). Verzerrungen sind schwer zu erkennen, da sie in der Regel nicht aktiv gesucht werden. Sie sind nicht auf fehlerhafte Lesevorgänge oder Datenverlust zurückzuführen, sondern auf Verletzungen logischer Einschränkungen, die den Daten auferlegt werden.

Betrachten wir beispielsweise eine Überwachungsanwendung, die die ständige Bereitschaft eines Bedieners erfordert:

BEGIN tx1;                      BEGIN tx2;
SELECT COUNT(*)
FROM operators
WHERE oncall = true;
0                               SELECT COUNT(*)
                                FROM operators
                                WHERE oncall = TRUE;
                                0
UPDATE operators                UPDATE operators
SET oncall = TRUE               SET oncall = TRUE
WHERE userId = 4;               WHERE userId = 2;
COMMIT tx1;                     COMMIT tx2;

In der oben genannten Situation kommt es zu einer Datensatzbeschädigung, wenn beide Transaktionen erfolgreich festgeschrieben werden. Obwohl es weder zu Dirty Reads noch zu Datenverlusten kam, war die Integrität der Daten gefährdet: Nun gelten zwei Personen gleichzeitig als Bereitschaftsdienst.

Serialisierbare Isolation, Schemadesign oder Datenbankeinschränkungen können dabei helfen, Schreibbeschädigungen zu vermeiden. Entwickler müssen in der Lage sein, solche Anomalien während der Entwicklung zu erkennen, um sie in der Produktion zu vermeiden. Gleichzeitig ist es äußerst schwierig, in der Codebasis nach Aufzeichnungsverzerrungen zu suchen. Insbesondere in großen Systemen, wenn unterschiedliche Entwicklungsteams für die Implementierung von Funktionen auf Basis derselben Tabellen verantwortlich sind und sich über die Besonderheiten des Datenzugriffs nicht einig sind.

Die Datenbank und der Benutzer sind sich nicht immer einig, was zu tun ist

Eines der Hauptmerkmale von Datenbanken ist die Garantie der Ausführungsreihenfolge, aber diese Reihenfolge selbst ist für den Softwareentwickler möglicherweise nicht transparent. Datenbanken führen Transaktionen in der Reihenfolge aus, in der sie empfangen werden, nicht in der vom Programmierer beabsichtigten Reihenfolge. Die Reihenfolge der Transaktionen ist insbesondere in hochbelasteten Parallelsystemen schwer vorherzusagen.

Während der Entwicklung, insbesondere bei der Arbeit mit nicht blockierenden Bibliotheken, können schlechter Stil und schlechte Lesbarkeit dazu führen, dass Benutzer glauben, dass Transaktionen sequentiell ausgeführt werden, obwohl sie tatsächlich in beliebiger Reihenfolge in der Datenbank ankommen könnten.

Auf den ersten Blick werden im folgenden Programm T1 und T2 nacheinander aufgerufen, aber wenn diese Funktionen nicht blockierend sind, geben sie das Ergebnis sofort im Formular zurück Versprechen, dann wird die Reihenfolge der Anrufe durch die Zeitpunkte bestimmt, zu denen sie in die Datenbank eingegeben wurden:

result1 = T1() // Echte Ergebnisse sind Versprechen
result2 = T2()

Wenn Atomizität erforderlich ist (d. h. alle Vorgänge müssen abgeschlossen oder abgebrochen werden) und die Reihenfolge wichtig ist, müssen die Vorgänge T1 und T2 innerhalb einer einzigen Transaktion ausgeführt werden.

Sharding auf Anwendungsebene kann außerhalb der Anwendung verschoben werden

Sharding ist eine Methode zur horizontalen Partitionierung einer Datenbank. Einige Datenbanken können Daten automatisch horizontal aufteilen, während andere dies nicht können oder nicht sehr gut darin sind. Wenn Datenarchitekten/-entwickler genau vorhersagen können, wie auf Daten zugegriffen wird, können sie horizontale Partitionen im Benutzerbereich erstellen, anstatt diese Arbeit an die Datenbank zu delegieren. Dieser Vorgang wird als „Sharding auf Anwendungsebene“ bezeichnet. (Sharding auf Anwendungsebene).

Leider führt dieser Name oft zu der falschen Vorstellung, dass Sharding in Anwendungsdiensten steckt. Tatsächlich kann es als separate Schicht vor der Datenbank implementiert werden. Abhängig vom Datenwachstum und den Schema-Iterationen können die Sharding-Anforderungen recht komplex werden. Einige Strategien können von der Möglichkeit der Iteration profitieren, ohne dass Anwendungsserver erneut bereitgestellt werden müssen.

Mehr Entwickler sollten dies über Datenbanken wissen
Ein Beispiel für eine Architektur, in der Anwendungsserver vom Sharding-Dienst getrennt sind

Die Verlagerung des Shardings in einen separaten Dienst erweitert die Möglichkeit, verschiedene Sharding-Strategien zu verwenden, ohne dass Anwendungen erneut bereitgestellt werden müssen. Geschwindigkeiten ist ein Beispiel für ein solches Sharding-System auf Anwendungsebene. Vitess bietet horizontales Sharding für MySQL und ermöglicht Clients die Verbindung über das MySQL-Protokoll. Das System segmentiert die Daten in verschiedene MySQL-Knoten, die nichts voneinander wissen.

Autoinkrementierung kann gefährlich sein

AUTOINCREMENT ist eine gängige Methode zum Generieren von Primärschlüsseln. Es kommt häufig vor, dass Datenbanken als ID-Generatoren verwendet werden und die Datenbank Tabellen zur Generierung von Identifikatoren enthält. Es gibt mehrere Gründe, warum die Generierung von Primärschlüsseln mithilfe der automatischen Inkrementierung alles andere als ideal ist:

  • In einer verteilten Datenbank ist die automatische Inkrementierung ein ernstes Problem. Zur Generierung der ID ist eine globale Sperre erforderlich. Stattdessen können Sie eine UUID generieren: Dies erfordert keine Interaktion zwischen verschiedenen Datenbankknoten. Die automatische Inkrementierung mit Sperren kann zu Konflikten führen und die Leistung bei Einfügungen in verteilten Situationen erheblich verringern. Einige DBMS (z. B. MySQL) erfordern möglicherweise eine spezielle Konfiguration und mehr Aufmerksamkeit, um die Master-Master-Replikation ordnungsgemäß zu organisieren. Und bei der Konfiguration passieren leicht Fehler, die zu Aufzeichnungsfehlern führen.
  • Einige Datenbanken verfügen über Partitionierungsalgorithmen, die auf Primärschlüsseln basieren. Aufeinanderfolgende IDs können zu unvorhersehbaren Hotspots und erhöhter Auslastung einiger Partitionen führen, während andere im Leerlauf bleiben.
  • Ein Primärschlüssel ist der schnellste Weg, auf eine Zeile in einer Datenbank zuzugreifen. Mit besseren Möglichkeiten zur Identifizierung von Datensätzen können sequentielle IDs die wichtigste Spalte in Tabellen in eine nutzlose Spalte voller bedeutungsloser Werte verwandeln. Bitte wählen Sie daher nach Möglichkeit einen weltweit eindeutigen und natürlichen Primärschlüssel (z. B. Benutzernamen).

Bevor Sie sich für einen Ansatz entscheiden, sollten Sie die Auswirkungen der automatischen Inkrementierung von IDs und UUIDs auf die Indizierung, Partitionierung und Sharding berücksichtigen.

Veraltete Daten können nützlich sein und erfordern keine Sperrung

Multiversion Concurrency Control (MVCC) implementiert viele der oben kurz besprochenen Konsistenzanforderungen. Einige Datenbanken (z. B. Postgres, Spanner) verwenden MVCC, um Transaktionen mit Snapshots – älteren Versionen der Datenbank – zu „füttern“. Snapshot-Transaktionen können zur Gewährleistung der Konsistenz auch serialisiert werden. Beim Lesen aus einem alten Snapshot werden veraltete Daten gelesen.

Das Auslesen leicht veralteter Daten kann beispielsweise nützlich sein, wenn aus den Daten Analysen erstellt oder ungefähre Aggregatwerte berechnet werden sollen.

Der erste Vorteil der Arbeit mit Altdaten ist die geringe Latenz (insbesondere, wenn die Datenbank über verschiedene Regionen verteilt ist). Zweitens sind schreibgeschützte Transaktionen sperrenfrei. Dies ist ein erheblicher Vorteil für Anwendungen, die viel lesen, sofern sie mit veralteten Daten umgehen können.

Mehr Entwickler sollten dies über Datenbanken wissen
Der Anwendungsserver liest Daten aus dem lokalen Replikat, die 5 Sekunden veraltet sind, auch wenn die neueste Version auf der anderen Seite des Pazifischen Ozeans verfügbar ist

DBMS löschen automatisch ältere Versionen und ermöglichen Ihnen dies in einigen Fällen auf Anfrage. Postgres ermöglicht es Benutzern beispielsweise, dies zu tun VACUUM auf Anfrage und führt diesen Vorgang auch regelmäßig automatisch durch. Spanner führt einen Garbage Collector aus, um Snapshots zu entfernen, die älter als eine Stunde sind.

Alle Zeitquellen unterliegen einer Verzerrung

Das bestgehütete Geheimnis der Informatik ist, dass alle Timing-APIs lügen. Tatsächlich kennen unsere Maschinen die genaue aktuelle Uhrzeit nicht. Computer enthalten Quarzkristalle, die Schwingungen erzeugen, die zur Zeitmessung dienen. Allerdings sind sie nicht genau genug und können der genauen Zeit voraus oder hinterherhinken. Die Schicht kann 20 Sekunden pro Tag erreichen. Daher muss die Zeit auf unseren Computern regelmäßig mit der Netzwerkzeit synchronisiert werden.

Für die Synchronisierung werden NTP-Server verwendet, der Synchronisierungsprozess selbst unterliegt jedoch Netzwerkverzögerungen. Selbst die Synchronisierung mit einem NTP-Server im selben Rechenzentrum dauert einige Zeit. Es ist klar, dass die Arbeit mit einem öffentlichen NTP-Server zu noch größeren Verzerrungen führen kann.

Atomuhren und ihre GPS-Gegenstücke eignen sich besser zur Bestimmung der aktuellen Uhrzeit, sind jedoch teuer und erfordern eine komplexe Einrichtung, sodass sie nicht in jedes Auto eingebaut werden können. Aus diesem Grund verwenden Rechenzentren einen mehrstufigen Ansatz. Atomuhren und/oder GPS-Uhren zeigen die genaue Uhrzeit an, die anschließend über sekundäre Server an andere Maschinen gesendet wird. Dies bedeutet, dass jede Maschine einen gewissen Versatz zur genauen Zeit erfährt.

Erschwerend kommt hinzu, dass sich Anwendungen und Datenbanken häufig auf unterschiedlichen Rechnern (wenn nicht sogar in unterschiedlichen Rechenzentren) befinden. Somit wird die Zeit nicht nur auf DB-Knoten, die auf verschiedene Maschinen verteilt sind, unterschiedlich sein. Auch auf dem Anwendungsserver wird es anders sein.

Google TrueTime verfolgt einen völlig anderen Ansatz. Die meisten Leute glauben, dass Googles Fortschritte in dieser Richtung durch den banalen Übergang zu Atom- und GPS-Uhren erklärt werden, aber das ist nur ein Teil des Gesamtbildes. So funktioniert TrueTime:

  • TrueTime verwendet zwei verschiedene Quellen: GPS und Atomuhren. Diese Uhren verfügen über nicht korrelierte Fehlermodi. [Einzelheiten finden Sie auf Seite 5 hier - ca. übersetzt), so dass ihre gemeinsame Nutzung die Zuverlässigkeit erhöht.
  • TrueTime verfügt über eine ungewöhnliche API. Es gibt die Zeit als Intervall mit eingebautem Messfehler und Unsicherheit zurück. Der tatsächliche Zeitpunkt liegt irgendwo zwischen der oberen und unteren Grenze des Intervalls. Spanner, die verteilte Datenbank von Google, wartet einfach, bis man mit Sicherheit sagen kann, dass die aktuelle Zeit außerhalb des zulässigen Bereichs liegt. Diese Methode führt zu einer gewissen Latenzzeit im System, insbesondere wenn die Unsicherheit auf den Mastern hoch ist, gewährleistet aber auch in einer global verteilten Situation die Korrektheit.

Mehr Entwickler sollten dies über Datenbanken wissen
Die Spanner-Komponenten verwenden TrueTime, wobei TT.now() ein Intervall zurückgibt, sodass der Spanner einfach schläft, bis er sicher sein kann, dass die aktuelle Zeit einen bestimmten Punkt überschritten hat

Eine verringerte Genauigkeit bei der Bestimmung der aktuellen Zeit bedeutet eine Verlängerung der Dauer von Spanner-Vorgängen und eine Verringerung der Leistung. Aus diesem Grund ist es wichtig, die höchstmögliche Genauigkeit beizubehalten, auch wenn es unmöglich ist, eine völlig genaue Uhr zu erhalten.

Verzögerung hat viele Bedeutungen

Wenn Sie ein Dutzend Experten fragen, was eine Verzögerung ist, werden Sie wahrscheinlich unterschiedliche Antworten erhalten. In DBMS wird die Latenz oft als „Datenbanklatenz“ bezeichnet und unterscheidet sich von dem, was der Client wahrnimmt. Tatsache ist, dass der Client die Summe aus Netzwerkverzögerung und Datenbankverzögerung beobachtet. Die Möglichkeit, die Art der Latenz zu isolieren, ist beim Debuggen wachsender Probleme von entscheidender Bedeutung. Versuchen Sie bei der Erhebung und Darstellung von Kennzahlen stets beide Arten im Auge zu behalten.

Leistungsanforderungen sollten für eine bestimmte Transaktion bewertet werden

Manchmal werden die Leistungsmerkmale eines DBMS und seine Einschränkungen im Hinblick auf den Schreib-/Lesedurchsatz und die Latenz angegeben. Dies bietet einen allgemeinen Überblick über die wichtigsten Systemparameter. Bei der Bewertung der Leistung eines neuen DBMS besteht ein viel umfassenderer Ansatz jedoch darin, kritische Vorgänge separat zu bewerten (für jede Abfrage und/oder Transaktion). Beispiele:

  • Schreibdurchsatz und Latenz beim Einfügen einer neuen Zeile in Tabelle X (mit 50 Millionen Zeilen) mit angegebenen Einschränkungen und Zeilenauffüllung in zugehörigen Tabellen.
  • Verzögerung bei der Anzeige von Freunden von Freunden eines bestimmten Benutzers, wenn die durchschnittliche Anzahl der Freunde 500 beträgt.
  • Latenz beim Abrufen der 100 wichtigsten Einträge aus dem Verlauf eines Benutzers, wenn der Benutzer 500 anderen Benutzern mit X Einträgen pro Stunde folgt.

Solche kritischen Fälle können in Evaluierungen und Experimenten einbezogen werden, bis Sie sicher sind, dass die Datenbank die Leistungsanforderungen erfüllt. Eine ähnliche Faustregel berücksichtigt diese Aufschlüsselung auch bei der Erfassung von Latenzmetriken und der Festlegung von SLOs.

Achten Sie beim Erfassen von Metriken für jeden Vorgang auf eine hohe Kardinalität. Verwenden Sie Protokolle, Ereigniserfassung oder verteilte Ablaufverfolgung, um leistungsstarke Debugging-Daten zu erhalten. Im Artikel "Möchten Sie die Latenz debuggen?» Sie können sich mit Methoden zur Verzögerungsdebuggung vertraut machen.

Verschachtelte Transaktionen können gefährlich sein

Nicht jedes DBMS unterstützt verschachtelte Transaktionen. Wenn dies jedoch der Fall ist, können solche Transaktionen zu unerwarteten Fehlern führen, die nicht immer leicht zu erkennen sind (d. h. es sollte offensichtlich sein, dass eine Anomalie vorliegt).

Sie können die Verwendung verschachtelter Transaktionen vermeiden, indem Sie Clientbibliotheken verwenden, die diese erkennen und umgehen können. Wenn verschachtelte Transaktionen nicht abgebrochen werden können, achten Sie bei ihrer Implementierung besonders darauf, unerwartete Situationen zu vermeiden, in denen abgeschlossene Transaktionen aufgrund verschachtelter Transaktionen versehentlich abgebrochen werden.

Das Kapseln von Transaktionen in verschiedenen Ebenen kann zu unerwarteten verschachtelten Transaktionen führen und es aus Sicht der Codelesbarkeit schwierig machen, die Absichten des Autors zu verstehen. Schauen Sie sich das folgende Programm an:

with newTransaction():
   Accounts.create("609-543-222")
   with newTransaction():
       Accounts.create("775-988-322")
       throw Rollback();

Was wird die Ausgabe des obigen Codes sein? Werden beide Transaktionen zurückgesetzt oder nur die innere? Was passiert, wenn wir uns auf mehrere Ebenen von Bibliotheken verlassen, die die Erstellung von Transaktionen für uns kapseln? Können wir solche Fälle erkennen und verbessern?

Stellen Sie sich eine Datenschicht mit mehreren Operationen vor (z. B. newAccount) ist bereits in eigenen Transaktionen implementiert. Was passiert, wenn Sie sie als Teil einer übergeordneten Geschäftslogik ausführen, die innerhalb einer eigenen Transaktion ausgeführt wird? Was wäre in diesem Fall die Isolation und Konsistenz?

function newAccount(id string) {
  with newTransaction():
      Accounts.create(id)
}

Anstatt nach Antworten auf solch endlose Fragen zu suchen, ist es besser, verschachtelte Transaktionen zu vermeiden. Schließlich kann Ihre Datenschicht problemlos Vorgänge auf hoher Ebene ausführen, ohne eigene Transaktionen zu erstellen. Darüber hinaus ist die Geschäftslogik selbst in der Lage, eine Transaktion zu initiieren, Operationen darauf auszuführen, eine Transaktion festzuschreiben oder abzubrechen.

function newAccount(id string) {
   Accounts.create(id)
}
// In main application:
with newTransaction():
   // Read some data from database for configuration.
   // Generate an ID from the ID service.
   Accounts.create(id)
   Uploads.create(id) // create upload queue for the user.

Transaktionen sollten nicht an den Anwendungsstatus gebunden sein

Manchmal ist es verlockend, den Anwendungsstatus in Transaktionen zu verwenden, um bestimmte Werte zu ändern oder Abfrageparameter zu optimieren. Die entscheidende Nuance, die es zu berücksichtigen gilt, ist der richtige Anwendungsbereich. Clients starten Transaktionen häufig neu, wenn Netzwerkprobleme auftreten. Wenn die Transaktion dann von einem Zustand abhängt, der durch einen anderen Prozess geändert wird, wählt sie je nach Möglichkeit eines Datenwettlaufs möglicherweise den falschen Wert. Bei Transaktionen muss das Risiko von Datenwettlaufbedingungen in der Anwendung berücksichtigt werden.

var seq int64
with newTransaction():
    newSeq := atomic.Increment(&seq)
    Entries.query(newSeq)
    // Other operations...

Die obige Transaktion erhöht die Sequenznummer bei jeder Ausführung, unabhängig vom Endergebnis. Wenn der Commit aufgrund von Netzwerkproblemen fehlschlägt, wird die Anfrage bei einem erneuten Versuch mit einer anderen Sequenznummer ausgeführt.

Abfrageplaner können Ihnen viel über eine Datenbank erzählen

Abfrageplaner bestimmen, wie eine Abfrage in einer Datenbank ausgeführt wird. Außerdem analysieren sie Anfragen und optimieren sie vor dem Versenden. Planer können auf der Grundlage der ihnen zur Verfügung stehenden Signale nur einige mögliche Schätzungen abgeben. Was ist beispielsweise die beste Suchmethode für die folgende Abfrage?

SELECT * FROM articles where author = "rakyll" order by title;

Die Ergebnisse können auf zwei Arten abgerufen werden:

  • Vollständiger Tabellenscan: Sie können sich jeden Eintrag in der Tabelle ansehen und Artikel mit einem passenden Autorennamen zurückgeben und diese dann bestellen.
  • Index-Scan: Sie können einen Index verwenden, um passende IDs zu finden, diese Zeilen abzurufen und sie dann zu ordnen.

Die Aufgabe des Abfrageplaners besteht darin, die beste Strategie zu ermitteln. Es ist zu bedenken, dass Abfrageplaner nur über begrenzte Vorhersagefähigkeiten verfügen. Dies kann zu Fehlentscheidungen führen. Datenbankadministratoren oder Entwickler können damit leistungsschwache Abfragen diagnostizieren und optimieren. Neue Versionen des DBMS können Abfrageplaner konfigurieren und die Selbstdiagnose kann bei der Aktualisierung der Datenbank helfen, wenn die neue Version zu Leistungsproblemen führt. Protokolle langsamer Abfragen, Berichte zu Latenzproblemen oder Statistiken zur Ausführungszeit können dabei helfen, Abfragen zu identifizieren, die optimiert werden müssen.

Einige vom Abfrageplaner angezeigten Metriken können Störungen unterliegen (insbesondere bei der Schätzung der Latenz oder CPU-Zeit). Eine gute Ergänzung zu Schedulern sind Tools zum Nachverfolgen und Verfolgen des Ausführungspfads. Sie ermöglichen Ihnen, solche Probleme zu diagnostizieren (leider bieten nicht alle DBMS solche Tools an).

Online-Migration ist schwierig, aber möglich

Online-Migration, Live-Migration oder Echtzeit-Migration bedeutet den Wechsel von einer Datenbank zu einer anderen ohne Ausfallzeiten oder Datenbeschädigung. Eine Live-Migration ist einfacher durchzuführen, wenn der Übergang innerhalb desselben DBMS/einer Engine erfolgt. Komplizierter wird die Situation, wenn ein Wechsel zu einem neuen DBMS mit anderen Leistungs- und Schemaanforderungen erforderlich ist.

Es gibt verschiedene Online-Migrationsmodelle. Hier ist einer davon:

  • Aktivieren Sie die Doppeleintragung in beiden Datenbanken. Die neue Datenbank verfügt zu diesem Zeitpunkt nicht über alle Daten, sondern akzeptiert nur die neuesten Daten. Sobald Sie sich dessen sicher sind, können Sie mit dem nächsten Schritt fortfahren.
  • Aktivieren Sie das Lesen aus beiden Datenbanken.
  • Konfigurieren Sie das System so, dass Lese- und Schreibvorgänge hauptsächlich in der neuen Datenbank ausgeführt werden.
  • Hören Sie auf, in die alte Datenbank zu schreiben, während Sie weiterhin Daten daraus lesen. Zu diesem Zeitpunkt enthält die neue Datenbank noch einige Daten. Sie sollten aus der alten Datenbank kopiert werden.
  • Die alte Datenbank ist schreibgeschützt. Kopieren Sie die fehlenden Daten aus der alten Datenbank in die neue. Nachdem die Migration abgeschlossen ist, wechseln Sie die Pfade zur neuen Datenbank, stoppen Sie die alte und löschen Sie sie aus dem System.

Für weitere Informationen empfehle ich die Kontaktaufnahme Artikel, in dem die auf diesem Modell basierende Migrationsstrategie von Stripe detailliert beschrieben wird.

Eine deutliche Vergrößerung der Datenbasis bringt eine Erhöhung der Unvorhersehbarkeit mit sich

Das Wachstum der Datenbank führt zu unvorhersehbaren Problemen im Zusammenhang mit ihrer Größe. Je mehr wir über die interne Struktur einer Datenbank wissen, desto besser können wir vorhersagen, wie sie skaliert. Einige Momente sind jedoch immer noch nicht vorhersehbar.
Wenn die Basis wächst, können frühere Annahmen und Erwartungen hinsichtlich Datenvolumen und Netzwerkbandbreitenanforderungen veraltet sein. In diesem Fall stellt sich die Frage nach umfassenden Designüberarbeitungen, umfassenden betrieblichen Verbesserungen, einem Umdenken bei der Bereitstellung oder einer Migration auf andere DBMS, um potenzielle Probleme zu vermeiden.

Denken Sie jedoch nicht, dass nur hervorragende Kenntnisse der internen Struktur der vorhandenen Datenbank erforderlich sind. Neue Maßstäbe werden neue Unbekannte mit sich bringen. Unvorhersehbare Schwachstellen, ungleichmäßige Datenverteilung, unerwartete Bandbreiten- und Hardwareprobleme, ständig steigender Datenverkehr und neue Netzwerksegmente zwingen Sie dazu, Ihren Datenbankansatz, Ihr Datenmodell, Ihr Bereitstellungsmodell und Ihre Datenbankgröße zu überdenken.

...

Als ich anfing, über die Veröffentlichung dieses Artikels nachzudenken, befanden sich bereits fünf weitere Punkte auf meiner ursprünglichen Liste. Dann kam eine riesige Zahl neue Ideen darüber, was sonst noch abgedeckt werden kann. Daher geht der Artikel auf die am wenigsten offensichtlichen Probleme ein, die maximale Aufmerksamkeit erfordern. Dies bedeutet jedoch nicht, dass das Thema erschöpft ist und ich werde in meinen zukünftigen Materialien nicht mehr darauf zurückkommen und keine Änderungen am aktuellen vornehmen.

PS

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen