Tema representativo de entrevista

Entrevista de Ingeniería de Datos: ¿Cómo Diseñas un Modelo Incremental Confiable en dbt?

DatosDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Cuando un conjunto de datos sigue creciendo y un recálculo completo diario resulta demasiado costoso, ¿cómo diseñaría un modelo incremental confiable en dbt?

Prompt y Alcance

El entrevistador le presenta una tabla de eventos que recibe escrituras continuas y le pide construir una tabla de hechos diaria. La primera ejecución puede escanear el historial, pero las ejecuciones posteriores deben procesar únicamente los datos modificados. Los eventos pueden llegar tarde, actualizarse o duplicarse, y las columnas del modelo pueden cambiar. Explique el límite del filtro, la clave única, la estrategia incremental, el plan de backfill y la recuperación tras fallos.

Asuma que el data warehouse soporta SQL, que el destino está agregado por date_day y que cada evento tiene event_at, updated_at y un event_id estable. Declare estas suposiciones al inicio. Sin una clave estable, la semántica de actualización y la deduplicación cambian.

Qué Evalúa el Entrevistador

El entrevistador quiere ver si usted puede reducir los datos escaneados sin sacrificar la corrección. Una respuesta básica menciona is_incremental() y un filtro de marca de tiempo (timestamp). Una respuesta sólida explica por qué el filtro debe cubrir las llegadas tardías, por qué el agregado necesita un unique_key que coincida con su granularidad y cuándo es obligatorio --full-refresh.

También están evaluando si usted separa tres riesgos: omitir eventos tardíos, escribir filas duplicadas en la granularidad del negocio y mezclar lógica antigua y nueva después de que cambia una transformación histórica. El material actual de entrevistas de ingeniería de datos trata los modelos incrementales, snapshots y grafos de dependencias como temas prácticos de preparación; esta pregunta combina SQL, modelado de datos y gobernanza de ejecución.

Preguntas de Aclaración Antes de Responder

¿Cuál es la granularidad del negocio?

Si una fila representa un día, date_day puede ser la clave única. Si una fila representa un usuario y un día, la clave debe ser (user_id, date_day). La granularidad cambia la condición de merge, las comprobaciones de duplicados y el costo del backfill.

¿Con qué retraso pueden llegar los datos?

Si los eventos normalmente no tienen más de dos días de retraso, recalcule los tres días más recientes. Si no hay un límite superior útil, una ventana fija es insuficiente; utilice una marca de agua (watermark), reparación de particiones o una reconciliación completa periódica. Una ventana debe cubrir la distribución de retraso aceptada.

¿Qué campo representa una actualización aguas arriba (upstream)?

event_at es el tiempo de negocio, mientras que updated_at es el momento de la última modificación. Filtrar solo por event_at omite un evento antiguo que se corrigió más tarde. Prefiera un updated_at confiable o una secuencia de cambios y verifique que el origen no retroceda en el tiempo.

¿Cómo se publican los cambios de modelo o columna?

Agregar una columna, eliminar una columna y cambiar la lógica de cálculo requieren tratamientos diferentes. Confirme si on_schema_change puede sincronizar la estructura, si las filas antiguas deben completarse mediante backfill y si se puede programar un full refresh.

Marco de Respuesta en 30 Segundos

“Primero confirmaría la granularidad de destino y el límite de retraso. La primera ejecución construye el modelo a partir de todo el historial. Las ejecuciones posteriores utilizan is_incremental() para filtrar por updated_at y mirar hacia atrás a través de una ventana de retraso. El destino declara un unique_key que coincide con su granularidad, de modo que los días recientes se actualizan en lugar de agregarse como duplicados. Deduplico la ventana por la versión del evento antes de agregar y luego escribo con merge o el equivalente del data warehouse. Pruebo la ventana, la clave y el comportamiento del cambio de esquema. Si los cambios lógicos hacen que los resultados históricos sean inconsistentes, ejecuto un --full-refresh controlado y reconstruyo los modelos afectados aguas abajo (downstream). Finalmente, monitoreo las filas procesadas, el tiempo máximo de actualización, las claves duplicadas y las diferencias entre la ejecución incremental y la completa”.

Análisis Detallado Paso a Paso

1. Definir una línea base de corrección con full-refresh

Escriba primero la consulta completa: lea todos los eventos y agréguelos a la granularidad de destino. Esa es la línea base de corrección. La salida incremental debe conciliar con un cálculo completo durante el mismo rango de tiempo. Optimizar antes de establecer esta línea base dificulta la detección de registros omitidos.

2. Elegir el límite del filtro incremental

Una rama incremental se aplica solo cuando la tabla de destino existe, --full-refresh está ausente y el modelo está configurado como incremental. Una opción es restar una ventana de retraso del tiempo máximo de actualización del destino:

sql
{{
  config(
    materialized = 'incremental',
    unique_key = ['date_day'],
    incremental_strategy = 'merge'
  )
}}

with source_events as (
  select *
  from {{ ref('app_events') }}
  {% if is_incremental() %}
    where updated_at >= (
      select coalesce(max(updated_at), '1900-01-01') from {{ this }}
    ) - interval '3 day'
  {% endif %}
)
select
  cast(event_at as date) as date_day,
  count(distinct event_id) as events,
  max(updated_at) as max_updated_at
from source_events
group by 1

El valor de tres días es una suposición de entrevista, no una constante universal. Elija la ventana a partir del retraso, el SLA y el costo de recálculo. Adapte la expresión de fecha al data warehouse.

3. Hacer que la clave única coincida con la granularidad del modelo

Para una tabla diaria, date_day es la clave. Para una tabla de usuario-día, use ['user_id', 'date_day']. Las columnas clave no deben contener valores nulos, o un merge puede fallar al coincidir y crear duplicados. Sin una clave, muchos adaptadores se comportan como append-only, por lo que recalcular una ventana puede escribir múltiples filas para una sola granularidad.

4. Deduplicar dentro de la ventana antes de la agregación

La reproducción o las actualizaciones CDC pueden producir varias versiones de un mismo evento. Ordene por event_id y el tiempo de actualización, conserve la versión más reciente y luego agregue:

sql
with ranked_events as (
  select
    *,
    row_number() over (
      partition by event_id
      order by updated_at desc, ingest_seq desc
    ) as rn
  from source_events
),
deduped_events as (
  select * from ranked_events where rn = 1
)
select
  cast(event_at as date) as date_day,
  count(*) as events,
  max(updated_at) as max_updated_at
from deduped_events
group by 1

Use ingest_seq solo si es un criterio de desempate estable para marcas de tiempo iguales. De lo contrario, clasifique la regla de desempate como un contrato de origen no resuelto. La deduplicación debe preceder a la agregación, o ambas versiones de un evento se contarán.

5. Elegir merge, partition overwrite o append

merge se adapta a la semántica de actualización e inserción indexada por una granularidad. Una carga de trabajo de recálculo por partición puede usar insert_overwrite, que se basa en particiones en lugar de claves de fila. El simple append es más sencillo cuando los eventos aguas arriba nunca cambian. Elija a partir de la semántica de actualización, el costo de escaneo y el soporte del adaptador en lugar de tratar una sola estrategia como universal.

6. Gestionar cambios de esquema y de lógica

Agregar una columna no necesariamente rellena las filas antiguas; las columnas eliminadas y los cambios de tipo pueden manifestarse solo en tiempo de ejecución. on_schema_change puede ser ignore, fail, append_new_columns o sync_all_columns, pero solo rastrea columnas de nivel superior y no reemplaza un backfill histórico. Si cambia la lógica de cálculo, el historial antiguo y el nuevo pueden seguir reglas diferentes, por lo que debe ejecutar --full-refresh y reconstruir los modelos incrementales aguas abajo afectados.

7. Diseñar el backfill y la recuperación ante fallos

Registre la ventana de retraso, el tiempo máximo de actualización del destino y la marca de agua de origen en los metadatos de ejecución. Después de una ejecución de ventana fallida, recalcule a partir de la última marca de agua confirmada del destino; no trate un valor en memoria de "procesado hasta" como verdad. Para reparaciones amplias, procese particiones de fechas con concurrencia acotada y luego concilie muestras contra una consulta completa para que una sola actualización no sobrecargue el data warehouse.

8. Cerrar el ciclo de verificación

Verifique al menos cuatro señales: cada event_id aparece como máximo una vez en la ventana; las claves de destino son únicas; la salida incremental reciente se mantiene dentro de una diferencia permitida respecto a un recálculo completo; y las filas procesadas junto con el updated_at máximo no dan saltos inesperados. Pruebe entradas vacías, eventos duplicados, actualizaciones de eventos antiguos, llegadas tardías, marcas de tiempo iguales al límite y una ejecución incremental después de un full refresh.

Respuesta de Ejemplo de Alta Calidad

