Performances des E/S côté serveur : Node vs. PHP vs. Java vs. Go

Oct 22, 2021
admin

Comprendre le modèle d’entrée/sortie (E/S) de votre application peut faire la différence entre une application qui fait face à la charge à laquelle elle est soumise, et une qui s’effrite face aux cas d’utilisation réels. Peut-être que lorsque votre application est petite et ne sert pas de charges élevées, cela a beaucoup moins d’importance. Mais lorsque la charge de trafic de votre application augmente, travailler avec le mauvais modèle d’E/S peut vous mettre dans un monde de malheur.

Et comme la plupart des situations où plusieurs approches sont possibles, il ne s’agit pas seulement de savoir laquelle est la meilleure, il s’agit de comprendre les compromis. Faisons une promenade à travers le paysage des E/S et voyons ce que nous pouvons épier.

Dans cet article, nous allons comparer Node, Java, Go, et PHP avec Apache, en discutant de la façon dont les différents langages modélisent leurs E/S, les avantages et les inconvénients de chaque modèle, et nous conclurons avec quelques benchmarks rudimentaires. Si vous vous préoccupez des performances d’E/S de votre prochaine application web, cet article est pour vous.

Les bases de l’E/S : A Quick Refresher

Pour comprendre les facteurs impliqués dans les E/S, nous devons d’abord revoir les concepts jusqu’au niveau du système d’exploitation. Bien qu’il soit peu probable que vous ayez à faire face à plusieurs de ces concepts directement, vous y faites face indirectement par le biais de l’environnement d’exécution de votre application en permanence. Et les détails ont leur importance.

Appels système

Premièrement, nous avons les appels système, qui peuvent être décrits comme suit :

  • Votre programme (dans le « user land », comme on dit) doit demander au noyau du système d’exploitation d’effectuer une opération d’E/S en son nom.
  • Un « syscall » est le moyen par lequel votre programme demande au noyau de faire quelque chose. Les spécificités de la façon dont cela est mis en œuvre varient entre les OS, mais le concept de base est le même. Il va y avoir une instruction spécifique qui transfère le contrôle de votre programme au noyau (comme un appel de fonction mais avec une sauce spéciale pour gérer cette situation). En général, les syscalls sont bloquants, ce qui signifie que votre programme attend que le noyau revienne vers votre code.
  • Le noyau effectue l’opération d’E/S sous-jacente sur le périphérique physique en question (disque, carte réseau, etc.) et répond au syscall. Dans le monde réel, le noyau pourrait avoir à faire un certain nombre de choses pour répondre à votre demande, notamment attendre que le périphérique soit prêt, mettre à jour son état interne, etc. mais en tant que développeur d’applications, vous ne vous souciez pas de cela. C’est le travail du noyau.

Appels bloquants contre appels non bloquants

Maintenant, je viens de dire plus haut que les syscalls sont bloquants, et c’est vrai dans un sens général. Cependant, certains appels sont catégorisés comme « non bloquants », ce qui signifie que le noyau prend votre demande, la place dans une file d’attente ou un tampon quelque part, puis revient immédiatement sans attendre que les E/S réelles se produisent. Donc, il ne « bloque » que pour une très brève période de temps, juste assez longtemps pour mettre votre demande en file d’attente.

Certains exemples (de syscalls Linux) pourraient aider à clarifier:- read() est un appel bloquant – vous lui passez un handle disant quel fichier et un tampon d’où livrer les données qu’il lit, et l’appel revient quand les données sont là. Notez que cela a l’avantage d’être agréable et simple.- epoll_create(), epoll_ctl() et epoll_wait() sont des appels qui, respectivement, vous permettent de créer un groupe de handles à écouter, d’ajouter/supprimer des handlers de ce groupe et ensuite de bloquer jusqu’à ce qu’il y ait une activité. Cela vous permet de contrôler efficacement un grand nombre d’opérations d’E/S avec un seul thread, mais je m’avance un peu. C’est génial si vous avez besoin de cette fonctionnalité, mais comme vous pouvez le voir, c’est certainement plus complexe à utiliser.

