0

Qu'est-ce que l'async signifie vraiment pour votre application web Python ?


En bref ⏱️ : L’async n’est pas une solution miracle. Si, comme 80% des applications, votre goulot d’étranglement est la base de données, passer à l’async sans une stratégie de pooling de connexions rigoureuse risque de dégrader vos performances au lieu de les améliorer.

La communauté Python est en effervescence avec l’amélioration du support async. Si vous avez un service existant, vous vous demandez peut-être si vous passez à côté de la “hype”.

🚀 Les benchmarks montrent souvent un débit plus élevé et promettent de gérer plus de requêtes avec moins de ressources. Mais le passage à l’async sera-t-il vraiment un “repas gratuit” pour votre service ?

La réalité diffère souvent des attentes. À moins que vous ne travailliez dans un environnement hautement distribué où votre service est le seul goulot d’étranglement (nécessitant 10 instances juste pour suivre le trafic), vous ne verrez probablement pas les gains promis.

⚠️ Attention : Vous pourriez même constater une baisse de performance en migrant aveuglément.

🧪 Méthode de benchmarking

Le décalage entre la théorie et la pratique vient souvent de la conception des applications. Les services standards parlent directement à une base de données. À mesure que le trafic augmente, la charge sur la base de données croît plus vite que sur le code Python.

Le goulot d’étranglement se déplace donc du code vers la DB. Pour ces tests, nous simulons cette réalité avec :

  • Django + PostgreSQL (Stack classique et robuste).
  • FastAPI (Le challenger “async-first”).
🛠️ Voir les détails de la configuration technique (Cliquez pour dérouler)

Pour exécuter les benchmarks, j’utilise la configuration suivante :

  • Granian comme serveur (supporte threads et processus).
    • Commande : granian --interface <asgi ou wsgi> --workers <1 ou 2> --blocking-threads 32 --port 3000 <application>.
  • rewrk pour le test de charge.
    • Commande : rewrk -d 30s -c 64 --host <host with route>.
  • Matériel (System76 Darter Pro) :
    • NixOS unstable
    • 12th Gen Intel® Core™ i7-1260P × 16
    • 64 GiB de RAM
    • Python 3.14
    • PostgreSQL 18

Note : Les benchmarks “free-threaded” arriveront dans un futur article une fois le support psycopg stabilisé.

Les 3 Scénarios Testés

  1. 📦 Contenu statique : Performance brute du serveur sans DB.
  2. 📖 Lecture DB : Une requête simple pour lire des données (jointure simple).
  3. 🔥 Écriture contentieuse : Utilisation de SELECT FOR UPDATE pour simuler une forte compétition sur les données.

📊 Résultats des tests

Nous comparons quatre configurations :

  1. Sync Django : WSGI standard.
  2. Sync Django Pooled : WSGI avec Pooling de connexions DB (souvent oublié, mais critique).
  3. Async Django : Mode ASGI.
  4. FastAPI : Async natif avec SQLAlchemy.

1. Contenu statique (Sans Base de données)

C’est la vitesse pure du framework.

ServeurWorkersRPS (Req/s)Latence moy.
Sync Django15,03213ms
Sync Django26,61410ms
Async Django1551116ms
Async Django21,12057ms
FastAPI126,2872.43ms
FastAPI237,3531.71ms

Observations :

  • 🏆 FastAPI domine totalement sur du contenu statique. Il est optimisé pour ça.
  • 📉 Async Django est étonnamment plus lent que sa version Sync ici.
  • Cependant, l’Async scale mieux : doubler les workers double presque le débit.

2. Lectures de base de données (Le vrai monde)

Connectons une DB PostgreSQL. Nous récupérons une citation et son auteur.

Note : Ici, nous testons surtout la base de données. Les différences viennent de l’overhead du framework.

ServeurConfigRPS (Req/s)Latence moy.
Sync Django1 worker456140ms
Sync Django Pooled2 workers1,82235ms
Async Django2 workers541118ms
FastAPI2 workers409156ms

💥 Le Choc :

  • Sync Django avec Pooling écrase la concurrence (+200% de performance !).
  • L’overhead de l’async se fait sentir négativement.
  • Même FastAPI traîne derrière une fois la DB impliquée sans pooling optimisé (ou équivalent).

3. Écritures contentieuses (Le cauchemar)

Nous simulons des transactions concurrentes qui doivent verrouiller des lignes (compteur de vues). Tout le monde se bat pour la même ressource.

ServeurConfigRPSLatence moy.
Sync Django1 worker170376ms
Sync Django Pooled2 workers160401ms
Async Django2 workers169378ms

⚖️ Égalité parfaite. Ici, c’est la base de données qui bloque tout le monde. Que votre code soit async ou sync, tout le monde attend le verrou de la DB. Le pooling DB permet cependant de lisser les pics de latence extrêmes.


Conclusion

Ces benchmarks révèlent une vérité importante : Django et Python sont hyper-optimisés pour le mode synchrone.

Ce qu’il faut retenir :

  1. Sync Django Pooled est souvent la meilleure option (performance + simplicité).
  2. FastAPI n’est plus rapide que s’il est le seul goulot d’étranglement (ex: proxy, calculs sans DB).
  3. L’async a un overhead non négligeable.

Conseil d’ami : Ne changez pas votre architecture pour suivre une mode. Si vous faites du CRUD classique, restez en Synchrone et activez le Pooling DB ! 📉➡️📈

Commentaires