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
- Publicació/subscripció davant de la cua punt a punt
- Temes, publicadors i subscriptors
- Tipus de subscripció i les seves garanties
- Confirmació de subscripció i els seus paranys
- El patró fan-out SNS→SQS
- Per què una cua al mig i no una Lambda directa
- Muntar el fan-out de MercadoFresco per CLI i amb boto3
- Filtratge per atributs i per cos del missatge
- Reintents, polítiques de lliurament i DLQ del mateix SNS
- Missatges amb estructura per protocol
- Temes FIFO i el seu encaix amb cues FIFO
- Seguretat: política de tema, xifratge i accés entre comptes
alertas-mercadofrescorevisitat, SMS i correu transaccional- Mètriques i depuració de lliuraments
- Cost i neteja
- Errors habituals i consells
- Exercicis
- 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-confirmadoHi 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 | Sí | Notificació push a l'app de repartiment |
firehose |
ARN de flux de lliurament | Sí | 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
SubscriptionConfirmationi no sap què fer-ne. El teu gestor esperaType: "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,NotificationiUnsubscribeConfirmation. - Confirmar sense verificar la signatura. Qualsevol que conegui la teva URL et pot enviar un JSON amb
un
SubscribeURLque apunta al seu propi tema i, si el visites a cegues, acabes subscrit a un tema aliè. Verifica sempreSignatureamb el certificat deSigningCertURL—i comprova que aquest domini éssns.<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 atrueen 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
doneRawMessageDelivery=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:
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"]}' $PERFILEl 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:
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-"}]}}' $PERFILLa 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 | Sí | No |
| Imbricació i arrays | No | Sí |
| 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"}' \
$PERFILCal 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 $PERFILEn 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.
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 $PERFILPer 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 $PERFILEsborrar 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
- Què és AWS?
- Configuració del teu compte d'AWS
- Infraestructura global d'AWS
- Consola d'administració d'AWS
- AWS CLI i SDK
Mòdul 2: Serveis principals d'AWS
Mòdul 3: Xarxes i lliurament de contingut
- Amazon VPC
- Grups de seguretat i llistes de control d'accés
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Mòdul 4: Seguretat i identitat
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager i Parameter Store
- AWS Shield
- AWS WAF
Mòdul 5: Monitoratge i gestió
- Amazon CloudWatch
- AWS X-Ray i traçabilitat distribuïda
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Mòdul 6: Bases de dades
- Com triar la base de dades adequada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Mòdul 7: Integració d'aplicacions
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrons d'integració: idempotència, reintents i cues de missatges fallits
