Read Models e proiezioni
Read Model è una tabella/indice/vista appositamente progettata per le letture veloci sotto uno specifico scenario alimentare. La proiezione è un processo che converte eventi/modifiche di origine in aggiornamenti di Read Model (solitamente idempotent upsert). In collegamento con CQRS, questo consente di scaricare il nucleo OLTP e stabilizzare p95/p99 letture, controllando la «freschezza».
Idee principali:- Denormalizzare come richiesta, non come «schema universale».
- Aggiorna incrementalmente e idipotenzialmente.
- Controlla chiaramente staleness e ordine.
1) Quando utilizzare Read Models (e quando no)
Adatto:- Frequenti letture pesanti (joine/aggregazioni/ordinamento) con ritardi di aggiornamento consentiti.
- Dashboard, cataloghi, landing, top-N, fidi personali, liste di ricerca.
- Separazione del carico: il kernel write è rigoroso, il piano read è veloce e scalabile.
- Operazioni che richiedono rigorosi invarianti per ogni registrazione (denaro, unicità). C'è strong path.
2) Tracciato architettonico (schema verbale)
1. Origine delle modifiche: eventi di dominio (event source) o CDC da OLTP.
2. Trasportatore di proiezioni: parser, aggregazione/denormalizzazione dell'idempotent upsert.
3. Read Store: database/indice ottimizzato per richiesta (RDBMS, invertebrati, ricerca).
4. API/client: SELECT/GET veloce, con attributi «as _ of/freshness».
3) Progettazione di Read Model
Per iniziare, quali campi, filtri, ordinamenti, paginazioni, top-N?
Denormalizza: memorizza i dati già combinati (nomi, importi, stati).
- Partizionamento per «tenant _ id», data, regione.
- Primary key: chiave aziendale + bidone temporale (ad esempio, '(tenant _ id, entity _ id)' o '(tenant _ id, bucket _ minute)').
- Indici per where/order by frequente.
- TTL/retenschn per vetrine temporanee (ad esempio 90 giorni).
4) Flusso di aggiornamenti e idepotenza
Idempotent upsert è la base della stabilità delle proiezioni.
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);
Regole:
- Ogni messaggio porta una versione/ora; accettiamo solo «fresco o uguale» (idempotency).
- Per le unità (contatori, importi) - Memorizzare lo state e utilizzare gli aggiornamenti switch (o gli approcci CRDT).
5) Origine modifiche: eventi vs CDC
Eventi (event surcing) - Ricca semantica, facile da costruire proiezioni diverse; l'evoluzione dei circuiti è importante.
CDC (replica logica): facile da collegare al database esistente Ci vorrà un mupping e un filtraggio degli update acustici.
- Garanzie di consegna (at-least-once) e DLQ per i messaggi «velenosi».
- Ordine chiave (partition key = 'tenant _ id: entity _ id').
6) Ordine, causalità e «freschezza»
Ordine chiave: gli eventi di un singolo oggetto devono venire in sequenza. Utilizzare la partizionalizzazione e le versioni.
Causalità (sessions/causal) - In modo che l'autore veda le sue modifiche (RYW), trasmettere la versione watermark nelle query.
Freschezza (bounded staleness) - Restituisci «as _ of »/« X-Data-Freshness» e tieni SLO (ad esempio p95-60 c).
7) Unità incrementali e top-N
Esempio di bustini di vendita minuti: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;
Per top-N:
- Supporta la vetrina classificata (ad esempio, «revenue DESC») e aggiorna solo le posizioni modificate (heap/skiplist/limited table).
- Salvare la finestra del top (ad esempio 100-1000 righe per segmento).
8) Ricerca e geo-proiezione
Cerca (ES/Opensearch) - Documento denormalizzato, trasformazioni pipeline, versione del documento = versione sorgente.
Geo: memorizzà POINT/LAT, LON ", precompilate i tyli/quadrotri.
9) Multi-tenente e regioni
«tenant _ id» è obbligatorio nelle chiavi di proiezione ed eventi.
Fairness: limitare il throughput delle proiezioni per tenant (WFQ/DRR) in modo che il rumore non freni gli altri.
Residency: la proiezione vive nella stessa regione del nucleo write; vetrine interregionali - aggregazioni/resoconti.
10) Osservabilità e SLO
Metriche:- «progection _ lag _ ms» (istochnik→vitrina), «freshness _ age _ ms» (dall'ultimo delta).
- throughput update, quota di errori, DLQ-rate, redrive-success.
- Dimensioni delle vetrine, p95/p99 latitanza delle letture.
- Теги: `tenant_id`, `entity_id`, `event_id`, `version`, `projection_name`, `attempt`.
- Annotazioni: soluzioni merge, passaggi di versione obsoleti.
11) Playbooks (runbooks)
1. Crescita del raggio: controllare il connettore/broker, aumentare le partenze, includere la priorità delle vetrine chiave.
2. Molti errori di schema: congelare i redrave, migrare gli schemi (backfill), riavviare con la nuova versione del mapper.
3. DLQ ripetuti: ridurre il batch, attivare il processore shadow e aumentare l'idampotenza.
4. Incoerenza della vetrina: esegue rebuild da registro/sorgente per finestra (selettivo per tenant/partition).
5. Chiavi hot: limitare la concorrenza della chiave, aggiungere code locali, portare l'aggregazione in una vetrina separata.
12) Conteggio completo (rebuild) e backfill
Approccio:- Interrompe il consumo (o passa alla nuova versione della vetrina).
- Conteggia i pacchetti (per partizioni/date/tenanti).
- Attiva il maglione a due fasi: prima riempiamo "read" v2 ", poi alterniamo atomicamente il routing di lettura.
13) Evoluzione dei circuiti (versioning)
'schema _ version'negli eventi/documenti.
La proiezione può leggere più versioni, la migrazione al volo.
Per i grandi cambiamenti, la nuova vetrina v2 e il traffico canareo.
14) Sicurezza e accesso
Ereditare RLS/ACL dall'origine; non fare in modo che la vetrina sia più ampia rispetto ai dati originali.
Mascherare il PII in proiezioni non necessarie per UX/analisi.
Controllo delle redrivisitazioni/rivisitazioni/modifiche manuali.
15) Modello di configurazione
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) Errori tipici
«Una vetrina per tutti i casi».
La mancanza di idampotenza delle riprese/corse negli apparecchi.
Dual-write direttamente in vetrina e OLTP → la soluzione temporanea.
Zero visibilità di freschezza, conflitto di aspettative con il prodotto.
Rebuild, senza un maglioncino a due →, è un buco nelle risposte.
Niente partitura/indici, aumento dei costi e della latitanza.
17) Ricette veloci
Catalogo/ricerca: vetrina documentale + upsert incrementale, lag 5-15 c, indici sotto i filtri.
I Dashboard sono botti minuti/orologio, unità SUM/COUNT, p95 freschezza 60 c.
Nastro personale: proiezione utente + causal/RYW per l'autore, fallback per la cache.
SaaS globale: vetrine regionali, unità cross-regionali; fairness per tenant.
18) Foglio di assegno prima della vendita
- La vetrina è progettata per una richiesta specifica; ci sono indici e partenze.
- L'origine delle modifiche è selezionata (eventi/CDC); garanzia di spedizione e ordine su chiave.
- upsert idempotente con versioni/tempo; Protezione contro i vecchi eventi.
- Il SLO di freschezza è definito e dato nelle risposte ('as _ of/freshness').
- Il DLQ e il redrave sicuro sono configurati; playbook su rebuild/backfill.
- Limitazioni della concorrenza (per-key serial) e fairness per tenant.
- Metriche di laga/errori/latency, alert per p95/p99 e crescita del DLQ.
- Versioning degli schemi e strategia delle migrazioni (v2 + maglioni).
- I criteri di accesso/PII sono stati ereditati e convalidati.
Conclusione
Il Read Models e le proiezioni sono un acceleratore di lettura ingegneristico, che offre un piccolo prezzo per la freschezza e l'infrastruttura di streaming per ottenere millisecondi prevedibili e scaricare il kernel. Progettate le vetrine sotto richiesta, rendete idompotenti gli update, misurate la lega e promettete chiaramente freschezza, e le vostre API rimarranno veloci anche se aumentano il carico di lavoro, i dati e la geografia.