Módulo 3: Star Vs Snowflake Vs One Big Table

El argumento de la tabla ancha (One Big Table)

Descripción

Las dos lecciones anteriores empujaron el modelo de Kiosko en una dirección: más normalización, más JOIN, más consistencia, menos duplicación. Esta lección empuja en la dirección exactamente opuesta, y explica por qué vale la pena hacerlo. La tabla ancha —One Big Table, OBT— lleva la denormalización al extremo: en vez de dividir el dato en varias tablas relacionadas por llave, lo junta todo en una sola tabla, con una fila por cada combinación relevante y todas las columnas descriptivas ya presentes, listas para consultar sin un solo JOIN. Esta lección no construye esa tabla todavía —eso es el trabajo de la lección 5—; construye el argumento que explica por qué, en el mundo del warehouse columnar moderno, esa forma dejó de ser un atajo perezoso y se convirtió en una decisión de diseño legítima.

Conexión con el módulo. Esta lección es puramente conceptual, pero es el puente necesario entre las dos primeras lecciones (que normalizaron) y las dos siguientes (que van a construir y medir la OBT). Sin este argumento, la lección 5 se sentiría como un retroceso arbitrario a "la forma incorrecta" en vez de una decisión informada.

Una analogía: por qué a veces sí conviene la mesa ya servida

En la lección 1 de este módulo, la OBT se presentó como la mesa ya servida — todo el plato preparado de antemano, listo para comer sin ir a la cocina. Vale la pena extender esa analogía un paso más, porque el argumento moderno de la tabla ancha no es "la mesa servida es siempre mejor que cocinar" — sería una afirmación absurda—. El argumento real depende de quién se sienta a la mesa y qué tan seguido repite la misma pregunta.

Piensa en un restaurante de comida rápida en la hora pico: cientos de personas piden, en su gran mayoría, uno de los mismos diez platos del menú. Preparar cada plato desde cero, cocinando cada ingrediente por separado en el momento exacto en que alguien lo pide, sería insostenible bajo esa demanda — el restaurante que sobrevive la hora pico es el que tiene los ingredientes de los platos más pedidos ya semi-preparados, listos para ensamblar en segundos. Ese es exactamente el caso de uso donde la OBT gana: un patrón de consulta conocido, repetido con mucha frecuencia, donde el costo de "preparar el plato de antemano" (construir la tabla ancha una vez) se paga muchas veces con la velocidad de servirlo (cada consulta del equipo de BI, sin JOIN). El error sería generalizar esa lógica a un menú a la carta, donde cada cliente pide algo distinto y semi-preparar todo de antemano desperdiciaría más de lo que ahorra — ese es el caso donde el star, flexible por diseño, sigue ganando, y es exactamente lo que la lección 6 va a mostrar con números.

Ejemplo trabajado: contando saltos de JOIN para una pregunta real de BI

Antes de construir la OBT en la lección 5, vale la pena ver el argumento en números concretos: para una pregunta de negocio típica de un tablero de BI, ¿cuántos JOIN necesita escribir un analista bajo cada una de las tres formas?

# obt_argument_preview.py
QUESTION = "Revenue por categoria de producto, por ciudad de tienda, separado en fin de semana vs entre semana"

SHAPES = [
    ("star", 3, "fact_orders -> dim_product (category), fact_orders -> dim_store (city), fact_orders -> dim_date (is_weekend)"),
    ("snowflake", 4, "fact_orders -> dim_product_normalized -> dim_category (category, 2 saltos), fact_orders -> dim_store (city), fact_orders -> dim_date (is_weekend)"),
    ("OBT (mart_daily_sales_obt)", 0, "ningun JOIN -- category, city e is_weekend ya son columnas de la misma fila"),
]

print(f"Pregunta de negocio: {QUESTION}\n")
for shape, joins, path in SHAPES:
    print(f"{shape:28} {joins} JOIN(s)")
    print(f"{'':28} {path}\n")

Qué esperar. Al correr python3 obt_argument_preview.py, la salida es exactamente esta:

Pregunta de negocio: Revenue por categoria de producto, por ciudad de tienda, separado en fin de semana vs entre semana

star                         3 JOIN(s)
                             fact_orders -> dim_product (category), fact_orders -> dim_store (city), fact_orders -> dim_date (is_weekend)

snowflake                    4 JOIN(s)
                             fact_orders -> dim_product_normalized -> dim_category (category, 2 saltos), fact_orders -> dim_store (city), fact_orders -> dim_date (is_weekend)

OBT (mart_daily_sales_obt)   0 JOIN(s)
                             ningun JOIN -- category, city e is_weekend ya son columnas de la misma fila

Esta pregunta —revenue por categoría, por ciudad, separado por fin de semana— es exactamente el tipo de pregunta que un tablero de BI repite decenas de veces al día, con distintos filtros: "muéstrame lo mismo pero solo para agosto", "ahora solo Bogotá", "ahora solo bebidas". Bajo el star, cada una de esas variaciones sigue necesitando tres JOIN; bajo el snowflake, cuatro; bajo la OBT, cero — la pregunta se resuelve con un GROUP BY directo sobre una sola tabla ya aplanada. La lección 7 de este módulo va a ejecutar esta misma pregunta, de verdad, por los tres caminos, y confirmar que los tres devuelven exactamente el mismo resultado.

Diagrama: el mismo dato, tres costos de acceso distintos

flowchart LR
    subgraph Consulta["La misma pregunta de BI"]
        Q["Revenue por categoria,\npor ciudad, por fin de semana"]
    end

    Q --> S["star: 3 JOIN\nfact_orders + 3 dimensiones"]
    Q --> SF["snowflake: 4 JOIN\nfact_orders + 4 tablas\n(dim_product_normalized -> dim_category)"]
    Q --> O["OBT: 0 JOIN\nGROUP BY directo\nsobre mart_daily_sales_obt"]

Profundización: por qué el almacenamiento columnar cambió el cálculo clásico

El argumento clásico contra la denormalización —el que domina la enseñanza de bases de datos relacionales desde hace décadas— dice, en esencia: "denormalizar duplica datos, y duplicar datos cuesta espacio de disco y riesgo de inconsistencia". Ese argumento sigue siendo válido en su forma —la lección 6 de este módulo lo va a confirmar con números—, pero nació en una era de almacenamiento orientado a filas (row-oriented), donde cada fila de una tabla se guarda físicamente junta, byte tras byte, en el disco. En ese mundo, repetir "Kiosko Centro" como texto en cada fila de una tabla ancha efectivamente ocupa espacio adicional, proporcional al número de repeticiones.

El almacenamiento columnar —Parquet, y los motores que lo usan como formato base, incluido DuckDB— cambia ese cálculo de forma importante. En un formato columnar, cada columna se comprime por separado, y una columna con pocos valores distintos repetidos muchas veces —como store_name, con solo tres valores posibles repetidos en decenas de filas— comprime extremadamente bien con técnicas como dictionary encoding (guardar cada valor único una sola vez, y reemplazar cada aparición por una referencia corta a ese valor) o run-length encoding (guardar "este valor se repite N veces seguidas" en vez de escribirlo N veces). Vas a ver esto con evidencia literal, no solo como afirmación, en la lección 5: a la escala de juguete de Kiosko, el archivo Parquet de la tabla ancha completa puede llegar a pesar menos que la suma de las cuatro tablas normalizadas del star — un resultado que contradice la intuición clásica, y que la lección 5 va a explicar con precisión por qué ocurre a esta escala, y por qué el patrón se revierte a escala de producción.

Esto es, precisamente, lo que reporta el benchmark de Fivetran citado en el diseño de esta guía: sobre datos reales en Redshift, Snowflake y BigQuery —motores columnares de producción, no un dataset de juguete—, una tabla ancha denormalizada resultó entre 25% y 50% más rápida en consultas típicas de BI, a costa de 2 a 3 veces más espacio de almacenamiento. El costo de espacio sigue existiendo a escala de producción —la compresión columnar lo reduce, no lo elimina—, pero la ganancia de velocidad, para el patrón de consulta correcto, es real y medida, no una promesa de marketing. El artículo de dataarchitect.studio, también citado en el diseño de esta guía, resume el argumento con una recomendación concreta que vale la pena adoptar como principio de esta lección: mantén un star schema como tu modelo central —el que sostiene flexibilidad, gobierno y la capacidad de responder preguntas que todavía no conoces—, y construye la tabla ancha encima de ese star, como una capa de servicio para los consumidores que sí conocen sus preguntas de antemano y las repiten con frecuencia. No es "star o OBT" — es "star, y opcionalmente OBT encima, para el caso de uso que lo justifique". La lección 5 construye exactamente esa capa, sobre el star que ya existe, sin reemplazarlo.

Errores comunes

Concluir que "la compresión columnar hace que la denormalización ya no cueste nada". Qué pasa: alguien, después de leer que Parquet comprime bien los valores repetidos, concluye que el argumento clásico contra la denormalización ya no aplica en absoluto, y que normalizar es simplemente una pérdida de tiempo en un mundo columnar. Por qué pasa: el hallazgo real —"la compresión columnar reduce el costo de la duplicación"— es fácil de exagerar hasta "elimina el costo por completo". Cómo detectarlo: si tu conclusión de esta lección es "nunca hay que normalizar nada", te falta la lección 6 — el benchmark de Fivetran, citado arriba, sigue reportando 2-3x más almacenamiento con OBT, incluso en motores columnares de producción. La compresión reduce el costo; no lo hace desaparecer. Cómo corregirlo: sostén las dos ideas a la vez — la compresión columnar cambió el cálculo respecto a la era de almacenamiento por filas, pero el costo de espacio (y el costo de mantenimiento que la lección 6 va a medir) sigue siendo real.

Pensar que esta lección recomienda reemplazar el star por la OBT. Qué pasa: alguien lee el argumento de esta lección y entiende que la guía está a punto de abandonar el star schema del módulo 2 en favor de la tabla ancha, tratando este módulo como una corrección de rumbo. Por qué pasa: presentar un argumento a favor de la OBT, después de dos lecciones enteras sobre normalización, puede sentirse como un cambio de opinión de la guía misma. Cómo detectarlo: si esperas que el módulo 4 (SCD) historice mart_daily_sales_obt en vez de dim_product, tienes esta confusión — ya se advirtió en la lección 1 de este módulo, y vale la pena repetirlo aquí. Cómo corregirlo: recuerda el argumento de dataarchitect.studio citado arriba — el star sigue siendo el modelo central; la OBT es una capa de servicio construida encima, para un consumidor específico (el equipo de BI de Kiosko), no un reemplazo.

Ignorar que el argumento depende del patrón de consulta, no de una propiedad universal de "lo ancho". Qué pasa: alguien generaliza el argumento de esta lección más allá de su alcance real, asumiendo que cualquier tabla ancha, para cualquier propósito, es automáticamente una buena idea. Por qué pasa: el argumento se presenta con ejemplos concretos y convincentes, y es fácil perder de vista la condición que lo sostiene. Cómo detectarlo: si no puedes nombrar, para un caso hipotético de tabla ancha, quién la consulta y con qué frecuencia repite el mismo patrón, no tienes la información necesaria para justificarla. Cómo corregirlo: la analogía del restaurante de esta lección lo deja explícito — la OBT gana cuando el patrón de consulta es conocido y repetido con frecuencia (el menú de diez platos más pedidos); pierde cuando el patrón es impredecible (el menú a la carta). La lección 7 va a mostrar el primer caso con evidencia; la lección 6, el segundo.

Ejercicios

Ejercicio 1 — Cuenta los JOIN para una pregunta distinta. Sin mirar el ejemplo trabajado, cuenta cuántos JOIN necesitaría, bajo el star y bajo la OBT, la pregunta "revenue total por producto, sin ningún otro desglose" (la misma pregunta del ejercicio 2 de la lección 7 del módulo 2).

Ver solución

Bajo el star: 1 JOINfact_orders unido a dim_product es suficiente, porque product_name (o category, si la pregunta lo pidiera) vive directamente en esa tabla. Bajo la OBT: 0 JOINmart_daily_sales_obt ya trae product_name como columna propia, así que un GROUP BY product_name directo resuelve la pregunta sin ningún JOIN. Esta pregunta, al ser más simple que la del ejemplo trabajado (un solo desglose, no tres), muestra una diferencia más pequeña entre star y OBT (1 salto contra 0) que la pregunta con tres desgloses del ejemplo trabajado (3 saltos contra 0) — el argumento de la OBT se hace más fuerte cuantos más desgloses simultáneos necesita una pregunta típica del equipo de BI.

Ejercicio 2 — Explica la analogía del restaurante con tus propias palabras. Usando la analogía de esta lección (comida rápida con un menú conocido y repetido vs. restaurante a la carta), explica en 2-3 frases por qué la OBT gana en el primer caso y pierde en el segundo.

Ver solución

En el restaurante de comida rápida, la mayoría de los clientes pide uno de los mismos diez platos, así que preparar esos ingredientes de antemano ahorra tiempo en cada pedido — el costo de "cocinar de antemano" se paga muchas veces, porque el mismo patrón se repite constantemente. En el restaurante a la carta, cada cliente pide algo distinto e impredecible, así que semi-preparar todo de antemano desperdiciaría ingredientes que nadie termina pidiendo en esa combinación exacta — ahí conviene cocinar cada plato desde cero, con flexibilidad, cuando llega el pedido. La OBT es la cocina de comida rápida: gana cuando el patrón de consulta es conocido y se repite con frecuencia (un tablero de BI que siempre pregunta lo mismo); el star es el restaurante a la carta: gana cuando las preguntas son impredecibles y cambian con cada análisis nuevo.

Ejercicio 3 — Explica, de memoria, la recomendación de dataarchitect.studio citada en esta lección. Sin releer la sección de profundización, escribe en 2-3 frases la recomendación concreta que el artículo de dataarchitect.studio ofrece sobre cómo conviven el star schema y la OBT en un warehouse real, no como alternativas excluyentes.

Ver solución

La recomendación es mantener el star schema como el modelo central del warehouse —la fuente de flexibilidad, gobierno y capacidad de responder preguntas nuevas que todavía no se conocen—, y construir la tabla ancha (OBT) encima de ese star, como una capa de servicio adicional para los consumidores que sí tienen un patrón de consulta conocido y repetido con frecuencia, como un tablero de BI de alto tráfico. No es una elección de "star o OBT" sino una arquitectura en capas: el star como base, la OBT como una vista materializada y denormalizada construida a partir de esa base, para el caso de uso específico que la justifica.

Resumen y siguiente paso

Esta lección construyó el argumento que sostiene la próxima: por qué la tabla ancha —One Big Table— dejó de ser, en un mundo de almacenamiento columnar comprimido, un atajo descuidado, y se convirtió en una decisión de diseño legítima para el patrón de consulta correcto. Contaste, en números concretos, cuántos JOIN le ahorra la OBT a una pregunta típica de BI (tres bajo el star, cuatro bajo el snowflake, cero bajo la OBT), y entendiste por qué la compresión columnar cambia —sin eliminar— el costo clásico de la denormalización.

Antes de avanzar deberías poder: explicar, con tus propias palabras, por qué el almacenamiento columnar cambió el cálculo clásico de "normalizar siempre ahorra espacio"; recitar la recomendación de dataarchitect.studio sobre cómo conviven el star y la OBT; y nombrar la condición (patrón de consulta conocido y repetido) bajo la cual la OBT gana.

La lección 5 deja de argumentar y empieza a construir: mart_daily_sales_obt, la tabla ancha real de Kiosko, con quince columnas, cero JOIN pendientes, y el mismo revenue de siempre.

Recursos