Aller au contenu principal

ADR-0157 : Résumé borné des spans dans PostgreSQL et objectifs SLO en base

Contexte​

La référence d'architecture range les grosses traces dans Opik ou Jaeger, les journaux dans OpenSearch et les fichiers dans le stockage objet du client (A6.1-23) ; PostgreSQL ne garde que des références de trace (trace_refs, A6.1-18) et les traces OpenTelemetry n'y sont pas copiées (B7.1-18). Le domaine observabilité nomme aussi la table service_slos.

Avant cette décision, eai.telemetry_spans (V065) recevait chaque span complet : tous ses attributs, sans liste ni taille maximale, donc aussi un prompt (gen_ai.prompt), une sortie de modèle (gen_ai.completion) ou des arguments d'outil envoyés par un SDK. Les objectifs SLO n'existaient qu'en configuration (EAI_SLO_*) et en mémoire.

Décision​

  1. eai.telemetry_spans devient un résumé borné de chaque span, et non une copie de la trace :
    • référence de trace (trace_id, span_id, parent_span_id), run, session, service, nom (256 caractères au plus), durée, statut, coût fournisseur, jetons d'entrée et de sortie (input_tokens, output_tokens) ;
    • les seuls attributs de la liste d'autorisation ALLOWED_ATTRIBUTES de services/control-plane/src/eai/domains/observability/telemetry.py (identifiants de run, modèle, nombre de jetons, coût, méthode et route HTTP, type d'erreur), valeurs scalaires seulement, chaque chaîne coupée à 128 caractères, l'ensemble sous 2048 octets ; attributes_dropped compte les attributs laissés de côté ;
    • aucun prompt, aucune sortie de modèle, aucun contenu métier n'entre dans PostgreSQL. La contrainte telemetry_spans_summary_bounded (V169) refuse en base un résumé de plus de 4096 octets ou un nom de plus de 256 caractères ; V169 a réduit les lignes existantes à la même liste.
  2. Le span complet, avec tous ses attributs, part vers le collecteur OpenTelemetry (EAI_OTEL_EXPORTER_OTLP_ENDPOINT, puis Jaeger ou Opik), comme avant. Les vues GET /v1/telemetry/overview et GET /v1/telemetry/traces/{trace_id} lisent le résumé et donnent le lien Opik vers la trace complète.
  3. Le résumé ne vit pas plus longtemps que nécessaire : la politique de rétention telemetry du tenant le purge, et le job retention du worker applique pour tous les tenants un plafond EAI_TELEMETRY_SPAN_MAX_DAYS (90 jours par défaut, de 1 à 3650).
  4. eai.service_slos (V169) porte les objectifs de chaque service de la plateforme (disponibilité, latence cible, part des requêtes sous la latence cible). GET /v1/telemetry/slo les lit (cache de 30 secondes vidé par une écriture) ; EAI_SLO_* ne sont plus que les valeurs par défaut d'un service sans ligne. PUT /v1/telemetry/slo/targets/{service} (platform_admin seul, audité telemetry.slo.target.update) les écrit. La table est celle de l'installation : tout contexte authentifié la lit, seul le contexte plateforme l'écrit (RLS forcée).

Un résumé borné a été préféré à des références seules : la vue d'ensemble (requêtes, erreurs, p50 et p95, coût par jour), le suivi FinOps des coûts par trace et l'export ClickHouse en ont besoin sans dépendre de la disponibilité d'Opik ou de Jaeger.

Conséquences​

  • Un attribut hors liste reste lisible dans Jaeger ou Opik, plus dans PostgreSQL. Ajouter un attribut au résumé demande de modifier ALLOWED_ATTRIBUTES et de vérifier qu'il ne porte aucun contenu.
  • Les règles Prometheus de deploy/observability/slo/eai-slo-rules.yaml gardent les valeurs par défaut : un objectif changé en base doit aussi y être reporté pour la vue de flotte sur 30 jours.

Preuves​

services/control-plane/tests/test_observability_data_model.py (résumé borné et export OTel complet, refus en base, rétention par politique et par plafond, SLO lus en base, RLS d'écriture), services/control-plane/tests/test_data_model_mapping.py, services/control-plane/tests/test_observability_slo.py, services/control-plane/tests/test_data_quality_governance.py, services/control-plane/tests/test_tracing.py.