Estructura de Modelos en dbt: Marts, Capa Dimensional y Materializaciones (View vs Table)
Diseñar modelos en dbt a escala empresarial no consiste simplemente en transformar SQL. La verdadera decisión arquitectónica está en definir contratos de datos, separar responsabilidades, construir una capa marts dbt consumible y elegir correctamente entre view, table e incremental según volumen, frecuencia de consulta, coste y criticidad.
En esta guía de nivel Senior / Staff / Architect construiremos un patrón de modelado dimensional con fact tables dbt, dimensiones, ref(), materializaciones y controles orientados a producción.
Lo que aprenderás
El objetivo es pasar de un proyecto dbt organizado por archivos SQL a una arquitectura de transformación explícita, gobernable y preparada para crecer.
Stack tecnológico
El patrón es independiente del warehouse, aunque las decisiones físicas de rendimiento deben adaptarse al motor analítico utilizado.
Matriz de decisión arquitectónica
La materialización no debería elegirse por costumbre. Debe responder al patrón de acceso, al tamaño del modelo, a la frecuencia de ejecución y al coste computacional.
| Patrón | Persistencia | Latencia de consulta | Coste de construcción | Uso recomendado |
|---|---|---|---|---|
| View | Definición lógica | Depende de la complejidad | Bajo durante el build | Transformaciones ligeras y modelos de bajo volumen de consumo. |
| Table | Datos persistidos | Generalmente predecible | Mayor durante el build | Marts, dimensiones y modelos consultados frecuentemente. |
| Incremental | Datos persistidos parcialmente actualizados | Generalmente predecible | Optimizable según estrategia | Fact tables grandes y cargas donde recalcular todo sería innecesariamente caro. |
| Ephemeral | No crea una relación persistente independiente | Depende del SQL final | Se incorpora al modelo consumidor | Lógica intermedia reutilizable que no necesita ser consultada directamente. |
Arquitectura recomendada: Staging → Intermediate → Marts
Una arquitectura mantenible separa la normalización de fuentes, la lógica de transformación y la interfaz final de consumo.
Normalización de fuentes
El objetivo es convertir fuentes operacionales en una representación coherente y predecible para las capas posteriores.
- Renombrado consistente.
- Tipado y casting.
- Normalización de timestamps.
- Eliminación de ruido técnico.
Lógica de transformación
Aquí se encapsulan joins, deduplicaciones, enriquecimientos y reglas complejas que no deberían contaminar los marts.
- Deduplicación.
- Construcción de entidades.
- Reglas de negocio reutilizables.
- Agregaciones intermedias.
Interfaz analítica
La capa marts expone modelos orientados al negocio y optimizados para sus consumidores.
- Dimensiones.
- Fact tables.
- Métricas y KPIs.
- Contratos de consumo.
Capa dimensional: dimensiones y fact tables
El error más frecuente es diseñar los marts como simples copias de las fuentes. Un modelo dimensional debe partir de una pregunta analítica y, sobre todo, de una granularidad definida.
Ejemplo de definición de granularidad
Supongamos que construimos fct_orders. Antes de escribir SQL debemos poder completar esta frase:
“Cada fila de fct_orders representa exactamente una línea de pedido confirmada.”
Esa decisión determina las claves, los joins, los agregados y las pruebas de calidad. Si una dimensión contiene múltiples registros para la misma clave durante el join, la fact table puede sufrir multiplicaciones silenciosas de filas.
Antipatrones críticos y FinOps
En producción, muchos problemas atribuidos al warehouse son realmente problemas de diseño del grafo de transformación.
Utilizar views para absolutamente todos los modelos puede parecer barato porque el build de dbt es ligero. Sin embargo, una view compleja puede trasladar el coste al momento de cada consulta.
Si un mart es consultado cientos o miles de veces al día y contiene joins, ventanas, deduplicaciones o agregaciones costosas, el coste computacional puede repetirse innecesariamente.
La solución no es convertir todo en tables. La solución es medir el patrón de consumo y seleccionar la materialización adecuada.
Regla arquitectónica: materializa como table los modelos que funcionan como interfaces analíticas estables y son consultados repetidamente; reserva view para lógica ligera o casos donde la persistencia no aporta suficiente valor.
ref(): el contrato entre modelos
La función ref() es una de las piezas fundamentales de dbt porque expresa dependencias entre modelos sin acoplar el SQL a nombres físicos.
Por qué evitar referencias físicas
Un SQL como FROM analytics.raw_orders acopla el modelo al nombre y al entorno físico de una tabla.
En cambio, FROM {{ ref('stg_orders') }} declara que el modelo actual depende de stg_orders.
Esto permite a dbt construir el grafo de dependencias y resolver las relaciones de forma coherente con el entorno de ejecución.
Implementación práctica de producción
El siguiente patrón separa staging, dimensión y fact table, y utiliza ref() para mantener el lineage explícito.
{{
config(
materialized='table',
tags=['marts', 'core']
)
}}
with orders as (
select *
from {{ ref('stg_orders') }}
),
customers as (
select
customer_id,
customer_key,
customer_segment
from {{ ref('dim_customers') }}
),
order_lines as (
select
order_id,
order_line_id,
customer_id,
product_id,
ordered_at,
quantity,
unit_price,
quantity * unit_price as gross_amount
from {{ ref('int_order_lines') }}
),
final as (
select
ol.order_id,
ol.order_line_id,
c.customer_key,
ol.product_id,
ol.ordered_at,
ol.quantity,
ol.unit_price,
ol.gross_amount
from order_lines ol
inner join customers c
on ol.customer_id = c.customer_id
)
select *
from final
Dónde colocar la configuración
La configuración puede declararse en el propio modelo o centralizarse mediante archivos de configuración del proyecto. Para equipos grandes, la consistencia de convenciones suele ser más importante que una única estrategia universal.
version: 2
models:
- name: fct_orders
description: >
Una fila por línea de pedido confirmada.
columns:
- name: order_line_id
description: Clave única de la línea de pedido.
data_tests:
- not_null
- unique
- name: customer_key
description: Clave dimensional del cliente.
data_tests:
- not_null
- name: gross_amount
description: Importe bruto de la línea.
data_tests:
- not_null
Patrones de diseño y mejores prácticas
Una arquitectura dbt madura no busca únicamente que el SQL funcione. Busca que el sistema pueda evolucionar sin convertir cada cambio en una operación de alto riesgo.
1. Contratos claros
Cada modelo de marts debe tener una responsabilidad concreta, nombres estables, granularidad documentada y columnas con significado empresarial.
2. Dependencias explícitas
Utiliza ref() para modelos internos y evita referencias físicas que oculten dependencias dentro del grafo.
3. Separación de responsabilidades
No conviertas un único modelo en un monolito de 1.500 líneas. Divide lógica compleja cuando exista una frontera semántica real.
4. Grain first
Define la granularidad antes de realizar joins. La mayoría de los errores graves de fact tables proceden de duplicaciones no detectadas.
5. Materialización basada en evidencia
Mide volumen, frecuencia de consulta, coste y SLA antes de decidir entre view, table o incremental.
6. Gobernanza integrada
Tests, documentación, ownership, naming conventions y lineage deben formar parte de la definición del modelo, no ser tareas posteriores.
Cuándo escalar hacia incremental
Una fact table que crece continuamente puede convertirse en el principal consumidor de recursos del proyecto si se reconstruye completamente en cada ejecución.
La pregunta correcta
No preguntes únicamente: “¿Esta tabla es grande?”
Pregunta: “¿Puedo identificar de forma fiable qué registros nuevos o modificados necesito procesar sin comprometer la corrección histórica?”
Una estrategia incremental requiere conocer las características de actualización de la fuente, la disponibilidad de una clave o timestamp adecuado, la posibilidad de late-arriving data y la estrategia de corrección de registros históricos.
Framework de implementación paso a paso
Este checklist permite introducir una arquitectura de marts sin empezar por una refactorización masiva.
Define el dominio
Determina qué área de negocio representa el mart: ventas, clientes, producto, finanzas, operaciones, marketing, etc.
Define la granularidad
Escribe explícitamente qué representa una fila de cada fact table y dimensión. Esta decisión debe preceder a los joins.
Normaliza staging
Limpia nombres, tipos, timestamps y convenciones sin introducir todavía reglas de negocio complejas.
Extrae lógica intermedia
Aísla deduplicaciones, joins complejos y transformaciones reutilizables en modelos intermedios.
Construye dimensiones
Define claves, atributos, estrategia histórica y reglas de unicidad antes de conectar las dimensiones con los hechos.
Construye fact tables
Usa la granularidad definida y controla explícitamente la cardinalidad de cada join.
Selecciona materialización
Comienza con una decisión simple y evoluciona hacia table o incremental cuando los datos de consumo lo justifiquen.
Añade observabilidad
Mide duración de builds, volumen procesado, fallos de tests, coste computacional y frecuencia de consumo.
Documenta el contrato
Documenta propósito, owner, granularidad, columnas críticas, SLA y consumidores principales.
Preguntas frecuentes sobre modelos en dbt
Respuestas orientadas a las decisiones que aparecen con mayor frecuencia al diseñar una arquitectura dbt empresarial.
¿Cuándo conviene usar table frente a view en dbt?
Una table suele ser preferible cuando el modelo se consulta frecuentemente, contiene transformaciones costosas o actúa como una interfaz estable para consumidores analíticos. Una view puede ser adecuada cuando se necesita lógica ligera, baja latencia de despliegue y se acepta ejecutar la transformación durante la consulta.
¿Por qué se recomienda utilizar ref() en los modelos de dbt?
ref() crea una referencia declarativa entre modelos, permite construir el grafo de dependencias y evita acoplar el SQL a nombres físicos de tablas generados por un entorno concreto.
¿Cómo debería estructurarse una capa marts en dbt?
Una capa marts debería exponer modelos orientados al consumo, normalmente organizados alrededor de hechos y dimensiones. La lógica compleja debería dividirse en capas intermedias para mantener contratos claros, reutilización, trazabilidad y menor acoplamiento.
