Les bugs notoires et comment les éviter sur l'exemple de ClickHouse

Si vous écrivez du code, préparez-vous aux problèmes. Ils le seront certainement, et ils devraient être attendus de tous les côtés: de votre code et de votre compilateur, du système d'exploitation et du matériel, et les utilisateurs lèvent parfois des «surprises». Si vous avez fait évoluer le cluster aux échelles cosmiques, attendez-vous à des bogues "d'espace". Surtout quand il s'agit de données provenant du trafic Internet.


Alexey Milovidov (o6CuFl2Q) parlera des problèmes les plus ridicules, décourageants et désespérés de son expérience dans le développement et le support de ClickHouse. Voyons comment ils ont dû être débogués et quelles mesures les développeurs devraient prendre dès le début, afin qu'il y ait moins de problèmes.

Bogues notoires


Si vous avez écrit du code, préparez-vous immédiatement aux problèmes.

Erreurs dans le code. Ils seront nécessaires. Mais disons que vous avez écrit le code parfait, compilé, mais des bogues apparaîtront dans le compilateur et le code ne fonctionnera pas correctement. Nous avons corrigé le compilateur, tout compilé - exécutez-le. Mais (de manière inattendue) tout fonctionne de manière incorrecte, car il y a aussi des bogues dans le noyau du système d'exploitation .

S'il n'y a pas de bogues dans le système d'exploitation, ils seront inévitablement liés au matériel . Même si vous avez écrit le code parfait qui fonctionne parfaitement sur le matériel parfait, vous rencontrerez toujours des problèmes, par exemple des erreurs de configuration . Il semblerait que vous ayez tout fait correctement, mais quelqu'un a fait une erreur dans le fichier de configuration, et tout ne fonctionne plus.

Lorsque tous les bugs ont été corrigés, les utilisateurs le termineront, car ils utilisent constamment votre code «incorrectement». Mais le problème n'est certainement pas dans les utilisateurs, mais dans le code: vous avez écrit quelque chose de difficile à utiliser .

Regardons ces bugs avec quelques exemples.

Bogues de configuration


Suppression de données . Le premier cas de la pratique. Heureusement, pas le mien et pas Yandex, ne vous inquiétez pas.

Introduction d'abord. L'architecture de réduction de carte d'un cluster (tel que Hadoop) se compose de plusieurs serveurs de données (nœuds de données) qui stockent les données et d'un ou plusieurs serveurs maîtres qui connaissent l'emplacement de toutes les données sur les serveurs.

Les nœuds de données connaissent l'adresse du maître et s'y connectent. L'assistant surveille où et quelles données doivent être localisées et donne différentes commandes aux nœuds de données: «Téléchargez les données X, vous devez avoir les données Y et supprimer les données Z». Qu'est-ce qui pourrait mal se passer?

Lorsqu'un nouveau fichier de configuration a été téléchargé sur tous les nœuds de données, ils se sont connectés par erreur au maître à partir d'un autre cluster, et non au leur. Le maître a examiné les données dont les nœuds de données ont été informés, a décidé que les données étaient incorrectes et devaient être supprimées. Le problème a été constaté lorsque la moitié des données ont été effacées.


Les bugs les plus épiques sont ceux qui conduisent à une suppression de données par inadvertance.
Éviter cela est très simple.

Ne supprimez pas les données . Par exemple, mettez-le de côté dans un répertoire séparé ou supprimez-le avec un retard. Tout d'abord, nous transférons afin qu'ils ne soient pas visibles pour l'utilisateur, et s'il constate que quelque chose a disparu en quelques jours, nous le retournerons.

Ne supprimez pas les données inattendues si la cause est inconnue . Limitez par programme le début de la suppression des données inconnues: inattendu, avec des noms étranges, ou s'il y en a trop. L'administrateur remarquera que le serveur ne démarre pas et écrit un message et comprendra.

Si le programme effectue des actions destructrices - isoler les tests et la production au niveau du réseau(iptables). Par exemple, la suppression de fichiers ou l'envoi d'e-mails est une action destructrice car elle «dévorera» l'attention de quelqu'un. Mettez-y un seuil: une centaine de lettres peuvent être envoyées, et pour mille une case à cocher de sécurité, qui est réglée avant que quelque chose de terrible ne se produise.

Configurations . Le deuxième exemple vient déjà de ma pratique.

