ETLs Robustas

Table of Contents

1. ETLs Robustas

Me lo enseño koldLight en verano del 2020 + material propio

1.1. Desacopla tiempo de ejecución de la horquilla temporal

La fecha depende del locale de la máquina, y aunque uses UTC, siempre puede estar retrasada la base de datos y que el último dato sea más antiguo de lo que marca la hora UTC actual

Si la base de datos no está actualizada y si por ejemplo envías un informe pillando la fecha de la máquina, puede que los informes de ese día/semana/mes se envíen vacíos (depende de detalles internos de cómo está el código, puede fallar o puede que lo envíe vacío)
El problema de enviar informes vacíos es que tus usuarios tienen la ilusión de que se envía todo bien, pero como el 90% de los informes no se abren nunca lo llegan a verificar nunca y toca volver a generarlos

1.2. Hazlo idempotente

Que coja la ultima fecha de actualización del destino como fecha inicial y la última fecha de actualización de la fuente como fecha final, haciendo un floor al intervalo temporal que está considerando
Esto define una horquilla temporal
Así si se ejecuta 2 veces seguidas, la segunda vez no carga datos porque el destino está actualizado
Todo funciona bien mientras el propio proceso no tarde más de la granularidad de tiempo que está cogiendo (por ejemplo si agrupa cada 5 min no puede tardar más de 5 min)

1.2.1. Proceso alternativo y discusión consistencia vs latencia

En Data Pipelines Pocket Reference no tienen fecha final, lo que puede dar lugar a casos curiosos:

@startuml
clock   "Data Insertions" as C1 with period 5 pulse 1 offset 0
binary  "Data Hazard Window"  as B
robust  "Process"  as P
scale 300 as 360 pixels


@0
P is Idle

@300
P is Preparing
B is high

@320
P is Running
B is low

@350
P is Idle
@enduml

etl_timing1.png

Si suponemos que el proceso de extración de datos tarda un cierto tiempo en arrancar y coger las fechas de actualización de las bases de datos, en esa ventana pueden entrar datos que pertenezcan al periodo siguiente pero que se incluyan en el periodo actual ⇒ tenemos duplicados (si hacemos floor del tiempo con el intervalo correspondiente)
Con floor, tenemos duplicados porque los del periodo siguiente se asignan al periodo anterior
Con ceil, tenemos falta de datos porque los del periodo siguiente cuentan como del periodo siguiente siguiente,

La manera de resolverlo en el libro es escribir los últimos datos y sobreescribir los penúltimos datos por si se hubiese colado algun dato que pertenece al último periodo en vez de al penúltimo

  • La decisión entre los dos es un tradeoff entre consistencia y latencia (PACELC theorem):
    • En el primer método no puedes reducir el tiempo entre extracciones porque te cargas los datos si el proceso tarda más que el intervalo por el que agrupa (favorece consistencia porque nunca te va a dar datos de un periodo que no se ha completado)
    • En el segundo método creo que es más seguro reducir el tiempo entre extracciones todo lo que sea posible en términos de recursos de la máquina porque siempre tienes los datos más frescos y luego los vas corrigiendo. En cualquier caso, favorece liveness sobre consistencia

1.3. Haz un upsert para corregir histórico

Un upsert es un update + insert, si hay datos con esa primary key, entonces los actualiza, y si no hay nada, inserta

1.4. Al hacer joins por primary key, hazte un calendario con todas las fechas

En una query SQL, si haces un group by y no hay datos para esa fecha, no te devuelve un nulo con esa fecha, sino que no te devuelve nada para esa fecha.
Si haces un left join con un calendario + otras pkeys, te aseguras de que siempre hay registros, aunque sean nulos

1.5. Para qué sirve una ETL? Dos objetivos

  • Caché → simplemente calcular cierto proceso de datos (agrupación, limipeza de datos, normalización) para que esté disponible más rápidamente
  • Base de datos / Almacenamiento: Puede haber cosas que sólo guarda tu base de datos. Esto es critico, si falla la ETL no hay nada más que lo esté guardando, o es muy complicado reconstruirlo

Esta división se tiene que reflejar en la arquitectura, para poder actualizar la parte de “caché” sin cargarte la parte de “base de datos / almacenamiento” en el proceso

1.5.1. Distinguir entre datos históricos y mutables

Histórico
se pueden obtener los datos de hace X horas (la ETL se comporta como caché), opuesto a realtime o efímero (la ETL se comporta como base de datos / almacenamiento)
Mutable
no es un valor fijo en el tiempo. Los más interesantes son los datos históricos y mutables, pues para que la base de datos destino esté actualizada, habrá que recalcular todos los datos históricos mutables

1.6. Estructuras de datos típicas de ETLs

1.6.1. Intervalo de tiempo

Siempre tienes que tratar con fecha de inicio, fecha de final, intervalo, como por ejemplo estas funciones de pandas:

1.6.2. Rellenar todos los valores posibles

Esta función puede hacerse genérica. Por ejemplo (esta no es genérica, estamos trabajando en ello)

import pandas as pd
def fill_df_region(df):
    region_id = pd.DataFrame({"region_id": [x for x in config.REGION_IDS]})
    model = pd.DataFrame({"model": [x for x in config.REGION_MODELS]})
    df_index = pd.merge(region_id, model, how="cross")
    df_completo = df_index.merge(df, how="left", on=["region_id", "model"])

Author: Julian Lopez Carballal

Created: 2024-10-21 Mon 08:19