Nodefony
Framework Node.js fullstack — HTTP & WebSocket, même contexte

Nodefony peut-il partir en production ?

Trois mesures — débit à travail égal, tenue dans la durée, dimensionnement d'un pod — et ce qu'elles ne prouvent pas.

Le verdict

Débit, à travail égal
92 %
du débit d'Express équipé des mêmes middlewares
Latence p99
9,57 ms
soit +2,40 ms face à cette même référence
Fuite mémoire
aucune
20 min de trafic continu · tas sans tendance (R² 0,00)
RSS d'un pod en régime
245 MB
palier atteint, pas une rampe

La performance n'est pas le point faible de Nodefony. À travail égal — c'est-à-dire face à un Express muni des mêmes middlewares (scope ALS, CORS, en-têtes de sécurité, contrôle CSRF, corrélation traceparent, zones de pare-feu) — le framework rend 92 % du débit pour +2,40 ms de p99. L'écart avec un serveur nu ne mesure pas une lenteur : il mesure le travail que le serveur nu ne fait pas.

Sur 20 minutes de charge continue, le tas ne monte pas et le débit ne s'érode pas. Le risque résiduel d'un passage en production n'est donc pas la performance — il est nommé plus bas.

1 · Où se situe Nodefony

Même route, même charge utile, même protocole de mesure, même concurrence (c64), NODE_ENV=production. Chaque valeur est la médiane de 3 tirs, une série étant refusée au-delà de 3 % de dispersion.

req/s node:http nu Fastify Express (nu) Express équipé Nodefony 0,0 10 000,0 20 000,0 30 000,0 40 000,0 33 821,32 31 959,58 17 418,25 13 332,99 12 226,22 Débit Débit médian par framework
req/s node:http nu Fastify Express (nu) Express équipé Nodefony 0,0 10 000,0 20 000,0 30 000,0 40 000,0 33 821,32 31 959,58 17 418,25 13 332,99 12 226,22 Débit Débit médian par framework
ms node:http nu Fastify Express (nu) Express équipé Nodefony 0,0 2,0 4,0 6,0 8,0 10,0 12,0 2,93 3,09 5,69 7,17 9,57 Latence p99 (plus bas = mieux) p99 par framework
ms node:http nu Fastify Express (nu) Express équipé Nodefony 0,0 2,0 4,0 6,0 8,0 10,0 12,0 2,93 3,09 5,69 7,17 9,57 Latence p99 (plus bas = mieux) p99 par framework
node:http nu33 8211,822,932,5 %aucun framework — plancher théorique
Fastify31 9601,943,091,3 %routing + sérialisation schématisée
Express (nu)17 4183,545,691,7 %route + res.json(), rien d'autre
Express équipé13 3334,667,171,9 %ALS, CORS, en-têtes de sécurité, CSRF, traceparent, zones
Nodefony12 2264,979,570,5 %le même travail, intégré au pipeline
La ligne qui compte est Express équipé, pas Express nu : comparer un pipeline complet à un res.json() revient à comparer une berline équipée à un kart. L'écart entre les deux lignes Express (17 418 → 13 333 req/s) chiffre le prix de ces fonctionnalités, indépendamment de Nodefony.

2 · La tenue dans la durée

20 minutes de trafic continu, 18 fenêtres retenues sur 20 (les premières sont écartées : un tas monte jusqu'à son régime). Ce banc cherche une pente, pas un écart entre deux mesures bruitées.

MB minutes 30 60 90 120 150 180 210 240 270 0 5 10 15 20 25 tas (heap) MB RSS MB
MB minutes 30 60 90 120 150 180 210 240 270 0 5 10 15 20 25 tas (heap) MB RSS MB
req/s minutes 7 000 8 000 9 000 10 000 11 000 12 000 0 5 10 15 20 25
req/s minutes 7 000 8 000 9 000 10 000 11 000 12 000 0 5 10 15 20 25
Tas
46,1 → 47,4 MB
R² 0,00 — aucune droite ne décrit ces points
RSS
239 → 245 MB
palier NON confirmé — 8,2 MB/h en seconde moitié, tout près du seuil
Dérive du débit
+48,3 %
il monte — aucune érosion
p99 sur la durée
15,11 ms
médiane · max 56,12 ms
Le RSS monte, et c'est normal. Le tas, lui, est plat : aucun objet JavaScript n'est retenu. Un RSS qui croît puis se stabilise, c'est l'allocateur qui ne rend pas ses arènes au système. La distinction se fait en découpant la série : la pente s'effondre sur la seconde moitié (8,2 MB/h contre 24,3 MB/h globalement). Une vraie fuite garde sa pente jusqu'au bout — c'est ce qui la définit.

3 · Dimensionner un pod

Constantes relevées par capacity.mjs en development (profiler ACTIF ⇒ borne basse) — le profileur et le chronométrage y sont actifs, donc ces chiffres sont une borne basse : en production ils montent.

Latence à charge modérée
0.33 / 0.45 / 0.54 ms
p50 / p95 / p99
Boucle consommée
293 µs/req
temps de boucle par requête HTTP
RAM par socket WS
12.8 KB
TLS terminé par Node
Écho WebSocket
9 208 msg/s
diffusion 1→100 : 398 604 livraisons/s
Le débit par pod (12 226 req/s) vient du comparatif ci-dessus, mesuré sur une route sans base de données. Une route qui interroge PostgreSQL descend autour de 1 400–1 600 req/s sur ce même poste — mais derrière Docker Desktop, dont le surcoût de virtualisation mesuré est d'un facteur 3,7. Pour dimensionner un déploiement réel, refaire la mesure sur la cible.

Ce que ces chiffres ne disent PAS

Un rapport qui ne montre que ses bons résultats rassure au lieu d'aider à décider. Les limites de cette campagne, nommées :

  • 20 minutes ne sont pas trois jours. Ce soak élimine les fuites grossières. Une fuite lente — quelques mégaoctets par heure — resterait invisible ici et tuerait un pod au bout d'une semaine.
  • Aucune valeur ABSOLUE n'est transposable. Poste de développement, macOS, 12 cœurs logiques, base de données derrière Docker Desktop. Les comparaisons à l'intérieur de cette page sont valides (même décor des deux côtés) ; les chiffres bruts, non.
  • Le multi-pod sous trafic réel n'est pas couvert ici — fan-out entre pods, backplane Redis, cohérence des sessions. D'autres bancs le font, pas celui-ci.
  • Le démarrage à froid coûte. Un pipeline riche présente plus de fonctions distinctes à optimiser au JIT : les premiers milliers de requêtes d'un pod neuf sont plus lentes. À couvrir par une sonde de disponibilité qui attend, ou un préchauffage.
  • Aucune mesure ne remplace des heures de vol. C'est le déficit réel, et il ne se comble pas en codant : il se comble en étant déployé.

Décor et provenance

ÉlémentValeur
Node.jsv26.7.0
Cœurs logiques12
ProcesseurIntel Core i9-8950HK @ 2.90 GHz
Concurrence (wrk)c64 · 4 fils · 10s par tir
Route du comparatifhttp://127.0.0.1:5151/nodefony/test/als-test/state
Route du soakhttp://127.0.0.1:5151/nodefony/test/als-test/state
Tirs par mesure3, médiane retenue, refus au-delà de 3 % de dispersion
Fenêtres du soak20 × 60s, 2 écartée(s)
# comparatif (une ligne par pile)
BENCH_CONN=64 bash .claude/skills/nodefony-load-test/bench-frameworks/bench.sh <bare|fastify|express|express-fair> 5161
BENCH_CONN=64 BENCH_URL=http://127.0.0.1:5151/nodefony/test/als-test/state \
  bash .claude/skills/nodefony-load-test/scripts/bench-ab-mono.sh nodefony NF_WITH_DEV_MODULES=1

# tenue dans la durée
node .claude/skills/nodefony-load-test/scripts/soak.mjs --minutes 20 --window 60

# capacité
node .claude/skills/nodefony-load-test/scripts/capacity.mjs

# cette page
node .claude/skills/nodefony-load-test/scripts/prod-readiness-report.mjs