无论是存算分离还是存算一体,client对于查询的正确性要求都是一致的,没有哪个客户会因为所谓的“架构优势”牺牲正确性,即使是ANN这样的‘近似查询’。而对于存算分离的架构,由于“存”和“算”发生的进程是不同的,那么如何保证数据的完整性&&一致性就是一个相比于存算一体更复杂的问题。本文从这个问题出发,介绍milvus是怎么在存算分离架构下保证查询数据的完整性,一致性和实时性的。本文涉及到一些前置知识,如果对读者造成困惑,可以参考MrPresent-Han:Milvus 存算分离系列-1:milvus架构简介
在讨论数据完整性之前,我们首先要明确数据实时更新带来的困难。假如数据没有实时写入,只是批量导入,那么“一存多用”就可以简化为下载+复制的问题,从而保证极佳的扩展性和相当低的复杂度。但问题是,数据库不是数据湖,绝大多数用户都不能接受小时甚至分钟级别的数据可见延迟,有些用户甚至要求新写入的数据立即可见,这就给存算分离架构带来了额外的复杂性。
这个问题的答案有2点,第一是target机制,第二是存算双写。
如前文所言,在milvus中,segment相当于是一张表的水平切片,同时也是分布式的最小unit。而milvus的target,可以暂时简单理解为“可读segment的集合”。
如上图所示,随着时间线的推进,client不断插入数据,在storage侧生成新的segment,由于target是immutable的,所以新的target会生成。mixCoord会定期检查target状态,当发现存在新的target的时候,就会命令compute节点load新的segment以供client查询,从而保证新写入的数据能被client正确查询到。
如上图所示,似乎target机制的推进和更新是一件非常简单的事情, 只要compute节点把object storage上的新的segment文件download下来就可以了,但实际上并没有着么简单。主要的难点有2处:
本文先说compaction
基本上lsm的storage设计都得接受compaction引入的额外复杂性。由于生成的segment是immutable的,那么小文件的控制和delete apply这两个关键问题都要依赖compaction。而在考虑compaction的情况下,target机制就没有上图展示的那么简单了。
如上图所示,在target3-target4的这个时间段内,segment1和segment2被compact成了segment4,而且数据写入又新生成了segment5。此时,对于client来说,有效的target=[segment3, segment4, segment5], 不应该包括segment1和2,否则会有结果数据重复的错误和重复计算的资源浪费,因此需要对segment1和segment2进行卸载并对segment5进行加载,从而完成整个target更新。
在复杂系统的设计过程中,一个很头疼点的点就是状态转换设计,只有系统的状态转换正确,一致性&&可用性才能有保证。而在状态转换过程中,Todo和Done状态往往是比较可控的,但是在两者之间的Doing状态,却是系统最容易出错甚至挂掉的点。那么就Milvus Target机制而言,比较典型的中间状态如下图。
首先是‘新上旧未下’的情况,即在某个时间点,新生成segment4已经加载了,但是segment2还没卸载,此时client会看到segment2上冗余的数据。
然后就是相反的情况,旧segment1和2都已经卸载了,但是新的segment4还没加载,此时client查询会发现少数据。
以上这两种情况,其实都是“Doing”问题,解决这种Doing问题可以采用2个点,第一是顺序,第二是commitPoint。那么在milvus的target机制中,我们如下设计。
部分读者可能已经发现上文的一个漏洞,到达commitPoint的时候,旧的segment还没有卸载掉,所以segment事冗余的,那么如何避免client读到旧的segment呢?答案是在target和segment之间建立targetVersion关系。
如上图所示,当mixCoord发现已经到达commitPoint的时候,会将仍然在target4包含的segment3-4-5对应的targetVersion更新为t4, 而segment1-2因为已经不在target中了,所以其对应的targetVersion没有更新,仍然是target3。当client查询发生的时候,当前可用的targetVersion是t4,所以query会忽略segment1和segment2,从而避免查询到冗余的segment。
注意,为了方便理解,部分细节被简化省略掉了。比如熟悉Milvus源码的同行可能知道,target的推进并不是mi xCoord直接指示querynode完成的,而是通过delegator进行的。这种细节并不影响整体理解,所以本文及后续系列文章会选择性地省略掉类似细节。
本文主要介绍了Milvus在存算分离架构下的target推进机制,在实际系统构件和运维过程中,还有很多其他的中间状态和细节问题需要解决,例如多副本,负载均衡,资源隔离。这些细节问题我们会在后续文章中一一补充。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。