选择存储
服务把它的日志、快照、定时器、检查点、租约、失败台账和预留保存在存储里。一种类型的一个存储就够了,大多数服务不需要更多。本页说明每种类型用来做什么,怎样在旁边为某个聚合类型加一个检索索引,以及为什么日志从不挪走。
角色
| 存储 | 保存 | 用于 |
|---|---|---|
| MongoDB | 全部:日志、快照、定时器、检查点、租约、失败台账和预留;提供快照查询和 events 查询 | 默认选择。一个副本集,不够时分片(MongoDB) |
| PostgreSQL | 全部,与 MongoDB 相同 | 你运行的是 PostgreSQL 时,同样可选(见下文) |
| Elasticsearch | 某个聚合类型快照的一份副本,以及通过 Sink 写入的事件 | 检索状态和事件:全文、模糊匹配、多维度聚合(Elasticsearch) |
| ClickHouse | 不是存储:发布流的 Sink,以及数据集的数据仓库 | 跨服务的分析(ClickHouse) |
安排聚合类型的数据
默认情况下,聚合类型只写入 MongoDB。 它的日志和快照都在那里,命令和状态读取读取它们,快照查询和
events查询也在那里运行。状态需要被检索的类型,快照仍保存在 MongoDB 上,另在 Elasticsearch 上加一份副本,由它的快照查询读取:
yamlwow: stores: main: { type: mongo } search: { type: elasticsearch } log: store: main aggregates: "[shop.product]": snapshot: stores: [main, search] query: snapshot: store: search它的快照查询(包括列表)读取 Elasticsearch。它的命令从 MongoDB 加载状态,状态读取(
GET …/state,包括minVersion)也读取 MongoDB,所以强一致读从不等待 Elasticsearch。只检索事件历史的类型,快照留在原处,让它的
events查询指向 Elasticsearch 存储:query.events.store: search。宿主会自行启动事件检索 Sink,不需要第二个快照存储。
不是双写
Elasticsearch 上的副本不是在命令路径中与主存储并列写入的。命令提交到日志,每个快照存储由它自己的一个订阅从日志写入:默认快照存储(wow.snapshot.stores 的第一个,否则为日志的存储)是 wow.snapshot,其余每个是 wow.snapshot:<store>,各有自己的检查点。快照存储只保留它收到的最高版本,所以收到重复或迟到提交的存储,最终停在日志所说的位置。Elasticsearch 可能落后于主存储,但不会永久偏离它,需要时随时可以从日志重建(重建与重放)。
Elasticsearch 从不是日志,也从不是主存储
- 近实时。 写入在刷新之后才可检索:默认情况下写入会等待刷新(
refresh: wait-for)。日志的尾读和命令的加载必须立即看到提交。 - 没有事务。 文档整体替换,由乐观并发保护;日志需要版本和请求上的唯一键,检索索引不维护这些。
- 映射会增长。 状态和事件的每个字段都会建立映射,所以映射随模型增长;映射必须改变的索引要重建,而不是迁移。
因此宿主拒绝把 Elasticsearch 存储作为日志存储,也拒绝把它作为聚合类型的主快照存储或唯一的快照存储:命令从主存储加载状态,状态读取是对它的强一致读。副本用于它擅长的检索,主存储用于一切必须是最新的东西。
代价
- 更多写入。 每个快照存储都要为它的类型的每次提交写一次:两个存储,快照写入就是两倍。
- 版本不一致。 检索可能显示比稍后的状态读取更旧的版本。答案必须是最新的时,读取状态。
- 又一个集群。 Elasticsearch 是在 MongoDB 之外要运行、规划容量、保护和监控的第二个系统。
什么时候选 PostgreSQL
PostgreSQL 保存 MongoDB 保存的一切,保证也相同,所以选择取决于你运行的是什么:
- 你的组织运行 PostgreSQL,有它的备份、监控和人员。
- 你希望数据在 SQL 里:表有文档说明,每个聚合类型一张(PostgreSQL)。
- 状态未被缓存的命令,在快照位于日志所在数据库时,一次往返读出快照和其后的日志;MongoDB 则分开读取。
PostgreSQL 不做文本检索:需要检索的类型加一份 Elasticsearch 副本,如上面的规则 2。为每个节点规划它的连接(规划连接池)。