← Tous les cours / Fondamentaux de l'architecture backend Cours
00 — VUE D'ENSEMBLE

Penser en systèmes, pas en code

À l'heure où des machines écrivent le code à notre place, comprendre <em>ce qu'on construit</em> compte plus que jamais. On va le faire en bâtissant un clone de Google Drive, brique par brique.

L'ingénierie logicielle change à toute vitesse : des machines écrivent désormais du code pour nous, et ça marche étonnamment bien. Mais à mesure que la production accélère, les pannes en cascade sur les grandes plateformes se multiplient. C'est le bon moment pour ralentir et se reposer la question la plus importante : qu'est-ce qu'on construit, au juste ? Pas commentquoi, et pourquoi.

L'architecture logicielle
L'ensemble des décisions structurantes qui rendent un système plus — ou moins — facile à faire évoluer. Une bonne architecture n'est pas une fin en soi : elle existe pour que le logiciel reste rapide et bon marché à étendre demain. Une mauvaise rend chaque nouvelle fonctionnalité plus lente et plus coûteuse à livrer.

Architecturer, ce n'est pas empiler des technologies à la mode. C'est penser en systèmes : voir les pièces, leurs responsabilités, et ce qui se passe quand l'une tombe ou quand la charge double. Pour rendre ça concret, on va construire ensemble un clone de Google Drive — uploader et télécharger des fichiers — en partant du plus simple possible, puis en le faisant grandir, décision après décision.

La règle du jeu : chaque étape ne s'ajoute que si elle résout un vrai problème de l'étape précédente. Pas de sur-ingénierie, pas de « au cas où ». On verra à chaque fois le pour et le contre, parce que la bonne architecture dépend toujours de l'endroit où en est ton projet.

1 serveur
+ base
+ scaling
microservices
distribué
💡 Le fil conducteur : chaque section part d'une douleur concrète de Google Drive (« le serveur tombe », « les uploads rament », « 500 personnes ouvrent le même fichier »), introduit une seule brique pour la résoudre, et pèse ce qu'elle coûte. À la fin, tu auras la carte mentale d'un système qui passe à l'échelle — et le réflexe de ne l'appliquer que quand c'est justifié.
01 — SERVEUR & BASE

Découpler le serveur de ses données

Tout commence par un serveur qui contient tout. Ça marche — jusqu'à la première panne, et jusqu'au jour où tu veux un deuxième serveur. Deux questions qui changent tout.

Au commencement, il y a un serveur. Tes utilisateurs lui envoient des requêtes, il leur répond, et toutes les données vivent à l'intérieur de lui — la liste des fichiers, leur emplacement, tout. C'est ainsi que démarrent la plupart des programmes, et honnêtement, ça fonctionne très bien… un temps.

Puis deux questions arrivent, et elles vont tout déterminer. Que se passe-t-il si le serveur meurt ? Comme les données sont dans le serveur, elles meurent avec lui. Et si tu veux un deuxième serveur ? Chacun aura sa propre copie des données, qui divergeront dès la première écriture. Un utilisateur écrit sur la machine A ; la machine B n'en sait rien.

⚠️ Données couplées à la machine = ni résilience, ni passage à l'échelle. Tant que l'état vit dans le serveur, tu ne peux ni le perdre sans tout perdre, ni le dupliquer sans le désynchroniser.

La solution, tu la connais sans doute : ajouter une base de données. Mais l'important n'est pas « ajouter une base » — c'est découpler. Le serveur devient sans état (stateless) : il ne retient rien entre deux requêtes, il va chercher et ranger les données dans une base partagée. N'importe quelle instance peut alors servir n'importe quel utilisateur, puisque la vérité est ailleurs.

Serveur A
Base partagée
Serveur B
Séparation des responsabilités
Le serveur se préoccupe de servir l'utilisateur ; la base se préoccupe de stocker les données. Si un serveur disparaît, la base s'en moque — elle ne le connaît même pas. C'est le tout premier principe d'architecture, et on le retrouvera à chaque étage, à des échelles de plus en plus grandes.

