Las fotos de MercadoFresco ya están en S3, con su ciclo de vida y su versionado. Pero el corazón del negocio sigue en el servidor de la oficina: la base de datos de pedidos. Ahí están los clientes, los productos, las cestas, las rutas de reparto y la facturación. Es el dato que no se puede perder, el que no puede estar caído los viernes a las 19:00, y el que hoy se respalda con un pg_dump a un disco externo que nadie ha probado restaurar nunca.

Esta lección migra esa base de datos a Amazon RDS (Relational Database Service), el servicio gestionado de bases de datos relacionales de AWS. No es solo "PostgreSQL en la nube": es delegar en AWS el parcheo, las copias, la conmutación por error y la replicación, tareas que en MercadoFresco nadie hacía porque nadie tenía tiempo. Y con las copias automáticas y la restauración a un punto en el tiempo cerramos definitivamente el problema 2, la parte que quedó abierta en 02-02 cuando resolvimos las copias de los discos pero no las de los datos transaccionales.

Contenido

  1. Qué es un servicio gestionado y qué deja de ser tu problema
  2. RDS frente a PostgreSQL instalado en una EC2
  3. Motores disponibles y cuál elige MercadoFresco
  4. Crear la instancia RDS por consola
  5. Crear la instancia RDS por CLI
  6. Multi-AZ: qué es realmente una réplica síncrona en espera
  7. Réplicas de lectura: los informes de Sara sin castigar producción
  8. Copias automáticas, retención y restauración a un punto en el tiempo
  9. Snapshots manuales y el cierre del problema 2
  10. Grupos de parámetros y ventanas de mantenimiento
  11. Monitorización: métricas básicas y Performance Insights
  12. Conectarse desde la aplicación sin credenciales en el código
  13. Escalado vertical y almacenamiento autoescalable
  14. Migrar desde el servidor de la oficina
  15. Costes y cómo parar una instancia de pruebas

Qué es un servicio gestionado y qué deja de ser tu problema

En el servidor de la oficina, PostgreSQL era responsabilidad íntegra de Marta: instalarlo, configurarlo, actualizarlo, vigilarlo, respaldarlo y levantarlo cuando se caía. En RDS, AWS asume una parte concreta y bien delimitada de ese trabajo.

Tarea Servidor de la oficina PostgreSQL en EC2 Amazon RDS
Comprar y mantener hardware Marta AWS AWS
Instalar el sistema operativo Marta AWS
Parchear el sistema operativo Marta (nunca) AWS
Instalar PostgreSQL Marta AWS
Aplicar parches de PostgreSQL Marta (nunca) AWS (en la ventana de mantenimiento)
Configurar copias automáticas Marta (a medias) AWS
Probar la restauración Nadie AWS proporciona PITR
Montar alta disponibilidad Imposible (compleja) AWS (una casilla)
Conmutación por error automática No existe AWS (1-2 minutos)
Crear réplicas de lectura Muy laborioso AWS (un comando)
Cifrado en reposo No AWS (una casilla)
Diseñar el esquema y las consultas Marta y Luis
Optimizar índices Marta y Luis
Seguridad de la aplicación Marta y Luis

Es el modelo de responsabilidad compartida de la lección 01-01 aplicado a un servicio concreto: la línea se mueve hacia arriba, pero nunca desaparece. RDS no va a arreglar una consulta sin índice ni va a diseñar tus tablas.

Lo que RDS no te deja hacer, y conviene saberlo antes de elegirlo:

  • No hay acceso al sistema operativo. Nada de SSH a la máquina de la base de datos. Si tu operativa depende de scripts en el servidor, hay que replantearla.
  • No hay superusuario real. El usuario maestro tiene el rol rds_superuser, potente pero limitado: no puede hacer todo lo que hace un superuser de PostgreSQL.
  • Las extensiones están en lista blanca. Solo puedes instalar las que AWS admite (que son muchas: PostGIS, pg_stat_statements, pgvector…).
  • Las versiones tienen calendario de fin de soporte y AWS acabará actualizándote.

RDS frente a PostgreSQL instalado en una EC2

La pregunta legítima de Luis es: "podríamos instalar PostgreSQL en una EC2 y sale más barato, ¿no?". La respuesta honesta es que el precio por hora es menor, y el coste total casi nunca.

Criterio PostgreSQL en EC2 Amazon RDS
Coste por hora Menor (solo la instancia + EBS) ~25-40 % más caro por el servicio gestionado
Tiempo de puesta en marcha Horas o días 10 minutos
Alta disponibilidad Montar Patroni/repmgr a mano Casilla Multi-AZ
Copias Scripts propios que hay que mantener Automáticas, con PITR al segundo
Parches Tu calendario, tu responsabilidad Ventana de mantenimiento
Acceso al SO Sí, control total No
Extensiones exóticas Cualquiera Solo las admitidas
Versiones muy antiguas o muy nuevas Cualquiera Las que ofrezca AWS
Coste de personal Alto y continuo Bajo

Elige EC2 solo si necesitas control del sistema operativo, una extensión no admitida, una versión concreta fuera del catálogo, o si tienes un equipo de administradores de bases de datos que ya hace ese trabajo. Para MercadoFresco, que tiene a Marta a media jornada para todo, RDS no es una opción: es la única opción sensata.

El cálculo que zanja la discusión: una db.t3.small en eu-west-1 cuesta alrededor de 30 USD al mes frente a unos 20 USD de la EC2 equivalente. Diez dólares de diferencia. Una sola incidencia nocturna de PostgreSQL al año, con Marta despierta a las 3:00, cuesta más que eso.

Motores disponibles y cuál elige MercadoFresco

Motor Cuándo se elige
PostgreSQL Estándar abierto, muy completo, extensiones (PostGIS, pgvector). Elección de MercadoFresco
MySQL Enorme base instalada, aplicaciones PHP heredadas
MariaDB Bifurcación de MySQL con licencia plenamente libre
Oracle Migraciones de sistemas empresariales existentes; licencia cara
SQL Server Ecosistema Microsoft; licencia incluida o propia
Db2 Sistemas heredados de IBM
Aurora (PostgreSQL/MySQL compatible) Motor propio de AWS, hasta 5× MySQL y 3× PostgreSQL, almacenamiento distribuido en 3 AZ. Se ve en la lección 06-03

MercadoFresco ya usaba PostgreSQL en el servidor de la oficina, así que RDS PostgreSQL permite migrar sin reescribir consultas. Es la decisión de menor riesgo.

Una aclaración que ahorra confusión: Aurora es también RDS, se gestiona desde la misma consola y comparte casi toda la operativa que veremos aquí, pero su arquitectura de almacenamiento es distinta. En 06-03 valoraremos si a MercadoFresco le compensa migrar a Aurora cuando abra en tres ciudades más. Y la elección general entre relacional, clave-valor o almacén de columnas es la lección 06-01.

Crear la instancia RDS por consola

  1. Consola → RDS → verifica la región Irlanda (eu-west-1)Crear base de datos.
  2. Método: Creación estándar (la fácil oculta opciones que necesitas ver).
  3. Motor: PostgreSQL, versión 16.x.
  4. Plantilla: Producción activa Multi-AZ y el almacenamiento provisionado por defecto. Para aprender, elige Capa gratuita y así no gastas; describimos aquí la de producción.
  5. Configuración:
    • Identificador: mercadofresco-pedidos
    • Usuario maestro: mfadmin (no uses postgres ni admin)
    • Contraseña: marca Administrar credenciales maestras en AWS Secrets Manager. Con esto la contraseña nunca la ves, nunca la escribes y AWS la rota sola. Secrets Manager es la lección 04-03.
  6. Configuración de la instancia: db.t3.small (2 vCPU, 2 GiB). Para producción real de MercadoFresco, db.m6g.large.
  7. Almacenamiento: gp3, 20 GiB, con escalado automático activado y máximo 100 GiB.
  8. Disponibilidad: Instancia en espera Multi-AZ. Es la casilla que resuelve la mitad del problema 1 para la base de datos.
  9. Conectividad: VPC por defecto, acceso público = No, grupo de seguridad sg-mercadofresco-basedatos. La red se ve en el módulo 3 (03-01 VPC, 03-02 grupos de seguridad).
  10. Autenticación: contraseña; opcionalmente autenticación IAM, que elimina las contraseñas.
  11. Configuración adicional:
    • Nombre de base de datos inicial: pedidos
    • Copias automáticas: 7 días de retención
    • Ventana de copias: 02:00-03:00 UTC (madrugada en España, tráfico mínimo)
    • Ventana de mantenimiento: domingo 04:00-05:00 UTC
    • Cifrado activado
    • Protección contra eliminación: activada
    • Performance Insights: activado (7 días son gratuitos)
  12. Revisa la estimación de coste mensual que muestra la consola y pulsa Crear base de datos.

La creación tarda entre 5 y 15 minutos (más con Multi-AZ, porque construye dos instancias).

Aviso de coste. Una db.t3.micro de una sola AZ entra en la capa gratuita durante 12 meses (750 h/mes, 20 GB de almacenamiento y 20 GB de copias). Multi-AZ duplica el coste de cómputo y no está en la capa gratuita. Si estás practicando, crea la instancia sin Multi-AZ y activa la casilla solo un rato para ver cómo funciona. El apartado final explica cómo pararla y borrarla.

Crear la instancia RDS por CLI

# 1. Grupo de subredes: indica a RDS en qué subredes puede colocar la instancia.
#    Deben ser al menos DOS, en DOS AZ distintas: es el requisito de Multi-AZ.
aws rds create-db-subnet-group \
  --db-subnet-group-name sng-mercadofresco \
  --db-subnet-group-description "Subredes privadas de MercadoFresco en dos AZ" \
  --subnet-ids subnet-aaa11111 subnet-bbb22222 \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=pedidos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=operaciones \
  --profile mercadofresco-dev --region eu-west-1

# 2. La instancia
aws rds create-db-instance \
  --db-instance-identifier mercadofresco-pedidos \
  --db-instance-class db.t3.small \
  --engine postgres \
  --engine-version 16.3 \
  --master-username mfadmin \
  --manage-master-user-password \
  --allocated-storage 20 \
  --max-allocated-storage 100 \
  --storage-type gp3 \
  --storage-encrypted \
  --db-subnet-group-name sng-mercadofresco \
  --vpc-security-group-ids sg-0abc123def456 \
  --no-publicly-accessible \
  --multi-az \
  --backup-retention-period 7 \
  --preferred-backup-window "02:00-03:00" \
  --preferred-maintenance-window "sun:04:00-sun:05:00" \
  --enable-performance-insights \
  --performance-insights-retention-period 7 \
  --deletion-protection \
  --db-name pedidos \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=pedidos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=operaciones \
  --profile mercadofresco-dev --region eu-west-1

# 3. Esperar a que esté disponible (bloquea hasta que termine)
aws rds wait db-instance-available \
  --db-instance-identifier mercadofresco-pedidos \
  --profile mercadofresco-dev --region eu-west-1

# 4. Obtener el punto de enlace (endpoint) al que se conectará la aplicación
aws rds describe-db-instances \
  --db-instance-identifier mercadofresco-pedidos \
  --query 'DBInstances[0].{Endpoint:Endpoint.Address,Puerto:Endpoint.Port,
           Estado:DBInstanceStatus,MultiAZ:MultiAZ,AZ:AvailabilityZone}' \
  --output table \
  --profile mercadofresco-dev --region eu-west-1

Los parámetros que más importan:

  • --manage-master-user-password: AWS genera la contraseña, la guarda en Secrets Manager y la rota automáticamente. Nunca la escribes ni la ves. Es la mejor práctica actual y evita el clásico --master-user-password "Password123" en el historial del shell.
  • --no-publicly-accessible: la base de datos no tiene IP pública. Solo se accede desde dentro de la VPC. Innegociable para datos de clientes.
  • --max-allocated-storage 100: activa el escalado automático de almacenamiento. Si el disco llega al 90 % de ocupación, RDS lo amplía solo hasta ese techo.
  • --deletion-protection: impide borrar la instancia por error. Para borrarla de verdad hay que desactivarlo primero, en un paso consciente y separado.

El punto de enlace tendrá esta forma:

mercadofresco-pedidos.abc123xyz.eu-west-1.rds.amazonaws.com:5432

Es un nombre DNS, no una IP, y eso es fundamental. Cuando ocurra una conmutación por error, AWS cambiará a qué apunta ese nombre y la aplicación se reconectará al servidor nuevo sin cambiar ni una línea de configuración. Si en algún sitio del código de MercadoFresco hay una IP de base de datos escrita a mano, hay que quitarla ahora.

Multi-AZ: qué es realmente una réplica síncrona en espera

Aquí hay un malentendido muy extendido que conviene deshacer con claridad: la instancia en espera de Multi-AZ no sirve tráfico. No es un segundo servidor que reparte carga. No puedes conectarte a ella. Es una copia sincronizada esperando a que la principal falle.

flowchart TB
    APP["Aplicacion de MercadoFresco<br/>instancias EC2 de la tienda"]
    EP["Punto de enlace DNS<br/>mercadofresco-pedidos...rds.amazonaws.com"]
    APP --> EP
    EP --> P
    subgraph AZ1["eu-west-1a"]
        P["PRINCIPAL<br/>lecturas y escrituras"]
    end
    subgraph AZ2["eu-west-1b"]
        S["EN ESPERA<br/>sin trafico<br/>no accesible"]
    end
    P -->|"replicacion SINCRONA<br/>cada commit se confirma<br/>en ambas antes de responder"| S
    P -.->|"copias automaticas<br/>se toman de la instancia en espera:<br/>sin impacto en produccion"| B["Copias en S3"]

Cómo funciona la replicación síncrona: cuando la aplicación confirma una transacción, PostgreSQL escribe en la principal y espera a que la escritura se haya confirmado también en la instancia en espera antes de responder "hecho" al cliente. Consecuencia doble:

  • Ventaja: cero pérdida de datos (RPO = 0). Lo confirmado está en las dos AZ.
  • Coste: cada escritura tarda un poco más, porque incluye un viaje de red entre AZ (típicamente 1-2 ms). En cargas con escrituras muy intensas se nota.

Qué pasa exactamente en una conmutación por error

  1. AWS detecta el fallo (de la instancia, del almacenamiento o de la AZ entera).
  2. Cambia el registro DNS del punto de enlace para que apunte a la instancia en espera.
  3. La antigua instancia en espera pasa a ser la principal.
  4. AWS crea una nueva instancia en espera en la otra AZ, en segundo plano.

Duración típica: 60-120 segundos. Durante ese rato la aplicación recibe errores de conexión. Multi-AZ no es magia invisible: es recuperación rápida, no ausencia de interrupción. La aplicación debe estar preparada:

  • Reintentos con espera exponencial en las operaciones de base de datos.
  • TTL de DNS bajo en el resolutor del cliente. Un pool de conexiones que cachea la IP para siempre seguirá intentando conectar al servidor caído.
  • Pool de conexiones que sepa descartar conexiones muertas (pool_pre_ping en SQLAlchemy).

Se puede provocar una conmutación a propósito para comprobar que la aplicación aguanta. Hazlo en un entorno de pruebas, y hazlo antes de que ocurra sin avisar:

aws rds reboot-db-instance \
  --db-instance-identifier mercadofresco-pedidos-pruebas \
  --force-failover \
  --profile mercadofresco-dev --region eu-west-1

Multi-AZ frente a réplica de lectura

Son cosas distintas y se confunden constantemente:

Multi-AZ (instancia en espera) Réplica de lectura
Para qué sirve Alta disponibilidad Escalar las lecturas
Replicación Síncrona Asíncrona (hay retardo)
¿Se puede leer de ella? No
¿Se puede escribir? No No (salvo que la promuevas)
Conmutación automática No (promoción manual)
Ubicación Otra AZ de la misma región Misma AZ, otra AZ u otra región
Número 1 (o 2 con el modo clúster Multi-AZ) Hasta 15 en PostgreSQL
Impacto en la latencia de escritura Sí, la aumenta No
Coste Duplica el cómputo Una instancia adicional por réplica

MercadoFresco necesita las dos, y por razones diferentes: Multi-AZ porque el negocio no puede perder pedidos, y réplicas de lectura porque los informes de Sara están matando la base de datos.

Réplicas de lectura: los informes de Sara sin castigar producción

El problema real, medido: Sara ejecuta cada lunes por la mañana una consulta que agrega los pedidos del mes por barrio y por franja horaria. Tarda 4 minutos y durante ese rato la tienda va lenta, porque el mismo servidor está atendiendo a los clientes.

Una réplica de lectura es una copia asíncrona de la base de datos que acepta consultas de solo lectura. Las escrituras siguen yendo a la principal; las lecturas pesadas se dirigen a la réplica.

aws rds create-db-instance-read-replica \
  --db-instance-identifier mercadofresco-pedidos-lectura \
  --source-db-instance-identifier mercadofresco-pedidos \
  --db-instance-class db.t3.small \
  --availability-zone eu-west-1b \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=analitica Key=Propietario,Value=sara \
         Key=CentroCoste,Value=marketing \
  --profile mercadofresco-dev --region eu-west-1

Fíjate en el etiquetado: Componente=analitica, Propietario=sara, CentroCoste=marketing. La réplica existe para Sara, así que su coste se imputa a marketing y no a operaciones. Esto es exactamente lo que hará posible el informe de asignación de costes de la lección 11-02.

Puntos que hay que tener claros:

  • La replicación es asíncrona: la réplica va unos segundos por detrás. Vigila la métrica ReplicaLag. Para informes agregados de ventas es perfectamente aceptable; para "consultar el estado del pedido que acabo de hacer", no.
  • La aplicación debe dirigir el tráfico. AWS te da un punto de enlace distinto para la réplica; el código decide cuál usa. RDS no reparte solo.
  • Una réplica se puede promover a instancia independiente, lo que rompe la replicación de forma irreversible. Es una estrategia válida de migración entre regiones.
  • Se pueden crear en otra región, lo que además da recuperación ante desastres geográfica.

En el código de MercadoFresco, la separación queda así:

import os
import psycopg

# Dos cadenas de conexión distintas, dos usos distintos.
DSN_ESCRITURA = os.environ["MF_DB_ESCRITURA"]   # apunta a mercadofresco-pedidos
DSN_LECTURA   = os.environ["MF_DB_LECTURA"]     # apunta a mercadofresco-pedidos-lectura


def crear_pedido(cliente_id: int, lineas: list) -> int:
    """Escritura: SIEMPRE contra la instancia principal."""
    with psycopg.connect(DSN_ESCRITURA) as con, con.cursor() as cur:
        cur.execute(
            "INSERT INTO pedidos (cliente_id, creado_en) VALUES (%s, now()) RETURNING id",
            (cliente_id,),
        )
        pedido_id = cur.fetchone()[0]
        cur.executemany(
            "INSERT INTO lineas_pedido (pedido_id, producto_id, cantidad) VALUES (%s,%s,%s)",
            [(pedido_id, l["producto_id"], l["cantidad"]) for l in lineas],
        )
        con.commit()
        return pedido_id


def informe_ventas_por_barrio(mes: str) -> list:
    """Lectura analitica pesada: contra la REPLICA, para no afectar a la tienda."""
    with psycopg.connect(DSN_LECTURA) as con, con.cursor() as cur:
        cur.execute(
            """
            SELECT c.barrio,
                   date_trunc('hour', p.creado_en) AS franja,
                   count(*)          AS num_pedidos,
                   sum(lp.cantidad)  AS unidades
            FROM pedidos p
            JOIN clientes c      ON c.id = p.cliente_id
            JOIN lineas_pedido lp ON lp.pedido_id = p.id
            WHERE to_char(p.creado_en, 'YYYY-MM') = %s
            GROUP BY c.barrio, franja
            ORDER BY num_pedidos DESC
            """,
            (mes,),
        )
        return cur.fetchall()

La consulta en SQL puro, para verla completa y entender por qué es cara: recorre todos los pedidos del mes, los une con clientes y líneas de pedido, y agrega por dos dimensiones. Con cientos de miles de filas eso es un recorrido secuencial largo que satura la CPU y expulsa datos calientes de la caché de PostgreSQL. Justo lo que no quieres que pase mientras 900 clientes están comprando.

Copias automáticas, retención y restauración a un punto en el tiempo

Aquí se cierra el problema 2 de MercadoFresco.

