Aller au contenu principal

Résultats de performance, quotas, RTO et RPO mesurés

Exigences : OBSE-013, OBSE-014, LLM_-009, RELE-014, OBSE-010. English version: en/performance-results.md.

Chaque chiffre de cette page provient d'une exécution de bash tools/perf/run.sh sur l'hôte décrit ci-dessous, le 2026-10-30 (dossier de résultats /tmp/eai-perf-final). Rien n'est estimé. Les chiffres dépendent de l'hôte (WSL2 partagé avec d'autres processus) : ils servent de référence de capacité locale, pas d'engagement de service.

Hôte​

nproc: 6
uname -sr: Linux 6.6.87.2-microsoft-standard-WSL2
free -g:
total used free shared buff/cache available
Mem: 47 20 3 0 23 26
Swap: 96 1 94

Commandes​

bash tools/perf/run.sh /tmp/eai-perf-final
cd services/control-plane && .venv/bin/python -m pytest -q tests/test_perf_measurements.py tests/test_finops_attribution.py tests/test_lineage_exploration.py

tools/perf/loadgen.py (Python asyncio et httpx, aucun autre outil) démarre sur des ports libres un PostgreSQL jetable (pgserver de services/control-plane/.venv, toutes les migrations appliquées), le faux fournisseur compatible OpenAI tools/perf/fake_upstream.py et le Control Plane sous uvicorn (un processus, EAI_AUTH_DEV=1, EAI_EGRESS_PRIVATE_ALLOWLIST=127.0.0.1/32, limitation de débit globale désactivée pour mesurer la plateforme et non le limiteur). Le temps CPU (utime + stime) et la RSS du processus API sont lus dans /proc/<pid>/stat et /proc/<pid>/status. Les résultats sont écrits dans load.json et load.md. tools/perf/resilience.py produit resilience.json.

Les scripts k6 de deploy/loadtest n'ont pas été exécutés : le binaire k6 est sous AGPL-3.0 et ne peut servir qu'en outil de test local jamais distribué ; le générateur maison est utilisé à la place.

Débit et latence (OBSE-014)​

400 requêtes par niveau de concurrence après 4 requêtes de chauffe. Scénarios : health (GET /healthz), model_gateway_chat (POST /v1/gateway/chat/completions vers le faux fournisseur : réservation de quota, PEP, DLP, appel, Usage Event, commit), tool_gateway_invoke (POST /v1/tools/{name}/invoke sur le gestionnaire intégré echo : le garde de sortie refuse par conception les destinations privées pour les outils des tenants, le chemin mesuré est donc la passerelle d'outils complète sans saut réseau). La colonne CPU est le temps CPU consommé par l'API pendant le niveau ; la RSS est celle de fin de niveau.

ScénarioConcurrenceRequêtesErreursTaux d'erreurDébit (req/s)p50 (ms)p95 (ms)p99 (ms)CPU API (s)RSS API (Mio)
health140000.000368.132.63.374.470.77127.7
health840000.000502.5114.5719.9382.080.74128.3
health3240000.000421.4754.79213.32299.120.73130.2
model_gateway_chat140000.00013.0973.9291.92110.6822.43209.7
model_gateway_chat840000.00019.9397.87523.22565.4234.52222.3
model_gateway_chat3240000.00020.41548.021960.262147.0650.18261.3
tool_gateway_invoke140000.00034.5625.6546.7166.936.16261.2
tool_gateway_invoke840000.00046.96163.84244.77302.097.61261.2
tool_gateway_invoke3240000.00042.2727.72938.551039.719.25261.5

Lecture : un seul processus uvicorn plafonne vers 20 appels de passerelle de modèle par seconde sur cet hôte ; au delà, la concurrence n'ajoute que de l'attente (la latence croît linéairement). La montée en charge passe par plusieurs réplicas, non mesurée ici (voir les éléments bloqués).

Quotas (LLM_-009)​

LimiteConfigurationRéponses observéesCode d'erreurReprise
Requêtes par minutemanifeste d'outil rate_limit.requests_per_minute: 55 x 200 puis 3 x 429, Retry-After: 60rate_limited200 de nouveau 60.6 s après le premier refus (fenêtre glissante de 60 s)
JetonsPOST /v1/quotas tenant, period: hour, limit_tokens: 1004 x 200 (96 jetons consommés) puis 429 avant tout appel fournisseurquota_exceeded200 dès que la limite est relevée par PUT /v1/quotas/{id}
Réservations simultanéesPOST /v1/quotas tenant, period: day, limit_cost: 0.30, réservations de 0.10 via POST /v1/quotas/reserve3 x 201 puis 2 x 429quota_exceeded201 dès la libération (POST /v1/quotas/{id}/release)

Le service de quotas ne propose pas de période inférieure à l'heure (request, run, hour, day, month) : la limite de jetons par minute n'existe pas en tant que telle, la limite la plus fine mesurée est la limite horaire. La reprise de la limite horaire après expiration de la fenêtre (une heure) n'a pas été attendue ; la reprise a été mesurée par relèvement de la limite.

Attribution des coûts (OBSE-013) et lignage (OBSE-010)​

Les identifiants de tenant ne sont volontairement pas des labels Prometheus (cardinalité et isolation). L'attribution par tenant, modèle et agent est prouvée par services/control-plane/tests/test_finops_attribution.py via GET /v1/finops/usage, GET /v1/finops/summary?group_by=model|agent|tenant et les lignes de l'export analytique vers ClickHouse, sans fuite entre tenants. Le parcours de lignage réponse vers source et retour est prouvé par services/control-plane/tests/test_lineage_exploration.py.

RTO et RPO (RELE-014)​

Objectifs RTO et RPO : à fixer par le client. Seules les valeurs mesurées figurent ici.

ComposantMesureValeur mesurée
PostgreSQLtaille de la base (migrations + 50 090 lignes de sonde)18 706 915 octets
PostgreSQLsauvegarde scripts/pg_backup.sh (format custom, vérifiée par pg_restore --list)0.16 s, 816 324 octets
PostgreSQLrestauration scripts/pg_restore.sh dans une base vide, avec contrôle des migrations9.059 s
PostgreSQLperte de données d'une sauvegarde logique (écritures toutes les 10 ms)50 lignes, fenêtre de 0.636 s écrite après l'instantané
Redispromotion du réplica (REPLICAOF NO ONE) après SIGKILL du primaire, jusqu'à la première écriture acceptée4.071 s
Redisclés acquittées par le primaire et absentes du réplica promu0 sur 350

La perte d'une sauvegarde logique est tout ce qui est écrit après l'instantané : le RPO réel d'une restauration est donc l'intervalle entre deux sauvegardes, plus cette fenêtre. Le temps de promotion Redis est mesuré du SIGKILL jusqu'à la première écriture acceptée par le réplica promu, la détection de panne par Sentinel ou l'opérateur n'est pas incluse. Un premier essai le même jour a donné 2.907 s de restauration et 0.0021 s de promotion : la variance de l'hôte partagé est forte, ces valeurs ne sont pas des bornes.

Éléments bloqués​

  • Mesures GPU : BLOQUÉ, aucun GPU sur cet hôte.
  • Haute disponibilité et montée en charge en cluster (plusieurs réplicas, PostgreSQL CloudNativePG, Redis Sentinel) : BLOQUÉ, aucun cluster disponible ; rien n'a été appliqué sur un cluster ou un compte cloud.