"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Création d'applications Full-Stack hautes performances avec React, Node.js et MongoDB : un voyage en matière d'évolutivité, de vitesse et de solutions

Création d'applications Full-Stack hautes performances avec React, Node.js et MongoDB : un voyage en matière d'évolutivité, de vitesse et de solutions

Publié le 2024-11-02
Parcourir:708

Building High-Performance Full-Stack Apps with React, Node.js & MongoDB: A Journey in Scalability, Speed & Solutions

Vous ouvrez votre application de production et remarquez qu'elle s'arrête. Le frontend ne répond pas. Les API backend expirent. Les requêtes MongoDB semblent s'exécuter indéfiniment. Votre boîte de réception est inondée de plaintes d'utilisateurs. Votre équipe se rassemble pour essayer de trier la situation.

Vous y êtes allé ? Ouais, moi aussi.

Je suis un développeur Full Stack senior et j'en ai marre des applications qui fonctionnent bien lorsque vous ne les utilisez qu'en tant qu'utilisateur unique ou lorsque l'espace problématique est simple mais qui se fanent et s'effondrent sous le trafic réel ou un tâche légèrement plus exigeante.

Restez avec moi et je vous expliquerai comment j'ai résolu ces problèmes à l'aide de React, Node.js et MongoDB.

Je ne vais pas simplement vous donner un autre vieux tutoriel, je vais partager une histoire. Une histoire sur la façon de résoudre les problèmes du monde réel et de créer une application rapide et hautement évolutive qui pourrait passer l'épreuve du temps et de tout ce qui lui est lancé.

1 : Quand React est devenu le goulot d'étranglement

Nous venions de déployer une mise à jour de notre application web, développée avec React, à mon travail. Nous étions confiants, pensant que les utilisateurs apprécieraient les nouvelles fonctionnalités.

Cependant, nous n'avons pas tardé à recevoir des plaintes : l'application se chargeait extrêmement lentement, les transitions étaient saccadées et les utilisateurs étaient de plus en plus frustrés. Même si nous savions que les nouvelles fonctionnalités étaient bénéfiques, elles ont par inadvertance entraîné des problèmes de performances. Notre enquête a révélé un problème : l'application regroupait tous ses composants dans un seul package, ce qui obligeait les utilisateurs à tout télécharger à chaque fois qu'ils accédaient à l'application.

Le correctif : nous avons implémenté un concept très utile appelé Lazy Loading. J'avais déjà eu cette idée auparavant, mais c'était exactement ce dont nous avions besoin. Nous avons complètement repensé la structure de l'application, en nous assurant qu'elle ne charge les composants nécessaires que lorsque cela est nécessaire.

Voici un aperçu de la façon dont nous avons mis en œuvre cette solution :

const Dashboard = React.lazy(() => import('./Dashboard'));
const Profile = React.lazy(() => import('./Profile'));

Loading...}>
  
  


Le résultat : L'impact de ce changement a été tout simplement remarquable. Nous avons constaté une énorme réduction de 30 % de notre offre groupée et les utilisateurs ont constaté un chargement initial beaucoup plus rapide. Le meilleur, c'est que les utilisateurs n'avaient aucune idée que certaines parties de l'application étaient encore en cours de chargement. Nous avons utilisé Suspense à bon escient et affiché un simple message de chargement non intrusif.

2 : Apprivoiser la bête de la gestion d'État dans React

Alors que nous avancions rapidement de quelques mois, notre équipe de développement atteignait son rythme et proposait de nombreuses nouvelles fonctionnalités. Mais parallèlement à la croissance, nous avons commencé par inadvertance à créer ce que j’appelle une application plus complexe. Redux est rapidement devenu un handicap plutôt qu'une aide pour faciliter des interactions simples.

J'ai donc passé du temps à créer un POC pour une meilleure alternative. J'ai documenté tout cela et animé plusieurs réunions de partage de connaissances sur ce à quoi pourrait ressembler cette approche. Nous avons finalement décidé en groupe d'essayer React Hooks (et en particulier useReducer) comme solution proposée pour la gestion de l'état, car en fin de compte, nous voulions un code plus simple et moins d'empreinte d'exécution massive que les versions plus récentes de Redux avaient une surcharge croissante avec de nombreux petits systèmes autonomes. États.

La transformation qui a suivi était tout simplement révolutionnaire. Nous nous sommes retrouvés à remplacer des dizaines de lignes de code passe-partout par une logique de hook concise et facile à comprendre. Voici un exemple illustrant la façon dont nous avons mis en œuvre cette nouvelle approche :

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count   1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const CounterContext = React.createContext();

function CounterProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    
      {children}
    
  );
}

Le résultat : L'impact de cette transition a été profond et de grande envergure. Notre candidature est devenue nettement plus prévisible et plus facile à raisonner. La base de code, désormais plus simple et plus intuitive, a permis à notre équipe d'itérer à un rythme beaucoup plus rapide. Peut-être plus important encore, nos développeurs juniors ont signalé une nette amélioration de leur capacité à naviguer et à comprendre la base de code. Le résultat final était une situation gagnant-gagnant : moins de code à maintenir, moins de bugs à éliminer et une équipe de développement sensiblement plus heureuse et plus productive.

3 : Conquérir le champ de bataille du backend – Optimiser les API Node.js pour des performances optimales

Bien que nous ayons pu introduire de nombreuses améliorations dans notre frontend, peu de temps après, nous avons rencontré plusieurs problèmes sur le backend. Les performances de notre API sont devenues horribles et rares sont les points de terminaison en particulier qui ont commencé à fonctionner de manière épouvantable. Ces points de terminaison effectuent une séquence d'appels vers différents services tiers et, avec la base d'utilisateurs croissante, le système n'était pas en mesure de gérer cette charge.

Ce qui n'allait pas était assez logique : nous n'étions PAS parallèles ! c'est-à-dire que les demandes adressées à chaque point de terminaison étaient traitées de manière séquentielle, c'est-à-dire que chaque appel suivant attendrait que l'appel précédent soit terminé. Dans ce système à grande échelle (centaines de milliers de demandes), cela s'est avéré désastreux.

La solution : pour résoudre ce problème, nous avons décidé de réécrire une grande partie de notre code et d'utiliser la puissance de Promise.all() pour effectuer la requête API de manière simultanée. Cela signifie que vous lancez plusieurs requêtes et que vous n'avez pas besoin d'attendre la fin de chaque appel pour lancer le suivant.

Pour ce faire, nous ne lançons pas un appel API, attendons qu'il se termine, en faisons un autre et ainsi de suite…

Au lieu de cela simplement en utilisant Promise.all(), tout a été lancé en même temps et beaucoup plus rapidement.

Voici un aperçu de la façon dont nous avons mis en œuvre cette solution :

const getUserData = async () => {
  const [profile, posts, comments] = await Promise.all([
    fetch('/api/profile'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  return { profile, posts, comments };
};

Le résultat : L'impact de cette optimisation a été immédiat et substantiel. Nous avons observé une réduction remarquable de 50 % des temps de réponse et notre backend a démontré une résilience considérablement améliorée sous de fortes charges. Les utilisateurs n'ont plus connu de retards frustrants et nous avons constaté une diminution spectaculaire du nombre de délais d'attente du serveur. Cette amélioration a non seulement amélioré l'expérience utilisateur, mais a également permis à notre système de traiter un volume de demandes beaucoup plus élevé sans compromettre les performances.

4 : La quête MongoDB — Apprivoiser la bête des données

À mesure que notre application gagnait du terrain et que notre base d'utilisateurs augmentait de plusieurs ordres de grandeur, nous avons dû faire face à un nouvel obstacle : comment faire évoluer ses données ? Notre instance MongoDB, autrefois réactive, a commencé à s'étouffer lorsqu'elle devait traiter des millions de documents. Les requêtes qui s'exécutaient auparavant en millisecondes prenaient quelques secondes ou expiraient.

Nous avons passé quelques jours à examiner les outils d'analyse des performances de MongoDB et avons identifié le grand méchant : les requêtes non indexées. Certaines de nos requêtes les plus courantes (par exemple, les demandes de profils utilisateur) analysaient des collections entières sur lesquelles ils pouvaient utiliser des index solides.

La solution : avec les informations que nous avions en main, nous savions que tout ce que nous avions à faire était de créer des index composés sur les champs les plus demandés et que cela réduirait définitivement le temps de recherche du corps de notre base de données. Voici comment nous avons procédé en ce qui concerne les champs « nom d'utilisateur » et « e-mail ».

db.users.createIndex({ "username": 1, "email": 1 });

Le résultat : L'impact de cette optimisation a été tout simplement remarquable. Les requêtes qui prenaient auparavant jusqu'à 2 secondes à s'exécuter s'exécutaient désormais en moins de 200 millisecondes, soit une performance dix fois supérieure. Notre base de données a retrouvé sa réactivité, nous permettant de gérer un volume de trafic nettement plus élevé sans ralentissement notable.

Cependant, nous ne nous sommes pas arrêtés là. Conscients que notre trajectoire de croissance rapide allait probablement se poursuivre, nous avons pris des mesures proactives pour garantir une évolutivité à long terme. Nous avons mis en œuvre le partitionnement pour distribuer nos données sur plusieurs serveurs. Cette décision stratégique nous a permis d'évoluer horizontalement, garantissant que notre capacité à gérer les données augmente parallèlement à l'expansion de notre base d'utilisateurs.

5. Adopter les microservices – Résoudre le casse-tête de l'évolutivité

À mesure que notre base d'utilisateurs continuait de se multiplier, il devenait de plus en plus évident que nous devions non seulement faire évoluer notre infrastructure, mais aussi faire évoluer notre application afin de pouvoir évoluer en toute confiance. L’architecture monolithique nous convenait bien lorsque nous étions une petite équipe, mais avec le temps elle est devenue assez encombrante. Nous savions que nous devions franchir le pas et commencer à construire une architecture de microservices : une tâche intimidante pour toute équipe d'ingénierie, mais avec beaucoup d'évolutivité et de fiabilité.

L'un des plus gros problèmes était la communication entre les services. Les requêtes HTTP ne fonctionnent vraiment pas dans notre cas et cela nous a laissé un goulot d'étranglement supplémentaire dans le système, car un grand nombre d'opérations attendaient une réponse sans relâche et tuaient le programme si nécessaire, il y avait trop de choses à faire. À ce stade, nous avons réalisé que l'utilisation de RabbitMQ était une réponse évidente ici, nous l'avons donc appliqué sans trop réfléchir.

Voici un aperçu de la façon dont nous avons mis en œuvre cette solution :

const amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', (err, conn) => {
  conn.createChannel((err, ch) => {
    const queue = 'task_queue';
    const msg = 'Hello World';

    ch.assertQueue(queue, { durable: true });
    ch.sendToQueue(queue, Buffer.from(msg), { persistent: true });
    console.log(`Sent ${msg}`);
  });
});

Le résultat : La transition elle-même ainsi que la communication effectuée via RabbitMQ ressemblaient à de la magie de notre point de vue… et les chiffres l'ont confirmé !!! Nous sommes devenus d’heureux propriétaires de microservices faiblement couplés où chaque service pouvait évoluer de manière indépendante. Soudainement, les véritables pics de trafic sur la zone DNS concrète n'impliquaient pas la crainte d'une panne du système (car quelle que soit l'opération de service qui demande la même chose car ils sont toujours en cascade), mais ont bien fonctionné, car les parties/opérations restantes ont simplement levé la main en disant calmement : « Je peux dormir ma chérie. La maintenance est également devenue plus facile et moins problématique, tandis que l'ajout de nouvelles fonctionnalités ou de mises à jour a permis un fonctionnement plus rapide et plus sûr.

Conclusion : tracer la voie à suivre pour l'innovation future

Chaque étape de ce voyage passionnant a été une leçon, nous rappelant que le développement full-stack ne se résume pas à l'écriture de code. Il s'agit de comprendre puis de résoudre des problèmes complexes et interdépendants : qu'il s'agisse de rendre nos frontaux plus rapides et de créer des backends pour résister aux pannes, ou encore de gérer des bases de données qui évoluent à mesure que votre base d'utilisateurs explose.

À l'horizon du second semestre 2024 et au-delà, la demande croissante d'applications Web ne ralentira pas. Si nous restons concentrés sur la création d’applications évolutives, aux performances optimisées et bien architecturées, nous sommes alors en mesure de résoudre n’importe quel problème aujourd’hui – et de tirer parti des autres défis de notre avenir. Ces expériences réelles ont grandement influencé ma façon d’aborder le développement full-stack – et j’ai hâte de voir où ces influences continueront à pousser notre industrie !

Mais et vous ? Avez-vous été confronté à des obstacles similaires ou avez-vous eu de la chance avec d'autres moyens créatifs de surmonter ces problèmes ? J'aimerais entendre vos histoires ou vos idées — faites-le-moi savoir dans les commentaires ou connectez-vous avec moi !

Déclaration de sortie Cet article est reproduit à: https://dev.to/mukhilpadmanabhan/building-high-performance-full-stack-apps-with-react-nodejs-mongodb-a-journney-in-scalabilité-speed-solutions-3fk0?1 s'il y a une contrefaçon, veuillez contacter Studylability-Speed-Solutions @163.com pour en désécher.
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3