Alura > Cursos de Dados > Cursos de Engenharias e Arquiteturas de Dados > Conteúdos de Engenharias e Arquiteturas de Dados > Primeiras aulas do curso Databricks: Delta Lake e Arquitetura Medalhão

Databricks: Delta Lake e Arquitetura Medalhão

Construindo um pipeline Lakehouse - Apresentação

Apresentando a instrutora e fazendo audiodescrição

Olá. Eu sou Agnes Huescas, sou instrutora da Alura e também sou engenheira de dados. No meu dia a dia, eu produzo pipelines (fluxos de dados), faço monitoramento e construo infraestruturas na nuvem. Com o objetivo de acessibilidade, eu irei me autodescrever.

Audiodescrição: Sou uma mulher branca, de cabelos castanhos, estou usando óculos e uma blusa de manga comprida azul. Ao fundo, vemos o estúdio da Alura, iluminado com luz roxa de um lado e mais azulada do outro.

Contextualizando desafios e estruturando a solução com Delta Lake

Durante nossa trajetória, vamos trabalhar com pipelines (fluxos de dados). Não basta lermos arquivos ou aplicarmos algumas transformações. Conforme os dados crescem e passam por diferentes atualizações, precisamos garantir que as escritas sejam confiáveis e que as mudanças possam ser rastreadas em uma estrutura organizada. Também precisamos lidar com situações como registros duplicados, atualizações de pedidos, exclusões, dados inválidos e novos dados chegando continuamente. Nesse cenário, o Delta Lake se torna uma parte importante da nossa arquitetura.

Durante o curso, nós vamos construir a espinha dorsal de um pipeline (fluxo de dados) de pedidos de e-commerce (comércio eletrônico). Nós começaremos recebendo pedidos em arquivos JSON. Depois, vamos organizar o processamento seguindo a arquitetura Medalhão, com as camadas bronze (bronze), silver (prata) e gold (ouro). Na camada bronze (bronze), manteremos os dados como recebidos, acrescentando apenas metadados. Na silver (prata), faremos validações, padronizações, correções e deduplicações. Na gold (ouro), vamos transformar os pedidos tratados em uma métrica de negócio.

O Delta Lake sustentará todas as etapas que utilizaremos. Ele será utilizado para armazenar as tabelas, registrar transações, manter o histórico de alterações e permitir operações confiáveis sobre os dados. Em vez de trabalharmos com arquivos isolados, vamos construir uma tabela que pode ser atualizada, consultada e versionada.

Detalhando o cronograma das aulas

Na Aula 1, vamos entender o que é o Delta Lake, o que ele resolve e como participa de uma Lakehouse (arquitetura de dados unificada). Também vamos criar nossa primeira tabela Delta e entender como as transações ACID ajudam a proteger as operações de escrita.

Na Aula 2, executaremos operações de UPDATE, DELETE e MERGE. Com MERGE, poderemos implementar UPSERTs.

Também utilizaremos inteligência artificial como apoio para revisarmos toda a nossa operação e identificarmos possíveis riscos no código.

Na Aula 3, vamos analisar o histórico da nossa tabela utilizando time travel (viagem no tempo) e entender como o Delta Lake permite investigar o estado dos dados em diferentes momentos. Também veremos operações de manutenção e criaremos validações para impedir que dados inválidos sejam salvos.

Na Aula 4, vamos construir a arquitetura medalhão completa, com as camadas bronze, silver e gold. Cada camada será responsável por uma etapa específica, facilitando a rastreabilidade e o reprocessamento. Por fim, vamos evoluir nossa ingestão da camada bronze para utilizar o Auto Loader. Em vez de relermos todos os arquivos, vamos ler apenas o que for novo. Também vamos acompanhar a chegada de uma nova coluna e a evolução do esquema da tabela.

Preparando o ambiente e pré-requisitos

Desde a primeira tabela, utilizaremos o Unity Catalog como padrão de organização. Nossas tabelas terão um endereço composto por catálogo, esquema e nome de tabela, o que torna a estrutura mais previsível e aproxima o projeto da forma como os objetos são organizados em ambientes profissionais.

Para acompanhar este curso, é importante que você já tenha conhecimento de PySpark. Você deve saber transformar dataframes, trabalhar com colunas e filtros, e compreender conceitos de esquema. Também é importante ter noção básica de pipelines (fluxos de processamento) de dados e ter concluído os outros cursos da Alura sobre Databricks.

Apoiando o estudo com comunidade e assistente

É importante ressaltar que, durante essa trajetória, você não estará sozinho. Você conta com o Discord da Alura, com o canal de dúvidas da Alura e com a Lúria, assistente virtual, que pode auxiliar durante todo o processo, trazendo exemplos adicionais, explicando conceitos em maior profundidade e sugerindo novos exercícios.

Durante o estudo, vamos utilizar os exercícios e os materiais de apoio para praticar de forma consistente. Você também pode contar com a Lúria para esclarecer dúvidas, aprofundar conteúdos e propor novos exercícios. Se quiser compartilhar seus resultados e conversar com outras pessoas estudantes, aproveite os espaços da comunidade.

Concluindo e iniciando a jornada

Ao final do curso, você terá concluído um pipeline (fluxo de processamento) que armazena, versiona, trata e organiza dados em uma arquitetura de Lakehouse. Vamos começar entendendo por que os pipelines (fluxos de processamento) transacionais podem ser difíceis de manter e como o Delta Lake nos ajuda nesse problema.

Espero que você aproveite este curso. Vamos começar.

Construindo um pipeline Lakehouse - O problema dos pipelines tradicionais

Apresentando os objetivos do curso

Olá.

Ao longo do curso, nós desenvolveremos, a partir de dados de e-commerce (comércio eletrônico), um pipeline (fluxo) completo de dados. Antes de utilizarmos efetivamente o Delta Lake, precisamos entender o que é o Delta Lake, por que o utilizamos e qual problema ele resolve.

Preparando o ambiente no Databricks

Nós já contamos com armazenamento de dados em CSV, Parquet e JSON. A dificuldade aparece quando precisamos atualizar esses dados: ao utilizar apenas arquivos, sem uma camada que controle as alterações realizadas sobre eles, surgem limitações. É esse ponto que vamos entender neste vídeo.

No Databricks, nós vamos abrir o notebook (bloco de notas) 01 Fundamento Delta. Para tornar o problema mais fácil de visualizar, começaremos com uma versão reduzida dos nossos pedidos de e-commerce (comércio eletrônico). Nesta primeira demonstração, armazenaremos os dados no formato Parquet. Parquet é um formato colunar amplamente utilizado em engenharia de dados por oferecer boa compressão e desempenho em consultas analíticas. A limitação que veremos não significa que Parquet seja inadequado; o problema ocorre quando utilizamos somente arquivos, sem uma camada que faça o controle das alterações.

Descrevendo os dados de exemplo

Começamos importando os módulos e as funções do PySpark, recuperamos o catálogo atual do Databricks e definimos o esquema utilizado no projeto. Também construímos o nome da tabela, que será utilizado nos próximos vídeos, e o caminho base do nosso volume. Nesta aula, ainda não criaremos uma tabela Delta; utilizaremos apenas o caminho base para gravar a demonstração em arquivo Parquet.

Neste exemplo, temos três pedidos: o primeiro é a compra de um notebook, o segundo é um smartphone e o terceiro, um monitor. Para cada pedido, armazenamos o identificador, o cliente, o produto, o valor, o status e a data da última atualização. Os pedidos 1001 e 1002 já foram aprovados, enquanto o 1003 está com status de processamento. A coluna de data de atualização será muito importante, pois nos permitirá distinguir informações antigas de atualizações mais recentes.

Implementando a preparação inicial em código

Definimos os nomes das colunas e criamos o nosso DataFrame. Como a data de atualização foi inicialmente informada como texto, utilizamos a função to_timestamp para converter para um tipo temporal. Isso nos permitirá trabalhar corretamente com a ordem das atualizações quando necessário.

Para implementar essas etapas iniciais no notebook, utilizamos o seguinte código:

from pyspark.sql import functions as f

CATALOG = spark.sql("SELECT current_catalog()").first()[0]
SCHEMA = f"{CATALOG}.dados_ecommerce"
TABELA = f"{CATALOG}.{SCHEMA}.tb_01_fundamentos_delta"
VOL_PATH = f"/Volumes/{CATALOG}/{SCHEMA}/arquivos"

dados = [
    (1001, "C01", "Notebook", 1500.0, "aprovado", "2024-07-01 10:15:00"),
    (1002, "C02", "Smartphone", 800.0, "aprovado", "2024-07-01 10:30:00"),
    (1003, "C03", "Monitor", 1200.0, "processando", "2024-07-01 11:00:00")
]

colunas = ["pedido_id", "cliente_id", "produto", "valor", "status", "data_atualizacao"]

df = spark.createDataFrame(dados, colunas).withColumn("data_atualizacao", f.to_timestamp("data_atualizacao"))

Gravando os dados iniciais em Parquet

Executamos a primeira célula e obtemos um DataFrame inicial, preparado em memória. Na próxima célula, vamos armazenar esses dados em Parquet e simular a chegada de uma atualização. Nosso objetivo é observar o que acontece quando utilizamos append para gravar uma nova versão de um pedido que já existe. O modo append acrescenta dados ao armazenamento, mas não procura automaticamente o registro anterior para atualizar.

Primeiro, definimos o caminho onde os arquivos Parquet serão gravados. Como esse notebook (bloco de notas) pode ser executado várias vezes durante os testes, removemos qualquer resultado anterior desta demonstração. Essa limpeza serve apenas para garantir que o resultado apresentado seja reproduzível.

Em seguida, gravamos o DataFrame utilizando o modo overwrite. Nesse momento, o caminho passa a conter os três pedidos que criamos.

O bloco abaixo reúne a definição do caminho, a limpeza de resultados anteriores e a gravação inicial em Parquet com mode("overwrite"):

caminho_parquet = f"{VOL_PATH}/demonstracoes/pedido_parquet"

if dbutils.fs.ls(caminho_parquet):
    dbutils.fs.rm(caminho_parquet, True)

(df.write.format("parquet")
    .mode("overwrite")
    .save(caminho_parquet))

Simulando atualização de pedido com append

Incluímos o pedido 1003 com status processando. Em seguida, simulamos uma atualização: o pagamento do pedido 1003 foi confirmado. Por isso, o status mudou de processando para aprovado. Observe que o identificador continua sendo 1003, pois tratamos do mesmo pedido. A data de atualização, entretanto, é mais recente. Portanto, esse registro não representa um novo pedido, e sim a mudança do estado de um pedido que já estava armazenado.

Gravamos a atualização utilizando o modo append, que já tínhamos mencionado anteriormente. Aplicamos a atualização com writeModeAppend. Esse modo acrescenta um novo registro aos arquivos que já existiam; ele não procura o pedido com o mesmo identificador e não substitui automaticamente a linha anterior. Para o armazenamento, apenas adicionamos um novo registro.

O código a seguir cria o DataFrame de atualização com a data mais recente e grava em append no mesmo caminho Parquet:

atualizacao = [(1003, "C03", "Monitor", 1200.0, "aprovado", "2024-07-02 09:00:00")]

df_atualizacao = (spark.createDataFrame(atualizacao, colunas)
                    .withColumn("data_atualizacao", f.to_timestamp("data_atualizacao")))

(df_atualizacao.write.mode("append").parquet(caminho_parquet))

Lendo e inspecionando os resultados

Agora, vamos executar a célula completa. Ao final da célula, fazemos novamente a leitura do arquivo Parquet, ordenamos o resultado pelo identificador do pedido com ORDER BY e exibimos no display. Vamos observar o que aconteceu com o pedido 1003.

Para isso, fazemos a leitura e ordenação dos dados gravados:

display(spark.read.parquet(caminho_parquet).orderBy("pedido_id"))

Observando as limitações do uso de arquivos

Agora temos quatro linhas: 1001, 1002 e 1003, sendo que 1003 aparece em duas linhas. Em uma delas, o status está processando; na outra, o status está aprovado. Pela data de atualização, percebemos que o registro aprovado é o mais recente (2.007), enquanto o processando é anterior (1.007). No entanto, o Parquet não interpretou a nova linha como uma atualização; ele apenas preservou as duas informações. A partir desse momento, qualquer processamento ou consulta precisa implementar uma regra para descobrir qual registro representa o estado atual desse pedido.

A atualização trabalhosa que acabamos de observar é apenas um dos desafios de trabalhar diretamente com arquivos isolados. No nosso exemplo, a atualização não substituiu o registro; ela apenas adicionou mais uma linha. Passamos a ter duas versões do mesmo pedido e precisamos identificar manualmente qual é a mais recente.

Também podemos encontrar outro problema: se uma escrita falhar durante a substituição dos arquivos, podemos terminar com dados parciais. Se o mesmo lote chegar novamente e for reprocessado sem controle, podemos gerar duplicidade. Quando um pedido já armazenado precisa ser corrigido, a atualização exige lógica adicional. E, se um erro for descoberto somente depois, recuperar o estado anterior pode ser difícil, porque os arquivos não mantêm sozinhos um histórico organizado dessas alterações.

Por isso, um pipeline (fluxo de processamento) confiável precisa contar com mais de um formato eficiente de armazenamento: um arquivo histórico e uma operação confiável. Nesta aula, vimos que o Parquet continua sendo um formato eficiente para armazenar e consultar dados. A dificuldade aparece quando os dados mudam ao longo do tempo. Ao gravarmos a atualização do pedido 1003 com append, não substituímos a informação anterior; apenas acrescentamos uma linha e, com isso, transferimos para o pipeline (fluxo de processamento) ou para o consumidor a responsabilidade de identificar qual é o registro mais recente.

No próximo vídeo, vamos conhecer a arquitetura de Lakehouse e o papel do Delta Lake nessa estrutura. Eu te vejo lá!

Construindo um pipeline Lakehouse - Arquitetura Lakehouse e o papel do Delta Lake

Contextualizando o problema com arquivos parquet

Olá.

No último vídeo, nós fizemos o armazenamento de pedidos de e-commerce em arquivos Parquet. Ao atualizar o pedido 1003, utilizamos o modo append para acrescentar o novo registro. O resultado gerou duas linhas: uma com status de processando e outra com status de aprovado. Os arquivos foram armazenados corretamente com as duas informações, mas não conseguimos identificar automaticamente qual é o mais recente.

Agora, precisamos entender como manter as vantagens desse armazenamento baseado em arquivos e, ao mesmo tempo, adicionar operações mais confiáveis sobre os dados. É nesse contexto que entra a arquitetura Lakehouse.

Comparando data lake e data warehouse

Um Data Lake permite que nós armazenemos um grande volume de dados com flexibilidade. Podemos receber arquivos de diferentes formatos, preservar os dados brutos e escalar o armazenamento conforme o volume cresce. Essa flexibilidade é uma das principais razões para a utilização do Data Lake em projetos de engenharia de dados.

Por outro lado, quando pensamos em um Data Warehouse, encontramos uma estrutura mais controlada para trabalhar com tabelas, esquemas e fazer consultas analíticas. O Data Warehouse oferece um modelo mais previsível para as pessoas consumidoras, mas normalmente exige que os dados sejam organizados antes de serem disponibilizados.

É aqui que surge a necessidade do Lakehouse. Essa arquitetura busca aproximar essas duas características: manter a flexibilidade e a escalabilidade do armazenamento do Data Lake, ao mesmo tempo em que oferece recursos associados ao gerenciamento de tabelas. É importante afirmar que não estamos dizendo que uma arquitetura é superior à outra; a escolha depende da necessidade do projeto.

Justificando o uso do lakehouse no curso

Neste curso, nós vamos usar o Lakehouse justamente porque precisamos trabalhar com arquivos que passarão por muitas atualizações, precisamos de histórico e de organização da camada dentro da plataforma.

Olhando para o fluxo do Lakehouse, nós começamos pelas fontes. Essas fontes podem produzir arquivos, eventos ou conjuntos de dados que precisam ser processados pela plataforma. Do outro lado, temos diferentes consumidores, como processos de engenharia de dados, análises e projetos de IA.

Entre a chegada e o consumo desses dados, nós precisamos de uma camada que faça o controle e organize as operações realizadas sobre eles. No nosso projeto, essa camada será o Delta Lake.

Detalhando o funcionamento do delta lake e do log de transações

O Delta Lake adiciona um log de transações e metadados aos arquivos armazenados. Isso permite que um conjunto de arquivos seja tratado como uma tabela, com uma estrutura conhecida e um histórico de operações realizadas. É importante entender que o Delta Lake não elimina o armazenamento em arquivos. Os dados de uma tabela Delta continuam sendo armazenados, principalmente, em arquivos Parquet. O diferencial é que esses arquivos deixam de ser considerados de forma isolada. Além dos arquivos de dados, o Delta Lake mantém um diretório de Delta Log, no qual são registradas as informações das transações realizadas na tabela.

Quando uma operação é confirmada, o log (registro) registra quais arquivos fazem parte da versão da tabela. Assim, ao fazer uma leitura, não precisamos procurar todos os arquivos existentes no diretório: a leitura utiliza o log (registro) para descobrir qual arquivo representa o estado válido daquela tabela naquele momento específico.

Podemos pensar o Parquet como o local onde os dados estão armazenados, enquanto o log (registro) é um mecanismo que registra os arquivos que devem ser interpretados em conjunto. A combinação desses dois elementos permite que nós transformemos os arquivos em uma tabela confiável. No exemplo da aula anterior, o Parquet armazenou duas versões do pedido 1003, mas não havia nenhum mecanismo que controlasse a tabela quanto à alteração. Com o Delta Lake, podemos executar a operação de atualização sobre a tabela, e essa operação é registrada no log (registro) como uma nova transação. Dessa forma, a consulta da tabela recebe um estado consistente dos dados, sem precisar decidir manualmente entre todas as versões do arquivo.

Nos próximos vídeos, vamos observar na prática a diferença ao criar, primeiro, uma tabela Delta e, depois, atualizar os pedidos.

Organizando os objetos com unity catalog

Além de armazenar e controlar as alterações, também precisamos organizar os objetos da plataforma. Por isso, vamos utilizar o Unity Catalog. Desde a primeira tabela, o Unity Catalog é um objeto que pode ser identificado usando três níveis: catálogo, esquema e tabela. O catálogo representa o nível mais amplo de organização. Dentro dele, utilizamos esquemas para agrupar objetos relacionados e, dentro de cada esquema, temos tabelas e outros objetos de dados. Por isso, o nome completo de uma tabela sempre segue catálogo.esquema.tabela. No exemplo apresentado no nosso slide (apresentação), temos curso.silver.pedido, que corresponde a catálogo, esquema e tabela. Essa estrutura cria um endereço previsível para cada objeto e evita ambiguidades, principalmente quando a plataforma possui várias tabelas e diferentes ambientes.

Podemos voltar ao nosso notebook (bloco de anotações). Nele, recuperamos automaticamente o catálogo atual do workspace (espaço de trabalho), depois definimos o esquema eCommerceLakeHouse, que será usado para organizar os objetos deste projeto e, por fim, construímos o nome completo da tabela, eCommerceLakeHouseDemoPedidosDelta. Dessa forma, não estamos apenas criando arquivos em um caminho; estamos preparando um objeto, identificando-o e organizando-o dentro do Unity Catalog.

Para materializar esses passos no notebook, usamos variáveis para capturar o catálogo atual, definir o esquema e compor o nome totalmente qualificado da tabela:

CATALOGO = spark.sql("SELECT current_catalog()").first()[0]
SCHEMA = "ecommerce_lakehouse"
TABELA = f"{CATALOGO}.{SCHEMA}.demo_pedidos_delta"

Esse trecho recupera o catálogo atual via Spark SQL, define o esquema ecommerce_lakehouse (equivalente ao eCommerceLakeHouse mencionado) e monta o nome da tabela no formato catálogo.esquema.tabela, pronto para ser utilizado nas próximas operações dentro do Unity Catalog.

Recapitulando os conceitos e próximos passos

Neste vídeo, entendemos que a arquitetura Lakehouse busca combinar a flexibilidade do Delta Lake com os recursos de gerenciamento de tabelas que teríamos mais no Data Warehouse (armazém de dados). O Delta Lake exerce um papel central nessa arquitetura: mantém os dados no arquivo Parquet e adiciona um log (registro) de transações e metadados que nos permitem interpretar esse arquivo como uma tabela confiável. Também conhecemos a organização de um Unity Catalog, em que cada tabela possui endereços formados por catálogo, esquema e tabela.

No próximo vídeo, vamos usar esse conceito para criar a nossa primeira tabela Delta e atualizar o pedido 1003, sem manter duas linhas conflitantes na consulta. Eu te vejo lá!

Sobre o curso Databricks: Delta Lake e Arquitetura Medalhão

O curso Databricks: Delta Lake e Arquitetura Medalhão possui 233 minutos de vídeos, em um total de 60 atividades. Gostou? Conheça nossos outros cursos de Engenharias e Arquiteturas de Dados em Dados, ou leia nossos artigos de Dados.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Engenharias e Arquiteturas de Dados acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas