Módulo 11 · Engenharia de Software · Aula 10

Armazenamento em
Grande Escala

Formato físico, organização em disco e propriedades distribuídas do armazenamento, com a construção de um lakehouse local em MinIO e DuckDB sobre os dados da Olist.

Computação 2 · Prof. Afonso Brandão · 01/09/2026

Object storage Parquet Réplica e quórum Lakehouse DuckDB · Olist

Continuidade · das Aulas 2-9 para a Aula 10

Do modelo lógico ao dado gravado em muitas máquinas

A modelagem define o que é gravado, e a governança define quem responde por ele. Esta aula trata de onde, como e em quantas cópias o dado fica guardado.

Aulas 2-4Modelagem dimensional
Aula 5Arquitetura de dados
Aula 7Otimização física
Aulas 8-9Governança de dados
Aula 10Armazenamento distribuído
A decisão de armazenamento antecede a de consulta: formato, particionamento, replicação e classe de storage determinam quantos bytes cada consulta lê, quanto ela custa e o que acontece quando um nó cai.

Roteiro · 120 minutos · uma hora de exposição, uma hora de prática

A primeira hora estabelece o critério, e a segunda constrói o lakehouse em sala

A segunda hora é atividade em grupo: ingerir os dados da Olist em MinIO e DuckDB, modelar dimensionalmente e responder a três perguntas de negócio.

1ª hora · Bloco 01
30 min

Fundamentos

O que escala, object storage, formato físico, cardinalidade e layout.

1ª hora · Bloco 02
18 min

Sistemas distribuídos

Partição, réplica, quórum, consistência e o efeito no pipeline.

1ª hora · Bloco 03
12 min

Lakehouse

Vocabulário, camadas por contrato, ciclo de vida e custo.

2ª hora · Atividade
60 min

Card de trabalho

MinIO e DuckDB, modelo dimensional e três respostas com SQL executado.

Objetivo: escolher o formato, a organização física, as camadas e o ciclo de vida para dados em escala, e provar a escolha construindo o lakehouse que responde às perguntas do negócio.

Bloco 1 · O que escala

As dimensões que determinam a escala

O dimensionamento por volume isolado desconsidera as variáveis que determinam o comportamento do sistema sob carga.

Throughput de escrita

Quanto entra por unidade de tempo e em que tamanho de lote.

MB/s · eventos/s

Concorrência de leitura

Quantos consumidores simultâneos disputam o mesmo conjunto.

consultas simultâneas

Latência exigida

O tempo de resposta aceitável para cada classe de consumo.

segundos · minutos

Tamanho dos arquivos

Poucos arquivos grandes ou muitos pequenos mudam o gargalo.

MB por arquivo

Retenção e recuperação

Por quanto tempo se guarda e em quanto tempo se restaura.

meses · RTO

Custo por consulta

Bytes varridos por pergunta respondida, somados ao custo de guarda.

bytes varridos
Dimensionar exclusivamente pelo volume armazenado. Um layout adequado a petabytes falha sob dez mil leitores concorrentes.

Bloco 1 · Object storage

O serviço garante a durabilidade, e a organização cabe ao projeto

Buckets e objetos oferecem escala e durabilidade, mas exigem convenções de nome, controle de acesso, versionamento e catálogo externo.

Modelo de objetos

Namespace plano e escrita imutável

O objeto é gravado integralmente e identificado por uma chave. A hierarquia de diretórios é aparente: o segmento que se assemelha a pasta constitui prefixo da chave, e a renomeação equivale a uma reescrita.

A convenção de prefixo constitui, portanto, a única estrutura disponível, e deve ser definida antes da gravação do primeiro arquivo.

Padronize a convenção de prefixo antes do primeiro arquivo.

Ative versionamento e defina explicitamente quem pode apagar.

Mantenha catálogo externo: o bucket não descreve o dado.

Separe zonas de ingestão, processamento e consumo por prefixo.

Registre a política de exclusão junto com a de retenção.

Tratar o bucket como sistema de arquivos. Sem catálogo, não há registro do que existe nem do schema vigente.

Bloco 1 · Formato físico

O formato se escolhe pelo padrão de leitura predominante

Cada formato otimiza um padrão de acesso e onera o padrão oposto.

CSVTexto delimitado
Favorece
Portabilidade e inspeção manual, com leitura por qualquer ferramenta.
Custa
Sem tipo nem estatística: toda consulta lê o arquivo inteiro.
Use quando
Intercâmbio pontual com terceiros e cargas de aterrissagem.
PARQColunar comprimido
Favorece
Leitura colunar, compressão por tipo e pruning por estatística.
Custa
Escrita mais cara, e leitura de linha inteira pouco eficiente.
Use quando
A consulta seleciona poucas colunas de muitas linhas.
AVROLinha serializada
Favorece
Serialização por registro e evolução de schema declarada.
Custa
Varredura analítica lê todas as colunas do registro.
Use quando
Transporte de eventos e gravação contínua registro a registro.
Escolha a compressão pelo par CPU/leitura, uma vez que o maior fator de redução constitui critério insuficiente. Fixe o formato por camada e documente cada exceção.

Bloco 1 · Por dentro do arquivo colunar

O pruning ocorre no row group, na coluna e na estatística de bloco

O arquivo guarda, por bloco e por coluna, mínimo, máximo e contagem de nulos. Esse metadado permite descartar a leitura sem abrir o dado.

Row group 1 · pedidos de janeiro
datamin-max
valorlido
Row group 2 · pedidos de fevereiro
Leitura seletiva

Duas reduções independentes

A projeção descarta as colunas não requisitadas, e o predicado descarta os blocos cujo intervalo não satisfaz o filtro. As duas se somam, e a segunda depende de o dado estar ordenado de forma coerente com o filtro.

Indicador

Bytes lidos por consulta

Essa métrica revela a qualidade do layout. O tempo de resposta varia com o cache, e os bytes lidos refletem a estrutura física do dado. Meça antes e depois de cada alteração estrutural.

Bloco 1 · Pruning em uma consulta

A mesma consulta é podada no diretório, no bloco e na coluna

Fato particionado por ano e mês em vinte e cinco diretórios, com dez colunas gravadas no arquivo e as linhas ordenadas por data_pedido dentro de cada partição. Cada nível reduz o conjunto antes do seguinte.

-- faturamento da primeira quinzena de novembro de 2017 SELECT sum(valor_total) FROM fato_pedido WHERE ano = 2017 AND mes = 11 AND data_pedido BETWEEN DATE '2017-11-01' AND DATE '2017-11-15';
Nível de descarteO que o motor consultaEfeito nesta consulta
Diretório · partiçãoOs prefixos ano= e mes= das chavesApenas os arquivos sob ano=2017/mes=11 são abertos; os demais são descartados pelo caminho, sem leitura de conteúdo.
Bloco · row groupO rodapé, com mínimo e máximo de data_pedido por blocoO bloco cujo intervalo declarado está inteiramente após 15 de novembro é descartado sem que o dado seja lido.
Coluna · projeçãoO schema declarado no rodapéDuas das dez colunas gravadas são lidas, data_pedido e valor_total; ano e mes vêm do caminho e não são lidas do arquivo.
Condição do segundo nível: o dado precisa estar ordenado pela coluna filtrada. Sem ordenação por data_pedido, cada bloco declara um intervalo que cobre o mês inteiro, e nenhum bloco é descartado. O arquivo permanece colunar e o descarte por bloco deixa de ocorrer.

Bloco 1 · Por dentro do arquivo orientado a registro

No Avro, o schema acompanha o dado dentro do próprio arquivo

O cabeçalho declara o schema em JSON, e cada bloco seguinte guarda registros inteiros em binário. A leitura devolve o registro completo, sem descarte de coluna.

Cabeçalho · uma vez por arquivo
Bloco 1 · 2 000 registros
datalido
valorlido
clientelido
reviewlido
Bloco 2 · 2 000 registros
datalido
valorlido
clientelido
reviewlido
Contrato

Evolução de schema declarada

O consumidor resolve o schema de escrita contra o próprio schema de leitura. O valor padrão do campo acrescentado permite ao consumidor novo ler registros antigos, o consumidor antigo ignora o campo que desconhece, e o alias permite renomear a coluna sem quebrar o contrato. A compatibilidade é verificável antes da publicação.

Uso

Transporte de eventos e aterrissagem

O marcador de sincronização divide o arquivo, e o consumidor lê a partir de qualquer ponto, o que preserva o paralelismo. O acréscimo de registros é barato, enquanto o Parquet se torna legível apenas quando o rodapé é gravado.

Padrão do pipeline: Avro no transporte de eventos e na aterrissagem, Parquet na camada analítica, com a conversão na fronteira entre a bronze e a silver.

Bloco 1 · Layout

Particione por coluna de filtro estável

A partição só ajuda quando aparece na cláusula de filtro e quando sua cardinalidade não pulveriza o conjunto.

# Layout que sustenta pruning lake/gold/fato_pedido/ ano=2017/mes=10/parte-0001.parquet // 512 MB ano=2017/mes=11/parte-0001.parquet // 480 MB # Layout que pulveriza o conjunto lake/raw/fato_pedido/ dia=2017-10-02/hora=10/cliente=9ef4/... // 1 MB
Regra prática: particione por coluna de filtro estável e de cardinalidade baixa ou moderada. O cliente e o identificador de pedido servem como chave de consulta, e a cardinalidade elevada os desqualifica como critério de partição.

Confirme que a coluna de partição aparece nos filtros mais frequentes.

Monitore o tamanho médio de arquivo e compacte quando ele cair.

Meça bytes lidos por consulta como indicador da qualidade do layout.

Evite níveis de partição que gerem diretórios com poucos registros.

Reordene o dado dentro da partição pela coluna de filtro secundária.

Ingestão em micro-lotes sem compactação. Milhões de arquivos de 1 MB tornam o metadado o gargalo.

Bloco 1 · Cardinalidade

A chave de partição se avalia pelo número de valores distintos

Candidatas medidas no conjunto da Olist, sobre os 112 650 itens de pedido. A coluna serve como critério quando produz partições grandes o bastante para justificar um arquivo.

Coluna candidataValores distintosItens por partiçãoVeredito
ano337 550Grosseira: o filtro por mês varre o ano inteiro.
ano e mês244 694Adequada: filtro frequente e cardinalidade estável ao longo do tempo.
customer_state2742% em SPEnviesada: uma partição concentra a carga e anula o paralelismo.
product_id32 9513,4Pulverizada: o rodapé de cada arquivo supera o dado que ele descreve.
order_id98 6661,1Pulverizada: aproximadamente uma linha por arquivo.
timestamp da compra98 1121,1Pulverizada: a precisão de segundo torna a coluna quase única.
Para a coluna de alta cardinalidade que aparece em filtro de igualdade, a ferramenta é a ordenação dentro da partição: o mínimo e o máximo por row group descartam blocos sem criar diretório algum.
Particionar por identificador de pedido ou por timestamp com precisão de segundo. A codificação por dicionário depende de repetição dentro do bloco, e com uma linha por arquivo o custo do formato colunar é pago sem contrapartida.

Bloco 1 · Compactação

O problema dos arquivos pequenos

Cada arquivo custa uma abertura, uma leitura de rodapé e uma entrada de metadado. Em escala, esse custo fixo domina o tempo de resposta.

Sintoma observávelCausa provávelCorreçãoEvidência de que funcionou
Tamanho médio de arquivo em quedaIngestão em micro-lotes sem consolidação.Rotina de compactação por partição fechada.Tamanho médio volta à faixa alvo.
Listagem lenta antes da consultaExcesso de objetos por prefixo.Compactação e revisão dos níveis de partição.Tempo de planejamento cai.
Consulta filtrada lê o conjunto inteiroFiltro não corresponde à coluna de partição.Reparticionar ou reordenar pela coluna de filtro.Bytes lidos por consulta caem.
Custo de varredura acima do orçadoFormato sem estatística ou partição ausente.Converter para colunar e particionar.Custo por consulta cai com o mesmo resultado.
Compare o resultado antes de comparar o desempenho: alteração de layout que modifica a resposta constitui defeito.

Bloco 2 · Sistemas distribuídos

Em escala, o armazenamento se realiza por particionamento e replicação

O conjunto excede a capacidade de um disco e a confiabilidade de uma máquina isolada. As duas respostas estruturais são a divisão e a repetição do dado.

Particionamento

Divisão do conjunto para capacidade e paralelismo

O conjunto é fatiado por uma chave e cada fatia reside em um nó. O mecanismo acrescenta capacidade e paralelismo, ao custo de distribuição desigual quando a chave é enviesada e de consultas que precisam cruzar fatias.

  • A chave uniforme evita a sobrecarga de um nó.
  • Junção entre partições distintas exige tráfego de rede.
Replicação

Repetição da fatia para durabilidade e leitura próxima

Cada fatia é gravada em mais de um nó. O mecanismo acrescenta durabilidade e disponibilidade de leitura, ao custo de espaço adicional e da necessidade de manter as cópias coerentes entre si.

  • A falha de um nó preserva o dado nas demais réplicas.
  • As cópias divergentes exigem regra de reconciliação.
Os serviços de object storage implementam ambos os mecanismos, e as garantias de consistência dependem do serviço: o Amazon S3 oferece consistência forte de leitura após escrita e de listagem desde dezembro de 2020, e o MinIO também é consistente; outros serviços admitem leitura e listagem defasadas.

Bloco 2 · Réplica e quórum

O número de confirmações que a escrita exige antes de ser considerada válida

O quórum estabelece a relação entre latência e garantia: quanto maior o número de réplicas exigidas na confirmação, maior o tempo de resposta e menor a probabilidade de perda.

Nó Agravou ✓
Nó Bgravou ✓
Nó Cindisponível
W + R > N escrita 2 · leitura 2 · réplicas 3
Leitura: com três réplicas, exigir duas confirmações na escrita e duas na leitura garante que toda leitura alcança ao menos uma cópia atualizada, e o sistema continua a servir com um nó fora de operação.

Declare o fator de replicação e o que ele custa em espaço.

Distinga durabilidade, que preserva o dado gravado, de disponibilidade, que garante a leitura no instante solicitado.

Registre o comportamento esperado quando um nó cai durante a escrita.

Distribua as réplicas em domínios de falha distintos.

Trate o reprocessamento como rotina, uma vez que a falha parcial constitui evento esperado.

Confundir réplica com backup. A réplica propaga o apagamento, e o backup preserva o estado anterior.

Bloco 2 · Consistência

Regimes de consistência entre réplicas

Sob partição de rede, o sistema opta entre responder com estado possivelmente desatualizado e recusar a resposta. Na ausência de partição, a opção se dá entre consistência e latência.

Consistência forte

Toda leitura devolve a última escrita

Exige coordenação entre réplicas antes de responder, ao custo de latência e de disponibilidade durante falhas. Constitui o regime esperado do catálogo de tabelas.

Consistência eventual

As cópias convergem após um intervalo

A leitura pode devolver estado anterior por algum tempo. Aceitável para a listagem de objetos e para as réplicas de leitura, e inaceitável para o ponteiro que indica a versão vigente da tabela.

CAP e PACELC

A escolha permanece na ausência de partição

Havendo partição de rede, a escolha recai entre consistência e disponibilidade, e na ausência de partição, entre consistência e latência. Toda arquitetura de dados realiza essa escolha, e explicitá-la integra o trabalho de projeto.

Pergunta de projeto: quais leituras do parceiro admitem estado defasado em cinco minutos e quais exigem o estado mais recente?

Bloco 2 · Consequências práticas

As propriedades distribuídas e seus efeitos no pipeline

Cada garantia ausente se converte em regra de engenharia na ingestão e na publicação do dado.

Propriedade distribuídaEfeito observável no lakehouseRegra de engenharia correspondente
Escrita não atômicaA consulta enxerga arquivos de uma carga ainda incompleta.Publicar por troca de ponteiro: só o commit no catálogo torna a versão visível.
Listagem eventual (depende do serviço)Arquivo recém-gravado pode não aparecer de imediato. S3 (desde dez/2020) e MinIO listam de forma consistente, mas a listagem inclui arquivos de carga em andamento.Registrar os arquivos no manifesto da tabela e consultá-lo no lugar da listagem do bucket.
Reentrega de mensagensO mesmo pedido é ingerido duas vezes após uma falha parcial.Ingestão idempotente por chave natural e janela, com deduplicação na camada silver.
Falha parcial de cargaMetade das partições atualizada, metade não.Reprocessar a partição integralmente, tratando a carga como unidade atômica.
Relógios distintosEventos chegam fora de ordem e com atraso.Separar data do evento da data de ingestão e declarar a janela de atraso aceita.

Bloco 3 · Vocabulário

Data warehouse, data lake, data mart e lakehouse

Os quatro coexistem no mesmo projeto. A distinção decorre do momento de aplicação do schema e do público atendido.

RepositórioSchemaDado que aceitaPúblicoUso típico
Data warehouseDefinido na escritaEstruturado e tratadoAnalista de negócioRelatório, BI, série histórica
Data lakeAplicado na leituraBruto: estruturado, semi e não estruturadoEngenheiro e cientista de dadosExploração e aprendizado de máquina
Data martDefinido na escrita, recortadoEstruturado, muitas vezes agregadoUma área específicaAnálise departamental
LakehouseDeclarado no catálogo, sobre arquivo abertoBruto e tratado, na mesma plataformaOs três públicos acimaUma cópia servindo BI e ciência de dados
O lakehouse abriga o modelo dimensional das Aulas 2-4 sobre arquivos abertos, com garantias transacionais.

Bloco 3 · Lakehouse

Tabela transacional sobre storage aberto

Formatos de tabela acrescentam atomicidade, histórico e evolução de schema a arquivos que continuam abertos e legíveis por vários motores.

Atomicidade

Escrita concorrente sem leitura suja

A tabela publica um novo snapshot apenas quando a escrita conclui. Quem lê continua vendo a versão anterior até a troca do ponteiro.

Histórico

Correção retroativa auditável

O histórico de snapshots permite reprocessar um período e comparar versões. O histórico torna reversível a operação de apagar e regravar.

Evolução

Schema declarado no catálogo

Acrescentar coluna, renomear e alterar tipo passam pelo catálogo, com registro de quando a mudança entrou em vigor.

Use tabela transacional quando houver escrita concorrente ou correção retroativa. Na ausência de ambas, arquivo particionado com catálogo é suficiente, e corresponde ao que será construído no laboratório.

Bloco 3 · Camadas

Bronze, silver e gold se distinguem pelo contrato de qualidade que assumem

A existência de uma camada se justifica pela mudança do compromisso de qualidade que ela assume.

Bronze

Contrato: fidelidade à origem. O dado é preservado como chegou, com carimbo de ingestão.
  • Sem correção nem descarte
  • Reprocessável a qualquer momento
  • Retenção longa, acesso restrito

Silver

Contrato: conformidade. Tipos, chaves e regras de qualidade validados e documentados.
  • Deduplicação e padronização
  • Rejeitos registrados com o motivo
  • Base para toda camada de consumo

Gold

Contrato: semântica de negócio. Métricas e dimensões com definição acordada.
  • Modelo dimensional das Aulas 2-4
  • Otimizada para o padrão de consulta
  • Exposta ao consumo analítico
Manter bronze, silver e gold sob o mesmo contrato de qualidade. Sem mudança de compromisso, as três constituem cópias do mesmo conjunto.

Bloco 3 · Ciclo de vida

A classe de storage acompanha a idade do dado

A transição entre classes é definida como regra automática, aplicada conforme a frequência de acesso decresce.

Quente

Acesso frequente e latência baixa. Maior custo de guarda, menor custo de leitura.

Período corrente e comparativo imediato.
Morna

Acesso ocasional. Guarda mais barata, com custo de recuperação por leitura.

Fechamentos anteriores e séries históricas recentes.
Fria

Acesso raro, com tempo mínimo de permanência e taxa de recuperação relevante.

Dados com mais de doze meses mantidos por exigência analítica.
Arquivo

Retenção legal. Recuperação sob demanda, medida em horas.

Obrigação regulatória e auditoria.
Antes de escolher a classe, defina a janela de retenção e o tempo aceitável de recuperação, os dois parâmetros dos quais a política de lifecycle deriva.

Bloco 3 · Custo e segurança

A varredura responde pela maior parte do custo

O baixo custo de guarda favorece o acúmulo. A despesa relevante decorre da leitura repetida de dados que poderiam ter sido movidos para classe mais fria ou expurgados.

Aplique regra de lifecycle automática para transição e expurgo.

Atribua orçamento e alerta de custo por domínio.

Conceda acesso mínimo por prefixo e audite o uso.

Cifre em repouso e em trânsito, com chave sob custódia declarada.

Separe dado pessoal por prefixo próprio, com retenção própria.

Vínculo com as Aulas 8-9

A governança se materializa no prefixo

A classificação, a propriedade e a retenção definidas na governança têm efeito quando se convertem em política de acesso, regra de lifecycle e rótulo de custo no próprio storage.

Indicador de FinOps

Custo por domínio e por consulta

O rótulo por domínio decompõe o custo do armazenamento e atribui a cada parcela um responsável pela redução.

Reter todo o acervo indefinidamente sob o argumento do baixo custo de guarda. A despesa relevante decorre da varredura repetida desse acervo.

Segunda hora · Card de trabalho em sala

Três perguntas de negócio respondidas sobre um lakehouse construído em sala

Em grupo, sobre os dados da Olist: ingerir, modelar dimensionalmente e responder com SQL executado sobre a camada analítica.

1

Qual região está com menor volume de vendas?

Declare antes o que é volume — receita ou quantidade de itens — e mantenha a definição nas três respostas.

2

Qual é o produto mais vendido nessa região?

Diga se "produto" é o item individual ou a categoria, e por que essa escolha responde melhor à pergunta do negócio.

3

Qual foi a sazonalidade desse produto ao longo do tempo?

Série mensal do primeiro ao último mês de venda, com a leitura do que explica os picos e os vales.

10 minMinIO no ar e dados baixados
15 minBronze: CSV para Parquet no bucket
20 minModelo dimensional: fato e dimensões
10 minAs três consultas, executadas
5 minEvidências e conclusão escrita
Entrega: o SQL versionado, as três respostas com número, e uma frase por pergunta explicando o que o número significa para o negócio.

Segunda hora · Arquitetura

O MinIO como object storage e o DuckDB como motor analítico

A arquitetura de um lakehouse em nuvem reproduzida em dois processos locais: um serviço compatível com S3, um motor analítico e Parquet como formato de intercâmbio.

origemCSV da OlistOito tabelas, 65 MB, disponíveis para download
object storageMinIO · bucket lakehouseAPI S3 local: bronze, silver e gold em Parquet particionado
motorDuckDB · httpfsLê e grava em s3:// e responde às três perguntas
Justificativa do MinIO: o bucket impõe o tratamento de chave, prefixo, credencial e endpoint, que são os elementos alterados quando o lakehouse migra da máquina local para a nuvem do parceiro.

Segunda hora · Preparação · 10 minutos

A colocação do bucket no ar e a conexão do motor

Três comandos no terminal e um bloco de configuração no DuckDB estabelecem o ambiente do laboratório.

# 1. MinIO no ar (console em http://localhost:9001) docker run -d --name minio -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=minioadmin \ quay.io/minio/minio server /data --console-address ":9001" # 2. bucket do laboratório docker run --rm --network host --entrypoint sh quay.io/minio/mc -c \ "mc alias set local http://127.0.0.1:9000 minioadmin minioadmin && \ mc mb --ignore-existing local/lakehouse" # 3. dados e motor unzip olist-csv.zip -d olist/ && duckdb lakehouse.duckdb
-- 4. no DuckDB: extensão S3 e credencial do MinIO INSTALL httpfs; LOAD httpfs; CREATE OR REPLACE SECRET minio ( TYPE s3, KEY_ID 'minioadmin', SECRET 'minioadmin', ENDPOINT 'localhost:9000', URL_STYLE 'path', USE_SSL false ); CREATE SCHEMA IF NOT EXISTS silver; CREATE SCHEMA IF NOT EXISTS gold;
Sem URL_STYLE 'path' e USE_SSL false, o DuckDB adota o estilo de domínio da AWS e a conexão falha sem mensagem esclarecedora.

Segunda hora · Camada bronze · 15 minutos

A gravação do CSV bruto em Parquet dentro do bucket

A camada preserva o conteúdo de origem. O ganho é de formato e de layout, e a contagem verifica a integridade da carga.

-- pedidos: particionado por ano e mês, com carimbo de ingestão -- reexecução: antes, mc rm -r --force local/lakehouse/bronze/orders COPY ( SELECT *, now() AS ingerido_em, CAST(strftime(order_purchase_timestamp, '%Y') AS INT) AS ano, CAST(strftime(order_purchase_timestamp, '%m') AS INT) AS mes FROM read_csv_auto('olist/olist_orders_dataset.csv', header = true) ) TO 's3://lakehouse/bronze/orders' (FORMAT parquet, PARTITION_BY (ano, mes), OVERWRITE_OR_IGNORE, COMPRESSION zstd); -- as demais tabelas, sem partição (são pequenas) COPY (SELECT *, now() AS ingerido_em FROM read_csv_auto('olist/olist_order_items_dataset.csv')) TO 's3://lakehouse/bronze/order_items.parquet' (FORMAT parquet); -- repita para customers, products e product_category_name_translation -- verificação obrigatória: a contagem da origem e a do destino coincidem? SELECT 'csv' AS origem, count(*) FROM read_csv_auto('olist/olist_orders_dataset.csv') UNION ALL SELECT 'bronze', count(*) FROM read_parquet('s3://lakehouse/bronze/orders/**/*.parquet');
Esperado: 99 441 pedidos dos dois lados. Anote também o tamanho do CSV e o do prefixo no bucket, que constitui a primeira evidência medida do encontro. Reexecução: OVERWRITE_OR_IGNORE substitui só arquivos de mesmo nome, e o prefixo é removido antes para não restarem arquivos da carga anterior.

Segunda hora · Modelagem · 20 minutos

O fato tem como grão um item de pedido entregue

As três perguntas se respondem sobre o mesmo fato, variando a dimensão de análise: geografia, produto e tempo.

Fato

fato_item_venda

Grão: um item de um pedido entregue. Medidas: preço, frete e valor total. Constitui o grão mais fino disponível, e as agregações em níveis superiores derivam dele.

Dimensão · Pergunta 1

dim_geografia

Do cliente deriva a UF e, por regra explícita, a região. O dado bruto não contém o atributo região: ele é produzido nesta dimensão.

Dimensão · Pergunta 2

dim_produto

Produto e categoria, com a tradução da categoria aplicada. Declare qual dos dois níveis responde a pergunta.

Dimensão · Pergunta 3

tempo

Mês da compra, derivado do carimbo do pedido. O mês constitui o eixo da sazonalidade e a chave de partição da bronze.

-- a regra que cria a dimensão que a pergunta 1 exige CREATE OR REPLACE TABLE gold.dim_geografia AS SELECT DISTINCT customer_id, customer_state AS uf, CASE WHEN customer_state IN ('AC','AP','AM','PA','RO','RR','TO') THEN 'Norte' WHEN customer_state IN ('AL','BA','CE','MA','PB','PE','PI','RN','SE') THEN 'Nordeste' WHEN customer_state IN ('DF','GO','MT','MS') THEN 'Centro-Oeste' WHEN customer_state IN ('ES','MG','RJ','SP') THEN 'Sudeste' WHEN customer_state IN ('PR','RS','SC') THEN 'Sul' END AS regiao FROM read_parquet('s3://lakehouse/bronze/customers.parquet');

Segunda hora · Modelagem · dimensão de produto

A dimensão de produto aplica a tradução da categoria

A dimensão tem uma linha por produto e a categoria em inglês, que é o domínio usado nas consultas das perguntas 2 e 3. A junção com a tradução é externa, para que nenhum produto se perca.

-- a dimensão que a pergunta 2 exige: uma linha por produto CREATE OR REPLACE TABLE gold.dim_produto AS SELECT p.product_id, coalesce(t.product_category_name_english, -- categoria traduzida p.product_category_name, -- sem tradução: nome original 'nao_informada') AS categoria -- sem categoria FROM read_parquet('s3://lakehouse/bronze/products.parquet') p LEFT JOIN read_parquet('s3://lakehouse/bronze/product_category_name_translation.parquet') t USING (product_category_name); -- conferência: a dimensão preserva todos os produtos da origem SELECT count(*) AS produtos, count(DISTINCT product_id) AS chaves FROM gold.dim_produto;
Esperado: 32 951 produtos e 32 951 chaves. Na origem, 610 produtos não têm categoria e duas categorias não têm tradução (pc_gamer e portateis_cozinha_e_preparadores_de_alimentos, com 13 produtos); o coalesce preserva os três casos com rótulo explícito.

Segunda hora · Silver e gold

Do arquivo bruto ao fato consultável

A silver garante a chave única e os tipos, e a gold junta o fato às dimensões e grava o resultado particionado pela coluna que as perguntas filtram.

-- silver: tipos declarados e uma linha por pedido CREATE OR REPLACE TABLE silver.pedido AS SELECT order_id, customer_id, order_status, CAST(order_purchase_timestamp AS TIMESTAMP) AS comprado_em, ingerido_em FROM read_parquet('s3://lakehouse/bronze/orders/**/*.parquet') WHERE order_id IS NOT NULL QUALIFY row_number() OVER (PARTITION BY order_id ORDER BY ingerido_em DESC) = 1; -- gold: o fato no grão do item entregue, com suas dimensões CREATE OR REPLACE TABLE gold.fato_item_venda AS SELECT i.order_id, i.order_item_id, i.product_id, g.regiao, g.uf, d.categoria, date_trunc('month', p.comprado_em) AS mes, i.price, i.freight_value, i.price + i.freight_value AS valor_total FROM read_parquet('s3://lakehouse/bronze/order_items.parquet') i JOIN silver.pedido p USING (order_id) JOIN gold.dim_geografia g ON g.customer_id = p.customer_id JOIN gold.dim_produto d ON d.product_id = i.product_id WHERE p.order_status = 'delivered'; -- reexecução: antes, mc rm -r --force local/lakehouse/gold/fato_item_venda COPY (SELECT * FROM gold.fato_item_venda) TO 's3://lakehouse/gold/fato_item_venda' (FORMAT parquet, PARTITION_BY (regiao), OVERWRITE_OR_IGNORE);
Decisão a registrar: o filtro por delivered exclui pedidos cancelados e em trânsito. Trata-se de escolha de negócio, com efeito direto sobre as três respostas.

Segunda hora · Respostas · 10 minutos

Cada uma das três perguntas corresponde a uma consulta

O modelo dimensional adequado reduz cada pergunta a um recorte do mesmo fato.

-- 1. região com menor volume SELECT regiao, count(*) AS itens, round(sum(valor_total), 2) AS receita FROM gold.fato_item_venda GROUP BY 1 ORDER BY receita ASC; -- 2. produto mais vendido nessa região SELECT categoria, count(*) AS itens, round(sum(valor_total), 2) AS receita FROM gold.fato_item_venda WHERE regiao = 'Norte' GROUP BY 1 ORDER BY itens DESC LIMIT 5; -- 3. sazonalidade desse produto, mês a mês SELECT mes, count(*) AS itens, round(sum(valor_total), 2) AS receita, round(100.0 * count(*) / sum(count(*)) OVER (), 1) AS pct_do_total FROM gold.fato_item_venda WHERE regiao = 'Norte' AND categoria = 'health_beauty' GROUP BY 1 ORDER BY 1;
Interpretar como queda de demanda a redução observada nos meses de borda. O conjunto abrange de setembro de 2016 a agosto de 2018, e os meses extremos contêm poucos dias de venda.

Segunda hora · Conferência

Os valores de referência para conferir o pipeline

Ordens de grandeza para comparação, medindo o volume por receita, sobre pedidos entregues e no grão do item.

EtapaO que verificarReferência
BronzeContagem do CSV contra a do Parquet no bucket.99 441 pedidos, idênticos dos dois lados.
FatoLinhas do fato após o filtro de entregues.Ordem de 110 mil itens, distribuídos em cinco regiões.
Pergunta 1Região de menor receita.Norte, com margem ampla sobre a penúltima região: cerca de 2 mil itens e R$ 0,40 milhão.
Pergunta 2Categoria líder no Norte, por número de itens.health_beauty, à frente de computers_accessories e sports_leisure.
Pergunta 3Série mensal dessa categoria no Norte.Vinte e um meses, de out/2016 a ago/2018, com volume crescente e picos em nov/2017 e jun-ago/2018.
Divergência numérica pode decorrer de outra definição de volume ou de outro recorte de status. Exige-se que a definição adotada esteja declarada por escrito.

Segunda hora · Uso de IA · em paralelo

A IA escreve o SQL, e o grupo responde pela evidência

O assistente acelera a escrita e o reconhecimento do schema. A verificação permanece atribuição do grupo e determina a correção do resultado.

Prompt 1 · Reconhecimento

Mapear o conjunto antes de carregar

"Estes são os cabeçalhos das oito tabelas do Olist. Proponha chaves primárias e estrangeiras e o caminho de junção para responder: qual região vende menos."

Prompt 2 · Geração

Escrever a carga e o modelo

"Escreva SQL DuckDB que grave os CSVs em Parquet no bucket s3://lakehouse via MinIO e monte um fato no grão do item entregue com dimensões de região, produto e mês."

Prompt 3 · Crítica

Procurar o erro no próprio resultado

"Aponte, neste SQL, o que duplica linhas na junção, o que quebra se a carga rodar duas vezes e o que altera o resultado em vez do desempenho."

Toda saída da IA é executada e conferida antes de entrar no repositório.

Verifique a contagem de linhas em cada camada e explique cada diferença.

Confirme os tipos com DESCRIBE: a inferência automática exige verificação.

Verifique a contagem do fato antes e depois de cada junção.

Registre o prompt junto do SQL, uma vez que ele integra o registro da decisão.

Verifique cada nome de coluna contra o schema: o assistente preenche lacunas por inferência.

Segunda hora · Medição

As evidências que cada grupo mede e registra

O registro das medidas antes e depois da alteração sustenta a decisão de layout.

EvidênciaComo obter no DuckDBO que ela demonstra
Volume por formatoTamanho do CSV contra o do prefixo no bucket, pelo console do MinIO.O efeito da codificação colunar e da compressão.
Número e tamanho de arquivosSELECT * FROM parquet_file_metadata('s3://lakehouse/bronze/orders/**/*.parquet')Se a estratégia de partição gerou arquivos pequenos.
Estatística por blocoSELECT * FROM parquet_metadata('s3://lakehouse/gold/fato_item_venda/**/*.parquet')Que mínimo e máximo por row group tornam o pruning possível.
Custo da consultaEXPLAIN ANALYZE na pergunta 3, lendo do CSV e lendo da gold particionada.Quanto do ganho vem do formato e quanto vem do layout.
IdempotênciaApós duas execuções da carga bronze, cada uma precedida da remoção do prefixo: SELECT count(*) AS arquivos, sum(num_rows) AS linhas FROM parquet_file_metadata('s3://lakehouse/bronze/orders/**/*.parquet')Que a reexecução não acumula arquivos nem duplica linhas: os dois números se repetem, com 99 441 linhas.

Ponte com a Aula 1 · Spec-Driven Development

Do tema à especificação executável

A decisão de armazenamento entra no projeto como requisito, decisão registrada e critério verificável.

RF-004

Requisito funcional

Manter cinco anos de eventos brutos consultáveis sob demanda para auditoria.

RNF-004

Requisito não funcional

Tamanho médio de arquivo entre 128 MB e 1 GB; dados com mais de 12 meses em classe fria; carga idempotente sob reentrega; custo mensal dentro do orçamento do domínio.

ADR-STO-01

Decisão de arquitetura

Parquet particionado por ano e mês, publicado por commit no catálogo. Descartado: JSON por dia, que impede pruning por coluna e multiplica arquivos pequenos. Consequência: exige compactação e manutenção de snapshots.

Cenário de aceite

Critério verificável

Dado um diretório com 10 000 arquivos de 1 MB, quando executo a compactação, então restam arquivos de pelo menos 128 MB e a mesma consulta lê menos bytes.

Entrega do card de trabalho

O lakehouse, as três respostas e a decisão registrada

O que o grupo entrega ao final da segunda hora, e o que será conferido.

Artefato

Pipeline versionado e as três respostas

O SQL da ingestão, do modelo e das consultas; as três respostas com número; e uma frase por pergunta que declare o significado daquele número para o negócio do parceiro.

1

Os dados estão no bucket do MinIO em Parquet, com partição declarada e justificada.

2

A contagem da bronze coincide com a do CSV de origem.

3

O fato tem grão declarado e as dimensões que as três perguntas exigem.

4

A definição de volume e o recorte de status estão declarados por escrito.

5

As três consultas são executadas sobre a camada gold e devolvem valores medidos.

6

Há ao menos uma evidência medida: tamanho, número de arquivos ou bytes lidos.

Aula 10 · Síntese

A decisão de armazenamento determina quantos bytes a próxima pergunta precisará ler e quantas cópias precisam concordar com a resposta.

Apache Parquet Documentation Apache Iceberg Documentation Delta Lake Protocol DuckDB Documentation — Parquet e COPY Olist — Brazilian E-Commerce Public Dataset AWS — Data warehouse, data lake e data mart
Pergunta final: no layout que o grupo propôs, quantos bytes a consulta mais frequente do parceiro precisa ler?

Sobre este encontro

Armazenamento em Grande Escala · 01/09/2026 · Prof. Afonso

Objetivo de aprendizagem

Ao final do encontro, o estudante deve ser capaz de escolher formato, organização física, camadas e política de ciclo de vida para os dados do projeto, justificando as decisões pelas propriedades distribuídas do armazenamento, e de construir um lakehouse que responda a perguntas de negócio com SQL executado.

Estratégia do encontro

Primeira hora de exposição dialogada em três blocos, cada um encerrado por checklist de aplicação e erro comum. Segunda hora de atividade em grupo: os estudantes ingerem os dados públicos da Olist em um lakehouse local com MinIO e DuckDB, modelam dimensionalmente no grão do item de pedido entregue e respondem a três perguntas de negócio, usando IA para gerar o SQL e verificando cada etapa por contagem, tipo e medição.

Estrutura do encontro

  1. 1ª hora · Bloco 1 (30 min) — O que escala, object storage, formato físico e layout: dimensões de escala, convenções de prefixo, anatomia do arquivo colunar e do arquivo orientado a registro, cardinalidade da chave de partição e compactação.
  2. 1ª hora · Bloco 2 (18 min) — Propriedades distribuídas: particionamento, replicação, quórum, consistência forte e eventual, e as regras de engenharia que cada garantia ausente impõe ao pipeline.
  3. 1ª hora · Bloco 3 (12 min) — Vocabulário de repositórios analíticos, lakehouse, camadas por contrato, ciclo de vida, custo e segurança.
  4. 2ª hora · Card de trabalho (60 min) — Em grupo: MinIO e DuckDB no ar (10 min), bronze em Parquet no bucket (15 min), modelo dimensional com fato e dimensões (20 min), as três consultas executadas (10 min), evidências e conclusão escrita (5 min).