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
eai.telemetry_spansdevient 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_ATTRIBUTESdeservices/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_droppedcompte 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.
- référence de trace (
- Le span complet, avec tous ses attributs, part vers le collecteur OpenTelemetry (
EAI_OTEL_EXPORTER_OTLP_ENDPOINT, puis Jaeger ou Opik), comme avant. Les vuesGET /v1/telemetry/overviewetGET /v1/telemetry/traces/{trace_id}lisent le résumé et donnent le lien Opik vers la trace complète. - Le résumé ne vit pas plus longtemps que nécessaire : la politique de rétention
telemetrydu tenant le purge, et le jobretentiondu worker applique pour tous les tenants un plafondEAI_TELEMETRY_SPAN_MAX_DAYS(90 jours par défaut, de 1 à 3650). 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/sloles 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_ATTRIBUTESet de vérifier qu'il ne porte aucun contenu. - Les règles Prometheus de
deploy/observability/slo/eai-slo-rules.yamlgardent 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.