Melhores práticas para LakeFlow Pipelines
Aplique estes padrões recomendados ao projetar, criar e operar pipelines, esteja você começando um novo pipeline ou melhorando um existente.
As páginas seguintes abordam com mais detalhes as decisões de design específicas:
tópico | Descrição |
|---|---|
Modele os dados da camada de ouro como fatos e dimensões em um esquema em estrela e mapeie a modelagem dimensional para os tipos de dataset de pipeline. | |
Entenda a idempotência e onde você obtém exatamente uma vez o processamento por default, versus as bordas em que você precisa adicionar gravações idempotentes. | |
Decida quantos conjuntos de dados pertencem a um único pipeline e quando dividir o trabalho em pipelines separados. | |
Siga uma lista de verificação sobre qualidade, confiabilidade, observabilidade, implementação, custo e governança de dados antes de executar um pipeline sem supervisão. |
Escolha o tipo de dataset correto
Os pipelines oferecem três tipos de datasets: tabelas de transmissão, views materializadas e views temporárias. Escolher o tipo certo para cada camada do seu pipeline evita custos de compute desnecessários e mantém seu código fácil de entender.
Tabelas de transmissão são a escolha certa para ingestão de dados e transformações de transmissão de baixa latência. Cada linha de entrada é lida e processada apenas uma vez, o que as torna ideais para cargas de trabalho somente de acréscimo, dados de alto volume e processamento orientado a eventos a partir de armazenamento em cloud ou barramentos de mensagens.
As visões materializadas são a escolha certa para transformações complexas e consultas analíticas. Os resultados são pré-computados e mantidos atualizados por meio de incremental refresh, portanto, as consultas a eles são rápidas. Não é possível modificar diretamente os dados em uma view materializada; a definição da query controla a saída.
As visualizações temporárias são visualizações com escopo de pipeline que organizam sua lógica de transformação sem materializar quaisquer dados no armazenamento. Use-as para etapas intermediárias que não precisam de uma tabela própria.
A tabela a seguir resume quando usar cada tipo:
Caso de uso | Tipo recomendado | Motivo |
|---|---|---|
Ingestão a partir de armazenamento em cloud ou barramento de mensagens | Tabela de transmissão | Processa cada registro uma vez; lida com alto volume e cargas de trabalho somente de acréscimo. |
Transmissões do CDC (inserções, atualizações, exclusões) | Tabela de transmissão | Usado como destino de |
Agregações e joins complexos | Visualização materializada | Atualização incremental; evita o recálculo completo a cada atualização. |
Aceleração de query de dashboard | Visualização materializada | Resultados pré-computados tornam as consultas mais rápidas do que em tabelas brutas. |
Transformações intermediárias (sem leitores downstream) | Vista temporária | Organiza a lógica do pipeline sem incorrer em custos de armazenamento. |
Para mais informações, consulte Tabelas de streaming, Views materializadas e O que são LakeFlow Pipelines?.
Use CDC declarativo em vez de MERGE imperativo
A implementação da captura de dados de alterações (CDC) com instruções SQL MERGE imperativas requer um código personalizado significativo para lidar corretamente com a ordenação de eventos, desduplicação, atualizações parciais e evolução do esquema. Cada uma dessas preocupações deve ser resolvida de forma independente, e o código resultante é difícil de manter e testar.
Os pipelines fornecem a instrução AUTO CDC ... INTO (SQL) e a função create_auto_cdc_flow() (Python), que lidam com a ordenação, desduplicação, eventos fora de ordem e evolução do esquema de forma declarativa. Você descreve o formato do feed de alterações e da tabela de destino, e o pipeline cuida do resto. AUTO CDC suporta tanto o SCD Tipo 1 (sobrescrita) quanto o SCD Tipo 2 (preservação do histórico).
Para obter mais informações, consulte captura de dados de alterações (CDC) e Snapshots e As APIs AUTO CDC: simplifique a captura de dados de alterações com pipelines.
Garantir a qualidade dos dados com base nas expectativas.
Expectativas são expressões SQL de verdadeiro/falso aplicadas a cada linha que passa por um dataset. Quando uma linha não atende à condição, o pipeline responde de acordo com a política de violação que você configurou. As expectativas emitem métricas para o log de eventos do pipeline, independentemente da política, permitindo acompanhar as tendências de qualidade dos dados ao longo do tempo.
Escolha uma política de violação
Três políticas de violação estão disponíveis. Escolha aquela que corresponde à sua tolerância a dados de baixa qualidade:
- warn (default): registros que não são válidos são gravados na tabela de destino e sinalizados nas métricas. Use esta política quando precisar capturar todos os dados, mas desejar visibilidade sobre problemas de qualidade.
- drop : Registros inválidos são descartados antes da gravação. Use esta opção quando linhas com erros forem esperadas e não devem ser propagadas para os fluxos subsequentes.
- fail : a atualização do pipeline para no primeiro registro inválido. Use isso para dados críticos em que qualquer registro inválido indica um problema upstream sério.
Os exemplos a seguir mostram cada política aplicada a uma tabela de transmissão:
- SQL
- Python
-- Warn: write invalid records but track them in metrics
CREATE OR REFRESH STREAMING TABLE orders_raw (
CONSTRAINT valid_order_id EXPECT (order_id IS NOT NULL)
) AS SELECT * FROM STREAM read_files("/volumes/raw/orders", format => "json");
-- Drop: discard invalid records before writing
CREATE OR REFRESH STREAMING TABLE orders_clean (
CONSTRAINT non_negative_amount EXPECT (amount >= 0) ON VIOLATION DROP ROW
) AS SELECT * FROM STREAM(orders_raw);
-- Fail: stop the pipeline on any invalid record
CREATE OR REFRESH STREAMING TABLE orders_critical (
CONSTRAINT required_customer_id EXPECT (customer_id IS NOT NULL) ON VIOLATION FAIL UPDATE
) AS SELECT * FROM STREAM(orders_clean);
from pyspark import pipelines as dp
# Warn: write invalid records but track them in metrics
@dp.table
@dp.expect("valid_order_id", "order_id IS NOT NULL")
def orders_raw():
return spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "json") \
.load("/volumes/raw/orders")
# Drop: discard invalid records before writing
@dp.table
@dp.expect_or_drop("non_negative_amount", "amount >= 0")
def orders_clean():
return spark.readStream.table("orders_raw")
# Fail: stop the pipeline on any invalid record
@dp.table
@dp.expect_or_fail("required_customer_id", "customer_id IS NOT NULL")
def orders_critical():
return spark.readStream.table("orders_clean")
Colocar registros inválidos em quarentena
Quando você deseja preservar registros perdidos para investigação, em vez de descartá-los silenciosamente, use um padrão de quarentena. Direcione as linhas que não passarem na validação para uma tabela de transmissão separada usando dois fluxos: um que descarta as linhas inválidas da tabela principal e um segundo que grava apenas as linhas inválidas em uma tabela de quarentena. Isso permite investigar, corrigir e reprocessar dados incorretos sem contaminar seu dataset limpo.
Para um exemplo detalhado do padrão de quarentena, consulte Recomendações de expectativa e padrões avançados.
Para obter mais informações sobre expectativas, consulte Gerenciar a qualidade dos dados com expectativas de pipeline.
Parametrize seus pipelines
Os pipelines possuem configurações default de catálogo e esquema, portanto, o código que lê e grava dentro do mesmo catálogo e esquema funciona em diferentes ambientes sem a necessidade de parâmetros. No entanto, se o seu pipeline precisar referenciar um segundo catálogo ou esquema (por exemplo, lendo de um catálogo de origem compartilhado que difere entre desenvolvimento e produção), evite codificar esses nomes diretamente no seu código-fonte. Em vez disso, defina-os como parâmetros de configuração do pipeline (pares key-value definidos nas configurações do pipeline) e faça referência a eles em seu código. Isso permite que uma única base de código seja executada corretamente em diferentes ambientes, trocando os valores dos parâmetros.
- SQL
- Python
CREATE OR REFRESH MATERIALIZED VIEW transaction_summary AS
SELECT account_id, COUNT(txn_id) AS txn_count, SUM(amount) AS total_amount
FROM ${source_catalog}.sales.transactions
GROUP BY account_id;
from pyspark import pipelines as dp
from pyspark.sql.functions import count, sum
@dp.materialized_view
def transaction_summary():
source_catalog = spark.conf.get("source_catalog")
return spark.read.table(f"{source_catalog}.sales.transactions") \
.groupBy("account_id") \
.agg(
count("txn_id").alias("txn_count"),
sum("amount").alias("total_amount")
)
Para obter mais informações, consulte Usar parâmetros com pipelines.
Escolha entre o modo de pipeline Trigger e contínuo
Modo Triggered processa todos os dados disponíveis e, em seguida, para. É a escolha certa para a grande maioria dos pipelines: aqueles que são executados em uma programação (por hora, diariamente ou sob demanda) e não exigem frescor de dados inferior a um minuto.
O modo contínuo mantém o cluster em execução e processa novos dados à medida que chegam. É apropriado apenas quando seu caso de uso requer latência na faixa de segundos a minutos. Como o modo contínuo requer um cluster sempre ativo, ele é significativamente mais caro do que o modo Trigger.
O modo em tempo real baseia-se no modo contínuo para atingir latência de sub-segundo, na faixa de milissegundos, para cargas de trabalho operacionais, como detecção de fraudes ou personalização em tempo real. Isso requer configuração adicional e planejamento de compute. Consulte Usar o modo em tempo real em LakeFlow Pipelines.
Para obter mais informações, consulte Modo de pipeline Trigger vs. contínuo e Configurar pipelines.
Use clusters líquidos para disposição de dados
O agrupamento líquido substitui o particionamento estático e o ZORDER para otimizar a disposição de dados em tabelas Delta. O particionamento estático exige que você selecione colunas de partição e reorganize os dados antecipadamente, o que pode causar distorção nos dados para valores distribuídos de forma desigual. O agrupamento líquido é autoajustável, resistente a distorções e incremental, reescrevendo apenas os dados que precisam de reorganização em cada execução.
Altere as colunas de clustering a qualquer momento sem reescrever a tabela completa à medida que os padrões de query evoluem.
O Databricks recomenda o liquid clustering automático, que permite ao Databricks selecionar e manter as colunas de clustering ideais com base na sua carga de trabalho de query. Ative-o com CLUSTER BY AUTO:
- SQL
- Python
CREATE OR REFRESH STREAMING TABLE events
CLUSTER BY AUTO
AS SELECT * FROM STREAM read_files("/volumes/raw/events", format => "parquet");
from pyspark import pipelines as dp
@dp.table(cluster_by_auto=True)
def events():
return spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "parquet") \
.load("/volumes/raw/events")
Para escolher as colunas de clustering por conta própria, especifique-as explicitamente:
- SQL
- Python
CREATE OR REFRESH STREAMING TABLE events
CLUSTER BY (event_date, region)
AS SELECT * FROM STREAM read_files("/volumes/raw/events", format => "parquet");
from pyspark import pipelines as dp
@dp.table(cluster_by=["event_date", "region"])
def events():
return spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "parquet") \
.load("/volumes/raw/events")
Para obter mais informações, consulte Tabelas de transmissão e Usar clustering líquido para tabelas.
Gerenciar pipelines com CI/CD e Pacotes de Automação Declarativa
Faça o controle de versão do código-fonte do seu pipeline e use Pacotes de Automação Declarativa para gerenciar implantações em vários ambientes.
Para obter mais informações, consulte Criar um pipeline com controle de versão, Converter um pipeline em um projeto de pacote e Usar parâmetros com pipelines.
Armazene o código do pipeline no controle de versão.
Armazene todos os arquivos de código-fonte do pipeline (Python e SQL) junto com a configuração do seu bundle em um repositório Git. O controle de versão de todo o projeto oferece uma história completa das alterações, facilita a colaboração e permite validar as alterações em um ambiente de desenvolvimento antes de promovê-las para a produção.
O Databricks recomenda Pacotes de Automação Declarativa para gerenciar este fluxo de trabalho. Um bundle define a configuração do seu pipeline em YAML junto com seu código-fonte, e a CLI databricks bundle permite validar, implantar e executar pipelines a partir do seu terminal ou de um sistema de CI/CD.
Use alvos de pacote para isolamento de ambiente.
Os pacotes permitem vários destinos (por exemplo, dev, staging, prod), cada um com seu próprio conjunto de substituições para nomes de catálogo, políticas de cluster, endereços de notificação e outras configurações. Combine destinos de pacote com parâmetros de pipeline para injetar os valores corretos específicos do ambiente no momento da implantação, mantendo seu código-fonte livre de constantes de ambiente.
Um fluxo de trabalho típico tem a seguinte aparência:
- Um desenvolvedor trabalha em uma branch de recurso, implantando em um pipeline de desenvolvimento pessoal em um catálogo de dev.
- Ao fazer merge na branch principal, um sistema de CI executa
databricks bundle validateedatabricks bundle deploy --target stagingpara validar e implantar o pipeline em um ambiente de staging. - Após a aprovação nos testes, o sistema de CI realiza a implantação na produção com
databricks bundle deploy --target prod.
Práticas recomendadas de transmissão
Utilize estes padrões para gerenciar o estado, controlar dados atrasados e manter a confiabilidade dos pipelines de transmissão.
Para obter mais informações, consulte Otimizar o processamento com estado com marcas d'água, Recuperar um pipeline de falha de ponto de verificação de transmissão e Preenchimento de data histórica com pipelines.
Use marcas d'água para operações com estado
As marcas d'água limitam o estado que o pipeline mantém na memória durante operações de transmissão com estado, como agregações de janela e deduplicação. Sem uma marca d'água, o estado cresce sem limites à medida que o pipeline acumula dados para cada key possível, eventualmente causando erros de falta de memória em pipelines de longa execução.
Uma marca d'água especifica uma coluna de Timestamp e um limite de tolerância para dados atrasados. Os registros que chegam após o limite ter sido ultrapassado são descartados. Escolha um limite que equilibre sua tolerância a dados atrasados com o custo de memória de manter esse estado aberto.
O exemplo a seguir calcula uma agregação de janela rotativa de um minuto com uma marca d'água de três minutos:
- SQL
- Python
CREATE OR REFRESH STREAMING TABLE event_counts AS
SELECT window(event_time, '1 minute') AS time_window, region, COUNT(*) AS cnt
FROM STREAM(events_raw)
WATERMARK event_time DELAY OF INTERVAL 3 MINUTES
GROUP BY time_window, region;
from pyspark import pipelines as dp
from pyspark.sql.functions import window
@dp.table
def event_counts():
return (
spark.readStream.table("events_raw")
.withWatermark("event_time", "3 minutes")
.groupBy(window("event_time", "1 minute"), "region")
.count()
)
Para garantir que as agregações sejam processadas incrementalmente, em vez de serem totalmente recalculadas a cada atualização, você deve definir uma marca d'água.
Entenda o estado de transmissão e o refresh completo
O estado da transmissão é incremental: o pipeline cria e mantém o estado entre as atualizações em vez de recalcular a partir do zero a cada vez. É isso que torna a transmissão com estado eficiente, mas também significa que, se você alterar a lógica de uma query com estado (por exemplo, modificando um limite de marca d'água ou alterando colunas de agregação), o estado existente não será mais compatível com a nova lógica. Nesse caso, você deve realizar um refresh completo para reprocessar toda a data histórica com a nova lógica e reconstruir o estado a partir do zero.
Um full refresh também pode levar à perda de dados se a fonte não reter dados históricos. Por exemplo, uma fonte Kafka com um curto período de retenção pode ter apenas os últimos minutos de dados disponíveis no momento do refresh, resultando em uma tabela que contém muito menos dados do que antes. Planeje cuidadosamente as alterações na lógica de queries com estado, especialmente para transmissões de alto volume, onde um refresh completo é dispendioso ou onde a fonte tem retenção de dados limitada. Utilizar a arquitetura de medalhão ajuda a criar tabelas de bronze com transformação mínima e permite que tabelas de prata ou ouro sejam recalculadas a partir das tabelas de bronze com histórico completo.
Joins de transmissão-transmissão
Joins de transmissão-transmissão exigem um watermark em ambos os lados do join e uma condição de join com limite de tempo. O intervalo de tempo na condição de join informa ao mecanismo de transmissão quando não são mais possíveis correspondências, permitindo que ele remova o estado que não pode mais ser correspondido. Se você omitir os watermarks ou a condição de limite de tempo, o estado crescerá sem limites.
O exemplo a seguir faz um join de eventos de impressão de anúncio com eventos de clique, exigindo que o clique ocorra dentro de três minutos da impressão:
- SQL
- Python
CREATE OR REFRESH STREAMING TABLE impression_clicks AS
SELECT imp.ad_id, imp.impression_time, clk.click_time
FROM STREAM(ad_impressions)
WATERMARK impression_time DELAY OF INTERVAL 3 MINUTES AS imp
JOIN STREAM(user_clicks)
WATERMARK click_time DELAY OF INTERVAL 3 MINUTES AS clk
ON imp.ad_id = clk.ad_id
AND clk.click_time BETWEEN imp.impression_time
AND imp.impression_time + INTERVAL 3 MINUTES;
from pyspark import pipelines as dp
from pyspark.sql.functions import expr
dp.create_streaming_table("impression_clicks")
@dp.append_flow(target="impression_clicks")
def join_impressions_and_clicks():
impressions = spark.readStream.table("ad_impressions") \
.withWatermark("impression_time", "3 minutes")
clicks = spark.readStream.table("user_clicks") \
.withWatermark("click_time", "3 minutes")
return impressions.alias("imp").join(
clicks.alias("clk"),
expr("""
imp.ad_id = clk.ad_id AND
clk.click_time BETWEEN imp.impression_time AND imp.impression_time + INTERVAL 3 MINUTES
"""),
"leftOuter"
)
Ao fazer um join de uma transmissão com uma tabela estática (um snapshot join), o snapshot da tabela estática é atualizado no início de cada microbatch. Isso significa que registros de dimensão que chegam com atraso não são aplicados retroativamente a fatos que já foram processados. Se a aplicação retroativa for necessária, use uma view materializada ou reestruture o pipeline.
Otimizar o desempenho do pipeline
Aplique estas técnicas para reduzir os custos de compute e acelerar as atualizações de pipeline.
Para obter mais informações, consulte Materialized views e Otimizar o processamento com estado com marcas d'água.
Evite arquivos pequenos
Fazer o trigger de um pipeline com muita frequência em uma fonte de baixo volume grava um grande número de arquivos pequenos no armazenamento em cloud. Arquivos pequenos degradam o desempenho de leitura porque cada arquivo requer uma pesquisa de metadados separada e um round trip de E/S, e as APIs de armazenamento em cloud limitam as operações de listagem em escala. Para evitar isso, escolha um intervalo de Trigger que corresponda ao seu volume de dados: execute pipelines com Trigger em uma programar que permita que uma quantidade significativa de dados se acumule entre as atualizações, em vez de continuamente.
Lidar com a assimetria de dados
A distorção de dados ocorre quando os valores em uma **join** ou **key** de agrupamento são distribuídos de forma desigual entre as partições, fazendo com que um pequeno número de tarefas processe a maior parte dos dados. Isso cria pontos de acesso intenso que aumentam o tempo de atualização de ponta a ponta. Utilize o agrupamento líquido para corrigir a distorção em tabelas armazenadas. Para compensar a distorção que ocorre durante o processamento em tempo real, aplique um sufixo aleatório às chaves com alta distorção antes de agrupá-las e agregá-las em duas etapas.
Para obter mais informações, consulte Usar clustering líquido para disposição de dados.
Use incremental refresh para visualizações materializadas.
Ao usar uma view materializada para uma agregação grande, o pipeline tenta refresh incremental, processando apenas as alterações upstream desde a última atualização, em vez de recalcular todo o conjunto de resultados. O refresh incremental é significativamente mais barato do que executar novamente a query do zero a cada trigger de pipeline. Para maximizar a chance de uma view materializada poder ser atualizada incrementalmente, escreva queries de agregação simples e determinísticas e evite construções que impeçam o processamento incremental, como funções não determinísticas.
Consulte Refresh incremental para views materializadas.
Otimizar joins
Para junções em que um dos lados é uma tabela de dimensão pequena, adicione uma dica de transmissão para instruir o Spark a transmitir a tabela menor para todos os executores em vez de realizar uma junção aleatória (shuffle join):
- SQL
- Python
CREATE OR REFRESH MATERIALIZED VIEW enriched_orders AS
SELECT o.*, /*+ BROADCAST(p) */ p.product_name, p.category
FROM orders o
JOIN products p ON o.product_id = p.product_id;
from pyspark import pipelines as dp
from pyspark.sql.functions import broadcast
@dp.materialized_view
def enriched_orders():
orders = spark.read.table("orders")
products = spark.read.table("products")
return orders.join(broadcast(products), "product_id")
Para joins de séries temporais por proximidade (por exemplo, encontrar o evento mais próximo dentro de um intervalo de tempo), use uma condição de join por intervalo e certifique-se de que ambos os lados tenham uma marca d'água ao fazer join de transmissões, ou considere agrupar os eventos em intervalos de tempo antes do join.
Monitore seus pipelines
O registro de eventos do pipeline é o principal elemento primitivo de observabilidade em pipelines. Cada execução do pipeline grava registros estruturados no log de eventos, abrangendo o progresso da execução, os resultados esperados em relação à qualidade dos dados, a linhagem dos dados e os detalhes dos erros. O registro de eventos é uma tabela Delta que você pode query diretamente.
Para consultar o log de eventos sem saber o caminho de armazenamento subjacente, use a função event_log() com valor de tabela em um cluster compartilhado ou SQL Warehouse:
SELECT * FROM event_log('<pipeline-id>')
WHERE event_type = 'flow_progress'
ORDER BY timestamp DESC
LIMIT 100;
Crie painéis de qualidade de dados consultando o event log para obter as métricas de expectativa. A coluna details contém uma estrutura JSON aninhada com contagens de aprovação/reprovação para cada restrição, que você pode usar para acompanhar as tendências de qualidade ao longo do tempo e receber alertas sobre regressões.
Para alertas orientados a eventos, use ganchos de eventos para Trigger webhooks personalizados ou serviços de notificação (como Slack ou PagerDuty) quando um pipeline falhar ou quando um limite de qualidade de dados for violado. Ganchos de eventos são funções Python executadas em resposta a eventos de pipeline.
Para obter mais informações, consulte Monitorar pipelines, Log de eventos do pipeline e Definir monitoramento personalizado de pipelines com ganchos de eventos.
Usar compute serverless
O Databricks recomenda compute serverless para novos pipelines. Com o serverless, não há configuração manual de cluster; o Databricks gerencia a infraestrutura automaticamente. Os pipelines serverless usam autoscale aprimorado que pode escalar tanto horizontalmente (mais executors) quanto verticalmente (tamanho maior de executor) em resposta às demandas de carga de trabalho. Os pipelines serverless sempre usam o Unity Catalog, portanto, a governança e o acompanhamento de linhagem são integrados por default.
O Serverless também é necessário para refresh incremental de views materializadas. No compute clássico, as views materializadas são sempre totalmente recalculadas, o que aumenta os custos de refresh. Se o refresh incremental for importante para sua carga de trabalho, use compute serverless.
Para uma comparação entre compute serverless e classic, consulte Serverless vs. classic compute para pipelines. Para obter mais informações sobre serverless, consulte Configurar um pipeline serverless.
Organizar pipelines com a arquitetura medallion
A arquitetura medallion organiza os dados em três camadas lógicas (bronze, prata e ouro), cada uma com um propósito distinto. Mapear os tipos de dataset de pipeline para a camada correta mantém as responsabilidades de cada camada claras e torna os pipelines mais fáceis de manter.
- Bronze : use tabelas de transmissão para ingerir dados brutos de armazenamento em cloud, barramentos de mensagens ou fontes de CDC. As tabelas bronze preservam os dados brutos de origem com transformação mínima, tornando possível que as camadas silver ou ouro reprocessam a partir da fonte na camada bronze caso os requisitos mudem.
- Silver : Use tabelas de transmissão para transformações incrementais em nível de linha (filtragem, limpeza e análise). Use view materializadas quando a lógica da camada silver envolver join de enriquecimento em tabelas de dimensão ou agregações complexas que se beneficiam do refresh incremental.
- Gold : use visualizações materializadas para pré-calcular agregações, métricas e resumos fornecidos a dashboards, ferramentas de relatório e consumidores downstream.
Separe a ingestão (bronze) e a transformação (silver e ouro) em pipelines distintos sempre que possível. O desacoplamento das camadas permite programar, monitorar e solucionar problemas de cada camada de forma independente, e uma falha em um pipeline de transformação não bloqueia a chegada de novos dados na camada bronze.
Para obter mais informações, consulte Tabelas de transmissão e Views materializadas.