最终的一致性不适用于流式传输

2020-07-14 22:25:21

流式系统不同步地消耗输入和产生输出:系统在任何时刻的输出可能不会反映到目前为止看到的所有输入。这些系统提供了关于其输出如何与其输入相关的各种保证。在较弱的(但不是不受欢迎的)保证中,就是最终的一致性。非正式地说,最终一致性意味着如果输入停止更改,输出将最终得到正确的结果。

在这篇文章中,我们将看到,只要它的输入流没有停止,自然的最终一致的计算就会产生无限大的系统错误。如果您正在进行哪怕是微不足道的计算,您应该准备好让您的结果永远不一致(这是一种不太流行的一致性定义)。至少在您暂停输入流并等待正确答案之前。

并不是所有的东西都输了!有提供强大一致性保证的流处理系统。物化和差分数据流都通过提供始终正确的答案来避免这些类别的错误,其他几个流系统也是如此。

如果您希望在结果中避免系统错误和持续错误,您可能应该检查您使用的流处理器是否提供更强的一致性保证。

最终一致性是分布式计算中用来实现高可用性的一致性模型,它非正式地保证,如果没有对给定数据项进行新的更新,则最终对该数据项的所有访问都将返回上次更新的值。

最常为键值存储调用最终一致性,其中每个键跟踪一个独立值,可以合理地想象不更新与键相关联的值的时间足够长,以至于可能会得出正确的答案。例如,如果数据库存储了从人们到他们地址的映射,您对您自己地址的更新可能不会立即可见,但是如果您给它几分钟时间,它可能会自动整理出来(如果您不进一步更新您的地址)。

要求只是人们停止更新特定的键,而不是完全停止使用数据库。世界上的其他地方可以一直读出地址,甚至可以一直读出您的陈旧地址,最终一致的系统必须最终更新您的地址(假设您不会一直重新提交更新)。最终一致性是键值存储的一致性的可行定义,其中绝大多数操作不冲突,可以合理地期待等待任何不一致。

外面有很多流计算。我将把重点放在一个与我们的一致性研究非常一致的课程上:增量视图维护。增量视图维护是指您定义了视图(本质上是绑定到查询的名称),并希望看到输出答案随着输入数据的更改而更改。

假设您定义了一个可以应用于静态数据集的查询,如下所示。

现在,底层数据可能会发生变化。当他们这样做时,我们应该对输出进行相应的更改。在本例中,我们希望看到数据中的记录计数发生了怎样的变化。

我们可能会编写更复杂的查询。例如,此查询确定其值在所有键中最大的键集:

随着数据的更改,我们希望看到生成的关键点集跟踪最大值。

下一个查询确定每个键的值的标准差,然后选择那些大得出奇的值。

随着数据的移动,当前的离群值集合也会移动,我们将很高兴被警告它们,这样我们就可以采取一些重要的行动。

对于这些查询是否是令人兴奋的计算,我没有强烈的看法,但我们将使用它们作为流计算的示例,这些流计算可能会出现令人惊讶的错误。如果您的计算比这些示例更复杂,您可能会有更多需要担心的问题。

不是很清楚,是吗?即使我们写入的是明确的键,我们希望正确的是所有这些键的聚合,而不是与特定键相关联的值。这个结果取决于所有的价值观。我们仍然可以推断出最终一致性的定义,这意味着如果输入完全停止更改,系统最终将更新为正确的数据记录计数。

虽然您不应该期望在野外看到这种情况,但是只要有任何未处理的输入记录尚未处理,最终一致的流系统肯定可以延迟其处理。

这实际上并不像你想象的那样不合理。许多流处理器故意分批输入以提高效率,并且只有在输入流中获得片刻新鲜空气后才开始工作。此技术允许他们通过批处理和重新排序更新(例如,将所有更新捆绑到同一键),在负载高峰期间提高吞吐量。看到更新无序是很自然的,但最极端的情况是,这种技术的结果是在负载高峰期间没有更新。

虽然这不一定是您在专业流处理器中会看到的,但最终一致性并不会阻止这样的行为。因此,虽然这不是担心最终一致性的最现实的原因,但它描绘了一幅关于我们可能需要注意的东西的图景。

让我们忽略这样一种可能性,即技术上正确的最终一致的处理器可能不会产生任何结果,相反,让我们来看看在不断变化的输入流上,对于更合理的系统会发生什么。

这就是在SQL中表示“argmax”的方式,它大致相当于集合data和select max(data.value)from data之间的连接。

一个通情达理的人可能会期望在这里看到具有最大值的密钥,并让最终一致的系统最终向其显示一些最大密钥。有些摸不着头脑,你可能会回到“任何钥匙”,因为在你看到它们的那一刻,它们可能不再是最大的。但最终我们应该会看到一些钥匙,对吧?

假设data和select max(data.value)from data之间的连接接收到其最终一致的数据输入晚于select max(data.value)from data。这不是不合理的,因为维护最大值可能比维护整个集合(数据)更容易。随着每条数据记录的到达,即使是那些在提交时具有最大值的记录也可能会发现,最大值在到达之前就已经前进了。它们不再与最大值匹配,并且不会作为输出生成。

让我们在差分数据流中演示这一点。我们将不得不伪装一些东西,因为不幸的是,它的一致性保证太强了。幸运的是,我们可以直接将瞬时延迟编程到数据流中。

