
Jornada para o Analytics Realtime - Lakehouse não é a Solução
Construir uma plataforma de analytics em tempo real parece um problema já resolvido. Temos streaming, formatos abertos, armazenamento praticamente ilimitado e engines capazes de consultar bilhões de registros. Ainda assim, muitas empresas continuam presas a pipelines D-1 — nos quais os dados de hoje só ficam disponíveis no dia seguinte — ou mantêm outra infraestrutura para reagir ao que acontece agora.
Essa distância entre receber um evento e usá-lo analiticamente em menos de um segundo é o tema da série O caminho até o analytics em tempo real. O ponto de partida é a construção de uma plataforma com latência subsecond. Cada artigo tratará de um obstáculo concreto, das soluções disponíveis e das decisões que aproximam — ou afastam — a arquitetura desse objetivo.
Mas ainda não é hora de apresentar a arquitetura. Algumas premissas fazem o problema parecer mais simples do que realmente é. A primeira envolve o Lakehouse: dados em formatos abertos, normalmente sobre armazenamento de objetos, com capacidades de tabela como transações, versionamento e evolução de schema.
A primeira premissa é esta: Ferramentas de Lakehouse não habilitam por si só analytics em tempo real.
Definindo tempo real
O rótulo “tempo real” costuma esconder cinco relógios diferentes:
- Ingestão: quando o evento entra no pipeline.
- Commit: quando os arquivos e a nova versão da tabela são publicados.
- Visibilidade: quando o dado aparece em um snapshot, isto é, uma versão consistente e consultável da tabela.
- Consulta: quanto a query demora para responder.
- Ponta a ponta: quanto passa entre o evento e a ação do consumidor.
Para esta série, o objetivo é o quinto relógio: subsecond de ponta a ponta. Reduzir somente o tempo do commit não resolve os outros quatro.
Uma tabela pode receber dados a cada trinta segundos e levar dez segundos para responder uma query. Outra pode responder em menos de um segundo sobre um snapshot de cinco minutos atrás. Nenhuma delas oferece necessariamente uma resposta subsecond sobre o estado atual.
Apache Iceberg suporta streaming writes. Spark Structured Streaming — mecanismo do Apache Spark para processar fluxos contínuos — pode publicar sucessivos micro-batches em uma tabela Iceberg. Mas “suporta streaming” informa apenas que a operação existe. Não revela a latência de ponta a ponta, quantos arquivos serão criados, o custo da publicação nem quem manterá a tabela depois.
O erro está neste salto:
aceita updates → aceita updates frequentes → entrega analytics em tempo real
A lista de features não resolve essa dúvida. O caminho de uma única atualização, do evento ao resultado, mostra o que fica escondido.
O ciclo de um update
Considere uma alteração de estoque:
{
"produto_id": 42,
"estoque": 7,
"evento_em": "2026-07-10T14:32:01.250Z"
}
Apache Iceberg é o exemplo principal porque sua especificação expõe o mecanismo com precisão. O problema, porém, não nasce do nome Iceberg. Ele aparece sempre que o caminho de atualização publica versões de uma tabela composta por arquivos. Outros table formats podem reorganizar parte do custo, mas ainda precisam conciliar frequência de publicação e granularidade física.
Para o novo estoque aparecer em uma consulta, ele atravessa um fluxo maior do que “gravar uma linha”: acumulação, publicação, leitura e resultado.
Os diagramas desta seção são modelos conceituais. Engines, catálogos e writers podem implementar detalhes diferentes.
1. Acumulação
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#F7F7F5",
"primaryTextColor": "#1F2937",
"primaryBorderColor": "#D6D3D1",
"tertiaryColor": "#FFF4DB",
"lineColor": "#6B7280",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 24, "rankSpacing": 30, "padding": 8}
}}%%
flowchart LR
classDef active fill:#FFF4DB,stroke:#B45309,color:#1F2937,stroke-width:2px;
classDef muted fill:#F7F7F5,stroke:#D6D3D1,color:#4B5563,stroke-width:1px;
A[1. Acumular eventos] --> B[2. Publicar tabela] --> C[3. Ler novo estado] --> D[4. Entregar resultado]
class A active;
class B,C,D muted;
O evento de estoque chega ao job de streaming, a aplicação que consome o fluxo contínuo. Em vez de publicar cada evento isoladamente, o job mantém os registros recebidos até o próximo trigger, o instante configurado para fechar o micro-batch e iniciar seu processamento.
Se o trigger ocorre a cada dez segundos, uma alteração pode esperar quase dez segundos antes mesmo de a escrita começar. Diminuir esse intervalo reduz a espera, mas também reduz a quantidade de dados disponível para formar cada arquivo.
2. Publicação
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#F7F7F5",
"primaryTextColor": "#1F2937",
"primaryBorderColor": "#D6D3D1",
"tertiaryColor": "#FFF4DB",
"lineColor": "#6B7280",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 24, "rankSpacing": 30, "padding": 8}
}}%%
flowchart LR
classDef active fill:#FFF4DB,stroke:#B45309,color:#1F2937,stroke-width:2px;
classDef muted fill:#F7F7F5,stroke:#D6D3D1,color:#4B5563,stroke-width:1px;
A[1. Acumular eventos] --> B[2. Publicar tabela] --> C[3. Ler novo estado] --> D[4. Entregar resultado]
class B active;
class A,C,D muted;
Quando o trigger dispara, as tasks da engine de processamento separam os registros de acordo com as partições e operações da tabela. Em seguida, gravam no S3 os arquivos com os novos dados ou com as remoções necessárias para representar um update.
O writer Iceberg organiza os arquivos produzidos em manifests — índices que registram quais arquivos pertencem à tabela — e prepara a metadata do novo snapshot. O catálogo, serviço que aponta para a versão corrente, troca atomicamente a referência antiga pela nova.
Essa troca é o commit. Até ela acontecer, o novo estoque permanece invisível. Depois dela, a versão existe — mas ainda precisa ser encontrada e consultada.
3. Leitura
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#F7F7F5",
"primaryTextColor": "#1F2937",
"primaryBorderColor": "#D6D3D1",
"tertiaryColor": "#FFF4DB",
"lineColor": "#6B7280",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 24, "rankSpacing": 30, "padding": 8}
}}%%
flowchart LR
classDef active fill:#FFF4DB,stroke:#B45309,color:#1F2937,stroke-width:2px;
classDef muted fill:#F7F7F5,stroke:#D6D3D1,color:#4B5563,stroke-width:1px;
A[1. Acumular eventos] --> B[2. Publicar tabela] --> C[3. Ler novo estado] --> D[4. Entregar resultado]
class C active;
class A,B,D muted;
Depois do commit, a engine de consulta precisa carregar a metadata atual. A partir do snapshot, ela usa manifests, partições e estatísticas para descobrir quais arquivos contêm dados relevantes para a query.
Os workers, processos que executam partes da consulta em paralelo, abrem esses arquivos no armazenamento de objetos, leem as colunas necessárias e realizam filtros, joins ou agregações. Publicar o snapshot tornou o update visível; não eliminou o tempo de planejar e executar a consulta. O consumidor só verá a mudança quando esse trabalho terminar.
4. Resultado
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#F7F7F5",
"primaryTextColor": "#1F2937",
"primaryBorderColor": "#D6D3D1",
"tertiaryColor": "#FFF4DB",
"lineColor": "#6B7280",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 24, "rankSpacing": 30, "padding": 8}
}}%%
flowchart LR
classDef active fill:#FFF4DB,stroke:#B45309,color:#1F2937,stroke-width:2px;
classDef muted fill:#F7F7F5,stroke:#D6D3D1,color:#4B5563,stroke-width:1px;
A[1. Acumular eventos] --> B[2. Publicar tabela] --> C[3. Ler novo estado] --> D[4. Entregar resultado]
class D active;
class A,B,C muted;
O consumidor recebe estoque = 7 somente quando a query termina. A latência de ponta a ponta soma a espera no micro-batch, a escrita, o commit, a descoberta do snapshot e a execução da consulta. Ingestão não é visibilidade; visibilidade não é query latency.
Diminuir o intervalo entre snapshots reduz apenas uma parte dessa soma e deixa menos dados em cada publicação. Um ajuste aparentemente simples de latência muda a forma física da tabela.
De uma tabela pequena a uma plataforma inteira
O custo fica mais claro em duas escalas: uma tabela de baixo volume e uma plataforma com 1.000 tabelas.
Uma tabela com 1 GB por hora
Suponha um fluxo constante de 1 GB por hora. Para simplificar a conta, considere 1 GB como 1.000 MB e ignore compressão, partições e paralelismo:
| Intervalo de publicação | Dados disponíveis por commit | Commits por hora |
|---|---|---|
| 1 hora | 1 GB | 1 |
| 10 minutos | ~167 MB | 6 |
| 1 minuto | ~16,7 MB | 60 |
| 10 segundos | ~2,8 MB | 360 |
| 1 segundo | ~0,28 MB | 3.600 |
O volume não muda. A granularidade física muda completamente.
A documentação atual do Iceberg usa 512 MB como tamanho-alvo padrão para arquivos de dados. O valor real depende das tasks, partições, compressão e distribuição, mas o contraste é suficiente: com publicação a cada dez segundos, todo o fluxo oferece apenas 2,8 MB antes de a engine dividi-lo.
Maior freshness significa trocar arquivos analíticos grandes por uma sequência de publicações pequenas. Agora multiplique essa decisão.
Uma plataforma com 1.000 tabelas
Agora imagine que uma Plataforma de Dados tenha 1.000 tabelas no Amazon S3 e tente publicar cada uma a cada 10 segundos:
| Frequência | Por tabela | Em 1.000 tabelas |
|---|---|---|
| Atualizações por minuto | 6 | 6.000 |
| Atualizações por dia | 8.640 | 8.640.000 |
| Atualizações em 30 dias | 259.200 | 259.200.000 |
São 259,2 milhões de commits por mês, cada um potencialmente envolvendo arquivos, manifests, metadata e uma atualização no catálogo.
Uma request PUT é a chamada usada para criar ou substituir um objeto no S3. Na página oficial de preços, a AWS usa US$ 0,005 por mil requests PUT em seus exemplos para S3 Tables. Mesmo assumindo apenas uma request cobrada por commit — um cenário otimista demais — o resultado seria:
259.200.000 requests ÷ 1.000 × US$ 0,005 = US$ 1.296/mês
Se cada commit criasse quatro objetos, a ordem de grandeza subiria para:
1.036.800.000 requests ÷ 1.000 × US$ 0,005 = US$ 5.184/mês
O cálculo não estima uma implantação Iceberg real. O número de objetos depende da engine, do writer, das partições e do volume. GETs, LISTs, catálogo, compute, armazenamento, transferência, retries, queries e compactação ficaram de fora. A conta serve para revelar a ordem de grandeza escondida por uma decisão local de “apenas dez segundos”.
Não existe um segundo exato em que o Iceberg “para de funcionar”. Essa nem é a pergunta relevante. Cada redução na espera é financiada por mais objetos, commits, coordenação ou manutenção.
Compactação
Escritas contínuas carregam pouco dado por commit. A própria documentação do Iceberg alerta que isso pode fazer a tabela rastrear muitos arquivos pequenos. Qualquer table format que materialize micro-batches curtos em novos arquivos precisa administrar essa fragmentação. Mais arquivos significam:
- mais entradas em manifests;
- mais objetos para encontrar e abrir;
- mais trabalho de planejamento;
- menor eficiência de scans;
- necessidade de combinar arquivos posteriormente.
Compactar melhora o estado final, mas não apaga o caminho percorrido. O sistema precisa:
- localizar e ler arquivos já persistidos;
- reescrevê-los em arquivos maiores;
- publicar outro snapshot;
- manter e depois remover arquivos ainda referenciados pelo histórico.
A compactação paga depois pela granularidade exigida antes.
O desenho deixa três limites estruturais:
| Limite | Consequência |
|---|---|
| Publicação por snapshots | O update só aparece após arquivos, metadata e commit |
| Granularidade baseada em arquivos | Menos espera produz arquivos menores e mais numerosos |
| Manutenção posterior | Compactação recupera eficiência reescrevendo o que já foi persistido |
Nada impede operar dentro desses limites. O erro é tratá-los como propriedades naturais de uma camada responsável pelo estado mais recente. O Lakehouse faz mais sentido no problema para o qual foi construído.
O papel do Lakehouse
O Lakehouse surgiu para combinar armazenamento aberto e econômico do Data Lake com capacidades esperadas de bancos analíticos. Nesse desenho, um table format define como os arquivos formam uma tabela consistente e como diferentes engines observam suas versões. Apache Iceberg contribui com recursos como:
- evolução segura de schema e particionamento;
- snapshots, time travel e rollback;
- isolamento serializável;
- concorrência otimista;
- planejamento eficiente de grandes tabelas;
- interoperabilidade entre engines.
Esses recursos fazem do Iceberg uma peça valiosa para consistência, gerenciamento e escala analítica. Também delimitam o papel do Lakehouse: a camada histórica pode participar da plataforma em tempo real sem ser responsável por servir o estado mais recente.
Essa é a primeira premissa da série:
Lakehouse não é sinônimo de tempo real. Se uma proposta começa e termina no suporte a updates do table format, ainda falta explicar onde estão os dados quentes.