Sara lleva dieciocho meses ejecutando el mismo informe. Abre su cliente SQL a media mañana, lanza la consulta del ticket medio por ciudad y se va a por un café, porque sabe que tarda entre cuatro y seis minutos. Lo que no sabía hasta el módulo 5 —hasta que el panel mercadofresco-produccion lo enseñó en un gráfico— es que durante esos minutos la latencia de la tienda sube, y que el viernes en que lo lanzó a las 18:30 no fue casualidad que atención al cliente recibiera quejas.

La migración a Aurora ha mejorado las cosas: un punto de enlace personalizado aísla las consultas de Sara en aurora-mf-lector-lotes y la tienda ya no las nota. Pero los informes siguen tardando lo mismo, porque el problema nunca fue de aislamiento: es que un motor orientado a filas lee 48 millones de filas completas de 40 columnas para usar 4, sin comprimir y sin paralelizar. Amazon Redshift es el almacén de datos de AWS: un motor columnar, comprimido y masivamente paralelo, diseñado exactamente para la pregunta que Sara hace todos los días. Aquí los informes salen definitivamente de la base de datos transaccional, se modelan en estrella y pasan de minutos a segundos.

Aviso de coste. Redshift es de los servicios que más rápido generan factura si se descuidan: un clúster provisionado olvidado cuesta cientos de dólares al mes. Al terminar cualquier prueba, elimina el espacio de nombres y el grupo de trabajo.

Aviso de cumplimiento. Un almacén analítico concentra en un solo sitio el historial completo de compra de todos los clientes: es, en términos de RGPD, el activo más sensible de MercadoFresco. La anonimización, la base legal del tratamiento analítico y la política de retención deben estar documentadas y revisadas por el responsable de protección de datos antes de la primera carga.

Contenido

  1. OLAP frente a OLTP: por qué un informe mata a una base transaccional
  2. Almacenamiento columnar, compresión y ejecución masivamente paralela
  3. Arquitectura: nodo líder, nodos de cómputo y cortes
  4. RA3, DC2 y Redshift Serverless
  5. El modelo en estrella de MercadoFresco
  6. Claves de distribución y de ordenación
  7. Carga de datos con COPY y la canalización nocturna
  8. Integración sin ETL desde Aurora
  9. Redshift Spectrum, Athena y AWS Glue
  10. Las consultas de negocio de Sara
  11. Vistas materializadas
  12. Concurrencia, colas WLM y escalado de concurrencia
  13. Seguridad, permisos y anonimización
  14. Costes, pausa y reanudación
  15. Errores comunes y consejos
  16. Ejercicios
  17. Conclusión

OLAP frente a OLTP: por qué un informe mata a una base transaccional

En 06-01 vimos la tabla que separa ambos mundos. Ahora toca ver el mecanismo concreto, con la consulta real de Sara:

-- El informe del ticket medio por ciudad, tal como está hoy en Aurora.
SELECT c.ciudad,
       COUNT(*)          AS pedidos,
       AVG(p.importe)    AS ticket_medio,
       SUM(p.importe)    AS facturacion
FROM   pedidos p
JOIN   clientes c ON c.id_cliente = p.id_cliente
WHERE  p.fecha_pedido >= CURRENT_DATE - INTERVAL '18 months'
GROUP  BY c.ciudad
ORDER  BY facturacion DESC;

La tabla pedidos tiene 40 columnas y 48 millones de filas, con un tamaño medio de fila de 380 bytes. La consulta necesita tres: id_cliente, importe y fecha_pedido, unos 28 bytes.

En un motor orientado a filas, los datos se guardan fila completa tras fila completa. Para leer 28 bytes hay que leer los 380, porque el disco se lee por bloques y en cada bloque hay filas enteras:

Aurora (filas) Redshift (columnas)
Datos leídos 48 M × 380 B = 18,2 GB 48 M × 28 B = 1,34 GB
Compresión típica Ninguna sobre los datos 3-4× → ≈380 MB
Paralelismo 1 proceso Todos los cortes a la vez
Duración medida 4-6 min 2-6 s

La reducción no es un truco: es leer 48 veces menos datos y repartirlos entre decenas de procesos.

Almacenamiento columnar, compresión y ejecución masivamente paralela

graph TB
    subgraph FILAS["Orientado a filas · Aurora"]
        F1["Bloque 1: (84213, 4471, 2026-08-02, 34.20, Valencia, ...37 columnas más)"]
        F2["Bloque 2: (84214, 8802, 2026-08-02, 51.90, Bilbao, ...37 columnas más)"]
    end
    subgraph COLS["Orientado a columnas · Redshift"]
        C1["Bloque A · id_pedido: 84213, 84214, 84215, 84216, ..."]
        C2["Bloque B · importe: 34.20, 51.90, 12.75, 88.40, ..."]
        C3["Bloque C · fecha: 2026-08-02, 2026-08-02, 2026-08-02, ..."]
    end
    Q["SELECT AVG(importe) ..."] -->|lee bloques enteros<br/>y descarta 37 columnas| FILAS
    Q -->|lee solo el bloque B| COLS

Guardar por columnas tiene una segunda ventaja que multiplica la primera: todos los valores de un bloque son del mismo tipo y muy parecidos entre sí, así que comprimen extraordinariamente bien.

Codificación Cómo funciona Buena para Ejemplo en MercadoFresco
AZ64 Compresión propia para tipos numéricos y de fecha Números, fechas importe, fecha_pedido
ZSTD Compresión general de alta relación Texto variable nombre_producto
BYTEDICT Diccionario de hasta 256 valores Baja cardinalidad estado, franja_reparto
RUNLENGTH Guarda valor y repeticiones Valores repetidos y ordenados id_ciudad si es clave de ordenación
RAW Sin comprimir Claves de ordenación pequeñas La primera columna de la clave

Redshift elige la codificación sola: con ENCODE AUTO —el comportamiento por defecto— analiza los datos cargados y aplica lo que corresponda. La recomendación práctica es dejarlo en automático salvo que se tenga una razón medida para lo contrario; ANALYZE COMPRESSION muestra qué recomendaría sobre datos ya cargados.

La tercera pieza es MPP (massively parallel processing): la tabla no vive en un sitio, está repartida entre todos los nodos y cada uno procesa su parte simultáneamente. Con 4 nodos de 4 cortes, la consulta de Sara se divide en 16 trabajos que leen y agregan 3 millones de filas cada uno a la vez, y los resultados parciales se combinan al final. De ahí la diferencia entre minutos y segundos.

Esto también explica por qué Redshift es malo en lo contrario: recuperar un pedido concreto por su identificador exige coordinar todos los nodos para devolver una fila. Aurora lo hace en 2 ms con un índice; Redshift tarda cientos de milisegundos. Redshift no sustituye a Aurora: la complementa.

Arquitectura: nodo líder, nodos de cómputo y cortes

graph TD
    CLI["Cliente SQL de Sara"] --> L["Nodo líder<br/>analiza, planifica, distribuye y combina"]
    L --> N1["Nodo de cómputo 1"]
    L --> N2["Nodo de cómputo 2"]
    N1 --> S1["Corte 1"]
    N1 --> S2["Corte 2"]
    N2 --> S3["Corte 3"]
    N2 --> S4["Corte 4"]
    S1 --> RMS["Almacenamiento gestionado en S3 · RA3"]
    S2 --> RMS
    S3 --> RMS
    S4 --> RMS

El nodo líder recibe la consulta, la analiza, genera el plan, compila el código y lo reparte; no guarda datos de usuario y es el único punto de conexión. Los nodos de cómputo ejecutan su porción y devuelven resultados parciales. Los cortes (slices) son las divisiones de cada nodo, una por vCPU: la unidad real de paralelismo y el motivo de que la clave de distribución importe tanto, porque si los datos no se reparten bien entre cortes, la mitad del hardware está parado.

RA3 frente a DC2, y Redshift Serverless

DC2 RA3 Serverless
Almacenamiento Local en el nodo (SSD) Gestionado en S3, con caché local Gestionado
Escalar cómputo y datos Acoplados Independientes Automático
Unidad de facturación Nodo-hora Nodo-hora RPU-hora
Cuándo está encendido Siempre Siempre (o pausado) Solo cuando hay consultas
Mínimo práctico 2 nodos 2 nodos 8 RPU
Administración Alta Media Ninguna

DC2 es la generación antigua: rápida pero con almacenamiento local, de modo que crecer en datos obliga a añadir nodos que no se necesitan por cómputo. RA3 separa ambas cosas con almacenamiento gestionado sobre S3 y caché local automática. Redshift Serverless elimina el concepto de nodo: se define un grupo de trabajo con capacidad base en RPU (Redshift Processing Units, mínimo 8), se paga por RPU-segundo mientras hay consultas ejecutándose, y cuando no hay actividad no se paga cómputo, solo el almacenamiento.

Por qué Serverless es la opción para MercadoFresco

El perfil de uso analítico de MercadoFresco, medido:

Dato Valor
Informes al día 3-8, con picos a fin de mes
Duración objetivo por informe 5-30 s
Tiempo total de cómputo al día ≈4 minutos
Usuarios analíticos 2 (Sara y una becaria)
Volumen del almacén 340 GB hoy, +12 GB/mes

Cuatro minutos de cómputo al día sobre 1.440 posibles: un clúster provisionado estaría encendido el 99,7 % del tiempo sin hacer nada.

RA3 provisionado (2 × ra3.xlplus) Serverless (8 RPU base)
Cómputo 730 h × 2 × 1,086 = 1.586 USD/mes ~2 h/mes × 8 RPU × 0,36 = ≈6 USD
Almacenamiento Incluido hasta 32 TB por nodo 340 GB × 0,024 = 8 USD
Administración Dimensionar, pausar, vigilar Ninguna
Total ≈1.586 USD ≈14 USD

Dos órdenes de magnitud, sin discusión. Un clúster provisionado se justifica cuando hay consultas prácticamente todo el día, decenas de analistas y carga predecible: será el caso de MercadoFresco dentro de unos años, no hoy.

# Serverless se compone de un espacio de nombres (datos, cifrado, permisos)
# y un grupo de trabajo (capacidad, red). Se crean por separado.
aws redshift-serverless create-namespace \
  --namespace-name mercadofresco-analitica \
  --admin-username analitica_admin --manage-admin-password \
  --kms-key-id alias/mercadofresco-datos \
  --default-iam-role-arn arn:aws:iam::111122223333:role/rol-redshift-mercadofresco \
  --iam-roles arn:aws:iam::111122223333:role/rol-redshift-mercadofresco \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=analitica Key=Propietario,Value=sara \
         Key=CentroCoste,Value=negocio \
  --region eu-west-1 --profile mercadofresco-dev

aws redshift-serverless create-workgroup \
  --workgroup-name wg-mercadofresco-analitica \
  --namespace-name mercadofresco-analitica \
  --base-capacity 8 --max-capacity 64 \
  --subnet-ids snet-mercadofresco-datos-a snet-mercadofresco-datos-b \
  --security-group-ids sg-mercadofresco-basedatos \
  --no-publicly-accessible \
  --region eu-west-1 --profile mercadofresco-dev

--max-capacity 64 es el límite de gasto: sin él, una consulta mal escrita escala y lo cobra. Y --no-publicly-accessible con subredes privadas es innegociable: el almacén contiene el historial de compra de todos los clientes.

El modelo en estrella de MercadoFresco

En OLTP se normaliza para evitar duplicación; en OLAP se desnormaliza en estrella: una tabla de hechos grande con las medidas numéricas, rodeada de dimensiones pequeñas con los atributos descriptivos. Menos uniones, y las que quedan son contra tablas pequeñas.

graph TD
    DP["dim_producto<br/>sk_producto · sku · nombre<br/>categoria · proveedor · alergenos"] --> H
    DC["dim_cliente<br/>sk_cliente · segmento<br/>antiguedad · franja_preferida"] --> H
    DT["dim_tiempo<br/>sk_tiempo · fecha · dia_semana<br/>es_festivo · semana · mes"] --> H
    DU["dim_ciudad<br/>sk_ciudad · ciudad · provincia<br/>comunidad · almacen_asignado"] --> H
    H["hechos_pedidos<br/>sk_tiempo · sk_producto · sk_cliente · sk_ciudad<br/>unidades · importe · descuento · coste_reparto"]
-- Tabla de hechos: una fila por línea de pedido. Es la tabla grande.
CREATE TABLE hechos_pedidos (
    sk_tiempo        INTEGER   NOT NULL,
    sk_producto      INTEGER   NOT NULL,
    sk_cliente       INTEGER   NOT NULL,
    sk_ciudad        SMALLINT  NOT NULL,
    id_pedido        BIGINT    NOT NULL,   -- referencia al sistema origen
    unidades         SMALLINT  NOT NULL,
    importe          DECIMAL(10,2) NOT NULL,
    descuento        DECIMAL(10,2) NOT NULL DEFAULT 0,
    coste_reparto    DECIMAL(10,2) NOT NULL DEFAULT 0
)
DISTSTYLE KEY
DISTKEY (sk_cliente)          -- se explica en la sección siguiente
SORTKEY (sk_tiempo, sk_ciudad);

-- Dimensión pequeña: se replica entera en cada nodo con DISTSTYLE ALL.
CREATE TABLE dim_ciudad (
    sk_ciudad        SMALLINT NOT NULL,
    ciudad           VARCHAR(80)  NOT NULL,
    provincia        VARCHAR(80)  NOT NULL,
    comunidad        VARCHAR(80)  NOT NULL,
    almacen_asignado VARCHAR(40)  NOT NULL
)
DISTSTYLE ALL
SORTKEY (sk_ciudad);

Dos detalles que sorprenden viniendo de PostgreSQL. Redshift no impone claves primarias ni foráneas: se pueden declarar, y conviene hacerlo porque el planificador las usa para optimizar, pero no las verifica —la integridad la garantiza el proceso de carga—; y no hay índices, porque la clave de ordenación cumple ese papel. Además, las dimensiones deben tener claves sustitutas (sk_*, enteros pequeños) y no los identificadores del origen: ocupan menos, comprimen mejor y permiten guardar historia cuando un atributo cambia —si un cliente se muda de ciudad, los pedidos antiguos deben seguir contando en la ciudad en la que se entregaron—.

Claves de distribución

La clave de distribución decide en qué corte cae cada fila, y es la decisión que más afecta al rendimiento.

Estilo Cómo reparte Cuándo usarlo Riesgo
KEY Por hash de una columna Tabla grande que se une siempre por esa columna Sesgo si la columna está mal repartida
ALL Copia completa en cada nodo Dimensiones pequeñas (< 2-3 M filas) Multiplica el almacenamiento y las escrituras
EVEN Por turnos, sin criterio Tablas que no se unen o sin columna clara Redistribución en cada unión
AUTO Redshift decide y cambia Punto de partida por defecto Menos control

Lo que ocurre cuando se elige mal es concreto y medible. Si hechos_pedidos se distribuye por sk_producto y la consulta une por sk_cliente, Redshift tiene que redistribuir la tabla de hechos por la red en cada consulta: es el paso DS_DIST_BOTH del plan de ejecución, y puede multiplicar por diez la duración. Peor todavía es el sesgo: si se distribuye por sk_ciudad y el 45 % de los pedidos son de Madrid, ese 45 % cae en un solo corte y un proceso trabaja mientras quince esperan. Es la clave caliente de 06-02, con otro nombre.

En MercadoFresco se elige DISTKEY (sk_cliente) porque hay decenas de miles de clientes con reparto razonablemente uniforme y porque las consultas de cohortes y de ticket medio se agrupan por cliente. Y las cuatro dimensiones van con DISTSTYLE ALL porque son diminutas: replicarlas cuesta poco y elimina toda redistribución en las uniones. Para diagnosticar sesgo se compara el número de filas por corte en las vistas del sistema: si el máximo y el mínimo difieren mucho, la clave está mal elegida.

Claves de ordenación

La clave de ordenación determina el orden físico de las filas en disco. Redshift guarda el valor mínimo y máximo de cada bloque de un megabyte, así que si la consulta filtra por la clave de ordenación, el motor descarta bloques enteros sin leerlos. Es el equivalente conceptual a un índice agrupado.

SORTKEY (sk_tiempo, sk_ciudad) en hechos_pedidos responde a que todas las consultas de Sara filtran por rango de fechas: con 18 meses de historia y una consulta del último trimestre, Redshift descarta el 83 % de los bloques antes de leer nada. El orden importa: la primera columna debe ser la que más se usa para filtrar por rango, y poner sk_ciudad delante desperdiciaría casi todo el beneficio porque el filtro por ciudad es de igualdad y aparece con menos frecuencia.

Tres avisos. Las cargas nuevas llegan sin ordenar y quedan en una región no ordenada que VACUUM SORT ONLY reorganiza; con clave de fecha y carga incremental por fecha apenas hace falta, porque los datos llegan ya casi en orden. ANALYZE actualiza las estadísticas del planificador, sin las cuales el plan puede ser malísimo —Serverless ejecuta ambos solo, en segundo plano—. Y las claves intercaladas (INTERLEAVED) dan peso igual a varias columnas, pero solo compensan en casos concretos y cuestan mucho mantenimiento: la recomendación por defecto es la compuesta.

Carga de datos con COPY

COPY es la única forma sensata de cargar volumen en Redshift: lee de S3 en paralelo desde todos los cortes a la vez. Un INSERT fila a fila es entre cien y mil veces más lento.

COPY hechos_pedidos
FROM 's3://mercadofresco-informes-analitica/hechos/pedidos/2026/08/02/'
IAM_ROLE 'arn:aws:iam::111122223333:role/rol-redshift-mercadofresco'
FORMAT AS PARQUET;

Cuatro decisiones detrás de esas cuatro líneas. Parquet, no CSV: es columnar y comprimido en origen, así que la carga es más rápida, no hay que declarar el esquema y los tipos vienen definidos —no hay ambigüedad entre 12,40 y 12.40—. Un prefijo, no un fichero: COPY carga todos los objetos del prefijo en paralelo, y la regla práctica es generar un múltiplo del número de cortes en ficheros de 1 a 128 MB comprimidos, porque un solo fichero gigante deja parados a todos los cortes menos uno. IAM_ROLE, nunca claves de acceso en el SQL: el rol lo asume el clúster y lo audita trail-mercadofresco. Y manifiesto cuando hace falta exactitud —un JSON que lista explícitamente los objetos con "mandatory": true—, que es lo que garantiza que la carga nocturna procesa exactamente los ficheros que la exportación generó, ni uno más ni uno menos.

{"entries": [
  {"url": "s3://mercadofresco-informes-analitica/hechos/pedidos/2026/08/02/part-000.parquet",
   "mandatory": true},
  {"url": "s3://mercadofresco-informes-analitica/hechos/pedidos/2026/08/02/part-001.parquet",
   "mandatory": true}
]}

Tras cada carga conviene revisar STL_LOAD_ERRORS, que explica fila a fila qué falló. Es la primera tabla que hay que mirar cuando un COPY se queja.

La canalización nocturna

graph LR
    A["Aurora · aurora-mf-lector-lotes<br/>02:00"] --> B["Exportación incremental<br/>pedidos del día"]
    B --> C["S3 · mercadofresco-informes-analitica<br/>Parquet particionado por fecha"]
    C --> D["COPY a tablas de preparación"]
    D --> E["Transformar y cargar<br/>dimensiones y hechos"]
    E --> F["Refrescar vistas materializadas"]
    F --> G["Métrica de éxito a CloudWatch<br/>y aviso a alertas-mercadofresco si falla"]

Cuatro reglas la hacen fiable. Incremental, no completa: se exportan solo los pedidos con fecha_modificacion posterior a la última marca de agua, no los 48 millones cada noche. Idempotente: si el proceso se reintenta, el resultado debe ser el mismo, y eso se consigue borrando la partición del día antes de cargarla dentro de una transacción. Desde la réplica, nunca desde el escritor: por eso existe aurora-mf-lector-lotes. Y con alarma, porque un fallo silencioso significa que Sara mira datos viejos sin saberlo, que es peor que no tener informe.

La orquestación de estos pasos —con reintentos, dependencias y manejo de fallos— es trabajo de Step Functions y EventBridge, que se ven en 07-03 y 07-04. Aquí basta con saber que la canalización existe y qué garantías debe cumplir.

Integración sin ETL desde Aurora

La integración sin ETL (zero-ETL) replica de forma continua las tablas de Aurora PostgreSQL a Redshift, con un retardo de segundos y sin escribir ni una línea de canalización. Se configura una integración entre el clúster origen y el espacio de nombres destino, y AWS mantiene la copia.

Canalización propia Integración sin ETL
Retardo Horas (nocturna) Segundos
Transformaciones Cualquiera Ninguna: llegan las tablas tal cual
Modelo resultante En estrella, optimizado Réplica del esquema OLTP
Mantenimiento El equipo AWS
Coste Cómputo del proceso E/S de la replicación

No son excluyentes, y la combinación es lo que MercadoFresco acaba usando: la integración sin ETL trae las tablas normalizadas en tiempo casi real y un proceso dentro de Redshift las transforma en el modelo en estrella. Se gana frescura y se elimina la parte más frágil de la canalización conservando el modelo optimizado.

Redshift Spectrum

Spectrum permite consultar datos que están en S3 sin cargarlos, mediante tablas externas definidas en el catálogo de datos de AWS Glue.

CREATE EXTERNAL SCHEMA historico_s3
FROM DATA CATALOG DATABASE 'mercadofresco_analitica'
IAM_ROLE 'arn:aws:iam::111122223333:role/rol-redshift-mercadofresco';

-- Une datos calientes (en Redshift) con datos fríos (en S3) en una sola consulta.
SELECT h.sk_ciudad, SUM(h.importe) AS actual, SUM(a.importe) AS historico
FROM   hechos_pedidos h
LEFT   JOIN historico_s3.pedidos_archivo a ON a.sk_ciudad = h.sk_ciudad
GROUP  BY h.sk_ciudad;

El caso de MercadoFresco: mantener en Redshift los últimos 24 meses y dejar en S3, en clases de acceso poco frecuente (02-03), todo lo anterior más los carritos abandonados que exportamos en 06-02. Se factura por TB escaneados, así que particionar por fecha y usar Parquet es lo que separa una consulta de céntimos de una de decenas de dólares.

Redshift frente a Athena, y AWS Glue como catálogo

Athena (05-03, donde consultamos trail-mercadofresco) también ejecuta SQL sobre S3. La pregunta honesta es cuándo basta.

Amazon Athena Amazon Redshift
Modelo de coste 5 USD por TB escaneado RPU-hora o nodo-hora
Infraestructura Ninguna Espacio de nombres o clúster
Latencia típica 5-60 s 1-10 s (datos cargados)
Optimización Solo formato y particiones Distribución, ordenación, vistas materializadas
Concurrencia Buena, con cuotas Muy buena, con WLM
Uniones complejas Aceptables Muy buenas
Vistas materializadas No
Cuándo Consultas esporádicas sobre datos en S3 Consultas repetidas, cuadros de mando, modelo dimensional

Athena basta para exploración ocasional, análisis de registros y cualquier caso de pocas consultas al mes. Redshift hace falta cuando las mismas consultas se repiten a diario, cuando hay cuadros de mando que refrescan solos, cuando hay uniones de varias tablas grandes o cuando el escaneo repetido en Athena empieza a costar más que el almacén. Para MercadoFresco, con 3-8 informes diarios sobre las mismas tablas y un modelo dimensional que mantener, Redshift Serverless es la elección; Athena sigue siendo la herramienta para los registros de CloudTrail y para exploraciones puntuales sobre S3.

AWS Glue aporta el catálogo de datos: el registro central de qué tablas existen, dónde están y qué esquema tienen. Athena, Spectrum y los trabajos de Glue lo comparten, así que definir una tabla una vez la hace visible desde los tres, y los rastreadores (crawlers) pueden deducir el esquema recorriendo un prefijo de S3.

Las consultas de negocio de Sara

-- 1. Ticket medio y facturación por ciudad, últimos 18 meses.
-- El filtro por sk_tiempo aprovecha la clave de ordenación y descarta bloques.
SELECT ci.ciudad,
       COUNT(DISTINCT h.id_pedido)             AS pedidos,
       ROUND(SUM(h.importe) / COUNT(DISTINCT h.id_pedido), 2) AS ticket_medio,
       ROUND(SUM(h.importe), 2)                AS facturacion
FROM   hechos_pedidos h
JOIN   dim_ciudad ci ON ci.sk_ciudad = h.sk_ciudad
JOIN   dim_tiempo t  ON t.sk_tiempo  = h.sk_tiempo
WHERE  t.fecha >= DATEADD(month, -18, CURRENT_DATE)
GROUP  BY ci.ciudad
ORDER  BY facturacion DESC;
-- 2. Productos que más se agotan los viernes en la franja de pico.
-- Compara las unidades vendidas los viernes 17-21h con la media del resto de días.
WITH viernes AS (
    SELECT h.sk_producto, SUM(h.unidades) AS uds_viernes
    FROM   hechos_pedidos h
    JOIN   dim_tiempo t ON t.sk_tiempo = h.sk_tiempo
    WHERE  t.dia_semana = 5
      AND  t.fecha >= DATEADD(month, -6, CURRENT_DATE)
    GROUP  BY h.sk_producto
),
resto AS (
    SELECT h.sk_producto, SUM(h.unidades) / 6.0 AS uds_media_dia
    FROM   hechos_pedidos h
    JOIN   dim_tiempo t ON t.sk_tiempo = h.sk_tiempo
    WHERE  t.dia_semana <> 5
      AND  t.fecha >= DATEADD(month, -6, CURRENT_DATE)
    GROUP  BY h.sk_producto
)
SELECT p.nombre, p.categoria,
       v.uds_viernes,
       ROUND(v.uds_viernes / NULLIF(r.uds_media_dia, 0), 2) AS factor_viernes
FROM   viernes v
JOIN   resto r      ON r.sk_producto = v.sk_producto
JOIN   dim_producto p ON p.sk_producto = v.sk_producto
WHERE  v.uds_viernes > 200
ORDER  BY factor_viernes DESC
LIMIT  25;

Es la consulta que permite a Marta decidir cuánto producto fresco encargar a la lonja el jueves por la noche: no el más vendido en absoluto, sino el que se dispara específicamente el viernes.

-- 3. Cohortes: qué porcentaje de clientes sigue comprando N meses después de su
--    primer pedido, agrupados por el mes en que se dieron de alta.
WITH primer_pedido AS (
    SELECT h.sk_cliente, DATE_TRUNC('month', MIN(t.fecha)) AS mes_alta
    FROM   hechos_pedidos h JOIN dim_tiempo t ON t.sk_tiempo = h.sk_tiempo
    GROUP  BY h.sk_cliente
),
actividad AS (
    SELECT p.mes_alta,
           DATEDIFF(month, p.mes_alta, DATE_TRUNC('month', t.fecha)) AS mes_relativo,
           COUNT(DISTINCT h.sk_cliente) AS clientes_activos
    FROM   hechos_pedidos h
    JOIN   dim_tiempo t    ON t.sk_tiempo  = h.sk_tiempo
    JOIN   primer_pedido p ON p.sk_cliente = h.sk_cliente
    GROUP  BY 1, 2
)
SELECT mes_alta, mes_relativo, clientes_activos,
       ROUND(100.0 * clientes_activos /
             FIRST_VALUE(clientes_activos)
               OVER (PARTITION BY mes_alta ORDER BY mes_relativo), 1) AS retencion_pct
FROM   actividad
WHERE  mes_relativo BETWEEN 0 AND 12
ORDER  BY mes_alta, mes_relativo;

Las tres tardaban minutos en Aurora y tardan segundos en Redshift. Y las tres son imposibles en DynamoDB: este es el criterio de «consultas exploratorias» de 06-01 hecho SQL.

Vistas materializadas

Una vista materializada guarda el resultado precalculado de una consulta. Redshift puede refrescarla de forma incremental, procesando solo los datos nuevos, y reescribe automáticamente las consultas para que usen la vista aunque el usuario no la mencione.

CREATE MATERIALIZED VIEW mv_ventas_diarias_ciudad
AUTO REFRESH YES
AS
SELECT t.fecha, h.sk_ciudad,
       COUNT(DISTINCT h.id_pedido) AS pedidos,
       SUM(h.importe)              AS facturacion,
       SUM(h.unidades)             AS unidades
FROM   hechos_pedidos h
JOIN   dim_tiempo t ON t.sk_tiempo = h.sk_tiempo
GROUP  BY t.fecha, h.sk_ciudad;

El cuadro de mando de negocio pasa de agregar 48 millones de filas a leer unos miles. AUTO REFRESH YES deja que Redshift decida cuándo refrescar según la actividad; con canalización nocturna también vale un REFRESH MATERIALIZED VIEW explícito al final de la carga, que da control exacto sobre cuándo cambian los números que ve Sara.

Concurrencia, colas WLM y escalado de concurrencia

La gestión de cargas de trabajo (WLM) organiza las consultas en colas con memoria y prioridad propias, para que un informe pesado no bloquee a los demás. El WLM manual exige configurar colas, memoria y concurrencia a mano y no se adapta; el WLM automático solo pide prioridades y se ajusta solo, y es la opción por defecto salvo casos muy específicos.

Con WLM automático se definen prioridades por grupo de usuarios: los cuadros de mando en HIGHEST porque son rápidos y muchos ojos los miran, las consultas exploratorias de Sara en NORMAL, y la carga nocturna en LOW porque a nadie le importa que tarde diez minutos más de madrugada.

Dos mecanismos complementarios: el escalado de concurrencia, que añade capacidad temporal cuando se acumula cola —en Serverless está integrado en el escalado por RPU—, y las reglas de monitorización de consultas (QMR), que abortan automáticamente lo que se descontrola (por ejemplo, cualquier consulta que supere 15 minutos o escanee más de 500 GB). Es la forma más eficaz de que un SELECT * accidental no se coma el presupuesto.

Seguridad, permisos y anonimización

Red. El grupo de trabajo vive en las subredes privadas snet-mercadofresco-datos-a y -b, con sg-mercadofresco-basedatos y sin acceso público; Sara se conecta por el editor de consultas v2 de la consola, que no requiere abrir nada. Cifrado en reposo con alias/mercadofresco-datos y en tránsito con TLS obligatorio. Y permisos de solo lectura para el grupo mercadofresco-analitica:

CREATE GROUP mercadofresco_analitica;
CREATE USER sara PASSWORD DISABLE IN GROUP mercadofresco_analitica;  -- federada

GRANT USAGE  ON SCHEMA analitica TO GROUP mercadofresco_analitica;
GRANT SELECT ON ALL TABLES IN SCHEMA analitica TO GROUP mercadofresco_analitica;
ALTER DEFAULT PRIVILEGES IN SCHEMA analitica
  GRANT SELECT ON TABLES TO GROUP mercadofresco_analitica;

-- Vista que expone lo necesario sin datos identificativos.
CREATE VIEW analitica.v_clientes_segmento AS
SELECT sk_cliente, segmento, antiguedad_meses, franja_preferida, sk_ciudad
FROM   analitica.dim_cliente;
REVOKE SELECT ON analitica.dim_cliente     FROM GROUP mercadofresco_analitica;
GRANT  SELECT ON analitica.v_clientes_segmento TO GROUP mercadofresco_analitica;

ALTER DEFAULT PRIVILEGES es la línea que la gente olvida: sin ella, las tablas creadas mañana no serán legibles y alguien acabará concediendo permisos de más para salir del paso.

Anonimización y RGPD. El almacén no debe contener nombre, dirección postal, teléfono, correo ni documento de identidad. Ninguna pregunta de negocio los necesita: el ticket medio por ciudad necesita la ciudad, no la calle. La canalización sustituye el identificador de cliente por una clave sustituta sin correspondencia reversible fuera de un almacén controlado, agrega la dirección a nivel de ciudad o código postal, y descarta el resto. Además, el análisis de datos es una finalidad distinta de la gestión del pedido y necesita su propia base legal, su mención en la política de privacidad y una política de retención documentada. Redshift ofrece control de acceso por filas y enmascaramiento dinámico de columnas para casos en que algún dato sensible deba permanecer. Todo este diseño debe revisarlo el responsable de protección de datos antes de la primera carga: es mucho más barato que rediseñar el almacén después.

Costes, pausa y reanudación

Concepto Precio orientativo eu-west-1 MercadoFresco
Serverless, RPU-hora ~0,36 USD ~2 h/mes de actividad = 6 USD
Almacenamiento gestionado 0,024 USD/GB-mes 340 GB = 8 USD
Spectrum 5 USD/TB escaneado Ocasional, < 2 USD
Instantáneas más allá del tamaño Precio de S3 Despreciable
Total ≈16 USD/mes

Con Serverless, la «pausa» es automática: sin consultas no hay RPU y no hay cargo de cómputo. Aun así conviene poner un límite de uso que avise o corte al superar un umbral mensual:

aws redshift-serverless create-usage-limit \
  --resource-arn arn:aws:redshift-serverless:eu-west-1:111122223333:workgroup/wg-mercadofresco-analitica \
  --usage-type serverless-compute --amount 200 --period monthly \
  --breach-action deactivate \
  --region eu-west-1 --profile mercadofresco-dev

En clústeres provisionados, pause-cluster y resume-cluster detienen el cobro de cómputo manteniendo los datos, y se pueden programar: es la palanca que convierte un entorno de desarrollo de 1.500 USD/mes en uno de 300.

Limpieza. Al terminar cualquier prueba: elimina el grupo de trabajo y el espacio de nombres —con instantánea final si los datos importan—, borra las tablas externas de Glue y revisa los objetos que hayas dejado en mercadofresco-informes-analitica. Comprueba con Cost Explorer que la etiqueta Componente=analitica vuelve a cero al mes siguiente.

Errores Comunes y Consejos

Usar Redshift como base de datos transaccional. Es el error conceptual grave. Redshift no tiene índices para búsquedas puntuales, y los INSERT fila a fila son lentísimos. Cargar por lotes con COPY, consultar en agregado; lo transaccional se queda en Aurora.

Cargar con INSERT. Entre cien y mil veces más lento que COPY, y además fragmenta la tabla. Si los datos llegan de uno en uno, se acumulan en S3 y se cargan por lotes.

Elegir la clave de distribución por intuición. Distribuir por una columna con pocos valores distintos produce sesgo y desperdicia el paralelismo. Distribuir por una columna distinta de la que se usa en las uniones produce redistribución en cada consulta. Ante la duda, AUTO, y revisar después con EXPLAIN buscando DS_DIST_BOTH.

Poner en la clave de ordenación una columna por la que nunca se filtra. No aporta nada. La primera columna debe ser aquella por la que se filtra por rango, que casi siempre es la fecha.

Olvidar ANALYZE y VACUUM en clústeres provisionados. Sin estadísticas el planificador toma decisiones malas y sin VACUUM la tabla se degrada. Serverless los ejecuta solo, pero conviene saberlo.

Cargar datos personales sin anonimizar «para no perder detalle». Se acaba con el activo más sensible de la empresa replicado en un sistema al que accede más gente, sin base legal clara. La anonimización se diseña antes de la primera carga.

Un solo fichero enorme en el COPY. Deja parados a todos los cortes menos uno. Divide en un múltiplo del número de cortes, en trozos de 1 a 128 MB comprimidos.

Dejar un clúster provisionado encendido para «probar». Es el descuido más caro de este módulo: 1.500 USD al mes por un clúster que nadie usa. Serverless o pausa programada.

Consejo: mide el antes y el después. Guarda la duración de los tres informes de Sara en Aurora y compárala con Redshift. Es el número que justifica el proyecto ante dirección, y lo tienes gratis en SVL_QUERY_METRICS y en los paneles del módulo 5.

Consejo: empieza pequeño y con Spectrum. Antes de diseñar el modelo en estrella completo, exporta a Parquet y consulta con Spectrum o Athena. Si con eso basta, te has ahorrado un almacén. La complejidad se añade cuando se demuestra que hace falta.

Ejercicios

Ejercicio 1: elegir claves de distribución y ordenación

MercadoFresco añade la tabla de hechos hechos_reparto, con una fila por entrega: 12 millones de filas, columnas sk_tiempo, sk_ciudad, sk_repartidor (40 valores distintos), sk_pedido, minutos_entrega, km_recorridos e incidencia (booleano, verdadero en el 3 % de los casos). Las consultas habituales son: retraso medio por ciudad y mes; incidencias por repartidor en la última semana; y un cruce con hechos_pedidos por sk_pedido para relacionar retraso con importe.

Decide DISTSTYLE, DISTKEY y SORTKEY, justifica cada elección, e indica qué pasaría si se distribuyera por sk_repartidor y qué señal del plan de ejecución lo delataría.

Ejercicio 2: Athena o Redshift

Una empresa hermana de MercadoFresco, dedicada a la venta de material de hostelería, tiene 40 GB de histórico de pedidos en S3 en formato CSV. Su analista lanza entre tres y cinco consultas al mes, siempre exploratorias y distintas entre sí, y no hay cuadros de mando. Está valorando montar Redshift Serverless «porque es lo que hacen los mayores».

Responde: (a) qué recomiendas y por qué, con números; (b) qué dos cambios en los datos harían que su opción fuera mucho más barata y rápida sin cambiar de servicio; (c) qué tres señales concretas indicarían, dentro de un año, que ha llegado el momento de pasar a Redshift.

Ejercicio 3: diseñar la canalización con garantías

Escribe el diseño de la canalización nocturna que lleva los pedidos del día desde aurora-mercadofresco-pedidos hasta hechos_pedidos. Debe cubrir: de qué punto de enlace se lee y por qué; cómo se seleccionan solo los registros nuevos o modificados; formato, particionado y tamaño de los ficheros en S3; cómo se garantiza la idempotencia si el proceso se reintenta; cómo se cargan las dimensiones cuando un atributo cambia (por ejemplo, un cliente que se muda de ciudad); qué se hace con los datos personales; y cómo se detecta y notifica un fallo. Indica también qué parte de este diseño desaparecería si se usara integración sin ETL.

Soluciones

Solución 1

DISTSTYLE KEY con DISTKEY (sk_pedido) y SORTKEY (sk_tiempo, sk_ciudad).

Distribución: la consulta más cara de las tres es el cruce con hechos_pedidos, que es la tabla grande. Si ambas tablas se distribuyen por la misma columna —sk_pedido—, las filas que hay que unir están en el mismo corte y la unión se resuelve localmente, sin mover nada por la red. Es el patrón de coubicación, y es la razón principal para elegir KEY. sk_pedido tiene además cardinalidad alta y reparto uniforme, así que no hay sesgo. Nota: hechos_pedidos está distribuida por sk_cliente, así que para aprovechar plenamente la coubicación habría que valorar unificar el criterio o aceptar la redistribución de la tabla menor, que con 12 millones de filas es asumible; la decisión se toma midiendo con EXPLAIN.

Ordenación: las dos primeras consultas filtran por rango temporal («por mes», «última semana»), así que sk_tiempo debe ir primero para descartar bloques. sk_ciudad en segunda posición ayuda al primer informe.

Si se distribuyera por sk_repartidor: con 40 valores distintos y un clúster de 16 cortes, como mucho 16 cortes recibirían datos y de forma muy desigual —los repartidores de Madrid tienen muchas más entregas—. El resultado es sesgo grave: unos pocos cortes con millones de filas y el resto casi vacíos, con el paralelismo desperdiciado. La señal en el plan de ejecución sería DS_DIST_BOTH o DS_BCAST_INNER en la unión con hechos_pedidos, y en las vistas del sistema se vería una diferencia enorme entre el corte con más filas y el que menos. La regla general: nunca distribuir por una columna de baja cardinalidad.

Solución 2

(a) Athena, sin duda. Con 40 GB y cinco consultas al mes, aunque cada una escaneara el histórico completo serían 0,04 TB × 5 × 5 USD = 1 USD al mes. Redshift Serverless costaría el almacenamiento más el cómputo de cada sesión, más el trabajo de diseñar y mantener un modelo dimensional que nadie va a explotar. Y la característica que decide: las consultas son siempre distintas y exploratorias, así que no hay nada que precalcular ni ninguna vista materializada que amortizar. Todo el valor de Redshift —modelo optimizado, vistas materializadas, concurrencia— se apoya en la repetición, y aquí no la hay.

(b) Dos cambios: convertir a Parquet y particionar por fecha. Parquet con compresión reduce 40 GB a unos 6-8 GB y, al ser columnar, una consulta que use 4 columnas de 30 escanea una fracción de eso. El particionado por año y mes en el prefijo de S3 permite que Athena descarte particiones enteras cuando la consulta filtra por fecha. Combinados, es habitual bajar de decenas de gigabytes escaneados a cientos de megabytes: la factura pasa a céntimos y la consulta, de un minuto a unos segundos. Ambos cambios son un trabajo de Glue de una tarde y no cambian de servicio.

(c) Tres señales para migrar a Redshift. Primera, la repetición: aparecen cuadros de mando o informes recurrentes diarios, y entonces las vistas materializadas y el modelo dimensional empiezan a amortizarse. Segunda, la concurrencia: pasan de un analista a un equipo con consultas simultáneas y empiezan a chocar con las cuotas de Athena. Tercera, el coste cruzado: la factura mensual de Athena se acerca al coste de un grupo de trabajo Serverless —15-20 USD al mes con este perfil—, momento en que Redshift da más por el mismo dinero. Un cuarto indicio cualitativo: cuando las consultas empiezan a unir tres o más tablas grandes, Athena se degrada mucho antes que Redshift.

Solución 3

Origen y punto de enlace. Se lee del punto de enlace personalizado que apunta a aurora-mf-lector-lotes, la réplica aislada de 06-03. Nunca del escritor —la exportación compite con los pedidos— y nunca del punto de enlace de lectura general, porque llenaría la caché de la réplica que sirve el historial a los clientes con páginas de 18 meses de antigüedad.

Selección incremental. Una marca de agua en Parameter Store (/mercadofresco/produccion/analitica/marca-agua) con la última fecha_modificacion procesada. La consulta selecciona WHERE fecha_modificacion > :marca AND fecha_modificacion <= :corte, con :corte fijado al inicio del proceso para que las escrituras ocurridas durante la exportación queden para la noche siguiente y no se pierdan ni se dupliquen. Requiere fecha_modificacion indexada y mantenida por disparador.

Formato en S3. Parquet con compresión Snappy, en s3://mercadofresco-informes-analitica/hechos/pedidos/anio=2026/mes=08/dia=02/, con ficheros de 64-128 MB en número múltiplo del paralelismo. El particionado por fecha en el prefijo es lo que hace baratas las consultas de Spectrum y permite reprocesar un día concreto.

