Citymobil - ein Handbuch zur Verbesserung der Verfügbarkeit bei Unternehmenswachstum für Startups. Teil 4



Dies ist der nächste Artikel der Reihe, der beschreibt, wie wir unsere Serviceverfügbarkeit in Citymobil erhöhen (Sie können die vorherigen Teile hier lesen: Teil 1 , Teil 2 , Teil 3 ). In weiteren Abschnitten werde ich ausführlich auf die Unfälle und Ausfälle eingehen.

1. Fehlerhafte Version: Datenbanküberlastung


Lassen Sie mich mit einem konkreten Beispiel für diese Art von Ausfall beginnen. Wir haben eine Optimierung implementiert: USE INDEX in einer SQL-Abfrage hinzugefügt; Sowohl beim Testen als auch in der Produktion beschleunigte es kurze Abfragen, aber die langen - verlangsamten sich. Die Verlangsamung der langen Abfragen wurde nur in der Produktion bemerkt. Infolgedessen führten viele lange parallele Abfragen dazu, dass die Datenbank eine Stunde lang nicht verfügbar war. Wir haben die Funktionsweise von USE INDEX gründlich untersucht. Wir haben es in der Do's and Dont-Datei beschrieben und die Ingenieure vor der falschen Verwendung gewarnt. Wir haben die Abfrage auch analysiert und festgestellt, dass sie hauptsächlich historische Daten abruft und daher für historische Anforderungen auf einem separaten Replikat ausgeführt werden kann. Selbst wenn dieses Replikat aufgrund einer Überlastung ausfällt, läuft das Geschäft weiter.

Wir sind danach immer wieder auf die gleichen Themen gestoßen und haben uns irgendwann entschlossen, diese Angelegenheit anzusprechen. Wir hatten den Code durchgesehen und alle möglichen Abfragen verschoben, ohne unseren Service auf die Replikate zu beeinträchtigen. Die Replikate selbst wurden je nach Kritikalitätsgrad aufgeteilt, sodass keiner von ihnen ausfallen und den Dienst beenden konnte. Als Ergebnis haben wir eine Architektur mit den folgenden Datenbanken entwickelt:

  • Master-Datenbank - für Schreibvorgänge und Abfragen, die sehr empfindlich auf die Datenaktualität reagieren;
  • Produktionsreplikate - für kurze Abfragen, die weniger empfindlich auf Datenaktualität reagieren;
  • Repliken für Preisanstiegskoeffizienten. Diese Replikate können 30-60 Sekunden zurückliegen. Dies ist nicht entscheidend, da sich die Koeffizienten nicht so häufig ändern. Wenn dieses Replikat ausfällt, funktioniert der Dienst nicht mehr. Die Preise stimmen einfach nicht vollständig mit dem Gleichgewicht zwischen Angebot und Nachfrage überein.
  • Replik für Betriebseinstellungen und das Call Center. Wenn es abstürzt, läuft das Geschäft weiter, jedoch ohne Kunden- und Treiberunterstützung, und wir können einige Einstellungen nicht vorübergehend ändern.
  • viele Replikate für Ad-hoc-Analysen und Dashboards;
  • MPP-Datenbank (Massively Parallel Processing) für einige umfangreiche Analysen mit vollständigen Abschnitten der historischen Daten.

Diese Architektur bot uns viel Raum für Wachstum und reduzierte eine Reihe von Abstürzen aufgrund nicht optimaler SQL-Abfragen. Aber es ist noch lange nicht perfekt. Wir planen, Sharding zu implementieren, damit wir Aktualisierungen und Löschungen skalieren und sehr empfindlich auf Datenaktualitätsabfragen reagieren können. Der MySQL-Sicherheitsspielraum ist nicht unendlich. Wir werden bald schwere Artillerie in Form einer In-Memory-Datenbank (z. B. Tarantool) benötigen. Ich werde auf jeden Fall in meinen nächsten Artikeln darüber sprechen.

Während wir uns mit nicht optimalem Code und Abfragen befassten, haben wir Folgendes verstanden: Jede Nichtoptimalität sollte beseitigt werden, bevor sie veröffentlicht wurde und nicht danach. Dies verringert das Risiko von Ausfällen und den Aufwand von Entwicklungsteams für die Optimierung. Wenn der Code bereits mit einigen neuen Versionen bereitgestellt wurde, ist es viel schwieriger, ihn zu optimieren. Als Ergebnis haben wir eine obligatorische Codeüberprüfung zur Optimierung eingeführt. Es wird von unseren erfahrensten Ingenieuren, unserer Elitetruppe, durchgeführt.

Wir haben auch damit begonnen, die besten Methoden zur Codeoptimierung zu sammeln, die für unsere Realia in den Do's und Dont's geeignet sind. Sie sind unten aufgeführt. Bitte nehmen Sie diese Praktiken nicht als unbestreitbare Wahrheit und versuchen Sie nicht, sie blind zu wiederholen. Jede Methode ist nur für eine bestimmte Situation und ein bestimmtes Geschäft sinnvoll. Dies ist nur ein Beispiel, um die Einzelheiten zu verdeutlichen:

  • Wenn eine SQL-Abfrage nicht vom Benutzer abhängt (z. B. die Bedarfskarte des Fahrers mit maximalen Tarifen und Anstiegskoeffizienten - diese Karte ist für jeden Benutzer der Treiber-App gleich), muss diese Abfrage in einem Cron-Skript mit ausgeführt werden eine bestimmte Frequenz (in diesem Fall ist einmal pro Minute ausreichend). Das Ergebnis muss in einen Cache (Memcached oder Redis) geschrieben werden, der im Produktionscode verwendet werden soll.
  • Wenn eine SQL-Abfrage mit den Daten arbeitet, deren Verzögerung für das Unternehmen nicht entscheidend ist, muss das Ergebnis in einem Cache mit einer gewissen TTL (z. B. 30 Sekunden) abgelegt und dann per Produktionscode aus dem Cache gelesen werden.
  • Wenn Sie sich in einer bestimmten Implementierung einer Servermethode (in PHP oder einer anderen serverseitigen Sprache) für eine SQL-Abfrage entscheiden, müssen Sie sicherstellen, dass die benötigten Daten nicht mit einer anderen SQL-Abfrage eingetroffen sind oder kurz vor dem Eintreffen stehen Ihr Code unten.
  • Gleiches gilt für Anfragen an einen Cache. Ein Cache ist schnell, aber immer noch eine Datenbank. So kann es auch überlastet werden. Ein häufiger Fehler ist, dass Sie denken, dass ein Cache eine Art normale speicherinterne Variable ist, und ihn als Variable verwenden. Der Zugriff darauf ist jedoch mit einem Netzwerk-Roundtrip verbunden und erzeugt eine Arbeitslast für Redis oder Memcached. Wenn die Daten bereits aus dem Cache eingetroffen sind, nehmen Sie nicht einfach das, was bereits entnommen wurde, aus dem Cache.
  • Wenn Sie während einer Webanforderungsverarbeitung (erneut in PHP oder einer anderen Sprache) eine Funktion aufrufen müssen, müssen Sie sicherstellen, dass keine zusätzlichen SQL-Abfragen oder Cache-Zugriffe ausgeführt werden. Wenn ein solcher Funktionsaufruf unvermeidlich ist, müssen Sie sicherstellen, dass er nicht geändert werden kann oder dass seine Logik nicht ausgefallen ist, um unnötige Datenbank- / Cache-Abfragen zu vermeiden.
  • Wenn eine SQL-Abfrage ausgeführt werden muss, müssen Sie unbedingt sicherstellen, dass Felder, die Sie benötigen, nicht zu bereits vorhandenen Abfragen in Ihrem Code oben oder unten hinzugefügt werden können.

2. Schlechter manueller Betrieb


Beispiele für diese Unfälle:

  • Bad ALTER, der die Datenbank überlastet oder eine Replikatverzögerung verursacht hat;
  • schlechtes DROP (z. B. sind wir auf einen Fehler in MySQL gestoßen, der die Datenbank beim Löschen einer Tabelle blockiert hat);
  • eine schwere Abfrage an einen Master, die versehentlich manuell durchgeführt wurde;
  • Ein Webserver wurde konfiguriert, obwohl er sich unter der tatsächlichen Arbeitslast befand, obwohl wir dachten, er sei außer Betrieb.

Um Ausfälle aus diesen Gründen zu minimieren, müssen wir jedes Mal die Art eines Unfalls untersuchen. Wir haben noch keine allgemeine Regel herausgefunden. Schauen wir uns noch einmal einige konkrete Beispiele an. Die Überspannungskoeffizienten (Taxifahrpreis zum Zeitpunkt und am Ort der hohen Nachfrage wird mit ihnen multipliziert) funktionierten irgendwann nicht mehr. Der Grund war, dass auf einem Datenbankreplikatserver ein Python-Skript arbeitete, bei dem die Daten für die Berechnung der Koeffizienten entnommen wurden und das Skript den gesamten Speicher verbrauchte und das Replikat ausfiel. Das Skript lief schon eine Weile; Der Einfachheit halber wurde es direkt auf der Replik ausgeführt. Das Problem wurde durch Neustart des Skripts behoben. Die folgenden Schlussfolgerungen wurden gezogen: Führen Sie keine fremden Skripte auf einem Datenbankserver aus (es wurde in Do's and Don't's geschrieben; sonst wäre es ein leerer Schuss!), Überwachen Sie die Speichernutzung auf einem Datenbankserver und benachrichtigen Sie per SMS Wenn diesem Server der Speicherplatz ausgeht.

Es ist wichtig, immer Schlussfolgerungen zu ziehen und sich nicht in der Situation "das Problem gesehen, behoben, vergessen" zu fühlen. Qualitativ hochwertiger Service kann nur angeboten werden, wenn man mit einer Schlussfolgerung davonkommt. Außerdem sind die SMS-Warnungen von entscheidender Bedeutung - sie erhöhen die Servicequalität, lassen sie nicht sinken und ermöglichen es uns, die Zuverlässigkeit zu erhöhen. Wie ein Bergsteiger, der eine stabile Position erreicht und sich dann in eine andere stabile Position hochzieht, diesmal jedoch nur höher.

Überwachung und Warnungen sind nicht sichtbar, wirken jedoch wie Eisenhaken, die in den Felsen des Unbekannten schneiden und verhindern, dass wir unter unsere Service Level Agreement fallen, die wir kontinuierlich erhöhen.

3. Osterei


Was wir "ein Osterei" nennen - ist eine Mine mit verzögerter Aktion, die wir noch nicht ausgelöst haben, obwohl sie schon eine Weile existiert. Außerhalb dieses Artikels wird dieser Begriff für undokumentierte Funktionen verwendet, die absichtlich erstellt wurden. In unserem Fall handelt es sich überhaupt nicht um eine Funktion, sondern um einen Fehler, der wie eine Zeitbombe wirkt und als Nebeneffekt einer gut gemeinten Aktivität auftritt.

Zum Beispiel:

  • Überfüllung von 32-Bit- auto_increment ;
  • Nichtoptimalität von Code / Konfiguration, ausgelöst durch hohe Arbeitsbelastung;
  • verzögertes Replizieren durch eine nicht optimale Abfrage, die durch ein neues Verwendungsmuster oder durch eine höhere Arbeitsbelastung hervorgerufen wird;
  • verzögertes Replikat durch eine nicht optimale UPDATE-Operation auf dem Master, die durch ein neues Workload-Muster verursacht wurde und die Replikation verzögerte.

Eine andere beliebte Art von Osterei ist nicht optimaler Code; genauer gesagt - nicht optimale SQL-Abfrage. Die Tabelle war früher kleiner und die Arbeitsbelastung geringer - die Abfrage funktionierte gut. Mit dem linearen Anstieg des Zeitplans und der linearen Zunahme der Arbeitsbelastung stieg der Ressourcenverbrauch eines Datenbankverwaltungssystems quadratisch. Normalerweise führt das zu einem drastischen negativen Effekt: Es ist, als wäre alles in Ordnung und dann plötzlich - hoppla!

Seltenere Szenarien - Kombination von Käfern und Ostereiern. Eine Veröffentlichung mit einem Fehler führte zur Vergrößerung einer Datenbanktabelle und zur Erhöhung einer Anzahl von Tabellenzeilen einer bestimmten Art, während ein bereits vorhandenes Osterei die Datenbanküberlastung aufgrund der langsameren Abfragen dieser erweiterten Tabelle verursachte.

Früher hatten wir jedoch einige nicht arbeitsbelastungsbezogene Ostereier. Zum Beispiel ein 32-Bit-Feld auto_increment in MYSQL. Nach etwas mehr als 2 Milliarden Zeilen schlagen Einfügungen fehl. Daher müssen wir in der modernen Welt nur 64-Bit-Felder auto_increment verwenden. Wir haben diese Lektion gut gelernt.

Wie gehe ich mit Ostereiern um? Die Antwort klingt einfach: a) Suche nach den alten Eiern, b) erlaube nicht, dass die neuen erscheinen. Wir versuchen beides. Die Suche nach den alten Eiern geht Hand in Hand mit unserer kontinuierlichen Codeoptimierung. Wir haben zwei der erfahrensten Ingenieure damit beauftragt, die Optimierung fast in Vollzeit durchzuführen. Sie finden Abfragen in slow.log, die am häufigsten Datenbankressourcen verwenden. Sie optimieren diese Abfragen und den Code um sie herum. Wir verringern die Wahrscheinlichkeit, dass neue Eier auftauchen, indem wir jedes Commit auf Optimalität testen, das von den oben genannten Sensei-Ingenieuren durchgeführt wird. Ihre Aufgabe ist es, auf die Fehler hinzuweisen, die sich auf die Leistung auswirken, Wege aufzuzeigen, um die Dinge zu verbessern und dieses Wissen an andere Ingenieure weiterzugeben.

Irgendwann, gleich nachdem wir ein anderes Osterei gefunden hatten, stellten wir fest, dass es gut ist, nach langsamen Abfragen zu suchen, aber wir hätten auch nach Abfragen suchen sollen, die langsam erscheinen, aber schnell funktionieren. Dies sind die nächsten Kandidaten, die im Falle eines weiteren explosiven Tischwachstums alles zum Absturz bringen. Das dumme, aber offensichtliche Beispiel hier ist eine Abfrage, die eine Tabelle mit 10 Zeilen vollständig scannt, ohne überhaupt Indizes zu verwenden. Es wird vorerst schnell funktionieren. Wenn die Tabelle jedoch groß genug ist, wird die Datenbank durch die Abfrage heruntergefahren. Das ist das Osterei.

4. Externe Ursachen


Dies sind die Ursachen, die wir anscheinend nicht gut kontrollieren können. Anders ausgedrückt, dies sind die Ursachen, die nur gemildert, aber nicht beseitigt werden können. Zum Beispiel:

  • Drosselung unserer Anfragen durch einen Kartendienstleister. Dies kann durch die Kontrolle der Service-Nutzung, die Einhaltung eines bestimmten Workload-Levels, die vorherige Planung der Workload-Erhöhung und den Kauf einer Service-Erweiterung gemindert werden. Auf Karten können wir jedoch nicht verzichten.
  • Netzwerkfehler in einem Rechenzentrum. Dies kann durch Platzieren der Kopie des Dienstes in einem Sicherungsdatenzentrum verringert werden. Wir können jedoch weder auf ein physisches Rechenzentrum noch auf eine Cloud verzichten.
  • Zahlungsservice nach unten. Dies kann durch die Sicherung von Zahlungsdiensten verringert werden. Auf Zahlungen können wir jedoch nicht verzichten.
  • Fehlerhafte Blockierung des Datenverkehrs durch einen DDoS-Schutzdienst. Sie können dies verringern, indem Sie den DDoS-Schutzdienst standardmäßig deaktivieren und ihn nur im Falle eines DDoS-Angriffs aktivieren. Auf DDoS-Schutz können wir jedoch nicht verzichten.

Da selbst die Minderung einer externen Ursache ein langwieriges und teures Unterfangen ist, haben wir begonnen, Statistiken für Unfälle zu sammeln, die durch externe Gründe verursacht wurden, und auf die Anhäufung kritischer Massen zu warten. Wir haben kein Rezept zur Definition einer kritischen Masse. Es geht nur um Intuition. Wenn wir beispielsweise fünf Mal aufgrund von Problemen mit dem DDoS-Schutzdienst vollständig ausgefallen wären, würde sich mit jeder nachfolgenden Ausfallzeit der Bedarf an Alternativen immer weiter verschärfen.

Auf der anderen Seite tun wir das definitiv, wenn wir irgendwie alles mit einem nicht verfügbaren externen Dienst zum Laufen bringen können. Hier hilft uns die Obduktionsanalyse jedes Ausfalls. Es sollte immer eine Schlussfolgerung geben. Was bedeutet, ob es Ihnen gefällt oder nicht - wir haben immer eine Problemumgehung gefunden.



Im letzten Teil werde ich über eine weitere Art von Ausfällen und die Schlussfolgerungen sprechen, die wir daraus gezogen haben, wie wir den Entwicklungsprozess geändert haben und welche Automatisierung wir eingeführt haben. Bleib dran!

Source: https://habr.com/ru/post/de450678/


All Articles