Iniciando a ingestão de csv
Chegamos à fase prática. Vamos trazer, aos poucos, as informações para que possamos trabalhar em conjunto. Nesta etapa, o objetivo é ingerir o primeiro arquivo CSV.
Neste cenário, o ponto importante é configurar o Databricks na modalidade Free Edition (edição gratuita). Ele está em execução em alguma cloud (nuvem), não informada pela plataforma. Na estrutura com a qual trabalhamos no dia a dia, é comum visualizar serviços de Object Store (armazenamento de objetos), como Amazon S3 e Azure Blob Storage. Todos eles são serviços de Object Store presentes nas nuvens públicas. Como isso funciona?
Entendendo object store e volumes
O Object Store é similar a um Google Drive, de forma resumida, porém utiliza um protocolo de alto desempenho otimizado para leitura, ficando disponível como o nosso Data Lake (lago de dados). No fim do dia, quando falamos de Data Lake, trata-se de um sistema de arquivos hospedado na nuvem, usando, por exemplo, Amazon S3 na AWS ou Azure Blob Storage na Azure.
Na versão Free Edition não há acesso direto a esses serviços. No dia a dia de engenharia de dados com Databricks, quando as plataformas já vêm configuradas, trabalhamos com a visão de volumes. O volume é uma abstração do Object Store: no fim do dia, o volume aponta para o Object Store. É nesse volume que enviamos dados manualmente, por exemplo, ou do qual consumimos dados do nosso Data Lake. Em resumo, pense que existe o S3 e, na frente dele, colocamos um volume; na hora de consumir dados, em vez de referenciar diretamente “S3 Data Lake Alura”, utilizamos apenas “Data Lake Alura”, que está conectado ao S3 onde está a pasta “Data Lake Alura”. A partir daí, usamos as pastas normalmente. Vamos trabalhar aqui diretamente com volume, sem a visão explícita do Object Store, entendendo que ele é uma abstração do Data Lake.
Organizando o workspace e importando notebooks
Vamos abrir, no menu lateral esquerdo, o “Workspace”. O “Workspace” é literalmente a estrutura de arquivos que utilizaremos. Ele possui o “Home”, que traz a pasta de usuário principal; o “Workspace”, com diretórios do mesmo ambiente (outros usuários); e o “Repos”, que era uma forma antiga de trabalhar com repositório Git (Git), pois hoje utilizamos o repositório Git dentro da própria pasta de usuário. É possível adicionar um repositório Git à pasta de usuário, integrando com Bitbucket, GitHub, GitLab, entre outros. Neste curso, não trabalharemos com repositórios Git; vamos trabalhar diretamente com os Workspaces dentro do Databricks.
Vamos criar uma pasta chamada “AluraDatabricks”, que utilizaremos ao longo do curso. É importante segui-la como padrão durante todo o percurso. Para criar, utilizamos o botão “Create” (criar), no centro, e escolhemos “Folder” (pasta). No nosso caso, o nome será “AluraDatabricks”. Após criar, a pasta aparecerá na listagem; se não aparecer, atualizamos a página (por exemplo, com F5) e clicamos na pasta.
Dentro de “AluraDatabricks”, vamos criar a pasta “Notebooks”. Para isso, criamos uma “Folder” chamada “Notebooks” e confirmamos em “Create”. Com isso, dentro de “AluraDatabricks” teremos a pasta “Notebooks”.
Dentro de “Notebooks”, vamos enviar o primeiro arquivo do projeto, 01-bronze-população. Podemos fazer o envio deste notebook entrando na pasta e realizando o upload (envio), ou arrastando o arquivo diretamente (“arrastar e soltar”). Para enviar pelo menu, abrimos o menu de três pontos (entre “Share” e “Send feedback”) e clicamos em “Import” (importar). Podemos importar por URL (URL) ou por arquivo; aqui usaremos por arquivo. Arrastamos e soltamos o arquivo 01-bronze-população na área indicada ou pesquisamos o arquivo localmente e confirmamos em “Import”. Após a importação, o notebook aparecerá; então clicamos nele para abrir.
Configurando catálogos e volumes de landing
O objetivo, agora, é conhecer como funcionam os principais elementos da plataforma na subida de arquivos. A primeira parte do notebook apresenta a criação do catálogo e do volume dentro do ambiente. O catálogo é a estrutura principal onde os dados ficam organizados. Podemos criar catálogos por contexto de negócio, por área, entre outras possibilidades. No nosso cenário, organizaremos pelo contexto da análise: vamos criar um catálogo chamado “Economia Regional” e, dentro dele, um volume de “Landing” para receber os arquivos CSV com os quais vamos trabalhar.
Podemos preparar isso por comando, definindo variáveis para os nomes antes de executar a criação de fato. Ao executar as células do notebook, o ambiente inicializa o mecanismo SQL em modo serverless (sem servidor). Em seguida, serão criadas variáveis como CATALOG (com o valor “economia_regional”, que será o nome do nosso catálogo), VOLUME (caminho base como “/Volumes”, seguido do nome do catálogo, “landing” e “arquivos”) e outra variável para o volume de “Landing” específico da população, que apontará para o volume criado e adicionará “/populacao” como nome de arquivo ou subdiretório. Neste momento, reforçamos: essas células apenas definem variáveis; ainda não criam os objetos no metastore.
Para registrar essas variáveis no notebook, usamos:
CATALOG = "economia_regional"
VOLUME = f"/Volumes/{CATALOG}/landing/arquivos"
LANDING_POPULACAO = f"{VOLUME}/populacao"
Criando estruturas via interface e código
Também podemos criar tudo via interface, antes de executar os comandos. Para isso, abrimos a seção de catálogos e utilizamos a opção de criação (“Create”) para cada um dos itens necessários (catálogo e volume), seguindo a mesma configuração descrita acima.
Ao clicar em Catálogo, vamos demonstrar rapidamente a navegação. Podemos acessar esta seção, na qual estão listados os catálogos: Workspace (espaço de trabalho), System (sistema) e Sample (amostra). Para criar um novo catálogo, clicamos no botão de adicionar (+), selecionamos a opção Create Catalog, informamos o nome, mantemos o tipo como Standard (padrão) e confirmamos em Create. Assim, criamos o catálogo desejado. Em seguida, podemos acessá-lo para criar o volume correspondente.
Vamos demonstrar no nosso código. Voltamos ao Workspace e abrimos o nosso Notebook (bloco de notas). Já executamos a criação da variável; agora, vamos começar a criar as estruturas.
Faremos a criação via SQL, utilizando Spark SQL, com o comando CREATE CATALOG IF NOT EXISTS. Em seguida, adicionamos um comentário indicando a qual domínio o catálogo pertence. Depois, ele criará os nossos schemas dentro desse catálogo: os schemas de Landing (zona de repouso), Bronze (bronze), Silver (prata) e Gold (ouro). Vamos deixar sem o schema Gold para criarmos manualmente pela interface e verificarmos como funciona esse processo. Na sequência, será criado o volume dentro do catálogo. Também mostraremos depois como realizar essa criação pela interface.
Para executar exatamente o que descrevemos — isto é, sem criar o schema Gold — usamos:
spark.sql(f"CREATE CATALOG IF NOT EXISTS {CATALOG} COMMENT 'Dominio de negócio: economia regional brasileira (IBGE)'")
for schema in ["landing", "bronze", "silver"]:
spark.sql(f"CREATE SCHEMA IF NOT EXISTS {CATALOG}.{schema}")
spark.sql(f"CREATE VOLUME IF NOT EXISTS {CATALOG}.landing.arquivos COMMENT 'Landing zone de arquivos brutos do projeto'")
Vamos executar a célula; ela fará a criação do nosso catálogo, dos nossos schemas e do volume. Removemos a criação do Gold para fazermos manualmente e, ainda, criaremos o volume dentro do catálogo que acabamos de configurar. O cenário será: vamos criar um catálogo de economia regional; dentro desse catálogo, no schema chamado Landing, criaremos um volume chamado arquivos.
Vamos aguardar a execução. Assim que for concluída, os catálogos e schemas estarão criados. Voltamos à seção de Catálogo e usamos o botão de atualização, Refresh (atualizar), no topo da tela. O catálogo de economia regional já aparece. Dentro dele, visualizamos três schemas criados: Bronze, Landing e Silver. No schema Landing, verificamos o volume arquivos.
Criando schema pela interface
Como criamos o schema pela interface?
Primeiro, relembramos a criação do catálogo: no botão + (mais), Create Catalog. Depois que o catálogo estiver criado, clicamos nele para abrir suas informações. No painel lateral, há um botão denominado Create Schema. Ao clicar, o sistema abre as propriedades do catálogo; aquele comentário inserido no código aparece automaticamente em Description (descrição).
Selecionamos Create Schema e informamos o nome do schema. Como mencionado, deixamos o Gold de fora no código para criarmos manualmente aqui. Escrevemos Gold, que corresponde ao schema da camada Medalhão Gold. No campo Storage Location (local de armazenamento), não preenchemos nada para manter o padrão do ambiente. Podemos inserir um comentário; por exemplo: Estrutura de dados para negócio com modelagem dimensional e metric views. Em seguida, clicamos em Create.
Concluímos, assim, a criação de um schema pela interface visual, sem utilizar código, dentro do nosso catálogo de economia regional.
Criando volume pela interface
Como criamos um volume pela interface?
Caso queiramos criar um volume sem usar código, procedemos assim:
- No topo do schema desejado, clicamos no botão + (mais) e selecionamos Create Volume.
- Informamos o nome do volume, por exemplo: Volume name exemplo.
- Mantemos o tipo como Managed (gerenciado).
- Definimos o caminho. Ele já vem preenchido porque selecionamos o schema Gold, mas podemos alterar para outro schema ou outro catálogo, se necessário.
- Clicamos em Create.
Feito isso, teremos um volume dentro do schema Gold. Não utilizaremos esse volume para outras finalidades; ele serve apenas como exemplo.
Justificando o volume na camada landing
Por que criamos esse volume em um schema chamado Landing?
Poderíamos colocar esse volume na Bronze? Poderíamos, não haveria problema técnico. Entretanto, conceitualmente, criamos o Landing como uma área de repouso do dado antes de sua ingestão oficial no ambiente.
Nas camadas Bronze, Silver e Gold (modelo Medalhão), a ideia é que os dados já estejam no formato padrão do ambiente. Neste cenário, é uma Tabela Delta gerenciada pelo Databricks. Mantemos essa separação conceitual para organizar melhor o fluxo de dados.
No Landing, o arquivo permanece no formato de origem, por exemplo, um CSV. Na Bronze, o dado que estava em CSV no Landing já estará transformado no modelo de Tabela Delta. Por isso, criamos o Landing como área de repouso. Muitas vezes, essa área pode ser diretamente o Amazon S3, o Azure Blob Storage etc. É o local onde os arquivos chegam sem transformação, no formato original.
Por exemplo:
- Se a origem for FTP com arquivos CSV, o CSV chega no Landing.
- Se a origem for uma API gerando JSON, o arquivo JSON ficará no Landing.
- Se a origem for um banco de dados com exportação em formato CDC (Insert, Update, Delete), múltiplos arquivos TXT chegarão ao Landing.
Depois, lemos a partir do Landing e carregamos para a Bronze já no modelo de Tabela Delta. Tudo certo.
Enviando arquivo ao volume pela interface
Voltamos ao nosso script no workspace (espaço de trabalho) do Notebook (bloco de notas) da aula de Databricks, em "notebooks", "Bronze População". Vamos para a próxima etapa: enviar o arquivo que estamos simulando como um CSV proveniente, por exemplo, de um FTP. Esse CSV será colocado no volume que criamos, em "volumes", "economia regional", "landing", "arquivos". Podemos realizar esse envio pela interface gráfica.
Primeiro, verificamos se o diretório está vazio executando a célula com display. Não há nada lá dentro. Para fazer isso no notebook:
display(dbutils.fs.ls(LANDING_POPULACAO))
Para enviar o arquivo, podemos utilizar o Catálogo. Há duas opções: abrir o Catálogo no próprio editor ou acessar a mesma área que utilizamos anteriormente. Em ambos os cenários, as opções são similares.
No Catálogo, clicamos no ícone de adição e escolhemos a opção Add data (adicionar dados) > Upload to volume. Selecionamos o arquivo do nosso curso que simula um arquivo vindo de um FTP, ou de um servidor de arquivos. Esse arquivo não vem de API. Dentro da nossa pasta do projeto, ele está na pasta de arquivos do projeto. Pesquisamos por "Projeto Dados" e selecionamos o arquivo população municípios 2019. Arrastamos o arquivo para a área de envio.
Definimos a estrutura de destino: em "todos os volumes", selecionamos "arquivos" e, dentro dele, "população". A estrutura final deve ficar em "economia regional", "landing", "arquivos", "população". Confirmamos que o volume correto está selecionado e clicamos em upload (envio). Observamos que o caminho exibido está correto: "Volumes", "economia regional", "landing", "arquivos", "população". Iniciamos o envio.
Em seguida, fechamos a barra lateral intermediária e executamos novamente a célula com display para verificar se o arquivo foi enviado como esperado. O arquivo foi carregado corretamente e agora está no sistema de arquivos do nosso ambiente, conforme o orientado.
Neste ponto, já estamos com o arquivo dentro do nosso ambiente. Criamos os catálogos, os esquemas do modelo medalhão e a camada "landing" para chegada de arquivos. Criamos o volume na camada "landing" para chegada de arquivos provenientes de sistemas de arquivos/servidores de arquivos. Enviamos o arquivo para essa camada "landing". Esse será o arquivo manual que vamos utilizar.
Lendo arquivos e avaliando a inferência de esquema
Agora, vamos realizar a leitura desses arquivos e configurar o Auto Loader para consumir continuamente. A ideia do Auto Loader, disponibilizado pelo Databricks via cloudFiles, é identificar sempre que um novo arquivo chegar e realizar a leitura e a ingestão do arquivo, mantendo o referencial do arquivo.
Temos um script que executa esse processo. Nele, criamos um DataFrame (estrutura de dados tabular) chamado Bronze, utilizando Spark SQL para fazer o select via cloudFiles na camada "landing", no formato CSV, com header (cabeçalho) e inferSchema (inferência de esquema) ativados, marcando qual é o administrador desse arquivo e definindo o encoding (codificação) como UTF-8. Em seguida, executamos printSchema (impressão do esquema), isto é, a estrutura de colunas desse arquivo, e mostramos uma amostra dos dados. Nesta etapa, o script apenas realiza a leitura e o consumo do dado. No código abaixo, faremos a leitura exploratória com a função read_files, mantendo os parâmetros descritos:
df_bronze = spark.sql(f"""
SELECT
*
FROM read_files(
'{LANDING_POPULACAO}',
format => 'csv',
header => true,
inferColumnTypes => true,
delimiter => ';',
encoding => 'UTF-8'
)
""")
df_bronze.printSchema()
display(df_bronze.limit(5))
Ao executar, o arquivo que enviamos é lido. A variável de caminho que criamos anteriormente já está sendo utilizada para apontar para o volume correto. O printSchema identificou as colunas: código do município, nome do município, sigla do estado, nome do UF, região, ano, população, além de uma coluna extra criada pelo sistema. Logo abaixo, há um print (impressão) das 5 primeiras linhas do arquivo.
Com isso, já conseguimos ler o arquivo e identificar pontos de atenção. É importante notar que os cabeçalhos do CSV contêm espaços e acentuação. No nosso trabalho como pessoas de engenharia de dados, precisamos explorar o arquivo e os dados para entender o que será necessário preparar na etapa seguinte. Aqui, já percebemos a necessidade de ajustar a nomenclatura das colunas para um padrão recomendado.
Com o inferSchema que aplicamos, o mecanismo consegue identificar automaticamente o tipo de dado por amostragem. No visualizador, os ícones com 1, 2, 3 indicam colunas numéricas (inteiros), enquanto o ícone “abc” indica colunas de texto (string (texto)). Isso também aparece refletido no printSchema. Entretanto, é fundamental ter cautela: a inferência sempre se baseia em uma amostragem do dado. Pode acontecer de a amostragem inicial indicar apenas valores inteiros, mas existir, em linhas posteriores, um cenário com valores alfanuméricos ou até hexadecimais. Nesse caso, a leitura falhará ao encontrar valores que não correspondem ao tipo inferido inicialmente.
Portanto, o inferSchema é muito útil, mas precisamos sempre validar se a inferência está correta para o conjunto completo de dados.
Criando tabela bronze em delta
O próximo passo é transformar esse dado em uma tabela Delta. Subimos o arquivo e fizemos a leitura para identificá-lo. Agora precisamos criar a tabela Delta que irá para a camada Bronze.
A camada Bronze é onde os dados brutos ficam disponíveis. O objetivo desta etapa é garantir que o dado chegue, seja ingerido e passe a existir oficialmente no ambiente. Entretanto, nessa camada o dado ainda não está pronto para consumo pela área de negócio nem para geração de insights; ele apenas existe no ambiente. Na camada Silver, quando aplicamos qualidade, o dado passa a poder ser consumido.
Um ponto de atenção é que identificamos que os cabeçalhos contêm acentos e espaços. Para lidar com isso, vamos configurar a propriedade delta.columnMapping.mode com o valor name. Sem essa configuração, poderíamos ter falha de gravação por causa de nomes de colunas inválidos (com acentos e espaços). A ideia é ativar o mapeamento de colunas e permitir que a Bronze faça a cópia fiel da origem. A padronização será realizada adiante, nas camadas seguintes. Aqui, nosso objetivo é garantir que o dado siga estritamente conforme a origem.
Este é um exemplo com o qual estamos trabalhando; existem várias maneiras de resolver. Cada empresa terá políticas diferentes para suas camadas, e isso é importante de entender. Estamos adotando um modo mais permissivo para garantir que o dado não seja corrompido nem se perca na chegada, considerando que estamos lidando com um sistema de arquivos, e arquivos aceitam qualquer conteúdo.
Para isso, vamos utilizar Spark SQL, executando um CREATE OR REPLACE TABLE no catálogo, posicionando a tabela na camada Bronze e nomeando-a populacao_municipios. No comando, referenciamos a variável de catálogo criada no início do script, definimos o schema e, dentro dele, o nome da tabela populacao_municipios.
Também vamos incluir um comentário na tabela. Essa é uma boa prática de gerenciamento de metadados, pois sistemas de IA generativa podem usar esses comentários para compreender o propósito da tabela.
Nas propriedades da tabela, adicionaremos a configuração delta.columnMapping.mode = name para garantir a permissividade nos nomes das colunas.
Em seguida, vamos executar um SELECT sobre o nosso volume (utilizando a variável que aponta para esse volume), selecionando todas as colunas e adicionando colunas de metadado: uma com o nome do arquivo de origem (filename) e outra com o momento exato da carga. Assim, já inserimos rastreabilidade e auditoria nos dados.
spark.sql(f"""
CREATE OR REPLACE TABLE {CATALOG}.bronze.populacao_municipios
COMMENT 'Bronze: população residente estimada por município (IBGE), cópia fiel do CSV de origem.'
TBLPROPERTIES('delta.columnMapping.mode' = 'name')
AS
SELECT
origem.*,
origem._metadata.file_name AS _arquivo_origem,
current_timestamp() AS _ingerido_em
FROM read_files(
'{LANDING_POPULACAO}',
format => 'csv',
header => true,
inferColumnTypes => true,
delimiter => ';',
encoding => 'UTF-8'
)
AS origem
""")
Ao executar, vamos criar a tabela e consumir o arquivo. O ambiente retorna um objeto do tipo DataFrame (estrutura tabular) e popula o campo numRowsAffected (número de linhas afetadas).
Validando a tabela criada
Agora, vamos validar se os arquivos estão dentro do esperado. Criamos:
- A variável tabela, apontando para o caminho da tabela;
- A variável total, que executa um count nessa tabela recém-criada;
- A variável ufs, que faz um count agrupando por UF e em seguida um collect para trazer os dados agrupados;
- A variável coluna, com as informações dos tipos das colunas.
Em seguida, executamos um print para exibir: quantidade total de linhas, quantas UFs distintas existem e quantas colunas a tabela possui. Obtemos o output (saída) com essas informações e avaliamos os resultados.
import json
TABELA = f"{CATALOG}.bronze.populacao_municipios"
total = spark.table(TABELA).count()
ufs = spark.sql(f"SELECT count(distinct `Sigla UF`) as n FROM {TABELA}").collect()[0]["n"]
colunas = [f"{f.name} ({f.dataType.simpleString()})" for f in spark.table(TABELA).schema.fields]
print(f"Linhas: {total}")
print(f"UFs distintas: {ufs}")
print(f"Colunas: {colunas}")
dbutils.notebook.exit(json.dumps({"linhas": total, "ufs": ufs, "colunas": colunas}, ensure_ascii=False))
O esperado é termos 5.571 linhas. O ambiente encerrou o notebook (caderno) com 5.571 linhas, 27 estados e a lista de colunas existentes, exatamente aquelas que já havíamos verificado. Funcionou conforme esperado.
Explorando a tabela no catálogo do databricks
Essa foi nossa primeira ingestão no ambiente e a criação da estrutura dos dados. Para visualizarmos no catálogo do Databricks, podemos acessar o Catálogo e atualizar para garantir que está atualizado. Em “arquivos”, veremos o arquivo que subimos manualmente. Na Bronze, veremos a tabela criada populacao_municipios. Ao clicar no nome, a descrição aparece, permitindo entender o que a tabela representa. Também visualizamos as colunas e o tipo da tabela: Managed, com formato Delta. Podemos ainda configurar elementos como tags e indicadores de qualidade de dados.
Na aba Sample Data (dados de exemplo), é possível executar uma consulta rápida sobre os dados para avaliação. Podemos selecionar o compute (computação) e ele fará a carga dos dados, por exemplo, executando um LIMIT 5 para retornar algumas linhas de amostra.
Na aba Lineage (linhagem), podemos clicar em See Lineage Graph (ver grafo de linhagem) para entender a origem e o fluxo desses dados. Veremos que eles vêm do volume de arquivos e geram a tabela populacao_municipios. Por enquanto, não há consumidores conectados a essa tabela, mas já é possível compreender visualmente o fluxo: origem em arquivo e geração da tabela. Podemos voltar ao Sample Data (dados de exemplo) para carregar novamente um exemplo dos dados.
Conferimos que as colunas estão do jeito que vieram da origem. Incluímos a coluna de metadado com o nome do arquivo CSV de origem e o momento de ingestão. Tudo correto.
Concluímos toda a etapa da primeira ingestão baseada em um arquivo manual — simulando a chegada a partir de um servidor de arquivos — e realizamos a passagem da camada Landing (aterrissagem) para a Bronze, já no formato Delta.
Com isso, encerramos este vídeo. Eu te vejo no próximo.