A 06-01 vam prendre la decisió: la cistella i les sessions surten de PostgreSQL. Els números que la
justificaven eren contundents —180.000 escriptures al dia, el 92 % actualitzacions del mateix registre,
accés sempre per clau, cap consulta exploratòria i 78 GB de taula dels quals 55 GB són cistelles
abandonades de fa més de dos mesos—. Aquesta taula és la que dispara el VACUUM que més E/S consumeix de
mercadofresco-pedidos.
Amazon DynamoDB és la base de dades clau-valor i documental gestionada d'AWS. No hi ha servidors,
ni versions per actualitzar, ni connexions per esgotar: hi ha una API HTTP i un model de dades que, si es
fa servir bé, respon en menys de 10 mil·lisegons independentment de si la taula té mil elements o
mil milions. El «si es fa servir bé» és la lliçó sencera: DynamoDB castiga sense pietat el disseny
relacional i recompensa el disseny orientat al patró d'accés. Aquí el Luis modela
mercadofresco-carritos des de les consultes que l'aplicació fa de debò, calcula la capacitat amb
les xifres del pic del divendres i activa el TTL que converteix els 55 GB d'escombraries en un problema que ja
no es pot repetir.
Avís de cost. Les taules sota demanda no costen res si no es fan servir, però sí que costen l'emmagatzematge (0,25 USD per GB i mes) i les còpies PITR; les provisionades cobren capacitat reservada encara que no arribi trànsit. En acabar, esborra les taules de prova. Dades fictícies.
Contingut
- Què és DynamoDB i quin problema resol
- Model de dades: taula, element, atributs i tipus
- Clau primària: partició i ordenació
- Particions, hash i claus calentes
- Dissenyar des de les consultes, no des de les entitats
- Disseny de taula única aplicat a
mercadofresco-carritos - Operacions d'escriptura i expressions de condició
- Lectura:
GetItem,Queryi per quèScangairebé sempre és un error - Operacions per lots i transaccions
- Índexs secundaris: GSI i LSI
- Capacitat, RCU i WCU amb les xifres del divendres
- Ràfegues, estrangulament i reintents amb retrocés exponencial
- Consistència eventual i consistència forta
- TTL: la fi de les cistelles zombis
- Streams, còpies de seguretat, xifratge i taules globals
- Accés des de boto3 i des de Lambda
- Cost comparat amb mantenir-ho a RDS
- La migració, pas a pas
- Errors habituals i consells
- Exercicis
- Conclusió
Què és DynamoDB i quin problema resol
DynamoDB és un servei totalment gestionat i sense servidor. No es tria una mida d'instància, no s'aplica cap pedaç, no es dimensiona l'emmagatzematge. Es crea una taula i s'hi escriu. La diferència estructural amb RDS resumeix per què encaixa amb la cistella:
| RDS PostgreSQL | DynamoDB | |
|---|---|---|
| Unitat d'escala | Instància (vertical) | Particions (horitzontal, automàtica) |
| Connexions | Limitades (200 a db.t3.small) |
Cap: peticions HTTP signades |
| Latència amb el volum | Es degrada en créixer | Constant per disseny |
| Consultes | SQL arbitrari | Només per clau i índexs definits |
| Esquema | Fix | Només la clau primària és obligatòria |
| Caducitat de dades | Un procés que cal escriure | TTL natiu i gratuït |
| Manteniment | VACUUM, versions, finestres |
Cap |
La fila de les connexions importa més del que sembla: l'alarma mercadofresco-rds-conexiones-altas
de 05-01 existeix perquè la instància té un sostre dur i les funcions Lambda el frecen al pic del
divendres. DynamoDB no té aquest concepte —cada operació és una crida HTTP independent autenticada
amb IAM—, així que mil Lambdes concurrents no esgoten res.
Model de dades: taula, element i atributs
Tres conceptes i cap més. Una taula és una col·lecció d'elements sense més esquema que la clau primària. Un element (item) equival a una fila i ocupa com a màxim 400 KB, inclosos els noms d'atributs. Un atribut és una parella nom-valor, i dos elements de la mateixa taula poden tenir atributs completament diferents.
L'absència d'esquema és llibertat i és perill. Res no impedeix desar precio com a número en un
element i com a cadena en un altre; la validació és responsabilitat de l'aplicació. A MercadoFresco
aquesta validació viu en una única capa d'accés a dades que tot el codi utilitza, precisament perquè
no depengui de la disciplina de qui escriu cada endpoint.
Clau primària: partició i ordenació
La clau primària és l'única estructura obligatòria i la decisió més difícil de revertir: per canviar-la cal crear una altra taula i migrar.
Una clau simple és només la clau de partició (PK, o hash key): identifica un element de manera única i s'hi accedeix per igualtat exacta, res més. Una clau composta hi afegeix la clau d'ordenació (SK, o range key): tots els elements amb la mateixa PK viuen junts, físicament ordenats per SK. Això habilita l'operació més potent de DynamoDB, recuperar d'una sola vegada un rang d'elements relacionats.
PK = CLIENTE#4471 SK = CARRITO#2026-08-02T18:04:11Z SK = CARRITO#2026-07-28T09:12:40Z SK = PERFIL SK = SESION#a91f...
Una sola operació pot demanar «tots els elements de CLIENTE#4471 la SK dels quals comenci per
CARRITO#, ordenats de més recent a més antic, els 5 primers». Aquesta consulta és la que a PostgreSQL
era un SELECT … WHERE id_cliente = ? ORDER BY fecha DESC LIMIT 5 amb el seu índex; aquí és un accés
directe a un bloc contigu de disc.
Tipus de dades
| Categoria | Tipus | Notació | Notes |
|---|---|---|---|
| Escalars | Cadena, Número, Binari, Booleà, Nul | S, N, B, BOOL, NULL |
Números amb 38 dígits de precisió |
| Documents | Llista, Mapa | L, M |
Imbricables fins a 32 nivells |
| Conjunts | Cadenes, Números, Binaris | SS, NS, BS |
Sense duplicats, sense ordre |
Dos avisos pràctics. No existeix el tipus data: es fan servir cadenes ISO 8601
(2026-08-02T18:04:11Z), que s'ordenen alfabèticament igual que cronològicament, o números Unix quan
cal comparar-los. I una cadena buida s'admet en atributs no clau, però no en atributs clau: un
id_sesion buit provoca un error de validació.
Particions, hash i claus calentes
DynamoDB reparteix les dades aplicant una funció hash al valor de la clau de partició. El resultat determina en quina partició física cau l'element. Cada partició suporta com a màxim 3.000 RCU i 1.000 WCU, i aquest límit és el que explica gairebé tots els problemes de rendiment reals.
graph LR
A["PK = CLIENTE#4471"] -->|hash| P1[Particio 1]
B["PK = CLIENTE#8802"] -->|hash| P2[Particio 2]
C["PK = CLIENTE#1043"] -->|hash| P1
D["PK = CLIENTE#9917"] -->|hash| P3[Particio 3]
P1 --> L1["Cada particio: max. 3.000 RCU / 1.000 WCU"]
P2 --> L1
P3 --> L1
Una clau calenta és un valor de PK que concentra un trànsit desproporcionat. La taula sencera pot tenir capacitat de sobres i tot i així estrangular-se perquè tot va a una partició.
| Clau calenta | Per què falla | Correcció |
|---|---|---|
PK = FECHA#2026-08-02 per a les comandes del dia |
Tot el trànsit del dia en una partició | PK = PEDIDO#<id> i un GSI per consultar per data |
PK = ESTADO#pendiente |
Milions d'elements sota un valor | PK = PEDIDO#<id>, GSI dispers només amb els pendents |
PK = TIENDA#mercadofresco |
Una única PK per a tot | Repartir per client o producte |
PK = PRODUCTO#<id> en un producte viral |
Un sol producte ho absorbeix tot | Repartir l'escriptura: PRODUCTO#<id>#<0-9> i llegir-ne les 10 |
A mercadofresco-carritos la PK és CLIENTE#<id>, amb desenes de milers de valors diferents i
trànsit repartit de manera natural. És el cas fàcil, i convé dir que és fàcil perquè el patró
d'accés ho era: si l'accés hagués estat «dona'm totes les cistelles abandonades avui», el disseny
seria un altre.
Dissenyar des de les consultes, no des de les entitats
El procediment correcte té tres passos i cap no és dibuixar entitats.
Pas 1: enumerar totes les consultes, extretes dels registres reals de l'aplicació:
| # | Consulta | Freqüència diària |
|---|---|---|
| C1 | Obtenir la cistella activa d'un client | 210.000 |
| C2 | Afegir, canviar quantitat o treure una línia | 180.000 |
| C3 | Obtenir la sessió web pel seu identificador | 340.000 |
| C4 | Llistar les últimes 5 cistelles d'un client (atenció al client) | 400 |
| C5 | Marcar una cistella com a convertida en comanda | 4.000 |
| C6 | Esborrar cistelles abandonades de més de 30 dies | Continu |
Pas 2: per a cada consulta, decidir com es resol. La regla és que tota consulta freqüent
s'ha de resoldre amb GetItem o Query sobre la taula o sobre un índex. Si alguna exigeix Scan, el
model està malament. Pas 3: dissenyar la clau perquè això es compleixi. I només llavors, mirar quines
entitats han quedat.
Disseny de taula única aplicat a mercadofresco-carritos
El disseny de taula única (single-table design) consisteix a desar diversos tipus d'entitat a la
mateixa taula, distingint-los per prefixos a la clau. Sona estrany venint de SQL i respon a una
raó molt concreta: a DynamoDB no hi ha JOIN, així que la manera de recuperar entitats relacionades
en una sola operació és que comparteixin clau de partició.
| Entitat | PK | SK | Atributs |
|---|---|---|---|
| Cistella | CLIENTE#<id> |
CARRITO#<ts_iso> |
lineas (L de M), importe, estado, expira_en |
| Sessió | CLIENTE#<id> |
SESION#<id_sesion> |
ip, agente, ultima_actividad, expira_en |
| Perfil lleuger | CLIENTE#<id> |
PERFIL |
ciudad, franja_reparto, alergenos |
graph TD
subgraph "Particio CLIENTE#4471"
P["SK = PERFIL · Valencia · franja 18-21h"]
C1["SK = CARRITO#2026-08-02T18:04:11Z · activo · 34,20 EUR"]
C2["SK = CARRITO#2026-07-28T09:12:40Z · convertido · 51,90 EUR"]
S1["SK = SESION#a91f3c · ultima_actividad 18:06:02"]
end
Q1["C1 i C4: Query PK + SK begins_with CARRITO#<br/>ScanIndexForward false, Limit 1 o 5"] --> C1
Q2["C3: GetItem amb PK i SK exactes"] --> S1
Amb aquest disseny, una sola Query sobre CLIENTE#4471 retorna el perfil, la cistella activa i les
sessions —tot el que la botiga necessita per pintar la capçalera— en una operació i uns pocs
mil·lisegons. A PostgreSQL això eren tres consultes a tres taules.
Quan el disseny de taula única no compensa: quan les entitats no es consulten mai juntes,
quan tenen cicles de vida i patrons de capacitat molt diferents, o quan l'equip encara no domina el
model. Ficar comandes i catàleg a la mateixa taula «perquè és la pràctica recomanada» és carregar amb la
complexitat sense cobrar el benefici. Aquí es justifica perquè C1, C3 i C4 comparteixen id_cliente.
Operacions d'escriptura: PutItem, UpdateItem, DeleteItem
import boto3
from datetime import datetime, timezone, timedelta
from decimal import Decimal
sessio = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
taula = sessio.resource("dynamodb").Table("mercadofresco-carritos")
ara = datetime.now(timezone.utc)
ts = ara.strftime("%Y-%m-%dT%H:%M:%SZ")
# PutItem: crea l'element o SUBSTITUEIX del tot el que tingui aquesta clau.
# Aquest "substitueix del tot" es la font numero u de perdues de dades:
# els atributs que no s'envien desapareixen.
taula.put_item(Item={
"PK": "CLIENTE#4471",
"SK": f"CARRITO#{ts}",
"estado": "activo",
"importe": Decimal("0.00"),
"lineas": [],
# expira_en es un numero Unix en segons: es el que el TTL sap llegir.
"expira_en": int((ara + timedelta(days=30)).timestamp()),
})UpdateItem és l'operació que cal fer servir el 90 % de les vegades: modifica atributs concrets sense
llegir abans l'element i és atòmica, de manera que dues peticions concurrents que incrementin un
comptador donen el resultat correcte sense bloqueigs.
# Afegir una linia a la cistella i sumar l'import, en una sola operacio atomica.
resposta = taula.update_item(
Key={"PK": "CLIENTE#4471", "SK": f"CARRITO#{ts}"},
UpdateExpression=(
"SET #lineas = list_append(if_not_exists(#lineas, :vacia), :linea), "
" #actualizado = :ahora ADD #importe :precio"
),
# Els noms van per alies perque moltes paraules son reservades a DynamoDB
# ("estado", "name", "size"...). Fer servir alies sempre evita sorpreses.
ExpressionAttributeNames={
"#lineas": "lineas", "#importe": "importe", "#actualizado": "actualizado_en"},
ExpressionAttributeValues={
":vacia": [],
":linea": [{"sku": "PESC-SALM-001", "uds": 2, "precio": Decimal("12.40")}],
":precio": Decimal("24.80"),
":ahora": ts},
ReturnValues="ALL_NEW", # retorna l'element resultant, sense lectura extra
)Els quatre verbs: SET assigna o crea (SET estado = :s), ADD suma a un número o afegeix a un
conjunt (ADD importe :p), REMOVE elimina un atribut o un element de llista (REMOVE lineas[2]) i
DELETE treu elements d'un conjunt (DELETE etiquetas :e).
DeleteItem esborra per clau. A mercadofresco-carritos gairebé no es fa servir: les cistelles les esborra el TTL
i les convertides es marquen, no s'eliminen.
Expressions de condició i escriptures condicionals
Una expressió de condició fa que l'escriptura només s'apliqui si es compleix; si no, l'operació
falla amb ConditionalCheckFailedException. És el mecanisme de bloqueig optimista de DynamoDB i
substitueix els SELECT … FOR UPDATE del món relacional.
from botocore.exceptions import ClientError
try:
# Marcar la cistella com a convertida NOMES si continua activa. Evita que dues
# pulsacions del boto "Pagar" generin dues comandes de la mateixa cistella.
taula.update_item(
Key={"PK": "CLIENTE#4471", "SK": f"CARRITO#{ts}"},
UpdateExpression="SET #e = :nuevo, id_pedido = :pedido",
ConditionExpression="attribute_exists(PK) AND #e = :esperado",
ExpressionAttributeNames={"#e": "estado"},
ExpressionAttributeValues={
":nuevo": "convertido", ":esperado": "activo", ":pedido": "PED-84213"},
)
except ClientError as e:
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
# No es un error de sistema: es el resultat correcte d'una cursa.
print("La cistella ja s'havia convertit; no es duplica la comanda.")
else:
raiseCondicions habituals: attribute_exists(PK) per a «actualitzar només si existeix»,
attribute_not_exists(PK) per a «crear només si no existeix» —el patró d'idempotència que es reprèn a
07-05—, comparadors (<, >, BETWEEN) i size().
Lectura: GetItem, Query i per què Scan gairebé sempre és un error
from boto3.dynamodb.conditions import Key, Attr
# GetItem: un element per la seva clau completa. L'operacio mes barata i rapida.
sessio_web = taula.get_item(
Key={"PK": "CLIENTE#4471", "SK": "SESION#a91f3c"},
ConsistentRead=False, # eventual: la meitat de cost, suficient aqui
).get("Item")
# Query: elements amb la MATEIXA clau de particio, filtrant per la d'ordenacio.
# Aquesta es la C1: la cistella activa mes recent del client.
resultat = taula.query(
KeyConditionExpression=Key("PK").eq("CLIENTE#4471") & Key("SK").begins_with("CARRITO#"),
FilterExpression=Attr("estado").eq("activo"),
ScanIndexForward=False, # descendent: la SK amb marca de temps dona la mes recent primer
Limit=1,
)
cistella = resultat["Items"][0] if resultat["Items"] else NoneLa diferència entre totes dues expressions és la que més diners costa quan no s'entén:
KeyConditionExpression actua abans de llegir —tria què es llegeix, només sobre PK i SK, i cobra només
el que retorna—, mentre que FilterExpression actua després —descarta resultats, admet qualsevol
atribut, i cobra tot el que s'ha llegit es retorni o no—. Filtrar per estado quan la partició té 3
cistelles és irrellevant; fer-ho sobre una partició amb 50.000 elements significa pagar 50.000
lectures per retornar-ne 1.
Scan llegeix la taula sencera i després aplica el filtre. Amb 78 GB migrats, un Scan són uns 10
milions de RCU: més d'1,25 USD per execució i uns quants minuts. I el pitjor no és el cost: és que
consumeix la capacitat que necessiten les operacions reals, estrangulant la botiga.
Scan és acceptable en tres casos: taules petites i estables (uns pocs milers d'elements), processos
per lots fora d'hora amb Segment/TotalSegments per paral·lelitzar, i exportacions puntuals.
Qualsevol altre ús indica que falta un índex o que la clau està mal dissenyada.
Operacions per lots i transaccions
| Operació | Límit | Atòmica | Cost | Ús a MercadoFresco |
|---|---|---|---|---|
BatchGetItem |
100 elements / 16 MB | No | Normal | Carregar diversos perfils alhora |
BatchWriteItem |
25 elements / 16 MB | No | Normal | Càrrega inicial de la migració |
TransactWriteItems |
100 elements / 4 MB | Sí | ×2 | Convertir cistella i marcar sessió |
TransactGetItems |
100 elements / 4 MB | Sí | ×2 | Lectura coherent de diversos elements |
Els lots no són atòmics: si 3 de 25 escriptures fallen, les altres 22 s'apliquen i les fallides
tornen a UnprocessedItems, que cal reintentar perquè el SDK no ho fa sol.
client = sessio.client("dynamodb")
# TransactWriteItems: tot o res. Convertir la cistella i tancar la sessio han
# de passar juntes. Costa el doble de capacitat; nomes s'usa quan cal.
client.transact_write_items(TransactItems=[
{"Update": {
"TableName": "mercadofresco-carritos",
"Key": {"PK": {"S": "CLIENTE#4471"}, "SK": {"S": f"CARRITO#{ts}"}},
"UpdateExpression": "SET #e = :c",
"ConditionExpression": "#e = :a", # la condicio avorta TOTA la transaccio
"ExpressionAttributeNames": {"#e": "estado"},
"ExpressionAttributeValues": {":c": {"S": "convertido"}, ":a": {"S": "activo"}},
}},
{"Update": {
"TableName": "mercadofresco-carritos",
"Key": {"PK": {"S": "CLIENTE#4471"}, "SK": {"S": "SESION#a91f3c"}},
"UpdateExpression": "REMOVE carrito_activo",
}},
])Límit conceptual important: una transacció de DynamoDB no admet lògica intermèdia. No es pot llegir, decidir al codi i escriure dins de la mateixa transacció. Per això a 06-01 es va descartar DynamoDB per al flux de confirmació de comanda.
Índexs secundaris: GSI i LSI
Un índex secundari permet consultar per atributs que no són la clau primària:
| GSI (global) | LSI (local) | |
|---|---|---|
| PK de l'índex | Qualsevol atribut | La mateixa que la taula |
| SK de l'índex | Qualsevol atribut | Un altre atribut |
| Quan es crea | En qualsevol moment | Només en crear la taula |
| Màxim | 20 (ampliable) | 5 |
| Capacitat | Pròpia i independent | Compartida amb la taula |
| Consistència | Només eventual | Eventual o forta |
| Límit de partició | Cap | 10 GB per PK |
La regla pràctica: fes servir GSI llevat que necessitis lectura fortament consistent per un atribut
alternatiu. Els LSI es decideixen el primer dia per sempre i el seu límit de 10 GB per partició és una
trampa silenciosa. mercadofresco-carritos necessita un GSI per a C6 i per a atenció al client:
# GSI "gsi-estado-fecha": llista cistelles per estat i rang de dates sense
# recorrer la taula. Amb capacitat sota demanda no cal dimensionar-lo.
aws dynamodb update-table --table-name mercadofresco-carritos \
--attribute-definitions AttributeName=estado,AttributeType=S \
AttributeName=actualizado_en,AttributeType=S \
--global-secondary-index-updates '[{"Create": {
"IndexName": "gsi-estado-fecha",
"KeySchema": [{"AttributeName": "estado", "KeyType": "HASH"},
{"AttributeName": "actualizado_en", "KeyType": "RANGE"}],
"Projection": {"ProjectionType": "INCLUDE",
"NonKeyAttributes": ["PK", "importe"]}}}]' \
--region eu-west-1 --profile mercadofresco-devProjeccions: KEYS_ONLY copia només les claus (mínim emmagatzematge, serveix quan n'hi ha prou de
localitzar i després llegir), INCLUDE copia les claus més els atributs triats (el cas habitual) i
ALL copia l'element sencer, duplicant la taula. Un GSI amb ALL sobre 78 GB hi afegeix 78 GB i
consumeix WCU a cada escriptura de la taula base; triar INCLUDE amb els tres atributs que es pinten
a la pantalla sol reduir aquest cost en un 80 %.
Compte amb la clau calenta del GSI: estado té pocs valors diferents, així que
gsi-estado-fecha concentra trànsit. Serveix per a processos per lots de baixa freqüència; no s'ha
de fer servir al camí crític de la botiga.
Capacitat: sota demanda davant de provisionada
| Sota demanda | Provisionada | |
|---|---|---|
| Es paga | Per petició | Per capacitat reservada per hora |
| Escala | Instantània | Amb autoescalat, en minuts |
| Preu unitari | ~6,9× més car per operació | Més barat si s'aprofita |
| Risc | Factura sorpresa | Estrangulament |
| Quan | Trànsit irregular o desconegut | Trànsit predictible i sostingut |
Es pot canviar de mode, però només un cop cada 24 hores, així que no és una palanca per al pic del divendres. Per a MercadoFresco la recomanació és començar sota demanda durant la migració i els dos primers mesos: no coneixem el trànsit real de la taula nova i el cost d'equivocar-se amb capacitat provisionada és estrangular la cistella un divendres. Amb dos mesos de mètriques, es recalcula.
RCU i WCU amb les xifres del divendres
Les unitats de capacitat són la moneda de DynamoDB. 1 WCU és una escriptura per segon de fins a 1 KB (una de 3,5 KB costa 4 WCU). 1 RCU és una lectura fortament consistent per segon de fins a 4 KB, o dues lectures eventuals (una lectura eventual de 7 KB costa 1 RCU). Les operacions transaccionals, tant de lectura com d'escriptura, costen el doble.
Càlcul per al pic del divendres, 17:00-21:00, amb 900 comandes/hora:
| Operació | Peticions/s al pic | Mida mitjana | Consistència | Unitats |
|---|---|---|---|---|
| C2 Actualitzar cistella | 12 | 3 KB | — | 12 × 3 = 36 WCU |
| C5 Convertir (transaccional) | 0,25 | 3 KB | — | 0,25 × 3 × 2 = 1,5 WCU |
| Escriure/refrescar sessió | 20 | 1 KB | — | 20 WCU |
| C1 Llegir cistella | 24 | 3 KB | eventual | 24 × (4÷4) ÷ 2 = 12 RCU |
| C3 Llegir sessió | 40 | 1 KB | eventual | 40 × 1 ÷ 2 = 20 RCU |
| C4 Atenció al client | 0,1 | 15 KB | eventual | 1 RCU |
Totals del pic: ≈58 WCU i ≈33 RCU. Amb capacitat provisionada i autoescalat al 70 % d'utilització,
es dimensionaria a uns 85 WCU i 50 RCU a la finestra del divendres, baixant a 15 WCU i 10
RCU de matinada. La conclusió que convé interioritzar: la càrrega que asfixiava db.t3.small
cap en menys de 100 unitats de capacitat. El problema mai no va ser el volum; va ser que aquelles escriptures
compartien motor, memòria i VACUUM amb les transaccions de comandes.
Ràfegues, estrangulament i reintents amb retrocés exponencial
DynamoDB acumula capacitat de ràfega: la no utilitzada dels últims 300 segons es desa i es pot consumir de cop. És un coixí útil i no s'ha de dissenyar comptant-hi: no està garantit.
Quan se supera la capacitat disponible, l'operació es rebutja amb
ProvisionedThroughputExceededException (o ThrottlingException). Causes, en ordre de freqüència
real: clau calenta, capacitat provisionada insuficient, un GSI amb menys capacitat que la taula
base, i creixement més ràpid del que l'autoescalat reacciona.
from botocore.config import Config
# El SDK reintenta sol, pero per defecte de manera conservadora. El mode
# "adaptive" ajusta el ritme d'enviament a l'estrangulament observat, que es
# just el que es vol en una taula compartida per moltes Lambdes.
config = Config(
retries={"max_attempts": 10, "mode": "adaptive"},
connect_timeout=1, read_timeout=3, # fallar rapid: una Lambda no s'ha de penjar
)
taula = sessio.resource("dynamodb", config=config).Table("mercadofresco-carritos")El retrocés exponencial amb fluctuació (jitter) és imprescindible: sense la part aleatòria, tots els clients reintenten alhora i reprodueixen la ràfega que va causar el problema.
Vigilància mínima a CloudWatch cap a alertas-mercadofresco: ThrottledRequests > 0 en 5 minuts,
pujades sobtades d'UserErrors (gairebé sempre un desplegament amb una errada),
SuccessfulRequestLatency p99 > 25 ms i ConsumedWriteCapacityUnits per damunt del 80 % del que s'ha
provisionat tant a la taula com a cada índex.
Consistència eventual i consistència forta
Cada escriptura es replica en tres AZ. Una lectura eventual pot arribar a una rèplica que encara no
té l'últim canvi; el desfasament típic és de menys d'un segon. L'eventual és la de per defecte i
costa 0,5 RCU per 4 KB; la forta (ConsistentRead=True) costa 1 RCU, té una mica més de latència i
no està disponible als GSI.
Aplicat a MercadoFresco, és la regla de 06-01 —eventual per mostrar, forta per decidir— feta codi: pintar la capçalera amb el nombre d'articles va eventual, però llegir la cistella just abans de convertir-la en comanda va forta, perquè d'aquella lectura depèn el que es cobra.
TTL: la fi de les cistelles zombis
Aquesta és la funcionalitat que tota sola justifica la migració. El TTL (Time To Live) esborra automàticament els elements l'atribut de caducitat dels quals —un número Unix en segons— ja ha passat, i l'esborrat és gratuït: no consumeix WCU.
aws dynamodb update-time-to-live --table-name mercadofresco-carritos \
--time-to-live-specification "Enabled=true, AttributeName=expira_en" \
--region eu-west-1 --profile mercadofresco-devQuatre coses que cal saber per no endur-se un ensurt:
- L'esborrat no és immediat. DynamoDB elimina en un termini típic de 48 hores. Si l'aplicació no ha de veure el que ha caducat, cal filtrar-ho també a la lectura.
- L'atribut ha de ser un número en segons. Mil·lisegons, o una cadena ISO, fan que el TTL ignori l'element silenciosament. És l'errada més comuna.
- Els esborrats apareixen als Streams amb
userIdentityde tipusService, cosa que permet arxivar la cistella abandonada abans que desaparegui. - No substitueix una política de retenció documentada. Sota el RGPD cal poder explicar quant es conserven les dades i per què; el TTL implementa la política, no la defineix.
Davant del que hi ha avui —un DELETE nocturn programat que costa E/S, bloqueigs i VACUUM, del qual
ningú no s'assabenta quan falla i que ha deixat créixer la taula fins als 78 GB—, el TTL és gratuït, no pot
fallar en silenci i manté la mida estable per disseny.
Streams: reaccionar als canvis
DynamoDB Streams publica un registre ordenat de cada modificació, amb retenció de 24 hores i
quatre vistes possibles: KEYS_ONLY, NEW_IMAGE, OLD_IMAGE i NEW_AND_OLD_IMAGES. Una funció
Lambda se subscriu al stream i reacciona a cada canvi.
Usos previstos: arxivar a mercadofresco-informes-analitica les cistelles que el TTL esborrarà
—la Sara vol analitzar l'abandonament a 06-04— i alimentar mètriques de negoci. L'orquestració d'esdeveniments
a més escala, amb encaminament i diversos consumidors desacoblats, és EventBridge i es veu a
07-03; Streams és el mecanisme de baix nivell lligat a aquesta taula.
Còpies de seguretat, xifratge i taules globals
Còpies de seguretat. PITR restaura a qualsevol segon dels últims 35 dies, s'activa amb
aws dynamodb update-continuous-backups --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true i restaura sempre a una taula nova: no sobreescriu.
Xifratge. Sempre actiu en repòs, sense opció de desactivar-lo. Amb la clau gestionada pel
client alias/mercadofresco-datos de 04-02 s'aconsegueix el que exigeix el criteri de MercadoFresco:
control de la rotació, registre de cada ús a trail-mercadofresco i capacitat de revocar l'accés
a les dades revocant permisos sobre la clau. Les taules globals, finalment, repliquen
actiu-actiu entre regions amb resolució de conflictes per «l'últim que escriu guanya»;
MercadoFresco opera només a eu-west-1 i no les necessita, però són l'equivalent d'Aurora
Global Database (06-03) si algun dia hi ha operació a França.
Accés des de boto3 i des de Lambda
resource converteix tipus automàticament (Decimal ↔ N) i és la interfície per a la lògica de
negoci; client exposa l'API crua amb el descriptor explícit ({"S": "..."}) i és l'única que
cobreix transaccions i administració. Avís: resource exigeix Decimal i rebutja float amb
Float types are not supported. És una molèstia deliberada: els imports en coma flotant són un error
en qualsevol sistema que manegi diners.
Política de privilegi mínim per a la Lambda mercadofresco-estado-pedido:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OperacionesDelCarrito",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query",
"dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": [
"arn:aws:dynamodb:eu-west-1:111122223333:table/mercadofresco-carritos",
"arn:aws:dynamodb:eu-west-1:111122223333:table/mercadofresco-carritos/index/*"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${aws:PrincipalTag/cliente}"]
}
}
},
{
"Sid": "SinScanNiBorradoNiAdministracion",
"Effect": "Deny",
"Action": ["dynamodb:Scan", "dynamodb:DeleteTable", "dynamodb:DeleteItem"],
"Resource": "*"
}
]
}Tres decisions deliberades. dynamodb:LeadingKeys limita les operacions a les claus de partició
que coincideixen amb una etiqueta del principal: és aïllament per client a nivell d'IAM, no de
codi. El Deny de Scan no és paranoia, és economia: impedeix que un desplegament precipitat fiqui un
Scan al camí crític. I el Deny de DeleteItem obliga que l'esborrat sigui sempre el TTL.
Neteja. Si has creat taules per practicar, esborra-les amb
aws dynamodb delete-table. Una taula sota demanda buida costa cèntims, però una de proves amb PITR actiu i uns quants GB carregats sí que apareix a la factura.
Cost comparat amb mantenir-ho a RDS
Xifres mensuals estimades per a eu-west-1, amb la càrrega descrita:
| Concepte | Avui a mercadofresco-pedidos |
DynamoDB sota demanda | DynamoDB provisionada |
|---|---|---|---|
| Escriptures (5,4 M/mes) | Inclòs | 4,05 USD | — |
| Lectura (17,4 M/mes) | Inclòs | 0,44 USD | — |
| Capacitat reservada | — | — | ~9,20 USD (85 WCU / 50 RCU) |
| Emmagatzematge | 78 GB × 0,127 = 9,91 USD | 8 GB × 0,25 = 2,00 USD | 2,00 USD |
| PITR | Inclòs a RDS | 1,60 USD | 1,60 USD |
| Part de la instància atribuïble | ~35 % de 118 USD = 41,30 USD | 0 | 0 |
| Total | ≈51,21 USD | ≈8,09 USD | ≈12,80 USD |
Dues observacions. Primer, 8 GB davant de 78 GB: el TTL elimina el 90 % de l'emmagatzematge perquè
les cistelles abandonades deixen d'acumular-se, i aquest sol fet ja gairebé paga la migració. Segon, i
més important, la xifra de 41,30 USD no és l'estalvi real: en alliberar aquella càrrega,
mercadofresco-pedidos deixa de necessitar el 35 % de la seva capacitat, cosa que obre la porta a reduir
mida o a absorbir creixement sense escalar. El valor de la migració no és a la línia de la factura
de DynamoDB, sinó a la que desapareix de la factura relacional. I el benefici que no apareix en
cap columna: desapareix el VACUUM que competia per l'E/S amb les comandes els divendres a la
tarda.
La migració, pas a pas
# Crear la taula sota demanda, xifrada amb la clau del projecte i etiquetada.
aws dynamodb create-table \
--table-name mercadofresco-carritos \
--attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
--key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=alias/mercadofresco-datos \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=carrito Key=Propietario,Value=luis \
Key=CentroCoste,Value=plataforma \
--region eu-west-1 --profile mercadofresco-dev| Fase | Acció | Criteri de sortida |
|---|---|---|
| 1 | Activar TTL, PITR i alarmes d'estrangulament | Alarmes verificades a alertas-mercadofresco |
| 2 | Escriptura doble; lectura de PostgreSQL | 7 dies amb error d'escriptura <0,01 % |
| 3 | Migrar només les cistelles actives de 30 dies | Mostra de 1.000 claus sense discrepàncies |
| 4 | Lectura de DynamoDB amb reserva a PostgreSQL | 7 dies amb reserves <0,1 % |
| 5 | Treure reserva i escriptura a PostgreSQL | TiempoConfirmacionPedido p99 estable o millor |
| 6 | Exportar sesiones a S3 i eliminar-la |
Còpia verificada; WriteIOPS d'RDS a la baixa |
I els 55 GB de cistelles abandonades no es migren: s'exporten en Parquet a
mercadofresco-informes-analitica —la Sara els analitzarà a 06-04— i es descarten: una migració és
l'única ocasió barata de no arrossegar les escombraries. I l'interruptor que fa tot això reversible sense
desplegar és el paràmetre /mercadofresco/produccion/carrito/origen a Parameter Store (04-03), amb
valors postgres, ambos o dynamodb, llegit en arrencar cada procés i desat a la memòria cau 60 segons.
Errors Habituals i Consells
Fer servir PutItem per actualitzar. PutItem substitueix l'element sencer: els atributs que no
envies desapareixen. Si dos processos fan PutItem sobre la mateixa cistella, el segon esborra el que
va escriure el primer. Per modificar, sempre UpdateItem.
Filtrar amb FilterExpression creient que estalvia. El filtre s'aplica després de llegir i
pagar. Si filtres molt, el model està malament: això és un índex que falta.
Ficar un Scan al camí crític. Funciona perfectament en desenvolupament amb 40 elements i
tomba la botiga amb 4 milions. El Deny explícit de la política d'IAM és la millor prevenció.
Triar malament la clau de partició. És l'única decisió que no es pot canviar sense migrar. Abans de
crear la taula, escriu les consultes i comprova que totes es resolen amb GetItem o Query.
Posar el TTL en mil·lisegons o en ISO 8601. Ha de ser un número Unix en segons; si no ho és, el TTL no esborra res i no avisa. I no és un temporitzador exacte: té un termini de fins a 48 hores, així que si la lògica exigeix que el que ha caducat no es vegi, cal filtrar-ho també en llegir.
Ignorar UnprocessedItems als lots. BatchWriteItem retorna els elements que no ha pogut
escriure i no els reintenta sol. En una migració, aquest descuit són cistelles perdudes.
Crear GSI amb projecció ALL per comoditat. Duplica l'emmagatzematge i consumeix WCU a cada
escriptura de la taula base. INCLUDE amb el que de debò es pinta sol ser suficient.
Consell: mesura des del primer dia. Activa ReturnConsumedCapacity="TOTAL" en desenvolupament i registra
les unitats consumides per operació. Una operació que consumeix 40 RCU per retornar una cistella indica
un problema de model que en producció costa diners.
Consell: fes servir DAX només quan ho demostris. DynamoDB Accelerator és una memòria cau davant de DynamoDB, amb latències de microsegons i la mateixa API. Costa des d'uns 90 USD/mes per node i només compensa amb lectures repetitives molt intenses. La cistella no ho és: cada client llegeix la seva. El catàleg sí que ho seria, i per a això MercadoFresco farà servir ElastiCache (06-05), que a més serveix dades que no són a DynamoDB.
Exercicis
Exercici 1: modelar l'històric de comandes
Atenció al client necessita una taula mercadofresco-pedidos-consulta a DynamoDB, alimentada des
d'Aurora, que resolgui sense Scan aquestes quatre consultes: (1) donat un id_pedido, retornar la comanda
completa (18.000/dia); (2) donats un client i un rang de dates, llistar les seves comandes de més recent
a més antiga (2.400/dia); (3) llistar les comandes en estat en_reparto d'una ciutat concreta
(350/dia); (4) donat un id_pedido, retornar l'historial de canvis d'estat (1.200/dia).
Defineix: PK i SK de la taula, els índexs necessaris amb la seva projecció, com es resol cada consulta, i justifica per què cap clau de partició no és calenta.
Exercici 2: calcular capacitat i decidir el mode
MercadoFresco llança una campanya de Black Friday amb aquestes previsions per a mercadofresco-carritos: 4
hores de pic amb 3.500 comandes/hora (davant de les 900 habituals), proporció d'operacions idèntica
a la del divendres normal, i trànsit normal les altres 20 hores. Preus d'eu-west-1: sota demanda
0,7452 USD per milió d'escriptures i 0,1491 USD per milió de lectures eventuals; provisionada
0,00065 USD per WCU-hora i 0,00013 USD per RCU-hora.
Calcula: (a) WCU i RCU necessàries al pic; (b) el cost del dia complet en mode sota demanda; (c) el cost en mode provisionat amb autoescalat, indicant quins valors configuraries; (d) quin mode tries i per què; (e) quins dos límits de DynamoDB podrien aparèixer i com els prevens.
Exercici 3: diagnosticar un estrangulament
Tres setmanes després de la migració, un divendres a les 18:20 salten les alarmes. ThrottledRequests de
mercadofresco-carritos: 4.200 en 5 minuts. ConsumedWriteCapacityUnits de la taula: 61 WCU de 120
provisionades. ConsumedWriteCapacityUnits de l'índex gsi-estado-fecha: 98 WCU de 100
provisionades. Un desplegament d'aquell matí va afegir un atribut historial (llista de canvis d'estat)
a cada cistella, i la mida mitjana de l'element va passar de 3 KB a 11 KB. SuccessfulRequestLatency sense
canvis.
Respon: (a) per què s'estrangula la taula si consumeix la meitat de la seva capacitat; (b) quin paper hi juga l'atribut nou; (c) tres mesures ordenades de més ràpida a més estructural; (d) què hauria evitat l'incident abans del desplegament.
Solucions
Solució 1
Taula mercadofresco-pedidos-consulta, disseny de taula única:
| Entitat | PK | SK |
|---|---|---|
| Comanda | PEDIDO#<id> |
META |
| Canvi d'estat | PEDIDO#<id> |
ESTADO#<ts_iso> |
Índexs: gsi-cliente-fecha (PK id_cliente, SK fecha_pedido, INCLUDE d'import, estat i
nombre de línies) resol la consulta 2; gsi-reparto-ciudad (PK ciudad_estado, SK fecha_pedido,
INCLUDE d'id_pedido i repartidor) resol la 3.
Resolució: la 1 és un GetItem amb PK=PEDIDO#<id>, SK=META. La 2, una Query sobre
gsi-cliente-fecha amb Key("id_cliente").eq(...) & Key("fecha_pedido").between(...) i
ScanIndexForward=False. La 3, una Query sobre gsi-reparto-ciudad amb
Key("ciudad_estado").eq("VALENCIA#en_reparto"). La 4, una Query sobre la taula amb
Key("PK").eq("PEDIDO#<id>") & Key("SK").begins_with("ESTADO#").
Per què cap clau no és calenta: PEDIDO#<id> té tants valors com comandes, amb repartiment
uniforme. id_cliente reparteix entre desenes de milers. ciudad_estado és l'única amb risc —poques
ciutats— però és dispersa: l'atribut només s'escriu mentre la comanda està en repartiment i
s'elimina en lliurar-la, així que l'índex conté uns centenars d'elements en tot moment i rep
350 consultes al dia. Els índexs dispersos són la tècnica correcta per consultar per estats
transitoris sense crear una partició calenta permanent.
Solució 2
(a) Capacitat al pic. El factor de multiplicació és 3.500 ÷ 900 = 3,89.
| Operació | Peticions/s | Unitats |
|---|---|---|
| Actualitzar cistella | 46,7 | 140 WCU |
| Convertir (transaccional) | 0,97 | 5,8 WCU |
| Sessions | 77,8 | 78 WCU |
| Total escriptura | ≈224 WCU | |
| Llegir cistella | 93,4 | 47 RCU |
| Llegir sessió | 155,6 | 78 RCU |
| Total lectura | ≈125 RCU |
(b) Sota demanda. Pic: 3,23 M escriptures i 1,80 M lectures en 4 h. Resta del dia, amb la càrrega normal de 58 WCU i 33 RCU: 4,18 M escriptures i 2,38 M lectures. Totals 7,41 M escriptures (5,52 USD) i 4,18 M lectures (0,62 USD) = ≈6,14 USD el dia.
(c) Provisionada. Amb autoescalat al 70 % caldrien 320 WCU i 180 RCU al pic, i 85/50 la resta: 4 h × (320 × 0,00065 + 180 × 0,00013) = 0,93 USD, més 20 h × (85 × 0,00065 + 50 × 0,00013) = 1,24 USD = ≈2,17 USD el dia. Però cal programar l'escalat amb antelació: l'autoescalat reacciona en minuts i el pic de Black Friday puja en segons.
(d) Elecció: sota demanda. Costa 3,97 USD més un sol dia. A canvi elimina el risc d'estrangular la cistella en la jornada de més facturació de l'any i no exigeix encertar una previsió que ningú no ha fet mai. Estalviar 4 USD arriscant vendes de Black Friday és l'exemple perfecte d'optimització mal dirigida; la conversa sobre capacitat provisionada es té al gener, amb dades.
(e) Dos límits. Primer, la partició: 1.000 WCU i 3.000 RCU. Amb CLIENTE#<id> ben
repartit no és un risc, llevat que un procés de proves concentri trànsit en pocs clients; es
prevé revisant que el trànsit sintètic faci servir identificadors variats. Segon, la capacitat
de les taules sota demanda, que escala ràpid però pot estrangular davant de multiplicacions instantànies
molt superiors al pic previ; es prevé configurant el rendiment en calent per endavant
—o escalfant la taula amb càrrega creixent— i amb reintents adaptatius al client.
Solució 3
(a) Per què s'estrangula la taula. No s'estrangula la taula: s'estrangula l'índex. Un GSI
té capacitat pròpia i independent, i gsi-estado-fecha és al 98 % dels seus 100 WCU. Quan un GSI
no pot absorbir les seves escriptures, DynamoDB rebutja les escriptures a la taula base, perquè no pot
deixar l'índex permanentment desincronitzat. La capacitat lliure de la taula és irrellevant: el
coll és aigües avall. És el mode de fallada de GSI més freqüent i el menys intuïtiu.
(b) L'atribut nou. L'element va passar de 3 KB a 11 KB, així que cada escriptura va passar de 3 WCU a 11
WCU: multiplicació per 3,7. I si la projecció del GSI és ALL, l'índex replica l'atribut
historial encara que ningú no el consulti per allà, amb la qual cosa hereta íntegrament l'augment. S'està pagant
capacitat d'índex per copiar dades que cap consulta de l'índex no fa servir.
(c) Tres mesures. Immediata (minuts): pujar la capacitat provisionada del GSI a 300 WCU;
restaura el servei a l'acte amb cost afegit menyspreable. A curt termini (el mateix dia): com que la
projecció no es pot modificar, crear un GSI nou amb INCLUDE dels atributs necessaris —sense
historial—, migrar les consultes i esborrar l'antic. Estructural: treure l'historial d'estats
de l'element i modelar-lo com a elements propis amb SK = ESTADO#<ts>, com a la solució 1; l'element
torna a 3 KB i l'historial creix sense engreixar res. És la correcció correcta: un atribut
que creix sense cota dins d'un element és sempre un error de model, i a més s'acostava al
límit dur de 400 KB.
(d) Què ho hauria evitat. Una prova de càrrega en preproducció amb la mida real d'element; una
alarma sobre ConsumedWriteCapacityUnits de l'índex al 80 %, i no només de la taula —el tauler
mercadofresco-produccion vigilava la taula, no el GSI—; i una revisió de disseny que hagués
detectat l'atribut de creixement il·limitat. Totes tres són barates; l'incident va ser un divendres a les
18:20.
Conclusió
La cistella i les sessions ja no són a PostgreSQL. Saps què és DynamoDB i per què encaixa aquí: sense servidors, sense connexions per esgotar, amb latència constant davant del volum i amb escriptura que escala horitzontalment en comptes d'exigir una instància més gran. Domines el model de dades —taula, element de fins a 400 KB, atributs sense esquema— i la decisió més difícil de revertir: la clau primària, amb la seva clau de partició repartida per hash entre particions de 3.000 RCU i 1.000 WCU, i la seva clau d'ordenació que agrupa físicament el que està relacionat. Saps reconèixer i corregir una clau calenta, inclòs el repartiment d'escriptures quan un producte es torna viral.
Sobretot, saps dissenyar des de les consultes: enumerar les sis consultes reals de la cistella abans
de dibuixar res, comprovar que totes es resolen amb GetItem o Query, i només llavors escriure el
model. D'aquí surt mercadofresco-carritos amb PK = CLIENTE#<id> i SK = CARRITO#<ts> /
SESION#<id> / PERFIL, un disseny de taula única que retorna en una operació el que abans eren
tres consultes a tres taules —i amb el criteri per saber quan aquest disseny no compensa.
Manegues les operacions: UpdateItem amb els seus quatre verbs en comptes del PutItem que esborra el que no
envies, expressions de condició com a bloqueig optimista perquè dues pulsacions del botó de pagar
no generin dues comandes, Query davant de Scan i la diferència que més diners costa
—KeyConditionExpression tria el que es llegeix, FilterExpression descarta el que ja has pagat—,
lots amb els seus UnprocessedItems i TransactWriteItems al doble de cost i sense lògica intermèdia. I
saps quan un GSI i quan un LSI, amb l'efecte real de les projeccions.
I tens els comptes: 58 WCU i 33 RCU cobreixen el pic del divendres —la càrrega que asfixiava
db.t3.small cap en menys de 100 unitats de capacitat—, sota demanda durant la migració perquè
estalviar quatre dòlars arriscant vendes és optimitzar malament, ràfegues i estrangulament amb reintents
adaptatius, consistència eventual per mostrar i forta per decidir, i el TTL que converteix els
78 GB en 8 GB i fa que les cistelles zombis no puguin tornar a existir. Amb Streams preparats per a
07-03, PITR actiu, xifratge amb alias/mercadofresco-datos i una política d'IAM que denega
explícitament Scan i DeleteItem. Cost: de 51 USD al mes a 8 USD, i la desaparició del VACUUM
que competia amb les comandes els divendres a la tarda.
Queda la càrrega principal. mercadofresco-pedidos ja no suporta la cistella, però continua sent una
instància de PostgreSQL amb Multi-AZ i una rèplica, amb un emmagatzematge que creix 12 GB al mes, una
commutació per error d'un parell de minuts i un cost que es paga sencer també de matinada. A
06-03, «Amazon Aurora», veurem l'evolució natural d'aquesta base de dades: la separació entre
còmput i emmagatzematge, el volum distribuït en sis còpies sobre tres zones de disponibilitat,
rèpliques de lectura amb retard de mil·lisegons, Serverless v2 perquè la vall nocturna deixi de
costar el mateix que el pic del divendres, clonació ràpida perquè el Luis provi amb dades realistes sense
duplicar cost, i la migració des de l'RDS actual amb temps d'aturada mínim.
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
