Read Models und Projektionen
Read Model ist eine speziell entworfene Tabelle/Index/Ansicht für schnelle Lesungen für ein bestimmtes Produktszenario. Projektion - Ein Prozess, der Ereignisse/Quelländerungen in Read Model-Updates umwandelt (normalerweise idempotent upsert). In Verbindung mit CQRS ermöglicht dies die Entlastung des OLTP-Kernels und die Stabilisierung der p95/p99-Lesungen durch Steuerung der „Frische“.
Die wichtigsten Ideen:- Denormalisierung auf Anfrage, nicht „universelles Schema“.
- Inkrementell und idempotent aktualisieren.
- Eindeutig verwalten staleness und Ordnung.
1) Wann man Read Models verwendet (und wann nicht)
Geeignet für:- Häufiges schweres Lesen (Joins/Aggregation/Sortierung) mit zulässiger Aktualisierungsverzögerung.
- Dashboards, Kataloge, Landings, „Top N“, persönliche Feeds, Suchlisten.
- Lastverteilung: Write-Core ist streng, Read-Plane ist schnell und skalierbar.
- Operationen, die strenge Invarianten „für jeden Datensatz“ erfordern (Geld, Einzigartigkeit). Da ist ein starker Pfad.
2) Architektonische Kontur (verbales Schema)
1. Quelle der Änderungen: Domain Events (Event Sourcing) oder CDC aus OLTP.
2. Projektionspipeline: Parser → Aggregation/Denormalisierung → idempotent upsert.
3. Read Store: DB/Index optimiert für Anfrage (RDBMS, Säulen-, Suchmaschinen).
4. API/Client: schnelles SELECT/GET mit den Attributen „as_of/freshness“.
3) Design des Lesemodells
Beginnen Sie mit der Abfrage: welche Felder, Filter, Sortierung, Pagination, Top N?
Denormalisieren: Speichern Sie bereits zusammengeführte Daten (Namen, Beträge, Status).
- Partitionierung: nach 'tenant _ id', Datum, Region.
- Primärschlüssel: Geschäftsschlüssel + temporäres Baquet (z. B.'(tenant_id, entity_id) 'oder' (tenant_id, bucket_minute)').
- Indizes: durch häufige where/order by.
- TTL/retention: für temporäre Vitrinen (z.B. 90 Tage).
4) Aktualisierungsfluss und Idempotenz
Idempotent upsert ist die Basis für die Stabilität von Projektionen.
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);
Regeln:
- Jede Nachricht trägt eine Version/Zeit; Wir akzeptieren nur „frisch oder gleich“ (idempotency).
- Für Aggregate (Zähler, Summen) - State speichern und kommutative Updates (oder CRDT-Ansätze) verwenden.
5) Quelle der Veränderung: Ereignisse vs CDC
Veranstaltungen (Event Sourcing): reiche Semantik, einfach, verschiedene Projektionen zu bauen; Die Entwicklung von Schemata ist wichtig.
CDC (logische Replikation): einfach an eine bestehende Datenbank anschließen; Mupping- DML→sobyty und Filterung von Rausch-Updates sind erforderlich.
- Versandgarantien (at-least-once) und DLQ für „giftige“ Nachrichten.
- Reihenfolge nach Schlüssel (partition key = 'tenant _ id: entity _ id').
6) Ordnung, Kausalität und „Frische“
Die Reihenfolge nach dem Schlüssel: die Ereignisse eines Objektes sollen nacheinander kommen; Partitionierung und Versionen verwenden.
Kausalität (Sitzung/causal): Damit der Autor seine Änderungen (RYW) sieht, übergeben Sie die Wasserzeichen-Version in Abfragen.
Frische (bounded staleness): Geben Sie „as _ of “/„ X-Data-Freshness“ zurück und halten Sie den SLO (z. B. p95 ≤ 60 c).
7) Inkrementelle Aggregate und Top N
Beispiel für Minutenverkaufsbakets: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;
Für die Top N:
- Pflegen Sie ein Ranking-Showcase (z.B. nach 'revenue DESC') und aktualisieren Sie nur die geänderten Positionen (heap/skiplist/limited table).
- Speichern Sie das „Fenster“ der Spitze (z. B. 100-1000 Zeilen pro Segment).
8) Such- und Geo-Projektionen
Suche (ES/Opensearch): denormalisiertes Dokument, Pipeline von Transformationen, Dokumentversion = Quellversion.
Geo: Speichern Sie' POINT/LAT, LON', vorher aggregieren Sie die Tiers/Quadrotry.
9) Multi-Tenant und Regionen
'tenant _ id' ist in Projektionsschlüsseln und Ereignissen obligatorisch.
Fairness: Begrenzen Sie throughput Projektionen per tenant (WFQ/DRR), damit „noise“ die anderen nicht bremst.
Residency: Die Projektion lebt in der gleichen Region wie der Write-Core; interregionale Vitrinen - Aggregate/Zusammenfassungen.
10) Beobachtbarkeit und SLO
Metriken:- 'projection _ lag _ ms' (istochnik→vitrina), 'freshness _ age _ ms' (seit dem letzten Delta).
- throughput-Aktualisierungen, Fehlerquote, DLQ-Rate, Redrive-Erfolg.
- Größe der Vitrinen, p95/p99 Latenz der Lesungen.
- Теги: `tenant_id`, `entity_id`, `event_id`, `version`, `projection_name`, `attempt`.
- Anmerkungen: Merge-Lösungen, Auslassungen veralteter Versionen.
11) Playbooks (Runbooks)
1. Lagwachstum: Überprüfen Sie den Konnektor/Broker, erhöhen Sie die Parteien, aktivieren Sie die Priorisierung von Schlüsselvitrinen.
2. Viele Schemafehler: Redrive einfrieren, Schemamigration durchführen (Backfill), mit neuer Mapper-Version neu starten.
3. Wiederholte DLQs: Batch reduzieren, „Schatten“ -Handler einschalten, Idempotenz verstärken.
4. Inkonsistenz der Vitrine: Rebuild der Vitrine aus der Zeitschrift/Quelle hinter dem Fenster durchführen (selektiv nach Tenant/Partition).
5. Hot Keys: Begrenzen Sie den Wettbewerb nach Schlüssel, fügen Sie lokale Warteschlangen hinzu und nehmen Sie das Gerät in ein separates Schaufenster.
12) Komplette Neuberechnung (Rebuild) und Backfill
Der Ansatz:- Stoppen Sie den Verbrauch (oder wechseln Sie zu einer neuen Version des Schaufensters).
- In Paketen neu berechnen (nach Parteien/Daten/Tenanten).
- Aktivieren Sie den zweiphasigen Switch: Zuerst füllen Sie' read __ v2', dann schalten Sie das Routing der Lesungen atomar um.
13) Entwicklung der Schaltungen (Versionierung)
'schema _ version' in Ereignissen/Dokumenten.
Projektion kann mehrere Versionen lesen, Migration „on the fly“.
Für große Veränderungen gibt es ein neues Schaufenster v2 und Kanarienverkehr.
14) Sicherheit und Zugang
Erben Sie RLS/ACL von der Quelle; Machen Sie das Schaufenster nicht breiter als die Originaldaten.
Maskieren Sie PIIs in Projektionen, die für UX/Analytics nicht erforderlich sind.
Auditierung von Redrives/Neuberechnungen/manuellen Bearbeitungen.
15) Konfigurationsvorlage
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) Typische Fehler
„Ein Schaufenster für alle Fälle“ → schwere Upgrades und schlechte p99.
Mangel an Idempotenz → Doppel/Sprünge in den Einheiten.
Dual-write direkt ins Schaufenster und OLTP → Unstimmigkeiten.
Null Frische → ein Konflikt der Erwartungen mit dem Produkt.
Rebuild ohne Zwei-Phasen-Switch → „Löcher“ in den Antworten.
Keine Partitionierung/Indizes → steigende Kosten und Latenz.
17) Schnelle Rezepte
Katalog/Suche: Dokumentationsvitrine + inkrementelles Upsert, Lag ≤ 5-15 c, Indizes für Filter.
Dashboards: Minuten-/Stundentanks, SUM/COUNT-Einheiten, Frische p95 ≤ 60 c.
Persönlicher Feed: Projektion nach Benutzer + causal/RYW für den Autor, Fallback pro Cache.
Global SaaS: regionale Schaufenster, Aggregate überregional; fairness per tenant.
18) Checkliste vor dem Verkauf
- Die Vitrine ist auf eine bestimmte Anforderung ausgelegt; Es gibt Indizes und Parteien.
- Änderungsquelle ausgewählt (Ereignisse/CDC); Liefergarantie und Schlüsselauftrag.
- Idempotent upsert mit Versionen/Zeit; Schutz vor „alten“ Ereignissen.
- Die Frische SLO ist definiert und wird in den Antworten gegeben ('as _ of/freshness').
- DLQ und Secure Redrive sind konfiguriert; playbook auf rebuild/backfill.
- Wettbewerbsbeschränkungen (per-key serial) und fairness per tenant.
- Lag/Error/Latency-Metriken, Alerts auf p95/p99 und DLQ-Wachstum.
- Versionierung von Schemata und Migrationsstrategie (v2 + Switch).
- Zugriffs-/PII-Richtlinien werden vererbt und validiert.
Schlussfolgerung
Read Models und Projektionen sind ein technischer Beschleuniger für Lesungen: Sie zahlen einen kleinen Preis für „Frische“ und Streaming-Infrastruktur, um vorhersehbare Millisekunden zu erhalten und den Kern der Aufnahmen zu entlasten. Gestalten Sie Schaufenster auf Wunsch, machen Sie Upgrades idempotent, messen Sie den Lag und versprechen Sie explizit Frische - und Ihre APIs bleiben auch bei steigender Belastung, Daten und Geografie schnell.