Une bonne entreprise avait en quelque sorte un étrange cluster ClickHouse. L'étrangeté était que les répliques n'étaient pas synchronisées. Lorsque le serveur a été redémarré, il n'a pas démarré et un message est apparu que toutes les données étaient incorrectes: «Il y a beaucoup de données inattendues, je ne démarre pas. Nous devons mettre le drapeau force_restore_dataet le comprendre. "

Personne ne pouvait le comprendre dans l'entreprise - ils ont juste mis le drapeau. Dans le même temps, la moitié des données ont disparu quelque part, entraînant des graphiques avec des lacunes. Les développeurs se sont tournés vers moi, j'ai pensé qu'il se passait quelque chose d'intéressant et j'ai décidé d'enquêter. Lorsque le matin est venu quelques heures plus tard et que les oiseaux ont commencé à chanter par la fenêtre, j'ai réalisé que je ne comprenais rien.

Le serveur ClickHouse utilise le service ZooKeeper pour la coordination. ClickHouse stocke les données et ZooKeeper détermine sur quels serveurs les données doivent se trouver: stocke les métadonnées sur les données sur quelle réplique doit se trouver. ZooKeeper est également un cluster - il se réplique selon un très bon algorithme de consensus distribué, avec une cohérence stricte.

En règle générale, ZooKeeper est composé de 3 machines, parfois 5. Dans la configuration ClickHouse, toutes les machines sont indiquées en même temps, la connexion est établie avec une machine aléatoire, interagit avec elle et ce serveur réplique toutes les demandes.

Qu'est-il arrivé? L'entreprise disposait de trois serveurs ZooKeeper. Mais ils ne fonctionnaient pas comme un cluster de trois nœuds , mais comme trois nœuds indépendants - trois clusters à partir d'un nœud. One ClickHouse se connecte à un serveur et écrit des données. Les répliques souhaitent télécharger ces données, mais elles sont introuvables. Au redémarrage, le serveur se connecte à un autre ZooKeeper: il voit que les données avec lesquelles il fonctionnait auparavant sont superflues, il faut les reporter quelque part. Il ne les supprime pas, mais les transfère dans un répertoire séparé - dans ClickHouse, les données ne sont pas si facilement supprimées.

Je décide de corriger la configuration de ZooKeeper. Je renomme toutes les données et fais une demande pour des ATTACHparties des données du répertoire detached/unexpeted_*.

En conséquence, toutes les données ont été restaurées, les répliques ont été synchronisées, il n'y a eu aucune perte, les graphiques étaient continus. L'entreprise est satisfaite, reconnaissante, comme si elle avait déjà oublié comment tout avait mal fonctionné auparavant.

Ce sont de simples bogues de configuration. Plus de bugs seront dans le code.

Bugs dans le code


Nous écrivons du code en C ++. Cela signifie que nous avons déjà des problèmes.
L'exemple suivant est un vrai bug de production sur le cluster Yandex.Metrica (2015) - une conséquence du code C ++. Le bug était que parfois l'utilisateur au lieu de répondre à la demande recevait un message d'erreur:

  • «Somme de contrôle ne correspond pas, données corrompues» - la somme de contrôle ne correspond pas, les données sont cassées - effrayant!
  • "LRUCache est devenu incohérent. Il doit y avoir un bogue »- le cache est devenu incohérent, probablement un bogue.

Le code que nous avons écrit s'informe qu'il y a un bogue là-bas.

"La somme de contrôle ne correspond pas, les données sont corrompues ." Les sommes de contrôle des blocs de données compressés sont vérifiées avant d'être décompressées. Habituellement, cette erreur apparaît lorsque les données sont cassées sur le système de fichiers. Pour diverses raisons, certains fichiers s'avèrent être des ordures lors du redémarrage du serveur.

Mais voici un autre cas: j'ai lu le fichier manuellement, la somme de contrôle correspond, il n'y a pas d'erreur. Une fois apparue, l'erreur est reproduite de manière stable sur demande répétée. Lorsque le serveur redémarre, l'erreur disparaît pendant un certain temps, puis réapparaît de manière stable.

Peut-être que le problème est en RAM? Une situation typique est lorsque des bits y battent. Je regarde dans dmesg(kern.log), mais il n'y a aucune exception de vérification de la machine - ils écrivent généralement quand quelque chose ne va pas avec la RAM. Si le serveur avait battu la RAM, non seulement mon programme ne fonctionnerait pas correctement, mais tous les autres généreraient des erreurs de manière aléatoire. Cependant, il n'y a aucune autre manifestation de l'erreur.

"LRUCache est devenu incohérent. Il doit y avoir un bug. " C'est une erreur claire dans le code, et nous écrivons en C ++ - peut-être un accès mémoire? Mais les tests sous AddressSanitizer, ThreadSanitizer, MemorySanitizer, UndefinedBehaviorSanitizer dans CI ne montrent rien.

Peut-être que certains cas de test ne sont pas couverts? Je récupère le serveur avec AddressSanitizer, je l'exécute en production - il n'attrape rien. Pendant un certain temps, l'erreur est supprimée en réinitialisant un cache de marque (cache de sachet).

L'une des règles de programmation dit: si ce n'est pas clair ce qu'est le bogue, regardez attentivement le code, en espérant y trouver quelque chose. Je l'ai fait, j'ai trouvé un bogue, l'ai corrigé - cela n'a pas aidé. Je regarde un autre endroit dans le code - il y a aussi un bug. Corrigé, encore une fois n'a pas aidé. J'en ai corrigé quelques autres, le code s'est amélioré, mais l'erreur n'a toujours pas disparu!

Cause. Essayer de trouver un modèle par serveur, par heure, par la nature de la charge - rien n'y fait. Puis il s'est rendu compte que le problème ne se manifeste que sur l'un des clusters, et jamais sur les autres. L'erreur n'est pas reproduite si souvent, mais elle apparaît toujours sur un cluster après un redémarrage, et tout est propre sur l'autre.

Il s'est avéré que la raison en est que sur le cluster «problème», ils ont utilisé une nouvelle fonctionnalité - les dictionnaires de cache. Ils utilisent l' allocateur de mémoire manuscrit ArenaWithFreeLists . Nous écrivons non seulement en C ++, mais nous avons également vu une sorte d'allocateurs personnalisés - nous nous condamnons deux fois aux problèmes.

ArenaWithFreeLists est une partie de la mémoire dans laquelle la mémoire est allouée consécutivement dans des tailles divisibles par deux: 16, 32, 64 octets. Si la mémoire est libérée, ils forment une liste liée de blocs FreeLists libres.

Regardons le code.

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] ->...
    }
}

Il utilise une fonction _bit_scan_reverseavec un trait de soulignement au début.
Il existe une règle non écrite: "Si une fonction a un trait de soulignement au début, lisez la documentation une fois, et si deux, lisez-la deux fois."
Nous écoutons et lisons la documentation: «int _bit_scan_reverse (int a). Définissez dst sur l'index du bit le plus élevé dans un entier 32 bits a. Si aucun bit n'est défini dans a, alors dst n'est pas défini . " Nous semblons avoir trouvé un problème.

En C ++, cette situation est considérée comme impossible pour le compilateur. Le compilateur peut utiliser un comportement indéfini, cette «impossibilité», comme hypothèse pour optimiser le code.

Le compilateur ne fait rien de mal - il génère honnêtement des instructions d'assemblage bsr %edi, %eax. Mais, si l'opérande est nul, l'instruction a bsrun comportement indéfini non pas au niveau C ++, mais au niveau CPU. Si le registre source est nul, le registre de destination ne change pas: il y avait des ordures à l'entrée, ces ordures resteront également à la sortie.

Le résultat dépend de l'endroit où le compilateur place cette instruction. Parfois, une fonction avec cette instruction est en ligne, parfois non. Dans le deuxième cas, il y aura quelque chose comme ce code:

bsrl %edi, %eax
retq

Ensuite, j'ai regardé un exemple de code similaire dans mon utilisation binaire objdump.



D'après les résultats, je constate que parfois le registre source et le registre destination sont les mêmes. S'il y avait zéro, le résultat sera également nul - tout va bien. Mais parfois, les registres sont différents et le résultat sera des ordures.

Comment ce bug se manifeste-t-il?

  • Nous utilisons les ordures comme index dans le tableau FreeLists. Au lieu d'un tableau, nous allons à une adresse distante et obtenons un accès à la mémoire.
  • Nous avons de la chance, presque toutes les adresses à proximité sont remplies de données du cache - nous gâchons le cache. Le cache contient des décalages de fichiers.
  • Nous lisons les fichiers avec le mauvais décalage. Du mauvais décalage, nous obtenons la somme de contrôle. Mais il n'y a pas de somme de contrôle, mais autre chose - cette somme de contrôle ne coïncidera pas avec les données suivantes.
  • Nous obtenons l'erreur «La somme de contrôle ne correspond pas, les données sont corrompues».

Heureusement, pas les données sont corrompues, mais seulement le cache en RAM. Nous avons été immédiatement informés de l'erreur, car nous avons vérifié les données. L'erreur a été corrigée le 27 décembre 2015 et est allée célébrer.

Comme vous pouvez le voir, le mauvais code peut au moins être corrigé. Mais comment corriger les bugs dans le matériel?

Bugs en fer


Ce ne sont même pas des bugs, mais des lois physiques - des effets inévitables. Selon les lois physiques, le fer est inévitablement buggy.

Écriture non atomique sur RAID . Par exemple, nous avons créé RAID1. Il se compose de deux disques durs. Cela signifie qu'un serveur est un système distribué: les données sont écrites sur un disque dur et sur un autre. Mais que se passe-t-il si les données sont écrites sur un disque et que l'alimentation est perdue lors de l'enregistrement sur le second? Les données sur une matrice RAID1 ne seront pas cohérentes. Nous ne pourrons pas comprendre quelles données sont correctes, car nous lirons un octet ou l'autre.

Vous pouvez y faire face en plaçant le journal. Par exemple, dans ZFS, ce problème est résolu, mais plus à ce sujet plus tard.

pourriture des bits sur disque dur et SSD. Les bits sur les disques durs et sur les SSD peuvent mal tourner comme ça. Les SSD modernes, en particulier ceux avec des cellules à plusieurs niveaux, sont conçus pour garantir que les cellules se détérioreront constamment. Les codes de correction d'erreur aident, mais parfois les cellules se détériorent tellement et tellement que même cela ne sauve pas. Des erreurs non détectées sont obtenues.

bit bascule dans la RAM (mais qu'en est-il ECC?). Dans la RAM des serveurs, les bits sont également corrompus. Il a également des codes de correction d'erreur. Lorsque des erreurs se produisent, elles sont généralement visibles à partir des messages du journal du noyau Linux dans dmesg. Lorsqu'il y a beaucoup d'erreurs, nous verrons quelque chose comme: "N millions d'erreurs de mémoire ont été corrigées." Mais les bits individuels ne seront pas remarqués et, à coup sûr, quelque chose sera bogué.

bit bascule au niveau du processeur et du réseau . Il y a des erreurs au niveau du processeur, dans les caches de processeur et, bien sûr, lors de la transmission de données sur un réseau.

Comment se manifestent généralement les erreurs de fer? Le ticket « Un znode mal formé empêche ClickHouse de démarrer » arrive à GitHub - les données du nœud ZooKeeper sont corrompues.

Dans ZooKeeper, nous écrivons généralement certaines métadonnées en texte brut. Il y a quelque chose qui ne va pas avec lui - " réplique " est écrit très étrange.



Il arrive rarement qu'en raison d'un bogue dans le code, un bit change. Bien sûr, nous pouvons écrire un tel code: nous prenons le filtre Bloom, changeons le bit à certaines adresses, calculons les adresses incorrectement, changeons le mauvais bit, il tombe sur certaines données. Ca y est, maintenant ClickHouse ce n'est pas « réplique » , mais « Repli b a » et tout les données est faux. Mais généralement, un changement d'un bit est un symptôme de problèmes de fer.

Vous connaissez peut-être l'exemple du bitquatting. Artyom Dinaburg a fait une expérience : il y a des domaines sur Internet qui ont beaucoup de trafic, bien que les utilisateurs ne s'y rendent pas seuls. Par exemple, un tel domaine FB-CDN.com est un CDN Facebook.

Artyom a enregistré un domaine similaire (et bien d'autres), mais a changé d'un bit. Par exemple, FA-CDN.com au lieu de FB-CDN.com. Le domaine n'a été publié nulle part, mais le trafic y est arrivé. Parfois, l'hôte FB-CDN a été écrit dans les en-têtes HTTP et la demande est allée à un autre hôte en raison d'erreurs de RAM sur les appareils des utilisateurs. La RAM avec correction d'erreur n'aide pas toujours. Parfois, il interfère même et conduit à des vulnérabilités (lire sur Rowhammer, ECCploit, RAMBleed).
Conclusion: vérifiez toujours les données vous-même.
Lors de l'écriture dans le système de fichiers, vérifiez la somme sans échec. Lors de la transmission sur le réseau, vérifiez également le résumé - ne vous attendez pas à ce qu'il y ait des sommes de contrôle.

Plus de bugs! ..


Mesures du cluster de production . Les utilisateurs en réponse à une demande obtiennent parfois une exception: «La somme de contrôle ne correspond pas: données corrompues» - la somme de contrôle n'est pas correcte, les données sont corrompues.



Le message d'erreur affiche des données détaillées: quel montant de chèque était attendu, quel montant de chèque se trouve réellement dans ces données, la taille du bloc pour lequel nous vérifions le montant du chèque et le contexte d'exception.

Lorsque nous avons reçu le paquet sur le réseau d'un serveur, une exception est apparue - cela semble familier. Peut-être encore en passant par la mémoire, la condition raciale ou autre chose.

Cette exception est apparue en 2015. Le bug a été corrigé, il n'est plus apparu. En février 2019, il est soudainement réapparu. A cette époque, j'étais à l'une des conférences, mes collègues ont traité le problème. L'erreur s'est reproduite plusieurs fois par jour sur 1000 serveurs avec ClickHouse: il n'est pas possible de collecter des statistiques sur un serveur, puis sur un autre. En même temps, il n'y avait aucune nouvelle version pour le moment. Cela n'a pas fonctionné et résolu le problème, mais après quelques jours, l'erreur elle-même a disparu.

Ils ont oublié l'erreur, et le 15 mai 2019, cela s'est répété. Nous avons continué à traiter avec elle. La première chose que j'ai faite a été de regarder tous les journaux et graphiques disponibles. Il les a étudiés toute la journée, n'a rien compris, n'a trouvé aucun schéma. Si le problème ne peut pas être reproduit, la seule option est de collecter tous les cas, recherchez les modèleset les dépendances. Peut-être que le noyau Linux ne fonctionne pas correctement avec le processeur, enregistre ou charge de manière incorrecte les registres.

Hypothèses et schémas


7 serveurs sur 9 avec E5-2683 v4 ont échoué. Mais de l'erreur sujette, seulement environ la moitié du E5-2683 v4 est une hypothèse vide.

Les erreurs ne sont généralement pas répétées . En plus du cluster mtauxyz, où il y a en effet des données corrompues (mauvaises données sur le disque). Ceci est un autre cas, nous rejetons l'hypothèse.

L'erreur ne dépend pas du noyau Linux - vérifiée sur différents serveurs, n'a rien trouvé. Rien d'intéressant dans kern.log, machine check exceptionpas de messages . Dans les graphiques réseau, y compris les retransmetteurs, le processeur, les E / S, le réseau, rien d'intéressant. Tous les adaptateurs réseau sur les serveurs sur lesquels des erreurs se produisent et n'apparaissent pas sont identiques.

Il n'y a pas de modèles . Que faire? Continuez à chercher des motifs. Deuxième essai.

Je regarde les serveurs de disponibilité:la disponibilité est élevée, les serveurs fonctionnent de manière stable , les erreurs de segmentation et quelque chose comme ça ne l'est pas. Je me réjouis toujours quand je vois que le programme s'est écrasé avec segfault - au moins il s'est écrasé. Pire, quand il y a une erreur, ça gâche quelque chose, mais personne ne le remarque.

Les erreurs sont regroupées par jour et se produisent en quelques jours. Dans environ 2 jours, plus apparaissent, dans certains moins, puis encore plus - il n'est pas possible de déterminer avec précision le moment de l'apparition des erreurs.

Certaines erreurs correspondent aux packages et au montant du chèque que nous attendions. La plupart des erreurs n'ont que deux options de package. J'ai eu de la chance car dans le message d'erreur, nous avons ajouté la valeur même de la somme de contrôle, ce qui a aidé à compiler des statistiques.

Aucun modèle de serveurd'où nous lisons les données. La taille du bloc compressé que nous vérifions est inférieure à un kilo-octet. Regardé les tailles d'emballage en HEX. Cela ne m'a pas été utile - la représentation binaire des tailles de paquets et des sommes de contrôle n'est pas perceptible.

Je n'ai pas corrigé l'erreur - je cherchais à nouveau des modèles. Troisième tentative.

Pour une raison quelconque, l'erreur n'apparaît que sur l'un des clusters - sur les troisièmes répliques du Vladimir DC (nous aimons appeler les centres de données par les noms de villes). En février 2019, une erreur est également apparue dans Vladimirs DC, mais sur une version différente de ClickHouse. Ceci est un autre argument contre l'hypothèse que nous avons écrit le mauvais code. Nous l'avons déjà réécrit trois fois de février à mai - l' erreur n'est probablement pas dans le code .

Toutes les erreurs lors de la lecture des paquets sur le réseau -while receiving packet from. Le package sur lequel l'erreur s'est produite dépend de la structure de la demande. Pour les demandes de structure différente, une erreur sur des sommes de contrôle différentes. Mais dans les demandes où l'erreur est sur la même somme de contrôle, les constantes diffèrent.

Toutes les demandes avec une erreur, sauf une, le sont GLOBAL JOIN. Mais à titre de comparaison, il y a une demande inhabituellement simple, et la taille du bloc compressé n'est que de 75 octets.

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

Nous rejetons l'hypothèse d'influence GLOBAL JOIN.

Le plus intéressant est que les serveurs affectés sont regroupés dans des plages par leurs noms :
mtxxxlog01-{39..44 57..58 64 68..71 73..74 76}-3.

J'étais fatiguée et désespérée, j'ai commencé à chercher des schémas complètement délirants. C'est bien que je n'ai pas pu déboguer le code en utilisant la numérologie. Mais il y avait encore des pistes.

  • Les groupes de serveurs problématiques sont les mêmes qu'en février.
  • Les serveurs problématiques sont situés dans certaines parties du centre de données. À DC Vladimir, il y a ce qu'on appelle des lignes - ses différentes parties: VLA-02, VLA-03, VLA-04. Les erreurs sont clairement regroupées: dans certaines files d'attente, c'est bon (VLA-02), dans d'autres problèmes (VLA-03, VLA-04).

Taper le débogage


Il ne restait plus qu'à déboguer en utilisant la méthode "lance". Cela signifie former l'hypothèse «Que se passe-t-il si vous essayez de le faire?» et collecter des données. Par exemple, j'ai trouvé une query_logrequête simple avec une erreur dans la table pour laquelle la taille du paquet est size of compressed blocktrès petite (= 107).



J'ai pris la demande, l'ai copiée et exécutée manuellement en utilisant clickhouse-local.

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

Avec l'aide de strace, j'ai reçu un instantané (vidage) de blocs sur le réseau - exactement les mêmes paquets qui sont reçus lorsque cette demande est exécutée, et je peux les étudier. Vous pouvez utiliser tcpdump pour cela, mais ce n'est pas pratique: il est difficile d'isoler une demande spécifique du trafic de production.

À l'aide de strace, vous pouvez tracer le serveur ClickHouse lui-même. Mais ce serveur fonctionne en production, si je fais cela, j'obtiendrai un tableau d'informations incompréhensibles. Par conséquent, j'ai lancé un programme distinct qui exécute exactement une demande. Déjà pour ce programme, je lance strace et j'obtiens ce qui a été transmis sur le réseau.

La demande est exécutée sans erreur - l'erreur n'est pas reproduite . S'il était reproduit, le problème serait résolu. Par conséquent, j'ai copié les paquets dans un fichier texte et j'ai commencé manuellement à analyser le protocole.



Le montant du chèque était le même que prévu. C'est exactement le paquet sur lequel parfois, à un autre moment, dans d'autres demandes, des erreurs se sont produites. Mais jusqu'à présent, il n'y a pas eu d'erreurs.

J'ai écrit un programme simple qui prend un paquet et vérifie le montant du chèque lors du remplacement d'un bit dans chaque octet. Le programme a effectué un retournement de bit à chaque position possible et a lu le montant du chèque.



J'ai démarré le programme et j'ai constaté que si vous modifiez la valeur d'un bit, vous obtenez exactement cette somme de contrôle cassée, pour laquelle il y a une plainte

Problème matériel


S'il y a une erreur dans le logiciel (par exemple, en passant par la mémoire), le basculement sur un seul bit est peu probable. Par conséquent, une nouvelle hypothèse est apparue - le problème est dans la glande.

On pourrait fermer le couvercle de l'ordinateur portable et dire: "Le problème n'est pas de notre côté, mais dans le matériel, nous ne faisons pas cela." Mais non, essayons de comprendre où est le problème: dans la RAM, sur le disque dur, dans le processeur, dans la carte réseau ou dans la RAM de la carte réseau dans l'équipement réseau.

Comment localiser un problème matériel?

  • Le problème est apparu et a disparu à certaines dates.
  • Serveurs concernés sont regroupés par leur nom: mtxxxlog01-{39..44 57..58 64 68..71 73..74 76}-3.
  • Les groupes de serveurs problématiques sont les mêmes qu'en février.
  • Les serveurs problématiques se trouvent uniquement dans certaines files d'attente du centre de données.

Des questions ont été posées aux ingénieurs réseau - les données battent sur les commutateurs réseau. Il s'avère que les ingénieurs réseau ont échangé des commutateurs pour d'autres exactement à ces dates. Après une question, ils les ont remplacés par les précédents et le problème a disparu.

Le problème est résolu, mais des questions demeurent (plus pour les ingénieurs).

Pourquoi l'ECC (mémoire de correction d'erreur) n'aide-t-il pas les commutateurs réseau? Parce que le basculement de plusieurs bits peut se compenser - vous obtenez une erreur non détectée.

Pourquoi les sommes de contrôle TCP ne sont-elles pas utiles? Ils sont faibles. Si un seul bit a changé dans les données, les sommes de contrôle TCP verront toujours le changement. Si deux bits ont changé, alors les changements peuvent ne pas être détectés - ils s'annulent mutuellement.

Un seul bit a changé dans notre package, mais l'erreur n'est pas visible. C'est parce que 2 bits ont changé dans le segment TCP: ils en ont calculé la somme de contrôle, cela a coïncidé. Mais dans un segment TCP, plus d'un paquet de notre application est localisé. Et pour l'un d'entre eux, nous considérons déjà notre somme de contrôle. Un seul bit a changé dans ce paquet.

Pourquoi les sommes de contrôle Ethernet ne sont pas utiles - sont-elles plus fortes que TCP? Montant de contrôle Ethernetvérifiez-résumez les données afin qu'elles ne se cassent pas pendant la transmission à travers un segment (je peux me tromper avec la terminologie, je ne suis pas ingénieur réseau). L'équipement réseau transfère ces paquets et peut transmettre certaines données pendant le transfert. Par conséquent, les montants des chèques sont simplement recomptés. Nous avons vérifié - sur le fil, les paquets n'ont pas changé. Mais s'ils battent sur le commutateur réseau lui-même, il recalculera le montant du chèque (il sera différent) et transmettra le paquet plus loin.
Rien ne vous sauvera - vérifiez vous-même. Ne vous attendez pas à ce que quelqu'un fasse cela pour vous.
Pour les blocs de données, une somme de contrôle de 128 bits est envisagée (cette surpuissance au cas où). Nous informons correctement l'utilisateur de l'erreur. Les données sont transmises sur le réseau, elles sont endommagées, mais nous ne les enregistrons nulle part - toutes nos données sont en ordre, vous ne pouvez pas vous inquiéter.

Les données stockées dans ClickHouse restent cohérentes. Utilisez des sommes de contrôle dans ClickHouse. Nous aimons tellement les sommes de contrôle que nous considérons immédiatement trois options:

  • Pour les blocs de données compressés lors de l'écriture dans un fichier, sur le réseau.
  • La vérification totale est la somme des données compressées pour la vérification du rapprochement.
  • Le contrôle total est la somme des données non compressées pour la vérification du rapprochement.

Il existe des bogues dans les algorithmes de compression de données, c'est un cas connu. Par conséquent, lorsque les données sont répliquées, nous considérons également la somme de contrôle totale des données compressées et la quantité totale de données non compressées.
N'ayez pas peur de compter les montants des chèques, ils ne ralentissent pas.
Bien sûr, cela dépend de ceux et de la façon de compter. Il y a des nuances, mais assurez-vous de considérer le montant du chèque. Par exemple, si vous comptez à partir des données compressées, alors il y aura moins de données, elles ne ralentiront pas.

Message d'erreur amélioré


Comment expliquer à l'utilisateur lorsqu'il reçoit un tel message d'erreur qu'il s'agit d'un problème matériel?



Si la somme de contrôle ne correspond pas, avant d'envoyer une exception, j'essaie de changer chaque bit - juste au cas où. Si la somme de contrôle converge lors du changement et qu'un bit est modifié, le problème est probablement le matériel.

Si nous pouvons détecter cette erreur, et si elle change lorsqu'un bit est changé, pourquoi ne pas le corriger? Nous pouvons le faire, mais si nous corrigeons les erreurs tout le temps, l'utilisateur ne saura pas que l'équipement est en problème.

Lorsque nous avons découvert qu'il y avait des problèmes dans les commutateurs, des personnes d'autres départements ont commencé à signaler: «Et nous avons un bit incorrectement écrit à Mongo! Et quelque chose nous est arrivé dans PostgreSQL! » C'est bien, mais il vaut mieux signaler les problèmes plus tôt.

Lorsque nous avons publié une nouvelle version de diagnostic, le premier utilisateur à qui il a travaillé a écrit une semaine plus tard: "Voici le message - quel est le problème?" Malheureusement, il ne l'a pas lu. Mais j'ai lu et suggéré avec une probabilité de 99% que si l'erreur apparaît sur un serveur, le problème est lié au matériel. Je laisse le pourcentage restant au cas où j'aurais mal écrit le code - cela se produit. En conséquence, l'utilisateur a remplacé le SSD et le problème a disparu.

"Delirium" dans les données


Ce problème intéressant et inattendu m'a inquiété. Nous avons des données Yandex.Metrica. Un simple JSON est écrit dans la base de données dans l'une des colonnes - paramètres utilisateur à partir du code JavaScript du compteur.

Je fais une sorte de demande et le serveur ClickHouse s'est écrasé avec segfault. De la trace de la pile, j'ai réalisé quel était le problème - un nouvel engagement de nos contributeurs externes d'un autre pays. Le commit corrigé, le segfault a disparu.

Je lance la même demande: SELECTdans ClickHouse, pour obtenir JSON, mais encore une fois, un non-sens, tout fonctionne lentement. J'obtiens JSON, et c'est 10 Mo. Je l'affiche et regarde plus attentivement: {"jserrs": cannot find property of object undefind...puis un mégaoctet de code binaire est tombé.



On pensait que c'était encore un passage de mémoire ou une condition de race. Beaucoup de ces données binaires sont mauvaises, elles peuvent contenir n'importe quoi. Si oui, je vais maintenant y trouver des mots de passe et des clés privées. Mais je n'ai rien trouvé, j'ai donc immédiatement rejeté l'hypothèse. C'est peut-être un bug dans mon programme sur le serveur ClickHouse? Peut-être dans un programme qui écrit (il est également écrit en C ++) - tout d'un coup, elle place accidentellement sa mémoire de vidage dans ClickHouse? Dans cet enfer, j'ai commencé à regarder de près les lettres et j'ai réalisé que ce n'était pas si simple.

Chemin de l'indice


Les mêmes ordures ont été enregistrées sur deux grappes, indépendamment l'une de l'autre. Les données sont indésirables, mais elles sont valides en UTF-8. Cet UTF-8 a des URL étranges, des noms de police et beaucoup de lettres "I" d'affilée.

Quelle est la particularité du petit «je» cyrillique? Non, ce n'est pas Yandex. Le fait est que dans le codage de Windows 1251, c'est le 255ème caractère. Et sur nos serveurs Linux, personne n'utilise l'encodage Windows 1251.

Il s'avère qu'il s'agit d'un vidage du navigateur: le code JavaScript du compteur métrique recueille les erreurs JavaScript. Il s'est avéré que la réponse est simple: tout vient de l'utilisateur .

On peut également en tirer des conclusions.

Bogues de partout sur Internet


Yandex.Metrica collecte le trafic d'un milliard d'appareils sur Internet: navigateurs sur PC, téléphones portables, tablettes. Les ordures viendront inévitablement : il y a des bogues dans les appareils des utilisateurs, partout de la RAM peu fiable et du matériel terrible qui surchauffe.

La base de données stocke plus de 30 billions de lignes (pages vues). Si vous analysez les données de ce tableau, vous pouvez y trouver n'importe quoi.

Par conséquent, il est correct de simplement filtrer ces ordures avant d’écrire dans la base de données. Pas besoin d'écrire des ordures dans la base de données - elle n'aime pas ça.

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

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

, .

All Articles