Desnormalización en bases de datos: Cuándo y cómo utilizarlo
Descubre cómo la desnormalización mejora el rendimiento de lectura al reducir las uniones y simplificar las consultas. Comprende las ventajas e inconvenientes, las técnicas y los casos de uso que lo convierten en una potente herramienta para los sistemas de análisis y generación de informes.
Heescrito mucho sobre la normalización y por qué es una base tan fiable para la integridad de los datos. No estoy contradiciéndome aquí. Sigo creyendo que un esquema bien normalizado es el punto de partida adecuado para la mayoría de los sistemas transaccionales. Este artículo trata sobre la decisión deliberada de ir en contra de esa pureza en situaciones muy específicas en las que el rendimiento de lectura es más importante que la forma normal estricta.
La desnormalización no es omitir la normalización. Es una optimización del rendimiento que se aplica a un modelo normalizado cuando las consultas reales, los usuarios reales y los SLA reales indican que las combinaciones y los cálculos sobre la marcha son demasiado lentos o costosos. En la práctica, se intercambia una lectura más rápida y consultas más sencillas por más almacenamiento, escrituras más complejas y un trabajo adicional de consistencia.
En este artículo, te mostraré cuándo es útil la desnormalización y cuándo no lo es, y cómo implementarla de forma segura en bases de datos SQL. El objetivo no es abandonar el buen diseño, sino añadir atajos cuando la carga de trabajo lo justifique.
¿Qué es la desnormalización en las bases de datos?
Si estás familiarizado con la normalización y el diseño de bases de datos, aquí tienes la respuesta breve:
La desnormalización es el acto deliberado de añadir datos redundantes a un esquema previamente normalizado para acelerar las lecturas y simplificar las consultas. Se trata de una optimización del rendimiento específica, ¡no una excusa para saltarse un buen modelado!
Si eres bastante nuevo en el diseño de bases de datos, tal vez te convenga analizar el significado de «normalizado», «desnormalizado» y «no normalizado». Estos tres términos se utilizan con bastante frecuencia en Internet, y es importante no confundirlos.
Normalizado: Los datos se dividen en tablas bien estructuradas que minimizan la redundancia y protegen la integridad ee (véase 3NF/BCNF).
Desnormalizado: Reintroduces la redundancia selectiva con columnas adicionales, valores precalculados o tablas preunidas, además de ese modelo normalizado, para acelerar las lecturas comunes. Con la desnormalización, se mantiene una única fuente de información veraz (las tablas normalizadas) y, a continuación, se conservan una o varias representaciones más rápidas para las rutas más utilizadas, como paneles de control, listados de productos, búsquedas, etc. Intercambias almacenamiento + complejidad de escritura por velocidad de lectura + consultas más sencillas.
Sin normalizar: Datos sin procesar, ad hoc o desordenados, cuya estructura y restricciones nunca se diseñaron adecuadamente. Eso no es lo que estamos haciendo aquí.
Ejemplo en SQL
Aquí tienes un pequeño ejemplo en SQL que te ayudará a visualizar la diferencia entre el modelado normalizado y el desnormalizado.
Modelización normalizada
-- Source of truthCREATETABLE customers (
customer_id BIGINTPRIMARYKEY,
name TEXTNOTNULL,
tier TEXTNOTNULL);CREATETABLE orders (
order_id BIGINTPRIMARYKEY,
customer_id BIGINTNOTNULLREFERENCES customers(customer_id),
order_total NUMERIC(12,2)NOTNULL,
created_at TIMESTAMPNOTNULLDEFAULTnow());-- Typical report needs a joinSELECT c.name, c.tier,SUM(o.order_total)AS revenue
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
GROUPBY c.name, c.tier;Con la tecnología de
Desnormalizado para una ruta de informes
-- Add redundant fields for faster readsALTERTABLE orders
ADDCOLUMN customer_name TEXT,ADDCOLUMN customer_tier TEXT;-- Now most reports avoid the joinSELECT customer_name, customer_tier,SUM(order_total)AS revenue
FROM orders
GROUPBY customer_name, customer_tier;Con la tecnología de
En este ejemplo, mantendrías a los clientes como fuente de verdad y te asegurarías de que orders.customer_name / orders.customer_tier permanecieran sincronizados (por ejemplo, mediante un desencadenador, una tarea CDC o un relleno programado). La misma verdad, dos representaciones, cada una optimizada para una tarea diferente.
¿La desnormalización es un mal diseño?
Existe la idea errónea de que la desnormalización es solo el resultado de un mal diseño. No es así si se mide, se delimita y se mantiene. Sí, la desnormalización a menudo viola las formas normales superiores (ese es el objetivo), pero es intencionada y está respaldada por un plan para preservar la coherencia. Un mal diseño es omitir por completo la normalización o introducir redundancias sin una estrategia de sincronización.
Si deseas obtener más información sobre el diseño de bases de datos , nuestro curso para principiantes sobre estetema es un buen punto de partida.
Normalización frente a desnormalización
Aquí tienes la comparación lado a lado. No es exhaustivo, pero te ayudará a comprender las ventajas e inconvenientes de cada enfoque.
Aspecto
Normalización
Desnormalización
Objetivo principal
Integridad, redundancia mínima, actualizaciones fáciles y correctas.
Lecturas más rápidas, consultas más sencillas en rutas activas
Ideal para
Sistemas OLTP con escrituras/actualizaciones frecuentes
Paneles con gran cantidad de información, páginas de búsqueda/listados, informes/análisis.
Leer rendimiento
A menudo requiere uniones, funciona bien con los índices adecuados.
Menos uniones, puede ser mucho más rápido y predecible.
Complejidad de la escritura
Simple: única fuente de verdad
Más alto: debes actualizar/sincronizar copias redundantes o agregados.
Almacenamiento
Lean
Más grande (columnas/tablas adicionales, vistas precalculadas)
Integridad de los datos
Diseño robusto (restricciones 3NF/BCNF)
Requiere mecanismos para evitar desviaciones (desencadenantes, CDC, trabajos).
Velocidad de cambio
Los cambios de nombre y las actualizaciones de las columnas se localizan.
Los cambios pueden propagarse por los datos duplicados.
Gastos generales operativos
Inferior: menos piezas móviles.
Más alto: políticas de actualización, rellenos, supervisión.
Modos de fallo
N+1 consultas, uniones lentas, índices faltantes
Datos obsoletos, inconsistencia, amplificación de escritura
Evolución del esquema
Previsible, fácil de refactorizar
Necesita planes de migración para representaciones redundantes.
Ejemplos típicos
Pedidos, clientes, transacciones
Listas de productos previnculadas; tablas de ventas agregadas; vistas materializadas/indexadas.
Por qué y cuándo desnormalizar
La desnormalización resulta realmente útil cuando los usuarios reales y las consultas reales se ven bloqueados por uniones, agregaciones o búsquedas repetidas. Una vez que hayas confirmado que la indexación, el ajuste de consultas y el almacenamiento en caché no son suficientes, es posible que desees recurrir a la desnormalización para optimizar la velocidad de lectura en patrones de acceso predecibles.
Cuándo puede ser útil la desnormalización
Cargas de trabajo con gran volumen de lectura:latencias p95/p99 dominadas por uniones o agregaciones, CPU dedicada a uniones hash/merge, alta rotación de la caché del búfer.
Formas de consulta estables:Los mismos paneles/puntos finales funcionan todo el día con filtros similares (por ejemplo, las ventas de ayer por categoría).
Uniones en abanico:Una tabla activa se une a otras 3-5 solo para renderizar una vista de tarjeta o lista.
Puntos de agregación:Calculas repetidamente totales, recuentos o valores más recientes en rangos amplios.
Presión SLA:El producto necesita respuestas inferiores a 200 ms, mientras que los planes actuales se desbordan al disco o escanean demasiado.
Casos de uso clásicos
Paneles de control e informes de BI (OLAP/análisis):Precalcula los agregados diarios/mensuales, mantén vistas materializadas de agrupaciones costosas o almacena tablas de hechos desnormalizadas para segmentos comunes.
Comercio electrónico/catálogo y páginas de búsqueda/listado:Duplicar nombre_categoría, nombre_marca, precio_con_impuestoso una proyección de productos preunida para listas y filtros rápidos.
CMS/blog/fuentes de noticias:Tienda author_name, tema_principalo renderized_excerpt en la tabla de artículos/publicaciones para evitar uniones o transformaciones en tiempo de ejecución.
Feed de actividad y contadores:Mantener like_count, follower_count, o latest_comment_at como atributos derivados en lugar de recalcularlos.
Análisis de eventos/registros a gran escala (OLAP/nosql):Aplanar los datos anidados para los almacenes columnares y mantener filas anchas compatibles con las particiones para que los escaneos sean predecibles.
Cuándo no desnormalizar y utilizar primero otra cosa
OLTP con gran volumen de escritura y consistencia estricta (pedidos, pagos, ajustes de inventario).
El cuello de botella es la falta o la deficiencia de índices, las consultas N+1 o el ORM comunicativo. Arregla eso primero.
Campos de alta volatilidad (por ejemplo, disponibilidad de productos que cambia cada segundo) donde la duplicación amplifica la rotación.
Equipos sin un plan claro de propiedad y sincronización (desencadenantes, CDC/tareas, políticas de actualización, supervisión de desviaciones). Sin esto, corres el riesgo de hacer más daño que bien.
El conjunto de datos es lo suficientemente pequeño como para que un índice de cobertura o una caché ya lo hagan rápido.
Realmente conviene utilizar la desnormalización cuando te proporciona la mayor velocidad de lectura con la menor carga operativa adicional, y solo después de haber descartado soluciones más económicas.
Si te has dado cuenta de que la desnormalización no es lo que necesitas en este momento, ¡no te preocupes! En la siguiente sección exploraremos las alternativas a la desnormalización.
Alternativas a la desnormalización
La desnormalización no es la solución a todos tus problemas. Antes de añadir redundancia, debes sacar todo el partido posible al motor y a tu aplicación. Estas soluciones son más baratas de mantener y, a menudo, ofrecen los mismos beneficios.
1) Indexación
Índices compuestos/de cobertura: En tus consultas SQL, coloca primero las columnas de filtro y, a continuación, las columnas GROUP BY /ORDER BY . Incluyo columnas de lista de selección para que el motor pueda atender la consulta solo desde el índice.
Índices filtrados/parciales: Indexa solo la parte más importante (por ejemplo, status = 'ACTIVE'), manteniendo el índice pequeño y rápido.
Índices de expresión/funcionales: Indexa en LOWER(email) o date_trunc('day', created_at) para evitar escaneos computados.
2) Ajuste de consultas y paginación
Evita SELECT *. Recupera solo lo que muestras.
Reemplaza las uniones innecesarias con EXISTS/SEMI cuando solo necesites comprobar la presencia.
Empuja los predicados hacia abajo: filtra pronto, agrega tarde.
Usa la paginación por teclado (WHERE created_at < ? ORDER BY created_at DESC LIMIT 50) para un desplazamiento estable y rápido.
3) Almacenamiento en caché
Usa la caché de la aplicación (por ejemplo, Redis) para consultas frecuentes y fragmentos renderizados.
Usa El almacenamiento en caché HTTP (ETag/Last-Modified) para páginas públicas y paneles de control.
Las cachés de corta duración (30-120 s) suelen eliminar la necesidad de realizar cambios en el esquema.
4) Leer réplicas
Descarga las lecturas pesadas a las réplicas. Ideal para paneles de control y exportaciones.
5) Partición y poda
Partición de rango/hash de tablas grandes para que tus exploraciones solo afecten a las particiones relevantes (por ejemplo, los últimos 30 días). Por cierto, esto no es desnormalización, simplemente se trata de reducir la cantidad de datos escaneados.
6) Almacenes columnares/OLAP
Envía análisis pesados a Snowflake/BigQuery/ClickHouse (a través de ELT/dbt). Mantén OLTP normalizado y deja que el almacén gestione formas amplias y fáciles de escanear.
7) Higiene ORM
Matar las consultas N+1 (carga anticipada o por lotes), establece listas de selección sensatas las listas de seleccióny limita tamaños de página. Una capa ORM limpia puede eliminar la necesidad de desnormalizar.
8) Columnas calculadas/generadas (mantener en la base de datos)
Deja que la base de datos mantenga los valores derivados (por ejemplo, price_with_tax) como columnas generadas/calculadas o mediante índices de expresión. Esto te permitirá obtener lecturas rápidas sin necesidad de sincronización a nivel de aplicación.
Técnicas de desnormalización
Estas son las formas más comunes de añadir redundancia controlada a un modelo normalizado. Para cada uno, mostraré qué hace, cuándo utilizarlo y cómo mantenerlo sincronizado.
Configuración: supongamos un núcleo normalizado con clientes, pedidos, artículos_de_pedido y productos.
1) Tablas «proyectadas» aplanadas/preunidas
Qué: Crea una tabla que pre-une las columnas que necesitas para lecturas rápidas (por ejemplo, listado de productos o un panel de control de pedidos).
Cuándo: Tu ruta activa une de 3 a 5 tablas de forma predecible.
Cómo (PostgreSQL):
-- Read-optimised projection for a typical orders listCREATETABLE orders_projection (
order_id BIGINTPRIMARYKEY,
created_day DATENOTNULL,
customer_id BIGINTNOTNULL,
customer_name TEXTNOTNULL,
total_amount NUMERIC(12,2)NOTNULL);-- Initial backfillINSERTINTO orders_projection (order_id, created_day, customer_id, customer_name, total_amount)SELECT o.order_id,
o.created_at::dateAS created_day,
c.customer_id,
c.name AS customer_name,SUM(oi.quantity * oi.unit_price)AS total_amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
GROUPBY o.order_id, created_day, c.customer_id, c.name;-- Index for fast filtering by day/customerCREATEINDEXON orders_projection (created_day, customer_id);Con la tecnología de
Opciones de sincronización:
Trabajo posterior a la escritura (poner en cola «order.updated» → volver a calcular la fila)
Trabajo por lotes nocturno/15 minutos (dbt/cron)
2) Columnas redundantes (copiar algunos atributos)
Qué: Duplica algunos atributos que se consultan con frecuencia en otra tabla para evitar uniones (por ejemplo, orders.customer_name).
Cuándo: Solo necesitas 1 o 2 valores y no deseas una proyección completa.
Cómo (ejemplo de disparador PostgreSQL):
ALTERTABLE orders
ADDCOLUMN customer_name TEXT,ADDCOLUMN customer_tier TEXT;-- Keep the redundant fields correct on insert/updateCREATEORREPLACEFUNCTION sync_order_customer_fields()RETURNSTRIGGERAS $
BEGINSELECT name, tier INTO NEW.customer_name, NEW.customer_tier
FROM customers WHERE customer_id = NEW.customer_id;RETURN NEW;END;
$ LANGUAGE plpgsql;CREATETRIGGER trg_orders_sync_customer
BEFORE INSERTORUPDATEOF customer_id ON orders
FOR EACH ROWEXECUTEFUNCTION sync_order_customer_fields();-- Propagate customer name/tier changes to existing ordersCREATEORREPLACEFUNCTION propagate_customer_changes()RETURNSTRIGGERAS $
BEGINUPDATE orders
SET customer_name = NEW.name,
customer_tier = NEW.tier
WHERE customer_id = NEW.customer_id;RETURN NEW;END;
$ LANGUAGE plpgsql;CREATETRIGGER trg_customer_propagate
AFTERUPDATEOF name, tier ON customers
FOR EACH ROWEXECUTEFUNCTION propagate_customer_changes();Con la tecnología de
Compensación: Las lecturas son sencillas, pero las escrituras se diversifican cuando un cliente realiza una actualización.
Qué: Persiste los valores que de otro modo calcularías sobre la marcha (por ejemplo, price_with_tax,latest_comment_at, item_count).
Cuándo: El cálculo se realiza constantemente y es determinista.
Dos patrones comunes:
Columna generada (la base de datos vuelve a calcular al escribir: tienes menos control, no hay referencias entre tablas)
Columna almacenada + activador/tarea (tú controlas cuándo y cómo cambia)
-- Generated column example (Postgres 12+; same-table expressions)ALTERTABLE products
ADDCOLUMN price_with_tax NUMERIC(12,2) GENERATED ALWAYS AS(price *1.20) STORED;-- Counter maintained by triggers (likes per post)ALTERTABLE posts ADDCOLUMN like_count INTEGERNOTNULLDEFAULT0;CREATEORREPLACEFUNCTION inc_like_count()RETURNSTRIGGERAS $
BEGINUPDATE posts SET like_count = like_count +1WHERE post_id = NEW.post_id;RETURN NEW;END; $ LANGUAGE plpgsql;CREATETRIGGER trg_like_insert
AFTERINSERTON post_likes
FOR EACH ROWEXECUTEFUNCTION inc_like_count();CREATEORREPLACEFUNCTION dec_like_count()RETURNSTRIGGERAS $
BEGINUPDATE posts SET like_count = GREATEST(like_count -1,0)WHERE post_id = OLD.post_id;RETURN OLD;END; $ LANGUAGE plpgsql;CREATETRIGGER trg_like_delete
AFTERDELETEON post_likes
FOR EACH ROWEXECUTEFUNCTION dec_like_count();Con la tecnología de
4) Tablas agregadas/resumen
Qué: Precalcula resúmenes como los ingresos diarios, los usuarios activos por día y los pedidos por categoría.
Cuándo: Los paneles repiten los mismos agrupamientos y los escaneos sin procesar son costosos.
CREATETABLE daily_sales (
sales_day DATEPRIMARYKEY,
gross_amount NUMERIC(14,2)NOTNULL,
order_count INTEGERNOTNULL);-- Incremental upsert for "yesterday" (run hourly/15-min)INSERTINTO daily_sales (sales_day, gross_amount, order_count)SELECT(o.created_at AT TIME ZONE 'UTC')::dateAS sales_day,SUM(oi.quantity * oi.unit_price)AS gross_amount,COUNT(DISTINCT o.order_id)AS order_count
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.created_at >= date_trunc('day',now()-interval'1 day')AND o.created_at < date_trunc('day',now())GROUPBY sales_day
ON CONFLICT (sales_day)DOUPDATESET gross_amount = EXCLUDED.gross_amount,
order_count = EXCLUDED.order_count;Con la tecnología de
Opciones de sincronización: trabajo programado (cron/dbt), actualizaciones en streaming (CDC) o agregadores basados en eventos.
5) Vistas materializadas (resultados de consultas persistentes)
Qué: Almacena el resultado de una consulta costosa como una tabla física que puedas indexar.
Cuándo: El conjunto de resultados es costoso, relativamente estable y puedes tolerar ventanas de actualización.
-- Expensive reportCREATE MATERIALIZED VIEW mv_category_sales ASSELECT p.category_id,
date_trunc('day', o.created_at)ASday,SUM(oi.quantity * oi.unit_price)AS revenue
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
GROUPBY p.category_id, date_trunc('day', o.created_at);-- Make it fast to queryCREATEINDEXON mv_category_sales (category_id,day);-- Refresh patterns-- Full, blocking:
REFRESH MATERIALIZED VIEW mv_category_sales;-- Non-blocking reads (requires unique index on the MV):-- 1) ensure a unique index exists (e.g., (category_id, day))-- 2) then:
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_category_sales;Con la tecnología de
Compensación: Tú gestionarás la cadencia de actualización, ya que la actualización incremental no es nativa en Postgres. Dicho esto, resulta bastante fácil cuando usas dbt o patrones personalizados de «solo añadir + reconstrucción por ventanas».
6) Proyecciones de «documentos» JSON precalculadas.
Qué: Almacena un blob JSON desnormalizado que se ajuste a las necesidades de tu API/UI (por ejemplo, ficha de producto), junto con índices JSON específicos.
Cuándo: Tu API sirve la misma forma repetidamente y tú quieres una lectura.
CREATETABLE product_cards (
product_id BIGINTPRIMARYKEY,
card_json JSONB NOTNULL);-- Build or rebuild card documentsINSERTINTO product_cards (product_id, card_json)SELECT p.product_id,
jsonb_build_object('id', p.product_id,'name', p.name,'brand', b.name,'price_with_tax', p.price *1.20,'category', c.name
)FROM products p
JOIN brands b ON b.brand_id = p.brand_id
JOIN categories c ON c.category_id = p.category_id
ON CONFLICT (product_id)DOUPDATESET card_json = EXCLUDED.card_json;-- GIN index for JSON queries if neededCREATEINDEX idx_product_cards_gin ON product_cards USING GIN (card_json);Con la tecnología de
Opciones de sincronización: reconstrucción basada en eventos cuando se producen cambios en el producto, la marca o la categoría, o actualización frecuente por lotes de los productos actualizados recientemente.
Elegir una estrategia de sincronización (guía rápida)
Desencadenantes: Son inmediatos y garantizan la coherencia de las transacciones. Por lo general, son ideales para un pequeño fan-out y un volumen de escritura bajo, pero pueden afectar a la latencia de escritura si se utilizan en exceso.
Aplicación de doble escritura: La aplicación escribe tanto el código fuente como la desnormalización. Es sencillo, pero más arriesgado. Puedes mitigar eso con reintentos idempotentes y patrones de bandeja de salida/CDC.
CDC → trabajador: Esa estrategia syn es fiable y escalable, ya que propaga los cambios de forma asíncrona. Es ideal cuando la consistencia eventual es aceptable.
Tareas programadas: La opción más sencilla para agregados y vistas materializadas. Solo tienes que elegir las ventanas de actualización que se ajusten a tu tolerancia de experiencia de usuario.
Te recomiendo que siempre incorpores la observabilidad en tu solución. No hay nada peor que no estar seguro de si los cambios se han procesado o replicado correctamente. También puede ayudarte a contabilizar tus comprobaciones de deriva y scripts de relleno.
Cómo implementar la desnormalización paso a paso
Como siempre, manipular tu base de datos es bastante arriesgado. Te recomiendo encarecidamente que repliques tu base de datos e intentes implementar primero la desnormalización en la réplica, solo para asegurarte de que todo funciona como esperas. Si tienes un entorno de desarrollo, ¡mucho mejor!
El proceso es bastante sencillo: medir → cambiar lo menos posible → mantenerlo correcto → verificar → supervisar → repetir.
1) Define tu perfil y establece un objetivo de éxito.
Captura la consulta exacta (texto + parámetros) y su frecuencia.
Registra una línea de base: EXPLAIN (ANALYZE, BUFFERS) plan, latencia p95, CPU/E/S, filas escaneadas.
Acuerda un criterio de éxito (por ejemplo, «p95 < 120 ms con un coste de escritura ≤1,2×»).
Confirma la tolerancia de consistencia (por ejemplo, «los análisis pueden tener un retraso de 5 minutos»).
2) Elige la desnormalización más pequeña que funcione.
Lo ideal es probar con una columna redundante o una tabla resumen en una proyección grande.
Dibuja la nueva forma:
¿Columnas redundantes? (cuáles y por qué)
¿Tabla agregada o vista materializada? (grano, claves, ventana de actualización)
Decide el modelo de consistencia:
Fuerte (desencadenante/transaccional) frente a eventual (CDC/trabajo/actualización de vista materializada).
Anota el presupuesto de amplificación de escritura (cuántas escrituras adicionales por evento son aceptables).
CDC/bandeja de salida → trabajador (buena escalabilidad, consistencia eventual):
La aplicación escribe en las tablas de origen y en una fila de la bandeja de salida en la misma transacción.
Un trabajador lee la bandeja de salida y actualiza el objetivo desnormalizado de forma idempotente.
Tareas programadas / Actualización de MV (ideal para agregados):
Define la cadencia de actualización, las ventanas y la estrategia de relleno.
La idempotencia no es negociable. Las actualizaciones deben poder repetirse de forma segura (por ejemplo, UPSERT con recálculo determinista). Confía en mí, te lo agradecerás más adelante.
4) Crear, rellenar y validar
Crea estructuras (tablas, columnas, índices, desencadenadores/tareas), pero mantén la aplicación sin cambios.
Relleno desde la fuente de verdad.
Validar la paridad con invariantes:
-- Example parity check: orders_projection vs live joinSELECTCOUNT(*)AS mismatches
FROM orders_projection p
JOIN orders o ON o.order_id = p.order_id
JOIN customers c ON c.customer_id = o.customer_id
WHERE p.customer_name <> c.name
OR p.total_amount <>(SELECTSUM(oi.quantity * oi.unit_price)FROM order_items oi WHERE oi.order_id = o.order_id
);Con la tecnología de
4. Corregir las discrepancias; volver a ejecutar hasta llegar a cero (o dentro de un margen de error acordado).
5) Corta con cuidado.
Envía el cambio de la aplicación detrás de un indicador de función o canario (por ejemplo, el 10 % del tráfico lee desde la ruta desnormalizada).
Ejecuta lecturas en segundo plano: calcula el resultado anterior en segundo plano y compara los hash/agregados para un subconjunto de solicitudes.
Mantén una reversión rápida (vuelve instantáneamente a la ruta normalizada).
6) Supervisa lo que realmente importa.
Crea un pequeño panel de control en la herramienta de observabilidad que prefieras:
Lee: latencia p50/p95, filas leídas, aciertos de búfer, estabilidad del plan de consulta.
Escribir: tiempo de escritura adicional, errores de activación/trabajo, retraso en la cola (CDC), marca de tiempo de la última actualización de MV.
Calidad de los datos: contadores de desviación (comprobaciones de paridad diarias), número de filas rellenadas, porcentaje de discrepancias.
Coste/huellatamaños de las tablas, aumento del índice, crecimiento del tamaño de MV.
7) Operar e iterar
Asignar la responsabilidad (equipo/sistema de guardia) y un manual de procedimientos (cómo reconstruir, rellenar, reparar). Esto suele pasarse por alto, pero es importante poder responder lo más rápido posible cuando se produce un incidente.
Reevaluar trimestralmente: ¿sigues necesitando esto? ¿Sigue teniendo la forma adecuada? ¿Podemos simplificarlo? Documenta la decisión y enlázala a los paneles de control.
Ventajas y desventajas de la desnormalización
Hemos tratado los detalles en tabla Normalización frente a desnormalización , así que aquí tienes un breve resumen.
Lo que ganas
Lecturas más rápidas y latencias p95/p99 más predecibles.
Consultas más sencillas en rutas activas (menos uniones/agregaciones)
Menor consumo de CPU/E/S por lectura y consultas analíticas más económicas.
Cuánto cuesta
Amplificación de escritura (actualizaciones/inserciones adicionales)
Riesgo de coherencia (datos obsoletos/inconsistentes a menos que se sincronicen correctamente)
Aumento del almacenamiento (columnas/tablas/vistas duplicadas)
Cambiar fan-out (las actualizaciones del esquema/lógica deben reflejarse)
Herramientas y tecnologías que admiten la desnormalización
No todas las bases de datos te ayudan a desnormalizar de la misma manera. Algunos te ofrecen funciones de primera clase para conservar los resultados de consultas costosas, mientras que otros esperan que tú mismo te encargues de ello mediante trabajos, desencadenadores o canalizaciones.
PostgreSQL
Postgres te ofrece varias formas de materializar formas optimizadas para la lectura.
Las vistas materializadas conservan el resultado de una consulta, por lo que las lecturas son instantáneas. Tú eliges cuándo actualizar (de forma programada o puntual) e incluso puedes actualizar simultáneamente para que los lectores no se vean bloqueados.
Para una duplicación ligera, las columnas generadas y los índices de expresión permiten a la base de datos mantener valores derivados y acelerar los filtros comunes sin necesidad de escribir lógica de sincronización adicional.
Cuando necesites una fuerte consistencia entre la fuente y una copia desnormalizada, puedes utilizar disparadores para mantener la sincronización (pero recuerda que añaden trabajo a tu ruta de escritura). Para actualizaciones por eventos o por lotes, puedes combinar Postgres con un programador (por ejemplo, pg_cron o un ejecutor de tareas externo) o con replicación lógica/CDC para transmitir los cambios a un trabajador que actualice tus tablas desnormalizadas de forma asíncrona.
Si deseas poner en práctica tus conocimientos y crear tus propias bases de datos y probar la desnormalización, echaun vistazo a nuestro curso de PostgreSQL.
SQL Server
SQL Server se inclina fuertemente por las vistas indexadas, que son una especie de vistas materializadas siempre activas, en las que el motor mantiene la vista sincronizada en cada inserción/actualización/eliminación, por lo que las lecturas son muy rápidas. La otra cara de la moneda es que ahora las escrituras mantienen tanto las tablas base como la vista, por lo que las cargas de trabajo OLTP pesadas pueden ralentizarse.
Aparte de eso, puedes utilizar columnas calculadas persistentes para cubrir atributos derivados de una sola fila.
Para la generación de informes a gran escala, los índices de almacenamiento en columnas son una alternativa a la desnormalización: comprimen y escanean grandes conjuntos de datos de manera muy eficiente, lo que puede eliminar la necesidad de duplicar los datos en primer lugar.
Oracle
Las vistas materializadas de Oracle son una opción madura con una actualización « FAST » (solo lo que ha cambiado) que utiliza registros de cambios para actualizar solo lo que ha cambiado, o una actualización « COMPLETE » (todo) cuando no te importa reconstruir todo.
Con Query Rewrite, el optimizador puede utilizar de forma transparente tu vista materializada cuando alguien consulta las tablas subyacentes, por lo que los equipos obtienen una mayor velocidad sin necesidad de cambiar el SQL de la aplicación. Como siempre, una lectura más rápida implica más trabajo en algún otro aspecto: mantener registros y actualizar vistas aumenta la carga operativa y de escritura.
MySQL / MariaDB
MySQL no tiene vistas materializadas nativas, por lo que la desnormalización suele construirse con tablas físicas y tareas programadas o desencadenadores para mantenerlas actualizadas.
Las columnas generadas ayudan con valores derivados simples. Esto funciona bien para paneles y resúmenes predecibles, pero, una vez más, vale la pena ser consciente de la cadencia de actualización y la complejidad de los desencadenantes para no sobrecargar accidentalmente las rutas de escritura críticas. Muchos equipos combinan esto con réplicas de lectura para descargar por completo las consultas de informes.
Snowflake / BigQuery (capa de análisis)
Los almacenes columnares están diseñados para datos amplios, fáciles de escanear y desnormalizados. Normalmente, se modelan tablas de hechos amplias con atributos útiles incorporados y, a continuación, se recurre a la partición/agrupación para podar lo que se escanea.
Ambas plataformas admiten vistas materializadas y tareas/consultas programadas para mantener los agregados actualizados sin tocar el sistema OLTP. A cambio de los costes de almacenamiento y actualización, obtienes lecturas muy predecibles y económicas a gran escala. Es perfecto para paneles de control y BI.
Este curso es un poco más avanzado que nuestro curso de PostgreSQL, por lo que, si ya te sientes cómodo con las bases de datos, no dudes en probar nuestra introducción al modelado de datos con Snowflake.
dbt (modelado y orquestación)
dbt se conoce como la capa de «infraestructura como código» para el análisis desnormalizado. Definís los modelos una vez, elegís una materialización (tabla, vista, incremental) y dejás que dbt se encargue de las compilaciones, las dependencias y las pruebas. Los modelos incrementales son especialmente útiles para tablas resumen en las que solo es necesario añadir y fusionar datos nuevos. ¡Es sin duda una de mis herramientas favoritas!
CDC y canales de datos
Cuando deseas que los cambios de la aplicación fluyan de forma fiable hacia estructuras desnormalizadas, Change Data Capture es la solución ideal. Herramientas como Debezium (autohospedadas) o conectores gestionados (Fivetran, Airbyte) transmiten los cambios a nivel de fila desde OLTP a trabajadores o almacenes que actualizan tablas de proyección, contadores o agregados. Esto suele ser coherente a largo plazo (ideal para paneles y feeds) y se adapta mucho mejor que meter todo en los activadores. Haz que las actualizaciones sean idempotentes, supervisa el retraso y mantén una ruta de relleno para los eventos tardíos o perdidos.
Conclusiones clave y reflexiones finales
La desnormalización es una optimización pragmática que se superpone a un diseño normalizado, no un rechazo del mismo. Estás sacrificando el almacenamiento y la complejidad de escritura a cambio de lecturas más rápidas y sencillas en rutas específicas y bien conocidas.
Recuerda:
Comienza normalizado. Utiliza la desnormalización solo cuando una consulta medida y con nombre lo requiera.
Agotar primero las soluciones más económicas (índices, reescritura de consultas, almacenamiento en caché, réplicas, OLAP).
Elige la desnormalización más pequeña que resulte rentable (una columna redundante, un agregado pequeño o una vista materializada).
Sé explícito sobre la frescura (consistencia fuerte frente a consistencia eventual) y crea una sincronización idempotente.
Mide antes y después, supervisa las desviaciones y ten a mano un interruptor de reversión.
¡Considera las tablas normalizadas como la fuente de verdad! Las piezas desnormalizadas son copias optimizadas para lectura que puedes reconstruir.
Autor
Marie Fayard
Soy un líder tecnológico con mentalidad de producto, especializado en el crecimiento de startups en fase inicial, desde el primer prototipo hasta la adaptación del producto al mercado y más allá. Siento una curiosidad infinita por saber cómo utiliza la gente la tecnología, y me encanta trabajar estrechamente con fundadores y equipos multifuncionales para dar vida a ideas audaces. Cuando no estoy creando productos, busco inspiración en nuevos rincones del mundo o me desahogo en el estudio de yoga.
Una guía completa sobre las mejores bases de datos vectoriales. Domina el almacenamiento de datos de alta dimensión, descifra información no estructurada y aprovecha las incrustaciones vectoriales para aplicaciones de IA.
Explora cómo el uso de un alias SQL simplifica tanto los nombres de las columnas como los de las tablas. Aprende por qué utilizar un alias SQL es clave para mejorar la legibilidad y gestionar uniones complejas.
Aprende a utilizar los disparadores SQL para automatizar tareas, mantener la integridad de los datos y mejorar el rendimiento de la base de datos. Prueba ejemplos prácticos como los comandos CREATE, ALTER y DROP en MySQL y Oracle.
Aprende a seleccionar fácilmente varias columnas de una tabla de base de datos en SQL, o a seleccionar todas las columnas de una tabla en una simple consulta.
Heescrito mucho sobre la normalización y por qué es una base tan fiable para la integridad de los datos. No estoy contradiciéndome aquí. Sigo creyendo que un esquema bien normalizado es el punto de partida adecuado para la mayoría de los sistemas transaccionales. Este artículo trata sobre la decisión deliberada de ir en contra de esa pureza en situaciones muy específicas en las que el rendimiento de lectura es más importante que la forma normal estricta.
La desnormalización no es omitir la normalización. Es una optimización del rendimiento que se aplica a un modelo normalizado cuando las consultas reales, los usuarios reales y los SLA reales indican que las combinaciones y los cálculos sobre la marcha son demasiado lentos o costosos. En la práctica, se intercambia una lectura más rápida y consultas más sencillas por más almacenamiento, escrituras más complejas y un trabajo adicional de consistencia.
En este artículo, te mostraré cuándo es útil la desnormalización y cuándo no lo es, y cómo implementarla de forma segura en bases de datos SQL. El objetivo no es abandonar el buen diseño, sino añadir atajos cuando la carga de trabajo lo justifique.
¿Qué es la desnormalización en las bases de datos?
Si estás familiarizado con la normalización y el diseño de bases de datos, aquí tienes la respuesta breve:
La desnormalización es el acto deliberado de añadir datos redundantes a un esquema previamente normalizado para acelerar las lecturas y simplificar las consultas. Se trata de una optimización del rendimiento específica, ¡no una excusa para saltarse un buen modelado!
Si eres bastante nuevo en el diseño de bases de datos, tal vez te convenga analizar el significado de «normalizado», «desnormalizado» y «no normalizado». Estos tres términos se utilizan con bastante frecuencia en Internet, y es importante no confundirlos.
Normalizado: Los datos se dividen en tablas bien estructuradas que minimizan la redundancia y protegen la integridad ee (véase 3NF/BCNF).
Desnormalizado: Reintroduces la redundancia selectiva con columnas adicionales, valores precalculados o tablas preunidas, además de ese modelo normalizado, para acelerar las lecturas comunes. Con la desnormalización, se mantiene una única fuente de información veraz (las tablas normalizadas) y, a continuación, se conservan una o varias representaciones más rápidas para las rutas más utilizadas, como paneles de control, listados de productos, búsquedas, etc. Intercambias almacenamiento + complejidad de escritura por velocidad de lectura + consultas más sencillas.
Sin normalizar: Datos sin procesar, ad hoc o desordenados, cuya estructura y restricciones nunca se diseñaron adecuadamente. Eso no es lo que estamos haciendo aquí.
Ejemplo en SQL
Aquí tienes un pequeño ejemplo en SQL que te ayudará a visualizar la diferencia entre el modelado normalizado y el desnormalizado.
Modelización normalizada
Desnormalizado para una ruta de informes
En este ejemplo, mantendrías a los clientes como fuente de verdad y te asegurarías de que
orders.customer_name / orders.customer_tierpermanecieran sincronizados (por ejemplo, mediante un desencadenador, una tarea CDC o un relleno programado). La misma verdad, dos representaciones, cada una optimizada para una tarea diferente.¿La desnormalización es un mal diseño?
Existe la idea errónea de que la desnormalización es solo el resultado de un mal diseño. No es así si se mide, se delimita y se mantiene. Sí, la desnormalización a menudo viola las formas normales superiores (ese es el objetivo), pero es intencionada y está respaldada por un plan para preservar la coherencia. Un mal diseño es omitir por completo la normalización o introducir redundancias sin una estrategia de sincronización.
Si deseas obtener más información sobre el diseño de bases de datos , nuestro curso para principiantes sobre estetema es un buen punto de partida.
Normalización frente a desnormalización
Aquí tienes la comparación lado a lado. No es exhaustivo, pero te ayudará a comprender las ventajas e inconvenientes de cada enfoque.
Aspecto
Normalización
Desnormalización
Objetivo principal
Integridad, redundancia mínima, actualizaciones fáciles y correctas.
Lecturas más rápidas, consultas más sencillas en rutas activas
Ideal para
Sistemas OLTP con escrituras/actualizaciones frecuentes
Paneles con gran cantidad de información, páginas de búsqueda/listados, informes/análisis.
Leer rendimiento
A menudo requiere uniones, funciona bien con los índices adecuados.
Menos uniones, puede ser mucho más rápido y predecible.
Complejidad de la escritura
Simple: única fuente de verdad
Más alto: debes actualizar/sincronizar copias redundantes o agregados.
Almacenamiento
Lean
Más grande (columnas/tablas adicionales, vistas precalculadas)
Integridad de los datos
Diseño robusto (restricciones 3NF/BCNF)
Requiere mecanismos para evitar desviaciones (desencadenantes, CDC, trabajos).
Velocidad de cambio
Los cambios de nombre y las actualizaciones de las columnas se localizan.
Los cambios pueden propagarse por los datos duplicados.
Gastos generales operativos
Inferior: menos piezas móviles.
Más alto: políticas de actualización, rellenos, supervisión.
Modos de fallo
N+1 consultas, uniones lentas, índices faltantes
Datos obsoletos, inconsistencia, amplificación de escritura
Evolución del esquema
Previsible, fácil de refactorizar
Necesita planes de migración para representaciones redundantes.
Ejemplos típicos
Pedidos, clientes, transacciones
Listas de productos previnculadas; tablas de ventas agregadas; vistas materializadas/indexadas.
Por qué y cuándo desnormalizar
La desnormalización resulta realmente útil cuando los usuarios reales y las consultas reales se ven bloqueados por uniones, agregaciones o búsquedas repetidas. Una vez que hayas confirmado que la indexación, el ajuste de consultas y el almacenamiento en caché no son suficientes, es posible que desees recurrir a la desnormalización para optimizar la velocidad de lectura en patrones de acceso predecibles.
Cuándo puede ser útil la desnormalización
Casos de uso clásicos
Cuándo no desnormalizar y utilizar primero otra cosa
Realmente conviene utilizar la desnormalización cuando te proporciona la mayor velocidad de lectura con la menor carga operativa adicional, y solo después de haber descartado soluciones más económicas.
Si te has dado cuenta de que la desnormalización no es lo que necesitas en este momento, ¡no te preocupes! En la siguiente sección exploraremos las alternativas a la desnormalización.
Alternativas a la desnormalización
La desnormalización no es la solución a todos tus problemas. Antes de añadir redundancia, debes sacar todo el partido posible al motor y a tu aplicación. Estas soluciones son más baratas de mantener y, a menudo, ofrecen los mismos beneficios.
1) Indexación
Índices compuestos/de cobertura: En tus consultas SQL, coloca primero las columnas de filtro y, a continuación, las columnas
GROUP BY/ORDER BY. Incluyo columnas de lista de selección para que el motor pueda atender la consulta solo desde el índice.Índices filtrados/parciales: Indexa solo la parte más importante (por ejemplo,
status = 'ACTIVE'), manteniendo el índice pequeño y rápido.Índices de expresión/funcionales: Indexa en
LOWER(email)odate_trunc('day', created_at)para evitar escaneos computados.2) Ajuste de consultas y paginación
Evita
SELECT *. Recupera solo lo que muestras.Reemplaza las uniones innecesarias con
EXISTS/SEMIcuando solo necesites comprobar la presencia.Empuja los predicados hacia abajo: filtra pronto, agrega tarde.
Usa la paginación por teclado (
WHERE created_at < ? ORDER BY created_at DESC LIMIT 50) para un desplazamiento estable y rápido.3) Almacenamiento en caché
4) Leer réplicas
5) Partición y poda
6) Almacenes columnares/OLAP
7) Higiene ORM
8) Columnas calculadas/generadas (mantener en la base de datos)
Deja que la base de datos mantenga los valores derivados (por ejemplo,
price_with_tax) como columnas generadas/calculadas o mediante índices de expresión. Esto te permitirá obtener lecturas rápidas sin necesidad de sincronización a nivel de aplicación.Técnicas de desnormalización
Estas son las formas más comunes de añadir redundancia controlada a un modelo normalizado. Para cada uno, mostraré qué hace, cuándo utilizarlo y cómo mantenerlo sincronizado.
Configuración: supongamos un núcleo normalizado con clientes, pedidos, artículos_de_pedido y productos.
1) Tablas «proyectadas» aplanadas/preunidas
Qué: Crea una tabla que pre-une las columnas que necesitas para lecturas rápidas (por ejemplo, listado de productos o un panel de control de pedidos).
Cuándo: Tu ruta activa une de 3 a 5 tablas de forma predecible.
Cómo (PostgreSQL):
Opciones de sincronización:
2) Columnas redundantes (copiar algunos atributos)
Qué: Duplica algunos atributos que se consultan con frecuencia en otra tabla para evitar uniones (por ejemplo, orders.customer_name).
Cuándo: Solo necesitas 1 o 2 valores y no deseas una proyección completa.
Cómo (ejemplo de disparador PostgreSQL):
Compensación: Las lecturas son sencillas, pero las escrituras se diversifican cuando un cliente realiza una actualización.
3) Atributos derivados (almacenar valores calculados)
Qué: Persiste los valores que de otro modo calcularías sobre la marcha (por ejemplo,
price_with_tax,latest_comment_at,item_count).Cuándo: El cálculo se realiza constantemente y es determinista.
Dos patrones comunes:
4) Tablas agregadas/resumen
Qué: Precalcula resúmenes como los ingresos diarios, los usuarios activos por día y los pedidos por categoría.
Cuándo: Los paneles repiten los mismos agrupamientos y los escaneos sin procesar son costosos.
Opciones de sincronización: trabajo programado (cron/dbt), actualizaciones en streaming (CDC) o agregadores basados en eventos.
5) Vistas materializadas (resultados de consultas persistentes)
Qué: Almacena el resultado de una consulta costosa como una tabla física que puedas indexar.
Cuándo: El conjunto de resultados es costoso, relativamente estable y puedes tolerar ventanas de actualización.
Compensación: Tú gestionarás la cadencia de actualización, ya que la actualización incremental no es nativa en Postgres. Dicho esto, resulta bastante fácil cuando usas dbt o patrones personalizados de «solo añadir + reconstrucción por ventanas».
6) Proyecciones de «documentos» JSON precalculadas.
Qué: Almacena un blob JSON desnormalizado que se ajuste a las necesidades de tu API/UI (por ejemplo, ficha de producto), junto con índices JSON específicos.
Cuándo: Tu API sirve la misma forma repetidamente y tú quieres una lectura.
Opciones de sincronización: reconstrucción basada en eventos cuando se producen cambios en el producto, la marca o la categoría, o actualización frecuente por lotes de los productos actualizados recientemente.
Elegir una estrategia de sincronización (guía rápida)
Te recomiendo que siempre incorpores la observabilidad en tu solución. No hay nada peor que no estar seguro de si los cambios se han procesado o replicado correctamente. También puede ayudarte a contabilizar tus comprobaciones de deriva y scripts de relleno.
Cómo implementar la desnormalización paso a paso
Como siempre, manipular tu base de datos es bastante arriesgado. Te recomiendo encarecidamente que repliques tu base de datos e intentes implementar primero la desnormalización en la réplica, solo para asegurarte de que todo funciona como esperas. Si tienes un entorno de desarrollo, ¡mucho mejor!
El proceso es bastante sencillo: medir → cambiar lo menos posible → mantenerlo correcto → verificar → supervisar → repetir.
1) Define tu perfil y establece un objetivo de éxito.
2) Elige la desnormalización más pequeña que funcione.
3) Diseña la ruta de sincronización.
La idempotencia no es negociable. Las actualizaciones deben poder repetirse de forma segura (por ejemplo,
UPSERTcon recálculo determinista). Confía en mí, te lo agradecerás más adelante.4) Crear, rellenar y validar
4. Corregir las discrepancias; volver a ejecutar hasta llegar a cero (o dentro de un margen de error acordado).
5) Corta con cuidado.
6) Supervisa lo que realmente importa.
Crea un pequeño panel de control en la herramienta de observabilidad que prefieras:
7) Operar e iterar
Ventajas y desventajas de la desnormalización
Hemos tratado los detalles en tabla Normalización frente a desnormalización , así que aquí tienes un breve resumen.
Lo que ganas
Cuánto cuesta
Herramientas y tecnologías que admiten la desnormalización
No todas las bases de datos te ayudan a desnormalizar de la misma manera. Algunos te ofrecen funciones de primera clase para conservar los resultados de consultas costosas, mientras que otros esperan que tú mismo te encargues de ello mediante trabajos, desencadenadores o canalizaciones.
PostgreSQL
Postgres te ofrece varias formas de materializar formas optimizadas para la lectura.
Las vistas materializadas conservan el resultado de una consulta, por lo que las lecturas son instantáneas. Tú eliges cuándo actualizar (de forma programada o puntual) e incluso puedes actualizar simultáneamente para que los lectores no se vean bloqueados.
Para una duplicación ligera, las columnas generadas y los índices de expresión permiten a la base de datos mantener valores derivados y acelerar los filtros comunes sin necesidad de escribir lógica de sincronización adicional.
Cuando necesites una fuerte consistencia entre la fuente y una copia desnormalizada, puedes utilizar disparadores para mantener la sincronización (pero recuerda que añaden trabajo a tu ruta de escritura). Para actualizaciones por eventos o por lotes, puedes combinar Postgres con un programador (por ejemplo, pg_cron o un ejecutor de tareas externo) o con replicación lógica/CDC para transmitir los cambios a un trabajador que actualice tus tablas desnormalizadas de forma asíncrona.
Si deseas poner en práctica tus conocimientos y crear tus propias bases de datos y probar la desnormalización, echaun vistazo a nuestro curso de PostgreSQL.
SQL Server
SQL Server se inclina fuertemente por las vistas indexadas, que son una especie de vistas materializadas siempre activas, en las que el motor mantiene la vista sincronizada en cada inserción/actualización/eliminación, por lo que las lecturas son muy rápidas. La otra cara de la moneda es que ahora las escrituras mantienen tanto las tablas base como la vista, por lo que las cargas de trabajo OLTP pesadas pueden ralentizarse.
Aparte de eso, puedes utilizar columnas calculadas persistentes para cubrir atributos derivados de una sola fila.
Para la generación de informes a gran escala, los índices de almacenamiento en columnas son una alternativa a la desnormalización: comprimen y escanean grandes conjuntos de datos de manera muy eficiente, lo que puede eliminar la necesidad de duplicar los datos en primer lugar.
Oracle
Las vistas materializadas de Oracle son una opción madura con una actualización «
FAST» (solo lo que ha cambiado) que utiliza registros de cambios para actualizar solo lo que ha cambiado, o una actualización «COMPLETE» (todo) cuando no te importa reconstruir todo.Con Query Rewrite, el optimizador puede utilizar de forma transparente tu vista materializada cuando alguien consulta las tablas subyacentes, por lo que los equipos obtienen una mayor velocidad sin necesidad de cambiar el SQL de la aplicación. Como siempre, una lectura más rápida implica más trabajo en algún otro aspecto: mantener registros y actualizar vistas aumenta la carga operativa y de escritura.
MySQL / MariaDB
MySQL no tiene vistas materializadas nativas, por lo que la desnormalización suele construirse con tablas físicas y tareas programadas o desencadenadores para mantenerlas actualizadas.
Las columnas generadas ayudan con valores derivados simples. Esto funciona bien para paneles y resúmenes predecibles, pero, una vez más, vale la pena ser consciente de la cadencia de actualización y la complejidad de los desencadenantes para no sobrecargar accidentalmente las rutas de escritura críticas. Muchos equipos combinan esto con réplicas de lectura para descargar por completo las consultas de informes.
Snowflake / BigQuery (capa de análisis)
Los almacenes columnares están diseñados para datos amplios, fáciles de escanear y desnormalizados. Normalmente, se modelan tablas de hechos amplias con atributos útiles incorporados y, a continuación, se recurre a la partición/agrupación para podar lo que se escanea.
Ambas plataformas admiten vistas materializadas y tareas/consultas programadas para mantener los agregados actualizados sin tocar el sistema OLTP. A cambio de los costes de almacenamiento y actualización, obtienes lecturas muy predecibles y económicas a gran escala. Es perfecto para paneles de control y BI.
Este curso es un poco más avanzado que nuestro curso de PostgreSQL, por lo que, si ya te sientes cómodo con las bases de datos, no dudes en probar nuestra introducción al modelado de datos con Snowflake.
dbt (modelado y orquestación)
dbt se conoce como la capa de «infraestructura como código» para el análisis desnormalizado. Definís los modelos una vez, elegís una materialización (tabla, vista, incremental) y dejás que dbt se encargue de las compilaciones, las dependencias y las pruebas. Los modelos incrementales son especialmente útiles para tablas resumen en las que solo es necesario añadir y fusionar datos nuevos. ¡Es sin duda una de mis herramientas favoritas!
CDC y canales de datos
Cuando deseas que los cambios de la aplicación fluyan de forma fiable hacia estructuras desnormalizadas, Change Data Capture es la solución ideal. Herramientas como Debezium (autohospedadas) o conectores gestionados (Fivetran, Airbyte) transmiten los cambios a nivel de fila desde OLTP a trabajadores o almacenes que actualizan tablas de proyección, contadores o agregados. Esto suele ser coherente a largo plazo (ideal para paneles y feeds) y se adapta mucho mejor que meter todo en los activadores. Haz que las actualizaciones sean idempotentes, supervisa el retraso y mantén una ruta de relleno para los eventos tardíos o perdidos.
Conclusiones clave y reflexiones finales
La desnormalización es una optimización pragmática que se superpone a un diseño normalizado, no un rechazo del mismo. Estás sacrificando el almacenamiento y la complejidad de escritura a cambio de lecturas más rápidas y sencillas en rutas específicas y bien conocidas.
Recuerda: