CQRS i oddzielenie odczytu/zapisu
Co to jest CQRS
CQRS (Command Query Responsibility Segregation) to podejście architektoniczne, które oddziela model danych od komponentów odpowiedzialnych za pisanie (polecenia) i czytanie (zapytania).
Pomysł: proces zmiany stanu jest zoptymalizowany pod kątem ważnych niezmienników i transakcji oraz czytania w celu szybkich, ukierunkowanych projekcji i skalowania.
klucz> Polecenia zmieniają stan i zwracają wynik operacji. Wnioski są tylko czytać i nie mają skutków ubocznych.
Dlaczego tego potrzebujesz?
Odczytaj wydajność: zmaterializowane projekcje dla konkretnych scenariuszy (taśmy, raporty, katalogi).
Stabilność ścieżki krytycznej: nagrywanie odizolowane od „ciężkich” połączeń i agregatów.
Wybór swobody przechowywania: OLTP do pisania, OLAP/cache/wyszukiwarki do czytania.
Przyspieszona ewolucja: Dodaj nowe poglądy bez ryzyka „łamania” transakcji.
Obserwowalność i audyt (zwłaszcza w połączeniu z Event Sourcing): łatwiej jest odzyskać i odtworzyć stan.
Kiedy stosować (a kiedy nie)
Nadaje się, jeśli:- Przeważają odczyty o różnych plasterkach danych i złożonej agregacji.
- Krytyczna ścieżka zapisu musi być subtelna i przewidywalna.
- Do czytania i pisania potrzebne są różne SLO/SLA.
- Wymagana jest izolacja logiki zapisu domeny od potrzeb analitycznych/wyszukiwania.
- Domena jest prosta, obciążenie jest niskie; CRUD sobie radzi.
- Silna spójność pomiędzy czytaniem a pisaniem jest obowiązkowa dla wszystkich scenariuszy.
- Zespół jest niedoświadczony, a złożoność operacyjna jest nie do przyjęcia.
Podstawowe pojęcia
Command-Intends zmieni stan ('Order', 'CapturePayment'). Sprawdzamy niezmiennie.
Query-Retrieves dane ('Na ById',' Transakcje z listy '). Żadnych skutków ubocznych.
Model zapisu: agregaty/niezmienne/transakcje; przechowywanie - relacyjny/wartość klucza/dziennik zdarzeń.
Model odczytu (projekcji): zmaterializowane tabele/indeksy/pamięć podręczna, synchronizowane asynchronicznie.
Spójność: Często eventual między nagrywaniem i czytaniem; ścieżki krytyczne - poprzez bezpośredni odczyt z modelu pisma.
Architektura (szkielet)
1. Usługa odpisu: akceptuje polecenia, waliduje niezmienne, przechwytuje zmiany (bazy danych lub zdarzeń).
2. Outbox/CDC: gwarantowana publikacja faktu zmian.
3. Procesory projekcji: Słuchaj zdarzeń/CDC i aktualizuj modele odczytu.
4. Usługa odczytu: obsługuje zapytania z zmaterializowanych widoków/buforów/wyszukiwań.
5. Sagi/orkiestra: współrzędne procesów krzyżowych.
6. Obserwowalność: opóźnienie projekcji, odsetek udanych aplikacji, DLQ.
Projektowanie modelu nagraniowego
Agregaty: jasne granice transakcji (na przykład „Zamówienie”, „Płatność”, „Saldo”).
Niezmienne: formalizować (kwoty pieniężne ≥ 0, wyjątkowość, limity).
Polecenia są idempotentne przez klucz (na przykład 'idempotence _ key').
Zakres transakcji jest minimalny; zewnętrzne skutki uboczne - poprzez skrzynkę zewnętrzną.
Przykład polecenia (Pseudo-JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
Projektowanie modelu czytania
Zacznij od zapytań: jakie ekrany/raporty są potrzebne?
Dopuszczalna jest denormalizacja: model odczytu - „zoptymalizowany pamięć podręczna”.
Kilka projekcji dla różnych zadań: wyszukiwanie (OpenSearch), raporty (pamięć kolumnowa), karty (KV/Redis).
TTL i reasekuracja: projekcje muszą być w stanie odzyskać ze źródła (powtórka zdarzenia/migawki).
Spójność i UX
Ewentualna spójność: interfejs może wyświetlać stare dane przez krótki czas.
Wzory UX: „dane są aktualizowane”..., optymistyczne interfejsy użytkownika, wskaźniki synchronizacji, blokowanie niebezpiecznych działań do czasu potwierdzenia.
W przypadku operacji wymagających silnej konsystencji (na przykład pokazujących dokładne saldo przed odpisem) należy odczytać bezpośrednio z modelu zapisu.
CQRS i Event Sourcing (opcjonalnie)
Event Sourcing (ES) przechowuje wydarzenia, a stan agregatu jest wynikiem ich konwolekcji.
Pakiet CQRS + ES zapewnia idealny audyt i łatwą ponowną ocenę projekcji, ale zwiększa złożoność.
Alternatywa: regularna baza danych OLTP + outbox/CDC → projekcje.
Replikacja: Outbox i CDC
Outbox (w jednej transakcji): zapisywanie zmian domeny + zapisywanie zdarzenia do skrzynki outbox; wydawca dostarcza do opony.
CDC: odczyt z dziennika bazy danych (Debezium itp.) → przekształcenie w zdarzenia domeny.
Gwarancje: domyślnie przynajmniej raz, konsumenci i prognozy muszą być idempotentne.
Wybór składowania
Napisz: relacyjne (PostgreSQL/MySQL) dla transakcji; KV/Dokument - gdzie niezmienne są proste.
Przeczytaj:- KV/Redis - karty i szybkie odczyty kluczy;
- Wyszukiwanie (OpenSearch/Elasticsearch) - wyszukiwanie/filtry/fasony;
- Kolumna (ClickHouse/Query) - raporty;
- Cache na CDN - publiczne katalogi/treści.
Wzorce integracji
Warstwa API: oddzielne punkty końcowe/usługi dla „poleceń” i „zapytań”.
Idempotencja: klucz operacji w nagłówku/ciele; przechowywanie ostatnich klawiszy z TTL.
Sagi/orkiestra: timeouts, kompensacje, powtarzalność kroków.
Backpressure-Limity paralelizmu procesorów projekcyjnych.
Obserwowalność
Napisz metryki: p95/99 opóźnienia poleceń, procent udanych transakcji, błędy walidacyjne.
Odczytaj mierniki: żądania p95/99, pamięć podręczna, obciążenie w klastrze wyszukiwania.
Opóźnienie projekcji (czas i komunikaty), szybkość DLQ, procent deduplikacji.
Śledzenie: 'trace _ id' przechodzi przez polecenie → outbox → rzut zapytania.
Bezpieczeństwo i zgodność
Oddzielenie praw: różne zakresy/role pisania i czytania; zasada najmniejszego przywileju.
PII/PCI: zminimalizować w projekcjach; szyfrowanie podczas odpoczynku/lotu; Maskowanie.
Audyt: Zespół Fix, aktor, wynik, 'trace _ id'; Archiwa WORM dla domen krytycznych (płatności, KYC).
Badania
Testy kontraktowe: dla poleceń (błędy, niezmienne) i zapytań (formaty/filtry).
Testy projekcyjne: złożyć serię zdarzeń/CDC i sprawdzić końcowy model odczytu.
Chaos/opóźnienie: wtryskiwanie opóźnienia do procesorów projekcyjnych; Sprawdź UX w opóźnieniu.
Powtarzalność: reasembling projections on the stand from snapshots/log.
Migracja i ewolucja
Nowe pola - dodatek w przypadku/CDC; modeli odczytu są odbudowane.
Podwójne zapisywanie podczas przeprojektowywania obwodów; trzymać stare projekcje do przełączania.
Wersioning: 'v1 '/' v2' zdarzenia i punkty końcowe, Sunset-plan.
Flagi funkcji: wprowadzenie nowych zapytań/projekcji wzdłuż kanarka.
Anty-wzory
CQRS „dla dobra mody” w prostych usługach CRUD.
Twarda synchroniczna zależność od odczytu-zapisu (zabija izolację i trwałość).
Jeden indeks dla wszystkich: mieszanie niejednorodnych zapytań w jednym czytelni.
Projekcje nie mają idempotencji → duplikaty i rozbieżności.
Niezdatne do odzyskania projekcje (brak powtórki/migawki).
Przykłady domen
Płatności (usługa online)
Napisz: „Autoryzuj”, „Przechwytywanie”, „Zwrot” w bazie danych transakcji; outbox publikuje płatność. '.
Przeczytaj:- Redis „karta płatnicza” dla interfejsu użytkownika;
- ClickHouse do raportowania;
- OpenSearch do wyszukiwania transakcji.
- Ścieżka krytyczna: autoryzacja ≤ 800 ms p95; spójność odczytu dla interfejsu użytkownika - ewentualna (do 2-3 s).
KYC
Napisz: polecenia do uruchomienia/aktualizacji stanu; Przechowywanie PII w bezpiecznej bazie danych.
Czytaj: lekka projekcja statusów bez PII; W razie potrzeby PII jest zaostrzony punktowo.
Bezpieczeństwo: różne zakresy stanu odczytu i dostępu do dokumentów.
Bilans (iGaming/Finance)
Napisz: Agregat „اBalance” z przyrostami/spadkami atomowymi; Idempotentne klucze do operacji.
Przeczytaj: pamięć podręczna do „szybkiej równowagi”; do odpisu - bezpośredni odczyt z zapisu (ścisła konsystencja).
Saga: depozyty/wnioski są koordynowane przez wydarzenia, w przypadku awarii - rekompensaty.
Lista kontrolna wdrażania
- Podkreślono agregaty i niezmienne wartości modelu pisma.
- Zdefiniowano kluczowe zapytania i zaprojektowano dla nich projekcje.
- Procesory projekcji Outbox/CDC i idempotent są konfigurowane.
- Istnieje plan migawki/powtórki.
- SLO: opóźnienie polecenia, opóźnienie projekcji, dostępność odczytu/zapisu oddzielnie.
- Wdrożono oddzielone prawa dostępu i szyfrowanie danych.
- Alerty DLQ/lag/awarie deduplicacji.
- Testy: Kontrakty, Projekcje, Chaos, Powtórka.
NAJCZĘŚCIEJ ZADAWANE PYTANIA
Czy Event Sourcing jest obowiązkowe dla CQRS?
Nie, nie jest. Możesz budować na regularnej bazie danych + outbox/CDC.
Jak radzić sobie z desynchronizacją?
Wyraźnie projektowanie UX, pomiar opóźnienia projekcji, niech krytyczne operacje czytać z zapisu.
Czy można zachować zarówno pisanie, jak i czytanie w tej samej usłudze?
Tak, fizyczna separacja jest opcjonalna; logiczny podział obowiązków jest obowiązkowy.
A co z transakcjami między agregatami?
Poprzez sagi i wydarzenia; W miarę możliwości unikaj transakcji rozproszonych.
Wynik
CQRS unties ręce: cienka, niezawodna ścieżka pisania z wyraźnymi niezmiennikami i szybko, ukierunkowane odczytuje z zmaterializowanych projekcji. Zwiększa to wydajność, upraszcza ewolucję i sprawia, że system jest bardziej odporny na stres - jeśli konsekwencja, obserwacja i migracja są zdyscyplinowane.