Die berüchtigten Fehler und wie man sie am Beispiel von ClickHouse vermeidet

Wenn Sie Code schreiben, bereiten Sie sich auf Probleme vor. Sie werden es definitiv sein und sollten von allen Seiten erwartet werden: von Ihrem Code und Compiler, vom Betriebssystem und der Hardware, und Benutzer werfen manchmal „Überraschungen“ auf. Wenn Sie den Cluster auf kosmische Skalen skaliert haben, erwarten Sie "Space" -Fehler. Besonders wenn es um Daten aus dem Internetverkehr geht.


Alexey Milovidov (o6CuFl2Q) wird über die lächerlichsten, entmutigendsten und hoffnungslosesten Probleme seiner Erfahrung bei der Entwicklung und Unterstützung von ClickHouse sprechen. Mal sehen, wie sie debuggt werden mussten und welche Maßnahmen die Entwickler von Anfang an ergreifen sollten, damit es weniger Probleme gibt.

Notorische Fehler


Wenn Sie Code geschrieben haben, bereiten Sie sich sofort auf Probleme vor.

Fehler im Code. Sie werden benötigt. Angenommen, Sie haben den perfekten Code geschrieben, kompiliert, aber im Compiler werden Fehler angezeigt, und der Code funktioniert nicht richtig. Wir haben den Compiler repariert, alles kompiliert - führen Sie es aus. Aber (unerwartet) funktioniert alles falsch, weil es auch Fehler im Kernel des Betriebssystems gibt .

Wenn das Betriebssystem keine Fehler enthält, liegt dies zwangsläufig in der Hardware vor . Selbst wenn Sie den perfekten Code geschrieben haben, der perfekt auf der perfekten Hardware funktioniert, treten immer noch Probleme auf, z. B. Konfigurationsfehler . Es scheint, dass Sie alles richtig gemacht haben, aber jemand hat einen Fehler in der Konfigurationsdatei gemacht, und alles funktioniert nicht mehr.

Wenn alle Fehler behoben wurden, beenden die Benutzer den Vorgang, da sie Ihren Code ständig "falsch" verwenden. Das Problem liegt aber definitiv nicht bei den Benutzern, sondern im Code: Sie haben etwas geschrieben, das schwer zu verwenden ist .

Schauen wir uns diese Fehler anhand einiger Beispiele an.

Konfigurationsfehler


Löschen von Daten . Der erste Fall aus der Praxis. Zum Glück nicht meine und nicht Yandex, mach dir keine Sorgen.

Einführung zuerst. Die Architektur eines Map-Reduce-Clusters (z. B. Hadoop) besteht aus mehreren Datenservern (Datenknoten), die Daten speichern, und einem oder mehreren Master-Servern, die den Speicherort aller Daten auf den Servern kennen.

Datenknoten kennen die Adresse des Masters und stellen eine Verbindung zu diesem her. Der Assistent überwacht, wo und welche Daten sich befinden sollen, und gibt den Datenknoten verschiedene Befehle: "Laden Sie die Daten X herunter, Sie müssen die Daten Y haben und die Daten Z löschen." Was könnte schiefgehen?

Wenn eine neue Konfigurationsdatei auf alle Datenknoten hochgeladen wurde, wurden sie fälschlicherweise von einem anderen Cluster aus mit dem Master verbunden und nicht mit ihrem eigenen. Der Master sah sich die Daten an, über die die Datenknoten informiert wurden, entschied, dass die Daten falsch waren und gelöscht werden sollten. Das Problem wurde festgestellt, als die Hälfte der Daten gelöscht wurde.


Die epischsten Fehler sind diejenigen, die zum versehentlichen Löschen von Daten führen.
Dies zu vermeiden ist sehr einfach.

Daten nicht löschen . Legen Sie es beispielsweise in ein separates Verzeichnis oder löschen Sie es mit Verzögerung. Zuerst übertragen wir, damit sie für den Benutzer nicht sichtbar sind. Wenn er feststellt, dass innerhalb weniger Tage etwas verschwunden ist, geben wir es zurück.

Löschen Sie keine unerwarteten Daten, wenn die Ursache unbekannt ist . Begrenzen Sie programmgesteuert den Beginn des Löschens unbekannter Daten: unerwartet, mit seltsamen Namen oder wenn zu viele vorhanden sind. Der Administrator wird feststellen, dass der Server nicht startet und eine Nachricht schreibt, und wird verstehen.

Wenn das Programm destruktive Aktionen ausführt, isolieren Sie Test und Produktion auf Netzwerkebene(iptables). Das Löschen von Dateien oder das Senden von E-Mails ist beispielsweise eine destruktive Aktion, da dadurch die Aufmerksamkeit einer Person „aufgefressen“ wird. Setzen Sie eine Schwelle auf sie: Hundert Briefe können gesendet werden, und für tausend setzen Sie ein Sicherheits-Kontrollkästchen, das gesetzt wird, bevor etwas Schreckliches passiert.

Konfigurationen . Das zweite Beispiel stammt bereits aus meiner Praxis.

Eine gute Firma hatte irgendwie einen seltsamen ClickHouse-Cluster. Das Seltsame war, dass die Replikate nicht synchronisiert wurden. Beim Neustart des Servers wurde er nicht gestartet und es wurde die Meldung angezeigt, dass alle Daten falsch waren: „Es gibt viele unerwartete Daten, ich werde nicht starten. Wir müssen die Flagge setzen force_restore_dataund es herausfinden. “

Niemand konnte es in der Firma herausfinden - sie haben nur die Flagge gesetzt. Gleichzeitig verschwand die Hälfte der Daten irgendwo, was zu Diagrammen mit Lücken führte. Die Entwickler wandten sich an mich, ich dachte, dass etwas Interessantes passiert, und beschlossen, dies zu untersuchen. Als der Morgen einige Stunden später kam und die Vögel vor dem Fenster zu singen begannen, wurde mir klar, dass ich nichts verstand.

Der ClickHouse-Server verwendet den ZooKeeper-Dienst zur Koordination. ClickHouse speichert Daten, und ZooKeeper bestimmt, auf welchen Servern welche Daten liegen sollen: Speichert Metadaten darüber, auf welchen Daten sich welches Replikat befinden soll. ZooKeeper ist auch ein Cluster - er repliziert nach einem sehr guten verteilten Konsensalgorithmus mit strenger Konsistenz.

In der Regel besteht ZooKeeper aus 3 Computern, manchmal aus 5. In der ClickHouse-Konfiguration werden alle Computer gleichzeitig angezeigt, die Verbindung wird mit einem zufälligen Computer hergestellt, interagiert mit diesem Computer und dieser Server repliziert alle Anforderungen.

Was ist passiert? Das Unternehmen hatte drei ZooKeeper-Server. Sie arbeiteten jedoch nicht als Cluster von drei Knoten , sondern als drei unabhängige Knoten - drei Cluster von einem Knoten. Ein ClickHouse stellt eine Verbindung zu einem Server her und schreibt Daten. Die Replikate möchten diese Daten herunterladen, sind jedoch nirgends zu finden. Beim Neustart stellt der Server eine Verbindung zu einem anderen ZooKeeper her: Er sieht, dass die Daten, mit denen er zuvor gearbeitet hat, überflüssig sind, und muss irgendwo verschoben werden. Er löscht sie nicht, sondern überträgt sie in ein separates Verzeichnis - in ClickHouse werden Daten nicht so einfach gelöscht.

Ich beschließe, die ZooKeeper-Konfiguration zu korrigieren. Ich benenne alle Daten um und fordere ATTACHTeile der Daten aus dem Verzeichnis an detached/unexpeted_*.

Infolgedessen wurden alle Daten wiederhergestellt, die Replikate wurden synchronisiert, es gab keine Verluste, die Diagramme waren kontinuierlich. Das Unternehmen ist zufrieden, dankbar, als hätte es bereits vergessen, wie alles zuvor schlecht funktioniert hatte.

Dies waren einfache Konfigurationsfehler. Weitere Fehler werden im Code enthalten sein.

Fehler im Code


Wir schreiben Code in C ++. Dies bedeutet, dass wir bereits Probleme haben.
Das nächste Beispiel ist ein echter Fehler aus der Produktion im Yandex.Metrica-Cluster (2015) - eine Folge des C ++ - Codes. Der Fehler war, dass der Benutzer manchmal eine Fehlermeldung erhielt, anstatt auf die Anfrage zu antworten:

  • "Prüfsumme stimmt nicht überein, beschädigte Daten" - die Prüfsumme stimmt nicht überein, die Daten sind kaputt - beängstigend!
  • "LRUCache wurde inkonsistent. Es muss ein Fehler darin sein “- der Cache wurde inkonsistent, höchstwahrscheinlich ein Fehler darin.

Der Code, den wir geschrieben haben, informiert sich selbst darüber, dass dort ein Fehler vorliegt.

" Prüfsumme stimmt nicht überein, beschädigte Daten ." Prüfsummen komprimierter Datenblöcke werden vor dem Dekomprimieren geprüft. Normalerweise tritt dieser Fehler auf, wenn Daten im Dateisystem beschädigt werden. Aus verschiedenen Gründen stellen sich einige Dateien beim Neustart des Servers als Müll heraus.

Aber hier ist ein anderer Fall: Ich habe die Datei manuell gelesen, die Prüfsumme stimmt überein, es gibt keinen Fehler. Einmal aufgetreten, wird der Fehler bei wiederholter Anforderung stabil reproduziert. Wenn der Server neu gestartet wird, verschwindet der Fehler für eine Weile und erscheint dann wieder stabil.

Vielleicht ist die Sache im RAM? Eine typische Situation ist, wenn Bits darin schlagen. Ich schaue in dmesg(kern.log), aber es gibt keine Maschinenprüfungsausnahmen - sie schreiben normalerweise, wenn etwas mit dem RAM nicht stimmt. Wenn der Server RAM geschlagen hätte, würde nicht nur mein Programm falsch funktionieren, sondern alle anderen würden zufällig Fehler erzeugen. Es gibt jedoch keine anderen Manifestationen des Fehlers.

"LRUCache wurde inkonsistent. Es muss ein Fehler darin sein. " Dies ist ein klarer Fehler im Code, und wir schreiben in C ++ - vielleicht Speicherzugriff? Tests unter AddressSanitizer, ThreadSanitizer, MemorySanitizer und UndefinedBehaviorSanitizer in CI zeigen jedoch nichts.

Vielleicht werden einige Testfälle nicht behandelt? Ich sammle den Server mit AddressSanitizer, führe ihn in der Produktion aus - er fängt nichts ab. Für einige Zeit wird der Fehler durch Zurücksetzen eines Markierungscaches (Sachet-Cache) behoben.

Eine der Programmierregeln besagt: Wenn der Fehler nicht klar ist, schauen Sie sich den Code genau an und hoffen Sie, dort etwas zu finden. Ich habe es getan, einen Fehler gefunden, ihn behoben - es hat nicht geholfen. Ich sehe mir eine andere Stelle im Code an - es gibt auch einen Fehler. Korrigiert, hat wieder nicht geholfen. Ich habe ein paar mehr behoben, der Code wurde besser, aber der Fehler verschwand immer noch nicht!

Ursache. Der Versuch, ein Muster nach Server, Zeit und Art der Last zu finden, hilft nichts. Dann erkannte er, dass sich das Problem nur in einem der Cluster und niemals in den anderen manifestiert. Der Fehler wird nicht so oft reproduziert, erscheint jedoch nach einem Neustart immer auf einem Cluster und auf dem anderen ist alles sauber.

Es stellte sich heraus, dass der Grund dafür ist, dass im "Problem" -Cluster eine neue Funktion verwendet wurde - Cache-Wörterbücher. Sie verwenden den handgeschriebenen Speicherzuordner ArenaWithFreeLists . Wir haben nicht nur in C ++ geschrieben, sondern auch eine Art benutzerdefinierte Allokatoren gesehen - wir verurteilen uns zweimal zu Problemen.

ArenaWithFreeLists ist ein Teil des Speichers, in dem der Speicher nacheinander in Größen zugewiesen wird, die durch zwei teilbar sind: 16, 32, 64 Byte. Wenn Speicher freigegeben wird, bilden sie eine einfach verknüpfte Liste freier FreeLists-Blöcke.

Schauen wir uns den Code an.

class ArenaWithFreeLists
{
    Block * free_lists[16] {};
    static auto sizeToPreviousPowerOfTwo(size_t size)
    {
        return _bit_scan_reverse(size - 1);
    }

    char * alloc(size_t size)
    {
        const auto list_idx = findFreeListIndex(size);
        free_lists[list_idx] ->...
    }
}

Es wird eine Funktion _bit_scan_reversemit einem Unterstrich am Anfang verwendet.
Es gibt eine ungeschriebene Regel: "Wenn eine Funktion am Anfang einen Unterstrich hat, lesen Sie die Dokumentation einmal und wenn zwei, lesen Sie sie zweimal."
Wir hören zu und lesen die Dokumentation: „int _bit_scan_reverse (int a). Setzen Sie dst auf den Index des höchsten gesetzten Bits in der 32-Bit-Ganzzahl a. Wenn in a keine Bits gesetzt sind, ist dst undefiniert . " Wir scheinen ein Problem gefunden zu haben.

In C ++ wird diese Situation für den Compiler als unmöglich angesehen. Der Compiler kann undefiniertes Verhalten, diese „Unmöglichkeit“, als Annahme für die Optimierung des Codes verwenden.

Der Compiler macht nichts falsch - er generiert ehrlich Montageanweisungen bsr %edi, %eax. Aber, wenn der Operand null ist, hat die Anweisung bsrnicht definiertes Verhalten nicht an der C ++ Ebene, sondern auf CPU - Ebene. Wenn das Quellregister Null ist, ändert sich das Zielregister nicht: Es gab etwas Müll am Eingang, dieser Müll bleibt auch am Ausgang.

Das Ergebnis hängt davon ab, wo der Compiler diese Anweisung ablegt. Manchmal ist eine Funktion mit dieser Anweisung inline, manchmal nicht. Im zweiten Fall gibt es so etwas wie diesen Code:

bsrl %edi, %eax
retq

Dann habe ich mir ein Beispiel für ähnlichen Code in meiner Binärdatei mit angesehen objdump.



Nach den Ergebnissen sehe ich, dass manchmal das Quellregister und das Zielregister gleich sind. Wenn es Null gab, ist das Ergebnis auch Null - alles ist in Ordnung. Aber manchmal sind die Register unterschiedlich und das Ergebnis ist Müll.

Wie manifestiert sich dieser Fehler?

  • Wir verwenden Garbage als Index im FreeLists-Array. Anstelle eines Arrays gehen wir zu einer entfernten Adresse und erhalten Speicherzugriff.
  • Wir haben Glück, dass fast alle Adressen in der Nähe mit Daten aus dem Cache gefüllt sind - wir verderben den Cache. Der Cache enthält Datei-Offsets.
  • Wir lesen Dateien mit dem falschen Versatz. Aus dem falschen Offset erhalten wir die Prüfsumme. Es gibt jedoch keine Prüfsumme, sondern etwas anderes - diese Prüfsumme stimmt nicht mit den folgenden Daten überein.
  • Wir erhalten den Fehler "Prüfsumme stimmt nicht überein, beschädigte Daten".

Glücklicherweise sind nicht Daten beschädigt, sondern nur der Cache im RAM. Wir wurden sofort über den Fehler informiert, da wir die Daten überprüfen. Der Fehler wurde am 27. Dezember 2015 behoben und ging zum Feiern.

Wie Sie sehen, kann zumindest der falsche Code behoben werden. Aber wie behebt man Fehler in der Hardware?

Käfer in Eisen


Dies sind nicht einmal Fehler, sondern physikalische Gesetze - unvermeidliche Auswirkungen. Nach physikalischen Gesetzen ist Eisen unweigerlich fehlerhaft.

Nichtatomares Schreiben in RAID . Zum Beispiel haben wir RAID1 erstellt. Es besteht aus zwei Festplatten. Dies bedeutet, dass ein Server ein verteiltes System ist: Daten werden auf eine Festplatte und auf eine andere geschrieben. Was aber, wenn Daten auf eine Disc geschrieben werden und die Stromversorgung während der Aufnahme auf die zweite unterbrochen wird? Daten auf einem RAID1-Array sind nicht konsistent. Wir werden nicht verstehen können, welche Daten korrekt sind, weil wir das eine oder andere Byte lesen werden.

Sie können damit umgehen, indem Sie das Protokoll platzieren. In ZFS ist dieses Problem beispielsweise gelöst, aber dazu später mehr.

Bit Rot auf HDD und SSD. Bits auf Festplatten und auf SSDs können einfach so schlecht werden. Moderne SSDs, insbesondere solche mit mehrstufigen Zellen, sollen sicherstellen, dass sich die Zellen ständig verschlechtern. Fehlerkorrekturcodes helfen, aber manchmal verschlechtern sich die Zellen so sehr und so sehr, dass selbst dies nicht spart. Es werden unentdeckte Fehler erhalten.

Bit flippt im RAM (aber was ist mit ECC?). Im RAM von Servern sind auch Bits beschädigt. Es hat auch Fehlerkorrekturcodes. Wenn Fehler auftreten, sind sie normalerweise in den Meldungen im Linux-Kernel-Protokoll in dmesg sichtbar. Wenn es viele Fehler gibt, sehen wir etwas wie: "N Millionen Fehler mit Speicher wurden behoben." Aber einzelne Teile werden nicht bemerkt und mit Sicherheit wird etwas fehlerhaft sein.

Bit-Flips auf CPU- und Netzwerkebene . Es gibt Fehler auf CPU-Ebene, in CPU-Caches und natürlich beim Übertragen von Daten über ein Netzwerk.

Wie manifestieren sich Eisenfehler normalerweise? Das Ticket " Ein fehlerhafter Znode verhindert das Starten von ClickHouse " geht an GitHub - die Daten im ZooKeeper-Knoten sind beschädigt.

In ZooKeeper schreiben wir normalerweise einige Metadaten im Klartext. Mit ihm stimmt etwas nicht - " Replik " ist sehr seltsam geschrieben.



Es kommt selten vor, dass sich aufgrund eines Fehlers im Code ein Bit ändert. Natürlich können wir einen solchen Code schreiben: Wir nehmen den Bloom-Filter, ändern das Bit an bestimmten Adressen, berechnen die Adressen falsch, ändern das falsche Bit, es fällt auf einige Daten. Das ist es, jetzt in ClickHouse ist es nicht „ Replik“ , sondern „ repli b a “ und auf sie alle Daten falsch ist. Aber normalerweise ist eine Änderung in einem Bit ein Symptom für Eisenprobleme.

Vielleicht kennen Sie das Beispiel der Bitquatting. Artyom Dinaburg hat ein Experiment durchgeführt : Es gibt Domains im Internet, die viel Verkehr haben, obwohl Benutzer diese Domains nicht alleine aufrufen. Beispielsweise ist eine solche Domain FB-CDN.com ein Facebook-CDN.

Artyom hat eine ähnliche Domain (und viele andere) registriert, aber ein bisschen geändert. Zum Beispiel FA-CDN.com anstelle von FB-CDN.com. Die Domain wurde nirgendwo veröffentlicht, aber es kam Verkehr dazu. Manchmal wurde der FB-CDN-Host in die HTTP-Header geschrieben, und die Anforderung wurde aufgrund von RAM-Fehlern auf den Geräten der Benutzer an einen anderen Host gesendet. RAM mit Fehlerkorrektur hilft nicht immer. Manchmal stört es sogar und führt zu Schwachstellen (lesen Sie über Rowhammer, ECCploit, RAMBleed).
Fazit: Überprüfen Sie die Daten immer selbst.
Überprüfen Sie beim Schreiben in das Dateisystem unbedingt. Überprüfen Sie beim Senden über das Netzwerk auch die Check-Summary - erwarten Sie nicht, dass dort Check-Summen vorhanden sind.

Weitere Bugs! ..


Produktionscluster-Metriken . Benutzer, die auf eine Anfrage antworten, erhalten manchmal eine Ausnahme: "Prüfsumme stimmt nicht überein: beschädigte Daten" - die Prüfsumme ist nicht korrekt, die Daten sind beschädigt.



In der Fehlermeldung werden detaillierte Daten angezeigt: Welcher Prüfbetrag wurde erwartet, welcher Prüfbetrag ist tatsächlich in diesen Daten enthalten, die Größe des Blocks, für den wir den Prüfbetrag prüfen, und der Ausnahmekontext.

Als wir das Paket über das Netzwerk von einem Server erhalten haben, ist eine Ausnahme aufgetreten - es kommt mir bekannt vor. Vielleicht wieder durch Erinnerung, Rassenzustand oder etwas anderes.

Diese Ausnahme trat 2015 auf. Der Fehler wurde behoben, er erschien nicht mehr. Im Februar 2019 erschien er plötzlich wieder. Zu dieser Zeit war ich auf einer der Konferenzen, meine Kollegen haben sich mit dem Problem befasst. Der Fehler wurde mit ClickHouse mehrmals täglich auf 1000 Servern reproduziert: Es ist nicht möglich, Statistiken auf einem Server und dann auf einem anderen zu sammeln. Gleichzeitig gab es zu diesem Zeitpunkt keine Neuerscheinungen. Es hat nicht geklappt und das Problem gelöst, aber nach ein paar Tagen ist der Fehler selbst verschwunden.

Sie vergaßen den Fehler und am 15. Mai 2019 wiederholte er sich. Wir haben uns weiter um sie gekümmert. Als erstes habe ich mir alle verfügbaren Protokolle und Grafiken angesehen. Er studierte sie den ganzen Tag, verstand nichts, fand keine Muster. Wenn das Problem nicht reproduziert werden kann, besteht die einzige Möglichkeit darin, alle Fälle zu erfassen und nach Mustern zu suchenund Sucht. Möglicherweise funktioniert der Linux-Kernel nicht richtig mit dem Prozessor, speichert oder lädt falsch Register.

Hypothesen und Muster


7 von 9 Servern mit E5-2683 v4 sind ausgefallen. Von der Fehleranfälligkeit ist jedoch nur etwa die Hälfte des E5-2683 v4 eine leere Hypothese.

Fehler werden normalerweise nicht wiederholt . Zusätzlich zum mtauxyz-Cluster, in dem tatsächlich beschädigte Daten vorhanden sind (fehlerhafte Daten auf der Festplatte). Dies ist ein weiterer Fall, wir lehnen die Hypothese ab.

Der Fehler hängt nicht vom Linux-Kernel ab - auf verschiedenen Servern überprüft, nichts gefunden. Nichts interessantes in kern.log, machine check exceptionkeine Nachrichten . In Netzwerkgrafiken, einschließlich Retransmittern, CPU, IO, Netzwerk, nichts Interessantes. Alle Netzwerkadapter auf den Servern, auf denen Fehler auftreten und nicht angezeigt werden, sind identisch.

Es gibt keine Muster . Was zu tun ist? Suchen Sie weiter nach Mustern. Zweiter Versuch.

Ich schaue mir Upsime-Server an:Die Betriebszeit ist hoch, die Server arbeiten stabil , Segfault und so etwas nicht. Ich freue mich immer, wenn ich sehe, dass das Programm mit segfault abgestürzt ist - zumindest ist es abgestürzt. Schlimmer noch, wenn es einen Fehler gibt, verdirbt es etwas, aber niemand bemerkt es.

Fehler werden nach Tag gruppiert und treten innerhalb weniger Tage auf. In etwa 2 Tagen erscheinen mehr, in einigen weniger, dann wieder mehr - es ist nicht möglich, den Zeitpunkt des Auftretens von Fehlern genau zu bestimmen.

Einige Fehler stimmen mit den Paketen und dem erwarteten Scheckbetrag überein. Die meisten Fehler haben nur zwei Paketoptionen. Ich hatte Glück, weil wir in der Fehlermeldung genau den Wert der Prüfsumme hinzugefügt haben, was beim Erstellen von Statistiken geholfen hat.

Keine Servermusterwoher wir die Daten lesen. Die Größe des komprimierten Blocks, den wir prüfen, beträgt weniger als ein Kilobyte. Sah auf die Packungsgrößen in HEX. Dies war für mich nicht nützlich - die binäre Darstellung von Paketgrößen und Prüfsummen ist nicht erkennbar.

Ich habe den Fehler nicht behoben - ich habe wieder nach Mustern gesucht. Dritter Versuch.

Aus irgendeinem Grund tritt der Fehler nur in einem der Cluster auf - in den dritten Replikaten im Vladimir DC (wir nennen Rechenzentren gerne nach Städtenamen). Im Februar 2019 trat auch in Vladimirs DC ein Fehler auf, jedoch in einer anderen Version von ClickHouse. Dies ist ein weiteres Argument gegen die Hypothese, dass wir den falschen Code geschrieben haben. Wir haben es von Februar bis Mai bereits dreimal umgeschrieben - der Fehler ist wahrscheinlich nicht im Code enthalten .

Alle Fehler beim Lesen von Paketen über das Netzwerk -while receiving packet from. Das Paket, bei dem der Fehler aufgetreten ist, hängt von der Struktur der Anforderung ab. Bei Anfragen, die sich in der Struktur unterscheiden, ein Fehler bei verschiedenen Prüfsummen. Bei Anforderungen, bei denen sich der Fehler auf derselben Prüfsumme befindet, unterscheiden sich die Konstanten.

Alle Anfragen mit einem Fehler, bis auf eine, sind GLOBAL JOIN. Zum Vergleich gibt es jedoch eine ungewöhnlich einfache Anforderung, und die komprimierte Blockgröße dafür beträgt nur 75 Byte.

SELECT max(ReceiveTimestamp) FROM tracking_events_all 
WHERE APIKey = 1111 AND (OperatingSystem IN ('android', 'ios'))

Wir lehnen die Hypothese des Einflusses ab GLOBAL JOIN.

Am interessantesten ist, dass die betroffenen Server nach ihren Namen in Bereiche gruppiert sind :
mtxxxlog01-{39..44 57..58 64 68..71 73..74 76}-3.

Ich war müde und verzweifelt und begann nach völlig wahnhaften Mustern zu suchen. Es ist gut, dass ich den Code nicht mit Numerologie debuggen konnte. Aber es gab immer noch Hinweise.

  • Die Gruppen der Problemserver sind dieselben wie im Februar.
  • Problemserver befinden sich in bestimmten Teilen des Rechenzentrums. In DC Vladimir gibt es sogenannte Linien - seine verschiedenen Teile: VLA-02, VLA-03, VLA-04. Fehler sind klar gruppiert: In einigen Warteschlangen ist es gut (VLA-02), in anderen Problemen (VLA-03, VLA-04).

Debugging eingeben


Es blieb nur das Debuggen mit der "Spear" -Methode. Dies bedeutet, die Hypothese zu bilden: "Was passiert, wenn Sie dies versuchen?" und Daten sammeln. Zum Beispiel habe ich eine query_logeinfache Abfrage mit einem Fehler in der Tabelle gefunden, für die die Paketgröße size of compressed blocksehr klein ist (= 107).



Ich nahm die Anfrage, kopierte sie und führte sie manuell mit clickhouse-local aus.

strace -f -e trace=network -s 1000 -x \
clickhouse-local --query "
    SELECT uniqIf(DeviceIDHash, SessionType = 0)
    FROM remote('127.0.0.{2,3}', mobile.generic_events)
    WHERE StartDate = '2019-02-07' AND APIKey IN (616988,711663,507671,835591,262098,159700,635121,509222)
        AND EventType = 1 WITH TOTALS" --config config.xml

Mit Hilfe von strace habe ich einen Snapshot (Dump) von Blöcken über das Netzwerk erhalten - genau dieselben Pakete, die empfangen werden, wenn diese Anforderung ausgeführt wird, und ich kann sie untersuchen. Sie können tcpdump dafür verwenden, dies ist jedoch unpraktisch: Es ist schwierig, eine bestimmte Anforderung vom Produktionsverkehr zu isolieren.

Mit strace können Sie den ClickHouse-Server selbst verfolgen. Aber dieser Server funktioniert in der Produktion. Wenn ich das mache, bekomme ich eine Reihe unverständlicher Informationen. Aus diesem Grund habe ich ein separates Programm gestartet, das genau eine Anforderung ausführt. Bereits für dieses Programm starte ich strace und bekomme, was über das Netzwerk übertragen wurde.

Die Anfrage wird fehlerfrei ausgeführt - der Fehler wird nicht reproduziert . Bei einer Reproduktion wäre das Problem behoben. Daher habe ich die Pakete in eine Textdatei kopiert und das Protokoll manuell analysiert.



Der Scheckbetrag war der gleiche wie erwartet. Dies ist genau das Paket, bei dem manchmal, zu einem anderen Zeitpunkt, bei anderen Anforderungen Fehler aufgetreten sind. Bisher sind jedoch keine Fehler aufgetreten.

Ich habe ein einfaches Programm geschrieben, das ein Paket nimmt und den Prüfbetrag überprüft, wenn ein Bit in jedem Byte ersetzt wird. Das Programm führte an jeder möglichen Position einen Bit-Flip durch und las den Prüfbetrag.



Ich habe das Programm gestartet und festgestellt, dass Sie, wenn Sie den Wert eines Bits ändern, genau die kaputte Prüfsumme erhalten, für die es eine Beschwerde gibt

Hardwareproblem


Wenn in der Software ein Fehler auftritt (z. B. beim Durchlaufen des Speichers), ist ein Einzelbit-Flip unwahrscheinlich. Daher erschien eine neue Hypothese - das Problem liegt in der Drüse.

Man könnte den Deckel des Laptops schließen und sagen: "Das Problem ist nicht auf unserer Seite, aber in der Hardware machen wir das nicht." Aber nein, versuchen wir zu verstehen, wo das Problem liegt: im RAM, auf der Festplatte, im Prozessor, auf der Netzwerkkarte oder im RAM der Netzwerkkarte in den Netzwerkgeräten.

Wie lokalisiere ich ein Hardwareproblem?

  • Das Problem trat auf und verschwand an bestimmten Daten.
  • Betroffene Server werden nach ihren Namen gruppiert : mtxxxlog01-{39..44 57..58 64 68..71 73..74 76}-3.
  • Die Gruppen der Problemserver sind dieselben wie im Februar.
  • Problemserver befinden sich nur in bestimmten Warteschlangen des Rechenzentrums.

Es gab Fragen an Netzwerktechniker - die Daten schlagen auf Netzwerk-Switches. Es stellt sich heraus, dass Netzwerktechniker genau an diesen Daten Switches gegen andere ausgetauscht haben. Nach einer Frage ersetzten sie sie durch die vorherigen und das Problem verschwand.

Das Problem ist behoben, es bleiben jedoch noch Fragen offen (nicht mehr für Ingenieure).

Warum hilft ECC (Fehlerkorrekturspeicher) bei Netzwerk-Switches nicht? Da sich mehrere Bit-Flip gegenseitig kompensieren können, wird ein unerkannter Fehler angezeigt.

Warum helfen TCP-Prüfsummen nicht? Sie sind schwach. Wenn sich nur ein Bit in den Daten geändert hat, wird die Änderung bei TCP-Prüfsummen immer angezeigt. Wenn sich zwei Bits geändert haben, werden die Änderungen möglicherweise nicht erkannt - sie heben sich gegenseitig auf.

In unserem Paket hat sich nur ein Bit geändert, aber der Fehler ist nicht sichtbar. Das liegt daran, dass sich im TCP-Segment 2 Bits geändert haben: Sie haben die Prüfsumme daraus berechnet, sie stimmte überein. In einem TCP-Segment befindet sich jedoch mehr als ein Paket unserer Anwendung. Und für einen von ihnen berücksichtigen wir bereits unsere Prüfsumme. In diesem Paket hat sich nur ein Bit geändert.

Warum helfen Ethernet-Prüfsummen nicht - sind sie stärker als TCP? Ethernet-PrüfbetragÜberprüfen Sie die Daten, damit sie während der Übertragung durch ein Segment nicht unterbrochen werden (ich kann mich mit der Terminologie irren, ich bin kein Netzwerktechniker). Netzwerkgeräte leiten diese Pakete weiter und können während der Weiterleitung einige Daten weiterleiten. Daher werden die Scheckbeträge einfach nachgezählt. Wir haben überprüft - auf dem Draht haben sich die Pakete nicht geändert. Wenn sie jedoch den Netzwerk-Switch selbst schlagen, wird der Prüfbetrag neu berechnet (er ist anders) und das Paket weitergeleitet.
Nichts wird Sie retten - überprüfen Sie selbst. Erwarten Sie nicht, dass jemand dies für Sie tut.
Für Datenblöcke wird eine 128-Bit-Prüfsumme berücksichtigt (dieser Overkill nur für den Fall). Wir informieren den Benutzer korrekt über den Fehler. Daten werden über das Netzwerk übertragen, sie sind beschädigt, aber wir zeichnen sie nirgendwo auf - alle unsere Daten sind in Ordnung, Sie können sich keine Sorgen machen.

Die in ClickHouse gespeicherten Daten bleiben konsistent. Verwenden Sie Prüfsummen in ClickHouse. Wir lieben Schecksummen so sehr, dass wir sofort drei Optionen in Betracht ziehen:

  • Für komprimierte Datenblöcke beim Schreiben in eine Datei in das Netzwerk.
  • Die Gesamtprüfung ist die Summe der komprimierten Daten für die Überprüfung der Abstimmung.
  • Die Gesamtprüfung ist die Summe der nicht komprimierten Daten für die Überprüfung der Abstimmung.

Es gibt Fehler in Datenkomprimierungsalgorithmen. Dies ist ein bekannter Fall. Daher berücksichtigen wir beim Replizieren der Daten auch die Gesamtprüfsumme der komprimierten Daten und die Gesamtmenge der nicht komprimierten Daten.
Haben Sie keine Angst, die Scheckbeträge zu zählen, sie werden nicht langsamer.
Natürlich kommt es darauf an, welche und wie man zählt. Es gibt Nuancen, aber achten Sie darauf, den Scheckbetrag zu berücksichtigen. Wenn Sie beispielsweise anhand der komprimierten Daten zählen, sind weniger Daten vorhanden, die jedoch nicht langsamer werden.

Verbesserte Fehlermeldung


Wie kann man dem Benutzer erklären, wenn er eine solche Fehlermeldung erhält, dass dies ein Hardwareproblem ist?



Wenn die Prüfsumme nicht übereinstimmt, versuche ich vor dem Senden einer Ausnahme, jedes Bit zu ändern - nur für den Fall. Wenn die Prüfsumme beim Ändern konvergiert und ein Bit geändert wird, liegt das Problem höchstwahrscheinlich an der Hardware.

Wenn wir diesen Fehler erkennen können und er sich ändert, wenn ein Bit geändert wird, warum nicht beheben? Wir können dies tun, aber wenn wir ständig Fehler beheben, weiß der Benutzer nicht, dass das Gerät ein Problem aufweist.

Als wir herausfanden, dass es Probleme mit den Schaltern gab, berichteten Leute aus anderen Abteilungen: „Und wir haben ein bisschen falsch an Mongo geschrieben! Und in PostgreSQL hat uns etwas erreicht! “ Das ist gut, aber es ist besser, Probleme früher zu melden.

Als wir eine neue Diagnoseversion veröffentlichten, schrieb der erste Benutzer, für den sie funktionierte, eine Woche später: "Hier ist die Nachricht - was ist das Problem?" Leider hat er es nicht gelesen. Aber ich habe mit einer Wahrscheinlichkeit von 99% gelesen und vorgeschlagen, dass das Problem bei der Hardware liegt, wenn der Fehler auf einem Server auftritt. Ich lasse den verbleibenden Prozentsatz für den Fall, dass ich den Code falsch geschrieben habe - dies passiert. Infolgedessen ersetzte der Benutzer die SSD und das Problem verschwand.

"Delirium" in den Daten


Dieses interessante und unerwartete Problem machte mir Sorgen. Wir haben Yandex.Metrica-Daten. Ein einfacher JSON wird in einer der Spalten in die Datenbank geschrieben - Benutzerparameter aus dem JavaScript-Code des Zählers.

Ich mache eine Anfrage und der ClickHouse-Server ist mit Segfault abgestürzt. Anhand der Stapelverfolgung wurde mir klar, was das Problem war - ein neues Commit unserer externen Mitarbeiter aus einem anderen Land. Das Commit behoben, Segfault verschwand.

Ich SELECTführe die gleiche Anfrage aus: In ClickHouse, um JSON zu bekommen, aber wieder, Unsinn, funktioniert alles langsam. Ich bekomme JSON und es ist 10 MB. Ich zeige es an und schaue aufmerksamer: {"jserrs": cannot find property of object undefind...und dann fiel ein Megabyte Binärcode heraus.



Es gab Gedanken, dass dies wieder eine Passage aus dem Gedächtnis oder eine Rassenbedingung ist. Viele solcher Binärdaten sind schlecht, sie können alles enthalten. Wenn ja, finde ich jetzt dort Passwörter und private Schlüssel. Da ich aber nichts gefunden habe, habe ich die Hypothese sofort zurückgewiesen. Vielleicht ist dies ein Fehler in meinem Programm auf dem ClickHouse-Server? Vielleicht in einem Programm, das schreibt (es ist auch in C ++ geschrieben) - plötzlich legt sie versehentlich ihren Speicherauszug in ClickHouse ab? In dieser Hölle begann ich mir die Briefe genau anzusehen und stellte fest, dass es nicht so einfach war.

Hinweis Pfad


Der gleiche Müll wurde in zwei Clustern unabhängig voneinander aufgezeichnet. Die Daten sind Junk, aber es ist gültig UTF-8. Dieses UTF-8 hat einige seltsame URLs, Schriftnamen und viele Buchstaben "I" in einer Reihe.

Was ist das Besondere an dem kleinen kyrillischen "Ich"? Nein, das ist nicht Yandex. Tatsache ist, dass es sich bei der Codierung von Windows 1251 um das 255. Zeichen handelt. Und auf unseren Linux-Servern verwendet niemand die Windows 1251-Codierung.

Es stellt sich heraus, dass dies ein Speicherauszug des Browsers ist: Der JavaScript-Code des Metrikzählers sammelt JavaScript-Fehler. Wie sich herausstellte, ist die Antwort einfach - alles kam vom Benutzer .

Auch hier können Schlussfolgerungen gezogen werden.

Bugs aus dem ganzen Internet


Yandex.Metrica sammelt Datenverkehr von 1 Milliarde Geräten im Internet: Browser auf PCs, Handys, Tablets. Müll wird unvermeidlich kommen : Es gibt Fehler in Benutzergeräten, überall unzuverlässigen RAM und schreckliche Hardware, die überhitzt.

Die Datenbank speichert mehr als 30 Billionen Zeilen (Seitenaufrufe). Wenn Sie die Daten aus dieser Tabelle analysieren, finden Sie dort alles.

Daher ist es richtig, diesen Müll einfach zu filtern, bevor Sie in die Datenbank schreiben. Sie müssen keinen Müll in die Datenbank schreiben - sie mag es nicht.

HighLoad++ ( 133 ), - , , ++ PHP Russia 2020 Online.

Badoo, PHP Russia 2020 Online . PHP Russia 2020 Online 13 , .

, .

All Articles