Leçon 11 / 20

Limiter la charge sur un service distant

25 min Python — typage, tests et performance Gratuit

Objectif : Plafonner le nombre de requêtes concurrentes envoyées par l’outil de dépenses.

Quand votre outil consulte un service distant pour plusieurs devises, lancer toutes les requêtes simultanément peut dépasser les capacités du service ou déclencher une limite de requêtes. Vous devez donc garder la collecte concurrente tout en plafonnant le nombre de consultations actives. Vous allez appliquer ce contrôle dans le module de collecte, sans changer la fonction appelée par le reste du projet.

Le sémaphore comme limite d’accès

Dans la leçon précédente, vous avez utilisé asyncio.gather pour lancer des consultations concurrentes et associer les résultats aux devises. Un sémaphore ajoute un nombre de places disponibles : une coroutine ne peut franchir la limite qu’après avoir obtenu une place, et la libère lorsqu’elle termine son bloc. Avec async with, cette libération est automatique, y compris si la coroutine quitte le bloc à la suite d’une erreur.

Le plafond porte sur les opérations qui détiennent une place du sémaphore, pas sur le nombre total de coroutines créées. Dans l’exemple, une coroutine est créée pour chaque devise, mais au maximum deux d’entre elles effectuent en même temps la consultation simulée. Choisissez cette valeur en fonction des contraintes documentées du service distant ; un sémaphore ne garantit pas, à lui seul, un quota de requêtes par seconde.

Mettre à jour le module de collecte

Remplacez le contenu de src/depenses/collecte.py par le fichier complet ci-dessous. La fonction recuperer_taux conserve une interface simple : elle reçoit les codes des devises et renvoie un dictionnaire associant chaque code au taux obtenu. La consultation simulée représente ici une opération d’entrée-sortie distante ; dans votre application, vous la remplacerez par l’appel asynchrone du client réseau choisi.

src/depenses/collecte.pyTexte
import asyncio


PLAFOND_CONSULTATIONS = 2


async def consulter_taux(devise: str) -> float:
    """Simule une consultation asynchrone d'un service de taux."""
    await asyncio.sleep(0.1)
    taux_simules: dict[str, float] = {
        "EUR": 1.0,
        "USD": 1.08,
        "GBP": 0.86,
        "CHF": 0.96,
        "CAD": 1.47,
    }
    return taux_simules.get(devise, 1.0)


async def recuperer_taux(devises: list[str]) -> dict[str, float]:
    """Récupère les taux en limitant les consultations simultanées."""
    semaphore = asyncio.Semaphore(PLAFOND_CONSULTATIONS)

    async def consulter_avec_limite(devise: str) -> tuple[str, float]:
        async with semaphore:
            taux = await consulter_taux(devise)
            return devise, taux

    resultats = await asyncio.gather(
        *(consulter_avec_limite(devise) for devise in devises)
    )
    return dict(resultats)


async def main() -> None:
    devises = ["EUR", "USD", "GBP", "CHF", "CAD"]
    taux = await recuperer_taux(devises)
    print(taux)


if __name__ == "__main__":
    asyncio.run(main())
Résultat attendu
{'EUR': 1.0, 'USD': 1.08, 'GBP': 0.86, 'CHF': 0.96, 'CAD': 1.47}

La variable semaphore est créée une fois par collecte : toutes les consultations de cet appel partagent donc le même plafond. Chaque coroutine attend une place avant d’appeler consulter_taux, puis la rend à la sortie du bloc. asyncio.gather attend toujours l’ensemble des résultats, que vous convertissez ensuite en dictionnaire ; l’ordre des éléments collectés reste associé à leur devise, comme dans l’exemple précédent.

Vérifier le comportement et choisir le plafond

Exécutez le module depuis la racine du projet avec python -m depenses.collecte, dans un environnement où src est accessible au chemin d’importation. Vous obtenez les cinq taux attendus. La simulation prend environ trois périodes d’attente de 0,1 seconde : deux consultations peuvent occuper les deux places pendant la première période, deux autres pendant la suivante, puis la dernière pendant la troisième. Cette durée est indicative et dépend de l’ordonnanceur.

Le plafond de deux sert à rendre l’exemple lisible, pas à prescrire une configuration de production. Un plafond trop élevé peut surcharger le service ; un plafond trop bas peut rallonger inutilement la collecte. Vous devez tenir compte des limites communiquées par le fournisseur et de la charge réelle de l’outil. La leçon consacrée aux délais traitera les consultations qui n’aboutissent pas dans le temps attendu ; ici, vous contrôlez uniquement le nombre d’opérations simultanées.

À retenir

  • Un sémaphore limite le nombre d’opérations qui accèdent simultanément à une ressource.
  • Placez l’opération distante dans un bloc « async with semaphore » pour réserver puis libérer une place.
  • Partagez le même sémaphore entre les consultations d’un appel de collecte.
  • Choisissez le plafond selon les contraintes du service : il ne représente pas automatiquement un quota par seconde.

Dans la prochaine leçon, vous apprendrez à gérer les délais et les échecs des consultations asynchrones.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.