Milvus删除分布式集群中的流数据的教程

747 阅读5分钟

以统一的批处理和流处理以及云原生架构为特色,Milvus 2.0在DELETE功能的开发过程中,比其前身带来了更大的挑战。由于其先进的存储-计算分解设计和灵活的发布/订阅机制,我们很自豪地宣布,我们实现了这一目标。在Milvus 2.0中,你可以用主键删除给定集合中的一个实体,这样被删除的实体将不再被列入搜索或查询的结果中。

请注意,Milvus中的DELETE操作是指逻辑删除,而物理数据的清理是在数据压实过程中进行的。逻辑删除不仅大大提升了受I/O速度限制的搜索性能,而且还有利于数据恢复。在时间旅行功能的帮助下,逻辑删除的数据仍然可以被检索出来。

使用方法

我们先来试试Milvus 2.0中的DELETE函数。(下面的例子在Milvus 2.0.0上使用PyMilvus 2.0.0)。

Python

from pymilvus import connections, utility, Collection, DataType, FieldSchema, CollectionSchema
# Connect to Milvus
connections.connect(
    alias="default", 
    host='x.x.x.x', 
    port='19530'
)
# Create a collection with Strong Consistency level
pk_field = FieldSchema(
    name="id", 
    dtype=DataType.INT64, 
    is_primary=True, 
)
vector_field = FieldSchema(
    name="vector", 
    dtype=DataType.FLOAT_VECTOR, 
    dim=2
)
schema = CollectionSchema(
    fields=[pk_field, vector_field], 
    description="Test delete"
)
collection_name = "test_delete"
collection = Collection(
    name=collection_name, 
    schema=schema, 
    using='default', 
    shards_num=2,
    consistency_level="Strong"
)
# Insert randomly generated vectors
import random
data = [
    [i for i in range(100)],
    [[random.random() for _ in range(2)] for _ in range(100)],
]
collection.insert(data)
# Query to make sure the entities to delete exist
collection.load()
expr = "id in [2,4,6,8,10]"
pre_del_res = collection.query(
    expr,
    output_fields = ["id", "vector"]
)
print(pre_del_res)
# Delete the entities with the previous expression
collection.delete(expr)
# Query again to check if the deleted entities exist
post_del_res = collection.query(
    expr,
    output_fields = ["id", "vector"]
)
print(post_del_res)

实施

在Milvus实例中,一个数据节点主要负责将流式数据(日志经纪人中的日志)打包成历史数据(日志快照),并自动刷新到对象存储。一个查询节点在完整的数据上执行搜索请求,也就是流媒体数据和历史数据。

为了充分利用集群中并行节点的数据写入能力,Milvus采用了基于主键散列的分片策略,将写入操作平均分配给不同的工作节点。也就是说,代理将一个实体的数据操作语言(DML)消息(即请求)路由到同一个数据节点和查询节点。这些消息通过DML-通道发布,由数据节点和查询节点分别消费,共同提供搜索和查询服务。

数据节点

在收到数据INSERT消息后,数据节点将数据插入到一个增长段中,这是一个为接收内存中的流媒体数据而创建的新段。如果数据行数或增长段的持续时间达到阈值,数据节点就会将其封闭,以防止任何进入的数据。然后,数据节点将包含历史数据的密封段冲到对象存储中。同时,数据节点根据新数据的主键生成一个Bloom filter,并将其与密封段一起冲到对象存储中,将Bloom filter保存为统计二进制日志(binlog)的一部分,其中包含该段的统计信息。

布隆过滤器是一种概率数据结构,由一个长的二进制矢量和一系列的随机映射函数组成。它可以用来测试一个元素是否是一个集合的成员,但可能会返回错误的阳性匹配。--维基百科

当数据DELETE消息进来时,数据节点会缓冲相应分片中的所有Bloom过滤器,并将它们与消息中提供的主键相匹配,以检索可能包括要删除的实体的所有片段(从增长的和密封的)。在确定了相应的段之后,数据节点在内存中对其进行缓冲,以生成Delta binlogs来记录删除操作,然后将这些binlogs和段一起冲回对象存储。

数据节点的DELETE工作流程

由于一个分片只被分配了一个DML-Channel,添加到集群中的额外查询节点将不能订阅DML-Channel。为了确保所有的查询节点都能收到DELETE消息,数据节点过滤来自DML-Channel的DELETE消息,并将其转发到Delta-Channel,以通知所有查询节点的删除操作。

查询节点

当从对象存储加载一个集合时,查询节点首先获得每个分片的检查点,该检查点标志着自上次刷新操作以来的DML操作。基于检查点,查询节点加载所有的密封段,以及它们的Delta binlog和bloom过滤器。随着所有数据的加载,查询节点会订阅DML-Channel、Delta-Channel和Query-Channel。

如果在集合加载到内存后有更多的数据INSERT消息,查询节点首先根据这些消息来确定增长的段,并在内存中更新相应的仅用于查询的bloom filters。那些查询专用的bloom filters在查询结束后不会被刷入对象存储。

查询节点中的DELETE工作流程

不能订阅DML-Channel的查询节点只允许处理密封段上的搜索或查询请求,因为它们只能订阅Delta-Channel,并接收由数据节点转发的DELETE消息。在从Delta-Channel收集到密封段的所有DELETE消息后,查询节点通过将提供的主键与密封段的Bloom过滤器相匹配来定位实体,然后在相应的段中记录删除操作。

最终,在搜索或查询中,查询节点根据删除记录生成一个比特集,以省略被删除的实体,并在所有段的剩余实体中进行搜索,无论段的状态如何。最后但并非最不重要的是,一致性级别会影响到删除数据的可见性。在强一致性级别下(如前面的代码示例所示),删除的实体在删除后立即不可见。而采用Bounded Consistency Level时,在删除的实体变得不可见之前会有几秒钟的延迟。