Fermer

mai 30, 2018

Construire un service central d'enregistrement en interne


Nous savons tous à quel point le débogage est important pour améliorer les performances et les fonctionnalités des applications. BrowserStack exécute un million de sessions par jour sur une pile d'application hautement distribuée! Chacune implique plusieurs parties mobiles, car la seule session d'un client peut couvrir plusieurs composants dans plusieurs régions géographiques.

Sans le cadre et les outils appropriés, le processus de débogage peut être un cauchemar. Dans notre cas, nous avions besoin d'un moyen de collecter les événements qui se déroulent à différentes étapes de chaque processus afin d'avoir une compréhension approfondie de tout ce qui se passe au cours d'une session. Avec notre infrastructure, résoudre ce problème est devenu compliqué car chaque composant peut avoir plusieurs événements de leur cycle de vie de traitement d'une demande.

C'est pourquoi nous avons développé notre propre outil de journalisation centralisée (CLS) pour enregistrer tous les événements importants enregistrés. une session. Ces événements aident nos développeurs à identifier les situations où quelque chose ne va pas dans une session et permettent de suivre certaines métriques clés du produit.

Le débogage des données va de choses simples comme la latence de réponse API à la surveillance de l'intégrité du réseau. Dans cet article, nous partageons notre histoire de la construction de notre outil CLS qui recueille de manière fiable 70G de données chronologiques pertinentes par jour à partir de plus de 100 composants, à l'échelle et avec deux instances EC3.M3.large

La décision de construire en interne [19659006] Tout d'abord, considérons pourquoi nous avons construit notre outil CLS en interne plutôt que d'utiliser une solution existante. Chacune de nos sessions envoie en moyenne 15 événements, depuis plusieurs composants vers le service, ce qui se traduit par environ 15 millions d'événements par jour.

Notre service devait pouvoir stocker toutes ces données. Nous avons cherché une solution complète pour prendre en charge le stockage, l'envoi et l'interrogation d'événements entre événements. Lorsque nous avons considéré des solutions tierces telles que Amplitude et Keen, nos mesures d'évaluation incluaient le coût, la performance dans le traitement des demandes parallèles élevées et la facilité d'adoption. Malheureusement, nous n'avons pas réussi à trouver une solution répondant à toutes nos exigences en termes de budget, même si les avantages auraient inclus un gain de temps et une réduction des alertes. Bien que cela exigerait des efforts supplémentaires, nous avons décidé de développer nous-mêmes une solution interne


 Building in-house
L'un des plus gros problèmes de construction interne est la quantité de ressources que nous devons dépenser pour maintenir il. (Crédit d'image: Source: Digiday )

Détails techniques

En termes d'architecture pour notre composant, nous avons décrit les exigences de base suivantes:

  • Client Performance
    N'a aucun impact sur les performances du client / composant envoyant les événements.
  • Scale [19659013] Capable de gérer un grand nombre de requêtes en parallèle
  • Performance du service
    Traitement rapide de tous les événements qui lui sont envoyés
  • Aperçu des données
    Chaque événement enregistré doit contenir des méta-informations être capable d'identifier le composant ou l'utilisateur, le compte ou le message et donner plus d'informations pour aider le développeur à déboguer plus rapidement.
  • Interface de requête
    Les développeurs peuvent interroger tous les événements pour une session particulière, rapports de santé des composants, ou générer des statistiques de performance significatives de nos systèmes.
  • Adoption plus rapide et plus facile
    Intégration facile à un composant existant ou nouveau sans encombrer les équipes et prendre leurs responsabilités

    Faible maintenance
    Nous sommes une petite équipe d'ingénierie, nous avons donc cherché une solution pour minimiser les alertes

Construction de notre solution CLS

Décision 1: Choisir une interface pour exposer

, nous ne voulions évidemment pas perdre nos données, mais nous ne voulions pas non plus que les performances des composants s'en ressentent. Sans parler du facteur supplémentaire qui empêche les composants existants de devenir plus compliqués, car cela retarderait l'adoption et la diffusion globales. En déterminant notre interface, nous avons considéré les choix suivants:

  1. Stocker des événements dans Redis local dans chaque composant, comme un processeur d'arrière-plan le pousse à CLS. Cependant, cela nécessite une modification de tous les composants, ainsi qu'une introduction de Redis pour les composants qui ne le contiennent pas déjà.
  2. Un modèle Publisher – Subscriber, où Redis est plus proche du CLS. Comme tout le monde publie des événements, encore une fois, nous avons le facteur de composants à travers le monde. Pendant la période de fort trafic, cela retarderait les composants. En outre, cette écriture peut sauter par intermittence jusqu'à cinq secondes (en raison de l'Internet uniquement).
  3. Envoi d'événements via UDP, ce qui a un impact moindre sur les performances de l'application. Dans ce cas, les données seraient envoyées et oubliées, cependant, l'inconvénient serait la perte de données.

Fait intéressant, notre perte de données sur UDP était inférieure à 0,1%, ce qui était acceptable pour nous d'envisager de créer un tel service. Nous avons réussi à convaincre toutes les équipes que cette perte valait la peine, et nous sommes allés de l'avant pour exploiter une interface UDP qui écoutait tous les événements envoyés.

Alors qu'un résultat a eu un impact moindre sur la performance d'une application, nous avons Nous sommes confrontés à un problème car le trafic UDP n'était pas autorisé depuis tous les réseaux, la plupart du temps par nos utilisateurs, ce qui nous a parfois empêché de recevoir des données. Pour contourner ce problème, nous avons pris en charge les événements de journalisation à l'aide de requêtes HTTP. Tous les événements provenant du côté de l'utilisateur seraient envoyés via HTTP, alors que tous les événements enregistrés à partir de nos composants seraient via UDP

Décision 2: Tech Stack (Language, Framework & Storage)

Nous sommes un magasin Ruby. Cependant, nous étions incertains si Ruby serait un meilleur choix pour notre problème particulier. Notre service devrait traiter beaucoup de demandes entrantes, ainsi que traiter beaucoup d'écritures. Avec le verrou Global Interpreter, il serait difficile d'obtenir un multithreading ou une simultanéité dans Ruby (ne vous offusquez pas, nous adorons Ruby!). Nous avions donc besoin d'une solution qui nous aiderait à réaliser ce type de concurrence.

Nous voulions également évaluer un nouveau langage dans notre pile technologique, et ce projet semblait parfait pour expérimenter de nouvelles choses. C'est à ce moment-là que nous avons décidé de donner un coup de projecteur à Golang, car il offrait un support intégré pour la concurrence, les threads légers et les go-routines. Chaque point de données consigné ressemble à une paire clé-valeur où 'clé' est l'événement et 'valeur' ​​lui sert de valeur associée.

Mais avoir une simple clé et valeur ne suffit pas pour récupérer une donnée liée à une session – il y a plus métadonnées à lui. Pour résoudre ce problème, nous avons décidé que tout événement devant être enregistré comporterait un identifiant de session avec sa clé et sa valeur. Nous avons également ajouté des champs supplémentaires tels que l'horodatage, l'ID utilisateur et le composant enregistrant les données, pour faciliter l'extraction et l'analyse des données.

Maintenant que nous avons décidé de notre structure de charge utile, nous avons dû choisir notre banque de données. Nous avons considéré Elastic Search, mais nous voulions également prendre en charge les demandes de mise à jour pour les clés. Cela déclencherait la réindexation du document entier, ce qui pourrait affecter les performances de nos écritures. MongoDB a plus de sens en tant que banque de données car il serait plus facile d'interroger tous les événements en fonction de l'un des champs de données qui seraient ajoutés.

Décision 3: La taille de la base de données est énorme et l'interrogation et l'archivage s'envolent

Afin de réduire la maintenance, notre service devrait gérer autant d'événements que possible. Étant donné le taux de publication de caractéristiques et de produits par BrowserStack, nous étions certains que le nombre de nos événements augmenterait à des taux plus élevés au fil du temps, ce qui signifie que notre service devrait continuer de bien fonctionner. À mesure que l'espace augmente, les lectures et les écritures prennent plus de temps, ce qui pourrait être un énorme coup pour les performances du service.

La ​​première solution que nous avons explorée était de déplacer les journaux d'une certaine période. journées). Pour ce faire, nous avons créé une base de données différente pour chaque jour, ce qui nous permet de trouver des journaux plus anciens qu'une période donnée sans avoir à numériser tous les documents écrits. Maintenant, nous supprimons continuellement les bases de données de plus de 15 jours de Mongo, tout en gardant bien sûr les sauvegardes au cas où.

La ​​seule pièce restante était une interface de développeur pour interroger les données liées à la session. Honnêtement, c'était le problème le plus facile à résoudre. Nous fournissons une interface HTTP, où les utilisateurs peuvent interroger les événements liés à la session dans la base de données correspondante dans MongoDB, pour toutes les données ayant un ID de session particulier.

Architecture

Parlons des composants internes du service, en considérant points suivants:

  1. Comme discuté précédemment, nous avions besoin de deux interfaces – une écoute sur UDP et une écoute sur HTTP. Nous avons donc construit deux serveurs, encore un pour chaque interface, pour écouter les événements. Dès qu'un événement arrive, nous l'analysons pour vérifier s'il contient les champs requis: ID de session, clé et valeur. Si ce n'est pas le cas, les données sont supprimées. Dans le cas contraire, les données sont transmises sur un canal Go à un autre goroutine, dont la seule responsabilité est d'écrire dans MongoDB.
  2. L'écriture sur le MongoDB peut être une source de préoccupation. Si les écritures dans MongoDB sont plus lentes que les données de taux reçues, cela crée un goulot d'étranglement. Cela, à son tour, affame les autres événements entrants et signifie des données abandonnées. Le serveur doit donc être rapide dans le traitement des journaux entrants et être prêt à traiter ceux à venir. Pour résoudre le problème, nous divisons le serveur en deux parties: la première reçoit tous les événements et les met en attente pour la seconde, qui les traite et les écrit dans MongoDB
  3. Pour la mise en file d'attente, nous avons choisi Redis. En divisant le composant entier en ces deux pièces, nous avons réduit la charge de travail du serveur, ce qui lui a permis de gérer plus de logs
  4. Nous avons écrit un petit service utilisant Sinatra server pour gérer tout le travail d'interrogation de MongoDB avec des paramètres donnés. Il renvoie une réponse HTML / JSON aux développeurs lorsqu'ils ont besoin d'informations sur une session particulière

Tous ces processus s'exécutent sur une seule instance de m3.large


 CLS v1
CLS v1 : Une représentation de la première architecture du système. Tous les composants fonctionnent sur une seule machine.

Feature Requests

Comme notre outil CLS a vu plus d'utilisation au fil du temps, il avait besoin de plus de fonctionnalités.

Métadonnées manquantes

Au fur et à mesure que le nombre de composants dans BrowserStack augmente, nous en demandons davantage à CLS. Par exemple, nous avions besoin de la possibilité d'enregistrer des événements à partir de composants sans ID de session. Dans le cas contraire, il en résulterait une charge pour notre infrastructure, qui affecterait les performances des applications et augmenterait le trafic sur nos serveurs principaux.

Nous avons résolu ce problème en activant la journalisation des événements avec d'autres clés, telles que les ID de terminal et d'utilisateur. Maintenant, chaque fois qu'une session est créée ou mise à jour, CLS est informé avec l'ID de session, ainsi que les ID utilisateur et terminal respectifs. Il stocke une carte qui peut être récupérée par le processus d'écriture dans MongoDB. Chaque fois qu'un événement contenant l'ID de l'utilisateur ou du terminal est récupéré, l'identifiant de session est ajouté

Handling Spamming (Problèmes de code dans d'autres composants)

CLS a également rencontré des difficultés de gestion des spams. Nous avons souvent trouvé des déploiements dans des composants qui généraient un volume important de requêtes envoyées à CLS. D'autres journaux souffriraient du processus, car le serveur devenait trop occupé pour les traiter et les journaux importants étaient supprimés.

En général, la plupart des données enregistrées étaient des requêtes HTTP. Pour les contrôler, nous permettons la limitation de débit sur nginx (en utilisant le module limit_req_zone), qui bloque les demandes de n'importe quelle adresse IP que nous avons trouvé en touchant plus d'un certain nombre de requêtes en un temps limité. Bien entendu, nous exploitons les rapports de santé sur toutes les adresses IP bloquées et informons les équipes responsables

Scale v2

À mesure que nos sessions augmentaient, les données consignées dans CLS augmentaient également. Cela a affecté les requêtes que nos développeurs exécutaient quotidiennement, et bientôt le goulot d'étranglement que nous avions était avec la machine elle-même. Notre installation se composait de deux machines principales exécutant tous les composants ci-dessus, avec un tas de scripts pour interroger Mongo et garder une trace des métriques clés pour chaque produit. Au fil du temps, les données sur la machine ont fortement augmenté et les scripts ont commencé à prendre beaucoup de temps CPU. Même après avoir essayé d'optimiser les requêtes Mongo, nous sommes toujours revenus sur les mêmes problèmes.

Pour résoudre ce problème, nous avons ajouté une autre machine pour exécuter les scripts de rapport d'intégrité et l'interface pour interroger ces sessions. Le processus impliquait le démarrage d'une nouvelle machine et la mise en place d'un esclave du Mongo fonctionnant sur la machine principale. Cela a aidé à réduire les pointes de CPU que nous avons vu tous les jours causés par ces scripts.


 CLS v2
CLS v2: Une représentation de l'architecture du système actuel. Les journaux sont écrits sur la machine maître et synchronisés sur la machine esclave. Les requêtes du développeur s'exécutent sur la machine esclave.

Conclusion

Construire un service pour une tâche aussi simple que l'enregistrement de données peut se compliquer, à mesure que la quantité de données augmente. Cet article traite des solutions que nous avons explorées, ainsi que des défis rencontrés lors de la résolution de ce problème. Nous avons expérimenté avec Golang pour voir si cela correspondait bien à notre écosystème, et jusqu'ici nous avons été satisfaits. Notre choix de créer un service interne plutôt que de payer pour un service externe a été merveilleusement rentable. Nous n'avions pas non plus besoin d'adapter notre configuration à une autre machine beaucoup plus tard – lorsque le volume de nos sessions augmentait. Bien sûr, nos choix de développement de CLS étaient entièrement basés sur nos besoins et nos priorités.

Aujourd'hui, CLS gère jusqu'à 15 millions d'événements chaque jour, ce qui représente jusqu'à 70 Go de données. Ces données sont utilisées pour nous aider à résoudre les problèmes auxquels nos clients sont confrontés au cours d'une session. Nous utilisons également ces données à d'autres fins. Compte tenu des informations fournies par les données de chaque session sur différents produits et composants internes, nous avons commencé à tirer parti de ces données pour suivre chaque produit. Ceci est réalisé en extrayant les métriques clés pour tous les composants importants.

Dans l'ensemble, nous avons vu un grand succès dans la construction de notre propre outil CLS. Si cela a du sens pour vous, je vous recommande d'envisager de faire de même!

 Éditorial de Smashing (rb, ra, il)




Source link