Uma consulta lenta quase sempre começa parecendo um problema de índice.
O time olha para o banco, adiciona um índice, reescreve um join, ajusta paginação, muda um where, cria uma cache pequena e segue.
Por algum tempo funciona.
Depois a tela cresce.
O painel precisa mostrar pedido, pagamento, entrega, cliente, risco, último evento recebido e próxima ação operacional. Cada informação pertence a um contexto diferente. Cada contexto tem seu próprio banco, sua própria regra, seu próprio dono e seu próprio ritmo de mudança.
A API que monta a tela começa a chamar vários serviços.
Ou começa a fazer consultas pesadas em tabelas que não foram desenhadas para aquele tipo de leitura.
Ou cria uma cache que ninguém sabe reconstruir direito quando os dados ficam errados.
Materialized View aparece nesse ponto.
Não como truque para acelerar SQL.
Mas como uma decisão arquitetural: construir uma visão de leitura a partir dos fatos que o sistema publica.
A tela não precisa ver o modelo interno
Em muitos sistemas, o modelo usado para tomar decisões acaba virando também o modelo usado para responder consultas.
Isso parece econômico.
Uma tabela de pedidos já existe.
Uma tabela de pagamentos já existe.
Uma tabela de entregas já existe.
Então a tela consulta tudo, junta tudo e monta uma resposta.
O problema é que o modelo interno normalmente foi desenhado para proteger consistência, não para servir a interface.
Ele separa entidades porque isso ajuda a gravar corretamente.
Ele normaliza dados porque isso reduz duplicidade.
Ele distribui responsabilidades porque cada serviço deveria controlar o próprio pedaço do domínio.
Só que uma leitura operacional costuma querer o contrário: um retrato pronto, agregado, filtrável e rápido.
Uma Materialized View assume essa diferença.
Ela não tenta expor o domínio inteiro.
Ela materializa uma pergunta.
Por exemplo:
order_id
customer_name
current_status
payment_status
delivery_status
risk_level
last_update_at
next_action
Essa estrutura talvez seja ruim como fonte de verdade.
Mas pode ser excelente como leitura.
Materializar é gravar uma resposta derivada
Uma Materialized View é uma visão persistida.
Ela não calcula tudo do zero a cada consulta.
Ela mantém uma estrutura pronta para responder a um caso de uso específico.
Em arquiteturas orientadas a eventos, essa visão costuma ser alimentada por eventos.
O serviço de pedidos publica OrderCreated.
O serviço de pagamentos publica PaymentApproved.
O serviço de entregas publica DeliveryDelayed.
Um consumidor lê esses fatos e atualiza uma projeção:
Kafka events -> Projection consumer -> Read model
Esse modelo de leitura pode estar em PostgreSQL, MongoDB, Redis, Elasticsearch, OpenSearch ou outro mecanismo escolhido pela pergunta que precisa ser respondida.
O ponto não é a tecnologia.
O ponto é que a view é derivada.
Ela nasce de eventos, pode ser reconstruída por replay e não deveria ser tratada como a única fonte de verdade do domínio.
Isso muda a forma de operar.
Se a projeção fica inconsistente, você não deveria precisar corrigir dado manualmente no escuro.
Você deveria conseguir entender quais eventos alimentam aquela view, desde quando ela ficou errada e como reconstruí-la com segurança.
Kafka ajuda porque preserva o histórico de mudanças
Kafka combina bem com Materialized Views porque ele permite que vários consumidores reajam ao mesmo fluxo de fatos.
Um consumidor atualiza a visão do painel operacional.
Outro atualiza um índice de busca.
Outro alimenta um relatório quase em tempo real.
Outro calcula alertas.
Todos partem dos mesmos eventos, mas cada um materializa uma resposta diferente.
Isso se conecta diretamente com CQRS: o lado de escrita decide e publica fatos; o lado de leitura transforma esses fatos em modelos otimizados para consulta.
Também se conecta com Event Sourcing, mas não é a mesma coisa.
Você pode ter Materialized Views sem Event Sourcing.
Basta que eventos confiáveis descrevam mudanças relevantes para a projeção.
Em um sistema CRUD tradicional com Outbox Pattern, por exemplo, a aplicação pode gravar seu estado atual no banco transacional e publicar eventos para alimentar views de leitura.
O Kafka não precisa ser a fonte primária do domínio para ser útil aqui.
Ele precisa ser um log confiável o suficiente para distribuir fatos e permitir reconstrução dentro dos limites definidos pela arquitetura.
A projeção precisa ser idempotente
Materialized View não elimina falhas.
Ela muda onde elas aparecem.
Um consumidor pode processar o mesmo evento duas vezes.
Pode cair depois de gravar a view e antes de confirmar offset.
Pode receber eventos fora da expectativa do desenvolvedor.
Pode ser reexecutado durante um replay.
Se a atualização da projeção não for idempotente, a view começa a mentir.
Imagine um evento PaymentApproved que incrementa um contador de pagamentos aprovados.
Se o mesmo evento for processado duas vezes, o número dobra.
Agora imagine uma projeção que grava o estado pelo identificador do evento, do pedido ou do pagamento.
Nesse caso, processar novamente o mesmo fato tende a produzir o mesmo resultado.
Idempotência não é detalhe de implementação.
É uma propriedade operacional da view.
A série já discutiu esse problema em consumidores lidando com duplicidade, e Materialized Views tornam isso ainda mais visível.
Uma projeção popular, usada por telas e relatórios, amplifica qualquer erro silencioso.
Quando ela mente, muita gente acredita.
Ordem importa, mas só dentro do recorte certo
Nem toda projeção precisa de ordenação global.
Na maioria dos casos, ela precisa de ordem por entidade, por agregado ou por chave de negócio.
Para atualizar a visão de um pedido, eventos daquele pedido precisam chegar na ordem correta.
OrderShipped antes de PaymentApproved talvez não faça sentido.
DeliveryDelayed antes de DeliveryScheduled talvez exija uma regra de compensação.
Em Kafka, a ordem é garantida dentro de uma partição.
Isso torna a escolha da key uma decisão de modelagem, não apenas uma configuração técnica.
Se a Materialized View é por pedido, a key provavelmente precisa preservar ordem por order_id.
Se a view é por cliente, talvez a key precise ser customer_id.
Se a view mistura vários recortes, talvez uma única projeção esteja tentando responder perguntas demais.
Esse é um sinal comum de desenho frágil.
Uma view boa costuma ter uma pergunta clara.
Quando a pergunta muda muito, talvez você precise de outra view.
Não de uma projeção genérica que tenta virar banco universal.
Reconstrução precisa ser parte do desenho
O teste real de uma Materialized View não é apenas responder rápido.
É responder à pergunta: como eu reconstruo isso?
Você pode precisar reconstruir porque uma regra mudou.
Porque um bug corrompeu parte da projeção.
Porque uma nova coluna foi adicionada.
Porque o índice de busca foi recriado.
Porque o banco de leitura foi perdido.
Se a view é derivada, reconstrução não deveria ser um evento traumático.
Mas isso exige planejamento.
Você precisa saber quais tópicos alimentam a projeção.
Precisa saber a partir de qual offset ou período reprocessar.
Precisa lidar com contratos antigos de eventos.
Precisa controlar efeitos colaterais para que replay não envie e-mail, não cobre cliente e não dispare integração externa.
Uma projeção de leitura deveria atualizar leitura.
Se ela também executa ação de negócio irreversível, replay vira risco.
Esse é um dos motivos para separar bem consumidores que materializam estado de consumidores que executam efeitos externos.
Os dois podem ler eventos.
Mas não têm a mesma responsabilidade.
Consistência eventual precisa aparecer no produto
Uma Materialized View alimentada por eventos quase sempre tem algum atraso.
O comando foi aceito.
O evento foi publicado.
O consumidor ainda não atualizou a view.
Durante esse intervalo, a tela pode mostrar dado antigo.
Isso não torna a arquitetura errada.
Torna a experiência do produto uma parte da arquitetura.
Depois que o usuário cancela um pedido, talvez a aplicação não deva buscar imediatamente a lista materializada e fingir que ela já reflete o cancelamento.
Talvez precise mostrar o resultado do comando.
Talvez precise marcar a linha como "atualizando".
Talvez precise bloquear uma ação até a projeção alcançar determinado ponto.
Talvez alguns relatórios aceitem atraso de minutos, enquanto uma mesa operacional aceite apenas segundos.
Materialized View não é desculpa para inconsistência invisível.
Ela é uma forma explícita de trocar atualização síncrona por leitura especializada.
Essa troca precisa ser compreendida por quem modela backend, por quem desenha produto e por quem opera produção.
O ponto que vale fixar
Materialized View não é uma cache com nome arquitetural.
Cache normalmente tenta esconder custo mantendo uma cópia temporária de algo que ainda é calculado em outro lugar.
Materialized View é uma projeção assumida como parte do desenho.
Ela tem eventos de entrada.
Tem regra de transformação.
Tem modelo de leitura.
Tem atraso aceitável.
Tem estratégia de replay.
Tem dono operacional.
Em arquiteturas orientadas a eventos, isso é poderoso porque permite que cada caso de uso leia um modelo adequado à sua pergunta, sem forçar o banco transacional ou o modelo de escrita a atender todo mundo.
Mas o preço existe.
Você passa a operar derivação, idempotência, ordenação, evolução de contrato e reconstrução.
Quando esse preço resolve um problema real de leitura, integração ou escala organizacional, Materialized Views são uma ferramenta excelente.
Quando o sistema só precisava de uma consulta simples, elas viram complexidade decorada.