“Primero confirmaría la granularidad del modelo, el límite de retraso y el campo de actualización de origen. Asuma que el destino tiene una fila por día y los eventos tienen event_id y updated_at estables. La primera ejecución construye a partir del historial; las ejecuciones posteriores utilizan is_incremental() y miran hacia atrás tres días desde el tiempo máximo de actualización del destino. Esa ventana se deriva del retraso, no de una regla fija.

Dentro de la ventana deduplico por ID de evento y versión, y luego agrego por día. El destino declara date_day como su unique_key y usa merge para que los días recientes se reemplacen en lugar de duplicarse. Para una granularidad de usuario-día utilizaría una clave compuesta. Filtrar solo por tiempo de evento omitiría correcciones posteriores de eventos antiguos, por lo que preferiría una marca de tiempo de actualización confiable o una secuencia de cambios.

Monitorearía el tamaño de la ventana, la marca de agua, las filas procesadas, las claves duplicadas y la reconciliación incremental frente a la completa. Una configuración de cambio de esquema puede manejar la evolución estructural, pero no rellena los valores históricos. Si la lógica cambia o el historial necesita reparación, ejecutaría un full refresh controlado o un backfill particionado y reconstruiría los modelos aguas abajo afectados. Probaría entradas vacías, eventos tardíos y duplicados, marcas de tiempo límite y el comportamiento de reintento antes de considerar que el modelo es confiable”.

Errores Comunes

Filtrar solo por event_at → se omiten actualizaciones antiguas → use updated_at o una marca de agua CDC explícita

El tiempo de negocio no cambia cuando se corrige un evento antiguo. Si se permiten actualizaciones, filtre por tiempo de actualización o secuencia de cambios y verifique su contrato.

Hacer merge sin una clave → las filas no se pueden emparejar de manera confiable → defina primero la granularidad y las claves no nulas

Una clave debe identificar exactamente una fila de destino. Si la granularidad es usuario-día, usar solo la fecha fusiona a diferentes usuarios en una sola fila.

Asumir que una nueva columna se completa automáticamente mediante backfill → los valores históricos permanecen vacíos → planifique un backfill o full refresh

La sincronización del esquema y la carga de datos históricos son procesos separados. Un cambio estructural puede ser ligero, pero las filas antiguas pobladas requieren una actualización o reconstrucción explícita.

Elegir una ventana fija de una hora → los datos tardíos quedan fuera → calibre con percentiles y reconciliación

Elija la ventana según la distribución de retraso, el SLA y el costo. Monitoree las llegadas fuera de la ventana y amplíe o repare cuando la distribución cambie.

Preguntas de Seguimiento y Respuestas

Si el 5% de los eventos llega con dos días de retraso, ¿cómo elegiría la ventana?

Primero confirme el retraso de frescura aceptado. Si un reporte diario puede corregirse al día siguiente, cubra de dos a tres días y concilie los eventos tardíos. Si los resultados del primer día deben ser estables, use una marca de agua más una cola de reparación en lugar de depender únicamente de una ventana SQL más grande. Valide la elección contra las curvas de retraso y costo en lugar de limitarse a copiar el porcentaje.

¿Qué sucede si unique_key está duplicado en el origen?

Las claves duplicadas en la entrada incremental o en el destino pueden hacer que un adaptador falle o produzca resultados indefinidos. Compruebe la unicidad en ambos lugares, identifique el origen de los duplicados, deduplique por versión de evento o redefina una clave compuesta que represente la verdadera granularidad. Un ID aleatorio no debe ocultar una clave de negocio inestable.

El SQL del modelo cambió, pero solo desea recalcular siete días. ¿Puede seguir ejecutándolo de forma incremental?

Solo si los resultados históricos no se ven afectados por la nueva lógica. Si el cambio afecta a todo el historial, una ejecución incremental de siete días crea una tabla con reglas mixtas. Ejecute un full refresh controlado o un recálculo particionado para el rango afectado y reconstruya los modelos aguas abajo.

La tabla aguas arriba fue truncada (truncated). ¿Cómo se recupera el modelo incremental?

Detenga el avance de la marca de agua, confirme la reconstrucción del origen y reproduzca desde una instantánea (snapshot) confiable o un punto CDC. Si el origen ya no cubre el historial requerido, una ejecución incremental trataría un origen incompleto como una línea base completa; restaure el snapshot o reconstruya el destino por completo.

Fuentes públicas

Preguntas relacionadas