# Objectifs SLO par groupe d'opérations

> Pourquoi - une équipe ne raisonne pas qu'opération par opération : elle promet une disponibilité par service (« l'API des évènements : 99,5 % »), tient…

Guide - Depuis la version 1.6.0 - Flexway v1.6.3 - https://flexway.io/docs/operations-slo-groups/

> Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la capacité côté produit : [guide produit](https://flexway.io/docs/product-overview/) ; l'appeler : [guide développeur](https://flexway.io/docs/developer-overview/).

**Pourquoi** - une équipe ne raisonne pas qu'opération par opération : elle promet une disponibilité **par
service** (« l'API des évènements : 99,5 % »), tient quelques **opérations critiques**, suit des **parcours**
(« s'inscrire », « payer ») et affiche un **chiffre de tête**. Depuis `1.6.0`, un objectif se
déclare sur un **groupe d'opérations**, dans le même fichier `slos` que ceux d'une opération : un budget, un
burn rate et une alerte `slo-burn` pour le groupe, au lieu de vingt budgets sans vue d'ensemble. Rien ne
change à la passerelle : aucune série, aucun label, aucune extension `x-flexway-*` de plus - la console lit
les séries existantes avec un filtre sur `operation`.

> **Pas encore complet** - les objectifs de groupe sont servis : déclaration, calcul dans les deux sources de
> métriques, alerte `slo-burn`, écrans de la console, couverts par les tests unitaires et de contrat. Restent,
> sans version annoncée : la preuve de bout en bout qu'un groupe brûle sous une panne injectée et que l'alerte
> arrive, et la mesure du coût d'évaluation. D'ici là, le plafond de 100 objectifs est provisoire.

```yaml
# Valeurs de la console
slos:
  # 1.5.0, inchangé : une opération critique (son identifiant est l'operationId)
  - operation: createOrder
    latency: { p95: 300ms, target: 99% }
  # Un service : toutes les opérations dont x-flexway-upstream nomme cet amont
  - name: events-api
    upstream: events-svc
    availability: 99.5%
  # Une étiquette OpenAPI (tags de l'opération), moins une sonde
  - name: results
    tag: results
    exclude: [healthz]
    availability: 99%
  # Un parcours : une liste explicite, la latence jugée sur chaque étape
  - name: register
    operations: [createAccount, verifyEmail, createProfile]
    availability: 99.5%
    latency: { p95: 800ms, target: 99%, per: member }
  # Le chiffre de tête : toutes les opérations servies, intégrées comprises (ai.*)
  - name: global
    all: true
    exclude: [healthz]
    availability: 99.9%
    window: 30d
```

| Clé | Règle | Défaut |
| --- | --- | --- |
| Sélecteur | **Exactement un** par entrée : `operation`, ou `name` et `upstream`, `tag`, `operations` ou `all: true` - zéro ou deux refusent le fichier | - |
| `name` | Obligatoire pour un groupe, interdit avec `operation` ; `[a-z0-9][a-z0-9-]{0,62}`, unique parmi les noms **et** les opérations déclarées | - |
| `upstream` | Un amont de `upstreams` de `flexway.yaml` : ses opérations **déclarées** (`x-flexway-upstream`), jamais le label `upstream` des séries - un succès du cache y compte sous `_none` | - |
| `tag` | Une étiquette OpenAPI ; une méthode gRPC n'en a pas | - |
| `operations` | 2 à 50 `operationId` sans doublon ; une seule s'écrit `operation` | - |
| `all` | `true` seulement : toutes les opérations servies, `ai.*` comprises | - |
| `exclude` | Avec `upstream`, `tag` ou `all` : 50 `operationId` au plus retirés du groupe (sondes, administration) | aucun |
| `latency.per` | `group` : le p95 du groupe (seaux additionnés) ; `member` : chaque membre ayant du trafic, le plus lent juge la minute | `group` |
| `window` | 1 à 90 j | `30d` |
| Plafond | 100 objectifs, un par signal déclaré ; un groupe compte pour un - provisoire tant que le coût d'évaluation n'est pas mesuré | 100 |

**Comment c'est calculé** - la disponibilité d'un groupe est **pondérée par les requêtes** (`1 - Σ 5xx /
Σ réponses` sur tous ses membres) : un membre à fort volume pèse ce que vivent ses requêtes, un membre sans
trafic ne compte ni pour ni contre. La latence est la part des minutes où le p95 tient la cible - celui du
groupe, ou celui du membre le plus lent avec `per: member`. Budget, burn rate (1 h, 6 h, 3 j) et seuils de
`slo-burn` sont ceux d'un objectif d'opération. Les membres sont **ceux d'aujourd'hui sur toute la fenêtre** :
une opération ajoutée au contrat sous l'étiquette entre dans le groupe au rechargement suivant de `/config`
(journal `console.alert.slo_members_changed`), sans historique avant elle. Un parcours mesure **ses
requêtes**, jamais le produit des disponibilités de ses étapes : fixez la cible en conséquence.

**À l'écran** - Trafic, onglet **Objectifs (SLO)** : les objectifs par niveau (global, services, parcours,
opérations), une ligne ouvre le panneau de l'objectif - qui consomme le budget (part de chaque membre),
membres et dernier changement, déclaration, « Créer une alerte ». La **Vue d'ensemble** porte l'objectif
`all` en tête ; l'écran **Opération** liste les groupes dont elle est membre. Admins seulement : un objectif
se calcule sur tous les tenants (`403` avant toute lecture pour un lecteur).

**Alerter** - `slo-burn` couvre déjà chaque groupe ; ses sujets sont les **identifiants d'objectif**
(l'`operationId`, ou le `name` d'un groupe). Pour notifier d'autres webhooks pour un objectif :

```yaml
alerts:
  rules:
    - { name: checkout-burn, kind: slo_burn, objective: register, notify: [ops-pager] }
```

**Compatibilité** - un fichier de `1.5.0` se charge à l'identique. Deux changements assumés, dans les notes
de version : une entrée `operation` qui nomme une opération absente de `/config` est désormais **refusée**
au chargement d'un fichier changé (elle était acceptée et jamais calculée) ; dans les webhooks,
`target.kind` de `slo-burn` vaut `objective` et chaque entrée `slos[]` d'un résumé porte `objective`,
`operation` valant `null` pour un groupe - un consommateur strict doit accepter ce `null`.

| Symptôme | Cause | Que faire |
| --- | --- | --- |
| Écran Alertes ou Objectifs : `Dernière version du fichier refusée` avec `upstream ... is not in the gateway's configuration` | Le fichier changé nomme un amont, une étiquette ou une opération que la passerelle ne sert pas | Corriger le nom ou déployer d'abord la passerelle ; la version précédente s'applique d'ici là |
| Un objectif `Invalide`, en bandeau | La configuration de la passerelle a changé **sous** un fichier accepté (amont retiré, opération renommée), ou le groupe est vide après `exclude` | Corriger `slos` ou rétablir ce qu'il nomme ; l'objectif n'est jamais calculé sur un groupe amputé |
| `the group has no operation once exclude is applied` | `exclude` retire tous les membres | Réduire `exclude` |
| `a group's name is an operation's too` | Un `name` égal à un `operationId` déclaré | Renommer le groupe |
| L'objectif global ne bouge pas malgré une panne d'une opération | Elle pèse peu dans le volume : la disponibilité est pondérée par les requêtes | Lire la part de chaque membre dans le panneau, et déclarer un objectif sur l'opération ou un groupe plus étroit |
| Rien en tête de la Vue d'ensemble | Aucun objectif `all: true`, ou session d'un lecteur | Déclarer un objectif global ; un lecteur ne le voit jamais |
