Read Models e projeções
O Read Model é uma tabela/índice/vista especialmente projetada para leituras rápidas sob um cenário de alimentos específico. A projeção é um processo que converte eventos/alterações de origem em atualizações do Read Model (normalmente idempotent upsert). Com o CQRS, isso permite descarregar o núcleo OLTP e estabilizar p95/p99 leituras, controlando o «frescor».
Ideias principais:- Denormalizar sob pedido, não «padrão universal».
- Atualizar de forma escalonada e idimpotente.
- Controlar estaleness e ordem claramente.
1) Quando usar o Read Models (e quando não)
Adequado:- Leituras pesadas frequentes (joinas/agregações/arrumações) com atrasos de atualização válidos.
- Dashboards, catálogos, lendings, top-N, fidas pessoais, listas de busca.
- Divisão de carga: núcleo write - rigoroso, plano read - rápido e escalável.
- Operações que exigem invariantes rigorosos «por cada gravação» (dinheiro, exclusividade). É strong path.
2) Circuito arquitetônico (padrão verbal)
1. Origem de alterações: eventos de domínio (event surcing) ou CDC do OLTP.
2. Linha de montagem: parser → agregação/desnormalização → idempotent upsert.
3. Read Store: BD/índice otimizado sob consulta (RDBMS, colinvertebrados, busca).
4. API/cliente: SELECT/GET rápido, com os atributos «as _ of/freshness».
3) Design do Read Model
Comece com a consulta: quais campos, filtros, ordens, paginação, top-N?
Mantenha os dados já combinados (nomes, somas, estatais).
- Particionamento por 'tenant _ id', data, região.
- Primary key: chave de negócio + baquete de tempo (por exemplo, '(tenant _ id, entity _ id)' ou '(tenant _ id, bucket _ minuto)').
- Índices: por where/order by frequente.
- TTL/retenshn: para vitrines temporárias (por exemplo, 90 dias).
4) Fluxo de atualizações e idempotação
Idempotent upsert é a base da estabilidade das projeções.
Pseudo:sql
-- Projection table
CREATE TABLE read_orders (
tenant_id TEXT,
order_id UUID,
status TEXT,
total NUMERIC(12,2),
customer JSONB,
updated_at TIMESTAMP,
PRIMARY KEY (tenant_id, order_id)
);
-- Idempotent update by event
INSERT INTO read_orders(tenant_id, order_id, status, total, customer, updated_at)
VALUES (:tenant,:id,:status,:total,:customer,:ts)
ON CONFLICT (tenant_id, order_id) DO UPDATE
SET status = EXCLUDED. status,
total = EXCLUDED. total,
customer = COALESCE(EXCLUDED. customer, read_orders. customer),
updated_at = GREATEST(EXCLUDED. updated_at, read_orders. updated_at);
Regras:
- Cada mensagem traz uma versão/hora; aceitamos apenas «fresco ou igual» (idempotency).
- Para aparelhos (contadores, somas) - Armazene o state e use atualizações de switch (ou abordagens CRDT).
5) Origem de alterações: eventos vs CDC
Eventos (event surcing): rica semântica, fácil de construir diferentes projeções; a evolução dos circuitos é importante.
CDC (replicação lógica): basta ligar ao banco de dados existente; vai precisar de um mapping de DML→sobyty e filtragem de updates de ruído.
- Garantias de entrega (at-least-once) e DLQ para mensagens «venenosas».
- Ordem da chave (partition key = 'tenant _ id: entity _ id').
6) Ordem, causalidade e «frescura»
Ordem da chave: os eventos de um único objeto devem vir em sequência; use a partilha e versões.
Causalidade (sessão/causal): Para que o autor veja suas alterações (RYW), envie versões watermark nas solicitações.
Recente (bounded staleness): devolva 'as _ of '/' X-Data-Freshness' e mantenha o SLO (por exemplo, p95 ≤ 60 c).
7) Unidades incorporadas e top-N
Exemplo de baquetes de venda de minutos:sql
CREATE TABLE read_sales_minute (
tenant_id TEXT,
bucket TIMESTAMP, -- toStartOfMinute revenue NUMERIC(14,2),
orders INT,
PRIMARY KEY (tenant_id, bucket)
);
-- Update by Event
INSERT INTO read_sales_minute(tenant_id, bucket, revenue, orders)
VALUES (:tenant,:bucket,:amount, 1)
ON CONFLICT (tenant_id, bucket) DO UPDATE
SET revenue = read_sales_minute. revenue + EXCLUDED. revenue,
orders = read_sales_minute. orders + 1;
Para Top N:
- Suporte a vitrine classificada (por exemplo, por 'revenue DESC') e atualize apenas as posições alteradas (heap/skiplist/limited place).
- Guarde a «janela» do top (por exemplo, 100-1000 linhas por segmento).
8) Projeções de busca e geo
Pesquisa (ES/Openserch): Documento denormalizado, transformações pipeline, versão do documento = versão da fonte.
Geo: Guarde 'POINT/LAT, LON', instale previamente os tales/quadrotros.
9) Multi-tenante e regiões
'tenant _ id' é obrigatório nas chaves de projeção e evento.
Fairness: Limite throughput projeções per tenant (WFQ/DRR) para evitar que o barulho freie os outros.
Residency: a projeção vive na mesma região do núcleo write; vitrines inter-regionais - equipamentos/resumos.
10) Observabilidade e SLO
Métricas:- 'project _ lag _ ms' (istochnik→vitrina), 'freshness _ age _ ms' (desde o último delta).
- thughput updates, número de erros, DLQ-rate, redrive-sucess.
- Tamanho das vitrines, p95/p99 latência de leitura.
- Теги: `tenant_id`, `entity_id`, `event_id`, `version`, `projection_name`, `attempt`.
- Anotações: soluções merge, omissões de versões ultrapassadas.
11) Playbooks (runbooks)
1. Crescimento da laje: verificar o conector/corretor, aumentar as partições, priorizar vitrines-chave.
2. Muitos erros de padrão: congelar redrave, migrar circuitos (backfill), reiniciar com a nova versão do mapper.
3. DLQ repetido: reduzir batch, ativar o processador «shadow», aumentar a idempotidade.
4. Incoerência da vitrine: execute rebuild vitrines do diário/fonte por janela (seletivo por tenant/partition).
5. Chaves quentes: limitar a concorrência da chave, adicionar filas locais, levar a unidade para uma vitrine separada.
12) Contagem completa (rebuild) e backfill
Abordagem:- Parar o consumo (ou mudar para a nova versão da vitrine).
- Repasse em lotes (por partituras/datas/tenantes).
- Activar o suingue de duas fases: primeiro preencher 'read' v2 ', depois alternar atômico para a leitura.
13) Evolução dos circuitos (versionização)
'schema _ version' em eventos/documentos.
A projeção pode ler várias versões, migrar para trás.
Para grandes alterações, a nova vitrine v2 e o tráfego canário.
14) Segurança e acesso
Herda RLS/LCA da origem; Não façam uma vitrine mais acessível do que os dados originais.
Disfarce o PII em projeções que não são necessárias para UX/analistas.
Auditoria de Redryvs/Redefinições/Redefinições manuais.
15) Modelo de configuração
yaml projections:
read_orders:
source: kafka. orders. events partition_key: "{tenant_id}:{order_id}"
idempotency: version_ts upsert:
table: read_orders conflict_keys: [tenant_id, order_id]
freshness_slo_ms: 60000 dlq:
topic: orders. events. dlq redrive:
batch: 500 rate_limit_per_sec: 50 read_sales_minute:
source: cdc. orders partition_key: "{tenant_id}:{bucket_minute}"
aggregate: increment retention_days: 90 limits:
per_tenant_parallelism: 4 per_key_serial: true observability:
metrics: [projection_lag_ms, dlq_rate, redrive_success, read_p95_ms]
16) Erros típicos
«Uma vitrine para todos os casos» → updates pesados e p99 ruins.
Falta de idempotação → duplas/corridas nas unidades.
Dual-write diretamente para a vitrine e OLTP → divergências.
Visibilidade zero de frescura → conflito de expectativas com o produto.
Rebuild sem um suingue de duas fases → «buracos» nas respostas.
Não há partilha/índice → aumento de custo e latência.
17) Receitas rápidas
Catálogo/pesquisa: vitrine documental + upsert incorporativo, lag ≤ 5-15 c, índices abaixo dos filtros.
Dashboards: tanques de minutos/relógio, equipamentos 'SUM/COUNT', p95 frescura ≤ 60 c.
Fita pessoal: projeção por usuário + causal/RYW para o autor, fallback para o dinheiro.
SaaS global: Vitrines regionais, equipamentos cruzados-regionais; fairness per tenant.
18) Folha de cheque antes de vender
- A vitrine foi projetada para um pedido específico; há índices e partituras.
- Origem das alterações selecionada (eventos/CDC); garantias de entrega e ordem de chave.
- Upsert Idempotent com versões/hora; protecção contra eventos antigos.
- O SLO de frescura é definido e dado nas respostas ('as _ of/freshness').
- O DLQ e o redrave seguro estão configurados; playbook em rebuild/backfill.
- Restrições à concorrência (per-key serial) e fairness per tenant.
- Métricas de laje/erro/latency, alertas em p95/p99 e crescimento do DLQ.
- Versionização de circuitos e estratégia de migração (v2 + switch).
- As políticas de acesso/PII foram herdadas e testadas.
Conclusão
O Read Models e Projeções é um acelerador de leitura de engenharia que paga um pequeno preço de «frescura» e infraestrutura de streaming para obter milissegundos previsíveis e descarregar o núcleo de gravações. Projete as vitrines sob demanda, torne os updates idimpotentes, mede a liga e claramente prometa frescura - e suas APIs permanecerão rápidas, mesmo com o aumento da carga de trabalho, dados e geografia.