Delta e Lakeflow: por onde o dado ruim passa sem dar erro

Delta e Lakeflow: por onde o dado ruim passa sem dar erro

Read in English: Delta and Lakeflow: where bad data passes with no error

Imagine um pipeline de sinistros em que o painel de qualidade está verde. A regra “valor pago maior ou igual a zero” foi declarada, e mesmo assim um sinistro com valor negativo entrou na tabela. Na mesma semana, o sistema de origem passou a enviar uma coluna nova, currency, e ela simplesmente sumiu, sem erro. Nenhum dos dois defeitos derrubou o pipeline. O caso é ilustrativo, mas o padrão é real: declarar uma regra e barrar o dado são coisas diferentes, e cada camada do Databricks e do Fabric escolhe o seu comportamento.

Declarar não é barrar

O que é um contrato de qualidade na entrada do dado?

É a lista do que o dado precisa cumprir antes de ser gravado, somada ao que acontece quando ele não cumpre: barrar, reter e contar, descartar ou só declarar. Não é um produto; é uma decisão de arquitetura que cada camada responde de um jeito.

O artigo sobre ontologia e dado governado tratou do sentido do dado e do contexto. Aqui o recorte é a conformidade: o que a camada de gravação faz quando o dado viola a regra.

Camada Padrão quando o dado viola Efeito
Delta: NOT NULL e CHECK Barra A transação falha com erro
Delta: chave primária, chave estrangeira e unique Só declara Não são impostas
Lakeflow: expectativa sem ação Retém O registro inválido é gravado no destino
Lakeflow: ON VIOLATION DROP ROW Descarta O registro some antes da gravação
Lakeflow: ON VIOLATION FAIL UPDATE Barra A atualização falha
Delta: MERGE com INSERT * e coluna extra na origem Ignora a coluna nova A gravação é aceita sem a coluna
Auto Loader com schema informado Ignora a coluna nova O stream não falha
Auto Loader sem schema informado Evolui o schema (addNewColumns) O stream para e, no reinício, segue com a coluna nova
Fabric: materialized lake view Barra, porque FAIL é o padrão A atualização para na primeira violação

Leitura das fontes: 15/10/2026. O quadro resume a documentação lida nessa data. O exemplo da seção prática foi executado localmente; nada aqui foi medido em um workspace Databricks ou em um tenant Fabric.

O problema que resolve

Quando a regra só existe no papel, o erro aparece longe da origem: no relatório que não fecha, na métrica que muda sem explicação. O mapa acima evita três erros: supor que toda regra declarada barra, tratar descarte e retenção como se fossem a mesma coisa e esquecer as portas que não passam pela regra, como o MERGE e a coluna nova no ingestor.

Quadro de seis faixas e três colunas: Auto Loader, expectativa do Lakeflow e constraint do Delta. As faixas mostram a fonte, o ponto do fluxo, o controle, o padrão, o que passa e como fazer barrar. Verde marca o que barra; âmbar, o que retém e conta; rosa, o que só declara ou ignora.
Três controles, seis perguntas. Verde barra, âmbar retém e conta, rosa deixa passar ou só declara. O caso de sinistros é ilustrativo. Clique para ampliar.

Como funciona — passo a passo

  1. Liste os defeitos que importam: valor negativo, identificador nulo, coluna nova, duplicidade.
  2. Decida o que cada defeito merece: barrar, reter em quarentena, reter e contar, ou descartar. É decisão de negócio, não de ferramenta.
  3. Ponha cada controle na camada que o impõe: NOT NULL e CHECK na tabela Delta, expectativa no pipeline Lakeflow, failOnNewColumns no Auto Loader.
  4. Teste as portas laterais: MERGE, append com coluna nova, modo de evolução do Auto Loader e reprocessamento.
  5. Leia a contagem de registros retidos ou descartados e transforme-a em alerta. Uma contagem que ninguém lê não protege nada.
  6. Marque o que só é informacional, como chave primária e unique, para ninguém tratá-lo como barreira.

O que barra: constraints e schema enforcement no Delta

No Delta, NOT NULL e CHECK são constraints impostas: segundo a documentação do Databricks, quando uma delas é violada, a transação falha com um erro. O Databricks também valida o schema na gravação: todas as colunas inseridas precisam existir na tabela de destino, e o exemplo da página falha ao inserir uma coluna desconhecida.

Há dois custos a conhecer. O primeiro: adicionar uma constraint eleva o protocolo de escrita da tabela quando a versão atual é menor que 3. Confira os clientes externos que gravam nela antes de aplicar. O segundo: ALTER TABLE ... ADD CONSTRAINT verifica todas as linhas existentes antes de aceitar o CHECK, e o mesmo vale ao adicionar NOT NULL. Em uma tabela que já recebeu dado ruim, a constraint não entra até o legado ser limpo, o que transforma a primeira aplicação em um pequeno projeto de saneamento.

-- Antes de aplicar, meça o legado que impediria a constraint
SELECT count(*) AS violacoes FROM claims WHERE paid_amount < 0;

ALTER TABLE claims ADD CONSTRAINT non_negative_amount CHECK (paid_amount >= 0);
ALTER TABLE claims ALTER COLUMN claim_id SET NOT NULL;

Chave primária, chave estrangeira e unique são constraints informacionais: definem relações entre campos e não são impostas. Quem espera barrar duplicidade com unique precisa de outra checagem. A própria página das constraints remete às expectativas do Lakeflow como conceito relacionado.

O que deixa passar: MERGE, Auto Loader e expectativa warn

MERGE com INSERT * e UPDATE SET *. A origem precisa ter todas as colunas do destino, e a imposição ignora colunas da origem que o destino não tem. No exemplo local, o MERGE com a coluna extra currency foi aceito e a coluna não entrou na tabela.

A porta também pode ser aberta de propósito. Segundo a referência de sintaxe do MERGE, no Databricks Runtime 15.2 ou superior, MERGE WITH SCHEMA EVOLUTION atualiza o schema da tabela de destino para acompanhar a origem, e a coluna nova passa a existir. São três comportamentos possíveis para o mesmo defeito, e só um deles deveria acontecer por acaso: nenhum.

Escolha no MERGE O que acontece com currency
MERGE padrão com INSERT * A coluna é ignorada e some sem erro
MERGE WITH SCHEMA EVOLUTION A coluna é adicionada ao destino
Teste de schema antes do MERGE O job falha se a origem trouxer coluna fora do contrato

Auto Loader com schema informado. O padrão de evolução é none: a coluna nova é ignorada e o stream não falha. Com failOnNewColumns, o stream falha e só reinicia depois de você atualizar o schema ou remover o arquivo ofensor. Com rescue, a coluna nova vai para a coluna de dados resgatados.

Sem schema informado, o padrão muda para addNewColumns: o stream para quando a coluna nova aparece, depois de o Auto Loader registrá-la no schema location; no reinício, segue com o schema atualizado. Ou seja, o mesmo Auto Loader ignora a coluna em um pipeline e interrompe o stream em outro, dependendo só de o schema ter sido informado.

Modo (cloudFiles.schemaEvolutionMode) Coluna nova chega Quando é o padrão
addNewColumns O stream para depois de atualizar o schema; no reinício, segue com ele Sem schema informado
none Ignorada; o stream segue Com schema informado
rescue Vai para a coluna de dados resgatados; o stream segue —
failOnNewColumns O stream falha até o schema ser atualizado ou o arquivo removido —

Para o caso de sinistros, em que a coluna nova precisa chamar atenção, a escolha explícita fica assim (trecho não executado):

(spark.readStream.format("cloudFiles")
    .option("cloudFiles.format", "json")
    .option("cloudFiles.schemaEvolutionMode", "failOnNewColumns")
    .schema("claim_id STRING, paid_amount DOUBLE, status STRING")
    .load("<caminho-da-origem>"))

Expectativa sem ação. Reter registros inválidos é o comportamento padrão, chamado warn na documentação: o registro inválido é gravado no destino. Com ON VIOLATION DROP ROW, ele é descartado antes da gravação e a contagem é registrada; com ON VIOLATION FAIL UPDATE, a atualização falha. Reter e contar pode ser a escolha certa, desde que alguém leia a contagem.

O Fabric escolheu outro padrão

Nas materialized lake views do Fabric, a ação padrão é FAIL: a atualização para na primeira violação, mesmo que você não a especifique. Com DROP, o processamento continua e os registros que violam a restrição são removidos; a visão de linhagem mostra a contagem. Se a mesma view define DROP e FAIL, FAIL prevalece.

É o inverso do padrão do Lakeflow: lá, a expectativa sem ação retém; aqui, a restrição sem ação interrompe. Quem leva uma regra de um lado para o outro herda um comportamento diferente sem mudar uma linha.

A precedência do FAIL tem uma consequência prática. No Lakeflow, cada expectativa tem a sua ação e elas convivem no mesmo dataset. Na view abaixo (trecho não executado, sintaxe da referência do Fabric), a intenção parece ser “barrar sinistro sem identificador e descartar valor negativo”, mas a documentação diz que, com as duas ações na mesma view, o FAIL prevalece:

CREATE OR REPLACE MATERIALIZED LAKE VIEW silver.claims_clean
(
  CONSTRAINT has_claim_id CHECK (claim_id IS NOT NULL) ON MISMATCH FAIL,
  CONSTRAINT non_negative_amount CHECK (paid_amount >= 0) ON MISMATCH DROP
)
AS SELECT claim_id, paid_amount, status FROM bronze.claims;

Leitura minha: se as duas ações forem necessárias, separe-as em duas views encadeadas, uma com DROP e outra com FAIL, em vez de confiar na combinação. Outro ponto: a linhagem mostra quantos registros foram descartados, não quais. Para guardar as linhas rejeitadas, aplique a mesma regra em uma etapa anterior que grave os inválidos em uma tabela própria.

Um caso de uso: sinistros na seguradora

O caso é ilustrativo: uma seguradora ingere sinistros e alimenta um painel de pagamentos. O artigo sobre a arquitetura medalhão mostra onde ficam as camadas; aqui o ponto é o que cada porta de entrada faz com o defeito.

Defeito Controle que barra Sem o controle
Valor pago negativo CHECK (paid_amount >= 0) no Delta ou FAIL UPDATE na expectativa Entra na tabela; com expectativa sem ação, entra e é contado
Sinistro sem identificador NOT NULL em claim_id Entra uma linha sem identificador
Coluna nova na origem (currency) failOnNewColumns no Auto Loader; o append no Delta já falha Com schema informado, o Auto Loader ignora; o MERGE ignora a coluna extra
Identificador duplicado Checagem própria: unique e chave primária são informacionais Duas linhas para o mesmo sinistro

Barrar, reter ou descartar: como decidir

A ferramenta executa a ação; quem escolhe é o negócio. Quatro perguntas resolvem a maior parte dos casos:

Pergunta sobre o defeito Se a resposta é sim No caso de sinistros
Ele corrompe dinheiro, identidade ou obrigação regulatória? Barrar (CHECK, NOT NULL, FAIL UPDATE) Valor pago negativo, sinistro sem claim_id
O registro tem valor, mas precisa de correção e reprocessamento? Quarentena: separar em um caminho próprio, para correção Sinistro com status desconhecido
O registro é ruído, sem valor de negócio? Descartar e contar (DROP ROW) Linha de teste ou de heartbeat do sistema de origem
A regra é nova e ninguém sabe a taxa de violação? Reter e contar (warn) por um ciclo, e então decidir Regra recém-criada de formato de CPF

O erro mais comum é usar DROP ROW para um defeito da primeira linha: o sinistro com valor negativo some do painel de pagamentos, a soma fecha e ninguém investiga. Descartar é adequado para ruído, não para dinheiro.

Na prática: o que a tabela Delta barra e o que aceita

Executei o exemplo abaixo localmente, com delta-rs (deltalake 1.6.6 e pyarrow 25.0.1). Ele não rodou em um workspace Databricks nem em um tenant Fabric: mostra o comportamento de uma tabela Delta com CHECK e NOT NULL. O que a documentação diz sobre MERGE, Auto Loader e expectativas fica citado, não reproduzido.

import json
import tempfile

import pyarrow as pa
from deltalake import DeltaTable, write_deltalake
from deltalake.exceptions import DeltaError

REQUIRED = pa.schema([
    pa.field("claim_id", pa.string(), nullable=False),
    pa.field("paid_amount", pa.float64()),
    pa.field("status", pa.string()),
])


def claims(rows, currency=None, schema=None):
    data = {
        "claim_id": pa.array([r[0] for r in rows], pa.string()),
        "paid_amount": pa.array([r[1] for r in rows], pa.float64()),
        "status": pa.array([r[2] for r in rows], pa.string()),
    }
    if currency:
        data["currency"] = pa.array([currency] * len(rows), pa.string())
    return pa.table(data, schema=schema)


def append(path, source):
    write_deltalake(path, source, mode="append")


def merge(path, source):
    (
        DeltaTable(path)
        .merge(source, "t.claim_id = s.claim_id", source_alias="s", target_alias="t")
        .when_matched_update_all()
        .when_not_matched_insert_all()
        .execute()
    )


def try_write(path, source, write):
    try:
        write(path, source)
        verdict = "accepted"
    except DeltaError:
        verdict = "blocked"
    table = DeltaTable(path)
    return {
        "verdict": verdict,
        "rows": table.to_pyarrow_table().num_rows,
        "version": table.version(),
        "source_columns": source.num_columns,
        "table_columns": len(table.schema().fields),
    }


def make_table(folder, constrained=True):
    path = f"{folder}/claims"
    first = claims([("C1", 100.0, "paid"), ("C2", 50.0, "open")], schema=REQUIRED)
    write_deltalake(path, first)
    if constrained:
        DeltaTable(path).alter.add_constraint({"non_negative_amount": "paid_amount >= 0"})
    return path


def run():
    with tempfile.TemporaryDirectory() as folder:
        path = make_table(folder)
        return {
            "check_on_append": try_write(path, claims([("C3", -50.0, "paid")]), append),
            "null_id_on_append": try_write(path, claims([(None, 10.0, "paid")]), append),
            "new_column_on_append": try_write(path, claims([("C4", 10.0, "paid")], "BRL"), append),
            "new_column_on_merge": try_write(path, claims([("C5", 30.0, "open")], "BRL"), merge),
            "check_on_merge": try_write(path, claims([("C6", -5.0, "paid")]), merge),
        }


print(json.dumps(run(), indent=2))

A saída, copiada da execução:

{
  "check_on_append": {
    "verdict": "blocked",
    "rows": 2,
    "version": 1,
    "source_columns": 3,
    "table_columns": 3
  },
  "null_id_on_append": {
    "verdict": "blocked",
    "rows": 2,
    "version": 1,
    "source_columns": 3,
    "table_columns": 3
  },
  "new_column_on_append": {
    "verdict": "blocked",
    "rows": 2,
    "version": 1,
    "source_columns": 4,
    "table_columns": 3
  },
  "new_column_on_merge": {
    "verdict": "accepted",
    "rows": 3,
    "version": 2,
    "source_columns": 4,
    "table_columns": 3
  },
  "check_on_merge": {
    "verdict": "blocked",
    "rows": 3,
    "version": 2,
    "source_columns": 3,
    "table_columns": 3
  }
}
Caso Veredito Linhas na tabela Versão
Valor negativo em append Barrado 2 1
Identificador nulo em append Barrado 2 1
Coluna nova em append Barrado 2 1
Coluna nova em MERGE Aceito 3 2
Valor negativo em MERGE Barrado 3 2

A tabela começa com duas linhas: a versão 0 é a gravação inicial e a 1, a constraint. Os três primeiros casos foram barrados e a tabela não mudou. O MERGE com currency foi aceito, criou a versão 2 com uma linha nova e deixou a coluna de fora: a tabela segue com três colunas. O último caso mostra que o CHECK também vale dentro do MERGE. Os erros observados foram DeltaError com “1 rows failed validation check” para CHECK e NOT NULL, e SchemaMismatchError para a coluna nova no append.

No Lakeflow, o mesmo contrato vira expectativa. Este trecho não foi executado: adapta ao caso a sintaxe mostrada na documentação, seguindo o quadro de decisão acima. Os dois defeitos que tocam dinheiro e identidade barram; o ruído do sistema de origem é descartado e contado.

CREATE OR REFRESH STREAMING TABLE claims_clean(
  CONSTRAINT has_claim_id        EXPECT (claim_id IS NOT NULL)     ON VIOLATION FAIL UPDATE,
  CONSTRAINT non_negative_amount EXPECT (paid_amount >= 0)         ON VIOLATION FAIL UPDATE,
  CONSTRAINT not_heartbeat       EXPECT (status <> 'heartbeat')    ON VIOLATION DROP ROW
) AS SELECT * FROM STREAM(raw.claims);

Quarentena: reter sem contaminar

Entre barrar tudo e descartar em silêncio há um meio-termo que a documentação descreve entre os padrões avançados de expectativas: marcar o registro inválido e separar os caminhos: uma view lê os válidos e outra os inválidos, ambas sobre a mesma tabela marcada. O pipeline não para, o painel recebe só o que é válido e as linhas inválidas seguem por um caminho separado, para correção. A página não registra o motivo da rejeição; gravar os inválidos em uma tabela própria é adaptação minha.

O trecho abaixo não foi executado; segue a estrutura do padrão documentado, adaptada por mim (coalesce e tabelas para os dois caminhos), aplicada aos sinistros:

from pyspark import pipelines as dp
from pyspark.sql.functions import expr

rules = {
    "has_claim_id": "claim_id IS NOT NULL",
    "non_negative_amount": "paid_amount >= 0",
}
# coalesce(..., false): uma comparação com NULL também vai para a quarentena
quarantine_rule = "NOT coalesce(({0}), false)".format(" AND ".join(rules.values()))

@dp.table(name="claims_flagged")
@dp.expect_all(rules)  # sem ação: retém e conta, por regra
def claims_flagged():
    return (
        spark.readStream.table("raw.claims")
        .withColumn("is_quarantined", expr(quarantine_rule))
    )

@dp.table(name="claims_valid")
def claims_valid():
    return spark.readStream.table("claims_flagged").filter("NOT is_quarantined")

@dp.table(name="claims_quarantine")
def claims_quarantine():
    return spark.readStream.table("claims_flagged").filter("is_quarantined")

O coalesce é uma escolha deliberada: em SQL, paid_amount >= 0 com paid_amount nulo resulta em NULL, a negação de NULL continua NULL, e os dois filtros deixam a linha de fora. Sem esse cuidado, o registro não aparece nem na tabela válida nem na quarentena: a quarentena ganha a sua própria porta lateral.

Duplicidade: a checagem que a chave não faz

Como chave primária e unique não são impostas, a duplicidade precisa de uma validação própria. Um dos padrões avançados da documentação usa uma materialized view auxiliar que conta ocorrências por chave. No exemplo Python da página, ela falha a atualização (expect_or_fail) quando uma chave se repete; a versão SQL abaixo é adaptação minha (trecho não executado):

CREATE OR REFRESH MATERIALIZED VIEW claims_pk_check(
  CONSTRAINT unique_claim_id EXPECT (num_entries = 1) ON VIOLATION FAIL UPDATE
) AS
SELECT claim_id, count(*) AS num_entries
FROM claims_clean
GROUP BY claim_id;

A view não impede a linha duplicada de entrar em claims_clean; ela faz falhar o fluxo da própria view, mas não segura as demais tabelas. Para barrar o que vem depois, separe a validação e o trabalho a jusante em pipelines distintos, orquestrados por um job em que a tarefa a jusante dependa da de validação. Se a duplicidade não pode nem chegar à tabela, o lugar dela é um MERGE pela chave, em vez de append.

Lendo a contagem

“Reter e contar” só protege se a contagem for lida. No Lakeflow, os resultados das expectativas ficam no event log do pipeline, nos eventos do tipo flow_progress. A documentação recomenda hoje a system table pipeline_events, que reúne os eventos de todos os pipelines dos workspaces da região e está em Beta; o event log por pipeline continua disponível pela função event_log().

A consulta abaixo usa o formato documentado para extrair registros aprovados e reprovados por expectativa, mas soma todas as atualizações guardadas no event log; a documentação filtra a última atualização por origin.update_id e avisa que alguns datasets podem não ter métricas (trecho não executado):

SELECT
  e.dataset,
  e.name                 AS expectation,
  SUM(e.passed_records)  AS passed,
  SUM(e.failed_records)  AS failed
FROM (
  SELECT explode(
    from_json(
      details:flow_progress:data_quality:expectations,
      'array<struct<name: string, dataset: string, passed_records: int, failed_records: int>>'
    )
  ) AS e
  FROM event_log('<pipeline-id>')
  WHERE event_type = 'flow_progress'
)
GROUP BY e.dataset, e.name
ORDER BY failed DESC;

Na system table pipeline_events, a documentação lê details com variant_get; esta consulta precisa de adaptação.

O passo seguinte é transformar a consulta em alerta, com um limite por expectativa, ou usar event hooks, que a página de monitoramento cita para executar ações, como enviar alertas, quando eventos são registrados. No Fabric, a contagem de descartes aparece na linhagem da materialized lake view.

Onde o agente entra

Um agente que consulta essa tabela responde com a mesma fluência quando o dado está certo e quando não está. Essa ponte é leitura minha, não afirmação da documentação: se o valor negativo entrou, o agente não tem como saber. A página do Genie Agent descreve um ambiente de domínio em que o time de dados configura dado confiável, métricas e regras de negócio; o que torna o dado confiável acontece antes, na gravação.

O data profiling, que antes se chamava Lakehouse Monitoring, fornece estatísticas resumidas da tabela. Pelo que a página descreve, ele mede. Que ele não bloqueia a gravação também é leitura minha. Por isso a ordem importa: a constraint e a expectativa decidem o que entra; o profiling observa o que entrou; e o agente deveria consultar só a tabela que passou pelas duas, como claims_valid, nunca a de quarentena.

Boas práticas para produção

  • Declare a ação de cada regra. Expectativa sem ação retém; se a intenção é barrar, escreva FAIL UPDATE.
  • Barre na tabela o que não pode entrar. NOT NULL e CHECK valem para toda gravação, inclusive MERGE, como o exemplo local mostrou.
  • Trate chave e unique como documentação, não como controle, e verifique duplicidade com uma checagem própria.
  • Fixe o comportamento da coluna nova. Com schema informado, o Auto Loader ignora; escolha failOnNewColumns ou rescue de propósito.
  • Use quarentena para o que tem valor. Descartar é para ruído; registro com dinheiro ou obrigação vai para quarentena, separado do fluxo válido, para correção e reprocessamento.
  • Limpe o legado antes da constraint. ADD CONSTRAINT e SET NOT NULL validam as linhas existentes; meça as violações antes de aplicar.
  • Abra a porta do MERGE só de propósito. WITH SCHEMA EVOLUTION adiciona a coluna nova; o padrão a ignora. Nenhum dos dois deveria ser surpresa.
  • Leia a contagem. Reter ou descartar sem ninguém olhar a métrica só adia o problema. Transforme a consulta ao event log em alerta com limite por regra.
  • Revise ao migrar de camada. O Lakeflow retém por padrão e o Fabric interrompe por padrão; no Fabric, FAIL prevalece sobre DROP na mesma view.
  • Versione as regras junto do pipeline. Expectativas são código; o artigo sobre Lakeflow como código mostra o caminho de deploy em que elas viajam com o repositório.

O que precisa estar de pé

A ordem abaixo é causal, não cronológica: cada estágio existe porque o anterior ficou de pé.

Fundação — cada regra tem uma ação declarada. Toda regra de qualidade na entrada diz se barra, retém ou descarta; o que precisa barrar vira NOT NULL, CHECK ou FAIL UPDATE; o que só declara, como chave e unique, está marcado como informacional.

Está de pé quando, para cada regra, alguém responde em um minuto se ela barra, retém ou só declara.

Produção com contexto — as portas laterais têm teste. MERGE, append com coluna nova e o modo de evolução do Auto Loader estão cobertos por teste, os registros com valor vão para quarentena, e a contagem de registros retidos ou descartados chega, como alerta, a quem pode agir.

Está de pé quando um registro retido ou descartado aparece em uma contagem que alguém lê e responde.

Escala e eficiência — o padrão vem no template. O template de pipeline do time traz as ações explícitas, os testes das portas laterais e a regra de que unique e chave primária não contam como controle.

Está de pé quando um pipeline novo chega a produção com as ações declaradas sem ninguém lembrar de pedir no review.

Perguntas frequentes (FAQ)

Chave primária e unique barram duplicidade no Delta?

Não. Na documentação do Databricks, chave primária, chave estrangeira e unique são informacionais: definem relações entre campos e não são impostas. Barre com NOT NULL e CHECK e verifique duplicidade com uma checagem própria.

A expectativa do Lakeflow barra o dado ruim?

Depende da ação. Sem ação, o registro inválido é gravado no destino; DROP ROW descarta antes da gravação; FAIL UPDATE impede a atualização.

O Auto Loader falha quando chega uma coluna nova?

Com schema informado, o padrão é none: a coluna nova é ignorada e o stream não falha. Com failOnNewColumns, o stream falha e só reinicia depois que você atualiza o schema ou remove o arquivo ofensor.

O Fabric trata a violação como o Lakeflow?

Não. Na materialized lake view, FAIL é o padrão e a atualização para na primeira violação; com DROP, os registros são removidos e a linhagem mostra a contagem. Se a mesma view tiver DROP e FAIL, o FAIL prevalece.

O MERGE pode adicionar a coluna nova em vez de ignorá-la?

Sim. No Databricks Runtime 15.2 ou superior, MERGE WITH SCHEMA EVOLUTION atualiza o schema do destino para acompanhar a origem. Sem essa cláusula, com INSERT * e UPDATE SET *, a coluna extra da origem é ignorada.

Como vejo quais registros foram descartados?

A contagem existe para warn e drop: no Lakeflow, no event log do pipeline (com fail, a atualização falha e não há métricas registradas; alguns datasets também não têm); no Fabric, na linhagem. As linhas em si não ficam guardadas com DROP. Para inspecioná-las, use o padrão de quarentena, que separa os inválidos em uma view sobre a tabela marcada.

Posso adicionar um CHECK em uma tabela que já tem dado ruim?

Não sem limpar antes. ALTER TABLE ... ADD CONSTRAINT verifica todas as linhas existentes e falha se alguma violar a regra.

Conclusão

Declarar uma regra não é barrar o dado. No Delta, NOT NULL e CHECK barram; no Lakeflow, a expectativa sem ação retém; chave e unique só declaram; o MERGE e o Auto Loader com schema informado ignoram a coluna nova; e o Fabric interrompe por padrão. Entre barrar e descartar existe a quarentena, e nenhuma delas vale sem alguém lendo a contagem. Antes de confiar no painel verde, pergunte de cada regra qual ação ela tem, por qual porta o dado pode contorná-la e quem é avisado quando isso acontece.

Referências oficiais

👉 Se você tem um pipeline em que a regra de qualidade está declarada, mas ninguém confirmou o que ela faz quando o dado viola, este é o momento de testar as portas laterais antes do próximo incidente. Quer trocar ideia sobre qualidade de dados no Databricks e no Fabric? Me chama no LinkedIn.

Compartilhe:

Eron Cavalcante

Eron Cavalcante atua como Cloud Solution Architect na Microsoft, com foco em plataformas de dados, IA generativa e arquitetura de nuvem para o setor financeiro. Combina experiência técnica em Azure, Databricks e engenharia de dados com uma visão de negócio orientada a valor, apoiando clientes na adoção responsável de IA em escala.

Deixe um comentário