CQRS және оқу/жазуды бөлу
CQRS дегеніміз не?
CQRS (Command Query Responsibility Segregation) - деректер моделі мен жазуға (commands) және оқуға (queries) жауап беретін компоненттерді бөлетін архитектуралық тәсіл.
Идея: жағдайды өзгерту процесі валидті инварианттар мен транзакцияларға, ал оқу - жылдам, мақсатты проекцияларға және масштабтауға оңтайландырылады.
Бұл не үшін қажет
Оқу өнімділігі: нақты сценарийлер (таспалар, есептер, каталогтар) бойынша материалданған проекциялар.
Сыни жолдың тұрақтылығы: жазба «ауыр» джоиндер мен агрегаттардан оқшауланған.
Сақтау орындарын таңдау еркіндігі: жазуға арналған OLTP, оқуға арналған OLAP/кэш/іздеу қозғалтқыштары.
Жедел эволюция: транзакцияларды «сындыру» қаупі жоқ жаңа көріністерді қосыңыз.
Бақылау және аудит (әсіресе Event Sourcing): қалпына келтіру және күту оңай.
Қашан қолдану (және қашан қолданылмайды)
Егер:- Әртүрлі деректер кесінділері мен күрделі агрегациясы бар оқулықтар басым болады.
- Жазудың сыни жолы жұқа және болжамды болуы тиіс.
- Оқу және жазу үшін әртүрлі SLO/SLA керек.
- Жазбаның домендік логикасын талдау/іздеу қажеттіліктерінен оқшаулау талап етіледі.
- Домен қарапайым, жүктеме жоғары емес; CRUD жұмыс істеуде.
- Оқу мен жазу арасындағы күшті келісім барлық сценарийлер үшін міндетті.
- Команда тәжірибесіз, ал операциялық күрделілік қолайсыз.
Негізгі ұғымдар
(Command) командасы: күйді өзгерту ниеті ('CreateOrder', 'CapturePayment'). Инварианттарды тексереді.
Сұрау (Query): деректерді алу ('GetOrderById', 'ListUserTransactions'). Жанама әсері жоқ.
Жазу моделі: агрегаттар/инварианттар/транзакциялар; сақтау - реляциялық/кілт-мән/оқиға журналы.
Оқу (проекция) моделі: материалданған кестелер/индекстер/кэш, асинхронды үндестіріледі.
Үйлесімділік: жазу мен оқу арасындағы жиі eventual; сыни жолдар - write-модельден тікелей оқу арқылы.
Сәулет (скелет)
1. Write-сервис: командаларды қабылдайды, инварианттарды валидациялайды, өзгерістерді тіркейді (ДБ немесе оқиғалар).
2. Outbox/CDC: өзгерістер фактісін кепілді жариялау.
3. Проекция процессорлары: оқиғаларды/CDC тыңдап, оқу үлгілерін жаңартады.
4. Read-service: материалданған көріністерден/кештерден/іздеуден queries қызмет көрсетеді.
5. Сағалар/оркестрлеу: кросс-агрегаттық процестерді үйлестіреді.
6. Байқалуы: проекция лаг, табысты қолдану пайызы, DLQ.
Жазу моделін жобалау
Агрегаттар: транзакциялардың нақты шектері (мысалы, 'Order', 'Payment', 'UserBalance').
Инварианттар: формализациялаңыз (ақша сомасы ≥ 0, бірегейлік, лимиттер).
Командалар кілт бойынша ұқсас (мысалы, 'idempotency _ key').
Транзакциялар қамту бойынша ең аз; сыртқы жанама әсерлер - outbox арқылы.
Команда үлгісі (псевдо-JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
Оқу үлгісін жобалау
Сұраулардан бастаңыз: қандай экрандар/есептер қажет?
Денормализацияға жол беріледі: read-модель - «оңтайландырылған кэш».
Әртүрлі тапсырмалар үшін бірнеше проекциялар: іздеу (OpenSearch), есептер (бағандық сақтау), карточкалар (KV/Redis).
TTL және қайта жинау: проекциялар көзден қалпына келтірілуі тиіс (оқиғалар репликасы/снапшоттар).
Үйлесімділік және UX
Eventual consistency: интерфейс ескі деректерді қысқа уақытта көрсетуі мүмкін.
UX-паттерндер: «деректер жаңартылады»..., optimistic UI, синхрондау индикаторлары, расталғанға дейін қауіпті әрекеттерді бұғаттау.
Күшті келісуді талап ететін операциялар үшін (мысалы, есептен шығару алдында нақты балансты көрсету) тікелей write-модельден оқыңыз.
CQRS және Event Sourcing (қалауы бойынша)
Event Sourcing (ES) оқиғаларды сақтайды, ал агрегаттың күйі - оларды орау нәтижесі.
CQRS + ES бумасы тамаша аудит және проекцияларды оңай қайта жинауды береді, бірақ күрделілікті арттырады.
Балама: кәдімгі OLTP-БД + outbox/CDC → проекция.
Репликалау: Outbox және CDC
Outbox (бір транзакцияда): домендік өзгерістерді жазу + outbox оқиғасын жазу; паблишер шинаға жеткізеді.
CDC: DB логынан оқу (Debezium және т.б.) → домендік оқиғаларға түрлендіру.
Кепілдіктер: әдепкі бойынша at-least-once, тұтынушылар мен проекциялар іспеттес болуы тиіс.
Сақтау орнын таңдау
Write: транзакциялар үшін реляциялық (PostgreSQL/MySQL); KV/Document - мұнда инварианттар қарапайым.
Read:- KV/Redis - карточкалар және жылдам кілт оқулар;
- Іздеу (OpenSearch/Elasticsearch) - іздеу/сүзгілер/фасеттер;
- Колонналық (ClickHouse/BigQuery) - есептер;
- CDN кэші - көпшілік каталогтар/мазмұн.
Интеграция үлгілері
API қабаты: 'commands' және 'queries' үшін жеке эндпоинттер/қызметтер.
Теңсіздік: тақырыптағы/денедегі операция кілті; TTL-мен recent-keys сақтау.
Сағалар/оркестрлеу: тайм-ауттар, өтемақылар, қадамдардың қайталануы.
Backpressure: проекция процессорларының параллелизмін шектеу.
Бақылау мүмкіндігі
write өлшемдері: командалардың жасырындылығы p95/99, сәтті транзакциялардың үлесі, валидация қателері.
Метрика read: p95/99 сұрау, hit-rate кэш, іздеу кластеріне жүктеме.
Проекция лагі (уақыты мен хабарламасы), DLQ-ставка, дедупликация пайызы.
Trace: 'trace _ id' командасы арқылы өтеді → outbox → проекция → query.
Қауіпсіздік және комплаенс
Құқықтарды бөлу: жазуға және оқуға арналған әртүрлі scopes/рөлдер; ең аз артықшылықтар қағидаты.
PII/PCI: проекцияларды азайтыңыз; at-rest/in-flight шифрлау; бүркемелеу.
Аудит: команда, актер, нәтиже, 'trace _ id'; Маңызды домендер үшін WORM-мұрағаттар (төлемдер, KYC).
Тестілеу
Contract tests: командалар үшін (қателер, инварианттар) және queries (пішімдер/сүзгілер).
Projection tests: оқиғалар/CDC сериясын ұсыныңыз және қорытынды оқу моделін тексеріңіз.
Chaos/latency: проекция процессорларына кідірістерді инжекциялау; UX тексеру.
Replayability: snapshot/log стендіндегі проекцияларды қайта жинау.
Көші-қон және эволюция
Жаңа өрістер - оқиғада аддитивті/CDC; read-модельдер қайта жиналады.
Схемалардың редизайнында қос жазба (dual-write); ескі проекцияларды ауыстырып қосқанға дейін сақтаймыз.
Оқиғалар мен эндпоинттердің 'v1 '/' v2' нұсқасы, Sunset-жоспары.
Feature flags: канарейка бойынша жаңа queries/проекцияларды енгізу.
Антипаттерндер
CQRS қарапайым CRUD-сервистерде «сән үшін».
Оқудың жазуға қатал синхронды тәуелділігі (оқшаулау мен орнықтылықты жояды).
Барлығына бір индекс: бір read-store-ға әртүрлі сұрауларды араластыру.
Проекцияларда сәйкессіздік жоқ → дубль және айырмашылықтар.
Қалпына келтірілмейтін проекциялар (реплея/снапшот жоқ).
Домен мысалдары
Төлемдер (онлайн-сервис)
Write: транзакциялық ДБ-да 'Authorize', 'Capture', 'Refund'; outbox 'payment' жариялайды.
Read:- Redis UI үшін «төлем карточкасы»;
- есептер үшін ClickHouse;
- Транзакцияларды іздеу үшін OpenSearch.
- Сындарлы жол: авторизация ≤ 800 мс p95; UI үшін оқу үйлесімділігі - eventual (2-3 с дейін).
KYC
Write: старт/апдейт мәртебесіне командалар; PII қорғалған ДҚ-да сақтау.
Read: PII жоқ статустардың жеңілдетілген проекциясы; PII қажет болған жағдайда нүктелі тартылады.
Қауіпсіздік: мәртебені оқу және құжаттарға қатынау үшін әртүрлі scopes.
Баланстар (iGaming/қаржы)
Write: атомарлық инкременттері/декременттері бар 'UserBalance' агрегаты; операцияға арналған демпотенттік кілттер.
Read: «жылдам баланс» үшін кэш; есептен шығару үшін - write-ден тікелей оқу (қатаң келісу).
Сага: депозиттер/қорытындылар оқиғалармен үйлестіріледі, істен шыққан кезде - өтемақылар.
Енгізу парағы
- Write үлгісінің агрегаттары мен инварианттары таңдалған.
- Негізгі queries анықталған және оларға арналған проекциялар жобаланған.
- outbox/CDC және idempotent проекция процессорлары теңшелді.
- Проекцияларды қайта жинау жоспары бар (snapshot/replay).
- SLO: командалардың жасырындылығы, жобалардың артта қалуы, жеке-жеке read/write қолжетімділігі.
- Қол жеткізу құқықтары бөлінді және деректерді шифрлау жүзеге асырылды.
- DLQ/лаг/дедупликациядағы алерттар.
- Тесттер: келісімшарттар, проекциялар, хаос, реплика.
FAQ
CQRS үшін Event Sourcing міндетті ме?
Жоқ. Кәдімгі БД + outbox/CDC құрылуына болады.
Рассинхронизациямен қалай күресуге болады?
UX-ті анық жобалау, проекция артта қалуын өлшеу, сыни операцияларға write-ден оқу беру.
Бір сервисте write және read ұстауға бола ма?
Иә, физикалық бөліну - опциондық; жауапкершілікті қисынды бөлу міндетті.
Агрегаттар арасындағы транзакциялар туралы не айтасыз?
Сағалар мен оқиғалар арқылы; мүмкін болса, бөлінген транзакциялардан аулақ болыңыз.
Жиынтық
CQRS қолдарын шешеді: нақты инварианттары бар жұқа, сенімді жазу жолы және материалданған проекциялардан жылдам, мақсатты оқулар. Бұл өнімділікті арттырады, эволюцияны жеңілдетеді және жүйені жүктемелерге төзімді етеді - егер келісімділікті, бақылаушылықты және көші-қонды тәртіптік түрде басқаратын болса.