Skip to main content
backend4 janvier 202614 min de lecture

Cloud Functions vs backend traditionnel : quand le serverless a du sens

Une comparaison concrète entre fonctions serverless et backends traditionnels — couvrant les coûts, la latence, l'expérience développeur, et quand chaque approche l'emporte.

serverlesscloud-functionsbackend
Cloud Functions vs backend traditionnel : quand le serverless a du sens

Chaque nouveau projet que je démarre impose la même décision : est-ce que je lance un serveur traditionnel ou est-ce que j'écris des cloud functions ? Après avoir livré des systèmes de production des deux côtés — Firebase Functions pour les workflows événementiels, AWS Lambda pour des endpoints API, et des serveurs Express/Fastify pour tout le reste — j'ai une opinion assez nuancée sur quand chaque approche l'emporte réellement. La réponse, comme on pouvait s'y attendre, est "ça dépend", mais les détails de ce dont cela dépend valent la peine d'être explorés en profondeur.

Définir les deux approches

Avant de comparer, permettez-moi d'être précis sur ce que j'entends par chacune.

Backend traditionnel désigne un processus serveur long — une application Node.js Express, un serveur Python FastAPI, un serveur HTTP Go — déployé sur une VM, un conteneur, ou une plateforme de calcul managée. Le processus démarre, reste en fonctionnement, et traite les requêtes à mesure qu'elles arrivent. Les cibles de déploiement incluent EC2, Cloud Run, ECS, Railway, Fly.io, ou un VPS nu.

Cloud functions (fonctions serverless) sont des gestionnaires de fonctions individuels qui s'exécutent en réponse à des événements. La plateforme gère entièrement l'infrastructure de calcul. Votre code s'exécute, retourne une réponse, et l'environnement d'exécution peut persister ou non pour la prochaine invocation. Les fournisseurs incluent AWS Lambda, Google Cloud Functions, Firebase Functions, Azure Functions, et Cloudflare Workers.

La distinction ne porte pas sur le langage ou le framework — elle porte sur le modèle d'exécution. Un backend traditionnel possède son cycle de vie de processus. Une cloud function non.

La réalité du cold start

Les cold starts sont la limitation serverless la plus discutée, et la réalité en 2026 est plus nuancée que ne le suggère le discours ambiant.

Ce qui se passe réellement pendant un cold start

Quand une cloud function n'a pas été invoquée récemment (ou quand les invocations concurrentes dépassent les instances warm disponibles), la plateforme doit :

  1. Provisionner un nouvel environnement d'exécution (microVM ou conteneur)
  2. Télécharger et extraire votre package de déploiement
  3. Initialiser le runtime (Node.js, Python, etc.)
  4. Exécuter votre code d'initialisation (imports de modules, configuration du SDK, connexions DB)
  5. Exécuter le gestionnaire de fonction réel

Les étapes 1-3 sont de l'overhead plateforme. L'étape 4 est celle où vos choix de code importent énormément.

Chiffres de cold start en pratique

Voici les temps de cold start que j'ai mesurés sur différentes plateformes et configurations dans des projets réels :

Plateforme Runtime Taille du bundle Cold Start
AWS Lambda Node.js 20 5 Mo 250-400ms
AWS Lambda Node.js 20 50 Mo 800-1200ms
AWS Lambda Python 3.12 10 Mo 400-600ms
Firebase Functions v2 Node.js 20 15 Mo 600-1000ms
Cloudflare Workers JavaScript 1 Mo 0-5ms
Google Cloud Functions v2 Node.js 20 10 Mo 300-500ms

Cloudflare Workers sont l'exception car ils utilisent des isolats V8 au lieu de conteneurs, ce qui élimine complètement la pénalité de démarrage du conteneur. Le compromis est un environnement d'exécution plus contraint.

Atténuer les cold starts

Plusieurs stratégies aident réellement :

Concurrence provisionnée (Lambda) : Maintient N instances warm en permanence. Coûte 0,0000041667 $ par Go-seconde de capacité provisionnée. Pour une fonction de 256 Mo, cela représente environ 3,20 $/mois par instance warm. Ça vaut le coup pour les endpoints sensibles à la latence.

Instances minimales (Cloud Run, Firebase Functions v2) : Même concept, nom différent. Cloud Run facture les instances inactives à un tarif réduit.

Réduction de la taille du bundle : C'est ce qui a le meilleur ratio impact/effort. Le tree-shaking, l'exclusion des dépendances de développement, et l'utilisation de clients SDK plus légers réduisent drastiquement le temps d'initialisation.

// Bad: imports the entire AWS SDK (adds 40MB+ to bundle)
import AWS from 'aws-sdk';

// Good: imports only the S3 client (adds ~3MB)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';

Initialisation différée : N'établissez pas de connexions de base de données et ne chargez pas de ressources lourdes au niveau du module. Initialisez-les à la première invocation et réutilisez-les entre les invocations warm.

import { Pool } from 'pg';

let pool: Pool | null = null;

function getPool(): Pool {
  if (!pool) {
    pool = new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 1, // Single connection per Lambda instance
    });
  }
  return pool;
}

export async function handler(event: APIGatewayEvent) {
  const db = getPool();
  const result = await db.query('SELECT * FROM users WHERE id = $1', [event.pathParameters?.id]);
  return { statusCode: 200, body: JSON.stringify(result.rows[0]) };
}

Comparaison des coûts à différentes échelles

Le coût est là où la conversation devient intéressante, car l'économie s'inverse à mesure que le trafic augmente.

Faible trafic (1 000 - 50 000 requêtes/jour)

À cette échelle, le serverless est presque toujours moins cher. Beaucoup de startups et de projets personnels sont ici.

Coût Lambda pour 30 000 requêtes/jour, durée moyenne 200ms, 256 Mo de mémoire :

  • Frais de requêtes : 900K requêtes/mois x 0,20 $/million = 0,18 $
  • Frais de calcul : 900K x 0,2s x 0,25 Go x 0,0000166667 $ = 0,75 $
  • Total : ~1 $/mois

Serveur traditionnel équivalent (t4g.small sur AWS) :

  • À la demande : 12,26 $/mois
  • Avec Savings Plan 1 an : ~8 $/mois

À faible trafic, vous payez un serveur toujours actif qui est inactif à 95 %.

Trafic moyen (100 000 - 1 000 000 requêtes/jour)

C'est la zone de croisement où la comparaison devient serrée.

Coût Lambda pour 500 000 requêtes/jour, durée moyenne 200ms, 256 Mo :

  • Frais de requêtes : 15M/mois x 0,20 $/million = 3,00 $
  • Frais de calcul : 15M x 0,2s x 0,25 Go x 0,0000166667 $ = 12,50 $
  • Total : ~15,50 $/mois

t4g.medium (2 vCPU, 4 Go RAM) :

  • À la demande : 24,53 $/mois
  • Avec Savings Plan : ~16 $/mois

Les coûts sont comparables, mais le serveur traditionnel gère ce trafic avec une marge significative. Si votre trafic est stable, le serveur est légèrement moins cher. Si votre trafic est en dents de scie (jours ouvrables, pics événementiels), la facturation par invocation de Lambda signifie que vous ne payez pas pour les périodes calmes.

Fort trafic (5 000 000+ requêtes/jour)

À cette échelle, le serverless est presque toujours plus cher pour les charges de travail soutenues.

Coût Lambda pour 5 000 000 requêtes/jour, durée moyenne 200ms, 512 Mo :

  • Frais de requêtes : 150M/mois x 0,20 $/million = 30 $
  • Frais de calcul : 150M x 0,2s x 0,5 Go x 0,0000166667 $ = 250 $
  • Total : ~280 $/mois

c6g.large (2 vCPU, 4 Go RAM) avec auto-scaling group :

  • 2 instances avec Savings Plan : ~60 $/mois
  • Avec load balancer : ~16 $/mois
  • Total : ~76 $/mois

L'approche traditionnelle est 3,7 fois moins chère à cette échelle, et l'écart se creuse avec l'augmentation du trafic.

Coûts cachés à prendre en compte

La facturation Lambda n'est pas le tableau complet. Ajoutez API Gateway (1 à 3,50 $ par million de requêtes), CloudWatch Logs (0,50 $/Go ingéré), et X-Ray tracing si vous l'utilisez. Côté traditionnel, ajoutez les coûts de load balancer, la surveillance, et le temps opérationnel pour gérer les instances et les déploiements.

Différences d'expérience développeur

DX serverless

Avantages :

  • Pas d'infrastructure à gérer (pas de correctifs, pas de configuration de mise à l'échelle)
  • Le déploiement par fonction signifie un rayon d'impact plus réduit
  • Développement local avec émulateurs (Firebase Emulator Suite, SAM CLI, Serverless Framework offline)
  • Observabilité intégrée via la journalisation et le traçage de la plateforme

Points faibles :

  • Le développement local ne correspond jamais parfaitement au comportement en production
  • Le débogage d'appels distribués de fonction à fonction est plus difficile que de parcourir un monolithe
  • Les limites de taille de déploiement (Lambda : 250 Mo décompressé) contraignent les choix de dépendances
  • La configuration IAM et des permissions est verbeuse et facile à mal configurer
  • Les cold starts rendent les boucles d'itération de développement plus lentes lors des tests sur des fonctions déployées

DX backend traditionnel

Avantages :

  • L'environnement de développement local est l'environnement de production (même processus, même état)
  • Le débogage est simple — attacher un débogueur, poser des points d'arrêt, parcourir le code
  • Pas de limites de taille de déploiement
  • Les patterns middleware, les hooks de cycle de vie de requête, et la gestion globale des erreurs sont naturels
  • Le support WebSocket, les processus long-running, et les jobs en arrière-plan fonctionnent sans services supplémentaires

Points faibles :

  • Vous possédez le cycle de vie de l'infrastructure (mises à jour, mise à l'échelle, health checks)
  • Les déploiements nécessitent une orchestration (rolling updates, périodes de grâce des health checks)
  • La mise à l'échelle nécessite de la configuration (auto-scaling groups, orchestration de conteneurs)
  • La surveillance et la journalisation nécessitent une configuration explicite

L'écart des frameworks se réduit

Des frameworks comme SST (Serverless Stack) et Architect ont considérablement amélioré la DX serverless. SST en particulier fournit un mode de développement "Live Lambda" où votre code exécuté localement gère les invocations Lambda en temps réel, éliminant complètement le cycle déploiement-test.

Côté traditionnel, des plateformes comme Railway, Fly.io et Render ont simplifié le déploiement à un git push. La charge opérationnelle de l'exécution d'un backend traditionnel a considérablement diminué.

Quand le serverless l'emporte

Charges de travail événementielles

Si votre code s'exécute en réponse à des événements — uploads de fichiers, changements de base de données, messages de file d'attente, tâches planifiées — le serverless est le choix naturel. Vous ne maintenez pas de boucle de polling ou de processus listener. La plateforme déclenche votre code exactement quand c'est nécessaire.

// Firebase Functions: triggered on Firestore document creation
import { onDocumentCreated } from 'firebase-functions/v2/firestore';

export const onOrderCreated = onDocumentCreated('orders/{orderId}', async (event) => {
  const order = event.data?.data();
  if (!order) return;

  // Send confirmation email
  await sendEmail(order.customerEmail, {
    template: 'order-confirmation',
    data: { orderId: event.params.orderId, items: order.items },
  });

  // Update inventory
  await updateInventory(order.items);

  // Notify restaurant
  await sendPushNotification(order.restaurantId, {
    title: 'New Order',
    body: `Order #${event.params.orderId} received`,
  });
});

Trafic sporadique ou imprévisible

Un outil interne qui reçoit 50 requêtes le lundi matin et 2 requêtes le reste de la semaine. Un récepteur de webhook qui traite les événements d'une API tierce. Un générateur de rapports planifié qui s'exécute 30 secondes une fois par jour. Ces charges de travail ont un coût quasi nul en serverless et gaspillent de l'argent sur une infrastructure toujours active.

Endpoints API à trafic faible à moyen

Pour l'API MVP d'une startup servant quelques milliers de requêtes par jour, le serverless fournit un backend fonctionnel avec un minimum d'overhead opérationnel et un coût quasi nul. Vous pouvez vous concentrer entièrement sur la logique métier.

Traitement de données en parallèle

Besoin de traiter 10 000 images, transcoder 500 vidéos, ou exécuter des analyses sur un grand jeu de données ? Distribuez sur des milliers d'invocations Lambda concurrentes, traitez en parallèle, et ne payez que pour le temps de calcul utilisé. Atteindre le même parallélisme avec des serveurs traditionnels nécessite de provisionner (et de payer) cette capacité de pointe.

Quand les backends traditionnels l'emportent

Connexions persistantes

Les connexions WebSocket, les Server-Sent Events, les streams gRPC, et le long-polling nécessitent tous une connexion persistante entre le client et le serveur. Les fonctions Lambda ont une limite d'exécution de 15 minutes et ne sont pas conçues pour des connexions de longue durée.

Vous pouvez contourner cela avec les API WebSocket d'API Gateway ou AWS IoT Core, mais la complexité et le coût dépassent souvent un simple serveur WebSocket sur Cloud Run ou une plateforme de conteneurs.

Pipelines de requêtes complexes

Quand une requête passe par l'authentification, le rate limiting, la validation d'entrée, la logique métier, les opérations de base de données, les mises à jour de cache, l'émission d'événements et le formatage de réponse — le pattern de pipeline middleware d'Express/Fastify/Koa est ergonomiquement supérieur au pattern de gestionnaire plat des cloud functions.

// Traditional: clean middleware pipeline
app.use(cors());
app.use(helmet());
app.use(rateLimiter);
app.use(authenticate);
app.use('/api/v1', apiRouter);
app.use(errorHandler);

Reproduire ceci dans Lambda nécessite soit un framework comme Middy (qui ajoute du poids et de la complexité) soit de dupliquer le code de configuration entre les fonctions.

Opérations avec état

Si votre application maintient un état en mémoire — un cache, un pool de connexions, un compteur de rate limiter, des données de session — un serveur traditionnel gère cela naturellement. Les fonctions Lambda peuvent réutiliser l'état entre les invocations warm, mais vous ne pouvez pas compter dessus. Tout état qui doit persister nécessite un service externe (Redis, DynamoDB), ce qui ajoute de la latence et du coût.

Exigences strictes de latence

Pour les API où chaque milliseconde compte — enchères en temps réel, serveurs de jeux, transactions financières — la latence imprévisible introduite par les cold starts est inacceptable, même avec la concurrence provisionnée. Un processus serveur bien optimisé avec un pool de connexions et des caches warm fournit des temps de réponse constants sous 10ms que le serverless ne peut pas garantir.

L'approche hybride

En pratique, la plupart des systèmes de production que je construis finissent par être hybrides. L'API principale fonctionne sur un backend traditionnel (Cloud Run ou une plateforme de conteneurs), tandis que les charges de travail auxiliaires fonctionnent sur des cloud functions.

Une architecture hybride pratique

Client
  │
  ├─→ API Gateway / Load Balancer
  │     └─→ Cloud Run (main API)
  │           ├─→ PostgreSQL (Cloud SQL)
  │           ├─→ Redis (Memorystore)
  │           └─→ Pub/Sub (event bus)
  │
  └─→ Cloud Functions (event handlers)
        ├─→ Image processing (on upload)
        ├─→ Email sending (on order creation)
        ├─→ Analytics aggregation (scheduled)
        └─→ Webhook processing (HTTP trigger)

L'API principale gère le trafic synchrone requête-réponse avec des connexions de base de données persistantes, du cache en mémoire et une latence constante. Les cloud functions gèrent les charges de travail asynchrones et événementielles où les cold starts n'importent pas car l'utilisateur n'attend pas de réponse.

Stratégie de migration : du traditionnel au serverless

Si vous avez un backend traditionnel existant et voulez adopter le serverless de manière sélective :

  1. Identifiez les opérations événementielles actuellement exécutées comme jobs en arrière-plan, tâches cron, ou consommateurs de files d'attente. Ce sont les plus faciles à migrer.
  2. Extrayez les endpoints indépendants qui ne partagent pas d'état avec d'autres endpoints. Les endpoints de health check, les récepteurs de webhook, et les API utilitaires sont de bons candidats.
  3. Gardez l'API principale traditionnelle sauf si vous avez une raison impérieuse de la décomposer. L'overhead opérationnel de la gestion de 50 fonctions Lambda dépasse souvent celui de la gestion d'un seul conteneur.
  4. Utilisez un bus d'événements (Pub/Sub, EventBridge, SQS) comme couche d'intégration entre votre API traditionnelle et les fonctions serverless. L'API publie des événements ; les fonctions s'abonnent et réagissent.

Stratégie de migration : du serverless au traditionnel

Aller dans l'autre direction est parfois nécessaire quand une API serverless dépasse son architecture :

  1. Ne réécrivez pas tout d'un coup. Commencez par consolider les fonctions à plus fort trafic dans un seul serveur API.
  2. Gardez les fonctions événementielles comme fonctions. Ne migrez que les gestionnaires requête-réponse.
  3. Utilisez les mêmes routes API. Pointez votre API Gateway/load balancer vers le nouveau serveur pour les routes migrées et continuez de router vers Lambda pour le reste.
  4. Migrez d'abord les connexions de base de données. Le plus gros gain de performance est de passer d'une configuration de connexion par invocation à un pool de connexions persistant.

Notes spécifiques aux plateformes

Firebase Functions (v2)

Firebase Functions v2 fonctionne sur Cloud Run en coulisses, ce qui lui donne des avantages significatifs par rapport à la v1 : concurrence configurable (plusieurs requêtes par instance), instances minimales, timeouts plus longs (jusqu'à 60 minutes), et allocations mémoire plus grandes. Si vous êtes sur Firebase Functions v1, la mise à jour vers la v2 vaut l'effort de migration.

Le Firebase Emulator Suite offre la meilleure expérience de développement local dans l'espace serverless. Il émule Firestore, Auth, Storage et Functions dans un seul environnement local, avec rechargement à chaud sur les modifications de code.

AWS Lambda

Lambda a l'écosystème d'intégration le plus profond — des déclencheurs depuis pratiquement tous les services AWS, des layers pour le code partagé, et SnapStart pour les charges de travail Java. La fonctionnalité function URL élimine le besoin d'API Gateway pour les cas d'utilisation simples, économisant 3,50 $ par million de requêtes.

Lambda@Edge et CloudFront Functions sont puissants pour la manipulation requête/réponse au niveau CDN, mais l'expérience de développement et de débogage est significativement pire que Lambda standard.

Cloud Run

Cloud Run brouille la frontière entre serverless et traditionnel. Il exécute des conteneurs (tout langage, tout framework), se réduit à zéro, et supporte la concurrence (plusieurs requêtes par instance). Il est serverless dans son modèle de facturation mais traditionnel dans son modèle d'exécution. Pour les équipes qui veulent l'économie serverless sans réécrire leur code en gestionnaires de fonctions, Cloud Run est souvent la bonne réponse.

Prendre la décision

Voici le cadre de décision que j'utilise :

  1. La charge de travail est-elle événementielle et asynchrone ? Serverless.
  2. Le trafic est-il faible et imprévisible ? Serverless.
  3. Avez-vous besoin de connexions persistantes ou d'état en mémoire ? Traditionnel.
  4. Une latence faible et constante est-elle critique ? Traditionnel.
  5. L'équipe est-elle petite et réticente aux opérations ? Serverless (ou Cloud Run).
  6. Le trafic est-il élevé et stable ? Traditionnel (moins cher).
  7. C'est un mélange de tout cela ? Hybride.

La pire décision est de traiter cela comme un choix tout ou rien. Utilisez le bon outil pour chaque charge de travail, connectez-les via des interfaces bien définies, et vous obtenez les avantages des deux sans vous engager entièrement dans l'un ou l'autre.

DU

Danil Ulmashev

Full Stack Developer

Intéressé par une collaboration ?