Read Models және проекциялар
Read Model - бұл нақты азық-түлік сценарийі бойынша жылдам оқу үшін арнайы жобаланған кесте/индекс/түр. Проекция - оқиғаларды/көздің өзгерістерін Read Model жаңартуларына (әдетте idempotent upsert) түрлендіретін процесс. CQRS бірге бұл OLTP ядросын жеңілдетуге және «жаңаруды» бақылай отырып, p95/p99 оқуларды тұрақтандыруға мүмкіндік береді.
Басты идеялар:- «Әмбебап схеманы» емес, сұрауды нормалау.
- Инкрементальды және демпотентті түрде жаңарту.
- Staleness пен тәртіпті басқару керек.
1) Read Models қашан пайдаланылады (және қашан - жоқ)
Жарайды:- Жаңартудың рұқсат етілген кідірісі бар жиі ауыр оқулар (джоиндер/агрегациялар/сұрыптаулар).
- Дашбордтар, каталогтар, лендингтер, «топ-N», дербес фидтер, іздеу тізімдері.
- Жүктемені бөлу: write-ядро - қатаң, read-жазықтық - жылдам және масштабталатын.
- «Әрбір жазбаға» қатаң инварианттарды талап ететін операциялар (ақша, бірегейлік). Онда - strong path.
2) Сәулеттік сұлба (сөздік схема)
1. Өзгерістер көзі: OLTP домен оқиғалары (event sourcing) немесе CDC.
2. Проекция конвейері: парсер → аггрегация/денормализация → idempotent upsert.
3. Read Store: Сұранысқа оңтайландырылған БД/индекс (RDBMS, баған, іздеу).
4. API/клиент: жылдам SELECT/GET, «as_of/freshness» атрибуттарымен.
3) Read Model жобалау
Сұраудан бастаңыз: қандай өрістер, сүзгілер, сұрыптаулар, пагинация, топ-N?
Теңдестіріңіз: біріктірілген деректерді (атауларды, сомаларды, мәртебелерді) сақтаңыз.
- Партиялануы: 'tenant _ id', күні, аймағы бойынша.
- Primary key: бизнес-кілт + уақытша бакет (мысалы, '(tenant_id, entity_id)' немесе '(tenant_id, bucket_minute)').
- Индекстер: жиіліктер бойынша where/order by.
- TTL/ретеншн: уақытша витриналар үшін (мысалы, 90 күн).
4) Жаңарту ағыны және іспеттілік
Idempotent upsert - проекциялар тұрақтылығының негізі.
Жалған: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);
Ережелер:
- Әрбір хабардың нұсқасы/уақыты болады; тек қана «жас немесе тең» (idempotency) қабылдаймыз.
- Агрегаттар үшін (есептегіштер, сомалар) - state сақтаңыз және коммутативті жаңартуларды (немесе CRDT тәсілдерін) пайдаланыңыз.
5) Өзгерістер көзі: оқиғалар vs CDC
Оқиғалар (event sourcing): бай семантика, әртүрлі проекцияларды құру оңай; схемалардың эволюциясы маңызды.
CDC (логикалық репликалау): бар ДБ-ға жай қосу; оқиғаларды DML → mapping және шулы жаңартуларды сүзу қажет.
- «Улы» хабарламалар үшін жеткізу кепілдіктері (at-least-once) және DLQ.
- Кілт бойынша тәртібі (partition key = 'tenant _ id: entity _ id').
6) Тәртіп, себеп және «жаңалық»
Кілт бойынша тәртіп: бір объектінің оқиғалары дәйекті түрде келуі тиіс; партиялануы мен нұсқаларын пайдаланыңыз.
Себептері (session/causal): автор өзінің өзгерістерін (RYW) көру үшін, сұрауларда watermark нұсқасын беріңіз.
Жаңалық (bounded staleness): 'as _ of '/' X-Data-Freshness' қайтарыңыз және SLO ұстаңыз (мысалы, p95 ≤ 60 c).
7) Инкрементальды агрегаттар және топ-N
Минуттық сату бакеттерінің мысалы: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;
Топ-N үшін:
- Реттелген сөрелерді (мысалы, 'revenue DESC' бойынша) қолдап, тек өзгерген позицияларды (heap/skiplist/limited table) жаңартыңыз.
- Топ «терезесін» сақтаңыз (мысалы, бір сегментке 100-1000 жол).
8) Іздестіру және гео-проекциялар
Іздеу (ES/Opensearch): нормаланған құжат, pipeline трансформациялар, құжат нұсқасы = көз нұсқасы.
Гео: 'POINT/LAT, LON' сақтаңыз, алдын ала тайлдар/квадротрларды біріктіріңіз.
9) Мульти-тенант және өңірлер
'tenant _ id' проекция кілттері мен оқиғаларда міндетті.
Fairness: «шулы» басқаларды тежемеу үшін throughput per tenant (WFQ/DRR) проекцияларын шектеңіз.
Residency: проекция write ядросымен бірдей аймақта тұрады; өңіраралық витриналар - агрегаттар/мәліметтер.
10) Бақылау және SLO
Өлшемдері:- 'projection _ lag _ ms' (көз → витрина), 'freshness _ age _ ms' (соңғы дельта сәтінен бастап).
- жаңартулар, қателер үлесі, DLQ-rate, redrive-success.
- Витриналардың өлшемі, p95/p99 оқу жасырындылығы.
- Теги: `tenant_id`, `entity_id`, `event_id`, `version`, `projection_name`, `attempt`.
- Аңдатпалар: merge-шешімдер, ескірген нұсқаларды жіберу.
11) Плейбуктар (runbooks)
1. Лагтың өсуі: коннектор/брокерді тексеру, партияларды ұлғайту, негізгі витриналарға басымдық беру.
2. Схеманың көптеген қателері: редраймды тоқтату, схемаларды көшіру (backfill), маппердің жаңа нұсқасымен қайта іске қосу.
3. Қайталанған DLQ: batch азайту, «көлеңкелі» өңдегішті қосу, демпотенттілікті күшейту.
4. Витринаның келіспеуі: журналдан/көзден терезе сыртына (tenant/partition бойынша селективті) витринаны rebuild орындау.
5. Ыстық кілттер: кілт бойынша бәсекелестікті шектеу, жергілікті кезектерді қосу, агрегатты жеке витринаға шығару.
12) Толық қайта есептеу (rebuild) және backfill
Тәсіл:- Тұтынуды тоқтату (немесе витринаның жаңа нұсқасына ауыстыру).
- Пакеттермен қайта санау (партия/күн/теңге бойынша).
- Екі фазалы свитчты қосу: алдымен 'read __ v2' -ні толтырыңыз, содан кейін оқу бағытын атомарлық түрде ауыстырып қосыңыз.
13) Схемалардың эволюциясы (нұсқалау)
Оқиға/құжаттарда 'schema _ version'.
Проекция бірнеше нұсқаны оқи алады, «ұшуда» көші-қон.
Ірі өзгерістер үшін - жаңа витрина v2 және канареялық трафик.
14) Қауіпсіздік және қол жетімділік
Көзден RLS/ACL мұраға қалдырыңыз; витринаны бастапқы деректерге қарағанда кеңірек жасамаңыз.
UX/талдау үшін қажет емес проекцияларда PII жасырыңыз.
Редрайвтардың/қайта есептеулердің/қолмен түзетулердің аудиті.
15) Конфигурациялық үлгі
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) Типтік қателер
«Барлық жағдайларға бір витрина» → ауыр апдейттер және нашар p99.
Агрегаттарда икемділіктің жоқтығы → дубль/секірулер.
Dual-write тікелей витринаға және OLTP → айырмашылықтар.
Жаңашылдықтың нөлдік көрінуі → өніммен күту қайшылығы.
Rebuild екі фазалы свитчсыз → жауаптардағы «тесіктер».
Партиялану/индекстер жоқ → құн мен жасырындылықтың өсуі.
17) Жылдам рецепттер
Каталог/іздеу: құжаттық витрина + инкрементальды upsert, lag ≤ 5-15 с, сүзгі индекстері.
Дашбордтар: минуттық/сағаттық бакеттер, агрегаттар 'SUM/COUNT', p95 жас ≤ 60 с.
Дербес таспа: пайдаланушы бойынша проекция + автор үшін causal/RYW, кэш үшін fallback.
Жаһандық SaaS: өңірлік витриналар, кросс-өңірлік агрегаттар; fairness per tenant.
18) Азық-түлік алдындағы чек-парағы
- Витрина нақты сұрау салуға жобаланған; индекстер мен партиялар бар.
- Өзгерістер көзі таңдалды (оқиғалар/CDC); жеткізу кепілдіктері және кілт бойынша тәртіп.
- Нұсқалары/уақыты бар теңшелетін upsert; «ескі» оқиғалардан қорғау.
- Жас SLO анықталған және жауаптарда беріледі ('as _ of/freshness').
- DLQ және қауіпсіз редрайв теңшелген; rebuild/backfill.
- Бәсекелестікті шектеу (per-key serial) және fairness per tenant.
- Лаг/қателер/latency өлшемдері, p95/p99 және DLQ биіктігі.
- Схемаларды нұсқалау және көші-қон стратегиясы (v2 + свитч).
- Қатынау/PII саясаты мұраға қалдырылды және тексерілді.
Қорытынды
Read Models және проекциялар - бұл инженерлік оқу жылдамдатқышы: сіз болжамды миллисекундтарды алу және жазба ядросын жеңілдету үшін «жаңалық» және стриминг инфрақұрылымының шағын бағасымен төлейсіз. Сұраныс бойынша витриналарды жобалаңыз, апдейттерді іспотентті жасаңыз, артта қалуды өлшеңіз және таза болуды анық уәде етіңіз - және сіздің API-ларыңыз жүктеме, деректер және география өскен кезде де жылдам болады.