通过查询实施解放
基于查询的数据解放涉及查询数据存储并将所选择的结果发布到相关的事件流中。一个使用合适的 API、SQL 或类 SQL 语言的客户端会被用于向数据存储请求特定的数据集。必须能够批量查询数据集以提供事件的历史记录,然后定期更新,以确保数据的更改被发布到输出事件流中。
此模式有几种查询类型。
批量加载
执行批量查询并加载数据集中的所有数据。当需要在每个轮询间隔加载整张表时,以及在进行增量更新之前,都需要执行批量加载。
批量加载成本很高,因为它需要从数据存储中获取整个数据集。对较小的数据集,这可能不是问题,但对大规模的数据集,特别是那些有百万或亿万条记录的数据集来说,则可能很困难。对于查询和处理大规模数据集的情况,我建议研究针对特定数据存储的最佳实践,因为这些最佳实践可能因存储器的实现而不同。
增量时间戳加载
使用增量时间戳加载,可以查询并加载自上一个查询结果的最大时间戳以来的所有数据。这种方法使用数据集中的一个 updated_at 列或字段来跟踪记录最后一次修改的时间。在每次增量更新时,只查询 updated_at 时间戳晚于最后一次处理时间的记录。
自增ID加载
自增 ID 加载是查询并加载比上一次处理的 ID 值大的所有数据。这需要一个严格有序的整型或长整型字段。在每次增量更新时,只查询 ID 值比上一次处理的 ID 值大的记录。这种方法通常用于查询存储不可变记录的表,比如发件箱表(参见 4.6 节)。
自定义查询
自定义查询仅受限于客户端查询语言。当客户端只需要较大数据集中的某个数据子集时,或者联结多个表中的数据并对其进行非范式化以避免内部数据模型过度暴露时,通常使用这种方法。例如,用户可以根据特定的字段过滤业务伙伴的数据,然后将每个合作伙伴的数据发送到自己的事件流。
增量更新
任何增量更新的第一步都是确保数据集中的记录有必需的时间戳或自增 ID。必须存在一个字段让查询可用于从要处理的记录中筛选出已被处理的记录。缺失这些字段的数据集需要把它们加上,数据存储需要配置成可以填充必需的 updated_at 时间戳或自增 ID 字段。如果这些字段无法添加到数据集中,那么基于查询的模式就无法使用增量更新。
第二步是确定轮询频率和更新时延。较高的更新频率可以为下游系统带来较低的数据更新时延,但是这会给数据存储造成比较大的总负载开销。考虑请求之间的间隔是否足以完成所有数据的加载也很重要。当旧的查询仍在加载时开始新的查询可能会导致竞争状态,即旧数据会覆盖输出事件流中较新的数据。
一旦选定增量更新字段并确定了更新频率,最后一步就是在增量更新启动之前执行一次批量加载。这次批量加载必须在进一步增量更新之前查询并生成数据集中的所有存量数据。
基于查询更新的优点
基于查询的更新具有以下优点。
可定制性
可以查询任何数据存储,并且所有客户端类型都能用于查询数据。
独立的轮询周期
可以以较高的频率执行某些特定查询以满足更严格的 SLA,而对于其他开销较大的查询可以降低执行频率以节省资源。
内部数据模型的隔离
关系型数据库可以通过使用底层数据的视图或物化视图来达到与内部数据模型的隔离。该技术可用来隐藏不应该暴露在数据存储之外的领域模型信息。
请记住,被解放的数据将是单一事实来源。要考虑是否隐藏或忽略了应该被解放的数据,或者是否应该重构源数据模型。这通常发生在数据被从遗留系统里解放出来的过程中,在这些系统里,业务数据和实体数据已经随着时间的推移相互交织在一起。
基于查询更新的缺点
基于查询的更新也有一些缺点。
需要 updated_at 时间戳
要查询的事件的底层表或命名空间必须有一列包含着它们的updated_at 时间戳。这对于跟踪最近一次的数据更新时间来做增量更新至关重要。
无法跟踪的硬删除
硬删除无法在查询结果中体现,所以要跟踪删除只能采用基于标记的软删除,比如 is_deleted 列。
数据集 schema 和输出事件 schema 之间脆弱的依赖关系
数据集 schema 变更时可能会出现与下游事件 schema 格式规则不兼容的情况。如果数据解放机制的代码与数据存储应用程序的代码是分离的,则崩溃的可能性会越来越大,这在基于查询的系统中很常见。
间歇捕获
数据只能在间歇性轮询中同步,这样对同一个记录的多次独立变更只能体现为一个事件。
生产资源消耗
查询使用底层系统资源来执行,这会在生产系统上造成不可接受的时延。使用只读副本可以减轻此问题,但会带来额外的财务成本和系统复杂性。
数据变更导致的查询性能变化
查询和返回的数据量取决于对底层数据所做的变更。在最坏的情况下,每次都会更改整个数据集。如果某次查询在下一次查询开始时仍未结束,则会出现竞争状态。