
Do full load ao incremental: por que computar mudanças não basta
Full load costuma ser a primeira estratégia de um pipeline porque simplifica o problema: a cada execução, o sistema lê todos os dados da origem, aplica a transformação e substitui o destino. Não precisa registrar até onde a execução anterior leu nem interpretar separadamente cada atualização ou exclusão.
Essa simplicidade local deixa de escalar quando se repete em milhares de pipelines. A plataforma passa a reler, recalcular e reescrever informações que já conhecia. Migrar para incremental, porém, exige mais do que ler menos linhas.
A decisão depende de como a informação muda, como a transformação reage e que garantia o consumidor espera do resultado. Essa combinação determina se o pipeline pode apenas acumular fatos ou se precisará preservar informações entre execuções, corrigir saídas, estabelecer completude ou recomputar.
1. Por que o full load é tão comum
Considere as compras de um cartão com limite de R$ 1.000. Cada compra possui um identificador estável, um valor e um status. Neste primeiro recorte, o pipeline soma as compras confirmadas e não estornadas. Esse total reduz o limite disponível, que indica quanto ainda pode ser gasto, e compõe a fatura em preparação, que indica quanto será cobrado no ciclo.
Na primeira execução, a origem contém apenas um registro:
| Compra | Valor atual | Status |
|---|---|---|
C-42 | R$ 100 | confirmada |
A compra afeta desde o início dois resultados:
- o limite disponível passa a R$ 900;
- a fatura em preparação passa a R$ 100.
Antes da execução seguinte, o valor de C-42 é corrigido para R$ 120 e uma nova compra, C-43, acrescenta R$ 80:
| Compra | Valor atual | Status |
|---|---|---|
C-42 | R$ 120 | confirmada |
C-43 | R$ 80 | confirmada |
As mudanças na origem e as execuções do pipeline ocorrem em momentos distintos:
%%{init: {
"theme": "base",
"themeVariables": {
"background": "#F8FAFC",
"primaryColor": "#FFFFFF",
"primaryTextColor": "#0F172A",
"primaryBorderColor": "#E2E8F0",
"secondaryColor": "#F1F5F9",
"tertiaryColor": "#FFE0F0",
"lineColor": "#5A6B7D",
"titleColor": "#0F172A",
"clusterBkg": "#F1F5F9",
"clusterBorder": "#E2E8F0",
"actorBkg": "#FFFFFF",
"actorBorder": "#E2E8F0",
"actorTextColor": "#0F172A",
"actorLineColor": "#CBD5E1",
"signalColor": "#D6006F",
"signalTextColor": "#0F172A",
"labelBoxBkgColor": "#F1F5F9",
"labelBoxBorderColor": "#E2E8F0",
"labelTextColor": "#0F172A",
"loopTextColor": "#0F172A",
"noteBkgColor": "#F1F5F9",
"noteBorderColor": "#CBD5E1",
"noteTextColor": "#0F172A",
"activationBkgColor": "#E3F6F9",
"activationBorderColor": "#007C91",
"sequenceNumberColor": "#D6006F",
"arrowheadColor": "#D6006F",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 28, "rankSpacing": 36, "padding": 8}
}}%%
sequenceDiagram
participant O as Compras na origem
participant A as Pipeline analítico
Note over O: C-42 · R$ 100 · confirmada
A->>O: Ler todos os dados (Full load)
Note over A: Limite disponível · R$ 900<br/>Fatura em preparação · R$ 100
Note over O: C-42 · corrigida para R$ 120
Note over O: C-43 · R$ 80 · confirmada
A->>O: Ler todos os dados (Full load)
Note over A: Limite disponível · R$ 800<br/>Fatura em preparação · R$ 200
Nas duas execuções, o pipeline repete o mesmo procedimento:
- ler todas as compras do ciclo;
- selecionar aquelas que estão confirmadas e não foram estornadas;
- somar seus valores;
- derivar a fatura e o limite disponível a partir dessa soma.
Na segunda execução, o pipeline soma o valor corrigido de C-42 e a nova compra C-43, chegando a R$ 200. A fatura recebe esse total, e o limite disponível resulta de R$ 1.000 - R$ 200 = R$ 800.
Essa reconstrução é a simplificação central: cada execução deriva os dois resultados de todos os dados que a origem disponibiliza naquele momento. Ela pressupõe que a origem ofereça uma versão coerente para leitura; o full load, sozinho, não cria essa garantia.
Ainda assim, ele é uma escolha razoável quando reler um volume pequeno custa pouco, quando a execução ocorre poucas vezes ou quando o destino precisa ser construído pela primeira vez.
As duas estratégias compõem o novo resultado de formas diferentes:
- full load: calcula novamente a partir de todos os dados lidos na execução atual; o resultado da execução anterior não participa do cálculo;
- incremental: parte do resultado anterior e aplica somente as mudanças recebidas desde então.
O incremental, portanto, precisa reutilizar resultados e preservar as informações necessárias para interpretar cada mudança corretamente.
2. Como o incremental atualiza um resultado
Depois da primeira carga, o pipeline parte da fatura de R$ 100 e do limite disponível de R$ 900, sem executar outro full load. Ele também preserva o registro atual de C-42 em uma estrutura chamada estado por chave.
Essa estrutura guarda apenas as informações necessárias para interpretar a próxima mudança; ela não é o total calculado nem precisa repetir todas as colunas da origem.
Quando o valor de C-42 é corrigido para R$ 120, a chave permite localizar a compra anterior, substituir seu valor e calcular o efeito da correção:
%%{init: {
"theme": "base",
"themeVariables": {
"background": "#F8FAFC",
"primaryColor": "#FFFFFF",
"primaryTextColor": "#0F172A",
"primaryBorderColor": "#E2E8F0",
"secondaryColor": "#F1F5F9",
"tertiaryColor": "#FFE0F0",
"lineColor": "#5A6B7D",
"titleColor": "#0F172A",
"clusterBkg": "#F1F5F9",
"clusterBorder": "#E2E8F0",
"actorBkg": "#FFFFFF",
"actorBorder": "#E2E8F0",
"actorTextColor": "#0F172A",
"actorLineColor": "#CBD5E1",
"signalColor": "#D6006F",
"signalTextColor": "#0F172A",
"labelBoxBkgColor": "#F1F5F9",
"labelBoxBorderColor": "#E2E8F0",
"labelTextColor": "#0F172A",
"loopTextColor": "#0F172A",
"noteBkgColor": "#F1F5F9",
"noteBorderColor": "#CBD5E1",
"noteTextColor": "#0F172A",
"activationBkgColor": "#E3F6F9",
"activationBorderColor": "#007C91",
"sequenceNumberColor": "#D6006F",
"arrowheadColor": "#D6006F",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 28, "rankSpacing": 36, "padding": 8}
}}%%
flowchart LR
classDef active fill:#FFE0F0,stroke:#D6006F,color:#0F172A,stroke-width:2px;
classDef muted fill:#FFFFFF,stroke:#E2E8F0,color:#5A6B7D,stroke-width:1px;
classDef support fill:#E3F6F9,stroke:#007C91,color:#0F172A,stroke-width:1.5px,stroke-dasharray:4 2;
classDef warning fill:#FFF4D6,stroke:#9A5B00,color:#0F172A,stroke-width:2px;
D["<b>Mudança recebida</b><br/>Compra: C-42<br/>Valor atual: R$ 120"] --> Q["Localizar e atualizar o <b>estado por chave</b>"]
S["<b>Estado por chave</b><br/>Compra: C-42<br/>Valor atual: R$ 100<br/>Status: confirmada"] --> Q
Q --> S2["<b>Estado por chave</b><br/>Compra: C-42<br/>Valor atual: R$ 120<br/>Status: confirmada"]
Q --> DV["<b>Mudança no valor comprometido (ΔV)</b><br/>− R$ 100 + R$ 120 = + R$ 20"]
DV --> V["<b>Resultados atualizados</b><br/>Fatura: R$ 100 → R$ 120<br/>Limite: R$ 900 → R$ 880"]
class Q active;
class D,DV muted;
class S,S2 support;
class V active;
Esse processo tem duas etapas.
Primeiro: calcular a mudança no resultado
Para corrigir C-42, o valor anterior precisa sair e o novo precisa entrar:
ΔV = - R$ 100 + R$ 120 = + R$ 20
ΔV é a mudança que a transformação produz no valor comprometido pelas compras. A correção fornece o novo valor, enquanto o estado por chave fornece o anterior. O estado atualizado permanece disponível para mudanças posteriores; ΔV descreve apenas o efeito da correção atual.
Depois: aplicar a mudança ao resultado existente
Somente depois de obter ΔV, o pipeline atualiza os resultados armazenados. Como a correção acrescentou R$ 20 ao valor comprometido, ela aumenta a fatura e reduz o limite na mesma medida:
fatura: R$ 100 + R$ 20 = R$ 120
limite: R$ 900 - R$ 20 = R$ 880
Por isso, os resultados anteriores também precisam permanecer disponíveis entre atualizações. Enquanto a fatura está aberta, cada ΔV altera os dois resultados em sentidos opostos:
| Mudança recebida | ΔV | Limite após a mudança | Fatura após a mudança |
|---|---|---|---|
C-42 confirmada por R$ 100 | + R$ 100 | R$ 900 | R$ 100 |
C-42 corrigida para R$ 120 | + R$ 20 | R$ 880 | R$ 120 |
C-43 confirmada por R$ 80 | + R$ 80 | R$ 800 | R$ 200 |
C-42 estornada | - R$ 120 | R$ 920 | R$ 80 |
Depois do fechamento, a fatura deixa de aceitar novos lançamentos daquele ciclo; um pagamento, por sua vez, libera limite sem alterar a cobrança emitida.
Na literatura de bancos de dados, atualizar o resultado de uma consulta sem calculá-lo inteiro novamente é chamado de manutenção incremental de views. O survey de Gupta e Mumick mostra que a estratégia depende da consulta, do tipo de mudança e das informações disponíveis. O DBSP oferece uma formulação moderna na qual operadores preservam informações entre atualizações, recebem mudanças da entrada e produzem mudanças na saída.
O custo da atualização depende da agregação
Antes de C-42 ser estornada, o estado por chave era:
| Compra | Valor atual | Status |
|---|---|---|
C-42 | R$ 120 | confirmada |
C-43 | R$ 80 | confirmada |
Essas compras produzem SUM = R$ 200 e MAX = R$ 120, a maior compra do ciclo. Quando C-42 é estornada, o pipeline localiza diretamente sua linha e descobre que R$ 120 deixaram de fazer parte das agregações.
SUM: atualizar o resultado anterior
O resultado armazenado de SUM é R$ 200. Depois de localizar C-42, basta retirar seu valor:
R$ 200 - R$ 120 = R$ 80
Com acesso direto à compra e ao resultado anterior, essa atualização leva tempo esperado constante, O(1). Ela não precisa percorrer as demais chaves.
MAX: procurar o resultado substituto
O resultado armazenado de MAX é R$ 120. Como esse valor pertencia a C-42, o estorno remove justamente o máximo atual. O resultado anterior não informa qual valor deve substituí-lo.
Usando somente o estado por chave, o pipeline percorre as compras confirmadas e não estornadas, encontra R$ 80 em C-43 e substitui o resultado de MAX. No pior caso, essa busca visita todas as linhas e custa O(n).
Os dois cálculos usam o mesmo estado por chave e mantêm apenas o resultado da agregação. A diferença está na operação: SUM deriva o novo total do valor anterior e da compra retirada; MAX, quando perde o máximo atual, precisa procurar o próximo candidato.
O DBSP parte da hipótese de que a mudança recebida é muito menor que a base. Quando quase tudo muda, ou quando atualizar uma agregação exige percorrer todo o estado por chave, a vantagem sobre o full load diminui. Nesse caso, o full load pode ser a escolha mais simples.
3. Quando uma mudança não traz contexto suficiente
Uma mudança isolada nem sempre contém informação suficiente para atualizar o resultado. C-45 aparece primeiro como pendente, com valor desconhecido, e só pode afetar limite e fatura depois da confirmação:
%%{init: {
"theme": "base",
"themeVariables": {
"background": "#F8FAFC",
"primaryColor": "#FFFFFF",
"primaryTextColor": "#0F172A",
"primaryBorderColor": "#E2E8F0",
"secondaryColor": "#F1F5F9",
"tertiaryColor": "#FFE0F0",
"lineColor": "#5A6B7D",
"titleColor": "#0F172A",
"clusterBkg": "#F1F5F9",
"clusterBorder": "#E2E8F0",
"actorBkg": "#FFFFFF",
"actorBorder": "#E2E8F0",
"actorTextColor": "#0F172A",
"actorLineColor": "#CBD5E1",
"signalColor": "#D6006F",
"signalTextColor": "#0F172A",
"labelBoxBkgColor": "#F1F5F9",
"labelBoxBorderColor": "#E2E8F0",
"labelTextColor": "#0F172A",
"loopTextColor": "#0F172A",
"noteBkgColor": "#F1F5F9",
"noteBorderColor": "#CBD5E1",
"noteTextColor": "#0F172A",
"activationBkgColor": "#E3F6F9",
"activationBorderColor": "#007C91",
"sequenceNumberColor": "#D6006F",
"arrowheadColor": "#D6006F",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 28, "rankSpacing": 36, "padding": 8}
}}%%
sequenceDiagram
title Como C-45 se torna informação suficiente
participant M as Mudanças disponíveis
participant E as Estado por chave
participant R as Resultados
M->>E: 30/07 · 14:02 · C-45 pendente · valor desconhecido
Note over E: C-45 · valor desconhecido · pendente
Note over E,R: Ainda não é possível produzir um delta
M->>E: 30/07 · 14:05 · C-45 confirmada · R$ 30
Note over E: C-45 · R$ 30 · confirmada
E->>R: Produzir ΔV = + R$ 30
Note over R: Limite · R$ 920 → R$ 890<br/>Fatura · R$ 80 → R$ 110
Até a confirmação, o pipeline preserva C-45 como uma entrada incompleta do estado por chave. Chamamos essa entrada de estado de reconstrução por chave: o conjunto mínimo de informações mantido até que seja possível formar o registro atual e produzir ΔV.
4. Lançamentos tardios mudam o passado conhecido
Mesmo completa, uma compra pode ficar disponível somente depois de o pipeline processar o período ao qual ela pertence. Esse é um lançamento tardio.
Um lançamento tardio envolve dois horários:
- tempo do evento: quando a compra aconteceu e a qual ciclo pertence;
- tempo do processamento: quando ela ficou disponível para o pipeline.
O ciclo de julho termina em 31/07, às 23:59. O batch executado em 01/08, às 00:15, termina com uma fatura de R$ 110 e um limite disponível de R$ 890. Dez minutos depois, outra compra fica disponível:
| Compra | Valor atual | Status | Tempo do evento | Tempo do processamento |
|---|---|---|---|---|
C-48 | R$ 80 | confirmada | 31/07, 23:58 | 01/08, 00:25 |
C-48 pertence ao ciclo já processado, mas só ficou disponível depois da execução das 00:15. Esse atraso também ocorre em batches incrementais; não depende de processamento em tempo real.
O pipeline forma o estado por chave de C-48 e calcula ΔV = + R$ 80. Como a fatura ainda está em preparação, a compra atualiza os dois resultados:
| Resultado | Valor anterior | Mudança aplicada | Valor novo |
|---|---|---|---|
| Limite disponível | R$ 890 | - R$ 80 | R$ 810 |
| Fatura em preparação | R$ 110 | + R$ 80 | R$ 190 |
Um full load posterior incorporaria C-48 reconstruindo o ciclo inteiro; o incremental aplica a mudança aos resultados já calculados. Nos dois casos, terminar um batch de julho não significa que todos os registros de julho já estejam disponíveis: o passado conhecido ainda pode se ampliar.
O Dataflow Model formaliza a separação entre o tempo em que um evento ocorreu e o tempo em que ficou disponível para processamento.
5. Quando uma resposta precisa ser definitiva
Uma fatura precisa ser fechada mesmo sem a garantia de que todas as compras de julho já chegaram. Neste exemplo, a fatura de julho fecha em 01/08, às 00:30.
Todas as compras abaixo foram realizadas em julho: as disponíveis até o horário de fechamento ainda podem entrar na fatura; as que se tornam disponíveis depois seguem para a próxima:
| Compra | Valor atual | Status | Tempo do evento | Tempo do processamento |
|---|---|---|---|---|
C-42 | R$ 120 | estornada | 29/07, 11:00 | 29/07, 11:00 |
C-43 | R$ 80 | confirmada | 28/07, 10:00 | 28/07, 10:00 |
C-45 | R$ 30 | confirmada | 30/07, 14:05 | 30/07, 14:05 |
C-48 | R$ 80 | confirmada | 31/07, 23:58 | 01/08, 00:25 |
C-49 | R$ 40 | confirmada | 31/07, 23:59 | 01/08, 01:15 |
C-49 foi realizada em julho, mas só ficou disponível às 01:15, 45 minutos depois do fechamento. Como ultrapassou a margem das 00:30, ela não altera a fatura já emitida e segue para a próxima.
Duas fronteiras fecham o resultado
Para emitir a fatura de julho, o pipeline precisa responder a duas perguntas:
- Quais registros pertencem ao ciclo?
- Quando todas as entradas relevantes avançaram o suficiente para que a fatura possa ser fechada?
Cada pergunta define uma fronteira.
Completude
| Fronteira | O que define |
|---|---|
| Do ciclo | quais compras pertencem ao ciclo de julho |
| De processamento | compras disponíveis até 00:30 entram na fatura de julho |
A fronteira do ciclo classifica C-48 e C-49 como compras de julho. A de processamento inclui C-48 nessa fatura, pois ela ficou disponível às 00:25, e encaminha C-49 à seguinte, pois ela só chegou às 01:15. Quando os dados vêm de entradas independentes, verificar se todas alcançaram essa segunda fronteira exige coordenação.
Neste contexto, completude significa que todas as entradas relevantes avançaram até a fronteira de processamento. Alcançada essa condição, a fatura pode ser emitida.
%%{init: {
"theme": "base",
"themeVariables": {
"background": "#F8FAFC",
"primaryColor": "#FFFFFF",
"primaryTextColor": "#0F172A",
"primaryBorderColor": "#E2E8F0",
"secondaryColor": "#F1F5F9",
"tertiaryColor": "#FFE0F0",
"lineColor": "#5A6B7D",
"titleColor": "#0F172A",
"clusterBkg": "#F1F5F9",
"clusterBorder": "#E2E8F0",
"actorBkg": "#FFFFFF",
"actorBorder": "#E2E8F0",
"actorTextColor": "#0F172A",
"actorLineColor": "#CBD5E1",
"signalColor": "#D6006F",
"signalTextColor": "#0F172A",
"labelBoxBkgColor": "#F1F5F9",
"labelBoxBorderColor": "#E2E8F0",
"labelTextColor": "#0F172A",
"loopTextColor": "#0F172A",
"noteBkgColor": "#F1F5F9",
"noteBorderColor": "#CBD5E1",
"noteTextColor": "#0F172A",
"activationBkgColor": "#E3F6F9",
"activationBorderColor": "#007C91",
"sequenceNumberColor": "#D6006F",
"arrowheadColor": "#D6006F",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 28, "rankSpacing": 36, "padding": 8}
}}%%
sequenceDiagram
title Lançamentos tardios antes e depois do fechamento
participant N as Compras na ordem de disponibilidade
participant C as Limite disponível
participant F as Faturas
Note over C: R$ 890
Note over F: Julho (Em preparação) · R$ 110
N-->F: 31/07 · 23:59 · fronteira do ciclo
Note over N: 31/07 · 23:58<br/>C-48 · R$ 80
N->>C: Reduzir R$ 80
Note over C: R$ 810
N->>F: Adicionar à fatura de julho
Note over F: Julho (Em preparação) · R$ 190
N-->F: 01/08 · 00:30 · fronteira de processamento
Note over F: Julho (Emitida) · R$ 190
Note over N: 31/07 · 23:59<br/>C-49 · R$ 40
N->>C: Reduzir R$ 40
Note over C: R$ 770
N->>F: Adicionar à próxima fatura
Note over F: Julho (Emitida) · R$ 190<br/>Agosto (Em preparação) · R$ 40
Coordenação
A fatura só pode ser emitida quando todas as entradas capazes de alterá-la tiverem processado dados até pelo menos 00:30. Suponha que o pipeline receba compras de duas processadoras de cartões independentes:
%%{init: {
"theme": "base",
"themeVariables": {
"background": "#F8FAFC",
"primaryColor": "#FFFFFF",
"primaryTextColor": "#0F172A",
"primaryBorderColor": "#E2E8F0",
"secondaryColor": "#F1F5F9",
"tertiaryColor": "#FFE0F0",
"lineColor": "#5A6B7D",
"titleColor": "#0F172A",
"clusterBkg": "#F1F5F9",
"clusterBorder": "#E2E8F0",
"actorBkg": "#FFFFFF",
"actorBorder": "#E2E8F0",
"actorTextColor": "#0F172A",
"actorLineColor": "#CBD5E1",
"signalColor": "#D6006F",
"signalTextColor": "#0F172A",
"labelBoxBkgColor": "#F1F5F9",
"labelBoxBorderColor": "#E2E8F0",
"labelTextColor": "#0F172A",
"loopTextColor": "#0F172A",
"noteBkgColor": "#F1F5F9",
"noteBorderColor": "#CBD5E1",
"noteTextColor": "#0F172A",
"activationBkgColor": "#E3F6F9",
"activationBorderColor": "#007C91",
"sequenceNumberColor": "#D6006F",
"arrowheadColor": "#D6006F",
"fontFamily": "Inter, Segoe UI, Helvetica Neue, Arial, sans-serif",
"fontSize": "15px"
},
"flowchart": {"curve": "basis", "nodeSpacing": 28, "rankSpacing": 36, "padding": 8}
}}%%
sequenceDiagram
title A fatura espera pelas duas processadoras
participant A as Processadora A
participant B as Processadora B
participant F as Fatura de julho
Note over F: Em preparação
Note over A: 31/07 · 23:50<br/>C-50 · R$ 100
A->>F: C-50 processada às 00:20
Note over A: Processou todas as chegadas até 00:30
Note over F: Não emitir<br/>Aguardar a processadora B
Note over B: 31/07 · 23:55<br/>C-51 · R$ 80
B->>F: C-51 processada às 00:29
Note over B: Processou todas as chegadas até 00:30
Note over A,B: A e B processaram as<br/>chegadas até 00:30
Note over F: Emitir a fatura
A processadora A alcança 00:30 antes da processadora B. Enquanto B estiver atrás, a fatura permanece aberta porque a fronteira comum é limitada pela entrada menos avançada:
fronteira comum = menor fronteira entre as entradas
Quando B também alcança 00:30, a fatura pode ser emitida. Trocar compras e avisos de progresso é comunicação; impedir o fechamento até que ambas satisfaçam a fronteira comum é coordenação.
O limite disponível permanece revisável e incorpora cada mudança assim que ela fica disponível. A fatura espera porque precisa transformar seu valor corrente em definitivo.
6. Aprofundamento teórico: como respostas evoluem e se tornam definitivas
Duas perguntas organizam as possibilidades de resposta:
- Como ela evolui? Pode apenas acumular conclusões ou substituir as anteriores.
- Que estabilidade ela oferece? Pode permanecer revisável ou assumir uma versão definitiva.
Monotonicidade responde à primeira pergunta. O teorema CALM (Consistency As Logical Monotonicity) usa essa propriedade para explicar por que uma resposta não monotônica e definitiva precisa de coordenação.
Monotonicidade: preservar ou substituir conclusões
Se eu aprender mais alguma coisa, precisarei retirar uma conclusão que já produzi?
Quando a resposta é não, a computação é monotônica. Quando é sim, ela é não monotônica.
A chegada de C-48 produz efeitos diferentes nas duas respostas:
| Resposta | Antes de C-48 | Depois de C-48 | Conclusão anterior |
|---|---|---|---|
| Compras observadas | C-42, C-43 e C-45 | C-42, C-43, C-45 e C-48 | permanece |
| Limite disponível | R$ 890 | R$ 810 | é substituída |
As compras observadas formam uma resposta monotônica porque conhecer C-48 não retira as compras anteriores. O limite disponível é não monotônico porque R$ 890 deixa de ser a resposta quando o valor passa a R$ 810. A fatura em preparação se comporta como o limite.
Podemos formalizar essa diferença representando como conjuntos tanto a informação conhecida quanto as conclusões produzidas. Chamamos a informação disponível em um momento de I e esse mesmo conhecimento acrescido de novos fatos de J. Portanto, I ⊆ J.
A condição de monotonicidade é:
I ⊆ J ⇒ f(I) ⊆ f(J)
Aqui, f é a transformação que produz a resposta, e ⊆ significa “está contido em”:
I ⊆ J:Jcontém tudo que o pipeline sabia emIe possivelmente novos fatos;f(I) ⊆ f(J): as conclusões produzidas a partir deIcontinuam presentes depois quefprocessaJ.
Na primeira linha da tabela, f(I) ⊆ f(J). Na segunda, a condição não vale. Ser não monotônica, porém, não impede que uma resposta seja mantida incrementalmente por meio de correções.
CALM: quando avançar de forma independente não basta
Enquanto a processadora A já alcançou 00:30 e a processadora B ainda não, limite e fatura permanecem sujeitos à mesma incerteza: B ainda pode informar uma compra capaz de alterar seus valores. A diferença está na garantia de cada resposta:
| Resposta não monotônica | Garantia | O que o pipeline pode fazer enquanto B está atrasada |
|---|---|---|
| limite disponível | revisável | publicar o valor atual e corrigi-lo depois |
| fatura | definitiva depois da emissão | manter em preparação e esperar por B |
Uma execução é livre de coordenação quando cada parte pode avançar sem esperar que todas satisfaçam uma condição comum. O limite aceita esse avanço provisório; a emissão da fatura, não.
O teorema CALM formaliza o limite da execução sem coordenação. Nos modelos em que foi demonstrado:
computação monotônica
⇔
admite execução consistente sem coordenação
Consistente, aqui, significa que os mesmos fatos levam ao mesmo resultado, independentemente da ordem e da distribuição em que foram processados. Para o caso relevante do exemplo, a consequência é direta: quando uma resposta não monotônica precisa se tornar definitiva, é necessário coordenar.
A fatura continuou sendo mantida por deltas durante todo o ciclo. A coordenação apareceu apenas no fechamento e não exigiu um full load.
O Keeping CALM: When Distributed Consistency is Easy apresenta essa relação, e a prova de Ameloot, Neven e Van den Bussche demonstra a equivalência em um modelo formal específico.
7. Como decidir entre full load e processamento incremental
A escolha não se resume a verificar se uma ferramenta consegue ler menos linhas. Ela exige responder a três perguntas.
1. A mudança possui informação suficiente?
O pipeline precisa de uma chave estável, das informações que distinguem inserções, atualizações e exclusões e dos horários que identificam registros tardios. Quando uma mudança chega incompleta, ele também precisa conseguir formar o estado de reconstrução por chave.
Se a origem não fornece informação suficiente e o pipeline não consegue reconstruir o contexto a partir do que preservou, não há como derivar deltas corretos. Nesse caso, o full load pode ser a única reconstrução confiável.
2. A manutenção custa menos do que a reconstrução?
Ser incrementalizável não torna uma transformação automaticamente vantajosa. O benefício aparece quando o trabalho acompanha o tamanho da mudança, em vez do tamanho da base. Para avaliar isso, é preciso considerar o estado por chave preservado e o custo de atualizar cada agregação. No exemplo, SUM aplica o delta diretamente; MAX pode precisar percorrer todas as chaves válidas para encontrar um substituto.
Quando quase toda a base muda ou quando preservar e consultar essas informações custa tanto quanto recomputar, o full load continua sendo uma escolha coerente.
3. Que garantia o resultado precisa oferecer?
Um resultado pode avançar sem fechamento quando apenas acumula conclusões ou quando o consumidor aceita revisões. Um resultado definitivo precisa declarar quais dados pertencem ao cálculo, até quando serão aceitos e o que acontecerá com registros tardios. Se a computação for não monotônica e depender de entradas distribuídas, o fechamento também exigirá completude e coordenação.
O incremental faz sentido quando as mudanças podem ser interpretadas, manter o resultado custa menos do que reconstruí-lo e o pipeline consegue entregar a garantia esperada.
Quando não é possível reconstruir o contexto das mudanças ou o custo da manutenção se aproxima do custo de reler a base, o full load permanece coerente. Ele também continua útil para inicialização, reconciliação e recuperação.
A escolha, portanto, não é entre uma técnica antiga e outra moderna. É entre reconstruir o resultado ou assumir explicitamente as informações, correções e garantias necessárias para mantê-lo ao longo do tempo.