Ce stockage relationnel — tables utilisateurs, tables fichiers, relations entre elles — a ses propres profondeurs (transactions, isolation, index). Si tu veux creuser ce qui se passe sous le SELECT, le cours Transactions SQL & Isolation et Index B-tree & le problème N+1 sont là pour ça.

Le simulateur ci-dessous rejoue le problème. Écris sur le serveur 1, puis lis depuis le serveur 2 — d'abord avec les données couplées (désynchro), puis avec une base découplée (cohérent).

Bascule le mode, écris sur un serveur, lis depuis l'autre :

02 — SCALING & LOAD BALANCER

Encaisser la charge : plus de serveurs

L'appli cartonne, mais elle rame. La réponse tient en un mot — scaler — et en une nouvelle pièce posée devant tes serveurs : le répartiteur de charge.

L'application grimpe dans le store, le trafic afflue — et les retours tombent : « c'est lent ». Un seul serveur n'encaisse plus toutes les requêtes. Heureusement, on l'a rendu sans état à l'étape précédente : on peut donc en ajouter un deuxième sans rien casser. Mais dès qu'il y a plus d'un serveur, une question surgit : qui décide où va chaque requête ?

Sans arbitre, tous les utilisateurs pourraient se ruer sur la même machine pendant que l'autre se tourne les pouces. Il faut une pièce au milieu qui distribue le trafic vers une machine en bonne santé : c'est le répartiteur de charge (load balancer).

Utilisateurs
Load balancer
Serveur 1 · 2 · 3
Les stratégies de répartition
Round robin : on alterne séquentiellement, chaque serveur reçoit son tour. Least connections : on envoie vers le serveur qui a le moins de connexions actives. Sticky sessions : un même utilisateur revient toujours sur le même serveur (utile s'il y a un état local à conserver). Un bon load balancer ajoute des health checks pour éviter les machines malades, et peut même router selon le chemin de l'URL (POST /upload → serveur de fichiers).

Le round robin est déjà très correct, mais il a une faille : tous les tours ne se valent pas. Si l'utilisateur A uploade un film de 20 Go pendant que l'utilisateur B télécharge une vignette, ce n'est pas la même charge du tout. Un load balancer plus malin, guidé par les health checks, enverra le gros travail là où il y a de la marge.

Deux façons de grandir

Scaling horizontalScaling vertical
PrincipeAjouter des machinesGrossir la machine (plus de RAM/CPU)
LimiteCoordination, état partagéPlafond physique d'une seule machine
RésilienceUne tombe, les autres tiennentUn seul point de défaillance

En vrai, on combine les deux : des machines costaudes et plusieurs d'entre elles. Et comme chaque machine coûte de l'argent, on vise le minimum suffisant : l'auto-scaling démarre à deux instances, puis en provisionne davantage (jusqu'à dix, par exemple) quand le trafic l'exige, et les retire quand il retombe.

💡 Le load balancer devient un nouveau bloc de construction. Une fois qu'on sait distribuer le trafic vers des machines saines, on peut scaler « à l'infini » — tant qu'on peut payer. C'est la première vraie brique d'un système distribué.

L'état : le vrai défi du scaling horizontal

Souviens-toi du principe de la section précédente : des données couplées à la machine empêchent de scaler. Dès qu'il y a plusieurs serveurs, ce principe se généralise à tout état local. Deux cas reviennent sans cesse, et il faut les traiter explicitement : les sessions et les fichiers.

Les sessions

À la connexion, on stocke souvent la session de l'utilisateur — état de connexion, panier, préférences — en mémoire du serveur. Avec une seule machine, impeccable. Mais derrière un load balancer, sa requête suivante peut tomber sur un autre serveur, qui n'a jamais entendu parler de lui : la session est perdue, l'utilisateur est déconnecté sans raison apparente.

SolutionPrincipeLimite
Sticky sessions Le load balancer renvoie toujours l'utilisateur sur le même serveur Contournement : si ce serveur tombe, la session disparaît ; la charge se répartit mal
Session store partagé Les sessions sortent de la mémoire du serveur vers un magasin commun (Memcached, Redis) La vraie solution : les serveurs redeviennent réellement sans état

La deuxième approche est la bonne par défaut. En rangeant les sessions dans un magasin partagé, tous les serveurs lisent et écrivent au même endroit : n'importe lequel peut servir n'importe quel utilisateur, et la mort d'une machine ne perd aucune session. Les sticky sessions ne sont qu'un palliatif, utile quand on ne peut pas (encore) externaliser.

Memcached ou Redis ?
Memcached est un cache clé-valeur en RAM, pur et simple — parfait pour des sessions volatiles. Redis fait la même chose, mais ajoute des structures de données riches et une persistance optionnelle : les sessions peuvent alors survivre à un redémarrage du magasin. Ce sont les deux choix classiques pour externaliser les sessions.
Serveur 1 · 2 · 3
Session store partagé
Redis / Memcached

Les fichiers, et tout disque local

Même piège avec les fichiers. Si un serveur écrit un fichier sur son disque local, les autres serveurs ne le voient pas — c'est exactement la désynchronisation de la section précédente, version système de fichiers. Là encore, deux familles de réponses :

SolutionIdéeQuand
Stockage réseau partagé (NFS…) Un volume monté sur tous les serveurs, qui voient le même système de fichiers Pratique pour du legacy ; reste un point central à gérer et à scaler
Object storage (S3, GCS) Un service dédié aux fichiers statiques, pensé pour l'échelle Le choix moderne — on y vient en détail à la section Object storage
🔑 Le principe général : tout ce qui vit dans la mémoire ou sur le disque d'un seul serveur — sessions, fichiers uploadés, cache local, fichiers temporaires — doit être externalisé vers un service partagé pour que le scaling horizontal fonctionne vraiment. Sans ça, « serveur sans état » n'est qu'un vœu pieux : le premier ajout de machine fait ressortir l'état caché. Le magasin de sessions, lui, s'appuie sur les mêmes outils que le cache — voir Redis et Cache & éviction.

Le simulateur ci-dessous distribue des requêtes selon la stratégie que tu choisis. Change d'algorithme et envoie du trafic : observe comment la répartition change.

Choisis une stratégie et envoie des requêtes :

03 — SCALER LA BASE

Scaler la base de données

Tu as multiplié les serveurs sans état — mais ils tapent tous sur une seule base. Tôt ou tard, c'est elle le goulot d'étranglement. Or on ne « scale » pas une base comme un serveur.

Voilà le paradoxe que la section précédente a laissé en suspens. Rendre les serveurs sans état permet d'en ajouter autant qu'on veut — mais ils lisent et écrivent tous dans la même base de données. Plus on ajoute de serveurs, plus on tape fort sur cette base unique. Elle devient le vrai goulot d'étranglement, et on ne peut pas la dupliquer naïvement : les données doivent rester cohérentes. Deux stratégies, complémentaires.

Réplicas de lecture : encaisser les lectures

Dans la plupart des applications, on lit bien plus qu'on n'écrit. L'idée : une base primaire reçoit toutes les écritures, et plusieurs réplicas en reçoivent une copie pour servir les lectures. La primaire diffuse ses changements aux réplicas en continu.

Réplica de lecture
Une copie de la base primaire, alimentée par réplication. Les écritures vont à la primaire ; les lectures se répartissent sur les réplicas. Comme les lectures dominent, on absorbe ainsi une charge énorme en ajoutant simplement des réplicas.
⚠️ Le retard de réplication. La réplication est souvent asynchrone : un réplica accuse un léger retard sur la primaire. Conséquence piégeuse : un utilisateur qui écrit puis lit aussitôt depuis un réplica peut voir l'ancienne valeur (« j'ai modifié, ça n'a pas changé ! »). Les parades : lire depuis la primaire juste après une écriture (read-your-writes), ou accepter une cohérence à terme quand le cas le permet.

Sharding : encaisser les écritures

Les réplicas ne soulagent que les lectures — toutes les écritures restent sur une seule primaire. Quand elle sature, il faut découper les données.

Sharding (partitionnement horizontal)
On répartit les données sur plusieurs bases selon une clé de sharding (par exemple user_id % N, ou par région géographique). Chaque shard ne détient qu'une tranche des données et de la charge d'écriture. C'est l'équivalent, au niveau de la base, du scaling horizontal des serveurs.
Réplicas de lectureSharding
Scale…les lecturesles écritures (et le volume)
CoûtRetard de réplicationRequêtes inter-shards difficiles, rééquilibrage délicat
QuandPresque toujours utileEn dernier recours, quand une primaire ne suffit plus
🔑 Ne shard pas trop tôt. Le sharding complique tout : plus de jointures faciles, une clé difficile à changer après coup. Avant d'en arriver là, beaucoup de charge de lecture s'évapore avec un cache (qu'on verra plus loin) et des réplicas. Le bon ordre : optimiser les requêtes et les index, puis cacher, puis répliquer, et seulement alors sharder.

La cohérence, l'isolation et les transactions sous-jacentes sont l'objet du cours Transactions SQL & Isolation ; l'efficacité des requêtes et des index, celui d'Index B-tree & le problème N+1.

Le simulateur ci-dessous illustre la séparation lecture/écriture et son piège. Écris une valeur, puis lis aussitôt depuis un réplica : observe le retard de réplication.

Écris, puis lis depuis un réplica avant qu'il ait rattrapé :

04 — MICROSERVICES & GATEWAY

Découper en services spécialisés

Tu embauches, certains endpoints rament. On ouvre alors la porte d'un monde puissant mais dangereux : des services spécialisés, et un portier unique pour les orchestrer.

L'application est rentable, les clients sont contents, et tu embauches. Mais certains endpoints rament — les uploads, notamment, ralentissent tout le monde parce que chaque serveur fait tout. C'est le moment d'ouvrir une porte puissante, et franchement dangereuse : les microservices.

Un microservice
Un service spécialisé qui sait faire une chose, et la fait très bien. Plutôt qu'un gros serveur qui gère fichiers, notifications, authentification et temps réel, on découpe en services indépendants — que des équipes distinctes peuvent développer et déployer chacune de leur côté.

Pour notre Google Drive, le découpage par domaine tombe sous le sens : un service Fichiers, un service Notifications, un service Auth, un service Temps réel (la synchro vers tes autres appareils). C'est la même séparation des responsabilités que serveur/base, mais à plus grande échelle.

⚠️ À n'ouvrir qu'à bon escient. Les microservices sont un monde complexe (réseau, cohérence, déploiements). On les choisit surtout sur une grosse organisation, quand des équipes ont besoin d'avancer en parallèle — pas par défaut. Sur un petit projet, un monolithe bien découpé reste souvent le bon choix.

Mais une fois les services séparés, comment l'extérieur leur parle-t-il ? Le load balancer seul ne suffit plus : il faut une pièce qui reçoit chaque requête, l'analyse et la route vers le bon service. C'est le API Gateway.

API Gateway
Le portier unique du système. Il route chaque requête vers le service concerné (/api/auth/login → service Auth), peut agréger plusieurs réponses en une seule, et porte l'authentification. Surtout, il devient le seul point d'entrée : tous les services vivent dans un réseau privé (VPC), invisibles depuis l'extérieur.
Client
API Gateway
Auth · Fichiers · Notif · Temps réel

Ce réseau privé répond à une vraie menace : sans lui, rien n'empêcherait un client d'appeler directement le service de fichiers en contournant l'authentification. En n'exposant que le gateway, tu reprends le contrôle de la porte. (Le gateway peut lui-même devenir un point de défaillance unique : on le scale donc lui aussi, derrière un load balancer.)

Le simulateur ci-dessous est ce gateway. Appelle un endpoint pour voir le routage — puis tente d'atteindre un service en direct.

Appelle un endpoint, ou tente un accès direct :

05 — AUTHENTIFICATION

Qui es-tu, et qu'as-tu le droit de faire ?

Dès qu'on expose des services, une question s'impose : qui appelle, et de quel droit ? Le jeton vérifié à l'entrée évite un aller-retour réseau à chaque requête.

Dès qu'il y a un gateway et des services, l'authentification devient incontournable. Deux notions à ne pas confondre, d'abord :

Authentification vs autorisation
L'authentification répond à « qui es-tu ? » (as-tu accès à l'application ?). L'autorisation répond à « qu'as-tu le droit de faire ? » (peux-tu uploader, supprimer ce fichier ?). Liées, mais distinctes : on peut authentifier au gateway, et autoriser plus finement dans chaque service.

Pour l'authentification, le schéma classique repose sur un jeton JWT : une chaîne signée qui prouve ton identité et porte une date d'expiration. À la connexion, l'utilisateur envoie ses identifiants ; le service Auth les vérifie, signe un jeton avec une clé privée, et le lui renvoie. Ensuite, chaque requête transporte ce jeton dans l'en-tête Authorization.

💡 Vérifier au plus tôt, sans aller-retour. Le gateway peut valider la signature et l'expiration du jeton lui-même, à l'entrée — sans appeler le service Auth à chaque requête. Si le jeton est invalide ou expiré, il répond 401 Unauthorized immédiatement, et le service métier n'est jamais sollicité. Une vérification au bord, c'est du réseau et du calcul économisés.
Requête + jeton
Gateway vérifie
Service (si valide)

La génération du jeton, elle, passe bien par le service Auth (et souvent un service Utilisateurs pour vérifier que le compte existe). La distinction est nette : générer un jeton demande une clé privée et une vérification d'identité ; valider un jeton ne demande que la signature — c'est pour ça qu'on peut le faire à la volée, au bord.

Ces mécanismes — jetons, signatures, sessions, contrôle d'accès — ont leur cours dédié côté Symfony : Symfony Security avancé. Et comme tout ça ne vaut rien si le transport n'est pas chiffré, vois aussi TLS/HTTPS.

Le simulateur ci-dessous est la vérification au bord. Choisis l'état du jeton, puis envoie une requête.

Règle l'état du jeton, puis appelle un endpoint protégé :

06 — OBJECT STORAGE

Uploader des fichiers sans saturer tes serveurs

Notre métier, c'est le fichier. Et la pire idée serait de le faire transiter par ton serveur ou ta base. La bonne consiste à laisser le client parler directement au stockage.

Notre métier, c'est le fichier — et pourtant on a soigneusement évité jusqu'ici de dire comment on l'uploade. C'est qu'il y a un piège. La pire idée serait d'envoyer le fichier directement à ton serveur, qui le pousserait dans la base relationnelle. Un film de 20 Go dans une base SQL ? Elle n'est pas faite pour ça. Et faire transiter tout ça par ton serveur, c'est l'exposer aux timeouts et aux attaques par épuisement.

Object storage
Un stockage dédié aux fichiers statiques (images, vidéos, archives) — un bucket S3, Google Cloud Storage… Il est conçu pour encaisser d'énormes volumes, là où une base relationnelle gère des lignes structurées. La règle d'or : les métadonnées en base, les octets dans le bucket.

Le flux malin tient en quelques temps. Le client annonce au gateway « je veux déposer ce fichier » avec ses métadonnées (nom, taille, type). Le service Fichiers crée une ligne en base (un id, le nom, la taille — pas les octets), puis demande au bucket une URL pré-signée : un lien temporaire et restreint qui autorise un upload direct.

Client
Gateway → Fichiers → Base (métadonnées)
URL pré-signée
Bucket (octets)
🔑 L'URL pré-signée est volontairement bridée : elle expire vite (quelques minutes) et limite la taille (« 50 Mo max »). Le client uploade alors directement vers le bucket, en court-circuitant complètement ton architecture. Tes serveurs ne voient jamais passer les octets — exactement le problème de lenteur du début, résolu.

Ce motif — déléguer le gros transfert au stockage, ne garder que les métadonnées — est au cœur des architectures serverless. On le retrouve dans les cours Raccourcisseur d'URL serverless et Chat temps réel serverless, où DynamoDB et les services managés AWS jouent ces rôles.

Le simulateur ci-dessous compare les deux chemins. Choisis une taille de fichier, et vois ce qui arrive selon que tu passes par le serveur ou par l'URL pré-signée.

Choisis une taille, puis compare les deux chemins d'upload :

07 — BROKER & ÉVÉNEMENTIEL

Découpler les services par messages

Un fichier arrive : il faut une miniature, une notification, une synchro. Les appeler en direct, c'est tout faire tomber dès qu'un service flanche. Un intermédiaire change la donne.

