La lliçó anterior va deixar MercadoFresco a mig desacoblar. cola-mercadofresco-pedidos funciona, la confirmació baixa a 400 ms i l'ERP pot caure sense endur-se vendes per davant, però la cua és un canal punt a punt: cada missatge el processa un consumidor i desapareix. Amb magatzem, correu, repartiment i analítica interessats en el mateix fet, la botiga ha acabat enviant quatre missatges a quatre cues diferents. Torna a saber qui són els seus consumidors, que és exactament el que volíem evitar. I quan màrqueting demani assabentar-se de les comandes per al seu programa de fidelització, caldrà tocar el codi de la botiga, desplegar-lo i provar-lo.

Amazon SNS (Simple Notification Service) és el servei de publicació/subscripció d'AWS. La botiga publica una vegada en un tema —«s'ha confirmat la comanda PED-084417»— i SNS lliura una còpia a cada subscriptor. Qui hi estigui subscrit és un detall de configuració, no de codi. Afegir màrqueting passa de ser un desplegament a ser una ordre d'una línia.

En aquesta lliçó el Luis crea mercadofresco-pedido-confirmado, munta el patró fan-out SNS→SQS amb les cues de magatzem, correu i analítica, filtra per atributs perquè la cua del repartiment en 24 hores només rebi el que li pertoca, i entén per fi què era realment el tema alertas-mercadofresco que fem servir des del mòdul 5.

Avís de cost. SNS cobra per publicació i per lliurament: el primer milió de peticions de l'API és gratis, i els lliuraments a SQS i Lambda també ho són. El que costa de debò és l'SMS (uns 0,06 USD per missatge a Espanya) i, en menor mesura, el correu. Un bucle de proves enviant SMS pot generar una factura sorprenent en minuts. Al final tens la neteja. Totes les dades són fictícies.

Contingut

  1. Publicació/subscripció davant de la cua punt a punt
  2. Temes, publicadors i subscriptors
  3. Tipus de subscripció i les seves garanties
  4. Confirmació de subscripció i els seus paranys
  5. El patró fan-out SNS→SQS
  6. Per què una cua al mig i no una Lambda directa
  7. Muntar el fan-out de MercadoFresco per CLI i amb boto3
  8. Filtratge per atributs i per cos del missatge
  9. Reintents, polítiques de lliurament i DLQ del mateix SNS
  10. Missatges amb estructura per protocol
  11. Temes FIFO i el seu encaix amb cues FIFO
  12. Seguretat: política de tema, xifratge i accés entre comptes
  13. alertas-mercadofresco revisitat, SMS i correu transaccional
  14. Mètriques i depuració de lliuraments
  15. Cost i neteja
  16. Errors habituals i consells
  17. Exercicis
  18. Conclusió

Publicació/subscripció davant de la cua punt a punt

La diferència entre SQS i SNS no és de tecnologia, és de qui coneix qui.

Cua (SQS) Tema (SNS)
Model Punt a punt Publicació/subscripció
Consumidors per missatge Un (el que l'agafi) Tots els subscrits
Qui coneix qui El productor coneix la cua El publicador no coneix ningú
Emmagatzematge Durador fins a 14 dies Cap: lliura i oblida
Si la destinació és caiguda El missatge espera Reintents i després es perd
Model de consum Sondeig (pull) Inserció (push)
Afegir un consumidor Cal que el productor hi enviï també Una subscripció, sense tocar el productor

La fila que cal gravar-se és la de l'emmagatzematge: SNS no guarda res. Un tema és un encaminador, no una bústia. Si publiques en un tema sense subscriptors, el missatge s'evapora sense error ni avís. I si l'únic subscriptor és un endpoint HTTP caigut, SNS reintenta segons la seva política de lliurament i, esgotada, descarta el missatge. Aquesta és la raó número u que el patró correcte per a feina important sigui SNS més SQS, no SNS a seques.

flowchart LR
    subgraph abans["Abans: la botiga coneix els seus quatre consumidors"]
        T1[Botiga] --> QA1[(cua magatzem)]
        T1 --> QB1[(cua correu)]
        T1 --> QC1[(cua analitica)]
        T1 --> QD1[(cua repartiment)]
    end
    subgraph despres["Despres: la botiga publica un fet i ja no en sap res"]
        T2[Botiga] -->|1 Publish| TEMA{{mercadofresco-pedido-confirmado}}
        TEMA --> QA2[(cola-mercadofresco-almacen)]
        TEMA --> QB2[(cola-mercadofresco-correo)]
        TEMA --> QC2[(cola-mercadofresco-analitica)]
        TEMA -.->|nou, sense tocar la botiga| QE2[(cola-mercadofresco-fidelizacion)]
    end
    style TEMA fill:#cfe2ff
    style QE2 fill:#d4edda

El canvi conceptual és que la botiga deixa de donar ordres («posa això a la cua del magatzem») i passa a comunicar fets («s'ha confirmat una comanda»). Un fet no té destinatari: qui hi tingui interès s'hi subscriu. Aquesta inversió —d'ordre a esdeveniment— és la base de l'arquitectura dirigida per esdeveniments que aprofundirem a 07-03.

Temes, publicadors i subscriptors

Un tema (topic) és un canal lògic amb nom i ARN. Crear-lo és instantani i gratuït:

export PERFIL="--profile mercadofresco-dev --region eu-west-1"
export ETIQUETES='Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion Key=Componente,Value=integracion Key=Propietario,Value=marta Key=CentroCoste,Value=tecnologia'

aws sns create-topic --name mercadofresco-pedido-confirmado \
  --attributes KmsMasterKeyId=alias/mercadofresco-datos \
  --tags $ETIQUETES $PERFIL
# → arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado

Hi ha dues classes de tema:

Tema estàndard Tema FIFO
Nom Lliure Ha d'acabar en .fifo
Ordre No garantit Estricte per MessageGroupId
Lliurament Almenys una vegada Exactament una vegada (finestra de 5 min)
Rendiment Pràcticament il·limitat 300 publicacions/s (3.000 en alt rendiment)
Subscriptors permesos Tots Només cues SQS FIFO (i Lambda des del 2023)
Preu de publicació 0,50 USD/milió 0,30 USD/milió + 0,017 USD/GB

Un publicador crida Publish amb l'ARN del tema; només necessita sns:Publish. Un subscriptor és un endpoint registrat al tema mitjançant Subscribe, amb un protocol i una adreça. El límit per tema és de 12,5 milions de subscripcions, així que a la pràctica no existeix.

Tipus de subscripció i les seves garanties

Protocol Endpoint Reintents Cas d'ús a MercadoFresco
sqs ARN de cua Fins a 100.000 s (~23 h) El principal: feina asíncrona fiable
lambda ARN de funció Fins a 100.000 s Reacció trivial sense necessitat d'esmorteir
https / http URL Configurable, fins a 23 h amb backoff Webhook a l'ERP del soci
email / email-json Adreça Sense reintents útils Avisos a la Marta des d'alertas-mercadofresco
sms Número E.164 Depèn de l'operadora Avís al repartidor d'urgència
application Endpoint de plataforma mòbil Notificació push a l'app de repartiment
firehose ARN de flux de lliurament Bolcat d'esdeveniments a mercadofresco-registros-web

Les garanties no són iguals. Els lliuraments a destinacions internes d'AWS (SQS, Lambda, Firehose) són els més fiables: reintents llargs, sense dependència de xarxa pública i DLQ disponible. Els lliuraments HTTP/S depenen que el teu endpoint estigui viu, respongui un 2xx en menys de 15 segons i aguanti ràfegues. Els de correu i SMS són, a efectes pràctics, «com a molt una vegada»: si el proveïdor rebutja, SNS no hi pot fer gaire, i no són adequats per a res que hagi de passar sí o sí.

Confirmació de subscripció i els seus paranys

Els protocols que apunten fora d'AWS —HTTP/S i correu— requereixen confirmació. SNS envia a l'endpoint un missatge de tipus SubscriptionConfirmation amb un SubscribeURL, i fins que algú el visita la subscripció queda en PendingConfirmation i no rep res.

{
  "Type": "SubscriptionConfirmation",
  "MessageId": "8f21a3c4-...",
  "Token": "2336412f37...",
  "TopicArn": "arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado",
  "Message": "You have chosen to subscribe to the topic ...",
  "SubscribeURL": "https://sns.eu-west-1.amazonaws.com/?Action=ConfirmSubscription&...",
  "Timestamp": "2026-08-02T18:41:07.000Z",
  "SignatureVersion": "1",
  "Signature": "EXAMPLEpH+..."
}

Els paranys, tots vistos en producció:

  • L'endpoint HTTP rep SubscriptionConfirmation i no sap què fer-ne. El teu gestor espera Type: "Notification" i retorna 400. La subscripció no es confirma mai i ningú no se n'assabenta fins que falta un esdeveniment. El gestor ha de distingir els tres tipus: SubscriptionConfirmation, Notification i UnsubscribeConfirmation.
  • Confirmar sense verificar la signatura. Qualsevol que conegui la teva URL et pot enviar un JSON amb un SubscribeURL que apunta al seu propi tema i, si el visites a cegues, acabes subscrit a un tema aliè. Verifica sempre Signature amb el certificat de SigningCertURL —i comprova que aquest domini és sns.<region>.amazonaws.com—.
  • El correu de confirmació acaba a la safata de brossa. L'avís de SNS arriba des de [email protected]; els filtres corporatius el bloquegen sovint.
  • El testimoni caduca als 3 dies. Passat aquest termini cal tornar a subscriure.
  • AuthenticateOnUnsubscribe. Sense aquest atribut, qualsevol amb l'enllaç de baixa pot donar de baixa l'endpoint. Posa'l a true en subscripcions HTTP de producció.

Les subscripcions a SQS, Lambda i Firehose no requereixen confirmació quan les crea un principal del mateix compte amb permisos suficients: es confirmen soles. És una raó més per preferir-les.

El patró fan-out SNS→SQS

Aquest és el patró d'integració més utilitzat a AWS i el que MercadoFresco adopta com a estàndard: un tema al qual se subscriuen diverses cues, i un consumidor per cua.

flowchart TD
    T[Botiga: Publish] --> TEMA{{mercadofresco-pedido-confirmado}}
    TEMA -->|sense filtre| QA[(cola-mercadofresco-almacen)]
    TEMA -->|sense filtre| QB[(cola-mercadofresco-correo)]
    TEMA -->|sense filtre| QC[(cola-mercadofresco-analitica)]
    TEMA -->|filtre franja_entrega = 24h| QD[(cola-mercadofresco-reparto-24h)]
    QA --> CA[Consumidor ERP] --> DA[/ERP del magatzem/]
    QB --> CB[Lambda correu] --> DB[/Proveidor SMTP/]
    QC --> CC[Consumidor analitica] --> DC[(Redshift)]
    QD --> CD[Lambda repartiment] --> DD[/API del repartidor/]
    QA -.->|4 fallades| DLQA[(mercadofresco-almacen-fallidas)]
    QB -.->|4 fallades| DLQB[(mercadofresco-correo-fallidas)]
    style TEMA fill:#cfe2ff
    style DLQA fill:#f8d7da
    style DLQB fill:#f8d7da

Cada consumidor avança al seu ritme. El de l'ERP pot anar lent i acumular 4.000 missatges sense que el correu se n'assabenti. Si el consumidor d'analítica té una fallada i cal aturar-lo dues hores, els seus missatges esperen a la seva cua i es processen després: ningú més no se'n veu afectat.

Per què una cua al mig i no una Lambda directa

SNS pot invocar funcions Lambda directament, i és temptador estalviar-se la cua. És un error tan bon punt la feina importa. Tres raons:

1. Reintents i durabilitat. Si SNS invoca una Lambda i la funció falla, SNS reintenta segons la seva política de lliurament i després descarta el missatge (o l'envia a la seva DLQ, si la vas configurar). Amb una cua pel mig, el missatge queda desat fins a 14 dies, el consumidor el reintenta les vegades que diguis i acaba en una DLQ que pots inspeccionar i reprocessar amb start-message-move-task. La cua converteix un esdeveniment efímer en feina pendent persistent.

2. Esmorteïment i contrapressió. El divendres a les 18:00 arriben 900 comandes/hora en ràfegues. Amb SNS→Lambda, Lambda escala tan de pressa com arribin les invocacions: si al darrere hi ha Aurora amb 200 connexions o l'SMTP amb el seu límit d'enviament, aquest pic es propaga intacte. Amb SNS→SQS→Lambda, la cua absorbeix la ràfega i MaximumConcurrency (07-01) marca a quin ritme es drena.

3. Aïllament de fallades. Amb quatre Lambdes subscrites al tema i una d'elles estrangulada pel límit de concurrència del compte, les seves invocacions fallen i es perden. Amb quatre cues, el problema queda contingut a la cua afectada.

Criteri SNS → Lambda SNS → SQS → consumidor
Durabilitat de la feina Nul·la després d'esgotar reintents Fins a 14 dies
Reprocessar després d'arreglar la fallada Impossible Redrive des de la DLQ
Control del ritme Cap MaximumConcurrency, mida de lot
Aïllament entre consumidors Baix Alt
Latència afegida ~0 Desenes de ms
Peces per mantenir 1 2

Quan sí que convé SNS→Lambda directe: reaccions trivials, idempotents i sense conseqüències si se'n perd alguna —refrescar una memòria cau, escriure una mètrica—, i quan la latència mínima és un requisit. Per a tot el que sigui una venda, un cobrament o un avís a un soci, cua pel mig.

Muntar el fan-out de MercadoFresco per CLI i amb boto3

El pas que més s'oblida és la política de cua: SNS no és un principal IAM del teu compte, així que necessita permís explícit a cada cua de destinació.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "PermitirEntregaDesdeTemaPedidoConfirmado",
    "Effect": "Allow",
    "Principal": { "Service": "sns.amazonaws.com" },
    "Action": "sqs:SendMessage",
    "Resource": "arn:aws:sqs:eu-west-1:111122223333:cola-mercadofresco-almacen",
    "Condition": {
      "ArnEquals": {
        "aws:SourceArn": "arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado"
      }
    }
  }]
}

Sense la condició aws:SourceArn, qualsevol tema d'SNS del món podria escriure a la teva cua. És el mateix patró de protecció que a 07-01 amb S3.

Amb això, el muntatge complet:

TEMA=arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado

for C in almacen correo analitica; do
  COLA_ARN=arn:aws:sqs:eu-west-1:111122223333:cola-mercadofresco-$C
  aws sqs set-queue-attributes \
    --queue-url https://sqs.eu-west-1.amazonaws.com/111122223333/cola-mercadofresco-$C \
    --attributes Policy="$(sed "s|COLA_ARN|$COLA_ARN|" politica-cola.json | tr -d '\n')" $PERFIL

  aws sns subscribe --topic-arn $TEMA --protocol sqs --notification-endpoint $COLA_ARN \
    --attributes RawMessageDelivery=true $PERFIL
done

RawMessageDelivery=true mereix un paràgraf propi. Per defecte, SNS embolcalla el teu missatge en un sobre JSON amb metadades, i el cos original queda com una cadena escapada dins del camp Message:

{
  "Type": "Notification",
  "MessageId": "1d9a...",
  "TopicArn": "arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado",
  "Message": "{\"pedido_id\":\"PED-2026-084417\",\"importe_eur\":48.20}",
  "Timestamp": "2026-08-02T18:41:07.123Z",
  "MessageAttributes": { "franja_entrega": { "Type": "String", "Value": "24h" } }
}

Això obliga el consumidor a fer un doble json.loads i a llegir els atributs d'un lloc diferent del que els llegiria si el missatge arribés directe a la cua. Amb RawMessageDelivery=true, la cua rep el cos tal qual i els atributs d'SNS es converteixen en atributs natius d'SQS: el mateix consumidor funciona tant si el missatge ve del tema com si es va enviar directament a la cua. Activa'l sempre en subscripcions SQS i Firehose.

El publicador queda així:

import json, os
from datetime import datetime, timezone
import boto3

sns = boto3.client("sns", region_name="eu-west-1")
TEMA = os.environ["ARN_TEMA_PEDIDO_CONFIRMADO"]


def publicar_comanda_confirmada(comanda):
    """La botiga comunica un fet. No sap ni li importa qui se n'assabenta."""
    resposta = sns.publish(
        TopicArn=TEMA,
        Subject=f"Comanda confirmada {comanda['pedido_id']}",  # nomes ho fan servir correu i HTTP
        Message=json.dumps({
            "version": 1,
            "pedido_id": comanda["pedido_id"],
            "cliente_id": comanda["cliente_id"],
            "importe_eur": float(comanda["importe_eur"]),
            "franja_entrega": comanda["franja_entrega"],      # "24h" | "estandar"
            "provincia": comanda["provincia"],
            "lineas": comanda["lineas"],
            "confirmado_en": datetime.now(timezone.utc).isoformat(),
        }, ensure_ascii=False),
        MessageAttributes={
            # Aquests atributs son els que avaluen les politiques de filtre.
            "tipo_evento":    {"DataType": "String", "StringValue": "PedidoConfirmado"},
            "franja_entrega": {"DataType": "String", "StringValue": comanda["franja_entrega"]},
            "provincia":      {"DataType": "String", "StringValue": comanda["provincia"]},
            "importe_eur":    {"DataType": "Number", "StringValue": str(comanda["importe_eur"])},
        },
    )
    return resposta["MessageId"]

Fixa't que els camps que serveixen per filtrar estan duplicats: al cos, perquè el consumidor els necessita, i als atributs, perquè SNS només mira aquí per defecte. És una duplicació deliberada i barata.

