读模型
Wow 里的每一次读都从日志派生。本页说明哪些读模型不写代码就有、什么时候要自己写、谁可以看到什么,以及 Wow 怎样不让读伤到写。
快照是默认读模型
快照是一个聚合最新的已提交状态,加上它的元数据:版本、租户、所有者、空间、删除标记、标签、第一个和最后一个事件的时间,以及第一次和最后一次提交的操作人。同一份副本有三种用途:
| 用途 | 怎样用 |
|---|---|
| 更快的写 | 缓存未命中时,内核用快照加上它之后的日志条目重建状态。 |
| 查询 | 查询协议直接读快照。字段来自 Manifest 里状态的 schema,不用写投影。 |
| 视图与数据集 | 视图引擎的记录视图和分析视图默认建立在快照上。 |
内置的 snapshot 订阅以版本为守卫写入快照。一批里有同一聚合的多个版本时,只写最新的那个,所以写入压力随聚合数增长,而不是随提交数增长。
只有快照满足不了某种读时,才自己写投影:合并多个聚合的视图、状态里没有的形状、读必须落在某个特定存储里。
只写不查、状态又很大的聚合,可以关闭快照。
快照是缓存
快照是派生的,不是事实。写侧从不需要重建快照:不合适的快照干脆不用,从日志重放状态即可。只有读需要快照正确,快照不正确时 Wow 会重建它们:
- 状态结构的变化是旧快照表达不了的,例如某个演化函数的含义变了,或者新字段必须从历史计算:提高聚合的状态修订号。新部署在后台原地从日志重建已存储的快照。增加一个可选字段不需要新的修订号。
- 少数快照是错的:用一条系统命令重建指定聚合的快照。
- 快照迁到另一个存储:从日志开头填充新存储,追平后它成为主存储。
重建期间,查询照常回答,响应会说明数据正在重建。
三个查询模型
| 模型 | 一条记录是 | 适用于 |
|---|---|---|
snapshot | 一个聚合的最新状态 | 基于当前状态的列表、搜索、分页、计数和聚合 |
events | 一个聚合的一次提交:版本和它的事件 | 审计轨迹、时间线、按事件的分析 |
dataset | 在发布的数据之上定义、由 ClickHouse 提供的宽表中的一行 | 跨聚合、跨上下文关联的分析 |
三者共用一个查询引擎:同样的过滤语言、同样的准入(schema 检查、策略、预算)、同样的观测,以及同样的关于模型能做什么的描述。每个模型带来自己的 schema 和后端。后端能做什么,比如支持哪些操作符或聚合,是客户端构建查询之前读取的能力描述的一部分,因为这取决于存储,而不是模型。
除了查询,客户端还可以直接读一个聚合的状态:最新状态、某个版本的状态、某个时间点的状态,或者状态逐次提交如何变化的轨迹。历史状态在一定限制内从日志重放得到。
写后即可查
命令默认等待它的快照(等待结果),所以紧随其后的查询能看到这次写入。读一个聚合的状态时,还可以要求一个最小版本,即命令结果返回的那个版本:服务用日志把落后的快照追上来,必要时短暂等待;聚合仍未达到这个版本时,返回 409 wow.query.min-version-not-reached 并附上当前版本。聚合的第一个快照写入之前,读它的状态由日志回答。
谁能看到什么
决定调用方能读什么的事实,和决定命令能改什么的事实是同一批,承载它们的字段名在 Manifest 里只定义一次:
- 租户。 查询总是在调用方的租户内读取。
- 所有者和空间。 作用域里有所有者的调用方只读它拥有的聚合;租户级调用方读它租户的所有聚合。空间对两者再加以限定。声明为属于租户的聚合(例如商品)不按所有者过滤。
- 已删除的聚合不包含在内,除非查询要求。
- 标签。 启用基于属性的访问控制后,用调用方的属性匹配每个聚合的标签。默认关闭。
- 敏感字段在调用方拿到的结果里被脱敏。
调用方不能看的东西,对它来说就不存在:读取它返回 404,绝不是 403,原因只写进审计日志。
读不能拖慢写
查询和命令常常共用一个数据库和它的连接池。占着连接的慢查询,会让需要连接的命令饿死。所以每个存储都有一个查询舱壁:
- 每个存储一个并发上限。 默认由查询与写共用的连接数推出。
- 短暂排队,然后拒绝。 超出上限的查询最多等约 200 毫秒,看是否有空出的名额。没有空出或队列已满时,以 429
wow.query.busy拒绝并带上Retry-After。一个页面发出的一阵查询会被吸收;持续过载则被快速拒绝。 - 跟随写入调整的上限。 上限每秒根据写路径的健康状况自我调整:追加变慢或等待连接时减半;写入健康且有查询在等时加一。
- 可续读的流。 流式列表在分块之间释放名额,并在持有时间到期后结束。它发出的每条记录都带一个位置,所以被服务端、网络或滚动升级断开的客户端,可以从断开的地方续读。
- 预算。 每个查询都有时间预算。超出预算的查询以 504
wow.query.timeout失败,原样重试没有意义。计数和分页总数有上限,除非允许精确计数。
ClickHouse 上的数据集经过同样的舱壁,所以分析拖不住写同一集群的 Sink。