Saltar al contenido

Oh, mi dbt (herramienta de creación de datos)

septiembre 23, 2021
1J6EKQFe2ez4FabzIA6E2mg

Oh, mi dbt (herramienta de creación de datos)

Mi experiencia y un par de notas sobre el uso de esta magnífica herramienta durante un mes.

Tomas Peluritis

12 de julio·10 min de lectura

Introducción

Toda mi vida estuve trabajando con datos. De alguna manera suena dramático cuando lo digo así. Básicamente, he hecho algunos análisis y trabajo básico con SQL como analista de negocios, pero nada donde necesitaría plantillas. La llamada carrera de BI comencé en 2013. Como consultor y trabajando principalmente con MSSQL en múltiples proyectos similares, hubiera sido una bendición tener algo como dbt (o al menos saber sobre Jinja en ese momento…); descartámoslo como falta de experiencia.

Gracioso que yo tried dbt solo ahora. Si soy honesto contigo, lo he estado usando durante ~ mes, así que ten en cuenta que no soy un profesional, solo estoy difundiendo el conocimiento y compartiendo lo que he encontrado. Puede encontrar muchos otros artículos medianos sobre algunos aspectos específicos o ir directamente a la fuente de dbt.

Prerrequisitos

En primer lugar, para que esto funcione, necesitaría Docker. Si no está familiarizado con la ventana acoplable, promocionaré la publicación de mi blog anterior que escribí hace algún tiempo al respecto. Cuando trabajo en entornos creados por Docker, prefiero usar VSCode con su opción de contenedor de desarrollo, donde básicamente crea un entorno aislado con todas mis configuraciones, montajes, etc. Si realiza algún cambio en la imagen de Docker existente, puede elegir el opción reconstruir imagen, la compondrá y la abrirá con todos los cambios. Muy útil si está desarrollando cosas para que pueda omitir manualmente la ventana acoplable-componer.

En mi imagen de la ventana acoplable, he creado un archivo de composición de ventana acoplable específico con dos componentes: postgres simple: 13-alpine y python 3.8. Al elegir Python 3.8.11 en lugar de 3.9: hubo algunos problemas al intentar instalar dbt debido a problemas de compatibilidad. También estoy usando la opción de montaje en mi archivo docker-compose para pasar el archivo profiles.yml adecuado para este proyecto específico.

Postgres Dockerfile:

FROM postgres:13-alpine
ENV POSTGRES_PASSWORD=nopswd
ENV POSTGRES_DB db
COPY init.sql /docker-entrypoint-initdb.d/

En el init.sql archivo, acabo de crear una base de datos llamada db.

Python Dockerfile:

FROM python:3.8
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

Nada sofisticado en requisitos, solo la biblioteca dbt.

Si ya tiene un entorno de producción con dbt y está configurando uno local, utilice siempre la misma versión de dbt que tiene en producción. Tuve problemas con la ejecución de dbt, pero mis colegas no. Causa raíz: todo el mundo estaba usando 0.19.0, e instalé la última versión en ese momento 0.19.2 y se produjeron algunos problemas de compatibilidad para dbt deps que teníamos en el archivo packages.yml.

Docker-compose, como mencioné, tiene algunas cosas más, pero nada sofisticado:

Quizás se pregunte por qué estoy abriendo el puerto 8001: es necesario para alguna función dbt que verá más adelante.

Empezando con dbt

Ok, ¿qué es este dbt? Quizás se esté preguntando. Básicamente, es una herramienta increíble para facilitar su parte de transformación en su flujo de ELT que le brinda linaje de datos, documentación y control total sobre las actualizaciones de datos si algunos datos subyacentes cambian en uno de los modelos en algún lugar intermedio. Realmente no quiero (y generalmente no me gusta) ir a los detalles del producto, ya que soy una persona más técnica, no un producto.

Ok, entonces hay un par de archivos importantes en dbt.

  • profiles.yml: archivo donde configura todas las conexiones y cómo las va a usar
  • dbt-project.yml: configuración específica para un proyecto dbt específico en el que tiene este archivo.

Repasemos el archivo profiles.yml:

1*fqeCmXbk79KrdIpy5lTZgQ

Tenemos que tener un perfil predeterminado; aquí será donde se ejecutará todo si no se especifica nada más. Los diferentes perfiles le permitirán probar fácilmente las canalizaciones en diferentes entornos (es decir, prueba y producción):

# Running on default:
dbt run
# Running on prod:
dbt run --profile prod
# Running on default with specified profile:
dbt run --profile default

Después de jugar en VSCode abriendo mi carpeta en el contenedor de desarrollo, es interesante ver si todo funciona según lo previsto.

1*OJGz0R0pcIQtgoZWKfVFZA

Como puede ver, tenemos un error en dbt_project.yml. Arreglemoslo.

Para simplificar y mantener la estructura dbt original, podemos inicializarlo. Para hacer esto, ejecutemos este comando:

dbt init MY_DBT_PROJECT_NAME

Ahora podemos ver cuál es la estructura que dbt nos espera y cómo funciona:

1*M6W scRvXbscTHXGzYK4Tw

Estructura del proyecto dbt inicializada. Imagen del autor

Comprobemos si todo lo demás funciona desde esta carpeta y los perfiles creados correctamente .yml

1*Tcb X9jN355qZ94qKtg76w

Resultados de depuración de dbt. Imagen del autor

¡Gran éxito! Nuestro entorno es completamente funcional y está listo para que podamos comprobarlo todo.

Intentemos ejecutar dbt en el perfil predeterminado:

dbt se ejecuta en el perfil predeterminado. Imagen del autor

Vemos que tenemos dos modelos (que corresponden a dos archivos llamados my_first_dbt_model.sql y my_second_dbt_model.sql), pero ¿qué son estas pruebas? ¿De dónde vienen? Profundicemos en la carpeta del modelo.

Podemos ver que tenemos el archivo schema.yml con contenido

1*xULYDGRDl8nFtp Sadtnyw

Archivo Schema.yml. Imagen del autor

Podemos ver que tenemos dos columnas descritas más pruebas: una columna tiene que ser única y no nula.

Descubrí que mis colegas están creando un archivo yml para cada modelo. En mi opinión, esta es una mejor opción:

  • visualmente se ve más claro
  • no hay conflictos de fusión porque, lo más probable, ¡habrá un desarrollador por modelo!

Si miramos las consultas, son sencillas. Crea una tabla con 1 y nulo, crea una vista de la primera tabla donde id = 1. Pero espere, nuestras pruebas no dijeron que fallamos. ¡Tenemos un valor nulo! Eso es porque no tiene ningún dato para probar. Entonces, después de ejecutar nuestro modelo, debemos probarlo.

Para ejecutar pruebas:

dbt test --model example

La salida en la consola se verá así:

Prueba fallida. Imagen del autor

Claramente, podemos ver que hay algunos problemas de nuestra parte y tenemos que solucionarlos.

La solución es sencilla. Cambiemos de nulo a algún número y probemos de nuevo. Seguiríamos viendo el mismo estado si ejecutáramos directamente «dbt test» después de la corrección. No ejecutamos el modelo, por lo que los datos subyacentes no cambiaron. Necesitamos ejecutarlo y probarlo.

ejecución de dbt y vista de prueba de dbt en la terminal. Imagen del autor

¡Hurra, acabamos de arreglar y ejecutar nuestros modelos con éxito!

Si ejecutamos dbt run tanto en dev / default como en prod, veríamos en DB todo esto

1*YDb1UFgp G9oZ87d4MqGTA

Vista de base de datos después de que dbt se ejecute de forma predeterminada y prod. Imagen del autor

detalles de dbt

Tras nuestro ejecutar dbt, teníamos esta carpeta creada. Su contenido:

Estructura de carpetas de destino. Imagen del autor

Para mí, los archivos interesantes están en el compilado / ejecutar directorio. Si bajamos por la madriguera del conejo, podemos encontrar nuestras consultas SQL analizadas.

También podríamos compilar nuestros archivos ejecutando:

dbt compile

Ejecutar crearía o actualizaría archivos en compilado y correr carpetas. También tendrá pruebas compiladas SQL, para que pueda comprender qué se estaba ejecutando en sus pruebas especificadas.

Si ocurre algún problema y no está del todo claro de qué se trata, consulte logs / dbt.log. es decir, en el trabajo, recibí el mensaje «Error de base de datos: permiso denegado para la base de datos X». No tengo ni idea de qué permisos me faltaban. Obtuve un enlace a la página de depuración de dbt y mi colega dijo que revisara los registros. A partir de ahí, encontré los permisos que me faltaban.

Un fragmento de archivo de registros. Imagen del autor

Imaginemos que tenemos una situación en la que nuestros datos que residen en la base de datos son grandes y queremos agregar una carga incremental. Genéricamente, haríamos un script si existe una tabla; créelo desde cero, de lo contrario, insértelo y (o) actualícelo. Básicamente, tenemos partes repetitivas de código y tenemos que mantenerlo en dos lugares. No cumple con DRY (Don’t Repeat Yourself). Afortunadamente, dbt tiene una característica sorprendente como una carga incremental. Para ello, crearemos una tabla fuente adicional usando Mockaroo. Ejecuté 01_mock_users_data.sql en mi base de datos local de Postgres. También hice un pequeño cambio y convertí la columna created_at para que fuera una columna de marca de tiempo en lugar de una fecha.

Creó un modelo simple para usar la macro is_incremental:

Si lo ejecutamos ahora y verificamos target / run:

create  table "db"."dbt_dev"."mock_users"
as (
select * from "db"."operational_db"."mock_users_data"
);

Corramos 02_more_mock_users_data.sql y vuelva a ejecutar dbt. ¡En target / run, podemos ver diferentes salidas!

select  * from "db"."operational_db"."mock_users_data"
-- this filter will only be applied on an incremental run
where created_at >= (select max(created_at) from "db"."dbt_dev"."mock_users")

Aunque hay un matiz aquí, se ejecutará exactamente mediante los filtros que especificó. La primera ejecución será para TODO el historial; la próxima ejecución será solo para filas nuevas. Es posible que la consulta inicial ni siquiera finalice o encuentre otros problemas en el camino (tiempo de espera, algunos límites estrictos en el tiempo de ejecución de la consulta, etc.). Por lo tanto, podría crear un filtro de límite superior en el que solo tomaría un par de días / semanas / mes y actualizarlo fácilmente de esta manera en varios lotes. Aunque es tedioso y tendrías que ejecutarlo manualmente para recuperarlo.

Descargo de responsabilidad: insert_by_period funciona solo con Redshift, dbt-vault created vault_insert_by_period funciona en Snowflake. Básicamente, solo estoy explicando mi viaje, lo que probé y comprobé a lo largo del camino.

Mencioné en la carga incremental «Macros», tal vez se pregunte qué es. Es un código personalizado, que se ejecuta para agregar alguna funcionalidad faltante o lógica más compleja. Es decir, antes mencionado una carga incremental tediosa. En nuestro caso, es una inserción condicional simple que cargaría nuestros datos iniciales en múltiples lotes. Puede consultarlo en la discusión original sobre esta macro aquí. Con todo, está incluido en el paquete dbt-utils. Podemos importar especificándolo en el archivo packages.yml. La versión 0.7.0 no era compatible con mi versión dbt de 0.19.2 (solicité 0.20, que es solo un candidato de lanzamiento en el momento en que se estaba escribiendo esta publicación de blog), así que usé 0.6.4.

1*epjmYPK7FSXfqR FAt1f7w

contenido de packages.yml. Imagen del autor

y podemos instalar dependencias con

dbt deps

Si seguimos toda la información de la versión para nuestro caso de uso de Postgres, no funcionará, ya que, como está escrito en los comentarios, ¡solo es adecuado para el desplazamiento al rojo! Después de esto, entré en la madriguera del conejo, verifiqué dbt-vault, hice algunos ajustes y creé mi propia macro usando comentarios en GitHub. Pero supongo que soy demasiado nuevo en macros, un tema avanzado, y no pude hacerlo funcionar. Tendré que profundizar en esto más tarde.

El nombre no explica realmente lo que hace. Al menos para mí, una instantánea significa el estado actual de los datos. Aunque en el caso de dbt, si creamos un modelo de instantánea (sugieren ponerlo en la carpeta «instantáneas»), tendremos SCD tipo 2 (por cierto, escribí un artículo sobre SCD2 en Spark hace algún tiempo, que cubre qué es un SCD).

Así que usemos los mismos datos de usuarios simulados para este ejemplo. Agreguemos la columna updated_at y hagamos que coincida con la columna created_at (03_update_at.sql). Sigamos el ejemplo básico de dbt docs y ejecutemos instantánea de dbt. Podemos ver cómo se ve la instantánea (solo interesado en columnas recién agregadas):

1*zX 3Vjzd1dkE4LsyUDYLVw

SCD tipo 2 de los datos de nuestros usuarios simulados. Imagen del autor

Podemos ver que tenemos dbt_scd_id y dbt_valid_from y dbt_valid_to, correspondiente a los cambios. Ejecutemos 04_change_some_names.sql y ejecutemos instantánea de dbt.

1*wDYYnabSHLwE1eYsawXDsA

SCD tipo 2. Imagen del autor

Bien, básicamente, configuramos lo que es único y dbt se encarga del resto. Eso habría sido útil muchas veces para mí. Mirando en la carpeta target / run / snapshots, podemos ver que nuestro código de instantánea también se generó para nosotros.

1*TMVI 6h5 NX8mNqD eGuKQ

Básicamente, podemos ver que creó una tabla temporal y luego hizo todas las comparaciones por nosotros.

Linaje de datos y …

close