Per a volums alts, publish_batch accepta 10 missatges per crida amb el mateix parany que send_message_batch: retorna 200 amb una llista Failed que cal inspeccionar.

Filtratge per atributs i per cos del missatge

Una política de filtre és un JSON associat a la subscripció. Si el missatge no hi encaixa, SNS no el lliura a aquell subscriptor —i no el cobra—. És la peça que evita l'antipatró de «tothom rep tot i cada consumidor descarta el que no l'interessa», que malbarata invocacions i omple les cues.

La Marta vol que cola-mercadofresco-reparto-24h només rebi comandes de la franja de 24 hores:

{ "franja_entrega": ["24h"] }
aws sns set-subscription-attributes \
  --subscription-arn arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado:9c1f... \
  --attribute-name FilterPolicy \
  --attribute-value '{"franja_entrega":["24h"]}' $PERFIL

El llenguatge de filtres admet molt més que la igualtat:

Operador Exemple Significat
Coincidència exacta {"franja_entrega": ["24h", "express"]} Val qualsevol dels dos
anything-but {"provincia": [{"anything-but": ["Baleares", "Canarias"]}]} Tot menys els territoris insulars
prefix {"tipo_evento": [{"prefix": "Pedido"}]} PedidoConfirmado, PedidoCancelado
suffix {"sku": [{"suffix": "-BIO"}]} Productes ecològics
numeric {"importe_eur": [{"numeric": [">=", 150]}]} Comandes grans
Rang numèric {"importe_eur": [{"numeric": [">", 50, "<=", 200]}]} Entre 50 i 200 €
exists {"cupon": [{"exists": true}]} Només si l'atribut hi és present
Combinació OR {"franja_entrega": ["24h"], "importe_eur": [{"numeric": [">=", 150]}]} Vegeu a sota

La regla que més confon: dins d'un atribut, la llista és un OR; entre atributs diferents, és un AND. L'última fila de la taula exigeix franja 24h i a més import ≥ 150. Per expressar un OR entre atributs diferents cal fer servir $or:

{
  "$or": [
    { "franja_entrega": ["24h"] },
    { "importe_eur": [{ "numeric": [">=", 150] }] }
  ]
}

Filtratge per cos del missatge. Des del 2022 SNS pot filtrar mirant dins del JSON del cos, cosa que elimina la duplicació de camps. S'activa posant FilterPolicyScope a MessageBody:

aws sns set-subscription-attributes --subscription-arn $SUB \
  --attribute-name FilterPolicyScope --attribute-value MessageBody $PERFIL

aws sns set-subscription-attributes --subscription-arn $SUB \
  --attribute-name FilterPolicy \
  --attribute-value '{"franja_entrega":["24h"],"lineas":{"sku":[{"prefix":"PESC-"}]}}' $PERFIL

La política pot navegar objectes imbricats i arrays. Dos límits que importen: el cos ha de ser JSON vàlid —si no, la subscripció no rep res i la fallada és silenciosa— i una subscripció té un únic àmbit de filtre, o atributs o cos, mai els dos.

Filtre per atributs Filtre per cos
Duplicar camps No
Imbricació i arrays No
Si el cos no és JSON Funciona igual No lliura res
Cost de publicació Igual Igual
Recomanació Contractes estables i camps plans Filtres rics sobre el detall

MercadoFresco fa servir atributs per a l'encaminament bàsic (tipus d'esdeveniment, franja, província) i cos per a casos rics, com avisar l'equip de fred quan alguna línia comença per PESC-.

Reintents, polítiques de lliurament i DLQ del mateix SNS

Quan SNS no aconsegueix lliurar, reintenta segons la política de lliurament del protocol. Per a SQS, Lambda i Firehose està predefinida i és generosa: fases immediata, amb pre-backoff, amb backoff exponencial i post-backoff, fins a unes 23 hores en total. Per a HTTP/S és configurable:

{
  "healthyRetryPolicy": {
    "minDelayTarget": 5,
    "maxDelayTarget": 300,
    "numRetries": 50,
    "numNoDelayRetries": 0,
    "numMinDelayRetries": 3,
    "numMaxDelayRetries": 10,
    "backoffFunction": "exponential"
  },
  "throttlePolicy": { "maxReceivesPerSecond": 20 }
}

maxReceivesPerSecond és la contrapressió cap a l'ERP del soci: encara que MercadoFresco publiqui 900 missatges en un minut, SNS no li n'enviarà més de 20 per segon. És l'única manera de protegir un endpoint HTTP que no pots escalar.

La DLQ d'SNS es configura per subscripció, no per tema, amb l'atribut RedrivePolicy. Recull els missatges que van esgotar els reintents cap a aquell subscriptor concret:

aws sns set-subscription-attributes --subscription-arn $SUB_ERP \
  --attribute-name RedrivePolicy \
  --attribute-value '{"deadLetterTargetArn":"arn:aws:sqs:eu-west-1:111122223333:mercadofresco-sns-fallidas"}' \
  $PERFIL

Cal distingir bé dues DLQ que conviuen a la mateixa arquitectura i no signifiquen el mateix:

DLQ de la subscripció SNS DLQ de la cua SQS
Què recull El que SNS no va poder lliurar El que el consumidor no va poder processar
Causa típica Endpoint caigut, permisos, cua esborrada Dades invàlides, dependència caiguda
Es configura a La subscripció La cua d'origen
Senyal Problema d'infraestructura o de permisos Problema d'aplicació o de dades

Totes dues necessiten alarma sobre ApproximateNumberOfMessagesVisible cap a alertas-mercadofresco.

Missatges amb estructura per protocol

Un mateix esdeveniment no es llegeix igual en un SMS de 160 caràcters que en una cua. Amb MessageStructure="json", un sol Publish porta una càrrega diferent per protocol:

sns.publish(
    TopicArn=TEMA_ALERTES,
    Subject="Cua de comandes endarrerida",
    MessageStructure="json",
    Message=json.dumps({
        "default": "La cua cola-mercadofresco-pedidos supera els 5 minuts d'antiguitat.",
        "email": ("Alarma: mercadofresco-pedidos-cola-retrasada\n\n"
                  "ApproximateAgeOfOldestMessage > 300 s durant 3 periodes.\n"
                  "Tauler: https://console.aws.amazon.com/cloudwatch/home#dashboards:name=mercadofresco-produccion"),
        "sms": "MercadoFresco: cua de comandes endarrerida >5 min",
        "sqs": json.dumps({"alarma": "cola-retrasada", "cola": "cola-mercadofresco-pedidos"}),
    }),
)

La clau default és obligatòria i s'utilitza per a qualsevol protocol sense entrada pròpia. Si falta, Publish retorna InvalidParameter. El Subject només el fan servir correu i HTTP; s'ignora a SQS i Lambda, i està limitat a 100 caràcters ASCII.

Sobre els atributs: un missatge n'admet fins a 10, i la seva mida compta per al límit de 256 KiB del missatge. Amb MessageStructure="json", cada càrrega per protocol també compta contra aquest límit.

Temes FIFO i el seu encaix amb cues FIFO

Un tema FIFO garanteix ordre i deduplicació d'extrem a extrem, però només pot lliurar a cues SQS FIFO (i, des del 2023, a funcions Lambda). No admet HTTP, correu ni SMS. Encaixa amb la cua d'estoc que vam crear a 07-01:

aws sns create-topic --name mercadofresco-stock-movimientos.fifo \
  --attributes FifoTopic=true,ContentBasedDeduplication=false \
  --tags $ETIQUETES $PERFIL

aws sns subscribe --topic-arn arn:aws:sns:eu-west-1:111122223333:mercadofresco-stock-movimientos.fifo \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:eu-west-1:111122223333:cola-mercadofresco-stock.fifo \
  --attributes RawMessageDelivery=true $PERFIL

En publicar cal aportar MessageGroupId i, si no hi ha deduplicació per contingut, MessageDeduplicationId:

sns.publish(
    TopicArn=TEMA_STOCK,
    Message=json.dumps(moviment, ensure_ascii=False),
    MessageGroupId=moviment["sku"],                         # ordre per producte
    MessageDeduplicationId=f"{moviment['pedido_id']}:{moviment['sku']}:{moviment['motivo']}",
    MessageAttributes={"motivo": {"DataType": "String", "StringValue": moviment["motivo"]}},
)

El MessageGroupId es propaga a la cua, de manera que l'ordre es conserva al llarg de tota la cadena. I el filtratge per atributs també funciona en temes FIFO, cosa que permet que una cua rebi només les reserves i una altra tots els moviments.

Seguretat: política de tema, xifratge i accés entre comptes

La política de tema controla qui publica i qui s'hi subscriu. Per defecte, només el propietari del compte. Aquesta l'endureix: la botiga publica, i només es poden crear subscripcions SQS.

{
  "Version": "2012-10-17",
  "Id": "politica-mercadofresco-pedido-confirmado",
  "Statement": [
    {
      "Sid": "SoloLaTiendaPublica",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda" },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado"
    },
    {
      "Sid": "SuscripcionesSoloDeColas",
      "Effect": "Allow",
      "Principal": { "AWS": "111122223333" },
      "Action": "sns:Subscribe",
      "Resource": "arn:aws:sns:eu-west-1:111122223333:mercadofresco-pedido-confirmado",
      "Condition": { "StringEquals": { "sns:Protocol": "sqs" } }
    }
  ]
}

La condició sns:Protocol és una salvaguarda pràctica: impedeix que algú subscrigui el seu correu personal a un tema que transporta adreces de clients. També existeix sns:Endpoint per restringir a endpoints concrets.

Xifratge. KmsMasterKeyId=alias/mercadofresco-datos xifra els missatges en repòs dins d'SNS. Dos advertiments: el publicador necessita kms:GenerateDataKey* i kms:Decrypt sobre la clau, i si un servei d'AWS publica al tema —CloudWatch en disparar una alarma, S3 en pujar-se un objecte— la política de la clau l'hi ha de permetre explícitament. És la causa habitual d'alarmes que deixen d'avisar just després d'activar el xifratge.

Accés entre comptes. El soci de repartiment té el seu propi compte d'AWS i vol consumir els esdeveniments. No se li donen credencials: s'afegeix el seu compte a la política del tema amb sns:Subscribe, i ell crea al seu compte una cua la política de la qual accepta el nostre tema. Cap de les dues parts no obté accés a la infraestructura de l'altra. És el mateix principi de mínim privilegi de 04-01 aplicat a la integració.

alertas-mercadofresco revisitat, SMS i correu transaccional

Des del mòdul 5 anem escrivint --alarm-actions arn:aws:sns:...:alertas-mercadofresco sense explicar del tot què passava. Ara està clar: una alarma de CloudWatch és un publicador d'SNS. Quan passa a ALARM, publica un JSON amb AlarmName, NewStateValue, NewStateReason i les dimensions de la mètrica. El tema té subscrit el correu de la Marta, i hi podria tenir subscrits —sense tocar ni una alarma— una cua que registri l'historial, una Lambda que obri un tiquet o un endpoint HTTPS cap a l'eina de guàrdies.

Aquest és exactament el valor del model: CloudWatch no sap qui s'assabenta de les seves alarmes. Afegir una destinació no requereix modificar 40 alarmes, només crear una subscripció.

Una millora immediata ara que coneixem el filtratge: subscriure l'SMS de guàrdia amb una política que només deixi passar el que és crític.

{ "AlarmName": [{ "prefix": "mercadofresco-pedidos-" }], "NewStateValue": ["ALARM"] }

Amb FilterPolicyScope=MessageBody, la Marta rep SMS només per alarmes de comandes que entren en estat ALARM, mentre que el correu ho continua rebent tot, incloses les tornades a OK.