Idempotencia. Dentro de una transacción: DELETE FROM hechos_pedidos WHERE sk_tiempo = :dia; y a continuación el COPY de la partición, con manifiesto que enumera explícitamente los ficheros y "mandatory": true. Si el proceso se reintenta, el resultado es idéntico. El manifiesto además evita cargar ficheros parciales de una ejecución abortada.

Dimensiones que cambian. Se aplica dimensión de variación lenta de tipo 2: cuando un cliente se muda de ciudad, no se actualiza su fila, se cierra la vigente (fecha_fin, es_actual = false) y se inserta una nueva con clave sustituta distinta. Así los pedidos antiguos siguen apuntando a la fila que estaba vigente cuando se entregaron y el histórico por ciudad no se reescribe solo. Si se sobrescribiera (tipo 1), la facturación de Valencia del año pasado cambiaría porque un cliente se mudó a Bilbao, que es exactamente el tipo de error que destruye la confianza en un almacén de datos.

Datos personales. Nombre, dirección, teléfono y correo no salen de Aurora. La exportación proyecta solo las columnas necesarias, sustituye id_cliente por la clave sustituta sk_cliente y agrega la dirección a ciudad y código postal. La correspondencia entre sk_cliente e id_cliente vive en Aurora, con acceso restringido, no en el almacén.

Detección de fallos. Cada ejecución publica una métrica personalizada MercadoFresco/Analitica/CargaCorrecta (1 o 0) y FilasCargadas en el espacio MercadoFresco/Tienda. Dos alarmas hacia alertas-mercadofresco: una si no hay ninguna carga correcta antes de las 06:00 —el caso peligroso es el silencio, no el error— y otra si FilasCargadas se desvía más de un 50 % de la media de los últimos 7 días, que detecta una exportación truncada. Los errores de carga se consultan en STL_LOAD_ERRORS.

Qué desaparece con integración sin ETL. Toda la extracción y el aterrizaje: consulta incremental, marca de agua, Parquet, particionado, manifiesto y COPY; AWS mantiene las tablas replicadas en segundos. Lo que no desaparece es la transformación al modelo en estrella, la lógica de dimensiones de tipo 2, la anonimización —crítico: la replicación trae las tablas tal cual, incluidos los datos personales, así que la proyección y el enmascaramiento hay que hacerlos ya dentro de Redshift con vistas y permisos— y la vigilancia de la frescura. Se simplifica la mitad frágil, no todo.

Conclusión

Los informes de Sara han salido de la base de datos transaccional. Entiendes por qué tenían que salir: una consulta que agrega 18 meses lee 18,2 GB en un motor orientado a filas para usar 1,34 GB de datos útiles, sin comprimir y con un solo proceso, mientras que el almacenamiento columnar lee solo las columnas necesarias, las comprime de 3 a 4 veces porque los valores contiguos se parecen, y las procesa en paralelo en todos los cortes a la vez. De 4-6 minutos a 2-6 segundos, y con la simetría que conviene recordar: Redshift es igual de malo devolviendo un pedido concreto por su identificador. No sustituye a Aurora, la complementa.

Conoces la arquitectura —nodo líder que planifica y combina, nodos de cómputo, cortes como unidad real de paralelismo—, la diferencia entre DC2, RA3 y Serverless, y la aritmética que decide para MercadoFresco: cuatro minutos de cómputo al día significan que un clúster provisionado estaría encendido el 99,7 % del tiempo sin hacer nada, 1.586 USD frente a 14 USD al mes. Con mercadofresco-analitica como espacio de nombres, wg-mercadofresco-analitica como grupo de trabajo en subredes privadas, y --max-capacity como límite de gasto real.

Sabes diseñar el esquema analítico: el modelo en estrella con hechos_pedidos y las dimensiones dim_producto, dim_cliente, dim_tiempo y dim_ciudad, con claves sustitutas y sin fiarte de unas claves foráneas que Redshift declara pero no verifica. Y sobre todo sabes elegir las dos claves que deciden el rendimiento: la de distribución, con KEY para coubicar las tablas grandes que se unen, ALL para dimensiones pequeñas y el aviso de que una columna de baja cardinalidad produce sesgo y deja la mitad del hardware parado; y la de ordenación, con la fecha primero, que descarta bloques enteros gracias a los mínimos y máximos por bloque.

Manejas la carga con COPY desde mercadofresco-informes-analitica —Parquet, un prefijo con muchos ficheros de 1 a 128 MB, IAM_ROLE en vez de claves, manifiesto con mandatory cuando la exactitud importa— y la canalización nocturna incremental, idempotente, desde la réplica y con alarma, porque el fallo peligroso es el silencioso. Conoces la integración sin ETL desde Aurora y cuándo combinarla con transformación propia; Spectrum para consultar S3 sin cargar; y la comparación honesta con Athena, que basta cuando las consultas son esporádicas y distintas, con Glue como catálogo compartido. Más las vistas materializadas, el WLM automático con prioridades por grupo y las reglas que abortan la consulta descontrolada.

Y sabes que este es el activo más sensible de la empresa: subredes privadas, cifrado con alias/mercadofresco-datos, solo lectura para el grupo mercadofresco-analitica con ALTER DEFAULT PRIVILEGES para que las tablas de mañana también estén cubiertas, y anonimización diseñada antes de la primera carga —ninguna pregunta de negocio necesita la calle del cliente— con revisión del responsable de protección de datos. Por unos 16 USD al mes, con límite de uso configurado y la instrucción de limpiar lo que se cree para practicar.

Queda la última carga de las cuatro que diagnosticamos en 06-01, y es la más absurda de todas. El catálogo se consulta 4.100 veces por minuto en hora punta para devolver, el 94 % de las veces, exactamente lo mismo que devolvió la vez anterior: precios y descripciones que cambian una vez al día, a las 06:00, cuando llega la carga de la lonja. Aurora resuelve cada una de esas consultas correctamente en unos milisegundos, pero hacerlo 4.100 veces por minuto para no aportar ninguna información nueva es trabajo desperdiciado que se paga en latencia, en capacidad y en factura. En 06-05, «Amazon ElastiCache», el catálogo se servirá desde memoria en microsegundos: veremos Redis frente a Memcached, los patrones de caché con su código, las estructuras de datos aplicadas al ranking del viernes y al carrito rápido, las sesiones que por fin harán la tienda verdaderamente sin estado, y el cierre del módulo con la capa de datos completa de MercadoFresco.

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