Il est important de comprendre l’ordre de grandeur de la différence de timing ici. Si un cœur de CPU fonctionne à 3GHz, sans entrer dans les optimisations que le CPU peut faire, il effectue 3 milliards de cycles par seconde (ou 3 cycles par nanoseconde). Un appel système non bloquant peut prendre des dizaines de cycles pour se terminer – ou « quelques nanosecondes ». Un appel qui se bloque pendant la réception d’informations sur le réseau peut prendre beaucoup plus de temps – disons par exemple 200 millisecondes (1/5 de seconde). Et disons, par exemple, que l’appel non bloquant a pris 20 nanosecondes et que l’appel bloquant a pris 200 000 000 nanosecondes. Votre processus vient d’attendre 10 millions de fois plus longtemps pour l’appel bloquant.

Le noyau fournit les moyens de faire à la fois des E/S bloquantes (« lisez à partir de cette connexion réseau et donnez-moi les données ») et des E/S non bloquantes (« dites-moi quand l’une de ces connexions réseau a de nouvelles données »). Et le mécanisme utilisé bloquera le processus appelant pendant des durées dramatiquement différentes.

Scheduling

La troisième chose qu’il est essentiel de suivre est ce qui se passe lorsque vous avez beaucoup de threads ou de processus qui commencent à bloquer.

Pour nos besoins, il n’y a pas une énorme différence entre un thread et un processus. Dans la vie réelle, la différence la plus notable liée aux performances est que, puisque les threads partagent la même mémoire et que les processus ont chacun leur propre espace mémoire, faire des processus séparés a tendance à prendre beaucoup plus de mémoire. Mais lorsque nous parlons d’ordonnancement, il s’agit en fait d’une liste de choses (threads et processus) qui doivent chacune obtenir une tranche de temps d’exécution sur les cœurs de processeur disponibles. Si vous avez 300 threads en cours d’exécution et 8 cœurs sur lesquels les faire tourner, vous devez diviser le temps de manière à ce que chacun obtienne sa part, chaque cœur fonctionnant pendant une courte période avant de passer au thread suivant. Cela se fait par le biais d’un « changement de contexte », en faisant passer le CPU de l’exécution d’un thread/processus au suivant.

Ces changements de contexte ont un coût qui leur est associé – ils prennent un certain temps. Dans certains cas rapides, il peut être inférieur à 100 nanosecondes, mais il n’est pas rare que cela prenne 1000 nanosecondes ou plus, selon les détails de mise en œuvre, la vitesse/architecture du processeur, le cache du CPU, etc.

Et plus il y a de threads (ou de processus), plus il y a de commutations de contexte. Lorsque nous parlons de milliers de threads, et de centaines de nanosecondes pour chacun d’entre eux, les choses peuvent devenir très lentes.

Cependant, les appels non bloquants disent essentiellement au noyau « ne m’appelez que lorsque vous avez de nouvelles données ou un événement sur l’une de ces connexions ». Ces appels non bloquants sont conçus pour gérer efficacement les grandes charges d’E/S et réduire le changement de contexte.

Avec moi jusqu’ici ? Parce que maintenant vient la partie amusante : Regardons ce que certains langages populaires font avec ces outils et tirons quelques conclusions sur les compromis entre la facilité d’utilisation et la performance… et d’autres trucs intéressants.

En guise de note, alors que les exemples présentés dans cet article sont triviaux (et partiels, avec seulement les bits pertinents montrés) ; l’accès aux bases de données, les systèmes de cache externes (memcache, et tout) et tout ce qui nécessite des E/S va finir par effectuer une sorte d’appel d’E/S sous le capot qui aura le même effet que les exemples simples présentés. De plus, pour les scénarios où les E/S sont décrites comme « bloquantes » (PHP, Java), la lecture et l’écriture des requêtes et réponses HTTP sont elles-mêmes des appels bloquants : Encore une fois, plus d’E/S cachées dans le système avec ses problèmes de performance à prendre en compte.

Il y a beaucoup de facteurs qui entrent dans le choix d’un langage de programmation pour un projet. Il y a même beaucoup de facteurs lorsque vous ne considérez que les performances. Mais, si vous êtes préoccupé par le fait que votre programme sera contraint principalement par les entrées/sorties, si la performance des entrées/sorties est un élément déterminant pour votre projet, voici les choses que vous devez savoir.

L’approche « Keep It Simple » : PHP

Dans les années 90, beaucoup de gens portaient des chaussures Converse et écrivaient des scripts CGI en Perl. Puis PHP est arrivé et, même si certaines personnes aiment le dénigrer, il a rendu la création de pages web dynamiques beaucoup plus facile.

Le modèle utilisé par PHP est assez simple. Il existe quelques variations mais votre serveur PHP moyen ressemble à :

Une requête HTTP arrive du navigateur d’un utilisateur et frappe votre serveur web Apache. Apache crée un processus distinct pour chaque requête, avec quelques optimisations pour les réutiliser afin de minimiser le nombre de processus qu’il doit faire (la création de processus est, relativement parlant, lente).Apache appelle PHP et lui demande d’exécuter le fichier .php approprié sur le disque.Le code PHP s’exécute et fait des appels E/S bloquants. Vous appelez file_get_contents() en PHP et sous le capot, il fait read()appels syscalls et attend les résultats.

Et bien sûr, le code réel est simplement intégré directement dans votre page, et les opérations sont bloquantes:

<?php// blocking file I/O$file_data = file_get_contents('/path/to/file.dat');// blocking network I/O$curl = curl_init('http://example.com/example-microservice');$result = curl_exec($curl);// some more blocking network I/O$result = $db->query('SELECT id, data FROM examples ORDER BY id DESC limit 100');?>

En termes d’intégration avec le système, c’est comme ça:

Pretty simple : un processus par requête. Les appels d’entrée/sortie ne font que se bloquer. Avantage ? C’est simple et ça marche. Inconvénient ? Frappez-le avec 20 000 clients en même temps et votre serveur s’enflammera. Cette approche ne s’adapte pas bien car les outils fournis par le noyau pour gérer les gros volumes d’E/S (epoll, etc.) ne sont pas utilisés. Et pour ajouter l’insulte à l’injure, l’exécution d’un processus séparé pour chaque requête a tendance à utiliser beaucoup de ressources système, en particulier la mémoire, qui est souvent la première chose dont vous manquez dans un scénario comme celui-ci.

Note : L’approche utilisée pour Ruby est très similaire à celle de PHP, et d’une manière large, générale et ondulante, ils peuvent être considérés comme les mêmes pour nos objectifs.

L’approche multithreaded : Java

Alors Java arrive, juste à peu près au moment où vous avez acheté votre premier nom de domaine et où il était cool de dire au hasard « point com » après une phrase. Et Java a le multithreading intégré dans le langage, ce qui (surtout pour l’époque où il a été créé) est assez génial.

La plupart des serveurs web Java fonctionnent en démarrant un nouveau thread d’exécution pour chaque requête qui arrive, puis dans ce thread appelle éventuellement la fonction que vous, en tant que développeur de l’application, avez écrite.

Faire des E/S dans une servlet Java a tendance à ressembler à quelque chose comme :

public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException{// blocking file I/OInputStream fileIs = new FileInputStream("/path/to/file");// blocking network I/OURLConnection urlConnection = (new URL("http://example.com/example-microservice")).openConnection();InputStream netIs = urlConnection.getInputStream();// some more blocking network I/Oout.println("...");}

Puisque notre méthode doGet ci-dessus correspond à une requête et est exécutée dans son propre thread, au lieu d’un processus séparé pour chaque requête qui nécessite sa propre mémoire, nous avons un thread séparé. Cela a quelques avantages, comme la possibilité de partager l’état, les données en cache, etc. entre les threads parce qu’ils peuvent accéder à la mémoire des autres, mais l’impact sur la façon dont il interagit avec le calendrier est encore presque identique à ce qui est fait dans l’exemple PHP précédemment. Chaque requête obtient un nouveau thread et les diverses opérations d’E/S se bloquent dans ce thread jusqu’à ce que la requête soit entièrement traitée. Les threads sont mis en commun pour minimiser le coût de leur création et de leur destruction, mais tout de même, des milliers de connexions signifient des milliers de threads, ce qui est mauvais pour le planificateur.

Un jalon important est que dans la version 1.4 Java (et une mise à niveau significative encore dans la 1.7) a gagné la capacité de faire des appels d’E/S non bloquants. La plupart des applications, web et autres, ne l’utilisent pas, mais au moins c’est disponible. Certains serveurs web Java essaient d’en tirer parti de diverses manières ; cependant, la grande majorité des applications Java déployées fonctionnent toujours comme décrit ci-dessus.

Java nous rapproche et a certainement de bonnes fonctionnalités prêtes à l’emploi pour les E/S, mais il ne résout toujours pas vraiment le problème de ce qui se passe lorsque vous avez une application fortement liée aux E/S qui se fait pilonner par plusieurs milliers de threads bloquants.

Les E/S non bloquantes comme citoyen de première classe : Node

L’enfant populaire sur le bloc quand il s’agit de meilleures E/S est Node.js. Quiconque a eu ne serait-ce qu’une brève introduction à Node s’est entendu dire qu’il est « non-bloquant » et qu’il gère efficacement les E/S. Et c’est vrai dans un sens général. Mais le diable est dans les détails et les moyens par lesquels cette sorcellerie a été réalisée importent quand il s’agit de performance.

Essentiellement, le changement de paradigme que Node met en œuvre est qu’au lieu de dire essentiellement « écrire votre code ici pour gérer la demande », ils disent plutôt « écrire le code ici pour commencer à gérer la demande. » Chaque fois que vous avez besoin de faire quelque chose qui implique des E/S, vous faites la demande et donnez une fonction de rappel que Node appellera quand ce sera fait.

Le code Node typique pour faire une opération d’E/S dans une demande va comme ceci:

http.createServer(function(request, response) {fs.readFile('/path/to/file', 'utf8', function(err, data) {response.end(data);});});

Comme vous pouvez le voir, il y a deux fonctions de rappel ici. La première est appelée quand une requête commence, et la seconde est appelée quand les données du fichier sont disponibles.

Ce que cela fait, c’est essentiellement donner à Node une opportunité de gérer efficacement les E/S entre ces rappels. Un scénario où ce serait encore plus pertinent est celui où vous faites un appel de base de données dans Node, mais je ne vais pas m’embêter avec l’exemple parce que c’est exactement le même principe : vous lancez l’appel de base de données, et donnez à Node une fonction de rappel, il effectue les opérations d’E/S séparément en utilisant des appels non bloquants et ensuite invoque votre fonction de rappel lorsque les données que vous avez demandées sont disponibles. Ce mécanisme de mise en file d’attente des appels d’E/S et de laisser Node s’en occuper, puis d’obtenir un rappel est appelé « boucle d’événement ». Et il fonctionne plutôt bien.

Il y a cependant un hic à ce modèle. Sous le capot, la raison a beaucoup plus à voir avec la façon dont le moteur JavaScript V8 (le moteur JS de Chrome qui est utilisé par Node) est mis en œuvre 1 qu’autre chose. Le code JS que vous écrivez s’exécute dans un seul thread. Réfléchissez-y un instant. Cela signifie que, même si les E/S sont effectuées à l’aide de techniques efficaces et non bloquantes, votre code JS qui effectue des opérations liées au CPU s’exécute dans un seul thread, chaque morceau de code bloquant le suivant. Un exemple courant de ce type d’opération est le passage en boucle des enregistrements d’une base de données pour les traiter d’une manière ou d’une autre avant de les envoyer au client. Voici un exemple qui montre comment cela fonctionne :

var handler = function(request, response) {connection.query('SELECT ...', function (err, rows) {if (err) { throw err };for (var i = 0; i < rows.length; i++) {// do processing on each row}response.end(...); // write out the results})};

Bien que Node gère efficacement les E/S, cette boucle for dans l’exemple ci-dessus utilise des cycles CPU à l’intérieur de votre seul et unique thread principal. Cela signifie que si vous avez 10 000 connexions, cette boucle pourrait mettre toute votre application au ralenti, selon le temps qu’elle prend. Chaque requête doit partager une tranche de temps, une à la fois, dans votre thread principal.

La prémisse sur laquelle repose tout ce concept est que les opérations d’E/S sont la partie la plus lente, il est donc plus important de les traiter efficacement, même si cela signifie faire d’autres traitements en série. C’est vrai dans certains cas, mais pas dans tous.

L’autre point est que, et bien que ce ne soit qu’une opinion, il peut être assez fatigant d’écrire un tas de callbacks imbriqués et certains soutiennent que cela rend le code significativement plus difficile à suivre. Il n’est pas rare de voir des callbacks imbriqués à quatre, cinq, voire plus de niveaux à l’intérieur du code Node.

Nous en revenons encore aux compromis. Le modèle Node fonctionne bien si votre principal problème de performance est l’entrée/sortie. Cependant, son talon d’Achille est que vous pouvez aller dans une fonction qui gère une requête HTTP et mettre du code intensif en CPU et amener chaque connexion à un crawl si vous ne faites pas attention.

Naturellement non bloquant : Go

Avant d’entrer dans la section pour Go, il est approprié pour moi de révéler que je suis un fanboy de Go. Je l’ai utilisé pour de nombreux projets et je suis ouvertement un partisan de ses avantages en termes de productivité, et je les vois dans mon travail lorsque je l’utilise.

Cela dit, regardons comment il traite les entrées/sorties. Une caractéristique clé du langage Go est qu’il contient son propre planificateur. Au lieu que chaque fil d’exécution corresponde à un seul fil d’OS, il fonctionne avec le concept de « goroutines ». Et le runtime Go peut assigner une goroutine à un thread OS et la faire exécuter, ou la suspendre et ne pas l’associer à un thread OS, en fonction de ce que fait cette goroutine. Chaque requête qui arrive du serveur HTTP de Go est traitée dans une goroutine séparée.

Le diagramme de la façon dont le planificateur fonctionne ressemble à ceci:

Sous le capot, cela est mis en œuvre par divers points dans le runtime de Go qui mettent en œuvre l’appel d’E/S en faisant la demande d’écriture/lecture/connexion/etc, mettent la goroutine actuelle en sommeil, avec l’information de réveiller la goroutine lorsque d’autres actions peuvent être entreprises.

En effet, le runtime Go fait quelque chose de pas terriblement différent de ce que fait Node, sauf que le mécanisme de rappel est intégré dans l’implémentation de l’appel E/S et interagit avec l’ordonnanceur automatiquement. Il ne souffre pas non plus de la restriction d’avoir tout votre code de gestionnaire exécuté dans le même thread, Go mappera automatiquement vos Goroutines à autant de threads OS qu’il juge appropriés en fonction de la logique de son ordonnanceur. Le résultat est un code comme celui-ci :

func ServeHTTP(w http.ResponseWriter, r *http.Request) {// the underlying network call here is non-blockingrows, err := db.Query("SELECT ...")for _, row := range rows {// do something with the rows,// each request in its own goroutine}w.Write(...) // write the response, also non-blocking}

Comme vous pouvez le voir ci-dessus, la structure de code de base de ce que nous faisons ressemble à celle des approches les plus simplistes, tout en réalisant des E/S non bloquantes sous le capot.

Dans la plupart des cas, cela finit par être « le meilleur des deux mondes ». Les E/S non bloquantes sont utilisées pour toutes les choses importantes, mais votre code a l’air d’être bloquant et a donc tendance à être plus simple à comprendre et à maintenir. L’interaction entre l’ordonnanceur de Go et l’ordonnanceur du système d’exploitation gère le reste. Ce n’est pas de la magie complète, et si vous construisez un grand système, cela vaut la peine de prendre le temps de comprendre plus en détail comment cela fonctionne ; mais en même temps, l’environnement que vous obtenez « prêt à l’emploi » fonctionne et évolue assez bien.

Go peut avoir ses défauts, mais de manière générale, la façon dont il gère les E/S n’en fait pas partie.

Mensonges, maudits mensonges et benchmarks

Il est difficile de donner des timings exacts sur la commutation de contexte impliquée avec ces différents modèles. Je pourrais aussi argumenter que c’est moins utile pour vous. Donc, à la place, je vais vous donner quelques repères de base qui comparent les performances globales du serveur HTTP de ces environnements de serveur. Gardez à l’esprit que de nombreux facteurs sont impliqués dans les performances de l’ensemble du chemin de requête/réponse HTTP de bout en bout, et les chiffres présentés ici ne sont que des échantillons que j’ai rassemblés pour donner une comparaison de base.

Pour chacun de ces environnements, j’ai écrit le code approprié pour lire un fichier de 64k avec des octets aléatoires, exécuter un hachage SHA-256 sur celui-ci N nombre de fois (N étant spécifié dans la chaîne de requête de l’URL, par exemple, .../test.php?n=100) et imprimer le hachage résultant en hex. J’ai choisi cela parce que c’est une façon très simple d’exécuter les mêmes benchmarks avec quelques entrées/sorties cohérentes et une façon contrôlée d’augmenter l’utilisation du CPU.

Voir ces notes de benchmark pour un peu plus de détails sur les environnements utilisés.

D’abord, regardons quelques exemples à faible concurrence. Exécuter 2000 itérations avec 300 requêtes concurrentes et seulement un hachage par requête (N=1) nous donne ceci :

Les temps sont le nombre moyen de millisecondes pour compléter une requête parmi toutes les requêtes concurrentes. Plus c’est bas, mieux c’est.

Il est difficile de tirer une conclusion à partir de ce seul graphique, mais il me semble qu’à ce volume de connexion et de calcul, nous voyons des temps qui ont plus à voir avec l’exécution générale des langages eux-mêmes, bien plus qu’avec les E/S. Notez que les langages qui sont considérés comme des « langages de script » (typage lâche, interprétation dynamique) se comportent le plus lentement.

Mais que se passe-t-il si nous augmentons N à 1000, toujours avec 300 requêtes concurrentes – la même charge mais 100x plus d’itérations de hachage (significativement plus de charge CPU):

Les temps sont le nombre moyen de millisecondes pour compléter une requête parmi toutes les requêtes concurrentes. Plus c’est bas, mieux c’est.

Tout à coup, les performances de Node chutent de manière significative, parce que les opérations gourmandes en CPU de chaque requête se bloquent les unes les autres. Et de manière intéressante, les performances de PHP s’améliorent considérablement (par rapport aux autres) et battent Java dans ce test. (Il convient de noter qu’en PHP, l’implémentation SHA-256 est écrite en C et que le chemin d’exécution passe beaucoup plus de temps dans cette boucle, puisque nous faisons maintenant 1000 itérations de hachage).

Maintenant, essayons 5000 connexions simultanées (avec N=1) – ou aussi proche de cela que j’ai pu le faire. Malheureusement, pour la plupart de ces environnements, le taux d’échec n’était pas négligeable. Pour ce graphique, nous allons regarder le nombre total de demandes par seconde. Plus c’est élevé, mieux c’est:

Nombre total de demandes par seconde. Plus c’est élevé, mieux c’est.

Et l’image semble bien différente. C’est une supposition, mais il semble qu’à un volume de connexion élevé, le surcoût par connexion impliqué dans le spawning de nouveaux processus et la mémoire supplémentaire qui y est associée dans PHP+Apache semble devenir un facteur dominant et gâche les performances de PHP. Clairement, Go est le gagnant ici, suivi par Java, Node et enfin PHP.

Bien que les facteurs impliqués dans votre débit global soient nombreux et varient aussi largement d’une application à l’autre, plus vous comprenez les entrailles de ce qui se passe sous le capot et les compromis impliqués, mieux vous vous porterez.

En résumé

Avec tout ce qui précède, il est assez clair qu’à mesure que les langages ont évolué, les solutions pour traiter les applications à grande échelle qui font beaucoup d’E/S ont évolué avec elle.

Pour être juste, PHP et Java, malgré les descriptions de cet article, ont des implémentations d’E/S non bloquantes disponibles pour une utilisation dans les applications web. Mais elles ne sont pas aussi courantes que les approches décrites ci-dessus, et les frais opérationnels liés à la maintenance des serveurs utilisant de telles approches devraient être pris en compte. Sans compter que votre code doit être structuré de manière à fonctionner avec de tels environnements ; votre application web PHP ou Java « normale » ne fonctionnera généralement pas sans modifications importantes dans un tel environnement.

À titre de comparaison, si nous considérons quelques facteurs importants qui affectent les performances ainsi que la facilité d’utilisation, nous obtenons ceci :

.

.

Langage Threads vs. Processus Non-bloquantes Facilité d’utilisation
PHP Processus Non
Java Threads Disponible Requiert des Callbacks
Node.js Les fils Oui Demande des rappels
Aller Les fils (Goroutines) Oui No Callbacks Needed

Les threads vont généralement être beaucoup plus efficaces en mémoire que les processus, puisqu’ils partagent le même espace mémoire alors que les processus ne le font pas. En combinant cela avec les facteurs liés aux E/S non bloquantes, nous pouvons voir qu’au moins avec les facteurs considérés ci-dessus, à mesure que nous descendons dans la liste, la configuration générale liée aux E/S s’améliore. Donc, si je devais choisir un gagnant dans le concours ci-dessus, ce serait certainement Go.

Même ainsi, dans la pratique, le choix d’un environnement dans lequel construire votre application est étroitement lié à la familiarité de votre équipe avec ledit environnement, et à la productivité globale que vous pouvez atteindre avec lui. Ainsi, il n’est pas forcément judicieux pour toutes les équipes de se jeter à l’eau et de commencer à développer des applications et des services Web en Node ou Go. En effet, la recherche de développeurs ou la familiarité de votre équipe interne est souvent citée comme la principale raison de ne pas utiliser un autre langage et/ou environnement. Cela dit, les temps ont changé au cours des quinze dernières années, beaucoup.

J’espère que ce qui précède aide à peindre une image plus claire de ce qui se passe sous le capot et vous donne quelques idées sur la façon de traiter l’évolutivité dans le monde réel pour votre application. Bonne entrée et sortie!

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.