RDS realiza dos cosas de forma continua y automática:

  1. Una copia completa diaria durante la ventana que hayas fijado (02:00-03:00 UTC en nuestro caso). Con Multi-AZ, se toma de la instancia en espera, así que producción no lo nota.
  2. Copia continua de los registros de transacciones (WAL) a S3, cada 5 minutos.

La combinación de ambas es lo que permite la restauración a un punto en el tiempo (Point-In-Time Recovery, PITR): puedes restaurar la base de datos a cualquier segundo dentro del periodo de retención, no solo a la hora de la copia diaria.

El escenario que justifica todo esto: el martes a las 11:47, un despliegue de Luis ejecuta por error un UPDATE sin WHERE que pone a cero el precio de todos los productos. A las 11:52 alguien se da cuenta.

# Ver hasta qué momento se puede restaurar
aws rds describe-db-instances \
  --db-instance-identifier mercadofresco-pedidos \
  --query 'DBInstances[0].LatestRestorableTime' \
  --profile mercadofresco-dev --region eu-west-1

# Restaurar a las 11:46, un minuto antes del desastre.
# IMPORTANTE: crea una instancia NUEVA. La original no se toca.
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier mercadofresco-pedidos \
  --target-db-instance-identifier mercadofresco-pedidos-recuperada \
  --restore-time 2026-08-04T11:46:00Z \
  --db-subnet-group-name sng-mercadofresco \
  --vpc-security-group-ids sg-0abc123def456 \
  --no-publicly-accessible \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=pruebas \
         Key=Componente,Value=pedidos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=operaciones \
  --profile mercadofresco-dev --region eu-west-1

Que la restauración cree una instancia nueva es deliberado y muy útil: puedes verificar los datos antes de tocar producción, o incluso extraer solo la tabla afectada y copiarla, en lugar de volver atrás toda la base de datos y perder los pedidos legítimos de esos cinco minutos.

Sobre la retención:

Retención Efecto
0 días Desactiva las copias automáticas y el PITR. Nunca en producción
1-7 días Valor por defecto (7). Suficiente para errores que se detectan rápido
8-35 días Máximo. Para requisitos de cumplimiento o errores de detección lenta

Las copias automáticas se almacenan gratis hasta el tamaño de la base de datos; más allá se cobran a precio de snapshot. Y hay un detalle crítico: al eliminar la instancia, las copias automáticas se borran con ella. Solo los snapshots manuales sobreviven.

Snapshots manuales y el cierre del problema 2

Un snapshot manual es una copia que tú creas y que vive hasta que tú la borres, incluso si eliminas la instancia. Es la copia para hitos: antes de una migración de esquema, antes de una actualización de versión mayor, al cierre del ejercicio fiscal.

# Antes de aplicar la migración de esquema de la campaña de Navidad
aws rds create-db-snapshot \
  --db-instance-identifier mercadofresco-pedidos \
  --db-snapshot-identifier mf-pedidos-antes-migracion-navidad-2026 \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=pedidos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=operaciones \
  --profile mercadofresco-dev --region eu-west-1

# Copiar el snapshot a otra región para recuperación ante desastres
aws rds copy-db-snapshot \
  --source-db-snapshot-identifier \
    arn:aws:rds:eu-west-1:111122223333:snapshot:mf-pedidos-antes-migracion-navidad-2026 \
  --target-db-snapshot-identifier mf-pedidos-dr-navidad-2026 \
  --kms-key-id alias/aws/rds \
  --profile mercadofresco-dev --region eu-central-1

Comparación de las tres formas de recuperación en RDS:

Copias automáticas (PITR) Snapshot manual AWS Backup
Quién las crea RDS, a diario Plan centralizado
Granularidad Cualquier segundo El instante de la creación Según el plan
Retención 0-35 días Indefinida La que definas
Sobreviven al borrado de la instancia No
Copia entre regiones No directamente
Uso típico Errores humanos recientes Hitos, retención larga Gobierno unificado de todos los servicios

Y así queda cerrado el problema 2 de MercadoFresco, sumando lo de 02-02 y lo de esta lección:

Antes (servidor de la oficina) Ahora (AWS)
Copia de las fotos Disco externo manual Snapshots EBS automáticos con DLM + S3 con versionado
Copia de la base de datos pg_dump cuando alguien se acordaba Automática diaria + WAL cada 5 min
Restaurar a un momento concreto Imposible PITR a cualquier segundo de los últimos 7 días
Copia fuera del edificio No Snapshots replicados a eu-central-1
Tiempo de restauración Horas, si el volcado servía 10-20 minutos
Prueba de restauración Nunca se hizo Trimestral, en instancia aparte, sin tocar producción

Con una advertencia que se repite y no sobra: la prueba de restauración trimestral es parte del plan, no un extra. Marta la tiene en el calendario.

Grupos de parámetros y ventanas de mantenimiento

Como no hay acceso al sistema operativo, no puedes editar postgresql.conf. En su lugar, RDS usa grupos de parámetros: un conjunto con nombre de ajustes que se asocia a una o varias instancias.

El grupo por defecto no se puede modificar, así que lo primero es crear uno propio:

aws rds create-db-parameter-group \
  --db-parameter-group-name pg16-mercadofresco \
  --db-parameter-group-family postgres16 \
  --description "Parametros de PostgreSQL 16 para MercadoFresco" \
  --profile mercadofresco-dev --region eu-west-1

aws rds modify-db-parameter-group \
  --db-parameter-group-name pg16-mercadofresco \
  --parameters \
    "ParameterName=log_min_duration_statement,ParameterValue=1000,ApplyMethod=immediate" \
    "ParameterName=shared_preload_libraries,ParameterValue=pg_stat_statements,ApplyMethod=pending-reboot" \
    "ParameterName=log_connections,ParameterValue=1,ApplyMethod=immediate" \
  --profile mercadofresco-dev --region eu-west-1

aws rds modify-db-instance \
  --db-instance-identifier mercadofresco-pedidos \
  --db-parameter-group-name pg16-mercadofresco \
  --apply-immediately \
  --profile mercadofresco-dev --region eu-west-1

Qué hemos configurado y por qué:

  • log_min_duration_statement = 1000: registra toda consulta que tarde más de 1 segundo. Es la forma más directa de encontrar las consultas que hunden la tienda los viernes.
  • shared_preload_libraries = pg_stat_statements: activa la extensión que acumula estadísticas por consulta. Base de cualquier trabajo serio de optimización.
  • ApplyMethod: immediate se aplica al momento; pending-reboot requiere reiniciar la instancia. Un parámetro dinámico marcado como pendiente no está haciendo nada todavía, y esto despista mucho.

Sobre la ventana de mantenimiento: es el intervalo semanal en el que AWS aplica parches del sistema operativo y del motor. Elígela en el valle de tráfico (domingo de madrugada para MercadoFresco, nunca un viernes por la tarde). Con Multi-AZ, el mantenimiento se aplica primero a la instancia en espera, luego se conmuta y después se parchea la otra: la interrupción se reduce al tiempo de la conmutación.

Las actualizaciones de versión menor pueden ser automáticas (--auto-minor-version-upgrade); las de versión mayor (de PostgreSQL 16 a 17) nunca lo son: requieren tu acción explícita y hay que probarlas antes en una instancia restaurada desde un snapshot.

Monitorización: métricas básicas y Performance Insights

RDS publica métricas en CloudWatch sin configurar nada. Las que hay que mirar:

Métrica Qué indica Umbral de alarma sugerido
CPUUtilization Uso de CPU > 80 % durante 10 min
DatabaseConnections Conexiones abiertas > 80 % de max_connections
FreeableMemory Memoria disponible < 10 % de la RAM total
FreeStorageSpace Espacio libre en disco < 10 % (o < 5 GB)
ReadLatency / WriteLatency Latencia de disco > 20 ms sostenido
ReplicaLag Retardo de la réplica de lectura > 30 s
DiskQueueDepth Operaciones de disco en cola > 5 sostenido

Performance Insights va un paso más allá: es un panel que muestra la carga de la base de datos descompuesta por consulta, por usuario, por host y por tipo de espera. Esa última dimensión es la que resuelve incidencias: no te dice solo "la CPU está al 90 %", te dice "el 70 % de la carga está esperando bloqueos de la tabla pedidos, y la consulta responsable es esta".

Los 7 días de retención son gratuitos y merece la pena activarlo siempre. La monitorización a fondo, con alarmas, paneles y agregación de registros, es la lección 05-01.

# Alarma sobre el espacio libre: el fallo más previsible y más evitable
aws cloudwatch put-metric-alarm \
  --alarm-name mercadofresco-pedidos-espacio-bajo \
  --alarm-description "Menos de 5 GB libres en la base de datos de pedidos" \
  --namespace AWS/RDS --metric-name FreeStorageSpace \
  --dimensions Name=DBInstanceIdentifier,Value=mercadofresco-pedidos \
  --statistic Average --period 300 --evaluation-periods 2 \
  --threshold 5000000000 --comparison-operator LessThanThreshold \
  --alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
  --profile mercadofresco-dev --region eu-west-1

Conectarse desde la aplicación sin credenciales en el código

Primero, la comprobación manual desde una instancia EC2 de la tienda (recuerda: la base de datos no es pública, así que hay que conectarse desde dentro de la VPC):

# Recuperar la contraseña generada por AWS desde Secrets Manager.
# Nunca se copia a un fichero ni se escribe en el historial.
export PGPASSWORD=$(aws secretsmanager get-secret-value \
  --secret-id "rds!db-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  --query 'SecretString' --output text \
  --profile mercadofresco-dev --region eu-west-1 | python3 -c "import sys,json; print(json.load(sys.stdin)['password'])")

psql -h mercadofresco-pedidos.abc123xyz.eu-west-1.rds.amazonaws.com \
     -U mfadmin -d pedidos -p 5432

unset PGPASSWORD

Una vez dentro, comprobaciones útiles:

-- Versión del motor
SELECT version();

-- Tamaño de la base de datos
SELECT pg_size_pretty(pg_database_size('pedidos')) AS tamano;

-- Las 5 consultas más lentas (requiere pg_stat_statements activado)
SELECT substring(query, 1, 80) AS consulta,
       calls,
       round(mean_exec_time::numeric, 2) AS ms_medios,
       round(total_exec_time::numeric, 2) AS ms_totales
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;

-- Conexiones activas por estado
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;

Y así se conecta la aplicación, leyendo el secreto en el arranque y sin que ninguna credencial toque el código ni el repositorio:

"""
Conexión de la tienda de MercadoFresco a RDS PostgreSQL.
Las credenciales se leen de Secrets Manager usando el rol IAM de la instancia EC2:
no hay contraseñas en el código, ni en variables de entorno, ni en el repositorio.
"""
import json
import os
import boto3
import psycopg
from psycopg_pool import ConnectionPool

REGION = "eu-west-1"
ID_SECRETO = os.environ["MF_SECRETO_BD"]          # solo el identificador, no la clave
HOST_ESCRITURA = os.environ["MF_BD_HOST_ESCRITURA"]
HOST_LECTURA = os.environ["MF_BD_HOST_LECTURA"]


def _credenciales() -> dict:
    """Lee usuario y contraseña de Secrets Manager.

    El rol IAM de la instancia EC2 (o de la Lambda) autoriza esta llamada:
    no se necesita ninguna clave de acceso. Detalle en las lecciones 04-01 y 04-03.
    """
    cliente = boto3.client("secretsmanager", region_name=REGION)
    respuesta = cliente.get_secret_value(SecretId=ID_SECRETO)
    return json.loads(respuesta["SecretString"])


def _dsn(host: str) -> str:
    c = _credenciales()
    # sslmode=require obliga a cifrar la conexión en tránsito.
    return (
        f"host={host} port=5432 dbname=pedidos "
        f"user={c['username']} password={c['password']} "
        f"sslmode=require connect_timeout=5"
    )


# Pools separados. min_size mantiene conexiones abiertas para evitar el coste
# de establecerlas en cada petición durante el pico de los viernes.
pool_escritura = ConnectionPool(_dsn(HOST_ESCRITURA), min_size=2, max_size=10)
pool_lectura = ConnectionPool(_dsn(HOST_LECTURA), min_size=1, max_size=5)


def estado_pedido(pedido_id: int) -> dict | None:
    """Consulta de solo lectura que SÍ debe ir a la principal.

    El cliente acaba de hacer el pedido: el retardo de la réplica podría
    hacer que no lo encontrase. Regla: lo que el usuario acaba de escribir,
    se lee de la principal.
    """
    with pool_escritura.connection() as con, con.cursor() as cur:
        cur.execute(
            "SELECT id, estado, creado_en, entrega_estimada FROM pedidos WHERE id = %s",
            (pedido_id,),
        )
        fila = cur.fetchone()
        if fila is None:
            return None
        return {
            "id": fila[0],
            "estado": fila[1],
            "creado_en": fila[2].isoformat(),
            "entrega_estimada": fila[3].isoformat() if fila[3] else None,
        }

Las tres reglas que resume este código:

  1. Ninguna credencial en el código ni en el repositorio. Solo el identificador del secreto.
  2. Autorización por rol IAM, no por claves de acceso. La instancia EC2 tiene un rol que le permite leer ese secreto concreto y nada más.
  3. sslmode=require: la conexión va cifrada. RDS proporciona el certificado.

Escalado vertical y almacenamiento autoescalable

Escalado vertical (cambiar la clase de instancia). Es una operación con interrupción, salvo con Multi-AZ, donde AWS modifica primero la instancia en espera, conmuta y luego la otra: la parada se reduce a los 60-120 segundos de la conmutación.

aws rds modify-db-instance \
  --db-instance-identifier mercadofresco-pedidos \
  --db-instance-class db.m6g.large \
  --apply-immediately \
  --profile mercadofresco-dev --region eu-west-1

--apply-immediately aplica el cambio ya, con la interrupción correspondiente. Sin ese parámetro, el cambio espera a la ventana de mantenimiento, que es lo que quieres en producción salvo urgencia.

Escalado del almacenamiento. Se puede ampliar en cualquier momento y sin parar, pero no reducir. Con --max-allocated-storage, RDS lo hace solo:

aws rds modify-db-instance \
  --db-instance-identifier mercadofresco-pedidos \
  --allocated-storage 50 --max-allocated-storage 200 \
  --apply-immediately \
  --profile mercadofresco-dev --region eu-west-1

Cuándo escalar cada cosa, en el caso de MercadoFresco:

Síntoma Causa probable Acción
CPU al 90 % con consultas lentas Falta CPU o faltan índices Primero mira los índices; luego sube de clase
FreeableMemory bajo, mucha lectura de disco El conjunto de trabajo no cabe en RAM Clase con más memoria (familia R)
Muchas lecturas analíticas Informes de Sara Réplica de lectura, no una instancia mayor
FreeStorageSpace bajando Crecimiento normal Escalado automático de almacenamiento
Latencia de escritura alta IOPS insuficientes Subir IOPS de gp3 o pasar a io2

El orden importa: subir de tamaño es la última opción, no la primera. Un índice que falta cuesta cero euros al mes; una clase de instancia mayor cuesta todos los meses, para siempre.

Migrar desde el servidor de la oficina

Con la base de datos de MercadoFresco (unos pocos GB y una ventana de mantenimiento nocturna aceptable), la migración clásica con volcado y restauración es suficiente.

# ---------- En el servidor de la oficina ----------

# 1. Volcado en formato "custom": comprimido y restaurable en paralelo.
pg_dump -h localhost -U postgres -d pedidos \
  --format=custom --compress=9 --verbose \
  --file=pedidos-2026-08-04.dump

# 2. Comprobar el volcado antes de confiar en él
pg_restore --list pedidos-2026-08-04.dump | head -20
ls -lh pedidos-2026-08-04.dump

# ---------- Desde una EC2 dentro de la VPC ----------

# 3. Subir el volcado a S3 (bucket privado, cifrado con KMS)
aws s3 cp pedidos-2026-08-04.dump \
  s3://mercadofresco-copias-basedatos/copias/base-datos/ \
  --profile mercadofresco-dev

# 4. Descargarlo en la EC2 y restaurar contra RDS.
#    -j 4 restaura con 4 procesos en paralelo: mucho más rápido.
pg_restore -h mercadofresco-pedidos.abc123xyz.eu-west-1.rds.amazonaws.com \
  -U mfadmin -d pedidos \
  --no-owner --no-privileges --verbose -j 4 \
  pedidos-2026-08-04.dump

Por qué --no-owner --no-privileges: los roles y permisos del servidor de origen no existen en RDS, y sin esas opciones la restauración se llena de errores por intentar asignar objetos a usuarios inexistentes.

Verificación obligatoria antes de conmutar el tráfico:

-- Comparar el número de filas de las tablas principales con el origen
SELECT 'pedidos' AS tabla, count(*) FROM pedidos
UNION ALL SELECT 'clientes', count(*) FROM clientes
UNION ALL SELECT 'productos', count(*) FROM productos
UNION ALL SELECT 'lineas_pedido', count(*) FROM lineas_pedido;

-- Comprobar que los índices y las restricciones han llegado
SELECT tablename, indexname FROM pg_indexes
WHERE schemaname = 'public' ORDER BY tablename;

-- Actualizar las estadísticas del planificador: sin esto, las consultas
-- pueden ir absurdamente lentas justo después de restaurar.
ANALYZE VERBOSE;

Plan de conmutación de MercadoFresco, un domingo de madrugada:

  1. 02:00 — Poner la tienda en modo mantenimiento (solo lectura).
  2. 02:05 — pg_dump final del servidor de la oficina.
  3. 02:20 — pg_restore en RDS y ANALYZE.
  4. 02:40 — Verificar recuentos y ejecutar la batería de pruebas de humo.
  5. 02:50 — Cambiar las variables de entorno de la aplicación al nuevo punto de enlace.
  6. 03:00 — Quitar el modo mantenimiento y vigilar métricas durante una hora.
  7. El servidor de la oficina se deja encendido y sincronizado una semana, por si hay que volver atrás.

Cuando no se puede parar la tienda, la herramienta es AWS DMS (Database Migration Service): copia el estado inicial y después replica los cambios en continuo (change data capture), de modo que la ventana de corte se reduce a segundos. Además puede migrar entre motores distintos (de Oracle a PostgreSQL, por ejemplo). Para MercadoFresco es exagerado; para una migración sin parada, es el camino.

Costes y cómo parar una instancia de pruebas

Componentes de la factura de RDS:

Componente Precio orientativo (eu-west-1)
Horas de instancia db.t3.micro ≈ 0,018 USD/h · db.t3.small ≈ 0,036 · db.m6g.large ≈ 0,171
Multi-AZ ×2 sobre las horas de instancia
Almacenamiento gp3 ≈ 0,127 USD/GB-mes
Copias Gratis hasta el tamaño de la base de datos; después ≈ 0,105 USD/GB-mes
Réplica de lectura Como una instancia más
Transferencia entre AZ Gratis dentro de Multi-AZ; de pago hacia internet
Performance Insights 7 días gratis; más retención, de pago

Coste mensual de la configuración de producción de MercadoFresco:

Instancia db.t3.small Multi-AZ: 0,036 × 2 × 730 =  52,56 USD
Almacenamiento 50 GB × 0,127                    =   6,35 USD
Copias (50 GB, dentro de lo gratuito)           =   0,00 USD
Réplica de lectura db.t3.small                  =  26,28 USD
--------------------------------------------------------------
Total                                           ≈  85,19 USD/mes

Unos 78 € al mes por una base de datos con alta disponibilidad, copias automáticas, PITR de 7 días y una réplica para analítica. Comparado con los 6.000 € del servidor original —que no tenía nada de eso— y con el tiempo que Marta dedicaba a mantenerlo, la conversación económica se vuelve fácil.

Parar una instancia de pruebas:

# Se detiene el cómputo (deja de facturarse); el almacenamiento sigue costando.
aws rds stop-db-instance \
  --db-instance-identifier mercadofresco-pedidos-pruebas \
  --profile mercadofresco-dev --region eu-west-1

aws rds start-db-instance \
  --db-instance-identifier mercadofresco-pedidos-pruebas \
  --profile mercadofresco-dev --region eu-west-1

Dos limitaciones importantes: RDS solo permite tener una instancia parada 7 días; al octavo la arranca sola. Y una instancia Multi-AZ no se puede parar. Para entornos de desarrollo que solo se usan en horario laboral, lo práctico es programar el arranque y la parada, o simplemente borrar y recrear desde un snapshot.

Eliminar por completo cuando termines la práctica:

# 1. Quitar la protección contra eliminación (paso consciente y separado)
aws rds modify-db-instance \
  --db-instance-identifier mercadofresco-pedidos-pruebas \
  --no-deletion-protection --apply-immediately \
  --profile mercadofresco-dev --region eu-west-1

# 2. Eliminar las réplicas primero
aws rds delete-db-instance \
  --db-instance-identifier mercadofresco-pedidos-lectura \
  --skip-final-snapshot \
  --profile mercadofresco-dev --region eu-west-1

# 3. Eliminar la principal, guardando un snapshot final por si acaso
aws rds delete-db-instance \
  --db-instance-identifier mercadofresco-pedidos-pruebas \
  --final-db-snapshot-identifier mf-pedidos-final-$(date +%Y%m%d) \
  --profile mercadofresco-dev --region eu-west-1

# 4. Los snapshots manuales siguen costando. Revísalos y bórralos si no los necesitas.
aws rds describe-db-snapshots --snapshot-type manual \
  --query 'DBSnapshots[].{ID:DBSnapshotIdentifier,GB:AllocatedStorage,Fecha:SnapshotCreateTime}' \
  --output table \
  --profile mercadofresco-dev --region eu-west-1

Errores Comunes y Consejos

  • Creer que la instancia en espera de Multi-AZ sirve lecturas. No. Para eso están las réplicas de lectura. Es el malentendido número uno de RDS.
  • Poner --publicly-accessible para "poder conectarme desde casa". Expone la base de datos a internet. La forma correcta es un bastión, Session Manager o una VPN.
  • Escribir la contraseña en el comando o en el código. Usa --manage-master-user-password y Secrets Manager (04-03).
  • Retención de copias a 0 días. Desactiva el PITR. Es la configuración que convierte un error humano recuperable en una pérdida definitiva.
  • Olvidar que las copias automáticas se borran con la instancia. Antes de eliminar, saca un snapshot final.
  • Cambiar un parámetro pending-reboot y esperar que actúe. No hace nada hasta reiniciar.
  • Restaurar y no ejecutar ANALYZE. Sin estadísticas, el planificador toma decisiones pésimas y parece que RDS "va lento".
  • Poner la ventana de mantenimiento en hora punta. Domingo de madrugada, nunca viernes por la tarde.
  • Subir de clase de instancia antes de mirar las consultas. Un índice que falta cuesta 0 USD al mes; una instancia mayor, todos los meses.
  • Leer de la réplica lo que el usuario acaba de escribir. El retardo asíncrono hará que a veces no aparezca. Lecturas críticas, siempre a la principal.
  • No probar nunca una conmutación por error. Provócala en un entorno de pruebas y comprueba que la aplicación se reconecta. Descubrirlo en producción sale caro.
  • Consejo: activa Performance Insights desde el primer día. Es gratis 7 días y el día que haya una incidencia querrás tener el histórico.

Ejercicios

Ejercicio 1: diseñar la topología de bases de datos

MercadoFresco abre en tres ciudades más (problema 3). La previsión es:

  • El pico de los viernes pasa a 2.100 pedidos/hora (escrituras intensas).
  • Sara pasa de un informe semanal a un panel que se refresca cada 15 minutos.
  • Aparece un requisito nuevo: si se pierde la región eu-west-1 entera, el negocio debe poder operar en menos de 4 horas.
  • Se mantiene el requisito de no perder ningún pedido confirmado.

Diseña la topología de RDS: qué instancias, de qué tipo, en qué AZ y regiones, y qué mecanismo cubre cada requisito. Indica qué requisito no puede cubrir RDS por sí solo.

Ejercicio 2: recuperar de un desastre con PITR

El jueves a las 16:12, un script de sincronización de catálogo ejecuta por error:

DELETE FROM lineas_pedido WHERE producto_id IN (SELECT id FROM productos WHERE activo = false);

Ha borrado 47.000 líneas de pedidos históricos que sí eran válidas. Nadie se da cuenta hasta el viernes a las 10:30, cuando Sara ve que las cifras de julio no cuadran. En ese tiempo se han registrado unos 1.400 pedidos nuevos que no se pueden perder.

Describe el procedimiento completo de recuperación, con los comandos, y explica por qué no se puede simplemente restaurar la base de datos al jueves a las 16:11.

Ejercicio 3: decidir dónde va cada consulta

Para cada consulta de MercadoFresco, indica si debe ir a la instancia principal o a la réplica de lectura, y justifica en una frase:

  • A) INSERT de un pedido nuevo desde la cesta.
  • B) "Mis pedidos" de un cliente que acaba de comprar hace 3 segundos.
  • C) Informe mensual de ventas agregadas por barrio.
  • D) Listado del catálogo de productos en la página principal.
  • E) Comprobación de stock justo antes de confirmar un pedido.
  • F) Exportación nocturna de todos los pedidos a S3 para analítica.

