Ressources

Limitation de concurrence

Limiter le nombre d'exécutions simultanées d'une méthode coûteuse avec le décorateur @frappe.concurrent_limit.

Limitation de concurrence

Une méthode exposée avec @frappe.whitelist() occupe un worker Gunicorn pendant toute son exécution. Une génération de PDF, un export ou un rapport lourd peut durer plusieurs secondes. Si beaucoup d'utilisateurs l'appellent en même temps, tous les workers sont occupés et le site ne répond plus.

Le décorateur @frappe.concurrent_limit() limite le nombre d'exécutions simultanées d'une méthode, sur tous les workers du bench.

Dodock l'utilise déjà sur les méthodes de génération de PDF de frappe.utils.print_format : download_pdf et report_to_pdf.

Utiliser le décorateur

Placez @frappe.concurrent_limit() sous @frappe.whitelist(), juste au-dessus de la fonction :

import frappe


@frappe.whitelist()
@frappe.concurrent_limit(limit=3, wait_timeout=30)
def export_rapport_annuel(annee: int):
    ...

Avec cet exemple, trois exports au plus s'exécutent en même temps. Une quatrième requête attend qu'un export se termine, pendant 30 secondes au plus.

Paramètres

ParamètreTypeDéfautDescription
limitint | NoneNoneNombre maximal d'exécutions simultanées. Avec None, la limite est automatique (voir ci-dessous).
wait_timeoutint10Attente maximale d'une place libre, en secondes.

Limite automatique : Moitié de workers × threads de Gunicorn, lus dans la ligne de commande du processus maître. Hors Gunicorn, la valeur de repli est 4 requêtes simultanées, donc une limite de 2.

La limite automatique est mise en cache dans Redis pendant une heure, pour tout le bench. Après un changement du nombre de workers, la nouvelle limite s'applique au plus tard une heure après.

Fonctionnement

Dodock garde dans Redis une liste de jetons, un par exécution autorisée. La clé Redis est concurrency:<module>.<fonction>.

  1. Au début de l'appel, la requête prend un jeton (BLPOP) et attend au plus wait_timeout secondes.
  2. À la fin de l'appel, en succès ou en erreur, la requête rend le jeton (LPUSH).
  3. Si aucun jeton n'est libre avant la fin de l'attente, Dodock lève frappe.exceptions.ServiceUnavailableError.

Si un worker s'arrête sans rendre son jeton, la clé de capacité expire après une heure. Dodock recrée alors la liste de jetons.

Si Redis ne répond pas, la limite ne s'applique pas : la méthode s'exécute et Dodock enregistre un journal d'erreur.

Réponse quand le serveur est occupé

Le client reçoit une réponse HTTP 503 avec l'en-tête Retry-After, égal à wait_timeout (1 seconde au moins). Le message est « Le serveur est occupé. Veuillez réessayer dans quelques secondes. »

HTTP/1.1 503 Service Unavailable
Retry-After: 30

Une intégration peut lire Retry-After pour planifier un nouvel essai.

Hors requête HTTP

Le décorateur ne fait rien hors d'une requête HTTP : tâches en arrière-plan, commandes bench, tests qui appellent la fonction directement.

Consulter la limite automatique

La méthode frappe.concurrency_limiter.get_stats renvoie la limite automatique. Elle demande le rôle « System Manager ».

frappe.call("frappe.concurrency_limiter.get_stats")
# Exemple de résultat : {"cached_limit": 4, "gunicorn_limit": 8}
CléContenu
cached_limitLimite automatique en cours (valeur en cache).
gunicorn_limitNombre de requêtes simultanées détecté pour Gunicorn (workers × threads).

Points d'attention

  • Réservez le décorateur aux méthodes lentes. Sur une méthode rapide, l'appel à Redis ajoute du temps sans bénéfice.
  • La limite compte les appels en cours, pas les appels sur une période. Pour limiter le nombre d'appels sur une période (par adresse IP ou par clé), utilisez le décorateur rate_limit (from frappe.rate_limiter import rate_limit).