Environ 30 fois plus de concurrence dans Node.js

Quelle est la meilleure façon d'augmenter de façon transparente la concurrence d'accès au service Node.js utilisé en production? C'est une question à laquelle mon équipe devait répondre il y a quelques mois.

Nous avons lancé 4000 conteneurs Node (ou «travailleurs»), qui assurent le fonctionnement de notre service d'intégration avec les banques. Le service a été initialement conçu pour que chaque travailleur soit conçu pour traiter une seule demande à la fois. Cela a réduit l'impact sur le système de ces opérations qui pourraient bloquer de manière inattendue le cycle des événements et nous ont permis d'ignorer les différences dans l'utilisation des ressources par diverses opérations similaires. Mais, comme nos capacités se limitaient à l'exécution simultanée de seulement 4 000 demandes, le système n'a pas pu être correctement mis à l'échelle. La rapidité de réponse à la plupart des demandes ne dépend pas de la capacité de l'équipement, mais des capacités du réseau. Par conséquent, nous pourrions améliorer le système et réduire le coût de son support si nous pouvions trouver un moyen de traiter de manière fiable les demandes en parallèle.



Après avoir étudié cette question, nous n'avons pas pu trouver un bon guide qui discuterait de la transition du «manque de parallélisme» dans Node.js à un «haut niveau de parallélisme». En conséquence, nous avons développé notre propre stratégie de migration, qui était basée sur une planification minutieuse, de bons outils, des outils de surveillance et une bonne dose de débogage. En conséquence, nous avons réussi à augmenter le niveau de parallélisme de notre système de 30 fois. Cela équivaut à réduire le coût de maintenance du système d'environ 300 000 dollars par an.

Ce matériel est consacré à l'histoire de la façon dont nous avons augmenté la productivité et l'efficacité de nos employés Node.js, et à ce que nous avons appris en procédant de cette façon.

Pourquoi avons-nous décidé d'investir dans le parallélisme?


Il peut sembler surprenant que nous ayons atteint de telles dimensions sans recourir au parallélisme. Comment est-ce arrivé? Seulement 10% des opérations de traitement de données effectuées par les outils Plaid sont lancées par des utilisateurs assis devant un ordinateur et ayant connecté leurs comptes à l'application. Tout le reste est constitué de transactions pour mettre à jour périodiquement des transactions qui sont effectuées sans la présence de l'utilisateur. La logique a été ajoutée au système d'équilibrage de charge que nous utilisons, ce qui garantit la priorité des demandes faites par les utilisateurs sur les demandes de mise à jour des transactions. Cela nous a permis de gérer des rafales d'activité des opérations d'accès aux API à 1000% ou même plus. Cela a été fait grâce à des transactions visant à mettre à jour les données.

Bien que ce schéma de compromis fonctionne depuis longtemps, il a été possible d'y discerner plusieurs moments désagréables. Nous savions qu'en fin de compte, cela pourrait nuire à la fiabilité du service.

  • Les pics de demandes d'API provenant des clients augmentaient de plus en plus. Nous craignions qu'une forte augmentation de l'activitĂ© puisse Ă©puiser nos capacitĂ©s de traitement des requĂŞtes.
  • L'augmentation soudaine des retards dans le traitement des demandes adressĂ©es aux banques a Ă©galement entraĂ®nĂ© une diminution de la capacitĂ© des travailleurs. Étant donnĂ© que les banques utilisent diverses solutions d'infrastructure, nous avons dĂ©fini des dĂ©lais d'expiration très prudents pour les demandes sortantes. Par consĂ©quent, le chargement de certaines donnĂ©es peut prendre plusieurs minutes. S'il arrivait que les retards dans l'exĂ©cution de nombreuses demandes auprès des banques augmenteraient soudainement considĂ©rablement, il se rĂ©vĂ©lerait que de nombreux travailleurs seraient simplement coincĂ©s dans l'attente de rĂ©ponses.
  • Le dĂ©ploiement dans ECS est devenu trop lent et mĂŞme si nous avons amĂ©liorĂ© la vitesse de dĂ©ploiement du système, nous ne voulions pas continuer Ă  augmenter la taille du cluster.

Nous avons décidé que la meilleure façon de traiter les goulots d'étranglement des applications et d'augmenter la fiabilité du système était d'augmenter le niveau de parallélisme dans le traitement des demandes. De plus, nous espérions que, comme effet secondaire, cela nous permettrait de réduire les coûts d'infrastructure et d'aider à mettre en œuvre de meilleurs outils pour surveiller le système. Cela et un autre à l'avenir porteraient leurs fruits.

Comment nous avons introduit les mises à jour, en veillant à la fiabilité


â–ŤOutils et surveillance


Nous avons notre propre équilibreur de charge, qui redirige les demandes vers les employés de Node.js. Chaque travailleur exécute un serveur gRPC utilisé pour traiter les demandes. Worker utilise Redis pour indiquer à l'équilibreur de charge qu'il est disponible. Cela signifie que l'ajout de parallélisme au système revient à simplement changer quelques lignes de code. À savoir, le travailleur, au lieu de devenir inaccessible après que la demande lui a été faite, doit informer qu'il est disponible jusqu'à ce qu'il se trouve occupé à traiter les N demandes qui lui sont parvenues (chacune d'elles). représenté par son propre objet Promise).

Certes, en fait, tout n'est pas si simple. Lors du déploiement des mises à jour du système, nous considérons toujours que notre objectif principal est de maintenir sa fiabilité. Par conséquent, nous ne pouvions pas simplement prendre et, guidés par quelque chose comme le principe YOLO, mettre le système en mode de traitement de requête parallèle. Nous nous attendions à ce qu'une telle mise à niveau du système soit particulièrement risquée. Le fait est que cela aurait un effet imprévisible sur l'utilisation du processeur, de la mémoire et des retards dans l'exécution des tâches. Étant donné que le moteur V8 utilisé dans Node.js gère les tâches dans la boucle d'événements, notre principale préoccupation était qu'il pourrait s'avérer que nous faisons trop de travail dans la boucle d'événements et ainsi réduire le débit du système.

Afin d'atténuer ces risques, nous avons, avant même la mise en production du premier collaborateur parallèle, assuré la disponibilité des outils de surveillance et des outils suivants dans le système:

  • La pile ELK que nous avons dĂ©jĂ  utilisĂ©e nous a fourni une quantitĂ© suffisante d'informations enregistrĂ©es, ce qui pourrait ĂŞtre utile pour comprendre rapidement ce qui se passait dans le système.
  • Nous avons ajoutĂ© plusieurs mĂ©triques Prometheus au système. Y compris les Ă©lĂ©ments suivants:

    • Taille de segment V8 obtenue Ă  l'aide de process.memoryUsage() .
    • Informations sur les opĂ©rations de rĂ©cupĂ©ration de place Ă  l'aide du package gc-stats .
    • DonnĂ©es sur le temps nĂ©cessaire Ă  la rĂ©alisation des tâches, regroupĂ©es par type d'opĂ©rations liĂ©es Ă  l'intĂ©gration avec les banques et par niveau de simultanĂ©itĂ©. Nous en avions besoin pour mesurer de manière fiable l'impact de la concurrence sur le dĂ©bit du système.
  • Nous avons crĂ©Ă© le panneau de contrĂ´le Grafana , conçu pour Ă©tudier le degrĂ© d'impact de la concurrence sur le système.
  • Pour nous, la possibilitĂ© de modifier le comportement de l'application sans avoir Ă  redĂ©ployer le service Ă©tait extrĂŞmement importante. Par consĂ©quent, nous avons crĂ©Ă© un ensemble de drapeaux LaunchDarkly conçus pour contrĂ´ler divers paramètres. Avec cette approche, la sĂ©lection des paramètres des travailleurs, calculĂ©s pour qu'ils atteignent le niveau maximal de parallĂ©lisme, nous a permis de mener rapidement des expĂ©riences et de trouver les meilleurs paramètres, en y consacrant quelques minutes.
  • Afin de savoir comment diffĂ©rentes parties de l'application chargent le processeur, nous avons intĂ©grĂ© les outils de collecte de donnĂ©es du service de production, sur la base desquels des diagrammes de flamme ont Ă©tĂ© construits.

    • Nous avons utilisĂ© le package 0x parce que les outils Node.js Ă©taient faciles Ă  intĂ©grer dans notre service et parce que la visualisation finale des donnĂ©es HTML a soutenu la recherche et nous a donnĂ© un bon niveau de dĂ©tail.
    • Nous avons ajoutĂ© un mode de profilage au système lorsque le travailleur a commencĂ© avec le package 0x activĂ© et, Ă  sa sortie, nous avons notĂ© les donnĂ©es finales dans S3. Ensuite, nous pourrions tĂ©lĂ©charger les journaux dont nous avons besoin depuis S3 et les visualiser localement en utilisant une commande de la forme 0x --visualize-only ./flamegraph .
    • Dans un certain laps de temps, nous avons commencĂ© le profilage pour un seul travailleur. Le profilage augmente la consommation de ressources et rĂ©duit la productivitĂ©, nous aimerions donc limiter ces effets nĂ©gatifs Ă  un seul travailleur.

▍ Démarrer le déploiement


Après avoir terminé la préparation préliminaire, nous avons créé un nouveau cluster ECS pour les «travailleurs parallèles». Ce sont les travailleurs qui ont utilisé les drapeaux LaunchDarkly pour définir dynamiquement leur niveau maximal de parallélisme.

Notre plan de déploiement du système comprenait une redirection progressive du volume croissant de trafic de l'ancien cluster vers le nouveau. Pendant ce temps, nous allions surveiller de près les performances du nouveau cluster. À chaque niveau de charge, nous avons prévu d'augmenter le niveau de parallélisme de chaque travailleur, en le portant à la valeur maximale à laquelle il n'y a pas d'augmentation de la durée des tâches ou de dégradation d'autres indicateurs. Si nous étions en difficulté, nous pourrions, en quelques secondes, rediriger dynamiquement le trafic vers l'ancien cluster.

Comme prévu, nous avons rencontré des problèmes délicats. Nous devions les étudier et les éliminer afin d'assurer le bon fonctionnement du système mis à jour. C'est là que le plaisir a commencé.

Développez, explorez, répétez


â–ŤAugmentation de la taille de segment maximale de Node.js


Lorsque nous avons commencé à déployer le nouveau système, nous avons commencé à recevoir des notifications d'achèvement de tâches avec un code de sortie différent de zéro. Eh bien, que puis-je dire - un début prometteur. Ensuite, nous avons enterré à Kibana et trouvé le journal nécessaire:

 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - Javascript heap out of memory 1: node::Abort() 2: node::FatalException(v8::Isolate*, v8::Local, v8::Local) 3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) 4: v8::internal::Factory::NewFixedArray(int, v8::internal::PretenureFlag) 

Cela rappelait les effets des fuites de mémoire que nous avions déjà rencontrées lorsque le processus s'est terminé de manière inattendue, donnant un message d'erreur similaire. Cela semblait tout à fait attendu: une augmentation du niveau de parallélisme conduit à une augmentation du niveau d'utilisation de la mémoire.

Nous avons suggéré que l'augmentation de la taille de segment de mémoire maximale de Node.js, qui est définie sur 1,7 Go par défaut, peut aider à résoudre ce problème. Ensuite, nous avons commencé à exécuter Node.js, en définissant la taille de --max-old-space-size=6144 maximale à 6 Go (en utilisant l'indicateur de ligne de commande --max-old-space-size=6144 ). Il s'agissait de la plus grande valeur adaptée à nos instances EC2. Pour notre plus grand plaisir, une telle décision nous a permis de faire face à l'erreur ci-dessus qui se produit en production.

▍ Identification des goulots d'étranglement de la mémoire


Après avoir résolu le problème d'allocation de mémoire, nous avons commencé à rencontrer un faible débit de tâches sur des travailleurs parallèles. En même temps, l'un des graphiques sur le panneau de commande a immédiatement attiré notre attention. Il s'agissait d'un rapport sur la façon dont les processus de travail parallèles utilisent un groupe.


Utilisation du tas

Certaines des courbes de ce graphique montaient continuellement - jusqu'à ce qu'elles se transforment, au niveau de la taille de tas maximale, en lignes presque horizontales. Nous ne l'avons vraiment pas aimé.

Nous avons utilisé des métriques système dans Prometheus afin d'éliminer les fuites d'un descripteur de fichier ou d'un socket réseau à cause des causes d'un tel comportement du système. Notre hypothèse la plus appropriée était que la collecte des ordures n'était pas effectuée assez souvent pour les objets anciens. Cela pourrait conduire au fait qu'au fur et à mesure du traitement des tâches, le travailleur accumulerait de plus en plus de mémoire allouée à des objets déjà inutiles. Nous avons supposé que le fonctionnement du système, pendant lequel son débit est dégradé, ressemble à ceci:

  • Le travailleur reçoit une nouvelle tâche et exĂ©cute certaines actions.
  • Au cours de l'exĂ©cution de la tâche, de la mĂ©moire est allouĂ©e sur le tas pour les objets.
  • Étant donnĂ© qu'une certaine opĂ©ration avec laquelle ils travaillent sur le principe du «fait et oublié» (alors on ne savait pas encore laquelle) est incomplète, les rĂ©fĂ©rences aux objets sont enregistrĂ©es mĂŞme après la fin de la tâche.
  • La rĂ©cupĂ©ration de place est ralentie du fait que le V8 doit analyser un nombre croissant d'objets dans le tas.
  • Étant donnĂ© que V8 implĂ©mente un système de collecte des ordures qui fonctionne selon le schĂ©ma d'arrĂŞt du monde (arrĂŞt du programme pendant la durĂ©e de la collecte des ordures), les nouvelles tâches recevront inĂ©vitablement moins de temps processeur, ce qui rĂ©duit le dĂ©bit du travailleur.

Nous avons commencé à rechercher dans notre code des opérations qui sont effectuées sur la base du principe «fait et oublié». Ils sont également appelés «promesses flottantes» («promesse flottante»). C'était simple - il suffisait de trouver les lignes dans lesquelles la règle de linter sans promesses flottantes était désactivée. Une méthode a attiré notre attention. Il a fait un appel à compressAndUploadDebuggingPayload sans attendre les résultats. Il semblait qu'un tel appel pouvait facilement se poursuivre longtemps même après la fin du traitement de la tâche.

 const postTaskDebugging = async (data: TypedData) => {    const payload = await generateDebuggingPayload(data);       //       ,    //        .    // tslint:disable-next-line:no-floating-promises    compressAndUploadDebuggingPayload(payload)        .catch((err) => logger.error('failed to upload data', err)); } 

Nous voulions tester l'hypothèse que de telles promesses flottantes étaient la principale source de problèmes. Si vous ne relevez pas ces défis, qui n'ont pas affecté le bon fonctionnement du système, pouvons-nous améliorer la vitesse des tâches? Voici à quoi ressemblaient les informations d'utilisation du tas après que nous nous soyons temporairement débarrassés des appels postTaskDebugging .


Utiliser le tas après avoir désactivé postTaskDebugging

Ça s'est avéré! Maintenant, le niveau d'utilisation du tas chez les travailleurs parallèles reste stable sur une longue période.

Il y avait le sentiment que dans le système, au fur et à mesure que les tâches étaient terminées, les "dettes" des appels compressAndUploadDebuggingPayload s'accumulaient progressivement. Si le travailleur a reçu des tâches plus rapidement qu'il n'a pu «rembourser» ces «dettes», alors les objets sous lesquels la mémoire a été allouée n'ont pas été soumis à des opérations de collecte des ordures. Cela a conduit à remplir le tas au sommet, que nous avons considéré ci-dessus, en analysant le graphique précédent.

Nous avons commencé à nous demander pourquoi ces promesses flottantes étaient si lentes. Nous ne voulions pas supprimer complètement compressAndUploadDebuggingPayload du code, car cet appel était extrêmement important pour que nos ingénieurs puissent déboguer les tâches de production sur leurs machines locales. D'un point de vue technique, nous pourrions résoudre le problème en attendant les résultats de cet appel et après avoir terminé la tâche, éliminant ainsi la promesse flottante. Mais cela augmenterait considérablement le temps d'exécution de chaque tâche que nous traitons.

Ayant décidé que nous n'utiliserions une telle solution au problème qu'en dernier recours, nous avons commencé à penser à optimiser le code. Comment accélérer cette opération?

â–ŤFix goulot d'Ă©tranglement S3


La logique de compressAndUploadDebuggingPayload facile à comprendre. Ici, nous compressons les données de débogage, et elles peuvent être assez importantes, car elles incluent le trafic réseau. Ensuite, nous téléchargeons les données compressées sur S3.

 export const compressAndUploadDebuggingPayload = async (    logger: Logger,    data: any, ) => {    const compressionStart = Date.now();    const base64CompressedData = await streamToString(        bfj.streamify(data)            .pipe(zlib.createDeflate())            .pipe(new b64.Encoder()),    );    logger.trace('finished compressing data', {        compression_time_ms: Date.now() - compressionStart,    );           const uploadStart = Date.now();    s3Client.upload({        Body: base64CompressedData,        Bucket: bucket,        Key: key,    });    logger.trace('finished uploading data', {        upload_time_ms: Date.now() - uploadStart,    ); } 

D'après les journaux de Kibana, il était clair que le téléchargement de données vers S3, même si son volume était petit, prenait beaucoup de temps. Nous ne pensions pas initialement que les sockets pourraient devenir un goulot d'étranglement dans le système, car l'agent HTTPS Node.js standard définit le paramètre maxSockets sur Infinity . Cependant, à la fin, nous avons lu la documentation AWS sur Node.js et trouvé quelque chose de surprenant pour nous: le client S3 réduit la valeur du paramètre maxSockets à 50 . Inutile de dire que ce comportement ne peut pas être qualifié d’intuitif.

Depuis que nous avons amené le travailleur dans un état où, en mode compétitif, plus de 50 tâches ont été effectuées, l'étape de téléchargement est devenue un goulot d'étranglement: il prévoyait l'attente de la libération du socket pour télécharger les données vers S3. Nous avons amélioré le temps de chargement des données en apportant la modification suivante au code d'initialisation du client S3:

 const s3Client = new AWS.S3({    httpOptions: {        agent: new https.Agent({            //                 //          S3.            maxSockets: 1024 * 20,        }),    },    region, }); 

▍ Accélération de la sérialisation JSON


Les améliorations du code S3 ont ralenti la croissance de la taille du segment de mémoire, mais elles n'ont pas conduit à une solution complète au problème. Il y avait une autre nuisance évidente: selon nos mesures, l'étape de compression des données dans le code ci-dessus a duré une fois 4 minutes. Il était beaucoup plus long que le temps de fin de tâche habituel, qui est de 4 secondes. Ne croyant pas nos yeux, ne comprenant pas comment cela peut prendre 4 minutes, nous avons décidé d'utiliser des benchmarks locaux et d'optimiser le bloc de code lent.

La compression des données se compose de trois étapes (ici, pour limiter l'utilisation de la mémoire, les flux Node.js sont utilisés). À savoir, dans la première étape, les données de chaîne JSON sont générées, dans la seconde, les données sont compressées à l'aide de zlib, dans la troisième, elles sont converties en encodage base64. Nous pensions que la source des problèmes pourrait être la bibliothèque tierce que nous utilisons pour générer des chaînes JSON - bfj . Nous avons écrit un script qui examine les performances de différentes bibliothèques pour générer des données de chaîne JSON à l'aide de flux (le code correspondant peut être trouvé ici ). Il s'est avéré que le package Big Friendly JSON que nous utilisions n'était pas du tout convivial. Il suffit de regarder les résultats de quelques mesures obtenues au cours de l'expérience:

 benchBFJ*100:    67652.616ms benchJSONStream*100: 14094.825ms 

Des résultats étonnants. Même dans un test simple, le paquet bfj s'est avéré être 5 fois plus lent que l'autre paquet, JSONStream. En découvrant cela, nous avons rapidement changé bfj en JSONStream et avons immédiatement vu une augmentation significative des performances.

â–Ť RĂ©duction du temps requis pour la collecte des ordures


Après avoir résolu les problèmes de mémoire, nous avons commencé à prêter attention à la différence de temps nécessaire pour traiter des tâches du même type entre les travailleurs réguliers et parallèles. Cette comparaison était tout à fait légitime, d'après ses résultats, nous avons pu juger de l'efficacité du nouveau système. Ainsi, si le rapport entre les travailleurs réguliers et parallèles était d'environ 1, cela nous donnerait l'assurance que nous pouvons rediriger le trafic vers ces travailleurs en toute sécurité. Mais lors des premiers lancements du système, le graphique correspondant dans le panneau de contrôle de Grafana ressemblait à celui illustré ci-dessous.


Le rapport du temps d'exécution des tâches par les travailleurs conventionnels et parallèles

Veuillez noter que parfois l'indicateur est de l'ordre de 8: 1, et cela malgré le fait que le niveau moyen de parallélisation des tâches est relativement faible et se situe aux alentours de 30. Nous savions que les tâches que nous résolvons concernant l'interaction avec les banques ne créent pas lourde charge sur les processeurs. Nous savions également que nos conteneurs «parallèles» n'étaient nullement limités. Ne sachant pas où chercher la cause du problème, nous sommes allés lire des documents sur l'optimisation des projets Node.js. Malgré le petit nombre de ces articles, nous sommes tombés sur ce matériel, qui traite de la réalisation de 600 000 connexions de socket Web compétitives dans Node.js.

En particulier, notre attention a été attirée sur l'utilisation de l' --nouse-idle-notification . Nos processus Node.js peuvent-ils passer autant de temps à collecter les ordures? Ici, en passant, le package gc-stats nous a donné l'occasion de regarder le temps moyen consacré à la collecte des ordures.


Analyse du temps consacré à la collecte des ordures

Nous avions le sentiment que nos processus passaient environ 30% du temps à collecter les ordures à l'aide de l'algorithme Scavenge. Ici, nous n'allons pas décrire les détails techniques concernant les différents types de collecte de déchets dans Node.js. Si ce sujet vous intéresse - jetez un œil à ce matériel. L'essence de l'algorithme Scavenge est que la récupération de place est souvent lancée pour effacer la mémoire occupée par les petits objets dans le tas Node.js appelé «nouvel espace».

Il s'est donc avéré que dans nos processus Node.js, la collecte des ordures démarre trop souvent. Puis-je désactiver le garbage collection V8 et l'exécuter moi-même? Existe-t-il un moyen de réduire la fréquence des appels de récupération de place? Il s'est avéré que le premier de ce qui précède ne peut pas être fait, mais le dernier - c'est possible! Nous pouvons simplement augmenter la taille de la zone "nouvel espace" en augmentant la limite de la zone "semi-espace" dans Node.js en utilisant l'indicateur de ligne de commande --max-semi-space-size=1024 . Cela vous permet d'effectuer plus d'opérations d'allocation de mémoire pour les objets de courte durée jusqu'à ce que le V8 démarre le garbage collection. En conséquence, la fréquence de lancement de telles opérations est réduite.


Résultats d'optimisation de la récupération de place

Encore une victoire! L'augmentation de la zone «nouvel espace» a entraîné une réduction significative du temps consacré à la collecte des ordures à l'aide de l'algorithme de récupération - de 30% à 2%.

â–ŤOptimisez l'utilisation du processeur


Après tout ce travail, le résultat nous convenait. Les tâches exécutées chez des travailleurs parallèles, avec une parallélisation de 20 fois le travail, fonctionnaient presque aussi rapidement que celles qui étaient effectuées séparément chez des travailleurs séparés. Il nous a semblé que nous avions surmonté tous les goulets d'étranglement, mais nous ne savions toujours pas exactement quelles opérations ralentissaient le système en production. Puisqu'il n'y avait plus d'endroits dans le système qui nécessitaient évidemment une optimisation, nous avons décidé d'étudier comment les travailleurs utilisent les ressources du processeur.

Sur la base des données collectées sur l'un de nos collaborateurs parallèles, un calendrier fougueux a été créé. Nous avions une visualisation claire à notre disposition, avec laquelle nous pouvions travailler sur la machine locale. Oui, voici un détail intéressant: la taille de ces données était de 60 Mo. C'est ce que nous avons vu en recherchant l' logger mots dans le graphique fougueux 0x.


Analyse des données avec les outils 0x

Les zones bleu-vert mises en évidence dans les colonnes indiquent qu'au moins 15% du temps processeur a été consacré à la génération du journal de travail. En conséquence, nous avons pu réduire ce temps de 75%. Certes, l'histoire de la façon dont nous avons fait cela fait l'objet d'un article séparé. (Astuce: nous avons utilisé des expressions régulières et fait beaucoup de travail avec les propriétés).

Après cette optimisation, nous avons pu traiter simultanément jusqu'à 30 tâches en un seul travailleur sans nuire aux performances du système.

Résumé


Le passage à des travailleurs parallèles a réduit les coûts annuels pour EC2 d'environ 300 000 dollars et a considérablement simplifié l'architecture du système. Maintenant, nous utilisons dans la production environ 30 fois moins de conteneurs qu'auparavant. Notre système est plus résistant aux retards de traitement des demandes sortantes et aux pics de demandes d'API provenant des utilisateurs.

Tout en parallélisant notre service d'intégration avec les banques, nous avons appris beaucoup de nouvelles choses:

  • Ne sous-estimez jamais l'importance d'avoir des mesures système de bas niveau. La capacitĂ© de surveiller les donnĂ©es liĂ©es Ă  la collecte des ordures et Ă  l'utilisation de la mĂ©moire nous a fourni une aide considĂ©rable pour dĂ©ployer le système et le finaliser.
  • Les graphiques flamboyants sont un excellent outil. Maintenant que nous avons appris Ă  les utiliser, nous pouvons facilement identifier de nouveaux goulots d'Ă©tranglement dans le système avec leur aide.
  • La comprĂ©hension des mĂ©canismes d'exĂ©cution de Node.js nous a permis d'Ă©crire un meilleur code. Par exemple, sachant comment V8 alloue de la mĂ©moire aux objets et comment fonctionne le ramasse-miettes, nous avons vu l'intĂ©rĂŞt d'utiliser la technique de rĂ©utilisation des objets aussi largement que possible. Parfois, pour mieux comprendre tout cela, vous devez travailler directement avec V8 ou expĂ©rimenter avec les indicateurs de ligne de commande Node.js.
  • , . maxSocket , Node.js, , , , AWS Node.js . , , , .

Chers lecteurs! Node.js-?

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


All Articles