Soluciones

Solución 1.

Requisito Solución Detalle
2.100 pedidos/h de escritura Escalado vertical de la principal a db.m6g.xlarge (o migración a Aurora, lección 06-03) Las escrituras no se pueden repartir entre réplicas: solo hay un escritor. Es el límite del modelo relacional clásico
No perder ningún pedido confirmado Multi-AZ en eu-west-1a / eu-west-1b Replicación síncrona: RPO = 0
Panel de Sara cada 15 min Réplica de lectura db.r6g.large en eu-west-1b Familia R por ser carga analítica intensiva en memoria; un retardo de segundos es irrelevante para un panel de 15 minutos
Sobrevivir a la pérdida de la región Réplica de lectura en eu-central-1 + snapshots automáticos copiados a esa región Ante desastre, se promueve la réplica a instancia independiente: minutos, muy por debajo de las 4 horas exigidas

Topología resultante:

eu-west-1a: mercadofresco-pedidos (principal, db.m6g.xlarge)
eu-west-1b: instancia en espera Multi-AZ (automática, no accesible)
eu-west-1b: mercadofresco-pedidos-lectura (réplica, db.r6g.large) → Sara
eu-central-1: mercadofresco-pedidos-dr (réplica entre regiones) → recuperación ante desastres

Lo que RDS no cubre por sí solo: el pico de escrituras de los viernes sigue dependiendo de una sola instancia escritora. Escalar verticalmente tiene un techo. Las soluciones reales están fuera de esta lección: encolar los pedidos con SQS para absorber el pico y escribirlos a ritmo sostenido (módulo 7), cachear las lecturas calientes con ElastiCache (06-05), o repartir la escritura con un modelo distinto de datos (DynamoDB, 06-02). Reconocer ese techo es parte de saber usar RDS.

Solución 2.

Por qué no se puede restaurar sin más al jueves a las 16:11: eso devolvería la base de datos al estado de ese instante, y con ello desaparecerían los 1.400 pedidos registrados entre el jueves por la tarde y el viernes por la mañana. Se arreglaría un problema creando otro peor. La restauración completa solo vale cuando el daño se detecta en minutos.

El procedimiento correcto es restaurar en paralelo y recuperar solo lo borrado:

# 1. Restaurar a una instancia NUEVA en el instante anterior al borrado.
#    Producción sigue funcionando sin enterarse.
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier mercadofresco-pedidos \
  --target-db-instance-identifier mercadofresco-pedidos-rescate \
  --restore-time 2026-08-06T16:11:00Z \
  --db-subnet-group-name sng-mercadofresco \
  --vpc-security-group-ids sg-0abc123def456 \
  --no-publicly-accessible \
  --db-instance-class db.t3.small \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=pruebas \
         Key=Componente,Value=pedidos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=operaciones \
  --profile mercadofresco-dev --region eu-west-1

aws rds wait db-instance-available \
  --db-instance-identifier mercadofresco-pedidos-rescate \
  --profile mercadofresco-dev --region eu-west-1
# 2. Extraer SOLO las filas borradas de la instancia de rescate.
pg_dump -h mercadofresco-pedidos-rescate.abc123.eu-west-1.rds.amazonaws.com \
  -U mfadmin -d pedidos \
  --table=lineas_pedido --data-only --format=custom \
  --file=lineas-rescate.dump
-- 3. En PRODUCCIÓN: cargar el volcado en una tabla temporal y reinsertar
--    únicamente lo que falta. ON CONFLICT protege de duplicar nada.
CREATE TABLE lineas_pedido_rescate (LIKE lineas_pedido INCLUDING ALL);
-- (aquí se restaura el volcado sobre lineas_pedido_rescate)

BEGIN;

INSERT INTO lineas_pedido
SELECT r.*
FROM lineas_pedido_rescate r
LEFT JOIN lineas_pedido a ON a.id = r.id
WHERE a.id IS NULL
ON CONFLICT (id) DO NOTHING;

-- Verificar ANTES de confirmar
SELECT count(*) FROM lineas_pedido;   -- debe haber subido ~47.000

COMMIT;

DROP TABLE lineas_pedido_rescate;
# 4. Eliminar la instancia de rescate para dejar de pagarla
aws rds delete-db-instance \
  --db-instance-identifier mercadofresco-pedidos-rescate \
  --skip-final-snapshot \
  --profile mercadofresco-dev --region eu-west-1

Lecciones que deja el incidente: el DELETE debía haberse ejecutado dentro de una transacción con verificación previa del recuento; los scripts de mantenimiento no deben usar el usuario maestro sino un rol con permisos acotados (04-01); y una alarma sobre variaciones bruscas del número de filas habría avisado el jueves y no el viernes.

Solución 3.

Consulta Destino Justificación
A) INSERT de pedido Principal Es una escritura. Las réplicas son de solo lectura, no hay alternativa
B) "Mis pedidos" tras comprar Principal El retardo asíncrono podría hacer que el pedido recién creado no apareciera. Regla: lo que el usuario acaba de escribir, se lee de la principal
C) Informe mensual por barrio Réplica Consulta pesada sobre datos históricos; unos segundos de retardo son irrelevantes y así no castiga a la tienda
D) Catálogo de la página principal Réplica (y además caché) Datos que cambian poco y se leen muchísimo. El caso de manual para una réplica; con ElastiCache delante sería aún mejor (06-05)
E) Stock antes de confirmar Principal Dato crítico y volátil: leer un stock desactualizado provoca vender producto que no existe. Además suele necesitar bloqueo (SELECT ... FOR UPDATE), que solo funciona en la principal
F) Exportación nocturna a S3 Réplica Lectura masiva en horario valle; se hace en la réplica precisamente para no tocar la principal ni siquiera de noche, cuando corren las copias

Patrón general: escrituras y lecturas críticas o inmediatas → principal; lecturas analíticas, históricas o tolerantes al retardo → réplica.

Conclusión

La base de datos de pedidos de MercadoFresco ya está en AWS, y con ella el corazón del negocio. Entiendes qué significa un servicio gestionado: AWS asume el hardware, el sistema operativo, los parches del motor, las copias y la conmutación por error; tú sigues siendo responsable del esquema, los índices, las consultas y la seguridad de la aplicación. Sabes también qué renuncias a cambio —acceso al sistema operativo, superusuario real, extensiones fuera de lista— y en qué casos concretos eso obliga a elegir PostgreSQL sobre EC2 en lugar de RDS.

Has creado la instancia mercadofresco-pedidos por consola y por CLI, con las decisiones que importan tomadas conscientemente: contraseña gestionada en Secrets Manager para no escribirla nunca, sin acceso público, cifrado activado, protección contra eliminación, escalado automático de almacenamiento, retención de copias de 7 días y ventanas de copia y mantenimiento colocadas en el valle de tráfico y nunca un viernes por la tarde.

Has deshecho el malentendido más común de RDS: la instancia en espera de Multi-AZ no sirve tráfico. Es una réplica síncrona que garantiza cero pérdida de datos y conmuta en 60-120 segundos cambiando el DNS del punto de enlace, con la contrapartida de sumar latencia a cada escritura y de exigir que la aplicación reintente. Frente a ella, las réplicas de lectura son asíncronas, sí se pueden consultar y son la herramienta correcta para que el panel de Sara no castigue a producción; has visto en código y en tabla qué consulta va a cada sitio.

Y has cerrado el problema 2 de MercadoFresco. Entre los snapshots de EBS automatizados con DLM de la lección 02-02, el versionado de S3 de la 02-03 y ahora las copias automáticas de RDS con registros de transacciones cada cinco minutos, la empresa ha pasado de "un pg_dump a un disco externo cuando alguien se acuerda" a poder restaurar a cualquier segundo de los últimos siete días. Has practicado la recuperación real de un borrado masivo restaurando en paralelo y reinsertando solo lo perdido, sin sacrificar los pedidos posteriores. Sumas a eso los snapshots manuales que sobreviven al borrado de la instancia, los grupos de parámetros con log_min_duration_statement y pg_stat_statements para cazar consultas lentas, Performance Insights para saber en qué espera realmente la base de datos, la conexión desde la aplicación sin una sola credencial en el código, el escalado vertical y de almacenamiento con su regla de oro —mira los índices antes de subir de clase— y el plan completo de migración con pg_dump/pg_restore y su alternativa sin parada, AWS DMS.

Queda una última pieza del módulo. Tenemos servidores que hay que dimensionar, arrancar y pagar aunque estén ociosos. Pero hay tareas de MercadoFresco que no necesitan un servidor esperando: generar la miniatura de una foto cuando se sube, responder a una consulta puntual del estado de un pedido, procesar un fichero. En la lección 02-05, «AWS Lambda», escribiremos código que se ejecuta solo cuando ocurre algo y solo se paga mientras corre, conectaremos por fin el evento de S3 que dejamos configurado en 02-03 para generar las miniaturas del catálogo, y recapitularemos qué piezas de MercadoFresco están ya en la nube y qué falta por construir.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados