8 funcionalidades para melhorar a rotina do seu projeto DBT.

Como melhoramos o nosso projeto no DBT com 8 dicas rápidas.

Alice Thomaz
7 min readSep 25, 2023

O DBT, ou Data Build Tool, é uma ferramenta de transformação de dados que se concentra em consultas SQL, permitindo que engenheiros e analistas de dados manipulem seu Data Warehouse com mais eficiência.

Se você ainda não conhece o DBT ou deseja aprofundar seus conhecimentos, recomendo a leitura do meu Guia prático do DBT. No guia, forneço uma visão geral da ferramenta e orientações para iniciar seu próprio projeto.

Neste artigo, vou compartilhar algumas dicas simples e rápidas que têm facilitado a rotina de desenvolvimento no DBT para a equipe de engenharia da Afya.

Tópicos mencionados neste artigo:

  • on-run-start & on-run-end
  • pre-hook & post-hook
  • persist_docs
  • variáveis de projeto
  • dbt docs
  • meta — yml
  • selectors.yml
  • elementary

>>on-run-start & on-run-end

Instruções SQL ou chamadas macro a serem executadas no início ou final dos processos: dbt run, dbt test, dbt seed, dbt snapshot, dbt build, dbt compile e dbt docs generate.

Ambas deverão ser definidas no arquivo do seu projeto — dbt_project.yml. Abaixo a aplicação que usamos para ambas atualmente:

  • on-run-start: Para iniciar nossos processos, utilizamos uma macro que invoca uma procedure no Redshift, projetada para limpar diariamente nossos schemas de teste e temporários. No início da macro, implementamos medidas de segurança, incluindo travas, para garantir que ela seja executada apenas em um job específico no DBT Cloud. Essa precaução evita qualquer potencial interrupção no fluxo de trabalho da equipe, impedindo que o DROP seja acionado durante o horário de expediente.

Chamada no projeto:

on-run-start:
#limpa o schema public
- "{{ admin_call_procedure('admin.proc_admin_drop_public_tables()') }}"
#limpa o schema scratch
- "{{ admin_call_procedure('admin.proc_admin_drop_scratch_tables(0, 1)') }}"

Macro:

{% macro admin_call_procedure(procedure) %}
{% if target.name == 'cloud' %}
{% set dbt_cloud_job_id = env_var('DBT_CLOUD_JOB_ID', '') %}
{% if dbt_cloud_job_id == '123456' %}
CALL {{procedure}};
{% endif %}
{% endif %}
{% endmacro %}
  • on-run-end: Para o final dos processos nós executamos algumas macros que fazem o controle de acesso dos nossos schemas do Redshift. Nesse caso como precisamos de um acesso super para fazer essas liberações, também incluímos a trava do target conforme exemplificado no guia Guia prático do DBT.

Chamada no projeto:

on-run-end:
#libera schemas de teste - all
- "{{ warehouse_cloud_group_grant_all_on_schemas(['group1', 'group2'], ['schema1', ' schema2']) }}"
#libera schemas de prod - select
- "{{ warehouse_cloud_group_grant_select_on_schemas(['group1', 'group2'], ['schema1', ' schema2']) }}"
#libera schemas de prod - all - cloud
- "{{ warehouse_cloud_user_grant_all_on_schemas(['user1], ['schema1', ' schema2']) }}"

Macro:

{% macro warehouse_cloud_group_grant_select_on_schemas(groups, schemas) %}
{% if target.name == 'cloud' %}
{% for schema in schemas %}
{% for group in groups %}
grant usage on schema {{ schema }} to group {{ group }};
grant select on all tables in schema {{ schema }} to group {{ group }};
alter default privileges in schema {{ schema }} grant select on tables to group {{ group }};
{% endfor %}
{% endfor %}
{% endif %}
{% endmacro %}

>> pre-hook & post-hook

As instruções SQL ou chamadas de macro podem ser executadas no início ou no final das modelagens. Vale ressaltar que, ao contrário do “on-run” que é executado após a conclusão de todos os modelos naquela rodada, o “hook” é executado em conjunto com a modelagem em que foi acionado.

Os “hooks” podem ser aplicados a configurações de modelos individuais ou a grupos de modelos pelo arquivo do projeto. Abaixo estão alguns casos de uso atualmente empregados em nosso projeto:

  • post-hook: Após cada modelo, executamos uma macro que verifica, por meio de uma procedure no Redshift, se o encode foi definido corretamente, seja como zstd ou az64. Se não estiver definido corretamente, fazemos a troca para o encode zstd em casos como “character varying(%)” e “boolean”. Para uma compreensão mais aprofundada sobre a importância do uso de codificação no Redshift, recomendo a leitura do artigo “Melhores práticas com Amazon Redshift: Arquitetura, organização e otimização de performance”.
    Esse é um exemplo que pode ser invocado no projeto e usado em todos os modelos daquela pasta.

Chamada no projeto:

models:
+post-hook: "{{ compress_zstd() }}"

Macro:

{%- macro compress_zstd() -%}
{#-- Only execute at runtime, not when compiling etc #}
{% if not execute %}
{{ return('select 1') }}
{% endif %}
{#-- Only execute for tables or incremental model #}
{% if target.name == 'default' and (model.config.materialized == 'table' or model.config.materialized == 'incremental') %}
call maint.redshift_dbt_enforce_encode_sp('{{this.schema}}' , '{{this.table}}');
{% else %}
{{ log('MACRO: Not compressing ' ~ this.table ~ '.', False) }}
{{ return('select 1') }}
{% endif %}
{%- endmacro %}
  • pre-hook: Em modelos incrementais nos quais não é possível definir uma chave única, optamos por usar a função “pre-hook” para invocar uma macro que realiza a limpeza dos dados dos últimos dias, conforme a quantidade definida pelo usuário. O ideal é que o número de dias definido na macro corresponda àquele utilizado nas cláusulas de chamada incremental do “ref{}”.
    Esse é um exemplo que pode ser invocado individualmente em cada modelagem, quando necessário.

Chamada no modelo:

{{
config(
materialized = 'incremental',
full_refresh = false,
dist = 'even',
sort = 'col_time',
pre_hook = "{{ incremental_delete_day('col_time', '15') }}"
)
}}

Macro:

{% macro incremental_delete_day(column, day) %}
{% if is_incremental() %}
DELETE FROM {{this}} WHERE {{column}}::DATE >= DATEADD(DAY, -{{day}}, CURRENT_DATE)::DATE
{% endif %}
{% endmacro %}

>> persist_docs

É possível habilitar uma opção que gera automaticamente os comentários especificados no arquivo schema.yml diretamente no banco de dados, abrangendo tanto as tabelas quanto as colunas. Essa funcionalidade é suportada por diversos adaptadores do dbt, incluindo Redshift, Snowflake, BigQuery e outros. No projeto, configurei essa opção para ser aplicada a todos os modelos, mas também é viável defini-la de forma individual para modelos específicos.

No projeto:

models:
project-path:
+persist_docs:
relation: true
columns: true

No modelo:

{{ 
config(
persist_docs={"relation": true, "columns": true}
)
}}

>> variáveis de projeto

É possível definir valores universais no projeto por meio de variáveis, incluindo a definição do fuso horário e recortes temporais utilizados em nossos modelos incrementais. Essas variáveis são extremamente úteis para evitar a redundância de valores e simplificar possíveis ajustes de lógica em diversos modelos simultaneamente.

A implementação dessas variáveis em modelos .sql é realizada por meio da cláusula: ‘{{ var(“var_name”) }}’

No projeto:

vars:
'dbt_date:time_zone': 'America/Sao_Paulo'
testing_days: 1
filter_days_raw: 2
filter_days: 2

No modelo:

WHERE 1=1
{% if target.name == 'dev' %}
AND col_tstamp >= DATEADD(DAY, -{{var('testing_days')}}, CURRENT_DATE)
{% elif is_incremental() %}
AND col_tstamp::DATE >= DATEADD(DAY, -{{var('filter_days_raw')}}, (SELECT MAX(p_col_tstamp::DATE) FROM {{ this }}))
{% endif %}

>> dbt docs

O DBT armazena automaticamente todos os metadados do seu projeto em um arquivo chamado manifest.json, lá conseguimos ter detalhes de todos os modelos, seeds, e pacotes utilizados. É possível visualiza-lo em formato de site seguindo os comandos:

dbt docs generate
dbt docs serve --port 8001

Após a execução do segundo comando, uma nova guia será aberta em seu navegador, apresentando uma visualização que contém informações detalhadas sobre os modelos, códigos, documentações, linhagens e muito mais.

>> meta — yml

É possível incorporar campos personalizados na documentação do modelo através do schema.yml, indo além das informações convencionais de nome e descrição. Esse recurso é habilitado por meio da função “meta”, e os dados associados a esses campos são automaticamente incorporados aos metadados gerados pelo DBT.

No schema.yml:

models:
- name: ---
description: ---
meta:
developer: 'Alice Thomaz'
domain: content
columns:
- name: (...)

Visão na documentação:

>> selectors.yml

À medida que a necessidade de personalizar novos fluxos de execução surge, este arquivo ganha importância. Com ele, é possível organizar o plano de execução dos Jobs, tudo de forma documentada e controlada por versão.

Por padrão, nosso principal job executa a sequência das sources diariamente, mas também conseguimos personalizar alguns jobs para situações específicas. Para realizar a execução, utilizamos o comando: dbt run — selector job_models_daily

Abaixo, apresento um exemplo de configuração do arquivo, no qual podemos definir os Jobs, suas descrições e quais modelos devem ser incluídos ou excluídos. Além disso, há a opção de determinar se, ao executar, os modelos pais e filhos da pasta ou arquivo selecionado também serão acionados:

selectors:
- name: job_models_daily
description: Job que executa os modelos do DBT diariamente, exceto os modelos X.
definition:
union:
- method: path
value: models/sources
children: true
- exclude:
- method: path
value: models/sources/folder_2
children: true

- name: job_models_2
description: Job que executa o modelo X e seus pais.
definition:
union:
- method: path
value: models/marts/folder_2/model_name.sql
children: false
parents: true

>> elementary

Este pacote é voltado para a observabilidade de dados, armazenando metadados do DBT e os resultados das execuções em tabelas no Data Warehouse. Após seguir os passos abaixo, sempre que os modelos forem executados no DBT, o schema “elementary” será automaticamente atualizado.

Ele tem desempenhado um papel fundamental no acompanhamento do tempo de execução de nossos trabalhos e na identificação de possíveis falhas. Além disso, complementa nossos metadados em conjunto com o manifest.json, que é gerado automaticamente pelo DBT.

Para obter mais detalhes sobre o uso de pacotes no DBT, recomendo a leitura do artigo “Guia prático do DBT: Organizando as pastas do projeto”.

Passo a passo:

1. Adicionar o campo no seu arquivo packages.yml

packages:
- package: elementary-data/elementary
version: 0.7.6

2. Rodar no terminal o comando: dbt deps

3. Adicionar a quebra do schema no dbt_project.yml:

models:
elementary:
+schema: elementary

4. Rodar no terminal o comando:

dbt run --select elementary

Algumas tabelas que ele gera:

  • Resultados da execução dos modelos:
    - dbt_run_results
    - model_run_results
    - snapshot_run_results
    - dbt_invocations
    - elementary_test_results
  • Metadados dos modelos:
    - dbt_models
    - dbt_tests
    - dbt_sources
    - dbt_exposures
    - dbt_metrics
    - dbt_snapshots

O DBT é uma ferramenta abrangente e em constante evolução. Neste artigo, exploramos algumas das funcionalidades que têm aprimorado a nossa experiência de uso no projeto.

Se você não está familiarizado com o DBT, recomendo a leitura do Guia prático do DBT. Para aqueles que já utilizam a ferramenta, espero que este texto os ajude de alguma forma. Caso tenha sugestões ou comentários sobre o conteúdo, por favor, não hesite em entrar em contato comigo pelo Linkedin.

English version: https://medium.com/@alice_thomaz/e9f22196ac4c

--

--