Citymobil - un manuel pour améliorer la disponibilité au milieu de la croissance des entreprises pour les startups. Partie 4



Ceci est le prochain article de la série décrivant comment nous augmentons notre disponibilité de service dans Citymobil (vous pouvez lire les parties précédentes ici: partie 1 , partie 2 , partie 3 ). Dans d'autres parties, je parlerai des accidents et des pannes en détail.

1. Mauvaise version: surcharge de la base de données


Permettez-moi de commencer par un exemple spécifique de ce type de panne. Nous avons déployé une optimisation: ajout de USE INDEX dans une requête SQL; pendant les tests ainsi qu'en production, cela a accéléré les requêtes courtes, mais les longues - ont ralenti. Le ralentissement des longues requêtes n'a été constaté qu'en production. En conséquence, un grand nombre de longues requêtes parallèles ont provoqué l'arrêt de la base de données pendant une heure. Nous avons étudié en profondeur le fonctionnement de USE INDEX; nous l'avons décrit dans le fichier Do's and Dont's et avons mis en garde les ingénieurs contre une utilisation incorrecte. Nous avons également analysé la requête et réalisé qu'elle récupère principalement des données historiques et, par conséquent, peut être exécutée sur une réplique distincte pour les requêtes historiques. Même si cette réplique tombe en panne en raison d'une surcharge, l'entreprise continuera de fonctionner.

Nous avons continué à tomber sur les mêmes problèmes par la suite, et à un moment donné, nous avons décidé de nous pencher sur cette question. Nous avions étudié le code et déplacé toutes les requêtes que nous pouvions sans compromettre notre service vers les répliques. Les répliques elles-mêmes étaient divisées en fonction de leur niveau de criticité, de sorte qu'aucune d'entre elles ne pouvait échouer et arrêter le service. En conséquence, nous avons conçu une architecture avec les bases de données suivantes:

  • base de données principale - pour les opérations d'écriture et les requêtes qui sont super sensibles à la fraîcheur des données;
  • répliques de production - pour les requêtes courtes qui sont moins sensibles à la fraîcheur des données;
  • répliques des coefficients de flambée des prix. Ces répliques peuvent avoir 30 à 60 secondes de retard; ce n'est pas crucial car les coefficients ne changent pas si souvent et si cette réplique tombe en panne, le service n'arrêtera pas de fonctionner; les prix ne correspondront tout simplement pas à l'équilibre entre l'offre et la demande;
  • réplique pour les paramètres opérationnels et le centre d'appels. S'il tombe en panne, l'entreprise continuera de fonctionner mais sans assistance client et pilote, et nous ne pourrons pas modifier temporairement certains paramètres;
  • de nombreuses répliques pour des analyses et des tableaux de bord ad hoc;
  • Base de données MPP (traitement massivement parallèle) pour des analyses massives avec des tranches complètes sur les données historiques.

Cette architecture nous a fourni un vaste espace de croissance et a réduit un certain nombre de plantages dus à des requêtes SQL non optimales. Mais c'est encore loin d'être parfait. Nous prévoyons de mettre en œuvre le partage, afin de pouvoir adapter les mises à jour et les suppressions et être extrêmement sensibles aux requêtes de fraîcheur des données. La marge de sécurité de MySQL n'est pas infinie. Nous aurons bientôt besoin d'un peu d'artillerie lourde sous la forme d'une base de données en mémoire (par exemple Tarantool). J'en parlerai certainement dans mes prochains articles.

Pendant que nous traitions du code et des requêtes non optimaux, nous avons compris ce qui suit: toute non-optimalité doit être éliminée avant sa publication et non après. Cela diminue le risque de pannes et les efforts des équipes d'ingénierie pour l'optimisation. Si le code a déjà été déployé avec quelques nouvelles versions en plus, il est beaucoup plus difficile de l'optimiser. En conséquence, nous avons introduit une révision de code obligatoire pour l'optimisation. Elle est réalisée par nos ingénieurs les plus expérimentés, notre force d'élite.

Nous avons également commencé à collecter les meilleures méthodes d'optimisation de code adaptées à notre réalité dans les choses à faire et à ne pas faire. Ils sont listés ci-dessous. S'il vous plaît, ne prenez pas ces pratiques comme une vérité indéniable et n'essayez pas de les reproduire aveuglément. Chaque méthode n'a de sens que pour une situation spécifique et une entreprise spécifique. Ceci est juste un exemple pour clarifier les détails:

  • Si une requête SQL ne dépend pas de l'utilisateur (par exemple, la carte de demande du conducteur avec les tarifs maximaux et les coefficients de surtension - cette carte est la même pour tout utilisateur de l'application du pilote), alors cette requête doit être effectuée dans un script cron avec une fréquence spécifique (une fois par minute suffit dans ce cas). Le résultat doit être écrit dans un cache (Memcached ou Redis) qui doit être utilisé dans le code de production.
  • Si une requête SQL fonctionne avec les données dont le délai n'est pas crucial pour l'entreprise, son résultat doit être placé dans un cache avec du TTL (30 secondes, par exemple), puis lu dans le cache par le code de production.
  • Si dans une implémentation de méthode serveur spécifique (en PHP ou dans un autre langage côté serveur) vous décidez de faire une requête SQL, vous devez vous assurer que les données dont vous avez besoin ne sont pas arrivées avec une autre requête SQL ou sur le point d'arriver dans votre code ci-dessous.
  • La même chose que ci-dessus va aux requêtes vers un cache. Un cache est rapide mais est toujours une base de données. Il peut donc également être surchargé. Une erreur courante est que vous pensez qu'un cache est une sorte de variable normale en mémoire et que vous l'utilisez comme variable. Mais son accès implique un aller-retour réseau et génère une charge de travail pour Redis ou Memcached. Par conséquent, si les données sont déjà arrivées du cache, alors ne prenez pas simplement du cache ce qui a déjà été pris.
  • Si pendant un traitement de demande Web (à nouveau en PHP ou dans tout autre langage) vous devez appeler une fonction, vous devez vous assurer qu'il n'y aura pas de requêtes SQL supplémentaires ni d'accès au cache. Si un tel appel de fonction est inévitable, vous devez vous assurer qu'il ne peut pas être modifié, ou que sa logique n'a pas été rompue afin d'éviter les requêtes inutiles de base de données / cache.
  • S'il est nécessaire d'effectuer une requête SQL, vous devez être absolument sûr que les champs dont vous avez besoin ne peuvent pas être ajoutés aux requêtes déjà existantes dans votre code au-dessus ou en dessous.

2. Mauvais fonctionnement manuel


Exemples de ces accidents:

  • mauvais ALTER qui a surchargé la base de données ou provoqué un retard de réplique;
  • mauvais DROP (par exemple, nous avons rencontré un bogue dans MySQL qui bloquait la base de données lors de la suppression d'une table);
  • une requête lourde sur un maître, faite manuellement par erreur;
  • un serveur Web était en cours de configuration même s'il était sous la charge de travail réelle alors que nous pensions qu'il était hors service.

Pour minimiser les pannes dues à ces raisons, nous devons enquêter sur la nature d'un accident chaque fois qu'il se produit. Nous n'avons pas encore trouvé de règle générale. Encore une fois, regardons quelques exemples spécifiques. Les coefficients de surtension (les frais de taxi au moment et au lieu de forte demande sont multipliés par eux) ont cessé de fonctionner à un moment donné. La raison en était qu'un script python fonctionnait sur un serveur de réplica de base de données où les données pour le calcul des coefficients étaient extraites et que le script utilisait toute la mémoire et la réplique descendait. Le script était en cours d'exécution depuis un certain temps; il fonctionnait directement sur la réplique pour des raisons de commodité. Le problème a été résolu par le redémarrage du script. Les conclusions suivantes ont été tirées: ne pas exécuter de scripts étrangers sur un serveur de base de données (il a été écrit dans les choses à faire et à ne pas faire; sinon, ce serait un coup vide!), Surveiller l'utilisation de la mémoire sur un serveur de base de données et alerter par SMS si ce serveur est sur le point de manquer de mémoire.

Il est essentiel de toujours tirer des conclusions et de ne pas se sentir à l'aise dans le genre de situation "vu le problème, réglé, oublié". Un service de qualité ne peut être offert que si l'on s'en va avec une conclusion. En plus de cela, les alertes SMS sont essentielles - elles augmentent le niveau de qualité du service, elles ne le laissent pas baisser et nous permettent d'augmenter sa fiabilité. Comme un alpiniste qui atteint une position stable puis se redresse vers une autre position stable, mais seulement plus haut cette fois.

La surveillance et les alertes ne sont pas visibles, mais elles agissent comme des crochets de fer taillant dans la roche de l'inconnu nous empêchant de tomber en dessous de notre accord de niveau de service que nous augmentons continuellement.

3. Oeuf de Pâques


Ce que nous appelons "un œuf de Pâques" - est une mine à action retardée que nous n'avons pas encore déclenchée, même si elle existe depuis un certain temps. En dehors de cet article, ce terme est utilisé pour les fonctionnalités non documentées créées exprès. Dans notre cas, ce n'est pas du tout une fonctionnalité, mais plutôt un bug qui agit comme une bombe à retardement et apparaît comme un effet secondaire d'une activité bien intentionnée.

Par exemple:

  • débordement de l'auto- auto_increment 32 bits;
  • non optimalité du code / de la configuration, déclenchée par une charge de travail élevée;
  • réplique retardée par une requête non optimale provoquée par un nouveau modèle d'utilisation ou par une charge de travail plus lourde;
  • réplique retardée par une opération UPDATE non optimale sur le maître causée par un nouveau modèle de charge de travail et retardé la réplication.

Un autre type populaire d'oeuf de Pâques est le code non optimal; pour être plus spécifique - requête SQL non optimale. La table était auparavant plus petite et la charge de travail était plus légère - la requête fonctionnait bien. Avec la croissance linéaire du calendrier et l'augmentation de la charge de travail linéaire, la consommation de ressources par un système de gestion de base de données augmentait de façon quadratique. Habituellement, cela conduit à un effet négatif drastique: c'est comme si tout allait bien et puis tout à coup - oups!

Scénarios plus rares - combinaison d'insectes et d'oeufs de Pâques. Une version avec un bogue a conduit à l'agrandissement d'une table de base de données et a augmenté un certain nombre de lignes de table d'un type spécifique tandis qu'un œuf de Pâques déjà existant a provoqué la surcharge de la base de données en raison des requêtes plus lentes sur cette table développée.

Cependant, nous avions des œufs de Pâques non liés à la charge de travail. Par exemple, un champ auto_increment 32 bits dans MYSQL. Après un peu plus de 2 milliards de lignes, les insertions échouent. Par conséquent, dans le monde moderne, nous devons utiliser uniquement les champs auto_increment 64 bits. Nous avons bien appris cette leçon.

Comment gérer les œufs de Pâques? La réponse semble simple: a) rechercher les anciens œufs, b) ne pas laisser apparaître les nouveaux. Nous essayons de faire les deux. La recherche des anciens œufs va de pair avec notre optimisation continue du code. Nous avons nommé deux des ingénieurs les plus expérimentés pour effectuer l'optimisation presque à plein temps. Ils trouvent dans slow.log les requêtes qui utilisent le plus les ressources des bases de données; ils optimisent ces requêtes et le code qui les entoure. Nous réduisons la possibilité d'émergence de nouveaux œufs en testant chaque validation d'optimalité effectuée par les ingénieurs sensei mentionnés ci-dessus. Leur tâche est de signaler les erreurs affectant les performances, de suggérer la manière d'améliorer les choses et de transmettre ces connaissances à d'autres ingénieurs.

