阅读模型和投影
Read Model是一种专门设计的表格/索引/视图,用于针对特定产品脚本进行快速阅读。投影是将事件/源更改转换为Read Model更新(通常为idempotent upsert)的过程。与CQRS结合,这允许卸载OLTP内核并稳定p95/p99读数,从而控制"新鲜度"。
主要想法:- 根据请求而不是"通用方案"进行非规范化。
- 增量和偶数更新。
- 明确地管理稳定和秩序。
1)何时使用Read Models(何时不使用)
适合:- 频繁重读(joins/聚合/排序),有效的更新延迟。
- Dashbords,目录,登陆,"top-N",个人围裙,搜索列表。
- 负载分离:写入核心-严格,读取平面-快速且可扩展。
- 需要严格的"每个条目"不变量的操作(金钱,唯一性)。那里有一个坚强的路径。
2)建筑轮廓(言语方案)
1.更改源:来自OLTP的域事件(事件源)或CDC。
2.投影输送机:解析器→ aggregation/去规范化 → idempotent upsert。
3.Read Store:针对查询优化的DB/索引 (RDBMS、柱形、搜索)。
4.API/客户端:快速SELECT/GET,具有"as_of/freshness"属性。
3) Read Model设计
从查询开始: 哪些字段、过滤器、排序、分页、前N?
去规范化:存储已经合并的数据(名称、金额、状态)。
钥匙:- 派对:通过"tenant_id",日期,区域。
- 主要钥匙:业务密钥+时间桶(例如"(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)。
- 对于聚合体(计数器、和)-存储状态并使用交换式更新(或CRDT方法)。
5)更改源: 事件vs CDC
事件(事件来源):丰富的语义,易于构建不同的投影;图的演变很重要。
CDC(逻辑复制):简单地连接到现有数据库;需要模拟DML→sobyty并过滤噪音升级。
- "有毒"消息的交付保证(at least-once)和DLQ。
- 按键排序(partition key='tenant_id: entity_id')。
6)顺序,因果关系和"新鲜"
按键顺序:一个对象的事件必须依次出现;使用派对和版本。
因果关系(session/causal):让作者看到他们的变化(RYW),在查询中传递水印版本。
新鲜(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)。
- 存储顶部的"窗口"(例如每段100-1000行)。
8)搜索和地理投影
搜索(ES/Opensearch):非归一化文档、转换管道、文档版本=源版本。
Geo:存储"POINT/LAT, LON",预聚合针脚/四边形。
9)多特南特和地区
"tenant_id"在投影键和事件中具有约束力。
Fairness:限制per tenant投影(WFQ/DRR),以便"嘈杂"不会抑制其他投影。
居住地:投影与写核生活在同一区域;区域间店面-总结/摘要。
10)可观察性和SLO
度量标准:- "projection_lag_ms" (istochnik→vitrina)、"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),重新启动新版本的mapper。
3.重复DLQ:减少击球,启用"影子"处理程序,增强等效性。
4.店面不一致:从每窗口的日志/来源执行重构店面(选择性tenant/partition)。
5.热键:限制按键竞争,添加本地队列,将单元带到单独的店面。
12)完全重新计票(rebuild)和backfill
方法是:- 停止消费(或切换到新版本的店面)。
- 用包重新计算(按批次/日期/tenants)。
- 启用两阶段卷轴:首先填充"read __ v2",然后原子切换读取路由。
13)方案的演变(转化)
事件/文档中的"schema_version"。
投影可以读取多个版本,"即时"迁移。
对于重大变化-新的v2展示柜和金丝雀流量。
14)安全和访问
从源位置继承RLS/ACL;不要在访问方面比原始数据更广泛。
将PII伪装成不需要UX/分析的投影。
审核红路/重新计票/手动编辑。
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。
单位→双打/跳跃缺乏等效性。
双写直接进入店面和OLTP →差异。
零新鲜度可见性→与产品的期望冲突。
Rebuild没有两相卷轴→答案中的"孔"。
没有分期付款/指数→价值上升和潜伏期上升。
17)快速食谱
目录/搜索:文档展示+增量upsert,lag ≤ 5-15 c,筛选器的索引。
Dashbords:分钟/小时垃圾箱,"SUM/COUNT"单元,p95新鲜度≤ 60 c。
个人磁带:作者按用户+causal/RYW投影,后退到缓存。
全球SaaS: 区域店面,跨区域分组;fairness per tenant.
18)售前支票清单
- 陈列柜的设计符合特定的要求;有指数和分期付款。
- 选择更改源(事件/CDC);交付保证和按键顺序。
- 具有版本/时间的Idempotent upsert;防止发生"旧"事件。
- 在答复("as_of/freshness")中定义并给出了新鲜度的SLO。
- DLQ和安全的重新分区配置;rebuild/backfill上的花花公子。
- 竞争限制(按键串行)和公平竞争限制。
- 滞后/错误/后退度量,p95/p99上的差值和DLQ的增长。
- 回收方案和迁移策略(v2+switch)。
- 访问策略/PII已继承和验证。
结论
阅读模型和投影是一种工程阅读加速器:您付出"新鲜度"和流媒体基础架构的少量代价,以获得可预测的毫秒并卸载记录核心。在查询下设计店面,使升级变得令人望而生畏,测量时差,并明确承诺新鲜-即使负载,数据和地理位置增加,您的API仍将保持快速。