Le fichier est dans le bucket. Mais l'upload doit déclencher des effets : générer une vignette, prévenir le service temps réel pour synchroniser tes autres appareils, peut-être envoyer une notification push. Comment ?

La tentation est d'appeler ces services directement, l'un après l'autre, dès l'upload terminé. C'est précisément le piège. Si le service de vignettes est lent, la requête expire. S'il est en panne, ton fichier reste sans vignette — et personne ne sait pourquoi. Et faire connaître tous tes services à la source de l'événement ne passe pas à l'échelle.

Le broker (file de messages, pub/sub)
Un intermédiaire au milieu — Kafka, RabbitMQ, un système pub/sub. La source publie un seul événement (« fichier uploadé ») ; le broker se charge de le distribuer aux services intéressés. La source ne connaît que le broker, pas les consommateurs.

Ce découplage débloque trois propriétés décisives :

PropriétéCe que ça apporte
DurabilitéLe message survit même si le broker redémarre
RedélivraisonPas d'accusé de réception ? On réessaie après un délai
Fan-outUn événement, copié vers plusieurs consommateurs
Fichier uploadé
Broker
Vignette · Temps réel · Notif
💡 Et quand un message ne passe vraiment pas ? Après plusieurs tentatives échouées, il part dans une file de lettres mortes (dead-letter queue) plutôt que d'être perdu ou rejoué à l'infini. On branche alors une alerte (Slack, Discord) pour savoir que « le message X n'a pas été livré ». Rien ne disparaît en silence.

C'est, à nouveau, de la séparation des responsabilités — à l'échelle du système entier. Tout ce mécanisme (files, accusés, redélivraison, DLQ, ordre, idempotence) a ses cours dédiés : Files de messages pour les concepts, et Symfony Messenger avancé pour la mise en œuvre.

Le simulateur ci-dessous est ce broker. Mets un consommateur en panne, publie un événement, et observe la redélivraison puis la file de lettres mortes.

Coupe un consommateur, puis publie un événement :

08 — CACHE & CDN

Arrêter de recalculer la même chose

Cinq cents personnes ouvrent le même fichier chaque jour. Refaire tout le trajet à chaque fois, c'est gâcher des ressources. Le cache et le CDN rapprochent la donnée de l'utilisateur.

L'application grimpe à 10 000 utilisateurs actifs : le signe que ça marche, et le début d'une nouvelle classe de problèmes. On entre dans le territoire de l'optimisation — et un avertissement d'emblée : on le garde pour la fin justement parce qu'il ne faut pas optimiser prématurément. On optimise quand la douleur est réelle, mesurée.

La douleur, ici : 500 personnes ouvrent le même fichier chaque jour. À chaque fois, le même trajet complet — gateway, service fichiers, base relationnelle pour les métadonnées, object storage pour les octets, et retour. Du calcul gâché à refaire à l'identique.

Le cache (ex. Redis)
Un stockage clé-valeur en RAM : l'accès mémoire est bien plus rapide que le disque. On y range les données petites et souvent lues — typiquement les métadonnées du fichier, qui ne changent presque jamais. La RAM est chère et rare : on n'y met pas les gros fichiers eux-mêmes.

Le motif à connaître par cœur, c'est le cache-aside :

Cache-aside (lecture)
1. La clé est-elle dans le cache ? Si oui → on retourne directement (hit).
2. Sinon (miss) → on va chercher en base, on écrit le résultat dans le cache, puis on retourne.
La requête suivante sur la même clé sera servie depuis la RAM.

Reste les octets du fichier — trop gros pour la RAM. Pour eux, on déporte le cache à la périphérie du réseau : le CDN.

CDN (Content Delivery Network)
Des centaines de petits points de présence répartis dans le monde, qui mettent en cache les fichiers statiques près des utilisateurs. Le premier visiteur de Lisbonne déclenche le trajet complet ; les 499 suivants reçoivent le fichier depuis le point de présence local — 20 ms au lieu d'une seconde. Plus besoin de traverser tout le système.
Utilisateur
CDN (proche)
Origine (1 fois)

Cache et CDN ont chacun leur cours : Cache & éviction pour les stratégies (quoi garder, quoi jeter), Redis pour l'outil, et Reverse Proxy, CDN & Cache pour la mise en cache au bord.