设想一个集合中可能有多个键,但我们只需要一个键。我们将定期增加与密钥关联的值(可能是使用的带宽、花费的资金、最近的访问或…)。。重要的是,我们将通过更新之间的时间间隔沿一条路径延迟更新。

我们将添加向数据添加元素的更改,一次一个。大致是这样的。

关键字和值并不重要,除了最大值增加之外。如果最大值在与一致性的“最终”性质相关的延迟时间内增加,则我们看不到任何结果:

可以说,我们没有看到任何重要报告。如果我们停止输入流,并允许连接的一个输入赶上另一个,我们最终会这样做。

如果我们延迟最大计算而不是数据流,会发生什么?如果更新覆盖了它们之前的值(即IF(key,2000)覆盖(key,1000)),那么我们也看不到任何输出,因为在最大值到达时,值已经改变。

最终的一致性非常适合于对齐数据的问题,因为这两个数据流中的任何一个的内容都可能会继续移动。在我们的例子中,最大值是有规律地向前推进的,因此当延迟的数据想要查找它时找不到它。或者,最大值经常落后,并且再也找不到产生它的值。也许你会很幸运,而且永远都不会是!

让我们用我能想到的最简单的“统计”例子来做第三次挥杆。在本例中,我们将确定平均值和标准偏差,并查找偏离平均值几个以上的记录。

标准差是方差的平方根,方差是与平均值的平方距离的平均值。

随着数据的变化,平均值也会发生变化,如果必须返回到所有以前的数据来更新与平均值的平方距离,这将是一件烦人的事情。幸运的是,有一种聪明的方法来维护数字流的方差,即使用不同的方差公式:

这两个平均值都可以递增地维护(通过计数和)。这在理论上似乎很好,模一些数值稳定性问题确实运行得相对较好(我们目前在Materialize中使用它)。

但是…。如果聚合不完全同步,会发生什么情况?为了让您体验一下我们即将到来的那种焦虑,请记住方差应该始终是非负的。我们可以确认这一点,因为它的差异项有一个有界的关系:

平均平方总是至少是平方平均值。这确保了方差始终为非负值。

如果沿着不总是一致的数据流路径计算这两个和,则第二项可能大于第一项,并且方差可能明显变为负值。如果你取平方根,…。显然,这不起作用(相关问题:您的流处理器是否能正确地从异常中恢复?)。

但是假设方差实际上并没有变成负值,它只是以令人惊讶的小而告终。我们上面的查询Recall,拉出了比它们应该在的位置多个标准差的记录。如果偏差接近于零,那可能是所有人都高于平均水平。或者,因为平均值暂时很大,也许每个人都超过了这个数字。谁知道呢?

这是流媒体最终一致性的痛点之一:谁知道呢?

如果你想使用上面的信息来做决定,它可能经常是错误的。比方说,您想要等待它正确;您要等待多长时间?如果您想要将一项购买标记为有风险,或者想要接受看似出人意料的优惠,或者想要执行一些其他具有后果的低延迟操作,请使用…。你怎么做到的?您必须在您的“低延迟”系统中内置多长时间才能解决暂时性错误?

在这一点上,有点担心不一致是合理的。如果您的数据并不总是一致,您可以进行一些自然烟雾测试。我想我应该通过它们中的一个来交谈,即使没有其他原因,也只是为了炫耀一些工作正常的东西。

我们经常通过计算集合中每个值的出现次数,将大型集合细化为直方图。我收集了一批纽约市的出租车,记录了每一次的乘车情况,包括乘客数量、车费金额和路程等信息。我们可以合理地对我们看到每对(乘客数、票价)对、或每对(乘客数、距离)或每对(票价、距离)的次数感兴趣。

这些直方图中的每一个都可能是独立有趣的;我们可以计算每对的结果,或者跟踪不同对的数量,或者跟踪计数最大的对。我们可以尝试并确定这三个属性中的任何一个都显示出它们的属性对之间的相关性。

这些直方图中的每一个还在其保留的各个属性上定义直方图。Histogram1和Histogram2都告诉我们每个乘客有多少人。Histogram1和Histogram3都告诉我们每笔车费支付的频率。历史图表2和历史图表3都告诉我们旅行距离的分布。

我们可以将监视器连接到每个错误流,并在出现单个记录时大声抱怨。差异数据流有一个Assert_Empty()方法,它就是这样做的。如果在这些Errors_*集合上运行它,它不会生成这样的报告,因为差异数据流甚至没有瞬时不一致。

最终一致性对于键值存储可能有一定意义,但对于具有低延迟要求的计算似乎没有太大意义。如果您的流处理器只提供最终的一致性保证,那么您应该认真考虑一下您实际希望它为您做些什么。

也就是说,像Noria这样的系统针对维护视图的键控查找,您可能会合理地期望停止更新影响查询结果的记录。这些系统可能会为“分析性”查询提供令人惊讶的结果,但是如果您不打算这样做(或者可以吸收这种复杂性),那么它们的一致性保证对您来说可能没什么问题。

最终,一致性保证归根结底取决于系统计划为您提供多少保证,以及在它和用例之间需要做多少额外的工作。在Materialize,我们打赌你们中的大多数人不想成为一致性专家,也不想要令人惊讶的错误结果。

如果您对不断变化的数据的一致SQL视图感兴趣,请立即查看物化,在下面注册以刺激重复出现的电子邮件内容,查看文档,或者只是匆忙从我们的存储库中抓取代码本身的副本并开始尝试!