Sobre SMS i correu. SNS serveix per a avisos operatius a persones conegudes: un SMS al tècnic de guàrdia, un correu a la Marta. No serveix per a correu transaccional ni per a campanyes: no hi ha plantilles, ni seguiment d'obertures, ni gestió de rebots, ni control de reputació del remitent, i el remitent és [email protected]. El correu de confirmació de comanda de MercadoFresco s'envia amb Amazon SES, que sí que ofereix domini propi, DKIM, plantilles i mètriques de lliurabilitat; la Lambda subscrita a cola-mercadofresco-correo crida SES, no SNS. Per a SMS massius, SNS exigeix a més sortir de l'entorn aïllat (sandbox) i registrar el remitent davant de l'operadora.

Mètriques i depuració de lliuraments

Mètrica Què indica Acció
NumberOfMessagesPublished Volum publicat Referència de trànsit
NumberOfNotificationsDelivered Lliuraments correctes Comparar amb el publicat × subscriptors
NumberOfNotificationsFailed Lliuraments fallits Alarma immediata
NumberOfNotificationsFilteredOut Descartades per política de filtre Si és el 100 %, el filtre està malament
NumberOfNotificationsFilteredOut-NoMessageAttributes El missatge no portava atributs Publicador que va oblidar els atributs
NumberOfNotificationsFilteredOut-InvalidAttributes Atributs amb tipus incorrecte Números enviats com a cadena, etc.
PublishSize Mida dels missatges A prop de 256 KiB → fer servir S3
SMSMonthToDateSpentUSD Despesa en SMS del mes Alarma de cost

Les tres mètriques de FilteredOut són les que més temps estalvien. El símptoma clàssic —«la cua no rep res i no hi ha cap error»— gairebé sempre és un filtre: o el publicador no envia els atributs, o els envia amb el tipus equivocat. Un Number enviat com a String no encaixa amb un filtre numeric, i SNS el descarta sense dir res.

aws cloudwatch put-metric-alarm --alarm-name mercadofresco-sns-entregas-fallidas \
  --namespace AWS/SNS --metric-name NumberOfNotificationsFailed \
  --dimensions Name=TopicName,Value=mercadofresco-pedido-confirmado \
  --statistic Sum --period 300 --evaluation-periods 1 --threshold 0 \
  --comparison-operator GreaterThanThreshold --treat-missing-data notBreaching \
  --alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco $PERFIL

Per depurar lliuraments fallits cal activar els registres d'estat de lliurament, que no estan actius per defecte: es configura un rol amb permís d'escriptura a CloudWatch Logs i un percentatge de mostreig (SQSSuccessFeedbackSampleRate, HTTPSuccessFeedbackSampleRate…). Amb ells apareixen a Logs els codis de resposta reals de l'endpoint, que és l'única cosa que permet distingir un 403 per permisos d'un 500 del soci. I com que tot Publish queda a trail-mercadofresco (05-03), sempre es pot comprovar si l'esdeveniment va arribar a publicar-se.

Cost i neteja

Concepte Preu (eu-west-1) MercadoFresco
Publicacions 1 M gratis, després 0,50 USD/M 240.000/mes → 0 USD
Lliuraments a SQS i a Lambda Gratis 960.000 → 0 USD
Lliuraments HTTP/S 0,60 USD/M 240.000 → 0,14 USD
Lliuraments per correu 2,00 USD/100.000 300/mes → 0,01 USD
SMS a Espanya ~0,06 USD/missatge 40/mes → 2,40 USD
Dades transferides 0,09 USD/GB de sortida ~0,4 GB → 0,04 USD
Total ~2,60 USD/mes

El que cal vigilar és l'SMS: un bucle de proves que enviï 5.000 missatges costa 300 USD en uns minuts. Posa sempre MonthlySpendLimit a les preferències d'SMS del compte i una alarma sobre SMSMonthToDateSpentUSD. Fixa't a més que el fan-out multiplica els lliuraments: publicar 240.000 vegades amb quatre subscriptors són 960.000 lliuraments, gratuïts cap a SQS però de pagament cap a HTTP.

aws sns list-subscriptions-by-topic --topic-arn $TEMA \
  --query 'Subscriptions[].SubscriptionArn' --output text $PERFIL | \
  xargs -n1 -I{} aws sns unsubscribe --subscription-arn {} $PERFIL
aws sns delete-topic --topic-arn $TEMA $PERFIL
aws sns delete-topic --topic-arn arn:aws:sns:eu-west-1:111122223333:mercadofresco-stock-movimientos.fifo $PERFIL

Esborrar el tema no esborra les cues subscrites ni els seus missatges: cal netejar-les a part amb les ordres de 07-01. I compte: esborrar un tema no elimina les subscripcions per correu per confirmar.

Errors Habituals i Consells

Publicar en un tema sense subscriptors. Publish retorna 200 i un MessageId, i el missatge s'evapora. No hi ha error, no hi ha mètrica de fallada. Verifica NumberOfNotificationsDelivered després de qualsevol desplegament que toqui subscripcions.

Oblidar la política de cua. La subscripció es crea sense queixar-se, però SNS no hi pot escriure. Els missatges es perden i NumberOfNotificationsFailed puja. És la fallada número u del fan-out.

Política de cua sense aws:SourceArn. Funciona, però deixa la teva cua oberta a qualsevol tema d'SNS.

No activar RawMessageDelivery. El consumidor rep el sobre d'SNS, necessita doble json.loads i els atributs apareixen on no els busca. Activa'l sempre en subscripcions SQS.

Filtres que descarten tot en silenci. El publicador no envia els atributs, o envia un número com a String quan el filtre fa servir numeric. Símptoma: zero missatges i zero errors. Mira NumberOfNotificationsFilteredOut i les seves dues variants. I no confonguis OR i AND: dins d'un atribut, llista = OR; entre atributs diferents, AND; per a OR entre atributs cal $or.

Subscriure Lambdes directament per a feina important, o suposar que SNS guarda els missatges. No els guarda: un consumidor caigut durant el desplegament perd definitivament tot el que s'ha publicat en aquella finestra si no hi ha cua pel mig.

Xifrar el tema i oblidar la política de la clau. Les alarmes de CloudWatch deixen de publicar i ningú no se n'assabenta —perquè justament el mecanisme d'avís és el que ha fallat—.

Consell: anomena els temes pel fet, no per la destinació. mercadofresco-pedido-confirmado, no mercadofresco-avisar-almacen. El nom forma part del contracte i no ha d'envellir quan canviïn els consumidors.

Consell: posa version al cos des del primer missatge. Quan d'aquí a un any calgui afegir un camp, els consumidors antics el podran ignorar amb seguretat.

Consell: filtra al tema, no al consumidor. Un consumidor que descarta el 90 % del que rep paga invocacions, peticions a la cua i complexitat per no res.

Exercicis

Exercici 1: el consumidor de màrqueting

Màrqueting vol processar les comandes confirmades per al seu programa de fidelització, però només li interessen les d'import igual o superior a 60 € o les de clients de Catalunya, i el seu sistema és un endpoint HTTPS que aguanta 5 peticions per segon i pateix caigudes de fins a 40 minuts.

Dissenya la integració: (a) què subscrius al tema i per què; (b) la política de filtre exacta en JSON; (c) què configures per protegir el seu endpoint del pic del divendres; (d) quines DLQ hi intervenen i què significa cadascuna; (e) què canvia al codi de la botiga.

Exercici 2: diagnòstic del fan-out mut

El Luis desplega el fan-out un dijous. El divendres al matí, la Marta observa: NumberOfMessagesPublished 8.400; NumberOfNotificationsDelivered 16.800; NumberOfNotificationsFailed 8.400; NumberOfNotificationsFilteredOut 0. La cua del magatzem i la de correu reben amb normalitat; la d'analítica és buida. cola-mercadofresco-analitica existeix, el seu consumidor funciona si se li envien missatges a mà, i la subscripció apareix com a Confirmed.

Explica: (a) què falla exactament i com ho dedueixes dels números; (b) per què la subscripció està confirmada tot i que no funcioni; (c) quina ordre executaries per confirmar el diagnòstic; (d) la correcció; (e) quina alarma hauria avisat el dijous a la tarda.

Exercici 3: SNS o SQS

Per a cada cas, decideix si fer servir SNS, SQS o SNS→SQS, i justifica-ho en dues frases: (a) la botiga avisa el magatzem que prepari una caixa; (b) tres equips volen assabentar-se de les cancel·lacions de comanda i se'n preveu un quart; (c) en pujar una foto cal generar la miniatura; (d) cal avisar la Marta per SMS quan la DLQ tingui missatges; (e) cal enviar els moviments d'estoc mantenint l'ordre per SKU a dos sistemes diferents; (f) cal cridar el servei de càlcul de ports per mostrar el preu a la pantalla de pagament.

Solucions

Solució 1

(a) S'hi subscriu una cua SQS (cola-mercadofresco-fidelizacion), no l'endpoint HTTPS. El motiu és la caiguda de 40 minuts: si SNS lliurés directament per HTTP, dependríem que la política de lliurament aguantés, i tot el que fallés després d'esgotar els reintents es perdria. Amb una cua, els esdeveniments esperen fins a 14 dies i es processen quan el sistema torni. Del consum se n'encarrega una Lambda subscrita a la cua que crida l'endpoint de màrqueting.

(b) Com que el requisit és un OR entre dos atributs diferents, cal $or:

{
  "$or": [
    { "importe_eur": [{ "numeric": [">=", 60] }] },
    { "provincia": ["Barcelona", "Girona", "Lleida", "Tarragona"] }
  ]
}

Si s'escrivissin els dos atributs al mateix objecte sense $or, SNS exigiria les dues condicions alhora i màrqueting perdria la majoria dels esdeveniments. Requisit: la botiga ha de publicar importe_eur com a Number i provincia com a String.

(c) La protecció és al consumidor, no a SNS: MaximumConcurrency=5 al mapatge d'origen d'esdeveniments de la Lambda, que limita el ritme al que l'endpoint aguanta; batch-size petit; i VisibilityTimeout de la cua ≥ 6 × timeout de la funció. La cua absorbeix el pic del divendres i el drena a 5 peticions per segon. (Si en lloc de cua s'hagués subscrit l'endpoint directament, l'eina equivalent seria throttlePolicy.maxReceivesPerSecond a la política de lliurament.)

(d) Dues DLQ diferents. La DLQ de la subscripció SNS recolliria fallades de lliurament del tema a la cua —permisos mal posats, cua esborrada—; a la pràctica gairebé mai s'activa perquè el lliurament a SQS és molt fiable, però convé tenir-la. La DLQ de la cua (mercadofresco-fidelizacion-fallidas, amb maxReceiveCount=4) recull el que la Lambda no va poder processar: si després de 40 minuts de caiguda l'endpoint continua rebutjant, aquests esdeveniments s'aïllen aquí i es reprocessen amb redrive.

(e) Res. Aquest és el resultat que buscàvem: la botiga ja publica el fet amb tots els atributs necessaris, i sumar màrqueting és crear una cua, una política de cua, una subscripció amb el seu filtre i una Lambda. Zero desplegaments del codi de la botiga.

Solució 2

(a) Els números quadren exactament amb una de les tres subscripcions fallant sempre. Amb 8.400 publicacions i tres subscriptors s'haurien de veure 25.200 lliuraments; n'hi ha 16.800 de correctes (dos subscriptors × 8.400) i 8.400 de fallits (el tercer × 8.400). Com que FilteredOut és 0, no és un problema de filtre: el missatge s'intenta lliurar i el lliurament falla. Combinat amb el fet que la cua d'analítica existeix i el seu consumidor funciona, el diagnòstic és clar: la política de cola-mercadofresco-analitica no permet a SNS escriure-hi. Gairebé amb seguretat, el bucle de desplegament del Luis va fallar en aplicar la política en aquella cua, o la va aplicar amb un aws:SourceArn equivocat.

(b) Confirmada i funcional són coses diferents. Les subscripcions SQS dins del mateix compte s'autoconfirmen en crear-se: SNS comprova que l'ARN és vàlid, no que tingui permís d'escriptura. El permís s'avalua a cada lliurament, no en subscriure. Per això l'estat és Confirmed i cada publicació falla amb AccessDenied.

(c) aws sqs get-queue-attributes --queue-url ...cola-mercadofresco-analitica --attribute-names Policy per veure si hi ha política i si l'aws:SourceArn coincideix amb l'ARN del tema. Complementari: activar els registres d'estat de lliurament del tema i mirar a CloudWatch Logs el codi d'error real dels lliuraments fallits, que dirà AccessDenied sense ambigüitat.

(d) Aplicar a la cua la mateixa política que a les altres dues, amb Principal sns.amazonaws.com, acció sqs:SendMessage, Resource l'ARN de la cua d'analítica i condició ArnEquals sobre aws:SourceArn amb l'ARN del tema. Res més: els missatges de les últimes hores ja s'han perdut —SNS no guarda res—, i aquí es veu per què l'arxiu i la reproducció d'esdeveniments d'EventBridge (07-03) resulten tan valuosos.

(e) Una alarma sobre NumberOfNotificationsFailed > 0 del tema, amb període de 5 minuts, hauria avisat als pocs minuts del desplegament del dijous en lloc de descobrir-se el divendres al matí. És l'alarma mínima obligatòria de qualsevol tema d'SNS. Com a complement, ApproximateNumberOfMessagesVisible de la cua d'analítica a 0 durant hores laborables també hauria estat un senyal.

Solució 3

(a) SQS. És una ordre amb un únic destinatari conegut i necessita durabilitat i reintents. Un tema no aporta res si només hi ha un interessat, i afegiria una peça més per mantenir.

(b) SNS→SQS. És el cas canònic de fan-out: diversos interessats en el mateix fet i un més previst. Cada equip amb la seva cua per aïllar-se de les fallades i del ritme dels altres.

(c) SQS. Un sol consumidor i feina idempotent. S3 pot publicar directament a la cua de miniatures; posar-hi un tema al mig només tindria sentit si un altre sistema necessités assabentar-se de les fotos noves.

(d) SNS. La destinació és una persona per un canal que SNS admet nativament. Aquí no cal cua: l'alarma de CloudWatch publica i SNS lliura. Afegir SQS no milloraria res perquè un SMS no es reprocessa.

(e) Tema FIFO → cues FIFO. Dues destinacions exigeixen fan-out i l'ordre per SKU exigeix FIFO d'extrem a extrem. mercadofresco-stock-movimientos.fifo amb MessageGroupId igual al sku i dues cues FIFO subscrites, cadascuna amb el seu filtre si escau.

(f) Cap: crida síncrona. El client necessita el preu del port ara per poder decidir. Ni cua ni tema: una crida HTTP amb temps d'espera curt, reintents acotats i un valor per defecte si el servei no respon. És el recordatori que l'asíncron no sempre és millor —només ho és quan el resultat no forma part de la resposta a l'usuari—.

Conclusió

MercadoFresco ha passat d'una botiga que enviava quatre missatges a quatre cues a una botiga que publica un fet a mercadofresco-pedido-confirmado i se'n desentén. Magatzem, correu i analítica tenen cadascun la seva cua subscrita amb RawMessageDelivery=true, els seus reintents i la seva DLQ; cola-mercadofresco-reparto-24h rep només les comandes de la franja urgent gràcies a una política de filtre; el tema està xifrat amb alias/mercadofresco-datos i la seva política restringeix qui publica i amb quins protocols s'hi pot subscriure. Sumar màrqueting és ara crear una cua i una subscripció, sense tocar ni desplegar el codi de la botiga.

Pel camí han quedat clares les idees que governen el patró: que SNS no emmagatzema res, cosa que converteix la cua intermèdia en obligatòria per a tota feina que importi; que la cua aporta durabilitat, esmorteïment i aïllament de fallades que la subscripció directa a Lambda no pot donar; que el filtratge per atributs o per cos evita el malbaratament que tothom ho rebi tot, amb la regla d'OR dins d'un atribut i AND entre atributs; que la DLQ de la subscripció i la DLQ de la cua assenyalen problemes diferents —lliurament davant de procés—; i que el tema alertas-mercadofresco del mòdul 5 era aquest mateix mecanisme des del principi, amb CloudWatch com a publicador que no sap qui l'escolta.

Però el fan-out té un sostre. SNS lliura el mateix missatge a tots els subscriptors i només sap filtrar amb regles senzilles sobre el que el publicador es va recordar d'incloure. Quan MercadoFresco vulgui encaminar de debò per contingut —que PedidoCancelado vagi a un lloc i StockBajo a un altre, tot sobre el mateix canal—, hauria de crear un tema per cada tipus d'esdeveniment i tornar a un mapa de subscripcions difícil de governar. A més hi ha fets que la botiga no publica i que també interessen: que una instància EC2 canviï d'estat, que Trusted Advisor detecti un recurs infrautilitzat, que un desplegament falli. I falta una cosa que avui fa mal: quan el consumidor d'analítica va estar mal configurat, els esdeveniments d'aquelles hores es van perdre per sempre, perquè un tema no guarda res.

A 07-03, «Amazon EventBridge», fem el salt del tema al bus d'esdeveniments: un canal on publiquen tant les aplicacions com els mateixos serveis d'AWS, amb regles que encaminen segons el contingut complet de l'esdeveniment, transformació de l'entrada per no acoblar la destinació al format d'origen, destinacions que van des de Lambda fins a una API de l'ERP, tasques programades amb cron, connexió directa amb els Streams de mercadofresco-carritos mitjançant Pipes, i —el que hauria salvat l'incident de la cua d'analítica— arxiu i reproducció de tot el que s'ha publicat.

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