Le simulateur ci-dessous applique le cache-aside. Demande un fichier plusieurs fois — le premier accès est un miss (lent), les suivants des hits (rapides).

Demande le même fichier plusieurs fois, puis vide le cache :

09 — RATE LIMITING

Protéger le système des abus

Tu passes à l'échelle, et avec elle viennent les abus. Un compteur par utilisateur, rangé dans le cache, suffit à fermer la porte avant que l'infrastructure ne souffre.

Dernière brique du voyage, et pas la moindre. Dès qu'un système passe à l'échelle, il faut le protéger des abus : sans garde-fou, un utilisateur malveillant peut épuiser tes ressources, gonfler ta facture et dégrader l'expérience de tout le monde — juste pour nuire.

La bonne nouvelle : on a déjà toutes les pièces. Le rate limiting s'appuie sur le cache mis en place à l'étape précédente.

Rate limiting
Une couche — sur le gateway, ou un service dédié — qui identifie chaque appelant (par son IP ou son jeton) et compte ses requêtes dans le cache. Comme le cache est en RAM, ce comptage est quasi gratuit. Au-delà d'un seuil (« 10 requêtes par minute »), l'appelant reçoit un 429 Too Many Requests.
💡 Une idée que tu connais déjà. Les limites de tokens par jour de Claude ou ChatGPT, c'est la même mécanique : certaines ressources sont coûteuses à calculer, on en plafonne donc l'usage. Le cache sert aussi à ça — pré-calculer une opération chère une fois, puis la resservir depuis la mémoire.

Il existe plusieurs algorithmes (fenêtre fixe, fenêtre glissante, token bucket…), chacun avec ses compromis sur les rafales. Le cours Rate Limiting les compare en détail.

Le simulateur ci-dessous compte tes requêtes dans une fenêtre. Clique vite pour franchir le seuil et déclencher le 429.

Envoie des requêtes — dépasse 10 dans la fenêtre :

10 — SYNTHÈSE

Le voyage, et le mot « ça dépend »

D'un serveur unique à un système distribué, chaque étape était une décision avec ses compromis. Récapitulons — et rappelons quand il ne faut surtout pas en faire autant.

Regarde le chemin parcouru. On est parti d'un serveur unique avec ses données dedans, et on a abouti à un système distribué — sans jamais ajouter une brique « pour faire bien ». Chaque étape répondait à une douleur précise :

DouleurBrique introduite
Le serveur tombe, les données meurent ; impossible de dupliquerBase découplée, serveur sans état
Un seul serveur ne suffit plusScaling + load balancer
Les uploads ralentissent tout, les équipes se marchent dessusMicroservices + API gateway
Qui appelle, et de quel droit ?Authentification (JWT) au bord
Les gros fichiers saturent serveurs et baseObject storage + URL pré-signée
Les effets en chaîne tombent si un service flancheBroker événementiel (+ DLQ)
On recalcule sans cesse la même choseCache (cache-aside) + CDN
Les abus épuisent les ressourcesRate limiting
🚫 N'applique pas tout ça par défaut. Chaque brique a un coût : complexité, réseau, exploitation. Les microservices, surtout, ne se justifient que sur une grosse organisation avec des équipes parallèles. Sur un petit projet, un monolithe bien découpé + une base + un cache va très loin. La sur-ingénierie est un problème à part entière.
Ce que tu emportes
Architecturer, c'est penser en systèmes : identifier la douleur réelle, peser le pour et le contre de chaque réponse, et n'ajouter une brique que lorsqu'elle est justifiée. Ce n'est pas qu'une affaire d'infrastructure — le cache touchait aux algorithmes, l'object storage à des décisions produit, le découpage au domaine métier. Comprendre le problème vient toujours avant la solution.

Chacune de ces briques est un monde en soi, et le wiki a un cours dédié pour la plupart — scaling et résilience des données (Transactions SQL), files et événements (Files de messages, Messenger), cache et diffusion (Cache & éviction, Redis, Reverse Proxy & CDN), sécurité (Security, TLS/HTTPS), limites (Rate Limiting). Cette section était la carte ; à toi d'explorer les territoires.