La lliçó 05-01 va acabar amb un èxit incòmode. La Marta va trobar la comanda de vuit segons: 7.402 ms dins de PostgreSQL, un patró N+1 amb 39 consultes. Però per arribar-hi va necessitar quatre consultes de Logs Insights, una cronologia muntada a mà i, sobretot, que algú hagués tingut la precaució de propagar la capçalera X-Peticion-Id per tots els components. Si demà el problema és a la connexió a la base de dades, o a la passarel·la de pagament, o en un import de la Lambda que triga dos segons a arrencar, cal començar de zero i endevinar una altra vegada on mirar.

El problema de fons no és d'esforç: és de model de dades. Les mètriques agreguen i perden el cas individual. Els registres conserven el cas individual però perden la relació entre el que va passar a la botiga i el que va passar a la Lambda. Cap dels dos no guarda l'estructura de la petició: qui va cridar qui, en quin ordre, quant va trigar cada tram i quins se solapen.

AWS X-Ray guarda exactament això. La seva unitat de treball no és un número ni una línia de text: és una petició completa, amb el seu arbre de crides i la seva cronologia. Del «alguna cosa va lenta» al «aquesta crida concreta triga 6,2 segons, i 5,9 són dins d'una consulta a pedidos».

Aquesta lliçó instrumenta la botiga de MercadoFresco, activa el rastreig a la Lambda mercadofresco-estado-pedido i torna a la comanda de 8,14 segons per veure-la, aquesta vegada, en un sol gràfic.

Avís de cost. X-Ray cobra per traça registrada i per traça recuperada o escanejada. És barat si el mostreig està ben configurat i ruïnós si es traça el 100 % d'un trànsit alt. Hi ha una secció sencera sobre estratègia de mostreig econòmica.

Contingut

  1. Per què les mètriques i els registres no basten
  2. El model de dades: traça, segment i subsegment
  3. L'identificador de traça i la seva propagació
  4. Mostreig: per què no es tracen totes les peticions
  5. Regles de mostreig de MercadoFresco
  6. Anotacions enfront de metadades
  7. Una traça completa d'una comanda, en un diagrama
  8. Permisos que necessita X-Ray
  9. Instrumentar la botiga amb aws_xray_sdk
  10. Subsegments manuals i anotacions útils
  11. El dimoni d'X-Ray a EC2 i en contenidors
  12. Activar el rastreig a mercadofresco-estado-pedido
  13. Activar el rastreig a l'ALB, a CloudFront i a API Gateway
  14. El mapa de serveis: com es llegeix
  15. Filtres de traça: el llenguatge de cerca
  16. Anàlisi de latència: histogrames i percentils
  17. El cas guiat: la comanda de 8,14 segons
  18. X-Ray Insights
  19. Relació amb CloudWatch ServiceLens
  20. OpenTelemetry i ADOT: l'alternativa oberta
  21. Cost i estratègia de mostreig econòmica
  22. Neteja

Per què les mètriques i els registres no basten

Mètriques (05-01) Registres (05-01) Traces (X-Ray)
Unitat Sèrie temporal agregada Línia de text Petició completa
Respon Quant? Va bé? Què ha passat exactament? On se n'ha anat el temps?
Conserva el cas individual No
Conserva la relació causal No No
Cardinalitat admesa Molt baixa Alta Alta
Cost dominant Nre. de sèries GB ingerits Nre. de traces
Retenció típica 15 mesos Dies o setmanes 30 dies
Detecta Que alguna cosa va malament Quin error ha sortit Quin component té la culpa

La columna que ho canvia tot és «conserva la relació causal». Un registre de la botiga diu llamada a lambda estado-pedido i un altre registre, en un altre grup, diu REPORT Duration: 480 ms. Que aquests dos esdeveniments pertanyin a la mateixa petició és una cosa que tu has de reconstruir. X-Ray ho sap de naixement, perquè l'identificador viatja amb la petició.

I hi ha tres preguntes que només una traça pot respondre:

  • Quanta estona ha estat la petició esperant enfront de treballant? Un registre amb «durada total 8.140 ms» no distingeix entre calcular i esperar algú altre.
  • Quines crides s'han fet en paral·lel i quines en sèrie? És la diferència entre 39 consultes de 190 ms seqüencials (7,4 s) i 39 consultes en paral·lel (0,2 s).
  • Què ha passat abans del codi? El temps d'arrencada en fred d'una Lambda, l'establiment de la connexió TLS, la resolució de DNS. Res d'això no apareix al registre de la teva aplicació, perquè la teva aplicació encara no s'estava executant.

El model de dades: traça, segment i subsegment

Tres conceptes i una jerarquia:

  • Traça (trace): tot el que passa arran d'una petició. S'identifica amb un ID de traça i agrupa segments de tots els serveis implicats.
  • Segment (segment): la feina d'un servei o recurs dins de la traça. La botiga genera un segment; la Lambda genera el seu. Conté nom, hora d'inici i de fi, i estat.
  • Subsegment: un tram dins d'un segment. Una consulta SQL, una crida HTTP a un altre servei, un bloc de càlcul que vols mesurar.
flowchart TD
    T["TRACA 1-68a2f4c1-3b8d9e2a5f7c1b4d8e0a2f6c<br/>durada total: 8.140 ms"]
    T --> S1["SEGMENT: mercadofresco-tienda<br/>0 ms - 8.140 ms"]
    S1 --> SS1["subsegment: validar_stock<br/>27 ms - 84 ms"]
    S1 --> SS2["subsegment: Invoke estado-pedido<br/>140 ms - 690 ms"]
    S1 --> SS3["subsegment: SQL inserir linies<br/>700 ms - 8.110 ms"]
    SS3 --> SS3a["39 consultes SELECT preu<br/>190 ms cadascuna, EN SERIE"]
    T --> S2["SEGMENT: mercadofresco-estado-pedido<br/>145 ms - 685 ms"]
    S2 --> SS4["subsegment: Initialization<br/>145 ms - 460 ms - arrencada en fred"]
    S2 --> SS5["subsegment: SNS Publish<br/>500 ms - 660 ms"]

Fixa't en el detall que fa útil aquest model: el segment de la Lambda (145-685 ms) està contingut dins del subsegment Invoke de la botiga (140-690 ms), i la diferència de 10 ms és la latència de xarxa i de la mateixa API de Lambda. Aquesta xifra no surt en cap registre de cap dels dos serveis.

Un segment, en la seva representació JSON real —simplificada—, es veu així:

{
  "trace_id": "1-68a2f4c1-3b8d9e2a5f7c1b4d8e0a2f6c",
  "id": "6b1c2d3e4f5a6b7c",
  "name": "mercadofresco-tienda",
  "start_time": 1785142692.004,
  "end_time": 1785142700.144,
  "http": {
    "request": {
      "method": "POST",
      "url": "https://mercadofresco.example/api/pedidos/confirmar",
      "client_ip": "203.0.113.45",
      "user_agent": "Mozilla/5.0 ..."
    },
    "response": { "status": 200, "content_length": 412 }
  },
  "aws": {
    "ec2": { "instance_id": "i-0abc123def456", "availability_zone": "eu-west-1a" }
  },
  "annotations": {
    "pedido_id": "48213",
    "metodo_pago": "tarjeta",
    "num_lineas": 38,
    "entorno": "produccion"
  },
  "metadata": {
    "default": {
      "carrito": { "productos": ["tomate-rama", "lechuga-batavia", "..."] }
    }
  },
  "subsegments": [
    {
      "id": "7c2d3e4f5a6b7c8d",
      "name": "validar_stock",
      "start_time": 1785142692.031,
      "end_time": 1785142692.088
    }
  ]
}

Els camps error, fault i throttle marquen el resultat, i la seva distinció importa perquè el mapa de serveis els pinta de colors diferents:

Camp Significa Codi HTTP Color al mapa
error Error del client 4xx Groc
fault Error del servidor 5xx Vermell
throttle Estrangulament 429 Morat
(cap) Correcte 2xx / 3xx Verd

L'identificador de traça i la seva propagació

Un ID de traça té aquesta forma:

1-68a2f4c1-3b8d9e2a5f7c1b4d8e0a2f6c
│  │        └── 96 bits aleatoris en hexadecimal
│  └── marca de temps Unix de l'origen, en hexadecimal
└── versio (sempre 1)

Que la marca de temps estigui dins de l'identificador no és decoratiu: permet a X-Ray localitzar la traça sense índex global, i és la raó que no es puguin consultar traces de més de 30 dies.

La propagació es fa amb la capçalera HTTP X-Amzn-Trace-Id:

X-Amzn-Trace-Id: Root=1-68a2f4c1-3b8d9e2a5f7c1b4d8e0a2f6c;Parent=6b1c2d3e4f5a6b7c;Sampled=1
Camp Què és
Root L'ID de la traça. El genera el primer component instrumentat.
Parent L'ID del segment que fa la crida. És el que construeix l'arbre.
Sampled 1 = traça-la; 0 = no la tracis. La decisió es pren un cop i es respecta.
Self L'afegeix l'ALB quan ja venia una capçalera del client.
sequenceDiagram
    participant C as Client
    participant CF as CloudFront
    participant ALB as alb-mercadofresco-tienda
    participant T as Botiga EC2
    participant L as Lambda estado-pedido
    participant D as DynamoDB / RDS

    C->>CF: POST /api/pedidos/confirmar
    CF->>ALB: (reenvia)
    ALB->>T: X-Amzn-Trace-Id: Root=1-68a2...;Sampled=1
    Note over ALB: L'ALB GENERA la capcalera<br/>si no venia
    T->>L: Invoke + Root=1-68a2...;Parent=6b1c...
    L->>D: Query + Root=1-68a2...;Parent=9d4e...
    D-->>L: resultat
    L-->>T: resposta
    T-->>C: 200 OK

Tres regles que cal interioritzar:

  1. El primer component instrumentat genera el Root. Si l'ALB té el rastreig actiu, el genera ell. Si no, el genera la teva aplicació.
  2. La decisió de mostreig es pren una sola vegada, a l'origen, i tothom la respecta. Si el Root arriba amb Sampled=0, la Lambda no enviarà el seu segment ni que tingui el rastreig activat. Això evita traces incompletes i és un comportament que confon molt en depurar: «he activat X-Ray a la Lambda i no veig res» sol voler dir que la decisió de mostreig es va prendre aigües amunt.
  3. Els SDK propaguen la capçalera automàticament a les crides sortints que intercepten. En una crida que no intercepten —un client HTTP exòtic, una cua pròpia— l'has de propagar tu.

Mostreig: per què no es tracen totes les peticions

MercadoFresco rep, un divendres normal, de l'ordre de 2 milions de peticions diàries. Traçar-les totes costaria, a 5 USD per milió de traces registrades, uns 300 USD al mes només de registre, sense comptar les recuperacions. I el 99,9 % d'aquestes traces serien idèntiques i avorrides: peticions de 80 ms que funcionen.

El mostreig decideix quina fracció es traça. Una regla de mostreig té dues parts:

  • reservoir (dipòsit): un nombre fix de peticions per segon que es tracen sempre. És el terra: garanteix que sempre tinguis exemples, fins i tot amb trànsit baixíssim.
  • fixed_rate: el percentatge de la resta que es traça. És el sostre proporcional: garanteix representativitat quan el trànsit puja.

Amb reservoir: 1 i fixed_rate: 0.05:

Peticions/segon Del dipòsit Del 5 % restant Total traçat
1 1 0 1 (100 %)
10 1 0,45 ~1,45 (14,5 %)
100 1 4,95 ~5,95 (6 %)
1.000 1 49,95 ~51 (5,1 %)

Aquest és el disseny: a volum baix traces gairebé tot (i no et costa res); a volum alt traces un percentatge estable (i el cost creix de manera controlada).

Les regles s'avaluen per prioritat, de menor a major número, i guanya la primera que coincideix. Els criteris de coincidència són: service_name, service_type, host, http_method, url_path, i atributs de la petició.

Regles de mostreig de MercadoFresco

L'estratègia de la Marta: traçar poc el que és barat i freqüent, traçar molt el que és car i important.

Prioritat Nom Coincideix amb Dipòsit Taxa fixa Motiu
100 muestreo-mercadofresco-checkout POST /api/pedidos/* 2/s 100 % Són els diners
200 muestreo-mercadofresco-admin /admin/* 1/s 50 % Poc trànsit, molt valor
300 muestreo-mercadofresco-estaticos /static/*, /favicon.ico 0/s 0 % Soroll pur
400 muestreo-mercadofresco-salud /salud 0/s 0 % El health check cada 15 s
9000 muestreo-mercadofresco-defecto Tota la resta 1/s 5 % Representativitat
# La regla que importa: tracar el 100% de les confirmacions de comanda.
aws xray create-sampling-rule --cli-input-json '{
  "SamplingRule": {
    "RuleName": "muestreo-mercadofresco-checkout",
    "Priority": 100,
    "FixedRate": 1.0,
    "ReservoirSize": 2,
    "ServiceName": "mercadofresco-tienda",
    "ServiceType": "*",
    "Host": "*",
    "HTTPMethod": "POST",
    "URLPath": "/api/pedidos/*",
    "Version": 1,
    "ResourceARN": "*",
    "Attributes": {}
  }
}' --profile mercadofresco-dev --region eu-west-1

# I la que estalvia mes diners: NO tracar el health check.
aws xray create-sampling-rule --cli-input-json '{
  "SamplingRule": {
    "RuleName": "muestreo-mercadofresco-salud",
    "Priority": 400,
    "FixedRate": 0.0,
    "ReservoirSize": 0,
    "ServiceName": "*",
    "ServiceType": "*",
    "Host": "*",
    "HTTPMethod": "GET",
    "URLPath": "/salud",
    "Version": 1,
    "ResourceARN": "*"
  }
}' --profile mercadofresco-dev --region eu-west-1

Aquest /salud es mereix un càlcul. L'ALB comprova /salud cada 15 segons contra cada instància (03-03). Amb 4 instàncies al pic: 4 × 4 = 16 peticions per minut, 691.200 al mes. Al 5 % serien 34.560 traces mensuals d'una resposta de 3 ms que diu OK. No aporten absolutament res i embruten els histogrames de latència amb milers de punts a 3 ms que desplacen els percentils. Excloure'ls és la primera optimització que cal fer sempre.

Les regles es gestionen de manera centralitzada: el SDK les descarrega cada 10 segons des de l'API d'X-Ray. Canvies una regla a la consola i totes les instàncies de l'ASG l'apliquen en segons, sense desplegar codi. Això permet una cosa molt útil: pujar el mostreig al 100 % durant un incident i abaixar-lo quan s'acaba.

Anotacions enfront de metadades

És la distinció més pràctica d'X-Ray i la que decideix si podràs trobar alguna cosa o no:

Anotació Metadada
Indexada? No
S'hi pot filtrar? No
Límit 50 per traça Mida del segment (64 KB)
Tipus Cadena, número, booleà Qualsevol JSON
Ús pedido_id, metodo_pago, provincia La cistella sencera, la resposta de la passarel·la

La regla és simple: si hi has de cercar, és una anotació; si només ho vols veure quan obris la traça, és una metadada.

I aquí hi ha l'avantatge sobre CloudWatch que convé subratllar. A 05-01 vam veure que posar PedidoId com a dimensió de mètrica costaria 190.000 USD al mes. Com a anotació d'X-Ray és gratis i, a més, cercable: annotation.pedido_id = "48213". És exactament el buit que les mètriques no poden cobrir.

Les anotacions de MercadoFresco, triades per respondre preguntes reals:

Anotació Exemple Pregunta que respon
pedido_id "48213" «El client diu que la seva comanda va trigar molt»
cliente_hash "a3f8c1e9" «Li passa sempre, a aquest client?»
metodo_pago "tarjeta" «És lent només amb una passarel·la?»
num_lineas 38 «La lentitud creix amb la mida de la comanda?»
provincia "Madrid" «És un problema d'una zona de repartiment?»
version_app "2.14.3" «Va començar amb l'últim desplegament?»
entorno "produccion" Separar producció de proves

Avís de privacitat. Les anotacions i les metadades s'emmagatzemen a X-Ray i són visibles per a qualsevol que tingui permís de lectura. Mai no hi posis correus, noms, adreces, números de targeta ni identificadors directes de persona. Per això MercadoFresco fa servir cliente_hash, un hash de l'identificador intern, i no el correu. Si manegues dades personals sota el RGPD, aquesta decisió l'ha de revisar qui porti el compliment a la teva organització.

Una traça completa d'una comanda, en un diagrama

gantt
    title Traca 1-68a2f4c1 - confirmacio de la comanda 48213 - total 8.140 ms
    dateFormat X
    axisFormat %L ms

    section Botiga EC2
    Segment botiga             :active, t1, 0, 8140
    validar_stock             :done,   t2, 27, 57
    Invoke estado-pedido      :done,   t3, 140, 550
    SQL inserir capcalera     :done,   t4, 700, 40
    SQL 39 SELECT preu        :crit,   t5, 745, 7410
    respondre                 :done,   t6, 8110, 30

    section Lambda estado-pedido
    Segment lambda             :active, l1, 145, 540
    Initialization arrencada freda :crit, l2, 145, 315
    SNS Publish               :done,   l3, 500, 160

    section RDS pedidos
    Consultes PostgreSQL      :crit,   r1, 745, 7402

Un gràfic així —que a la consola d'X-Ray s'anomena vista de cascada i es genera sol— explica la història sencera en dos segons de lectura: hi ha una barra vermella de 7,4 segons i tota la resta és irrellevant en comparació. Compara-ho amb el full de càlcul que la Marta va muntar a mà a 05-01.

Permisos que necessita X-Ray

El rol rol-mercadofresco-tienda ha de poder enviar segments i descarregar les regles de mostreig. AWS té una política gestionada per a això:

aws iam attach-role-policy \
  --role-name rol-mercadofresco-tienda \
  --policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess \
  --profile mercadofresco-dev

Si prefereixes el mínim privilegi explícit, que és el que ensenyem a 04-01, el contingut efectiu és aquest:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnviarTrazas",
      "Effect": "Allow",
      "Action": [
        "xray:PutTraceSegments",
        "xray:PutTelemetryRecords"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DescargarReglasDeMuestreo",
      "Effect": "Allow",
      "Action": [
        "xray:GetSamplingRules",
        "xray:GetSamplingTargets",
        "xray:GetSamplingStatisticSummaries"
      ],
      "Resource": "*"
    }
  ]
}

Cap d'aquestes accions no admet ARN de recurs —X-Ray no té recursos per traça—, de manera que "Resource": "*" és correcte aquí; és el mateix cas que cloudwatch:PutMetricData a 05-01.

Per al rol de la Lambda, rol-lambda-miniaturas i el de mercadofresco-estado-pedido, n'hi ha prou amb la política gestionada AWSXRayDaemonWriteAccess, o l'encara més específica AWSXRayWriteOnlyAccess.

I per llegir traces, el grup de persones que fa guàrdia necessita:

{
  "Effect": "Allow",
  "Action": [
    "xray:GetTraceSummaries",
    "xray:BatchGetTraces",
    "xray:GetServiceGraph",
    "xray:GetTraceGraph",
    "xray:GetTimeSeriesServiceStatistics",
    "xray:GetGroups",
    "xray:GetInsightSummaries",
    "xray:GetInsight"
  ],
  "Resource": "*"
}

Instrumentar la botiga amb aws_xray_sdk

Instal·lació:

pip install aws-xray-sdk

I la instrumentació mínima d'una aplicació Flask, que és el que corre a les instàncies de l'ASG:

"""Instrumentacio d'X-Ray a la botiga de MercadoFresco."""
from flask import Flask, request, jsonify
from aws_xray_sdk.core import xray_recorder, patch_all
from aws_xray_sdk.ext.flask.middleware import XRayMiddleware

# 1. Configurar el gravador ABANS de crear l'aplicacio.
xray_recorder.configure(
    service="mercadofresco-tienda",       # nom del node al mapa
    daemon_address="127.0.0.1:2000",      # on escolta el dimoni
    context_missing="LOG_ERROR",          # NO llancar excepcio si falta context
    sampling=True,                        # fer servir les regles centralitzades
    plugins=("EC2Plugin",),               # afegeix instance_id i AZ al segment
)

# 2. patch_all instrumenta automaticament les llibreries admeses:
#    boto3, botocore, requests, httplib, sqlite3, psycopg2, pymysql, aiohttp...
patch_all()

app = Flask(__name__)

# 3. El middleware crea un segment per cada peticio HTTP entrant,
#    llegeix la capcalera X-Amzn-Trace-Id i respecta la decisio de mostreig.
XRayMiddleware(app, xray_recorder)

Quatre decisions que cal entendre, perquè cadascuna evita un problema real:

  • context_missing="LOG_ERROR" és probablement el paràmetre més important del fitxer. Amb el valor per defecte (RUNTIME_ERROR), qualsevol codi instrumentat que s'executi fora d'una petició HTTP —una tasca programada, un script de manteniment, un fil de fons, el publicador de mètriques per lots de 05-01— llança una excepció i tomba la tasca. Amb LOG_ERROR, escriu un error al registre i continua. L'observabilitat no ha de tombar mai l'aplicació.
  • plugins=("EC2Plugin",) fa que cada segment inclogui l'instance_id i la zona de disponibilitat. Quan una sola instància de l'ASG estigui degradada, aquesta dada és la que ho revela. Hi ha equivalents ECSPlugin i ElasticBeanstalkPlugin.
  • patch_all() inclou psycopg2, el controlador de PostgreSQL. Això vol dir que cada consulta a mercadofresco-pedidos genera un subsegment automàticament, amb el text de la consulta sanejat. És exactament el que delatarà l'N+1.
  • daemon_address: el SDK no parla amb l'API d'X-Ray. Escriu paquets UDP a un dimoni local, que s'encarrega d'agrupar-los i enviar-los. Per això una crida instrumentada afegeix microsegons, no mil·lisegons.

Si prefereixes instrumentar amb més detall, patch() accepta la llista de mòduls: patch(("boto3", "psycopg2", "requests")). Evita instrumentar sqlite3 en producció si el fas servir per a memòries cau locals d'alta freqüència: generaries milers de subsegments inútils.

Subsegments manuals i anotacions útils

La instrumentació automàtica cobreix les crides a serveis externs. Els trams del teu codi els has de marcar tu:

"""Confirmacio de comanda, instrumentada."""
import hashlib
from aws_xray_sdk.core import xray_recorder


@app.route("/api/pedidos/confirmar", methods=["POST"])
def confirmar_comanda():
    dades = request.get_json()
    pedido_id = dades["pedido_id"]

    # ANOTACIONS: indexades i filtrables. Es el primer que es fa,
    # perque hi siguin encara que la peticio falli despres.
    segment = xray_recorder.current_segment()
    segment.put_annotation("pedido_id", str(pedido_id))
    segment.put_annotation("metodo_pago", dades["metodo_pago"])
    segment.put_annotation("num_lineas", len(dades["lineas"]))
    segment.put_annotation("provincia", dades["direccion"]["provincia"])
    segment.put_annotation("version_app", app.config["VERSION"])
    # Hash, MAI el correu ni l'identificador directe del client.
    segment.put_annotation(
        "cliente_hash",
        hashlib.sha256(dades["cliente_id"].encode()).hexdigest()[:8],
    )

    # METADADES: no indexades, pero visibles en obrir la traca.
    segment.put_metadata("carrito", dades["lineas"], "negocio")
    segment.put_metadata("importe_total", dades["total_eur"], "negocio")

    # SUBSEGMENT com a gestor de context: es tanca sol, fins i tot si hi ha excepcio.
    with xray_recorder.in_subsegment("validar_stock") as sub:
        disponible = validar_stock(dades["lineas"])
        sub.put_annotation("stock_ok", disponible)
        if not disponible:
            # error = problema del client (4xx), groc al mapa.
            sub.add_error_flag()
            return jsonify({"error": "sin stock"}), 409

    with xray_recorder.in_subsegment("calcular_portes"):
        ports = calcular_portes(dades["direccion"])

    # Aquesta crida NO necessita subsegment manual: patch_all ha instrumentat
    # boto3, i la invocacio de Lambda apareix sola a la traca.
    resposta = cliente_lambda.invoke(
        FunctionName="mercadofresco-estado-pedido",
        Payload=json.dumps({"pedido_id": pedido_id}),
    )

    # Aquesta tampoc: psycopg2 esta instrumentat i cada consulta
    # genera el seu propi subsegment amb el SQL sanejat.
    with xray_recorder.in_subsegment("persistir_pedido"):
        desar_comanda(pedido_id, dades["lineas"], ports)

    return jsonify({"estado": "confirmado", "pedido_id": pedido_id}), 200

I el decorador, per a funcions que es criden des de diversos llocs:

from aws_xray_sdk.core import xray_recorder


@xray_recorder.capture("calcular_portes")
def calcular_portes(direccion):
    """Cada crida a aquesta funcio crea el seu propi subsegment."""
    ...

Feina en fils i en tasques asíncrones

L'error d'instrumentació més habitual en producció. El SDK desa el segment actual en una variable de context per fil. Si engegues un fil nou, aquest fil no té context i tot el que hi executis instrumentat fallarà o es perdrà:

import threading
from aws_xray_sdk.core import xray_recorder

def processar_en_paralel(entitat):
    # Capturar el segment al fil pare...
    segment_pare = xray_recorder.current_subsegment() or xray_recorder.current_segment()

    def treball():
        # ...i restaurar-lo al fil fill.
        xray_recorder.context.put_segment(segment_pare)
        with xray_recorder.in_subsegment("trabajo_paralelo"):
            fer_alguna_cosa(entitat)

    threading.Thread(target=treball).start()

Si això et sembla fràgil, tens raó: ho és. És un dels arguments seriosos a favor d'OpenTelemetry, que resol la propagació de context de manera més neta. Ho veiem al final de la lliçó.

El dimoni d'X-Ray a EC2 i en contenidors

El dimoni d'X-Ray és un procés lleuger que escolta al port 2000/UDP, agrupa els segments que li envien els SDK i els fa arribar a l'API d'X-Ray per lots.

flowchart LR
    A["Aplicacio<br/>aws_xray_sdk"] -->|"UDP 2000<br/>microsegons"| D["Dimoni X-Ray<br/>proces local"]
    D -->|"HTTPS per lots<br/>cada segon"| X["API d'X-Ray<br/>eu-west-1"]

Per què aquesta arquitectura? Perquè desacobla l'aplicació de la xarxa. Si l'API d'X-Ray va lenta o està caiguda, l'aplicació no se n'assabenta: continua escrivint paquets UDP que, en el pitjor cas, es perden. Una traça perduda no és cap problema; una botiga bloquejada esperant l'API de traces, sí.

Instal·lació a les instàncies de l'ASG, afegida al user data de lt-mercadofresco-tienda:

#!/bin/bash
set -euo pipefail

# Amazon Linux 2023. AWS publica el paquet en un bucket per regio.
curl -fsSL -o /tmp/xray.rpm \
  "https://s3.dualstack.eu-west-1.amazonaws.com/aws-xray-assets.eu-west-1/xray-daemon/aws-xray-daemon-3.x.rpm"
dnf install -y /tmp/xray.rpm

cat > /etc/amazon/xray/cfg.yaml <<'YAML'
TotalBufferSizeMB: 16
Concurrency: 8
Region: eu-west-1
Socket:
  UDPAddress: 127.0.0.1:2000
  TCPAddress: 127.0.0.1:2000
LocalMode: false
LogLevel: warn
YAML

systemctl enable --now xray
systemctl is-active xray || exit 1

Notes de camp:

  • UDPAddress: 127.0.0.1:2000, no 0.0.0.0. Només ha d'escoltar a la interfície local; ningú de fora no li ha d'enviar segments.
  • TotalBufferSizeMB: si la memòria intermèdia s'omple, el dimoni descarta segments i ho anota al seu registre. Veure SegmentsRejectedCount creixent vol dir que cal pujar-lo o abaixar el mostreig.
  • No cal obrir res a sg-mercadofresco-tienda: el trànsit és local. La sortida cap a l'API d'X-Ray passa pel NAT (03-01) o, millor encara, per un punt d'enllaç d'interfície de VPC per a com.amazonaws.eu-west-1.xray si vols que ni tan sols passi per internet.

En contenidors (mòdul 10), el dimoni es desplega com a contenidor sidecar dins de la mateixa definició de tasca, i l'aplicació l'atansa per localhost a ECS amb xarxa awsvpc. Es detalla a 10-01.

Activar el rastreig a mercadofresco-estado-pedido

A Lambda no hi ha cap dimoni per instal·lar: AWS l'executa per tu dins de l'entorn d'execució. N'hi ha prou amb una casella i un permís.

# 1. El permis
aws iam attach-role-policy \
  --role-name rol-mercadofresco-estado-pedido \
  --policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess \
  --profile mercadofresco-dev

# 2. La casella: mode de rastreig actiu
aws lambda update-function-configuration \
  --function-name mercadofresco-estado-pedido \
  --tracing-config Mode=Active \
  --profile mercadofresco-dev --region eu-west-1

Els dos modes:

Mode Comportament
PassThrough (per defecte) Només traça si la petició ja venia amb Sampled=1
Active La funció decideix el mostreig si no venia decidit

Per a mercadofresco-estado-pedido, que la crida la botiga, PassThrough seria suficient i més barat. Per a mercadofresco-generar-miniaturas, que la dispara un esdeveniment d'S3 i no té origen instrumentat, cal Active o no es traçarà mai res.

Només amb això ja obtens el segment amb la durada, l'arrencada en fred i els errors. Per veure dins de la funció, cal instrumentar igual que a EC2:

"""mercadofresco-estado-pedido, instrumentada."""
import json
import os
import boto3
from aws_xray_sdk.core import xray_recorder, patch_all

# A Lambda el dimoni es a l'adreca de la variable d'entorn,
# que el mateix entorn d'execucio defineix. No cal configurar res.
patch_all()

sns = boto3.client("sns")
TEMA = os.environ["ARN_TEMA_ALERTAS"]


def handler(esdeveniment, context):
    pedido_id = esdeveniment["pedido_id"]

    # A Lambda, el segment arrel el crea l'entorn d'execucio i es
    # de nomes lectura: les anotacions van en un SUBSEGMENT.
    subsegment = xray_recorder.begin_subsegment("procesar_estado")
    try:
        subsegment.put_annotation("pedido_id", str(pedido_id))
        subsegment.put_annotation("origen", esdeveniment.get("origen", "tienda"))

        estat = calcular_estat(pedido_id)
        subsegment.put_annotation("estado_resultante", estat)

        # boto3 esta pedacat: aquesta crida apareix sola com a subsegment
        # i a mes dibuixa el node d'SNS al mapa de serveis.
        sns.publish(
            TopicArn=TEMA,
            Subject=f"Pedido {pedido_id}: {estat}",
            Message=json.dumps({"pedido_id": pedido_id, "estado": estat}),
        )
        return {"estado": estat}

    except Exception as e:
        subsegment.add_exception(e, [])   # marca fault: vermell al mapa
        raise
    finally:
        xray_recorder.end_subsegment()

El detall que fa perdre temps a tothom la primera vegada: a Lambda no pots anotar el segment arrel. El crea i el controla l'entorn d'execució. xray_recorder.current_segment() existeix, però put_annotation sobre ell s'ignora silenciosament. Les anotacions van sempre en un subsegment propi.

Un altre detall valuós: el subsegment Initialization que veus a les traces de Lambda és l'arrencada en fred. Si el teu p99 de Duration és dolent però el p50 és bo, mira aquest subsegment: gairebé sempre és un import pesat que es pot moure dins del handler o reduir. És el diagnòstic que a 02-05 només podíem intuir.

Activar el rastreig a l'ALB, a CloudFront i a API Gateway

Servei Què aporta Com s'activa
ALB Genera X-Amzn-Trace-Id si no ve Automàtic, sempre actiu
API Gateway Segment propi amb latència d'integració Casella per etapa
CloudFront No genera segments d'X-Ray Es correlaciona per x-amz-cf-id
SQS / SNS Propaguen la capçalera Automàtic (07-01, 07-02)
Step Functions Segment per estat Casella (07-04)

L'ALB genera la capçalera automàticament i no hi ha res per activar: és la raó per la qual la botiga de MercadoFresco rep un Root sense que ningú ho hagi programat. El que l'ALB no fa és generar un segment propi, així que el seu temps de procés no surt com a node al mapa. Aquesta latència es continua veient a la mètrica TargetResponseTime de CloudWatch (05-01), i aquest és un bon exemple que les dues eines es complementen.

Si algun dia MercadoFresco posa una API darrere d'API Gateway:

aws apigateway update-stage \
  --rest-api-id abc123def4 --stage-name produccion \
  --patch-operations op=replace,path=/tracingEnabled,value=true \
  --profile mercadofresco-dev --region eu-west-1

CloudFront és la peça que falta, i convé ser honest: CloudFront no participa en les traces d'X-Ray. El seu identificador propi és x-amz-cf-id, que apareix als seus registres d'accés. Per correlacionar el temps de la CDN amb la traça, MercadoFresco registra aquest valor com a anotació:

cf_id = request.headers.get("X-Amz-Cf-Id")
if cf_id:
    segment.put_annotation("cf_id", cf_id)

Amb això, donada una traça pots buscar la línia corresponent als registres de CloudFront a S3, i a l'inrevés. No és tan còmode com un node al mapa, però tanca el cercle.

El mapa de serveis: com es llegeix

El mapa de serveis és el gràfic que X-Ray construeix agregant totes les traces de la finestra temporal seleccionada. No és un diagrama d'arquitectura dibuixat per ningú: és el que realment passa, deduït del trànsit.

flowchart LR
    C(("Client")) --> T["mercadofresco-tienda<br/>1.240 t/min<br/>lat. mitjana 0,18 s<br/>errors 0,2%"]
    T --> L["mercadofresco-estado-pedido<br/>Lambda<br/>lat. mitjana 0,48 s"]
    T --> R[("mercadofresco-pedidos<br/>PostgreSQL<br/>lat. mitjana 2,10 s<br/>VERMELL")]
    T --> S3[("mercadofresco-catalogo-fotos<br/>S3")]
    L --> SN["alertas-mercadofresco<br/>SNS"]
    L --> R

Com es llegeix, element a element:

Element Significat
Cercle Un node: un servei, un recurs o el client
Mida del cercle Volum de trànsit
Anell verd Peticions correctes
Anell groc error — fallades 4xx (culpa del client)
Anell vermell fault — fallades 5xx (culpa teva)
Anell morat throttle — estrangulament (429)
Fletxa Crida d'un node a un altre
Node de client L'origen: no és un servei teu

Els tipus de node també importen: X-Ray distingeix els nodes de servei (una cosa que tu instrumentes, amb el seu propi segment) dels nodes de recurs descendent (una base de dades, un bucket d'S3, que no estan instrumentats però el temps dels quals es mesura des del qui crida). Un node d'RDS vermell vol dir que les crides a RDS fallen o triguen, no que el servidor de PostgreSQL informi de res.

Tres patrons que es reconeixen d'un cop d'ull en un mapa real:

  1. Un node vermell aïllat amb tota la resta verda: el culpable és clar.
  2. Tot vermell aigües avall d'un node: hi ha una fallada en cascada; el culpable és el més profund.
  3. Un node que apareix de sobte i no hi hauria de ser: una dependència que algú ha introduït sense dir-ho. És una de les raons per les quals aquest mapa és útil fins i tot sense incidents.

Els grups permeten tenir un mapa filtrat, per exemple només del flux de compra:

aws xray create-group \
  --group-name grupo-mercadofresco-pedidos \
  --filter-expression 'service("mercadofresco-tienda") AND annotation.entorno = "produccion" AND http.url CONTAINS "/api/pedidos"' \
  --insights-configuration InsightsEnabled=true,NotificationsEnabled=true \
  --profile mercadofresco-dev --region eu-west-1

Un grup amb InsightsEnabled genera, a més, mètriques pròpies a CloudWatch, sobre les quals es poden crear alarmes.

Filtres de traça: el llenguatge de cerca

Aquí hi ha la potència real d'X-Ray. El llenguatge d'expressions de filtre:

Expressió Troba
service("mercadofresco-tienda") Traces que passen per aquest servei
service("mercadofresco-pedidos") { fault } Traces on aquest node ha fallat
responsetime > 5 Traces de més de 5 segons
duration > 3 AND duration < 10 Entre 3 i 10 segons
http.status = 500 Per codi de resposta
http.url CONTAINS "/api/pedidos" Per ruta
annotation.pedido_id = "48213" Una comanda concreta
annotation.cliente_hash = "a3f8c1e9" Totes les peticions d'un client
annotation.num_lineas > 25 Comandes grans
annotation.version_app = "2.14.3" Només la versió nova
error = true Fallades 4xx
fault = true Fallades 5xx
throttle = true Estrangulaments
service("estado-pedido") { fault } AND responsetime > 2 Combinacions amb AND, OR, NOT
edge("mercadofresco-tienda", "mercadofresco-pedidos") Traces que recorren aquesta aresta concreta

Des de la CLI:

# Totes les confirmacions lentes de les ultimes 3 hores
aws xray get-trace-summaries \
  --start-time $(date -d '3 hours ago' +%s) \
  --end-time $(date +%s) \
  --filter-expression 'http.url CONTAINS "/api/pedidos/confirmar" AND responsetime > 5' \
  --query 'TraceSummaries[].[Id,Duration,ResponseTime,Http.HttpStatus]' \
  --output table \
  --profile mercadofresco-dev --region eu-west-1

# I el detall complet d'una traca concreta
aws xray batch-get-traces \
  --trace-ids 1-68a2f4c1-3b8d9e2a5f7c1b4d8e0a2f6c \
  --profile mercadofresco-dev --region eu-west-1

Un script que la Marta fa servir quan arriba una queixa concreta, i que substitueix les quatre consultes de Logs Insights de 05-01:

"""Buscar la traca d'una comanda concreta i desglossar on se n'ha anat el temps."""
import boto3
import time

xray = boto3.client("xray", region_name="eu-west-1")


def investigar_comanda(pedido_id, hores=24):
    ara = int(time.time())
    resums = xray.get_trace_summaries(
        StartTime=ara - hores * 3600,
        EndTime=ara,
        FilterExpression=f'annotation.pedido_id = "{pedido_id}"',
    )

    if not resums["TraceSummaries"]:
        print(f"Sense traces per a la comanda {pedido_id}.")
        print("Causes possibles: no s'ha mostrejat, o han passat mes de 30 dies.")
        return

    for resum in resums["TraceSummaries"]:
        traca_id = resum["Id"]
        print(f"\nTraca {traca_id}: {resum['Duration']:.3f} s")

        detall = xray.batch_get_traces(TraceIds=[traca_id])
        for traca in detall["Traces"]:
            for segment in traca["Segments"]:
                import json
                doc = json.loads(segment["Document"])
                dur = doc["end_time"] - doc["start_time"]
                print(f"  [{dur*1000:8.1f} ms] {doc['name']}")
                for sub in doc.get("subsegments", []):
                    d = sub["end_time"] - sub["start_time"]
                    marca = " <-- AQUI" if d > 1.0 else ""
                    print(f"      [{d*1000:8.1f} ms] {sub['name']}{marca}")


investigar_comanda("48213")

Anàlisi de latència: histogrames i percentils

La consola d'X-Ray ofereix, per a cada node del mapa i per a cada cerca, un histograma de latència en escala logarítmica. És l'eina que revela el que un percentil resumeix.

Un histograma unimodal —una sola gepa— vol dir que totes les peticions es comporten de manera semblant: si és lent, és lent per a tothom, i el problema és estructural.

Un histograma bimodal —dues gepes— vol dir que hi ha dues poblacions diferents de peticions, i aquesta és la troballa més valuosa que ofereix X-Ray. El de mercadofresco-tienda al pic del divendres:

Latència Peticions Interpretació
60-200 ms 94 % Peticions normals
200 ms - 2 s 4 % Fitxes de producte amb moltes fotos
5-9 s 2 % La segona gepa: el problema

Un p95 d'1,8 s no hauria mostrat res estrany. El p99 de 8,2 s sí. Però el que de debò convenç és veure que hi ha dues gepes separades: no és una degradació general, és un subconjunt concret de peticions que es comporta d'una altra manera. I a X-Ray pots seleccionar la segona gepa a l'histograma amb el ratolí i veure només aquelles traces. Això porta directament al cas guiat.

Una comparació útil que cal aprendre a fer, entre dues versions de l'aplicació:

# Latencia p95 abans del desplegament de la versio 2.14.3
aws xray get-time-series-service-statistics \
  --start-time $(date -d '2026-07-29 00:00' +%s) \
  --end-time   $(date -d '2026-07-30 00:00' +%s) \
  --group-name grupo-mercadofresco-pedidos \
  --entity-selector-expression 'service("mercadofresco-tienda")' \
  --period 3600 \
  --profile mercadofresco-dev --region eu-west-1

I el filtre que aïlla exactament l'efecte d'un desplegament, que és la pregunta que més vegades es fa després de publicar una versió:

annotation.version_app = "2.14.3" AND responsetime > 2

El cas guiat: la comanda de 8,14 segons

Tornem a la comanda 48213, aquesta vegada amb X-Ray. Compara l'esforç amb les quatre consultes de 05-01.

Pas 1. Trobar la traça. Una sola cerca:

annotation.pedido_id = "48213"

Apareix una traça, 8,14 s. A 05-01 això va costar dues consultes d'Insights i saber el hash del client.

Pas 2. Obrir la vista de cascada. Es veu immediatament:

Tram Durada % del total
validar_stock 57 ms 0,7 %
Invoke mercadofresco-estado-pedido 550 ms 6,8 %
Initialization (arrencada en fred) 315 ms 3,9 %
calcular_portes 12 ms 0,1 %
persistir_pedido 40 ms 0,5 %
SELECT × 39 sobre productos 7.410 ms 91,0 %
Resta 71 ms 0,9 %

Pas 3. Veure la naturalesa del problema. En desplegar el subsegment de la base de dades apareixen 39 subsegments consecutius, cadascun d'uns 190 ms, amb el mateix SQL sanejat:

SELECT precio, iva, promocion FROM productos WHERE id = ?

Això és el que un registre no pot ensenyar: la forma. Trenta-nou barres idèntiques, una darrere de l'altra, sense solapar-se. El diagnòstic es llegeix al gràfic abans que ningú llegeixi el SQL. És el patró N+1: el codi recorre les línies de la comanda i consulta el preu de cada producte per separat.

Pas 4. Confirmar que és sistemàtic i no un cas aïllat. El filtre:

annotation.num_lineas > 25 AND responsetime > 4

Retorna 187 traces en 24 hores. I el complementari:

annotation.num_lineas < 10 AND responsetime > 4

Retorna 2. La lentitud creix amb el nombre de línies de la comanda: confirmat. Aquesta és exactament la pregunta que les anotacions eren allà per respondre, i per això num_lineas era una anotació i no una metadada.

Pas 5. Quantificar l'impacte en el negoci. Amb el filtre anterior sobre 30 dies: 5.610 comandes afectades, amb més de 4 segons d'espera a la confirmació. Són les comandes més grans, és a dir, les de major import. Aquesta dada és la que converteix una tasca tècnica en una prioritat.

Pas 6. Arreglar. Una sola consulta en lloc de 39:

SELECT id, precio, iva, promocion FROM productos WHERE id = ANY(%s);

Pas 7. Verificar amb dades, no amb impressions. Es desplega com a versió 2.14.4 i es compara:

annotation.version_app = "2.14.4" AND annotation.num_lineas > 25
Mètrica Abans (2.14.3) Després (2.14.4)
p50 de confirmació 1,9 s 0,21 s
p95 6,8 s 0,44 s
p99 8,4 s 0,61 s
Subsegments SQL per comanda 39-42 3
DatabaseConnections al pic 185 96

Fixa't en l'última fila, que és un efecte secundari que ningú no havia previst: en eliminar l'N+1, la pressió sobre mercadofresco-pedidos cau a la meitat. L'alarma mercadofresco-rds-conexiones-altas de 05-01 deixa d'estar al límit tots els divendres. Un problema de latència era també un problema de capacitat.

I el pas 8, el que tanca el cicle amb 05-01: ara que sabem quin és el problema, es crea una alarma perquè no torni sense avisar. El grup grupo-mercadofresco-pedidos amb Insights activat publica mètriques a CloudWatch, i la mètrica TiempoConfirmacionPedido que vam crear a 05-01 ja serveix per detectar la regressió. X-Ray diagnostica; CloudWatch vigila. Cap dels dos no substitueix l'altre.

X-Ray Insights

Insights analitza contínuament les traces d'un grup, construeix una línia base de comportament normal i detecta anomalies sense que tu defineixis llindars. Quan en troba una, obre un «insight» amb:

  • El moment d'inici i l'estat (actiu o resolt).
  • La causa arrel probable: el node i el tipus d'error que més hi contribueix.
  • L'impacte: nombre de peticions i d'usuaris afectats.
  • Un gràfic d'anomalia enfront de la línia base.
aws xray update-group \
  --group-name grupo-mercadofresco-pedidos \
  --insights-configuration InsightsEnabled=true,NotificationsEnabled=true \
  --profile mercadofresco-dev --region eu-west-1

aws xray get-insight-summaries \
  --start-time $(date -d '7 days ago' +%s) --end-time $(date +%s) \
  --states ACTIVE CLOSED \
  --group-name grupo-mercadofresco-pedidos \
  --profile mercadofresco-dev --region eu-west-1

Amb NotificationsEnabled=true, cada insight genera un esdeveniment d'EventBridge (07-03), que es pot encaminar a alertas-mercadofresco. Aquest és l'enllaç que converteix X-Ray d'eina de diagnòstic en eina de detecció.

Dues limitacions honestes:

  • Insights detecta anomalies respecte al que és habitual, no problemes. Si el teu servei sempre ha trigat 8 segons, no ho marcarà mai.
  • Necessita volum. Amb poc trànsit mostrejat, la línia base és sorollosa i produeix falsos positius. És una raó més per no mostrejar a l'1 %.

Relació amb CloudWatch ServiceLens

ServiceLens és la vista de CloudWatch que uneix els tres senyals en una sola pantalla. És literalment el mapa de serveis d'X-Ray, però amb les mètriques de CloudWatch i un accés directe als registres correlacionats.

Des de ServiceLens pots… I així…
Veure el mapa amb mètriques de CloudWatch superposades Latència, taxa d'error i peticions per node
Clicar un node i veure'n les mètriques Sense canviar de consola
Clicar un node i veure'n els registres Filtrats per la finestra temporal del pic
Passar d'un pic de latència a les traces d'aquest pic En dos clics
Veure els contribuents principals a l'error Quina URL, quina instància

Requisit: X-Ray actiu i, per a la correlació de registres, que els grups estiguin associats. El flux de treball que ServiceLens habilita, i que és el que la Marta acaba fent servir cada dia:

flowchart LR
    A["Alarma CloudWatch<br/>latencia p95 alta"] --> B["ServiceLens:<br/>quin node esta vermell?"]
    B --> C["X-Ray: traces<br/>d'aquest pic"]
    C --> D["Cascada:<br/>quin subsegment?"]
    D --> E["Logs Insights:<br/>que deia el registre<br/>en aquell instant"]
    E --> F["Diagnostic"]

Mètrica → mapa → traça → subsegment → registre. Aquest és el camí complet, i ara MercadoFresco el té sencer.

OpenTelemetry i ADOT: l'alternativa oberta

Seria deshonest ensenyar X-Ray sense dir que avui l'estàndard del sector és OpenTelemetry (OTel), un projecte de la CNCF que defineix una API, un SDK i un protocol (OTLP) neutrals respecte al proveïdor.

AWS Distro for OpenTelemetry (ADOT) és la distribució d'AWS d'OpenTelemetry, amb suport oficial, que pot enviar traces a X-Ray i a qualsevol altra destinació alhora.

X-Ray SDK OpenTelemetry / ADOT
Estàndard Propietari d'AWS Obert (CNCF)
Senyals Traces Traces, mètriques i registres
Destinacions X-Ray X-Ray, Prometheus, Jaeger, Grafana, Datadog…
Llenguatges 6 oficials Dotzenes
Instrumentació automàtica Bona a AWS Molt àmplia, amb agent automàtic a Java/Python/Node
Propagació de context Pròpia (X-Amzn-Trace-Id) W3C Trace Context (traceparent) + AWS
Maduresa a AWS Molt alta, anys Alta i creixent
Complexitat inicial Baixa Mitjana (hi ha un col·lector per configurar)
Risc de dependència Alt Baix
Recomanació d'AWS avui Amb suport Preferit per a projectes nous

Què recomanar, amb criteri:

  • Projecte nou, o amb intenció de no lligar-se a AWS: OpenTelemetry amb ADOT. El cost inicial extra —configurar el col·lector— es paga sol la primera vegada que vulguis enviar les mateixes traces a un altre lloc, o migrar.
  • Projecte existent ja instrumentat amb el SDK d'X-Ray, tot dins d'AWS: no hi ha pressa. Funciona, té suport i migrar té un cost.
  • Cas de MercadoFresco: la Marta ha instrumentat amb el SDK d'X-Ray perquè és el més ràpid de posar en marxa i tot el seu sistema és a AWS. Ha anotat al registre de decisions tècniques que la migració a ADOT és l'evolució natural quan l'aplicació creixi o si algun dia es planteja multinúvol. No és deute tècnic ocult: és una decisió conscient amb data de revisió.

Una nota de compatibilitat que estalvia un mal tràngol: X-Ray propaga X-Amzn-Trace-Id i OTel propaga traceparent (W3C). Si barreges tots dos en un mateix sistema, cal configurar el propagador xray a OTel perquè les traces no es trenquin a la frontera.

Un exemple mínim d'ADOT en Python, perquè en vegis la diferència de forma:

pip install aws-opentelemetry-distro

# La instrumentacio automatica no toca el teu codi
OTEL_PYTHON_DISTRO=aws_distro \
OTEL_PYTHON_CONFIGURATOR=aws_configurator \
OTEL_TRACES_EXPORTER=otlp_proto_http \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces \
OTEL_PROPAGATORS=xray \
OTEL_RESOURCE_ATTRIBUTES="service.name=mercadofresco-tienda" \
opentelemetry-instrument python app.py

Fixa't que no hi ha ni una sola línia de codi de l'aplicació: opentelemetry-instrument embolcalla el procés i instrumenta les llibreries conegudes. Això, per a una aplicació gran i existent, és un avantatge enorme.

Cost i estratègia de mostreig econòmica

Concepte Preu de referència Capa gratuïta mensual
Traça registrada 5,00 USD per milió 100.000
Traça recuperada o escanejada 0,50 USD per milió 1.000.000
X-Ray Insights 1,00 USD per milió de traces analitzades
Emmagatzematge Inclòs (30 dies)

«Traça recuperada» és cada vegada que algú obre una traça o executa un filtre. Consultar és barat; registrar és el que surt car.

Càlcul per a MercadoFresco. Trànsit: uns 2 milions de peticions al dia, 60 milions al mes.

Escenari A: sense regles, mostreig per defecte (1/s + 5 %).

Concepte Traces/mes Cost
Registrades ~3,3 milions 16,50 USD
Recuperades ~200.000 0,00 USD (capa gratuïta)
Total ~16,50 USD

Escenari B: amb les regles de MercadoFresco.

Regla Peticions/mes Mostreig Traces
/salud 691.200 0 % 0
Estàtics 34.000.000 0 % 0
/api/pedidos/* 660.000 100 % 660.000
/admin/* 40.000 50 % 20.000
Resta 24.600.000 1/s + 5 % ~1.400.000
Total registrades ~2,08 milions
Concepte Cost
Registrades: (2.080.000 − 100.000) × 5 / 1.000.000 9,90 USD
Recuperades: ~500.000 0,00 USD
Insights sobre el grup de comandes ~0,70 USD
Total ~10,60 USD/mes

Menys cost que l'escenari A i amb el 100 % de les confirmacions de comanda traçades, que és l'única cosa que de debò importa. Això és el que fa una bona estratègia de mostreig: no traça menys, traça millor.

Escenari C: l'error, mostreig al 100 % de tot.

60 milions de traces: 300 USD/mes, més l'impacte en el rendiment del dimoni i en la usabilitat de la consola, que s'omple de traces de /salud. No ho facis mai fora d'una finestra de depuració acotada.

Les cinc regles de l'estratègia econòmica:

  1. Excloure sempre /salud i els estàtics. És el 57 % del trànsit de MercadoFresco i el seu valor diagnòstic és zero.
  2. Traçar al 100 % les rutes crítiques de negoci. Són poques peticions i són les que importen.
  3. Dipòsit d'almenys 1/s a la regla per defecte, per tenir exemples fins i tot de matinada.
  4. Pujar el mostreig temporalment durant un incident i abaixar-lo en acabar. Les regles són centralitzades i s'apliquen en 10 segons, sense desplegar.
  5. Vigilar el cost amb un pressupost filtrat per servei = X-Ray (mòdul 11).

I una advertència de rendiment, no de cost: cada subsegment té un preu en mida. Un segment no pot passar de 64 KB, i un patch_all() sobre una aplicació que fa milers de consultes petites per petició genera segments enormes que el dimoni acaba descartant. Si veus SegmentsRejectedCount al registre del dimoni, revisa què estàs instrumentant.

Neteja

# Desactivar el rastreig a Lambda
aws lambda update-function-configuration \
  --function-name mercadofresco-estado-pedido \
  --tracing-config Mode=PassThrough \
  --profile mercadofresco-dev --region eu-west-1

# Esborrar les regles de mostreig
for R in muestreo-mercadofresco-checkout muestreo-mercadofresco-admin \
         muestreo-mercadofresco-estaticos muestreo-mercadofresco-salud; do
  aws xray delete-sampling-rule --rule-name "$R" \
    --profile mercadofresco-dev --region eu-west-1
done

# Esborrar el grup
aws xray delete-group --group-name grupo-mercadofresco-pedidos \
  --profile mercadofresco-dev --region eu-west-1

# Treure els permisos
aws iam detach-role-policy --role-name rol-mercadofresco-tienda \
  --policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess \
  --profile mercadofresco-dev

# I a les instancies, aturar el dimoni
sudo systemctl disable --now xray

Important: les traces ja registrades no es poden esborrar i caduquen soles als 30 dies. No hi ha emmagatzematge que continuï costant. El que sí que cal fer és treure la instrumentació del codi si no la penses fer servir més, o com a mínim deixar context_missing="LOG_ERROR" perquè un SDK sense dimoni no generi errors al registre.

Errors Habituals i Consells

1. «He activat X-Ray i no veig res». Per ordre de probabilitat: (a) el dimoni no s'està executant o no escolta al 2000/UDP; (b) falta xray:PutTraceSegments al rol; (c) la petició va arribar amb Sampled=0 i la decisió es respecta aigües avall; (d) estàs mirant la regió equivocada.

2. Posar anotacions al segment arrel d'una Lambda. S'ignoren en silenci: el segment arrel el controla l'entorn d'execució. Les anotacions van en un subsegment creat per tu.

3. Deixar context_missing amb el seu valor per defecte. Qualsevol codi instrumentat fora d'una petició HTTP —tasques programades, fils, scripts— llançarà excepcions i tombarà el procés. Posa-hi sempre LOG_ERROR.

4. Confondre anotació amb metadada. Si no pots filtrar per alguna cosa, és que la vas posar com a metadada. Només les anotacions estan indexades, i n'hi ha un màxim de 50 per traça.

5. Posar dades personals a les anotacions. Correu, nom, adreça, targeta: mai. Fes servir un hash. Les traces les veu tothom qui tingui permís de lectura, i viuen 30 dies.

6. Traçar el health check. El /salud cada 15 segons genera desenes de milers de traces inútils al mes, embruta els histogrames i desplaça els percentils. És la primera exclusió que cal crear.

7. Mostrejar al 100 % «per no perdre's res». 300 USD al mes en el cas de MercadoFresco, una consola inservible i el dimoni descartant segments. Traçar millor no és traçar més.

8. Perdre el context en fils. El segment viu en una variable per fil. Si engegues un fil nou, li has de passar el segment explícitament. És l'error més freqüent en aplicacions amb paral·lelisme.

9. Buscar traces de fa dos mesos. No existeixen: X-Ray reté 30 dies i punt. Si necessites anàlisi històrica, exporta el que t'interessi a S3 mentre estigui disponible.

10. Esperar que CloudFront aparegui al mapa. No hi apareix. Correlaciona amb l'anotació cf_id i els registres de la CDN a S3.

11. Instrumentar sense propagar a les crides pròpies. Si el teu codi crida un altre servei amb un client HTTP que el SDK no pedaça, la capçalera X-Amzn-Trace-Id no viatja i la traça es talla aquí. Es propaga a mà.

12. Fer servir X-Ray com a substitut dels registres. No ho és. Les traces estan mostrejades: la petició que busques pot no ser-hi. Els registres ho tenen tot. Es complementen: la traça et diu on mirar, el registre et diu què deia.

Consell final: la inversió que més rendiment dona a X-Ray no és la instrumentació, sinó triar bé cinc o sis anotacions. pedido_id, cliente_hash i num_lineas són les que han resolt aquest cas. Pensa quines preguntes et faran —«a aquest client li passa sempre», «va començar amb el desplegament de dimarts», «només amb comandes grans»— i anota exactament el que cal per respondre-les amb un filtre.

Exercicis

Exercici 1: dissenyar l'estratègia de mostreig d'un servei nou

MercadoFresco llança una API pública perquè les botigues de barri associades consultin estoc i creïn comandes a l'engròs. Trànsit previst:

Ruta Peticions/dia Latència típica Criticitat
GET /api/v1/stock/{sku} 4.000.000 25 ms Mitjana
GET /api/v1/catalogo 120.000 300 ms Mitjana
POST /api/v1/pedidos-mayoristas 3.000 1,8 s Màxima
POST /api/v1/facturas 800 4,0 s Màxima
GET /salud 5.760 2 ms Nul·la
GET /api/v1/docs 400 40 ms Nul·la

Requisits: cada comanda i cada factura han de poder investigar-se individualment; cal poder diagnosticar la latència d'/stock sense arruïnar-se; el pressupost d'X-Ray és de 25 USD al mes.

Dissenya el conjunt complet de regles de mostreig amb prioritats, dipòsits i taxes. Calcula el nombre de traces mensuals i el cost. Justifica cada regla i digues quines anotacions posaries a l'API.

Exercici 2: llegir una cascada i identificar tres problemes

Aquesta és la cascada d'una traça de 6,45 segons de POST /api/pedidos/confirmar:

[0 ms]      mercadofresco-tienda                                  6.450 ms
[12 ms]     ├─ subsegment: cargar_configuracion                      890 ms
[905 ms]    ├─ subsegment: SecretsManager GetSecretValue             310 ms
[1.220 ms]  ├─ subsegment: validar_stock                              45 ms
[1.270 ms]  ├─ subsegment: SELECT ... FROM productos WHERE id=?       95 ms
[1.370 ms]  ├─ subsegment: SELECT ... FROM productos WHERE id=?       92 ms
[1.465 ms]  ├─ subsegment: SELECT ... FROM productos WHERE id=?       88 ms
[1.560 ms]  ├─ (repetit 11 vegades mes, en serie)                  1.030 ms
[2.595 ms]  ├─ subsegment: Invoke mercadofresco-estado-pedido      3.780 ms
[2.610 ms]  │   └─ SEGMENT Lambda                                  3.750 ms
[2.615 ms]  │       ├─ Initialization                              2.980 ms
[5.600 ms]  │       ├─ SNS Publish                                    95 ms
[5.700 ms]  │       └─ SELECT ... FROM pedidos                        60 ms
[6.380 ms]  └─ subsegment: respondre                                  70 ms

Identifica almenys tres problemes diferents, ordena'ls per impacte en mil·lisegons, proposa una solució concreta per a cadascun i estima la latència resultant. Indica també quina anotació o quin subsegment afegiries per poder detectar cadascun d'aquests problemes de manera automàtica en el futur.

Exercici 3: el cas que X-Ray no resol sol

Un dilluns, la Sara avisa que el 3 % de les comandes de l'última setmana no han arribat a generar la factura. Els clients tenen la comanda confirmada i el repartiment en marxa, però no hi ha factura. No hi ha errors a CloudWatch, no ha saltat cap alarma, mercadofresco-estado-pedido no registra Errors, i a X-Ray les traces d'aquestes comandes apareixen completes i en verd.

Arquitectura implicada: la botiga confirma la comanda, publica al tema alertas-mercadofresco, i un procés de facturació subscrit a aquest tema genera el PDF i el puja a mercadofresco-informes-analitica. Aquest procés de facturació no està instrumentat amb X-Ray.

Explica: per què X-Ray no ho ha detectat, què li falta a l'observabilitat de MercadoFresco, quina combinació de les eines de 05-01 i 05-02 faries servir per diagnosticar-ho, i quina instrumentació i quina alarma deixaries muntades perquè la propera vegada es detecti en minuts. Sigues concret amb els comandes.

Solucions

Solució 1

Anàlisi prèvia. El trànsit total és de 4.124.960 peticions/dia ≈ 124 milions/mes. Al mostreig per defecte (5 %) serien 6,2 milions de traces: 30,50 USD. Ens passem del pressupost i, a sobre, gastaríem gairebé tot en traces d'/stock idèntiques.

Regles proposades:

Prioritat Nom Coincideix Dipòsit Taxa Traces/mes
100 muestreo-api-facturas POST /api/v1/facturas 1/s 100 % 24.000
110 muestreo-api-pedidos-mayoristas POST /api/v1/pedidos-mayoristas 1/s 100 % 90.000
200 muestreo-api-catalogo GET /api/v1/catalogo 1/s 10 % ~400.000
300 muestreo-api-stock GET /api/v1/stock/* 1/s 0,5 % ~600.000
800 muestreo-api-salud GET /salud 0/s 0 % 0
810 muestreo-api-docs GET /api/v1/docs 0/s 0 % 0
9000 muestreo-api-defecto Resta 1/s 5 % ~50.000

Càlcul detallat d'/stock, que és on hi ha el 97 % del trànsit i on es decideix tot:

  • 4.000.000/dia ÷ 86.400 s = 46,3 peticions/segon.
  • Dipòsit: 1/s × 2.592.000 s/mes = 2.592.000… que ja és massa. Compte amb el dipòsit quan el volum és alt: el dipòsit és un terra, no un sostre, i amb 46 peticions per segon sempre n'hi ha una per traçar.

Ho refem: amb dipòsit 1/s el terra mensual és de 2,59 milions de traces només d'/stock (12,95 USD). És massa per al que aporta. Dipòsit 0 i taxa fixa del 0,05 %:

  • 124.000.000 × 0,0005 = 62.000 traces/mes. Suficient per a un histograma de latència representatiu d'una ruta que respon en 25 ms.

Taula corregida:

Prioritat Nom Dipòsit Taxa Traces/mes
100 muestreo-api-facturas 1/s 100 % 24.000
110 muestreo-api-pedidos-mayoristas 1/s 100 % 90.000
200 muestreo-api-catalogo 1/s 10 % ~360.000
300 muestreo-api-stock 0/s 0,05 % ~62.000
800 muestreo-api-salud 0/s 0 % 0
810 muestreo-api-docs 0/s 0 % 0
9000 muestreo-api-defecto 1/s 5 % ~50.000
Total ~586.000

Cost: (586.000 − 100.000) × 5 / 1.000.000 = 2,43 USD/mes de registre, més recuperacions dins de la capa gratuïta. Molt per sota dels 25 USD, i amb el 100 % de factures i comandes a l'engròs traçat.

Amb el marge sobrant es pot pujar /catalogo al 25 % i activar Insights sobre el grup de l'API.

La lliçó de l'exercici: el dipòsit és perillós en rutes d'altíssim volum, perquè garanteix un terra d'una traça per segon que en un mes són 2,6 milions. Per al trànsit massiu, dipòsit 0 i taxa molt baixa; per al trànsit escàs i valuós, dipòsit alt i taxa 100 %.

Anotacions proposades per a l'API:

Anotació Motiu
tienda_id Cada botiga associada és un client; cal poder aïllar-la
factura_id / pedido_mayorista_id Requisit explícit: investigació individual
num_lineas El mateix que a la botiga: la lentitud creix amb la mida
version_api v1, v2: comparar versions
plan_tarifa Els clients premium tenen millor latència?
sku No: 40.000 valors. Aniria com a metadada o al registre

Solució 2

Problema 1 — Arrencada en fred de la Lambda: 2.980 ms (46 % del total).

El subsegment Initialization de gairebé 3 segons és una arrencada en fred patològica. El normal en Python és 200-600 ms; 2.980 ms indica import molt pesats —típicament pandas, numpy, un SDK sencer— o una connexió establerta a l'àmbit del mòdul.

Solucions, per ordre de cost-benefici:

  • Revisar els import i moure al handler els que només es fan servir en alguns camins.
  • Fer servir boto3.client() a nivell de mòdul (això està bé: es reutilitza entre invocacions) però no obrir-hi connexions a la base de dades.
  • Reduir la mida del paquet de desplegament; fer servir capes per a les dependències grans.
  • Si després d'això continua sent alt i la latència importa: concurrència provisionada (02-05), que elimina l'arrencada en fred a canvi de pagar per capacitat reservada.

Estalvi estimat: de 2.980 a ~400 ms. −2.580 ms.

Problema 2 — L'N+1, una altra vegada: 1.305 ms (20 %).

Catorze SELECT ... FROM productos WHERE id=? en sèrie, d'uns 93 ms cadascun. El mateix patró del cas guiat, en una altra ruta.

Solució: WHERE id = ANY(...) en una sola consulta. Estalvi: d'1.305 a ~100 ms. −1.205 ms.

Nota: 93 ms per un SELECT per clau primària també és alt. Val la pena revisar si hi ha un índex adequat, si la connexió s'estableix a cada consulta (no hi ha agrupació de connexions), o si la instància d'RDS està saturada. Un subsegment conectar_bd separat ho aclariria.

Problema 3 — Configuració carregada a cada petició: 890 ms (14 %).

cargar_configuracion de 890 ms al principi de cada petició és configuració llegida de disc, de xarxa o de Parameter Store en calent. Hauria de carregar-se un cop en arrencar el procés i desar-se a memòria.

Solució: carregar a l'inici del procés i refrescar cada N minuts en un fil de fons. Estalvi: de 890 a ~1 ms. −889 ms.

Problema 4 — Secrets Manager a la ruta crítica: 310 ms (5 %).

GetSecretValue a cada petició. Com vam veure a 04-03, el secret rota cada 30 dies: no hi ha cap raó per llegir-lo a cada compra. Es desa a la memòria cau amb un TTL de 5-15 minuts i es reintenta en rebre un error d'autenticació.

Estalvi: de 310 a ~0 ms en el 99,9 % de les peticions. −310 ms.

Problema 5 — La crida a la Lambda és síncrona i bloquejant.

Els 3.780 ms de l'Invoke són enterament a la ruta crítica del client. Però, de debò cal esperar que es calculi l'estat de la comanda abans de respondre «confirmada»? Gairebé amb tota seguretat no: és un bon candidat a invocació asíncrona o a una cua (07-01).

Estalvi potencial: els 3.780 ms sencers desapareixen del que el client percep.

Resum ordenat per impacte:

# Problema Estalvi
1 Arrencada en fred de la Lambda −2.580 ms
2 N+1 de productes −1.205 ms
3 Configuració a cada petició −889 ms
4 Secret sense memòria cau −310 ms
5 Invocació síncrona innecessària −3.780 ms (arquitectural)

Latència resultant aplicant 1-4: 6.450 − 4.984 = ~1.470 ms. Aplicant-hi a més el 5: ~430 ms. De 6,45 s a menys de mig segon.

Instrumentació per detectar-ho automàticament en el futur:

Què afegir Detecta
Subsegment conectar_bd separat de la consulta Falta d'agrupació de connexions
Anotació num_consultas_sql per petició L'N+1, amb un filtre annotation.num_consultas_sql > 10
Anotació cache_config_hit (booleà) Configuració recarregada indegudament
Anotació arranque_frio a la Lambda Traces afectades per arrencada en fred
Alarma sobre el p99 de Duration de la Lambda La regressió d'arrencada en fred
X-Ray Insights al grup Canvis de comportament no previstos

L'anotació num_consultas_sql es mereix un comentari: és una anotació derivada, calculada per la mateixa aplicació comptant les consultes de la petició. Amb ella, un filtre annotation.num_consultas_sql > 10 troba tots els N+1 del sistema, presents i futurs, sense saber per endavant on són. És el tipus d'anotació que distingeix una instrumentació pensada d'una copiada.

Solució 3

Per què X-Ray no ho ha detectat, i és important entendre-ho bé.

X-Ray traça el que està instrumentat. El procés de facturació no ho està, així que per a X-Ray no existeix. Les traces de la botiga acaben a SNS Publish amb estat 200 —el missatge que es va publicar correctament— i tot apareix verd. La fallada és després de l'últim punt instrumentat, i aquest és el punt cec estructural de qualsevol sistema de traces.

I hi ha un segon motiu, més subtil: encara que instrumentessis el facturador, el patró és asíncron. La botiga publica i se n'oblida. La traça de la botiga acaba en publicar; la del facturador seria una traça diferent, disparada pel missatge. X-Ray les relaciona si el SDK propaga la capçalera a través d'SNS (ho fa, als atributs del missatge), però l'absència d'una traça no genera cap senyal. Ningú no està comptant quants missatges s'haurien d'haver processat.

El que li falta a l'observabilitat de MercadoFresco: no té cap comprovació que les dues meitats d'un procés asíncron quadrin. És el buit clàssic de les arquitectures basades en esdeveniments, i es resol amb reconciliació, no amb traces.

Diagnòstic, pas a pas:

1. Quantificar i acotar en el temps. Consulta directa a la base de dades per saber quants i quan:

SELECT date_trunc('hour', p.confirmado_en) AS hora,
       count(*) FILTER (WHERE f.id IS NULL) AS sin_factura,
       count(*) AS total
FROM pedidos p
LEFT JOIN facturas f ON f.pedido_id = p.id
WHERE p.confirmado_en > now() - interval '7 days'
GROUP BY 1 ORDER BY 1;

Si les fallades es concentren en unes hores concretes, hi ha una causa puntual; si estan repartides uniformement al 3 %, hi ha una fallada probabilística —temps d'espera esgotat, condició de cursa, límit de concurrència—.

2. Comprovar la baula d'SNS. Les mètriques d'SNS a CloudWatch diuen si el missatge va sortir i si el lliurament va fallar:

aws cloudwatch get-metric-statistics \
  --namespace AWS/SNS --metric-name NumberOfNotificationsFailed \
  --dimensions Name=TopicName,Value=alertas-mercadofresco \
  --start-time $(date -d '7 days ago' -u +%FT%TZ) \
  --end-time $(date -u +%FT%TZ) \
  --period 3600 --statistics Sum \
  --profile mercadofresco-dev --region eu-west-1

Si NumberOfNotificationsFailed és zero, el missatge va arribar al facturador i el problema és dins d'ell. Si no és zero, el problema és de lliurament i cal mirar la política de reintents d'SNS (07-02) i si hi ha cua de missatges fallits.

3. Mirar els registres del facturador amb Logs Insights. Aquí és on 05-01 fa la feina:

fields @timestamp, @message, pedido_id, error
| filter ispresent(pedido_id)
| stats count() as eventos by pedido_id
| filter eventos < 2
| limit 50

És a dir: comandes que van entrar al facturador però no van arribar a registrar la línia de finalització. I la comprovació creuada definitiva, buscant una comanda concreta sense factura a tots els grups alhora:

aws logs start-query \
  --log-group-names /mercadofresco/tienda/aplicacion \
                    /mercadofresco/facturacion/aplicacion \
  --start-time $(date -d '3 days ago' +%s) --end-time $(date +%s) \
  --query-string 'fields @timestamp, @log, @message
                  | filter @message like /48213/
                  | sort @timestamp asc' \
  --profile mercadofresco-dev --region eu-west-1

Si la comanda apareix a la botiga i no apareix mai a facturació, el missatge s'ha perdut. Si apareix i es talla a mitges, el procés va morir: memòria, temps d'espera esgotat, excepció no capturada.

Diagnòstic probable —i el més freqüent en aquest escenari—: el facturador triga més del que li permet el seu temps d'espera en generar el PDF de comandes grans, mor, i SNS no reintenta indefinidament. Sense cua de missatges fallits, el missatge desapareix en silenci. És un 3 % estable: les comandes més grans.

El que cal deixar muntat, en quatre capes:

a) Instrumentar el facturador amb X-Ray. És el primer i el més obvi:

from aws_xray_sdk.core import xray_recorder, patch_all
xray_recorder.configure(service="mercadofresco-facturacion",
                        context_missing="LOG_ERROR")
patch_all()

Amb anotacions pedido_id i factura_id. A partir d'aquí, la traça d'una comanda inclou la seva facturació i el filtre service("mercadofresco-facturacion") { fault } troba les fallades.

b) Una cua de missatges fallits. Sense ella no hi ha manera de saber què s'ha perdut. MercadoFresco ja té el patró muntat amb mercadofresco-miniaturas-fallidas (02-05); aquí cal l'equivalent, i amb una alarma sobre ApproximateNumberOfMessagesVisible > 0. El patró complet —cues, reintents, idempotència— és la lliçó 07-05.

c) La mètrica de reconciliació, que és la solució de fons. Un procés que cada 15 minuts compara comandes confirmades amb factures emeses i publica la diferència:

"""Reconciliacio: publica quantes comandes fa mes de 30 minuts que no tenen factura."""
import boto3

cw = boto3.client("cloudwatch", region_name="eu-west-1")

pendents = comptar_comandes_sense_factura(antiguitat_minuts=30)

cw.put_metric_data(
    Namespace="MercadoFresco/Tienda",
    MetricData=[{
        "MetricName": "PedidosSinFactura",
        "Dimensions": [{"Name": "Entorno", "Value": "produccion"}],
        "Value": pendents,
        "Unit": "Count",
    }],
)

I la seva alarma:

aws cloudwatch put-metric-alarm \
  --alarm-name mercadofresco-pedidos-sin-factura \
  --alarm-description "Hi ha comandes confirmades sense factura despres de 30 minuts" \
  --namespace MercadoFresco/Tienda --metric-name PedidosSinFactura \
  --dimensions Name=Entorno,Value=produccion \
  --statistic Maximum --period 900 --evaluation-periods 2 --datapoints-to-alarm 2 \
  --threshold 5 --comparison-operator GreaterThanThreshold \
  --treat-missing-data breaching \
  --alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
  --profile mercadofresco-dev --region eu-west-1

Amb --treat-missing-data breaching: si el mateix procés de reconciliació deixa d'executar-se, això també és un incident.

d) La regla general que cal extreure. En un sistema asíncron, les traces no detecten el que no va passar. Només detecten el que va passar malament. Per al que no va passar cal una mètrica de reconciliació que compari les dues meitats del procés: comandes enfront de factures, missatges enviats enfront de processats, fitxers pujats enfront de miniatures generades.

És exactament el mateix raonament que a 05-01 va portar a posar --treat-missing-data breaching a mercadofresco-sin-pedidos: l'absència d'un senyal és un senyal, però només si algú l'està comptant.

Conclusió

MercadoFresco ha passat del «alguna cosa va lenta» al «aquesta crida concreta triga 6,2 segons, i 5,9 són dins d'una consulta». Saps per què les mètriques i els registres no basten: les mètriques agreguen i perden el cas individual, els registres conserven el cas però no la relació causal entre el que va passar a la botiga i el que va passar a la Lambda. Només una traça guarda l'estructura completa d'una petició.

Domines el model: traça, segment per servei i subsegment per tram, amb error (groc, 4xx), fault (vermell, 5xx) i throttle (morat). Coneixes l'ID de traça amb la seva marca de temps incrustada —i per què això implica els 30 dies de retenció— i la capçalera X-Amzn-Trace-Id amb els seus camps Root, Parent i Sampled, amb la regla que més despista: la decisió de mostreig es pren un cop a l'origen i tothom la respecta, així que «he activat X-Ray a la Lambda i no veig res» gairebé sempre vol dir que va arribar amb Sampled=0.

Saps configurar el mostreig amb el seu dipòsit i la seva taxa fixa, i per què aquesta combinació traça gairebé tot quan hi ha poc trànsit i un percentatge estable quan n'hi ha molt. Has muntat les regles de MercadoFresco: 100 % d'/api/pedidos/*, 0 % d'/salud i dels estàtics, 5 % per defecte. I has après que el dipòsit és traïdor en rutes d'altíssim volum, on 1/s són 2,6 milions de traces al mes.

Tens clara la distinció que decideix si trobaràs alguna cosa: anotacions indexades i filtrables (màxim 50, cardinalitat lliure) enfront de metadades que només es veuen en obrir la traça. I saps que pedido_id com a anotació d'X-Ray és gratis i cercable, mentre que com a dimensió de mètrica a CloudWatch costaria 190.000 USD al mes: és exactament el buit que X-Ray cobreix. Amb l'advertència de privacitat corresponent: cliente_hash, mai el correu.

Has instrumentat la botiga amb aws_xray_sdkxray_recorder.configure amb context_missing="LOG_ERROR" perquè l'observabilitat no tombi mai l'aplicació, patch_all() que instrumenta boto3 i psycopg2 de manera que cada consulta genera el seu subsegment, i el EC2Plugin que afegeix la instància i l'AZ—, has creat subsegments manuals amb in_subsegment, i saps que en fils cal passar el context a mà. Has desplegat el dimoni a les instàncies de l'ASG, entenent per què existeix: el SDK escriu UDP local i no espera mai la xarxa. I has activat el rastreig a mercadofresco-estado-pedido amb una política i Mode=Active, sabent que a Lambda les anotacions van en un subsegment perquè el segment arrel és de només lectura, i que el subsegment Initialization és l'arrencada en fred que a 02-05 només podíem intuir.

Saps llegir el mapa de serveis —mida, colors, nodes de client i de recurs, i els tres patrons que es reconeixen d'un cop d'ull— i cercar amb el llenguatge de filtres: service(), fault, responsetime > 5, edge(), annotation.pedido_id = "48213". I saps llegir un histograma bimodal, que és on de debò es veuen les dues poblacions de peticions que un percentil resumeix en un número.

I has tancat el cas que arrossegàvem des del mòdul 4. La comanda 48213: una sola cerca per anotació, la cascada, i allà hi era —39 subsegments consecutius de 190 ms, el 91 % del temps total, un N+1—. Confirmat com a sistemàtic amb annotation.num_lineas > 25 AND responsetime > 4, quantificat en 5.610 comandes afectades en 30 dies, corregit amb WHERE id = ANY(...), i verificat: p95 de 6,8 s a 0,44 s, i de regal DatabaseConnections de 185 a 96 al pic del divendres. Compara això amb les quatre consultes i el full de càlcul de 05-01.

Coneixes X-Ray Insights amb les seves notificacions via EventBridge, ServiceLens com la vista que uneix mètrica → mapa → traça → subsegment → registre, i OpenTelemetry amb ADOT com l'estàndard obert que avui AWS recomana per a projectes nous, amb la decisió de MercadoFresco anotada i amb data de revisió en lloc d'amagada. I saps que tot això costa uns 10,60 USD al mes gràcies a una estratègia de mostreig que traça millor, no més, enfront dels 300 USD de traçar-ho tot.

Queda una pregunta del mòdul 4 sense respondre, i és la que ni les mètriques ni les traces poden contestar. CloudWatch sap el que l'aplicació diu d'ella mateixa. X-Ray sap per on ha passat una petició. Cap dels dos no sap qui ha cridat l'API d'AWS. Ningú no sap encara qui va desxifrar la darrera còpia de la base de dades amb alias/mercadofresco-datos, ni qui va llegir el secret mercadofresco/produccion/rds/mfadmin, ni des de quina adreça IP, ni si alguna d'aquestes crides va fallar amb AccessDenied perquè algú estava provant portes.

Aquest registre existeix, es diu AWS CloudTrail i ho porta gravant tot des del primer dia sense que ningú l'hagi mirat. A la lliçó 05-03, «AWS CloudTrail», veurem la diferència essencial entre registrar crides a l'API i registrar el que diu la teva aplicació, l'historial gratuït de 90 dies enfront d'un trail persistent, com crear trail-mercadofresco cap a un bucket xifrat amb validació d'integritat —i per què aquest bucket ha de ser impossible d'esborrar—, l'anatomia comentada d'un esdeveniment real de kms:Decrypt, la diferència de cost entre esdeveniments de gestió i esdeveniments de dades, i com investigar amb Athena i SQL qui va assumir un rol, qui va llegir un secret i quines crides van fallar amb AccessDenied.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats