Optimiser les performances d'une API FastAPI : Guide des meilleures pratiques
Productivite & Outils

Optimiser les performances d'une API FastAPI : Guide des meilleures pratiques

La plupart des APIs FastAPI lentes ne le sont pas à cause du framework : elles mélangent du code synchrone dans des fonctions async, empilent les requêtes N+1 et cachent au hasard. Voici les cinq chantiers qui donnent le plus de résultats, avec le code et les mesures qui vont avec.

21 mai 2026
6 min de lecture
Par Admin

Je me souviens encore de cette nuit blanche de février 2023. Mon API FastAPI venait de planter en production pour la troisième fois en une semaine. 50 000 utilisateurs connectés, des timeouts partout, et moi qui fixais mes logs avec l'amère réalisation que ma maîtrise du développement backend Python (Django & FastAPI) n'était peut-être pas aussi solide que je le pensais. Cette expérience douloureuse m'a forcé à repenser complètement mon approche des performances FastAPI.

Aujourd'hui, après avoir optimisé plus d'une quinzaine d'APIs en production chez TokamDarius, je peux affirmer sans détour : 90% des problèmes de performance FastAPI viennent de mauvaises pratiques de base, pas de limitations techniques du framework. Et je vais vous expliquer exactement comment les éviter.

Cache stratégique

Le caching, c'est comme les fondations d'une maison : si tu le rates au début, tout s'effondre plus tard. J'ai longtemps cru que FastAPI était naturellement rapide et que le cache était optionnel. Erreur monumentale.

Mon premier réflexe était d'ajouter du cache partout, comme un pansement sur une jambe de bois. Redis sur toutes les routes, cache en mémoire sur les fonctions, TTL à 300 secondes par défaut. Résultat ? Des données incohérentes et des bugs impossibles à déboguer.

La vraie stratégie, c'est le cache à plusieurs niveaux :

  • Cache applicatif avec @lru_cache pour les fonctions pures
  • Cache Redis pour les données partagées entre instances
  • Cache HTTP avec les headers appropriés pour les clients
python
from functools import lru_cache
import redis
from fastapi import FastAPI, Header

@lru_cache(maxsize=1000)
def get_expensive_calculation(param: str):
    # Calcul coûteux en CPU
    return complex_algorithm(param)

Ce qui m'a sauvé, c'est de mesurer avant d'optimiser. Utilise fastapi-cache pour débuter, mais implemente ton propre système dès que tu dépasses 1000 requêtes par seconde. D'ailleurs, j'ai écrit un article complet sur Django vs FastAPI : mon retour d'expérience sur 15 APIs où je détaille les différences de performance entre les deux.

Gestion asynchrone

Ici, on touche au cœur du problème. FastAPI est asynchrone par design, mais 99% des développeurs l'utilisent de manière synchrone sans s'en rendre compte. Moi le premier.

Mon erreur classique était de mixer du code sync dans des fonctions async :

python
# MAUVAIS - bloque l'event loop
async def get_user_data(user_id: int):
    user = db.query(User).filter(User.id == user_id).first()  # Synchrone !
    return user

# BON - vraiment asynchrone
async def get_user_data(user_id: int):
    async with database.transaction():
        user = await database.fetch_one(
            "SELECT * FROM users WHERE id = :id", {"id": user_id}
        )
    return user

La maîtrise du développement backend Python (Django & FastAPI) passe obligatoirement par la compréhension de l'asynchrone. Tu ne peux pas faire semblant.

Mes règles non-négociables :

  • SQLAlchemy async avec asyncpg pour PostgreSQL
  • httpx au lieu de requests pour les appels HTTP externes
  • Dependency injection asynchrone pour les connexions DB

Le piège, c'est que ton code peut fonctionner en développement avec 10 utilisateurs et planter en production avec 1000 connexions simultanées. J'ai appris ça à mes dépens.

Configuration middleware

Les middlewares, c'est le système nerveux de ton API. Mal configurés, ils peuvent transformer ta Ferrari en vélo rouillé. J'ai vu des APIs perdre 70% de leurs performances à cause d'un middleware de logging mal écrit.

Mon middleware de profiling personnel ressemble à ça :

python
import time
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware

class PerformanceMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start_time = time.time()
        response = await call_next(request)
        process_time = time.time() - start_time
        
        # Log seulement si > 1 seconde
        if process_time > 1.0:
            logger.warning(f"Slow request: {request.url} took {process_time:.2f}s")
        
        response.headers["X-Process-Time"] = str(process_time)
        return response

L'ordre des middlewares est critique. Mets le profiling en premier, la compression en dernier. CORS avant l'authentification. J'ai passé 6 heures à déboguer un problème d'auth qui venait juste d'un mauvais ordre de middlewares.

Évite les middlewares tiers non-maintenus. J'utilise uniquement :

  • CORSMiddleware de Starlette
  • GZipMiddleware pour la compression
  • Mon middleware custom de monitoring

Optimisation base de données

C'est là que ça devient sérieux. 95% des problèmes de performance viennent de la base de données, pas de Python. Et FastAPI ne peut pas compenser des requêtes SQL pourries.

Ma règle d'or : une route = une requête SQL maximum. Si tu fais plus, tu es dans le N+1 problem et tu vas souffrir.

python
# MAUVAIS - N+1 queries
async def get_users_with_posts():
    users = await database.fetch_all("SELECT * FROM users")
    for user in users:
        user["posts"] = await database.fetch_all(
            "SELECT * FROM posts WHERE user_id = :id", {"id": user["id"]}
        )
    return users

# BON - Une seule requête avec JOIN
async def get_users_with_posts():
    return await database.fetch_all("""
        SELECT u.id, u.name, p.title, p.content
        FROM users u
        LEFT JOIN posts p ON u.id = p.user_id
    """)

J'utilise SQLAlchemy Core avec FastAPI, pas l'ORM. Plus verbeux, mais les performances sont incomparables. Pour des APIs haute performance, l'ORM est un luxe qu'on ne peut pas se permettre.

Connection pooling obligatoire :

python
from databases import Database
database = Database(
    "postgresql://user:pass@host/db",
    min_size=5,
    max_size=20
)

Monitoring et profiling

Impossible d'optimiser ce qu'on ne mesure pas. J'ai longtemps volé à l'aveuglette, en me fiant à mon "feeling" de développeur. Catastrophique.

Mon stack de monitoring actuel chez TokamDarius :

  • Prometheus + Grafana pour les métriques système
  • py-spy pour le profiling Python en production
  • PostgreSQL logs analysés avec pgBadger
  • Custom metrics dans FastAPI avec prometheus-client
python
from prometheus_client import Counter, Histogram
import time

REQUEST_COUNT = Counter('requests_total', 'Total requests', ['method', 'endpoint'])
REQUEST_LATENCY = Histogram('request_duration_seconds', 'Request latency')

@app.middleware("http")
async def monitor_requests(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    
    REQUEST_COUNT.labels(
        method=request.method,
        endpoint=request.url.path
    ).inc()
    
    REQUEST_LATENCY.observe(time.time() - start)
    return response

Ce qui m'a ouvert les yeux, c'est de profiler en production avec des vrais utilisateurs. Mes tests de charge en local ne reflétaient pas la réalité. Les patterns d'usage réels révèlent des goulots d'étranglement invisibles en développement.

Utilise py-spy sans modération :

bash
py-spy record -o profile.svg -d 60 -p $(pgrep -f "uvicorn")

Tu découvriras que 80% de ton temps CPU se passe dans 3 fonctions. Optimise ces 3 fonctions, ignore le reste.

Déploiement production

Le déploiement, c'est là où la théorie rencontre la réalité brutale. J'ai eu ma dose de réveils à 3h du matin à cause de configurations foireuses.

Uvicorn seul en production = suicide professionnel. Utilise Gunicorn avec des workers Uvicorn :

bash
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8000 \
  --access-logfile - \
  --error-logfile - \
  --log-level info

Calcul du nombre de workers : (2 * CPU cores) + 1. Mais teste en conditions réelles, la théorie ne suffit pas.

Ma configuration Nginx pour FastAPI :

nginx
upstream fastapi {
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
}

server {
    location / {
        proxy_pass http://fastapi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
    }
}

Health checks obligatoires :

python
@app.get("/health")
async def health():
    try:
        await database.fetch_one("SELECT 1")
        return {"status": "healthy"}
    except:
        return {"status": "unhealthy"}

Je ne compte plus les fois où j'ai découvert des problèmes de performance uniquement en production. Load balancer mal configuré, connexions DB qui traînent, memory leaks invisibles en développement... La liste est longue.

D'ailleurs, si tu veux éviter les mêmes erreurs que moi avec Django en production, j'ai documenté mes erreurs les plus coûteuses dans Django en prod : 7 erreurs qui m'ont coûté des nuits.


L'optimisation FastAPI, c'est 20% de technique et 80% de mesure. Tu peux appliquer toutes les bonnes pratiques du monde, si tu ne mesures pas l'impact réel, tu optimises peut-être dans le vide.

Ma recommandation : commence par monitorer ton API actuelle pendant une semaine. Identifie tes 3 endpoints les plus lents. Optimise-les un par un avec les techniques que j'ai partagées. Mesure l'impact. Répète.

La maîtrise du développement backend Python (Django & FastAPI) ne vient pas des tutoriels YouTube ou des cours en ligne. Elle vient des nuits blanches, des bugs en production, et de la volonté d'apprendre de ses erreurs.

Alors arrête de procrastiner et va mesurer les performances de ton API dès maintenant. Tu me remercieras dans 6 mois.

Tags

API
Django
Erreurs
Python

A lire aussi

A

Admin

Développeur web à Jonquière, au Saguenay. Je conçois et je code des applications web sur mesure en Django, React et TypeScript.