Les quatre lliçons anteriors han muntat les peces per separat: on viu el codi, com es construeix i es prova, com es desplega sense tallar el servei, i com s'encadena tot. Aquesta lliçó no afegeix serveis nous. Agafa un canvi real del Luis i el segueix des del seu portàtil fins a producció, pas a pas, veient en cada punt què es comprova i què passa exactament si falla.

El canvi triat no és trivial a propòsit. La Sara ha demanat afegir una franja horària de lliurament a la comanda, i això toca quatre coses alhora: el codi de la botiga, un consumidor de cola-mercadofresco-pedidos, l'esquema d'Aurora i el contracte de l'esdeveniment PedidoConfirmado. És exactament el tipus de canvi que al mòdul 7 va provocar un incident —un productor desplegat abans que el seu consumidor— i que aquí farem sense que ningú escrigui una ordre ni creui els dits.

Avís de cost. El recorregut complet d'aquest canvi —quatre execucions del pipeline entre desenvolupament, preproducció i producció, més les construccions de les PR— costa uns 0,60 USD. AppConfig, que apareix al final, cobra 0,0000002 USD per petició de configuració amb memòria cau de 60 s: per a MercadoFresco, cèntims al mes. Dades fictícies.

Contingut

  1. El canvi: franja horària de lliurament
  2. El recorregut complet, d'un cop d'ull
  3. Branca, commits i pull request
  4. Construcció, proves i artefacte
  5. Compatibilitat d'esquema: expansió i contracció a Aurora
  6. Compatibilitat de contracte: versionar PedidoConfirmado
  7. L'ordre correcte entre productor i consumidor
  8. Desplegament en desenvolupament, fum i aprovació de la Marta
  9. Canari en producció, vigilància i promoció
  10. Configuració i secrets per entorn
  11. La piràmide de proves aplicada
  12. Mètriques DORA: MercadoFresco abans i després
  13. Runbook de reversió i la fallada que es detecta tard
  14. Desplegaments petits i banderes de funcionalitat
  15. El divendres a la tarda com a decisió de negoci
  16. Tancament del mòdul i cost
  17. Errors habituals i consells
  18. Exercicis
  19. Conclusió

El canvi: franja horària de lliurament

La Sara ho justifica amb una dada: el 31 % de les incidències de repartiment són per absència del client. Cada una costa un segon intent, una trucada i, en producte fresc, de vegades la mercaderia. La petició és afegir tres franges —manana, tarde, 24h— al procés de compra.

El que sembla un camp de formulari toca cinc peces:

Peça Què canvia Risc
Botiga web (asg-mercadofresco-tienda) Selector al procés de compra Baix
API de comandes Accepta i persisteix franja_entrega Mitjà
Esquema d'Aurora Columna nova a pedidos Alt: no reverteix amb el codi
Esdeveniment PedidoConfirmado Atribut nou al detail Alt: hi ha consumidors aliens
Consumidor de magatzem Llegeix la franja per ordenar les rutes Mitjà

Els dos riscos alts són el cor de la lliçó, i comparteixen una propietat: són les dues coses que la reversió automàtica de CodeDeploy no pot desfer. Tota la resta torna enrere en noranta segons.

El recorregut complet, d'un cop d'ull

flowchart TB
    A[Luis: branca funcionalidad/franja-horaria] --> B[Push: pipeline sobre PR<br/>construeix i prova]
    B -->|Vermell| B1[La PR no es pot fusionar]
    B -->|Verd| C[PR 47: revisio de la Marta<br/>1 aprovacio obligatoria]
    C --> D[Squash a desarrollo]
    D --> E[Construccio: artefacte<br/>1.6.0-a3f9c21 immutable]
    E --> F[Qualitat: estatic,<br/>contracte, secrets]
    F --> G[Migracio EXPANDIR<br/>columna NULLABLE]
    G --> H[Desplegar consumidor<br/>PRIMER]
    H --> I[Desplegar botiga en dev]
    I --> J[Fum dev: flux de compra]
    J --> K[Preproduccio + fum]
    K --> L[APROVACIO MARTA<br/>versio, diff, migracio]
    L --> M[Produccio: blue/green<br/>+ canari en cobraments]
    M --> N[Porta de qualitat<br/>15 min de metriques]
    N -->|OK| O[Promocio: etiqueta v1.6.0]
    N -->|Degradat| P[REVERSIO automatica<br/>90 s]
    O --> Q[Setmanes despres:<br/>CONTRAURE]

Branca, commits i pull request

El Luis parteix de desarrollo, seguint el model de 08-01, i s'imposa el límit de tres dies:

git checkout desarrollo && git pull
git checkout -b funcionalidad/franja-horaria

Els commits, amb el format convencional que el pipeline llegirà després:

feat(pedidos): afegir franja_entrega al model i a l'API
feat(eventos): incloure franja_entrega a PedidoConfirmado com a versio 2
feat(almacen): ordenar rutes de repartiment per franja
test(pedidos): cobrir franges valides, invalida i absent
docs(catalogo-eventos): documentar PedidoConfirmado v2

El primer feat determina que la versió pugi a 1.6.0 —MINOR, funcionalitat compatible—. Si algun portés BREAKING CHANGE: al cos, seria 2.0.0, i això és justament el que evitarem.

En empènyer la branca, el disparador de pull request de 08-04 arrenca el pipeline en mode reduït: construcció, proves i qualitat, sense etapes de desplegament. El resultat es publica a la PR com a comprovació d'estat, i la PR no es pot fusionar si està en vermell. Aquí es tanca per fi la protecció de main que va quedar oberta a 08-01: no n'hi ha prou que algú aprovi, la construcció ha d'estar en verd.

La PR #47 porta la plantilla de 08-01, amb una secció que en aquest canvi és la més important:

## Riscos
- Esquema Aurora: columna NULLABLE, sense DEFAULT, sense index. Fase EXPANDIR.
  NO reverteix amb el codi. Contraccio prevista per d'aqui a 3 setmanes.
- Esdeveniment PedidoConfirmado passa a versio 2: atribut OPCIONAL afegit.
  Els consumidors v1 l'ignoren. Verificat amb proves de contracte.
- Ordre de desplegament: consumidor de magatzem ABANS que la botiga.

La Marta revisa i aprova; el Luis fusiona amb squash, i el commit resultant entra a desarrollo.

Construcció, proves i artefacte

En fusionar, el pipeline arrenca complet. L'etapa de construcció fa el de 08-02 i produeix mercadofresco-tienda-1.6.0-a3f9c21.zip, amb el commit a les metadades.

Això és l'únic que es construeix en tot el recorregut. El mateix objecte d'S3 es desplegarà en desenvolupament, en preproducció i en producció. Si el pipeline reconstruís abans de producció, l'aprovació de la Marta deixaria de significar res, com vam veure a l'exercici 1 de 08-04.

Comprovació Bloqueja Què passa si falla
ruff i format Falla en 2 s; el Luis corregeix i torna a empènyer
git-secrets Falla i cal rotar si el secret era real
Proves unitàries (218) La PR no es fusiona
Cobertura ≥ 75 % Escriure les proves que falten
pip-audit Sí, amb avís Actualitzar la dependència o documentar
Proves de contracte La clau d'aquest canvi; vegeu més avall
bandit No, informe Es revisa els dilluns

Compatibilitat d'esquema: expansió i contracció a Aurora

Aplicant la regla de 08-03, el canvi d'esquema no va en el mateix pas que el codi i es parteix en tres fases separades en el temps.

Fase 1, EXPANDIR, en una etapa pròpia del pipeline abans de qualsevol desplegament d'aplicació:

-- migraciones/2026-08-02-001-expandir-franja-entrega.sql
-- Compatible amb la versio 1.5.2, que no coneix aquesta columna.
ALTER TABLE pedidos ADD COLUMN franja_entrega VARCHAR(10) NULL;
-- Sense NOT NULL: la 1.5.2 insereix sense la columna i no falla.
-- Sense DEFAULT: no reescriu 4,2 milions de files ni bloqueja la taula.
-- Sense index: s'afegeix a la fase de contraccio, amb CONCURRENTLY.

Els tres comentaris són l'exercici sencer. Amb NOT NULL, la versió anterior fallaria en cada inserció durant el desplegament gradual. Amb DEFAULT, a PostgreSQL 11 i posteriors no es reescriu la taula, però en motors anteriors sí, i sobre 4,2 milions de files és un bloqueig de minuts. I l'índex s'ajorna perquè CREATE INDEX bloqueja escriptures: es farà amb CONCURRENTLY i fora del desplegament.

Fase 2, MIGRAR, és el codi 1.6.0: escriu franja_entrega quan ve i tolera NULL quan no.

Fase 3, CONTRAURE, tres setmanes després i com a canvi propi, quan cap instància no serveix ja la 1.5.2 i s'ha comprovat que totes les comandes noves porten franja:

CREATE INDEX CONCURRENTLY idx_pedidos_franja ON pedidos(franja_entrega);
UPDATE pedidos SET franja_entrega = '24h' WHERE franja_entrega IS NULL;
ALTER TABLE pedidos ALTER COLUMN franja_entrega SET NOT NULL;

El control automàtic que impedeix saltar-se això és l'anàlisi de migracions a CodeBuild que vam dissenyar a l'exercici 3 de 08-03: falla la construcció si detecta DROP COLUMN, RENAME COLUMN, SET NOT NULL o DROP TABLE en un fitxer de migració sense l'etiqueta -- CONTRACCION-APROBADA. La fase 3 la porta i la fase 1 no la necessita.

Compatibilitat de contracte: versionar PedidoConfirmado

L'altre risc alt. PedidoConfirmado el consumeixen tres destinacions de bus-mercadofresco: la cua del magatzem, la d'analítica i l'API de l'ERP del soci. Els dos últims no els controla el Luis, i aquí hi ha el problema: publicar un esdeveniment diferent pot trencar algú que ni saps que està escoltant.

Canvi És compatible? Què fer
Afegir un camp opcional Pujar la versió menor; endavant
Afegir un camp obligatori No Tractar-lo com a opcional amb valor per defecte
Treure un camp No Deprecar, avisar, esperar, treure
Reanomenar un camp No Afegir el nou, publicar tots dos, treure el vell
Canviar el tipus d'un camp No Camp nou amb nom diferent
Restringir valors permesos No Ampliar, mai restringir

Afegir franja_entrega com a camp opcional és el cas fàcil, i l'esdeveniment puja a versió 2:

{
  "version": "2",
  "source": "mercadofresco.tienda",
  "detail-type": "PedidoConfirmado",
  "detail": {
    "id_pedido": "PED-2026-00841",
    "id_cliente": "CLI-4471",
    "importe_eur": 48.20,
    "lineas": [{ "sku": "FRUT-0012", "uds": 3 }],
    "franja_entrega": "tarde",
    "confirmado_en": "2026-08-02T18:14:22Z"
  }
}

Un consumidor v1 rep un objecte amb un camp que no coneix i l'ignora, sempre que estigui escrit amb la regla d'or: sigues estricte amb el que publiques i tolerant amb el que reps. Un consumidor que validi l'esquema amb additionalProperties: false es trencaria, i per això la prova de contracte és bloquejant:

# tests/contrato/test_pedido_confirmado.py
import pytest
from app.eventos import construir_esdeveniment_comanda_confirmada
from consumidores.almacen_v1 import processar as processar_v1   # el consumidor ANTIC
from consumidores.almacen_v2 import processar as processar_v2

COMANDA = {"id_pedido": "PED-2026-00841", "id_cliente": "CLI-4471",
           "importe_eur": 48.20, "lineas": [{"sku": "FRUT-0012", "uds": 3}],
           "franja_entrega": "tarde"}

def test_consumidor_ANTIC_no_es_trenca_amb_esdeveniment_NOU():
    """Compatibilitat cap endavant: v1 ha d'ignorar el camp que no coneix."""
    r = processar_v1(construir_esdeveniment_comanda_confirmada(COMANDA))
    assert r["estado"] == "procesado"          # no llanca, no rebutja

def test_consumidor_NOU_tolera_esdeveniment_ANTIC():
    """Compatibilitat cap enrere: durant el desplegament hi haura esdeveniments v1 en vol."""
    sense_franja = {k: v for k, v in COMANDA.items() if k != "franja_entrega"}
    ev = construir_esdeveniment_comanda_confirmada(sense_franja)
    r = processar_v2(ev)
    assert r["estado"] == "procesado"
    assert r["franja_asignada"] == "24h"        # valor per defecte explicit

def test_franja_invalida_es_rebutja_abans_de_publicar(bus_fals):
    with pytest.raises(ValueError, match="franja_entrega"):
        construir_esdeveniment_comanda_confirmada({**COMANDA, "franja_entrega": "noche"})
    assert bus_fals.esdeveniments_publicats == 0

Les dues proves centrals són la segona i la tercera, i convé entendre per què es necessiten totes dues. La segona comprova compatibilitat cap endavant: el consumidor vell amb l'esdeveniment nou, que és el que passa tan bon punt la botiga comença a publicar la v2 i encara queda algun consumidor sense actualitzar. La tercera comprova compatibilitat cap enrere: el consumidor nou amb l'esdeveniment vell, que és el que passa amb els missatges que ja eren a la cua quan es va desplegar el consumidor. Durant un desplegament gradual les dues situacions existeixen alhora, i una prova sola només en cobreix la meitat.

Per a l'ERP del soci, que no es pot provar, la salvaguarda és diferent: l'arxiu d'EventBridge que vam activar a 07-03. Si el soci es trenca, hi ha 90 dies d'esdeveniments per reproduir.

L'ordre correcte entre productor i consumidor

Aquí es tanca l'incident que va quedar plantejat al mòdul 7. La regla és curta i val per a qualsevol canvi de contracte:

Desplega sempre primer el que llegeix, després el que escriu.

El raonament és de teoria de conjunts. Un consumidor nou entén els esdeveniments vells i els nous —perquè ho hem provat—, així que desplegar-lo primer és segur: durant la finestra només hi ha esdeveniments vells, que sap processar. A l'inrevés no funciona: si el productor surt primer, hi ha esdeveniments v2 circulant cap a consumidors v1 durant els minuts que duri el desplegament, i n'hi ha prou que un validi estrictament perquè apareguin missatges a mercadofresco-pedidos-fallidos.

Al pipeline això és una etapa amb dues accions i runOrder diferent, que és exactament el mecanisme de 08-04:

{
  "name": "DesplegarProduccion",
  "actions": [
    { "name": "ConsumidorAlmacen", "runOrder": 1,
      "configuration": { "ApplicationName": "app-mercadofresco-trabajadores",
                         "DeploymentGroupName": "dg-mercadofresco-trabajadores-produccion" },
      "inputArtifacts": [{ "name": "PaqueteTienda" }] },
    { "name": "TiendaWeb", "runOrder": 2,
      "configuration": { "ApplicationName": "app-mercadofresco-tienda",
                         "DeploymentGroupName": "dg-mercadofresco-tienda-produccion" },
      "inputArtifacts": [{ "name": "PaqueteTienda" }] }
  ]
}

runOrder: 1 per al consumidor i 2 per a la botiga. L'ordre deixa de dependre que algú se'n recordi: està declarat, versionat i s'executa igual totes les vegades. Aquest és, en una frase, el valor de tot el mòdul 8.

Desplegament en desenvolupament i proves de fum

En desenvolupament el pipeline fa servir AllAtOnce perquè la velocitat importa més que la disponibilitat. Les proves de fum de 08-04 corren contra l'entorn acabat de desplegar i afegeixen dos casos d'aquest canvi:

def test_comanda_amb_franja_es_confirma_i_persisteix():
    r = _crear_comanda({"franja_entrega": "tarde"})
    assert r.status_code == 201
    assert r.json()["franja_entrega"] == "tarde"
    assert r.elapsed.total_seconds() < 2.0

def test_comanda_SENSE_franja_continua_funcionant():
    """Compatibilitat cap enrere contra l'entorn real, no simulat."""
    r = _crear_comanda({})
    assert r.status_code == 201
    assert r.json()["franja_entrega"] in (None, "24h")

La segona prova és la que de veritat importa: verifica contra un entorn real que un client amb l'app antiga al mòbil continua podent comprar. Si falla, el pipeline s'atura en desenvolupament i ningú no se n'ha assabentat fora de l'equip.

L'aprovació de la Marta

A les 10:42 li arriba la notificació a alertas-mercadofresco i a Slack:

Pipeline: pipeline-mercadofresco-tienda | Execucio: 7a1f2c33
Versio 1.6.0-a3f9c21 | Autor: luis | PR #47
Fum preproduccio: OK (12/12) | Latencia p95 preprod: 380 ms
MIGRACIO: fase EXPANDIR aplicada (columna NULLABLE, no reverteix amb el codi)
ESDEVENIMENT: PedidoConfirmado passa a v2 (atribut opcional)
Finestra: OK (dimarts 10:42, fora de la franja prohibida)
Diff: github.com/mercadofresco/mercadofresco-tienda/compare/v1.5.2...1.6.0-a3f9c21

La Marta obre el diff, veu 214 línies en quatre fitxers, comprova que la migració és d'expansió i aprova. Quaranta segons. No perquè confiï cegament, sinó perquè el missatge respon per endavant a les preguntes que es faria: què entra, qui ho ha fet, si s'ha provat, què no reverteix i si és bon moment.

Canari en producció i vigilància

L'etapa de producció fa tres desplegaments coordinats:

Ordre Què Estratègia Durada
1 Consumidor de magatzem Al lloc, AllAtOnce ~2 min
2 Lambda mercadofresco-cobrar-pago Canary10Percent5Minutes ~5 min
3 Botiga web Blue/green, espera de 30 min ~12 min

Durant el procés vigilen tres alarmes connectades a la reversió automàtica: mercadofresco-alb-latencia-alta, mercadofresco-pedidos-fallidos i mercadofresco-cobrar-pago-errores-canario. I en acabar, la porta de qualitat de 08-04 observa 15 minuts les mètriques de MercadoFresco/Tienda, amb la finestra dimensionada per cabre dins dels 30 minuts de terminationWaitTimeInMinutes —si la porta trigués més, l'entorn blau ja estaria acabat i la reversió de 90 segons hauria deixat d'existir—.

Promoció o reversió automàtica

Si tot va bé, el pipeline acaba, CodeDeploy acaba l'entorn blau als 30 minuts i una acció final etiqueta la versió:

git tag -a v1.6.0 -m "Franja horaria de lliurament"
git push origin v1.6.0

El que s'etiqueta és el que va arribar a producció i va sobreviure a la porta de qualitat, no el que es va construir. És una diferència important: l'etiqueta certifica un fet, no una intenció.

Si alguna cosa falla, cada mecanisme actua en el seu termini:

Fallada Qui la detecta Reacció Temps
Errors al canari Alarma de l'àlies CodeDeploy retorna el pes Segons
Latència alta després del canvi de trànsit mercadofresco-alb-latencia-alta L'oient torna al blau ~90 s
Missatges a la DLQ mercadofresco-pedidos-fallidos Reversió + avís ~2 min
Degradació subtil sense alarma Porta de qualitat Atura el pipeline ~15 min
Res de tot l'anterior Una persona Runbook Minuts o hores

I la part que cap mecanisme no reverteix: la columna franja_entrega continua allà. No passa res, perquè és NULLABLE i la 1.5.2 la ignora. Aquest és exactament el motiu que la fase d'expansió sigui innòcua: converteix un canvi irreversible en un que no molesta.

Configuració i secrets per entorn

L'artefacte és el mateix en els tres entorns, així que la diferència entre ells no pot ser al paquet: és a la configuració que es llegeix en arrencar, amb el que vam muntar a 04-03.

aws ssm put-parameter --name /mercadofresco/produccion/franjas_entrega_activas \
  --value "manana,tarde,24h" --type String --overwrite \
  --profile mercadofresco-dev --region eu-west-1
Tipus de valor On viu Exemple
Configuració no sensible Parameter Store /mercadofresco/<entorn>/ Franges actives, llindars
Secrets Secrets Manager, amb rotació mercadofresco/produccion/rds/mfadmin
Constants del codi Al repositori Format de data, codis d'error
Valors «a mà» Enlloc

L'última fila és la regla: si un valor difereix entre entorns, ha de ser a Parameter Store. Un valor posat a mà en una instància desapareix en el següent desplegament blue/green —les instàncies són noves— i reapareix com una fallada intermitent impossible de diagnosticar.

La piràmide de proves aplicada

Nivell Quantes On corren Durada Bloqueja?
Unitàries 218 CodeBuild, sense xarxa 45 s
Integració 34 CodeBuild amb moto 1 min
Contracte 12 CodeBuild 15 s
Fum 12 Contra dev, preprod i prod 40 s
Càrrega 1 Preproducció, nocturna 20 min No: informe

La forma de la piràmide no és estètica: la base és ampla perquè les proves de baix són barates, ràpides i precises, i el cim és estret perquè les de dalt són lentes, cares i fràgils. Un equip amb 200 proves d'extrem a extrem i 20 unitàries té una suite que triga una hora, falla de manera intermitent i no diu on és el problema.

Les proves de contracte són el nivell que gairebé ningú no té i el que aquest canvi demostra necessari: costen 15 segons i són l'única cosa que detecta que un consumidor aliè es trencarà.

Mètriques DORA: MercadoFresco abans i després

Les quatre mètriques DORA mesuren la capacitat de lliurament d'un equip. Les de MercadoFresco, mesurades sobre els tres mesos anteriors al mòdul 8 i sobre els dos posteriors:

Mètrica Abans (maig-juliol) Després (agost-setembre) Categoria
Freqüència de desplegament 1,2 per setmana 4,8 per setmana De mitjana a alta
Temps de lliurament (commit → producció) 6 dies i 4 h 3 h 20 min De mitjana a alta
Taxa de fallada de canvis 23 % 7 % De baixa a alta
Temps de restauració 47 min (mediana) 4 min De mitjana a elit

Tres lectures que convé fer sobre aquests números, perquè les xifres soles enganyen:

La freqüència puja perquè el cost marginal de desplegar baixa. Abans, desplegar costava al Luis una hora de feina i una dosi de nervis, així que acumulava canvis: «ja que desplego, que hi vagin els quatre». Ara costa zero, així que desplega tan bon punt alguna cosa està llesta. I això realimenta la taxa de fallada: un desplegament d'un canvi és molt més fàcil de diagnosticar que un de quatre.

El temps de restauració és la millora més gran i la més important. De 47 minuts a 4 no és una millora de procés, és un canvi de naturalesa: 47 minuts de botiga degradada un divendres a les 19:00 són unes 700 comandes afectades; 4 minuts en són unes 60. I la major part d'aquests 4 minuts és detecció, no reacció, perquè la reversió en si triga 90 segons.

La taxa de fallada del 7 % no és zero i no ho hauria de ser. Un equip amb 0 % de fallades gairebé sempre està desplegant massa poc i massa tard. L'objectiu no és no fallar mai, és que fallar sigui barat: que una fallada costi 4 minuts i no 47.

I un advertiment. Les mètriques DORA són útils com a termòmetre de l'equip i perilloses com a objectiu individual: tan bon punt «freqüència de desplegament» passa a ser una meta, apareixen desplegaments buits per pujar el número. Mesura-les, mira la tendència, i no les pengis en un tauler de rendiment personal.

Runbook de reversió i la fallada que es detecta tard

Els mecanismes automàtics cobreixen els primers quinze minuts. El cas difícil és la fallada que apareix a les 19:30 d'un desplegament de les 11:00, quan l'entorn blau ja no existeix i la porta de qualitat va donar el vistiplau fa hores.

Aquest és el runbook, pensat per seguir-se a les tres de la matinada:

1. Contenir (0-5 min). Es pot apagar sense desplegar? Si el canvi està darrere d'una bandera de funcionalitat, desactivar-la és qüestió de segons i és sempre la primera opció. Si no, deshabilitar la transició del pipeline cap a producció amb disable-stage-transition perquè ningú no empitjori la situació mentre es diagnostica.

2. Decidir la direcció (5-10 min). És la decisió que més s'erra sota pressió:

Situació Direcció Per què
Només ha canviat el codi Enrere: desplegar la versió anterior Ràpid i segur
Hi ha hagut migració d'expansió Enrere: la columna sobra però no molesta Innòcua per disseny
Hi ha hagut migració de contracció Endavant: corregir i desplegar Tornar enrere trencaria més
L'esdeveniment ha pujat de versió Depèn de si hi ha consumidors actualitzats Vegeu més avall

3. Executar (10-20 min). Cap enrere és un desplegament normal de la versió anterior pel pipeline, fent servir l'artefacte que continua a S3 amb la seva ruta predictible —tienda/1.5.2-8b2e440/paquete.zip—, cosa que fa la reversió possible en un minut. És la raó que la regla de cicle de vida del bucket sigui de 60 dies i no de 3.

4. Verificar (20-30 min). /salud retorna la versió esperada, TiempoConfirmacionPedido torna a la seva franja normal, PedidosConfirmados recupera el ritme, i la DLQ deixa de créixer.

5. Recuperar les dades, el pas que s'oblida i el que més mal deixa. Els missatges acumulats a mercadofresco-pedidos-fallidos es processen amb el runbook de la DLQ de 07-05 —contenir, classificar, corregir, reprocessar—, mai amb un redrive abans de corregir la causa. I si l'esdeveniment havia pujat de versió, el que es va publicar durant l'incident es reprodueix des de l'arxiu d'EventBridge, sempre amb FilterArns per no repetir efectes que ja han passat.

6. Post-mortem sense culpables, en 48 hores. Què ha passat, per què les portes no ho han detectat i quina prova nova ho hauria detectat. Aquest últim punt és l'únic lliurable obligatori: un post-mortem que no produeix una prova nova és una reunió.

Desplegaments petits i banderes de funcionalitat

Dues pràctiques que multipliquen el valor de tot l'anterior.

Desplegaments petits i freqüents. No és una preferència estètica: és aritmètica del diagnòstic. Un desplegament amb un canvi té un sospitós; un amb sis en té quinze parelles possibles d'interacció. La regla de MercadoFresco és que una branca viu tres dies com a màxim —de 08-01— i que si un canvi no hi cap, es parteix en increments compatibles cap enrere, exactament com s'ha partit aquest.

Banderes de funcionalitat amb AWS AppConfig. Separen desplegar de publicar: el codi surt a producció apagat, i s'encén quan i per a qui es decideixi.

# app/config.py
import boto3, json, time
client = boto3.client("appconfigdata")
_cache, _expira, _token = {}, 0, None

def bandera(nom, per_defecte=False):
    """Llegeix AppConfig amb cache de 60 s. Davant d'un error, retorna el valor per defecte."""
    global _cache, _expira, _token
    try:
        if time.time() > _expira:
            if _token is None:
                _token = client.start_configuration_session(
                    ApplicationIdentifier="mercadofresco",
                    EnvironmentIdentifier="produccion",
                    ConfigurationProfileIdentifier="banderas",
                    RequiredMinimumPollIntervalInSeconds=60)["InitialConfigurationToken"]
            r = client.get_latest_configuration(ConfigurationToken=_token)
            _token = r["NextPollConfigurationToken"]
            contingut = r["Configuration"].read()
            if contingut:
                _cache = json.loads(contingut)
            _expira = time.time() + 60
        return _cache.get(nom, {}).get("enabled", per_defecte)
    except Exception:
        return per_defecte      # una bandera trencada MAI no ha de tombar la botiga

Amb això, el procés de compra fa if bandera("franja_entrega_activa"): i el Luis desplega el dimarts amb la bandera apagada, l'encén el dimecres al 10 % de clients, mira les mètriques i arriba al 100 % el dijous. Desplegar deixa de ser el moment de risc.

Aspecte Desplegament canari Bandera de funcionalitat
Què controla Quina versió del codi corre Quin comportament s'activa
Granularitat % de peticions Per client, regió o segment
Temps d'apagada 90 s (reversió) Segons, sense desplegar
Durada raonable Minuts Dies o setmanes
Cost si se n'abusa Cap Deute tècnic: branques mortes al codi

L'última fila és l'advertiment. Una bandera és codi temporal amb data de caducitat: cal apuntar-la al backlog el mateix dia que es crea i esborrar-la tan bon punt estigui al 100 % i estable. Un sistema amb quaranta banderes antigues té un espai d'estats que ningú no pot raonar ni provar.

El divendres a la tarda com a decisió de negoci

«No es desplega en divendres» és una de les regles més repetides del sector, i gairebé sempre pel motiu equivocat. El motiu habitual és la por: no sabem què passarà i no volem ser aquí per veure-ho. Això no és una política, és un símptoma —que el pipeline no dona confiança, que la reversió no està provada, que ningú no sap quant es triga a tornar enrere—.

Amb el que MercadoFresco té ara, la pregunta es pot respondre amb números:

Factor Dilluns 10:00 Divendres 19:00
Comandes/hora ~180 ~900
Comandes afectades per 4 min de degradació ~12 ~60
Persones disponibles per respondre 3 1
Temps fins a la finestra següent 1 h 60 h

La regla de MercadoFresco no és «no es desplega en divendres», és «no es desplega a producció entre les 16:00 i les 22:00 dels divendres». I la raó no és la por: és que el mateix incident costa cinc vegades més en aquesta franja i hi ha un terç de l'equip per atendre'l. És una decisió de negoci, presa amb dades, revisable si les dades canvien. Fora d'aquesta franja es desplega el divendres com qualsevol altre dia, inclosa la tarda de dijous i el matí de divendres.

I s'implementa com a control, no com a recordatori: una transició deshabilitada de manera programada, perquè no depengui que algú se'n recordi a les 18:50.

Tancament del mòdul i cost

El quart problema del curs queda resolt. El mòdul va començar amb el codi de MercadoFresco en un portàtil, un scp per SSH un divendres a la tarda i cap manera de tornar enrere tret de buscar el fitxer anterior. Acaba amb un canvi que toca quatre sistemes recorrent el camí complet sense que ningú escrigui una ordre.

Problema del curs Mòdul Estat
1. Servidor únic que no aguanta el pic 2-3 Resolt: ASG, ALB, CloudFront
2. Seguretat i secrets 4 Resolt: IAM, KMS, Secrets Manager, WAF
3. Confirmació de comanda de 2.893 ms 6-7 Resolt: 400 ms amb cues i esdeveniments
4. Desplegaments arriscats 8 Resolt
5. Infraestructura creada a mà 9 Pendent

La cadena completa, en una frase per baula: 08-01 va posar el codi en un repositori amb branques curtes, pull requests i protecció de main. 08-02 va convertir cada push en una construcció neta que prova, escaneja i produeix un artefacte immutable. 08-03 va convertir el desplegament en una operació governada amb ganxos, estratègies graduals i reversió automàtica davant d'alarma. 08-04 ho va encadenar tot en un pipeline amb etapes, aprovació informada i portes de qualitat. I 08-05 ha demostrat que la cadena aguanta un canvi de veritat, amb esquema i contracte inclosos.

Concepte Cost mensual
CodeCommit / GitHub / CodeConnections / AppConfig 0,10 USD
CodeBuild (~1.850 min) 20,90 USD
CodeDeploy (capacitat blue/green) 2,00 USD
CodePipeline V2 3,50 USD
Artefactes a S3 i registres 1,30 USD
Total del mòdul 8 ≈ 27,80 USD/mes

Vint-i-vuit dòlars al mes. La comparació correcta no és contra zero: és contra 47 minuts de mediana de restauració, un 23 % de desplegaments que fallaven i les hores que el Luis dedicava a desplegar a mà.

Errors Habituals i Consells

Desplegar el productor abans que el consumidor. És l'incident del mòdul 7 i la regla és una sola frase: primer el que llegeix, després el que escriu.

Ficar el canvi d'esquema al mateix desplegament que el codi. La reversió reverteix artefactes, no bases de dades.

Una migració d'expansió amb NOT NULL o amb índex. Deixa de ser innòcua: trenca la versió anterior o bloqueja la taula.

Provar només la compatibilitat cap enrere. Durant un desplegament gradual existeixen les dues direccions alhora; calen les dues proves.

Treure o reanomenar un camp d'un esdeveniment. No és compatible encara que el consumidor «sembli» tolerant. Afegir, publicar tots dos, esperar, treure.

Reconstruir l'artefacte abans de producció. Invalida totes les proves i l'aprovació anteriors.

Configuració diferent posada a mà en una instància. Desapareix en el següent blue/green i torna com una fallada intermitent indiagnosticable.

Banderes de funcionalitat que mai no es retiren. Quaranta banderes antigues són un espai d'estats que ningú no pot provar. Apunta-la al backlog el dia que la crees.

Una bandera que tomba la botiga si AppConfig falla. El valor per defecte davant d'un error ha de ser sempre segur.

Fer servir les mètriques DORA com a objectiu individual. Apareixen desplegaments buits i la mètrica deixa de mesurar res.

Redrive de la DLQ abans de corregir la causa. Els missatges tornen a fallar i es perd informació.

Consell: escriu la secció «Riscos» de la PR abans d'escriure el codi. Si no saps quins riscos té el teu canvi, encara no l'has dissenyat. I assaja la reversió a propòsit un cop per trimestre, cronometrant-la: és l'única manera que el número del runbook sigui cert.

Consell: en el post-mortem, el lliurable obligatori és una prova nova. Sense això, és una reunió.

Consell: converteix «no despleguem els divendres» en una finestra amb dades. Una regla que es justifica amb números es respecta; una que es justifica amb por se la salten el dia que hi ha pressa.

Exercicis

Exercici 1: el canvi que no cap en tres dies

La Sara demana ara una cosa més gran: lliuraments programats fins amb 7 dies d'antelació, amb calendari de disponibilitat per codi postal, preus diferents segons el dia i un límit de comandes per franja i zona. Toca la botiga, l'API, tres taules noves a Aurora, el consumidor de magatzem, l'esdeveniment PedidoConfirmado (que necessita fecha_entrega a més de franja_entrega) i un càlcul de preu nou. El Luis estima tres setmanes.

Descompon-lo en increments que respectin el límit de tres dies per branca. Per a cadascun indica què es desplega, què canvia a l'esquema i en quina fase, si necessita bandera de funcionalitat i què es pot verificar en producció en acabar-lo. Digues explícitament quin és el primer i per què.

Exercici 2: la fallada de les 19:30

Dimarts, 11:00: es desplega la 1.6.0 amb tot en verd. A les 19:30, la Sara avisa que al panell mercadofresco-negocio les comandes amb franja tarde han caigut a zero des de les 17:00, mentre que manana i 24h van normals. PedidosConfirmados total està només un 8 % per sota del normal, cap alarma no ha saltat i la porta de qualitat va donar el vistiplau a les 11:20. L'entorn blau ja no existeix.

Respon: (a) per què cap mecanisme automàtic no ho va detectar; (b) els primers 15 minuts, en ordre; (c) cap a on revertir i per què, sabent que la migració va ser d'expansió; (d) què es fa amb les comandes d'aquelles dues hores i mitja; (e) quins tres controls nous sortirien del post-mortem.

Exercici 3: mesurar i millorar DORA

MercadoFresco porta dos mesos amb el pipeline. Dades de l'últim mes: 19 desplegaments a producció; una mediana de temps de lliurament de 3 h 20 min, de la qual 2 h 40 min és espera de l'aprovació de la Marta; 2 desplegaments revertits de 19; mediana de temps de restauració de 4 min, però amb un cas de 95 minuts —la fallada detectada tard—. La Marta vol arribar a categoria elit en les quatre mètriques.

Per a cadascuna: digues on és MercadoFresco, quin és l'objectiu elit, quin canvi concret proposaries i quin risc té aquest canvi. Indica quina abordaries primer i justifica-ho.

Solucions

Solució 1

Sis increments, cadascun desplegable i verificable per separat. El principi que els ordena és que cada increment ha de ser útil o innocu per si sol, mai «mitja funcionalitat a mig camí».

# Què es desplega Esquema Bandera Verificable en acabar
1 Taules de calendari i disponibilitat, buides; ningú no les llegeix EXPANDIR: 3 taules noves No Les taules existeixen i no afecten res
2 Càrrega del calendari i API interna de consulta Cap No /interno/disponibilidad?cp=08001 respon
3 fecha_entrega a la comanda i a l'esdeveniment (v3), opcional EXPANDIR: columna NULLABLE Sí, apagada Les comandes continuen funcionant sense data
4 El consumidor de magatzem llegeix fecha_entrega amb valor de reserva Cap No Processa esdeveniments amb data i sense
5 Selector de data al procés de compra Cap Sí, al 10 % Un 10 % de clients veu el calendari
6 Preu per dia i límit per franja i zona EXPANDIR: columna de preu Preus correctes amb la bandera activa

El primer és l'increment 1, i la raó és la regla d'aquesta lliçó: el canvi d'esquema és l'única cosa que no reverteix amb el codi, així que va sol, en el seu propi desplegament, sense res més que pugui fallar i obligar a una reversió. Tres taules buides que ningú no consulta són el canvi més segur que existeix: si alguna cosa va malament, no hi ha res per desfer. Ficar-les al mateix desplegament que el codi nou seria barrejar el risc irreversible amb el reversible, que és just el que 08-03 adverteix.

Dues observacions més. L'increment 4 va abans que el 5 per la regla de l'ordre: el consumidor entén abans que el productor comenci a publicar. I el 3 desplega l'esdeveniment v3 amb la bandera apagada, de manera que el camp existeix al codi però no es publica fins que s'encengui: això dona una finestra per verificar els consumidors en producció sense haver canviat res de veritat.

Solució 2

(a) Perquè cap alarma no mesurava la dimensió que va fallar. PedidosConfirmados és un agregat, i una caiguda del 8 % en el total està dins del soroll normal d'un dimarts; l'alarma de latència no salta perquè el sistema no va lent, està rebutjant; la DLQ no creix perquè probablement no hi ha excepció, sinó una validació que retorna un 400 net. I la porta de qualitat va fer la seva feina correctament a les 11:20, sis hores abans que el problema aparegués: era una franja horària amb poc trànsit de tipus tarde.

La lliçó de fons és incòmoda i val per a qualsevol sistema: les portes automàtiques detecten el que se'ls ha dit que mirin, en la finestra en què miren. Una fallada que només afecta un segment i només es manifesta sis hores després és invisible per a elles per construcció. Ho va detectar la Sara mirant un panell de negoci, i això no és un fracàs del sistema: és la raó per la qual existeix mercadofresco-negocio.

(b) Els primers 15 minuts. Contenir: si franja_entrega està darrere d'una bandera d'AppConfig, apagar-la —els clients tornen al comportament anterior en segons i l'incident acaba aquí—. Si no, deshabilitar la transició cap a producció perquè ningú no desplegui a sobre. Confirmar: comprovar a CloudWatch que la caiguda de tarde és real i no un problema del panell, i mirar els registres de l'API filtrant per peticions amb franja_entrega=tarde —el més probable és un 400 o un 422 sistemàtic—. Acotar: fallen totes les comandes de tarde o només algunes? des de les 17:00 exactes? Que comenci a les 17:00 i no a les 11:00 és la pista principal: suggereix una condició horària, per exemple una validació que rebutja la franja tarde quan ja ha passat la seva hora de tall, amb la zona horària mal calculada. Avisar: l'equip i la Sara, amb el que se sap i el que no.

(c) Cap enrere, a la 1.5.2, si la bandera no existeix o no ho resol. I es pot fer sense por precisament perquè la migració va ser d'expansió: la columna franja_entrega és NULLABLE, la 1.5.2 no la coneix i la ignora, així que revertir el codi deixa el sistema exactament com estava abans del dimarts. Les comandes de les últimes hores conserven la seva franja a la columna, que es recuperarà en desplegar la versió corregida. Si la migració hagués estat de contracció, la resposta seria la contrària —cap endavant, corregint— i amb la botiga degradada mentrestant: la fase d'expansió és el que converteix aquesta decisió en fàcil.

(d) Les comandes d'aquelles dues hores i mitja. No són a la DLQ, perquè probablement ni van arribar a crear-se: són clients que van intentar triar tarde, van veure un error i se'n van anar. És una pèrdua de vendes, no de dades, i cal tractar-la com a tal: estimar el volum comparant amb la mateixa franja dels dimarts anteriors, i decidir amb la Sara si escau alguna acció comercial. Si a més hi hagués comandes creades amb la franja mal assignada, s'identifiquen per rang temporal i es corregeixen amb un script revisat, mai amb un UPDATE improvisat en producció. I si hi ha esdeveniments que no es van publicar, es recuperen de l'arxiu d'EventBridge amb FilterArns.

(e) Tres controls del post-mortem. Una alarma per dimensió, no només per agregat: una mètrica PedidosConfirmados amb dimensió FranjaEntrega i una alarma que salti si qualsevol franja cau a zero durant 15 minuts en horari comercial. És barata i hauria avisat a les 17:15 en lloc de a les 19:30. Una prova de fum amb rellotge: els casos que depenen de l'hora cal provar-los a hores diferents, així que una execució sintètica cada hora amb les tres franges, o proves unitàries amb el rellotge simulat cobrint les vores —i molt en particular la zona horària, que aquí és la sospitosa—. I estendre la porta de qualitat amb una segona avaluació diferida: una comprovació automàtica a les 4 i a les 12 hores del desplegament que compari les mètriques per dimensió amb el mateix període de la setmana anterior i avisi si alguna cosa s'ha mogut. No bloqueja el pipeline —ja ha acabat— però converteix «ho va veure la Sara per casualitat» en «ho ha avisat el sistema».

Solució 3

Freqüència de desplegament. MercadoFresco està en 19 al mes, uns 4,4 per setmana: categoria alta. L'elit és sota demanda, diverses vegades al dia. El canvi: treure l'aprovació manual —que és el que imposa un ritme diari— i agrupar menys. El risc: treure-la sense que la porta de qualitat estigui madura canvia un control per una hipòtesi, i les condicions de sortida que la Marta va escriure a 08-04 existeixen precisament per a això.

Temps de lliurament. 3 h 20 min és alta; l'elit és menys d'una hora. I el diagnòstic està servit: 2 h 40 min de les 3 h 20 són espera humana, així que el pipeline tècnic ja triga 40 minuts i està en rang elit. El canvi no és optimitzar la construcció: és l'aprovació. Dues mesures graduals abans de treure-la del tot: aprovació des de Slack amb AWS Chatbot, que baixa l'espera d'hores a minuts perquè la Marta no necessita ser davant de l'ordinador, i un suplent designat. El risc de la primera és pràcticament nul, i per això és la que cal fer ja.

Taxa de fallada de canvis. 2 de 19 és un 10,5 %: categoria mitjana-alta; l'elit està per sota del 15 %, així que tècnicament ja hi és. Però el número enganya sense mirar els casos: una de les dues fallades va ser la de les 19:30, que no va detectar cap porta. El canvi útil no és baixar el percentatge, és millorar la detecció amb els tres controls de l'exercici 2. El risc de perseguir el número per si mateix és el de sempre: es deixen de desplegar canvis petits per no arriscar l'estadística.

Temps de restauració. Mediana de 4 minuts, que és elit, però amb un cas de 95 minuts. Aquí la mediana menteix: el que fa mal és la cua. El canvi: banderes de funcionalitat en tot canvi de comportament visible, que converteixen la restauració en segons sense desplegar, i l'alarma per dimensió que redueix el temps de detecció, que és on eren 90 d'aquests 95 minuts.

Primer, l'aprovació des de Slack. És la que més impacte té —2 h 40 min de 3 h 20—, la més barata —una integració d'una tarda—, la de menor risc —no elimina cap control, només el fa més accessible— i la que a més millora indirectament la freqüència, perquè l'espera de l'aprovació és el que empeny a agrupar canvis. Començar per treure l'aprovació seria atacar la mateixa mètrica amb molt més risc i sense haver complert les condicions que el mateix equip es va posar.

Conclusió

Un canvi del Luis ha recorregut el camí complet. Va començar com una petició de la Sara sobre el 31 % d'incidències de repartiment i va acabar en producció tres hores després, tocant el codi de la botiga, un consumidor de cola-mercadofresco-pedidos, l'esquema d'Aurora i el contracte de PedidoConfirmado, sense que ningú escrivís una ordre. La branca va durar dos dies, la PR es va revisar amb la construcció en verd, l'artefacte es va construir una vegada i aquest mateix objecte va recórrer els tres entorns, la Marta va aprovar en quaranta segons amb la informació al davant, i la porta de qualitat va confirmar amb mètriques reals que la botiga continuava sana.

El que fa que això funcioni no són els serveis, són les dues disciplines que aquesta lliçó ha posat en pràctica. La primera és la compatibilitat: la migració en fase d'expansió amb la columna NULLABLE, sense NOT NULL, sense DEFAULT i sense índex, que converteix un canvi irreversible en un d'innocu; i l'esdeveniment versionat amb un atribut opcional, amb les dues proves de contracte que gairebé ningú no escriu —el consumidor vell amb l'esdeveniment nou i el consumidor nou amb l'esdeveniment vell—, perquè durant un desplegament gradual les dues situacions existeixen alhora. La segona és l'ordre: primer el que llegeix, després el que escriu, declarat com a runOrder al pipeline i no com una cosa que algú ha de recordar. Aquí queda tancat l'incident que el mòdul 7 va deixar obert.

Tens la piràmide de proves amb els seus nivells bloquejants i el que gairebé ningú no té —les de contracte, quinze segons que són l'única cosa que detecta que un consumidor aliè es trencarà—; la configuració per entorn a Parameter Store i els secrets a Secrets Manager, amb la regla que un valor posat a mà en una instància desapareix en el següent blue/green i torna com una fallada indiagnosticable; i el runbook de reversió amb la seva decisió més difícil, la direcció: enrere si només ha canviat codi o hi ha hagut expansió, endavant si hi ha hagut contracció.

I tens els números. Freqüència de desplegament d'1,2 a 4,8 per setmana. Temps de lliurament de 6 dies a 3 hores. Taxa de fallada del 23 % al 7 %. Temps de restauració de 47 minuts a 4. Amb les tres lectures que els acompanyen: que la freqüència puja perquè el cost marginal de desplegar baixa, que la restauració és la mètrica que de veritat canvia la naturalesa del risc, i que un 0 % de fallades seria senyal d'estar desplegant massa poc —l'objectiu no és no fallar, és que fallar costi quatre minuts i no quaranta-set—. Més les banderes de funcionalitat amb AppConfig, que separen desplegar de publicar i tenen el seu propi deute si ningú no les retira, i la conversió de «no despleguem els divendres» en una finestra justificada amb dades: cinc vegades més comandes en joc i un terç de l'equip disponible. Una decisió de negoci, no de fe.

El quart problema del curs queda resolt, i per uns 28 dòlars al mes. MercadoFresco té un codi governat, una construcció que verifica, un desplegament que es reverteix sol i un pipeline que ho encadena tot.

Però hi ha una asimetria difícil de justificar i que aquesta lliçó ha deixat a la vista dues vegades. El codi de l'aplicació està governat; la infraestructura no. La VPC es va crear amb create-vpc en un terminal. Les cues amb create-queue. Les regles d'EventBridge pujant un JSON. Les alarmes, els grups de seguretat, els grups de destinació de l'ALB, les taules de DynamoDB: tot existeix perquè algú va escriure una ordre algun dia, i enlloc no hi ha un registre de per què. Ningú no pot recrear l'entorn de MercadoFresco des de zero. Ningú no pot garantir que preproducció tingui la mateixa configuració que producció —de fet no la té, i la diferència es descobreix en cada incident—. I el mateix pipeline que existeix perquè ningú no desplegui a mà es va crear a mà, amb un JSON al portàtil del Luis. Els tres entorns comparteixen a més el compte 111122223333, així que el radi d'explosió no està acotat, les quotes es comparteixen i la factura no se separa de veritat.

Al mòdul 9, «Infraestructura com a codi i govern de comptes», ataquem el cinquè i últim problema del curs. 09-01, «AWS CloudFormation», porta les plantilles declaratives, les piles, els conjunts de canvis que diuen què passarà abans que passi i la reversió d'infraestructura. 09-02, «AWS CDK», permet definir aquesta mateixa infraestructura amb codi de veritat, amb abstraccions i proves. 09-03, «Elastic Beanstalk», mostra l'alternativa gestionada i quan compensa. I 09-04, «AWS Organizations», resol la separació seriosa d'entorns amb comptes diferents, polítiques de control de serveis i facturació consolidada. Al final d'aquest mòdul, tot el que hem construït en vuit mòduls podrà recrear-se des de zero amb una ordre.

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