A dbt core-t telepíthetjük lokálisan vagy használhatnánk a Docker konténerüket. Mivel az ő kedvükért sem akarjuk root-tal futtatni a Dockert, ezért saját konténert csinálunk saját user-rel, ami egyáltalán nem nehéz. Szokásosan Postgrest hozunk be adatbázisnak, amelyet egyetlen táblával és benne egyetlen rekorddal seed-elünk.
Induláskor így néz ki a mappánk:
$ tree -aF --dirsfirst
.
├── docker/
│ ├── dbt/
│ │ └── Dockerfile
│ └── postgres/
│ └── Dockerfile
├── docker-compose.yml
├── dump.sql
├── .env
├── Makefile
└── profiles.yml
Docker
Nézzük sorjában. A .env
fájlunk tartalma:
DB_PORT=5432
DB_DATABASE=dbt_test
DB_USER=root
DB_PASSWORD=root
DOCKER_USER=myuser
A docker/dbt/Dockerfile
tartalma:
FROM python:3.11.4-slim-bullseye
ARG DOCKER_USER
RUN apt-get update && apt-get install -y git
RUN pip3 install dbt-postgres
RUN useradd -m -u1000 -gusers ${DOCKER_USER}
USER ${DOCKER_USER}
WORKDIR /home/${DOCKER_USER}/dbt/
ENTRYPOINT ["/usr/local/bin/dbt"]
Az itteni WORKDIR-t fogjuk bindelni Compose-ban. Gitet csak azért telepítünk a konténerben, hogy ne panaszkodjon a dbt, használni biztosan nem ezt fogjuk.
A docker/postgres/Dockerfile
tartalma:
FROM postgres:12
RUN apt-get update && apt-get install -y less
ENV PAGER="/usr/bin/less -S"
CMD ["-c", "client_min_messages=warning"]
A docker-compose.yml
tartalma:
version: "3"
services:
postgres:
build: ./docker/postgres
restart: always
environment:
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
PGDATA: /var/lib/postgresql/data
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "${DB_PORT}:5432"
dbt:
build:
context: ./docker/dbt
args:
DOCKER_USER: ${DOCKER_USER}
restart: "no"
profiles: ["one-off"]
user: ${DOCKER_USER}
environment:
DB_PORT: ${DB_PORT}
DB_DATABASE: ${DB_DATABASE}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
volumes:
- .:/home/${DOCKER_USER}/dbt/
depends_on:
- postgres
volumes:
db-data:
A Makefile
-ba később bekerülhetnek a dbt parancsok is. Egyelőre ennyi:
include .env
.PHONY: help
help:
@echo "Task Runner. Usage:"
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-38s\033[0m %s\n", $$1, $$2}' Makefile
.PHONY: pg-shell
pg-shell: ## Open the psql shell.
docker-compose --file docker-compose.yml \
exec postgres psql -U ${DB_USER} -w ${DB_DATABASE}
.PHONY: seed
seed: ## Load the dump into the db.
@PGPASSWORD=${DB_PASSWORD} psql -hlocalhost -p${DB_PORT} -U${DB_USER} -fdump.sql ${DB_DATABASE}
Mintaadatok
seed.sql
:
create schema if not exists landing;
create schema if not exists staging;
drop table if exists landing.customer cascade;
create table landing.customer
(
id bigserial primary key,
email text not null,
username text not null,
activated boolean default false,
created_at timestamp default now() not null
);
insert into landing.customer (id, email, username, activated, created_at)
values (1, 'user1@example.com', 'user1', true, '2023-03-23 13:59:38');
Ezzel minden készen áll a teszt db elindítására:
$ docker-compose up -d
$ make seed
dbt mappák és fájlok
A db kapcsolatot a profiles.yml
definiálja, pg1
a kapcsolati profil általunk választott neve. A környezeti változókat dotenvből átadtuk a Docker-konténernek. A host a Compose-beli Postgres-szolgáltatásunk neve.
pg1:
send_anonymous_usage_stats: false
target: dev
outputs:
dev:
type: postgres
threads: 2
host: postgres
port: "{{ env_var('DB_PORT') | int }}"
dbname: "{{ env_var('DB_DATABASE') }}"
user: "{{ env_var('DB_USER') }}"
password: "{{ env_var('DB_PASSWORD') }}"
schema: staging
A fentiekből úgy tűnhet, hogy itt csak a kimenetről van szó, de ez a forrás definíciója is, leszámítva a sémát (mi most landing
-ből staging
-be töltünk). Jusson eszünkbe, hogy kizárólag transzformációról van szó, adatbázison belül; a dbt nem tud több külső forrást kezelni, mert nem az a feladata (hanem csak a T az ELT-ből). Felvehetünk viszont dev és prod környezetet is (ez a target az outputs listában), bár a gyakorlatban nem futtatnánk kézzel prodot, szóval nem lesz rá szükség. A default target-et viszont kötelező megadni. A dbt minden egyes futtatásnál hazatelefonál, amit a send_anonymous_usage_stats
kapcsol ki. Az erre való utalást csak véletlenül vettem észre a doskiban, lehetne transzparensebb az efajta működés egy szabad szoftverben (Apache-2.0).
Nekem elsőre picit zavarosnak tűnt néhány dolog, amit utólag a doski és az elnevezések inkonzisztenciáival magyaráznék. Nem tökéletes, de felfogható.
Hozzuk létre a dbt-projektünket (-s
= --skip-profile-setup
):
$ docker-compose run --rm dbt init -s dbt_test
Megjelenik egy dbt_test
mappa default tartalommal.
$ tree -aF -L 1 --dirsfirst dbt_test/
dbt_test/
├── analyses/
├── logs/
├── macros/
├── models/
├── seeds/
├── snapshots/
├── target/
├── tests/
├── dbt_project.yml
├── .gitignore
└── README.md
Nézzük először a projektet leíró dbt_project.yml
fájlt:
name: 'dbt_test'
version: '1.0.0'
config-version: 2
# This setting configures which "profile" dbt uses for this project.
profile: 'pg1'
# You probably won't need to change these!
# megj.: vajon miért nem egy paths objektum tulajdonságai ezek?
model-paths: ["models"]
analysis-paths: ["analyses"]
test-paths: ["tests"]
seed-paths: ["seeds"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]
# Configuring models
models:
# Be sure to namespace your model configs to your project name
# megj.: ez vajon minek?
dbt_test:
# Config indicated by + and applies to all files under models/example/
example:
+materialized: view
Ezen belül is a models szekciót, a modell konfigurációt. Az example helyére írjuk a célsémánk nevét: staging. A materializáció table vagy view lehet (esetleg incremental, ephemeral), view az alapértelmezett, és az megfelel a staging trafóknak.
Következzen a trafók leírása a models/
mappában. Először töröljük az example mappát, majd hozzuk létre a staging-et. A források tetszőleges nevű YAML-fájlokba kerülnek, hozzuk létre a models/staging/stg__sources.yml
fájlt:
version: 2
sources:
- name: landing
description: DW landing zone.
loader: Airbyte
schema: landing
tables:
- name: customer
description: Original customer table.
Alapértelmezetten a schema megegyezik a name-mel, nem is kéne szerepeltetni; fel kell sorolni viszont a forrástáblákat.
Itt is elhelyezhetünk modell konfigurációt (nem kötelező), amely kerülhetne akár a fenti YAML-fájlba, vagy egy külön tetszőleges nevűbe. Hozzuk létre most a models/staging/stg__models.yml
fájlt. Egyetlen forrástáblánk van, amint fentebb láttuk (landing.customer
), ezért itt is egyetlen modellt látunk (staging.stg_users
).
version: 2
models:
- name: stg_users
description: Customer table transformed.
columns:
- name: user_id
description: PK
tests:
- unique
- not_null
- name: email
description: email of the customer
tests:
- unique
- name: username
description: unique name of the customer
tests:
- unique
- not_null
Maguk a modellek (más néven: a transzformációk) SQL-lekérdezések, mégpedig nem DML, hanem SELECT utasítások, ami ügyes egyszerűsítés. Íme a models/staging/stg_users.sql
, amely valójában egy Jinja template:
with
-- like an import
source as (
select * from {{ source('landing', 'customer') }}
),
-- like a function def
final as (
select
-- renaming
id as user_id,
email,
username,
-- categorizing/bucketing
case when email is null then 0 else 1 end as has_email,
-- data normalization
case when activated = 'Y' then 1 else 0 end as is_active,
-- type casting
created_at::date
from source
)
-- like a function call
select * from final
dbt futtatása
Most értünk el a dbt kipróbálásához. Először teszteljük a profilunkat:
$ docker-compose run --rm \
dbt debug --project-dir dbt_test
Látnunk kell a környezetünk paramétereinek felsorolását, a végén egy zöld “All checks passed!” üzenettel.
Jelenlegi beállításainkkal innentől mindig szükség lesz a --project-dir dbt_test
argumentumra. Átírhatnánk a dbt konténerben a WORKDIR
-t, és akkor nem lenne rá szükség. Egyébként ennek a dbt parancs globális argumentumának kéne lennie, mert így kissé sután használható a dbt Docker-konténerben.
Jöhet maga a futás:
$ docker-compose run --rm \
dbt run --project-dir dbt_test
Itt egy “Completed successfully” üzenetet kell látnunk. Mit csinál e parancs? Létrehozza a modelleket, esetünkben a fent definiált staging.stg_users
view-t. Megnézhetjük egy lekérdezés eredményét a modell létrehozása nélkül is:
$ docker-compose run --rm \
dbt show --project-dir dbt_test -m stg_users
A fenti modell konfigurációban szerepeltek mező szintű tesztek. Emellett a tests/
mappában elhelyezhetünk lekérdezéseket is, amelyek akkor sikeresek, ha nincs eredményük. Ezeket mind meghívhatjuk:
$ docker-compose run --rm \
dbt test --project-dir dbt_test
Generálhatunk dokumentációt is egy statikus weboldal formájában a target/
mappába.
$ docker-compose run --rm \
dbt docs generate --project-dir dbt_test