Data Build Tool ou o que há em comum entre um Data Warehouse e um Smoothie


Quais são os princípios de um Data Warehouse ideal?

Concentre-se no valor comercial e na análise na ausência de código padrão. Gerenciando o DWH como uma base de código: controle de versão, revisão, teste automatizado e IC. Modularidade, extensibilidade, código aberto e comunidade. Documentação amigável do usuário e visualização de dependência (Data Lineage).

Mais sobre tudo isso e sobre o papel da DBT no ecossistema Big Data & Analytics - bem-vindo ao gato.

Olá a todos


Em contato com Artemy Kozyr. Por mais de 5 anos, trabalho com data warehouses, construindo ETL / ELT, além de análise e visualização de dados. Atualmente, trabalho na Wheely , ensino na OTUS no curso Data Engineer , e hoje quero compartilhar com você um artigo que escrevi na véspera do início de uma nova inscrição no curso .

Breve revisão


A estrutura DBT tem tudo a ver com a letra T na sigla ELT (Extrair - Transformar - Carregar).

Com o advento de bancos de dados analíticos produtivos e escalonáveis, como BigQuery, Redshift, Snowflake, qualquer sentido de fazer transformações fora do Data Warehouse desapareceu. 

O DBT não descarrega dados de fontes, mas oferece enormes oportunidades para trabalhar com dados que já estão carregados no armazenamento (no armazenamento interno ou externo).


O principal objetivo do DBT é pegar o código, compilá-lo no SQL, executar os comandos na sequência correta no Repositório.

Estrutura do projeto DBT


O projeto consiste em diretórios e arquivos de apenas 2 tipos:

  • Modelo (.sql) - unidade de transformação expressa por uma consulta SELECT
  • Arquivo de configuração (.yml) - parâmetros, configurações, testes, documentação

Em um nível básico, o trabalho está estruturado da seguinte forma:

  • O usuário prepara o código do modelo em qualquer IDE conveniente
  • Usando a CLI, os modelos são iniciados, o DBT compila o código do modelo no SQL
  • O código SQL compilado é executado no Warehouse na sequência especificada (gráfico)

Aqui está a aparência do lançamento da CLI:


Tudo é SELECT


Este é um recurso matador da estrutura da Ferramenta de Construção de Dados. Em outras palavras, o DBT abstrai todo o código relacionado à materialização de suas consultas no Warehouse (variações dos comandos CREATE, INSERT, UPDATE, DELETE ALTER, GRANT, ...).

Qualquer modelo envolve a gravação de uma consulta SELECT, que define o conjunto de dados resultante.

Ao mesmo tempo, a lógica de transformação pode ser multinível e consolidar dados de vários outros modelos. Um exemplo de modelo que criará uma vitrine de pedidos (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

Que coisas interessantes podemos ver aqui?

Primeiro: o CTE (Common Table Expressions) é usado para organizar e entender o código que contém muitas transformações e lógica de negócios

Segundo: o código do modelo é uma mistura de SQL e da linguagem Jinja (linguagem de modelagem).

No exemplo, o loop for foi usado para formar o valor de cada método de pagamento especificado na expressão definida . A função ref também é usada - a capacidade de se referir dentro do código a outros modelos:

  • Em tempo de compilação, ref será convertido em um ponteiro de destino em uma tabela ou exibição no Repositório
  • ref permite criar um gráfico de dependências do modelo

É o Jinja que adiciona possibilidades quase ilimitadas ao DBT. Os mais usados:

  • Instruções if / else - instruções de ramificação
  • Para loops - loops
  • Variáveis ​​- Variáveis
  • Macro - Criar macros

Materialização: Tabela, Vista, Incremental


Estratégia de Materialização - uma abordagem segundo a qual o conjunto resultante de dados do modelo será armazenado no Repositório.

Em uma consideração básica, isto é:

  • Tabela - tabela física no armazenamento
  • Ver - visualizar, tabela virtual no Repositório

Existem estratégias de materialização mais complexas:

  • Incremental - carregamento incremental (grandes tabelas de fatos); novas linhas são adicionadas, as modificadas são atualizadas, as excluídas são limpas 
  • Efêmero - o modelo não se materializa diretamente, mas participa como CTE em outros modelos
  • Quaisquer outras estratégias que você possa adicionar

Além das estratégias de materialização, são abertas oportunidades de otimização para armazéns específicos, por exemplo:

  • Floco de neve : tabelas transitórias, comportamento de mesclagem, clustering de tabelas, concessões de cópia, visualizações seguras
  • Redshift : Distkey, Sortkey (intercalada, composta), Visualizações de ligação tardia
  • BigQuery : particionamento e cluster de tabelas, comportamento de mesclagem, criptografia KMS, etiquetas e tags
  • Spark : formato de arquivo (parquet, csv, json, orc, delta), partition_by, clustered_by, buckets, incremental_strategy

Atualmente, os seguintes repositórios são suportados:

  • Postgres
  • Redshift
  • Bigquery
  • Floco de neve
  • Presto (parcialmente)
  • Faísca (parcialmente)
  • Microsoft SQL Server (adaptador da comunidade)

Vamos melhorar o nosso modelo:

  • Tornar seu preenchimento incremental (Incremental)
  • Adicionar segmentação e chaves de classificação para Redshift

--  : 
--  ,      (unique_key)
--   (dist),   (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() -%}
       --        
       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

Gráfico de Dependência do Modelo


Ele é uma árvore de dependências. Ele é um DAG (Gráfico Acíclico Dirigido - Gráfico Acíclico Direcional).

O DBT constrói um gráfico com base na configuração de todos os modelos de projeto, ou melhor, os links ref () dentro dos modelos para outros modelos. Ter um gráfico permite que você faça o seguinte:

  • Executando modelos na sequência correta
  • Paralelização do curativo da janela
  • Executando um subgráfico arbitrário 

Exemplo de visualização de gráfico:


Cada nó do gráfico é um modelo, as arestas do gráfico são fornecidas pela expressão ref.

Qualidade e documentação de dados


Além da formação dos modelos, o DBT permite testar várias asserções sobre o conjunto de dados resultante, como:

  • Não nulo
  • Único
  • Integridade de referência - integridade de referência (por exemplo, customer_id na tabela de pedidos corresponde ao ID na tabela de clientes)
  • Lista válida correspondente

Você pode adicionar seus próprios testes (testes de dados personalizados), como, por exemplo,% de desvio de receita com indicadores de um dia, uma semana, um mês atrás. Qualquer suposição formulada como uma consulta SQL pode ser um teste.

Dessa maneira, desvios e erros indesejados nos dados podem ser detectados na vitrine do armazenamento.

Em termos de documentação, o DBT fornece mecanismos para adicionar, criar versões e distribuir metadados e comentários no nível do modelo e até atributos. 

Aqui está como é a adição de testes e documentação no nível do arquivo de configuração:

 - name: fct_orders
   description: This table has basic information about orders, as well as some derived facts based on payments
   columns:
     - name: order_id
       tests:
         - unique #    
         - not_null #    null
       description: This is a unique identifier for an order
     - name: customer_id
       description: Foreign key to the customers table
       tests:
         - not_null
         - relationships: #   
             to: ref('dim_customers')
             field: customer_id
     - name: order_date
       description: Date (UTC) that the order was placed
     - name: status
       description: '{{ doc("orders_status") }}'
       tests:
         - accepted_values: #    
             values: ['placed', 'shipped', 'completed', 'return_pending', 'returned']

E aqui está como essa documentação já aparece no site gerado:


Macros e módulos


O objetivo do DBT não é tanto tornar-se um conjunto de scripts SQL, mas fornecer aos usuários ferramentas poderosas e ricas em recursos para criar suas próprias transformações e distribuir esses módulos.

Macros são conjuntos de construções e expressões que podem ser chamadas como funções nos modelos. As macros permitem reutilizar o SQL entre modelos e projetos, de acordo com o princípio de engenharia DRY (Não se repita).

Exemplo de macro:

{% 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 %}

E seu uso:

{% set column_name = 'product' %}
select
 product,
 {{ rename_category(column_name) }} --  
from my_table

O DBT vem com um gerenciador de pacotes que permite aos usuários publicar e reutilizar módulos e macros individuais.

Isso significa a capacidade de baixar e usar bibliotecas, como:

  • dbt_utils : trabalhando com Data / Hora, Chaves Substitutas, Testes de Esquema, Pivô / Não Dinâmico e outros
  • Modelos de vitrine prontos para serviços como Snowplow e Stripe 
  • Bibliotecas para Data Warehouses específicos, como Redshift 
  • Registro - Módulo de registro DBT

Uma lista completa de pacotes está disponível no dbt hub .

Ainda mais recursos


Aqui, descreverei vários outros recursos e implementações interessantes que eu e a equipe usamos para construir um Data Warehouse no Wheely .

Separação de ambientes de tempo de execução DEV - TEST - PROD


Mesmo dentro do mesmo cluster DWH (dentro de diferentes esquemas). Por exemplo, usando a seguinte expressão:

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 -%}
 
)

Esse código diz literalmente: para ambientes dev, test, ci, obtenha dados apenas nos últimos 3 dias e não mais. Ou seja, a execução nesses ambientes será muito mais rápida e exigirá menos recursos. Quando iniciada em um ambiente de prod , a condição do filtro será ignorada.

Materialização alternativa de codificação de coluna


Redshift é um DBMS da coluna que permite especificar algoritmos de compactação de dados para cada coluna individual. A escolha de algoritmos ideais pode reduzir o espaço em disco ocupado em 20-50%.

A macro redshift.compress_table executa o comando ANALYZE COMPRESSION, cria uma nova tabela com os algoritmos de codificação de coluna recomendados, indicados pelas teclas de segmentação (dist_key) e sort (key_key), transfere os dados para ele e exclui a cópia antiga, se necessário.

Assinatura de macro:

{{ compress_table(schema, table,
                 drop_backup=False,
                 comprows=none|Integer,
                 sort_style=none|compound|interleaved,
                 sort_keys=none|List<String>,
                 dist_style=none|all|even,
                 dist_key=none|String) }}

Lançamento do modelo de log


Para cada execução do modelo, você pode pendurar ganchos que serão executados antes do lançamento ou imediatamente após a criação do modelo:

   pre-hook: "{{ logging.log_model_start_event() }}"
   post-hook: "{{ logging.log_model_end_event() }}"

O módulo de registro permitirá que você registre todos os metadados necessários em uma tabela separada, de acordo com a qual você pode auditar e analisar posteriormente as áreas problemáticas (gargalos).

Veja como é o painel nos dados de pesquisa no Looker:


Automação de armazenamento


Se você usar extensões da funcionalidade do Armazenamento usada, como UDF (Funções Definidas pelo Usuário), é muito conveniente implementar o DBT na versão dessas funções, no controle de acesso e no lançamento automático de novos lançamentos.

Usamos o UDF no Python para calcular valores de hash, domínios de endereços de correspondência e decodificação de máscaras de bits.

Macro de exemplo que cria UDF em qualquer tempo de execução (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 %}

Na Wheely, usamos o Amazon Redshift, que é baseado no PostgreSQL. Para Redshift, é importante coletar regularmente estatísticas sobre tabelas e liberar espaço em disco - os comandos ANALYZE e VACUUM, respectivamente.

Para fazer isso, os comandos da macro redshift_maintenance são executados todas as noites:

{% 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


É possível usar o DBT como um serviço (Serviço Gerenciado). Em um conjunto:

  • Web IDE para desenvolvimento de projetos e modelos
  • Configuração do trabalho e definição de agendamento
  • Acesso simples e conveniente aos logs
  • Site com a documentação do seu projeto
  • Conexão CI (Integração Contínua)


Conclusão


Cozinhar e consumir DWH é tão agradável e benéfico quanto beber batidos. O DBT consiste em Jinja, extensões personalizadas (módulos), compilador, mecanismo (executor) e gerenciador de pacotes. Depois de coletar esses elementos, você obtém um ambiente de trabalho completo para o seu Data Warehouse. Atualmente, não há uma maneira melhor de gerenciar transformações no DWH.



As crenças seguidas pelos desenvolvedores de DBT são formuladas da seguinte maneira:

  • Código, não GUI, é a melhor abstração para expressar lógica analítica complexa
  • O trabalho com dados deve adaptar as melhores práticas de desenvolvimento de software (Engenharia de Software)

  • A infraestrutura crítica de dados deve ser controlada pela comunidade de usuários como software de código aberto
  • Não apenas ferramentas de análise, mas o código se tornará cada vez mais parte da comunidade Open Source.

Essas crenças centrais geraram um produto que hoje é usado por mais de 850 empresas e formam a base de muitas extensões interessantes que serão criadas no futuro.

Para os interessados, há um vídeo de uma lição aberta que passei há alguns meses atrás, como parte de uma lição aberta no OTUS - Data Build Tool para o repositório Amazon Redshift .

Além do DBT e do Data Warehouses, como parte do curso Data Engineer na plataforma OTUS, meus colegas e eu realizamos aulas sobre vários outros tópicos relevantes e modernos:

  • Conceitos de arquitetura para aplicativos de Big Data
  • Pratique com Spark e Spark Streaming
  • Métodos e ferramentas de aprendizado para carregar fontes de dados
  • Construindo mostruários analíticos em DWH
  • NoSQL: HBase, Cassandra, ElasticSearch
  •  
  • :

:


  1. DBT documentation — Introduction
  2. What, exactly, is dbt? — DBT 
  3. Data Build Tool Amazon Redshift — YouTube, OTUS
  4. Greenplum — 15 2020
  5. Data Engineering — OTUS
  6. Building a Mature Analytics Workflow
  7. It’s time for open source analytics — Open Source
  8. Continuous Integration and Automated Build Testing with dbtCloud — CI DBT
  9. Getting started with DBT tutorial — ,
  10. Jaffle shop — Github DBT Tutorial — Github,




.



All Articles