
Vilka principer bygger det perfekta datalagret pÄ?
Fokus pÄ affÀrsvÀrde och analys utan boilerplate-kod. Hantering av DWH som en kodbas: versionering, granskning, automatiserad testning och CI. Modularitet, skalbarhet, öppen kÀllkod och gemenskap. AnvÀndarvÀnlig dokumentation och visualisering av beroenden (Data Lineage).
Mer om allt detta och DBT:s roll i ekosystemet för Big Data & Analytics â vĂ€lkommen under kĂ„pan.
Hej alla
Artemiy Kozyr hÀr. Jag har arbetat med datalager i över 5 Är, jobbat med byggen av ETL/ELT, samt dataanalys och visualisering. Jag Àr för nÀrvarande verksam pÄ , undervisar pÄ OTUS i kursen , och idag vill jag dela med mig av en artikel som jag skrev inför starten av ett nytt urval för kursen.
Kort översikt
DBT-ramverket handlar om bokstaven T i akronymen ELT (Extract â Transform â Load).
Med framvĂ€xten av kraftfulla och skalbara analytiska databaser som BigQuery, Redshift, Snowflake, har det förlorat sin mening att utföra transformationer utanför datalagret.Â
DBT laddar inte ner data frÄn kÀllor, men ger enorma möjligheter att arbeta med de data som redan laddats in i lagret (i Internal eller External Storage).

DBT:s huvudsakliga syfte Àr att ta koden, kompilera den till SQL och köra kommandona i rÀtt ordning i lagret.
Strukturen för DBT-projektet
Projektet bestÄr av tvÄ typer av kataloger och filer:
- Modell (.sql) â en transformationsenhet uttryckt som en SELECT-frĂ„ga
- Konfigurationsfil (.yml) â parametrar, instĂ€llningar, tester, dokumentation
PÄ grundlÀggande nivÄ byggs arbetet upp pÄ följande sÀtt:
- AnvÀndaren förbereder kodmodeller i valfri bekvÀm IDE
- Modellerna startas med hjÀlp av CLI, DBT kompilerar kodmodellerna till SQL
- Den kompilera SQL-koden körs i lagret i angiven sekvens (graf)
SÄ hÀr kan en start se ut frÄn CLI:

Allt Àr SELECT
Det hĂ€r Ă€r en killer-funktion i Data Build Tool-ramverket. Med andra ord, DBT abstraherar all kod relaterad till materialisering av dina frĂ„gor i lagret (varianter av kommandona CREATE, INSERT, UPDATE, DELETE ALTER, GRANT, âŠ).
Varje modell innebÀr att skriva en SELECT-frÄga som definierar det resulterande datasaet.
Samtidigt kan logiken för transformationerna vara flervÄnings och konsolidera data frÄn flera andra modeller. Ett exempel pÄ en modell som bygger en bestÀllningsvisning (f_orders):
{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
with orders as (
select * from {{ ref('stg_orders') }}
),
order_payments as (
select * from {{ ref('order_payments') }}
),
final as (
select
orders.order_id,
orders.customer_id,
orders.order_date,
orders.status,
{% for payment_method in payment_methods -%}
order_payments.{{payment_method}}_amount,
{% endfor -%}
order_payments.total_amount as amount
from orders
left join order_payments using (order_id)
)
select * from final
Vad intressant kan vi se hÀr?
För det första: CTE:er (Common Table Expressions) anvÀnds - för att organisera och förstÄ koden, som innehÄller mÄnga transformationer och affÀrslogik
För det andra: Modellkoden Àr en blandning av SQL och (mallsprÄk).
I exemplet anvĂ€nds en slinga for för att berĂ€kna summan för varje betalningsmetod som anges i uttrycket set. Dessutom anvĂ€nds funktionen ref â en möjlighet att referera inom koden till andra modeller:
- Vid kompilering ref kommer det att omvandlas till en riktad pekare mot tabellen eller vyerna i lagret
- ref möjliggör att bygga en graf av beroenden för modeller
Det Àr just som ger DBT nÀstan obegrÀnsade möjligheter. De mest anvÀnda av dem Àr:
- If / else-satser - grenkoder
- For-loopar - loopar
- Variabler - variabler
- Makro - skapa makron
Materialisering: Table, View, Incremental
Materialiseringsstrategi - en metod dÀr den resulterande datamÀngden frÄn modellen kommer att sparas i lagret.
Ett grundlÀggande sÀtt att se pÄ detta Àr:
- Table - en fysisk tabell i lagret
- View - en vy, en virtuell tabell i lagret
Det finns ocksÄ mer komplexa materialiseringsstrategier:
- Incremental - inkrementell inlĂ€sning (av stora faktatabeller); nya rader lĂ€ggs till, Ă€ndrade uppdateras, borttagna tas bortÂ
- Ephemeral - modellen materialiseras inte direkt, men deltar som CTE i andra modeller
- Andra strategier som du kan lÀgga till sjÀlv
Utöver materialiseringsstrategier öppnas möjligheter för optimering för specifika lagringssystem, till exempel:
- Snowflake: Transient tables, Merge behavior, Table clustering, Copying grants, Secure views
- Redshift: Distkey, Sortkey (interleaved, compound), Late Binding Views
- BigQuery: Table partitioning & clustering, Merge behavior, KMS Encryption, Labels & Tags
- Spark: Filformat (parquet, csv, json, orc, delta), partition_by, clustered_by, buckets, incremental_strategy
För nÀrvarande stöds följande lagringslösningar:
- Postgres
- Redshift
- BigQuery
- Snowflake
- Presto (delvis)
- Spark (delvis)
- Microsoft SQL Server (community-adapter)
LÄt oss förbÀttra vÄr modell:
- Gör dess innehÄll inkrementellt (Incremental)
- LÀgg till segmenterings- och sorteringsnycklar för Redshift
-- Modellkonfiguration:
-- Inkrementellt innehÄll, unik nyckel för uppdatering av poster (unique_key)
-- Segmenteringsnyckel (dist), sorteringsnyckel (sort)
{{
config(
materialized='incremental',
unique_key='order_id',
dist="customer_id",
sort="order_date"
)
}}
{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
with orders as (
select * from {{ ref('stg_orders') }}
where 1=1
{% if is_incremental() -%}
-- Denna filter kommer att tillÀmpas endast för inkrementell körning
and order_date >= (select max(order_date) from {{ this }})
{%- endif %}
),
order_payments as (
select * from {{ ref('order_payments') }}
),
final as (
select
orders.order_id,
orders.customer_id,
orders.order_date,
orders.status,
{% for payment_method in payment_methods -%}
order_payments.{{payment_method}}_amount,
{% endfor -%}
order_payments.total_amount as amount
from orders
left join order_payments using (order_id)
)
select * from final
Modellernas beroendegraf
Det kallas Ă€ven beroendetrĂ€d. Det Ă€r ocksĂ„ en DAG (Directed Acyclic Graph â Riktad Acyklisk Graf).
DBT bygger grafen baserat pÄ konfigurationen av alla projektmodeller, mer specifikt referenserna ref() inom modellerna till andra modeller. Att ha en graf möjliggör följande:
- Körning av modeller i korrekt sekvens
- Parallellisering av skapande av datalager
- Körning av en godtycklig undergrafÂ
Ett exempel pÄ grafvisualisering:

Varje nod i grafen Àr en modell, kanterna i grafen bestÀms av uttrycket ref.
Datakvalitet och Dokumentation
Förutom att skapa sjÀlva modellerna, lÄter DBT oss testa en rad antaganden (assertions) om den resulterande datamÀngden, sÄsom:
- Not Null
- Unique
- Referensintegritet â referensintegritet (till exempel, customer_id i tabellen orders motsvarar id i tabellen customers)
- ĂverensstĂ€mmelse med en lista över tillĂ„tna vĂ€rden
Det gÄr att lÀgga till egna tester (custom data tests), som exempelvis % avvikelse i intÀkterna med siffror frÄn igÄr, förra veckan, eller förra mÄnaden. Varje antagande, formulerat som en SQL-frÄga, kan bli ett test.
PÄ sÄ sÀtt kan oönskade avvikelser och fel i data spÄras i datalager.
NĂ€r det gĂ€ller dokumentation tillhandahĂ„ller DBT mekanismer för att lĂ€gga till, versionera och distribuera metadata och kommentarer pĂ„ modellnivĂ„ och till och med pĂ„ attributnivĂ„.Â
SÄ hÀr ser det ut att lÀgga till tester och dokumentation pÄ konfigurationsfilnivÄ:
 - name: fct_orders
description: Denna tabell har grundlÀggande information om bestÀllningar, samt nÄgra hÀrledda fakta baserade pÄ betalningar
columns:
- name: order_id
tests:
- unique # kontroll av unika vÀrden
- not_null # kontroll av null
description: Detta Àr en unik identifierare för en bestÀllning
- name: customer_id
description: Utlandsskydd till tabellen för kunder
tests:
- not_null
- relationships: # kontroll av referentiell integritet
to: ref('dim_customers')
field: customer_id
- name: order_date
description: Datum (UTC) dÄ bestÀllningen gjordes
- name: status
description: '{{ doc("orders_status") }}'
tests:
- accepted_values: # kontroll av giltiga vÀrden
values: ['placed', 'shipped', 'completed', 'return_pending', 'returned']
SÄ hÀr ser denna dokumentation ut nÀr den redan Àr pÄ den genererade webbplatsen:

Makron och Moduler
Syftet med DBT Àr inte sÄ mycket att vara en uppsÀttning SQL-skript, utan att ge anvÀndarna kraftfulla och rika verktyg för att bygga sina egna transformationer och distribuera dessa moduler.
Makron Ă€r en uppsĂ€ttning konstruktioner och uttryck som kan anropas som funktioner inom modeller. Makron gör det möjligt att Ă„teranvĂ€nda SQL mellan modeller och projekt i enlighet med ingenjörsprincipen DRY (Donât Repeat Yourself).
Exempel pÄ makro:
{% macro rename_category(column_name) %}
case
when {{ column_name }} ilike '%osx%' then 'osx'
when {{ column_name }} ilike '%android%' then 'android'
when {{ column_name }} ilike '%ios%' then 'ios'
else 'other'
end as renamed_product
{% endmacro %}
Och dess anvÀndning:
{% set column_name = 'product' %}
select
product,
{{ rename_category(column_name) }} -- anrop av makro
from my_table
DBT levereras med en paketchef (packages) som lÄter anvÀndare publicera och ÄteranvÀnda individuella moduler och makron.
Det betyder möjlighet att ladda ner och anvÀnda sÄdana bibliotek som:
- : arbete med Datum/Tid, Surrogatnycklar, Schema-tester, Pivot/Unpivot och annat
- FĂ€rdiga mallar för datalager som och Â
- Bibliotek för specifika datalager, till exempel Â
- â Modul för loggning av DBT:s aktivitet
Den fullstÀndiga listan över paket kan ses pÄ .
Ănnu fler möjligheter
HÀr beskriver jag nÄgra andra intressanta funktioner och implementeringar som jag och teamet anvÀnder för att bygga ett datalager i .
Delning av körmiljöer DEV â TEST â PROD
Ăven inom en och samma DWH-kluster (inom olika scheman). Till exempel med hjĂ€lp av följande uttryck:
with source as (
select * from {{ source('salesforce', 'users') }}
where 1=1
{%- if target.name in ['dev', 'test', 'ci'] -%}
where timestamp >= dateadd(day, -3, current_date)
{%- endif -%}
)
Denna kod sÀger bokstavligen: för miljöerna dev, test, ci ta endast med data frÄn de senaste 3 dagarna och inte mer. Det vill sÀga att körningar i dessa miljöer kommer att gÄ mycket snabbare och krÀva fÀrre resurser. NÀr den körs i miljön prod kommer filtervillkoret att ignoreras.
Materialisering med alternativ kolumnkodning
Redshift Ă€r en kolumnorienterad DBMS som möjliggör att ange komprimeringsalgoritmer för varje enskild kolumn. Valet av optimala algoritmer kan minska den upptagna lagringen med 20â50%.
Makro kommer att köra kommandot ANALYZE COMPRESSION, skapa en ny tabell med rekommenderade kolumnkodningsalgoritmer, angivna segmenteringsnycklar (dist_key) och sorteringsnycklar (sort_key), överföra data till den, och om nödvÀndigt ta bort den gamla kopian.
Makrosignatur:
{{ compress_table(schema, table,
drop_backup=False,
comprows=none|Integer,
sort_style=none|compound|interleaved,
sort_keys=none|List,
dist_style=none|all|even,
dist_key=none|String) }}
Loggning av modellkörningar
För varje körning av en modell kan man anhÀnga hooks (hooks) som utförs innan starten eller omedelbart efter avslutningen av modells skapande:
   pre-hook: "{{ logging.log_model_start_event() }}"
post-hook: "{{ logging.log_model_end_event() }}"
Loggningsmodulen gör det möjligt att registrera all nödvÀndig metadata i en separat tabell, som senare kan anvÀndas för att utföra revision och analys av problemomrÄden (bottlenecks).
SÄ hÀr ser instrumentpanelen ut pÄ loggningsdata i Looker:

Automatisering av underhÄll av datalagret
Om du anvÀnder nÄgra funktionalitetsförlÀngningar i det anvÀnda datalagret, sÄsom UDF (User Defined Functions), Àr versionshantering av dessa funktioner, hantering av Ätkomster, och automatiserad utrullning av nya versioner mycket bekvÀmt att genomföra i DBT.
Vi anvÀnder UDF i Python för att berÀkna hash-vÀrden, domÀner av e-postadresser, och avkoda bitmasker (bitmask).
Exempel pÄ ett makro som skapar UDF i vilken körningsmiljö som helst (dev, test, prod):
{% macro create_udf() -%}
{% set sql %}
CREATE OR REPLACE FUNCTION {{ target.schema }}.f_sha256(mes "varchar")
RETURNS varchar
LANGUAGE plpythonu
STABLE
AS $$
import hashlib
return hashlib.sha256(mes).hexdigest()
$$
;
{% endset %}
{% set table = run_query(sql) %}
{%- endmacro %}
PĂ„ Wheely anvĂ€nder vi Amazon Redshift, som Ă€r baserat pĂ„ PostgreSQL. För Redshift Ă€r det viktigt att regelbundet samla in statistik om tabeller och frigöra diskutrymme â kommandona ANALYZE och VACUUM, respektive.
För detta körs kommandona frÄn makrot redshift_maintenance varje natt:
{% macro redshift_maintenance() %}
{% set vacuumable_tables=run_query(vacuumable_tables_sql) %}
{% for row in vacuumable_tables %}
{% set message_prefix=loop.index ~ " of " ~ loop.length %}
{%- set relation_to_vacuum = adapter.get_relation(
database=row['table_database'],
schema=row['table_schema'],
identifier=row['table_name']
) -%}
{% do run_query("commit") %}
{% if relation_to_vacuum %}
{% set start=modules.datetime.datetime.now() %}
{{ dbt_utils.log_info(message_prefix ~ " Vacuuming " ~ relation_to_vacuum) }}
{% do run_query("VACUUM " ~ relation_to_vacuum ~ " BOOST") %}
{{ dbt_utils.log_info(message_prefix ~ " Analyzing " ~ relation_to_vacuum) }}
{% do run_query("ANALYZE " ~ relation_to_vacuum) %}
{% set end=modules.datetime.datetime.now() %}
{% set total_seconds = (end - start).total_seconds() | round(2) %}
{{ dbt_utils.log_info(message_prefix ~ " Finished " ~ relation_to_vacuum ~ " in " ~ total_seconds ~ "s") }}
{% else %}
{{ dbt_utils.log_info(message_prefix ~ ' Skipping relation "' ~ row.values() | join ('"."') ~ '" as it does not exist') }}
{% endif %}
{% endfor %}
{% endmacro %}
DBT Cloud
Det finns möjlighet att anvÀnda DBT som en tjÀnst (Managed Service). I paketet ingÄr:
- Web IDE för utveckling av projekt och modeller
- Konfiguration av jobb och schemalÀggning
- Enkel och smidig Ätkomst till loggar
- Webbplats med dokumentationen för ditt projekt
- Anslutning av CI (Continuous Integration)

Slutsats
Att förbereda och anvÀnda DWH blir lika trevligt och givande som att dricka smoothies. DBT bestÄr av Jinja, anvÀndardefinierade tillÀgg (moduler), en kompilator, en motor (executor) och en paketförvaltare. Genom att sammanföra dessa element fÄr du en komplett arbetsmiljö för ditt datalager. Det finns knappast idag ett bÀttre sÀtt att hantera transformationer inom DWH.
De övertygelser som DBT-utvecklarna följde formuleras sÄ hÀr:
- Kod, inte GUI, Àr den bÀsta abstraktionen för att uttrycka komplex analytisk logik.
- Arbete med data bör anpassa de bÀsta metoderna inom programvaruutveckling (Software Engineering).
- Den viktigaste infrastrukturen för databehandling bör kontrolleras av anvÀndarsamhÀllet som programvara med öppen kÀllkod.
- Inte bara analytiska verktyg, utan Àven kod kommer alltmer att bli en del av Open Source-gemenskapen.
Dessa grundlÀggande övertygelser har skapat en produkt som idag anvÀnds av mer Àn 850 företag, och de utgör grunden för mÄnga intressanta utvidgningar som kommer att skapas i framtiden.
För de som Ă€r intresserade finns det en videoinspelning av en öppen lektion som jag höll för nĂ„gra mĂ„nader sedan som en del av en öppen lektion pĂ„ OTUS â .
Förutom DBT och datalagerdÀck, hÄller jag och mina kollegor förelÀsningar om en rad andra aktuella och moderna Àmnen inom Data Engineer-kursen pÄ OTUS:
- Arkitekturella koncept för Big Data-applikationer.
- Praktik med Spark och Spark Streaming.
- Att lÀra sig sÀtt och verktyg för att ladda datakÀllor.
- Bygga analytiska visningar i DWH.
- NoSQL-koncept: HBase, Cassandra, ElasticSearch.
- Principer för organisering av övervakning och orkestrering.Â
- Slutprojekt: sammanfoga alla fÀrdigheter med mentorsupport.
LĂ€nkar:
- â Officiell dokumentation.
- â Ăversikt av en av författarna till DBT.Â
- â YouTube, inspelning av öppen lektion OTUS.
- â NĂ€sta öppna lektion den 15 maj 2020.
- â OTUS.
- â En blick mot framtiden för databehandling och analys.
- â Utvecklingen av analys och pĂ„verkan av Open Source.
- â Principer för att bygga CI med DBT.
- â Praktik, steg-för-steg-instruktioner för sjĂ€lvstĂ€ndigt arbete.
- â Github, kod för utbildningsprojekt
KĂ€lla: habr.com