À un moment donné, juste après avoir trouvé un autre œuf de Pâques, nous avons réalisé que c'était une bonne chose de rechercher des requêtes lentes, mais nous aurions également dû rechercher les requêtes qui semblent lentes mais qui fonctionnent rapidement. Ce sont les prochains prétendants à tout planter en cas de nouvelle croissance explosive de la table. L'exemple stupide mais évident ici est une requête qui analyse complètement une table de 10 lignes sans utiliser d'index du tout. Cela fonctionnera rapidement pour le moment. Cependant, lorsque la table est suffisamment grande, la requête supprime la base de données. C'est l'œuf de Pâques.

4. Causes externes


Ce sont des causes que nous semblons incapables de maîtriser très bien. Autrement dit, ce sont les causes qui ne peuvent être atténuées mais pas éliminées. Par exemple:

  • Limiter nos demandes par un fournisseur de services cartographiques. Il peut être atténué via le contrôle de l'utilisation des services, le respect d'un niveau de charge de travail spécifique, la planification préalable de l'augmentation de la charge de travail et l'achat d'extension de service. Cependant, nous ne pouvons pas nous passer de cartes.
  • Panne de réseau dans un centre de données. Il peut être atténué en plaçant la copie du service dans un centre de données de sauvegarde. Cependant, nous ne pouvons pas nous passer d'un centre de données physique ou cloud.
  • Service de paiement en panne. Il peut être atténué par la sauvegarde des services de paiement. Cependant, nous ne pouvons pas nous passer de paiements.
  • Blocage errant du trafic par un service de protection DDoS. Il peut être atténué en désactivant le service de protection DDoS par défaut en ne l'activant qu'en cas d'attaque DDoS. Cependant, nous ne pouvons pas nous passer de la protection DDoS.

Étant donné que même l'atténuation d'une cause externe est une entreprise longue et coûteuse, nous avons commencé à collecter des statistiques sur les accidents causés par des raisons externes et à attendre l'accumulation de masse critique. Nous n'avons pas de recette pour définir une masse critique. C'est une simple intuition. Par exemple, si nous étions complètement en panne cinq fois en raison, disons, des problèmes du service de protection DDoS, à chaque arrêt ultérieur, le besoin d'une alternative deviendrait de plus en plus aigu.

D'un autre côté, si nous pouvons d'une manière ou d'une autre faire fonctionner tout avec un service externe indisponible, nous le ferons certainement. L'analyse post mortem de chaque panne nous aide ici. Il devrait toujours y avoir une conclusion. Ce qui signifie que cela vous plaise ou non - nous proposons toujours une solution de contournement.



Dans la dernière partie, je vais parler d'un autre type de pannes et des conclusions que nous en avons tirées, de la façon dont nous avons modifié le processus de développement, de l'automatisation que nous avons introduite. Restez à l'écoute!

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


All Articles