很荣幸咱们最新的论文《Manu: A Cloud Native Vector Database Management System》被数据库畛域国内顶会 VLDB’22 录用。这两天刚好在大会上分享了论文内容。正好趁热打铁写一篇文章,将梳理后的论文内容分享给大家,聊聊背地的设计与思考。
本文将尝试对 Manu(Milvus 2.0 版本代号)这个“面向向量数据管理而设计的云原生数据库系统”中的要害设计理念和准则进行论述。如果想要理解更具体的内容,大家能够参考咱们曾经公布的论文和 Milvus 我的项目的 GitHub 开源仓库[[GitHub 仓库]](https://github.com/milvus-io/…)。
我的项目背景
在最后设计和实现 1.0 版本的 Milvus 时,咱们次要的指标是良好地反对向量治理的相干性能,并对向量检索的性能进行优化。随着和用户交换的不断深入,咱们发现了理论业务中对于向量数据库的一些广泛需要,而这些需要在 Milvus 新近版本的框架之下是难以失去良好的解决的。
咱们能够将这些需要演绎为以下几类:继续变动的需要、须要更灵便的一致性策略、须要组件级的弹性扩大能力、以及须要更简略高效的事务处理模型。
需要仍旧在继续变动
对于解决向量数据而言,业务中的需要目前仍未齐全定型。
业务需要从最晚期的次要对向量数据进行 K 近邻搜寻,逐步演变为范畴搜寻、反对各类自定义间隔指标、向量标量数据联结查问以及多模态查问等越来越多样的查问语义等等。
这就要求向量数据库的架构具备足够的灵活性,可能疾速、麻利的实现对新需要的反对。
须要更灵便的一致性策略
以内容举荐场景为例,业务对于时效性有较高的要求。当新内容产生时,咱们在几分钟甚至几秒钟内将内容举荐给用户,通常是能够承受的。但如果要隔一天或者更久才将内容举荐进来,则会对举荐成果造成较大影响。在这类场景下,如果“死磕”强一致性将会带来较大的零碎开销,但如果仅提供最终一致性,业务成果则难以失去保障。
针对这个问题,咱们提出了一套解决方案:用户能够依据业务的需要,来指定插入数据在可被查问前所能容忍的最大时延。对应的调整零碎在数据处理时的一些机制,来提供对业务的最终成果保障。
须要反对组件级的弹性扩大
向量数据库的各个组件对资源需要的差别很大,在不同的利用场景下,各个组件负载强度也是不同的。比方,向量检索和查问的组件须要大量的计算和内存资源来确保性能,而其余负责数据归档、元数据管理的组件仅需大量资源就能够失常运行。
从利用类型来看的话,举荐类的利用最要害的需要是大规模并发查问的能力,所以通常只有查问组件会承当较高的负载;剖析类的利用经常须要导入大量离线数据,此时负载压力就落在数据插入和构建索引这两个相干组件上。
为了进步资源的利用率,须要让各个功能模块具备独立的弹性扩大能力,让零碎的资源应用更加贴合利用的理论需要。
须要更简略高效的事务处理模型
严格来说,这点不算是需要特色,属于零碎设计上能够利用的优化空间。
随着机器学习模型形容能力日益加强,业务通常会偏向将繁多实体的多种维度的数据进行交融,以一个对立的向量数据进行示意。比方做用户画像的时候,将个人资料、偏好特色、社交关系等信息进行交融。因而,向量数据库能够仅用单表来进行数据保护,而不须要实现相似传统数据库的“JOIN 操作”。这样一来,零碎仅需反对单表上行级别的 ACID 能力,而不须要反对波及多表的简单事务,为零碎中组件解耦和性能优化留出了较大的设计空间。
设计指标
作为 Milvus 的第二个大版本,Manu 的定位是一个面向云原生设计的分布式向量数据库系统。
在设计 Manu 的时候,咱们综合思考了下面提到的各种需要,联合分布式系统设计所须要的常见要求,提出了五个大的指标:继续演进能力、可调一致性、良好的弹性、高可用、高性能。
继续演进能力
为了在性能演进的同时,可能将零碎的复杂性限度在可控范畴之内,咱们须要对系统进行很好的解耦,确保零碎中的性能组件能够较为独立的减少、缩小以及批改。
可调一致性
为了可能让用户指定新插入数据的查问可见提早,零碎须要反对 delta consistency。Delta consistency 要求所有的查问可能至多查问到 delta 工夫单位之前的所有相干数据,而 delta 是能够由用户利用依据业务需要指定的。
良好的弹性
为了进步资源应用效率,须要做到组件级的细粒度弹性,同时也要求资源分配策略可能思考组件对硬件资源需要的差异性。
高可用和高性能
高可用是所有云数据库的根本需要,须要做到零碎在多数服务节点或组件的生效状况下,不影响零碎其余服务的失常运行,同时可能进行无效的故障复原。
高性能则是向量数据库场景下“陈词滥调”的问题了,在设计过程中须要严格的控制系统框架层面所产生的开销,从而保障良好的性能。
零碎架构
Manu 采纳了一个四层的架构来实现对读 / 写、计算 / 存储以及有状态 / 无状态组件的拆散。
如下图所示,零碎架构能够分为 access layer,coordinator layer,worker layer 以及 storage layer 四层。此外,Manu 将日志作为零碎的骨干,用于各组件间的协同和通信。
Access layer
Access layer 拜访层由若干个无状态的 proxy 组成。
这些 proxy 负责接管用户申请,将解决过的申请转发给零碎内的相干组件进行解决,并将处理结果收集整理好之后返回给用户。Proxy 会缓存局部的零碎元数据,并基于这些数据对用户申请进行合法性检查。例如,被查问的数据集是否真的存在。
Coordinator layer
Coordinator layer 负责管理系统状态,保护零碎元数据以及协调各个性能组件实现零碎中的各类工作。
Manu 总共有四种类型的 coordinator。咱们将不同性能的 coordinator 都拆分开来设计,这样一来,一方面能够隔离故障,另一方面也不便各个性能组件进行独立的演进。
此外,出于可靠性思考,每种 coordinator 通常都有多个实例:
- Root coordinator 负责解决数据集创立 / 删除等数据管理类型的申请,并对各个数据集的元数据进行治理。比方,蕴含的属性信息,每种属性的数据类型。
- Data coordinator 负责管理系统中数据的长久化工作,一方面协调各个 data node 解决数据更新申请,另一方面保护各个数据集中数据存储的元数据。例如,每个数据集的数据分片列表以及各分片的存储门路。
- Index coordinator 负责管理系统中数据索引的相干工作,一方面协调各个 index node 实现索引工作,另一方面记录各数据集的索引信息,蕴含:索引类型,相干参数,存储门路等。
- Query coordinator 负责管理各个 query node 的状态,并依据负载的变动调整数据和索引在各 query node 上的分配情况。
Worker layer
Worker layer 负责真正的执行零碎中的各类工作。
所有的 worker node 都是无状态的,它们仅会读取数据的只读正本并进行相干的操作,并且他们之间不须要互相的通信合作。因而,worker node 的数量能够很不便的依据负载的变动进行调整。Manu 应用不同类型的 work node 来实现不同数据处理工作,这样做使得各个性能组件能够依据负载和 QoS 要求的差别独立进行弹性伸缩。
Storage layer
Storage layer 长久化的存储系统状态数据、元数据、用户数据和相干的索引数据。
Manu 应用高可用的分布式 KV 零碎(如 etcd)来记录零碎状态和元数据。在更新相干数据时,须要首先将数据写入 KV 零碎,而后再将其同步到各相干 coordinator 的缓存中。对于用户数据和索引数据等规模较大的数据,Manu 采纳对象存储服务(如 S3)进行治理。对象存储服务的高提早并不会导致 Manu 的数据处理性能问题,因为 worker node 解决数据前会从对象存储中获取对应数据的只读拷贝并缓存在本地,所以绝大部分的数据处理都是在本地实现的。
日志作为零碎骨干
为了更好的解耦各个性能组件,使其可能独立的进行资源弹性调整和性能演进,Manu 采纳了“日志即数据(log as data)”的设计理念,并将日志作为零碎骨干用来连贯各个组件。在 Manu 中,日志被组织为长久化的可被订阅的信息,而零碎中的各个组件则是日志数据的订阅者。
Manu 中的日志内容次要有 WAL(write ahead log)和 binlog 两类。零碎中数据的存量局部被组织在 Binlog 当中,而增量局部则在 WAL 中,两者在提早、容量和老本等方面都起到了互补的作用。
如上图所示,logger 负责将数据写入 WAL,是整个日志零碎的数据入口。Data node 订阅了 WAL 的内容并负责对数据进行解决并存入 Binlog 中。而 query node、index node 等其余组件放弃了互相独立的关系,仅靠订阅日志来同步数据内容。
除此之外,日志零碎还负责了零碎外部组件间的局部通信性能,各组件能够借助日志广播系统外部事件。比方,data node 能够告知其余组件有哪些新的数据分片被写入了对象存储系统,index node 能够告知所有 query coordinator 有新的索引构建实现等。各类信息能够依据其性能类别被组织成不同的 channel。每个组件只须要订阅和本人性能相干的 channel 即可,而无需监听所有被播送的日志内容。
工作流程
接下来,咱们从数据插入、索引构建和向量查问三个方面,介绍 Manu 零碎外部各类工作的工作流程。
数据插入
上图中展现了数据插入相干组件的工作流程。
数据插入申请经 proxy 解决后会被哈希到了不同的分桶当中。零碎中通常有多个 logger 依照一致性哈希的形式来解决各个哈希桶中的数据。每个哈希桶中的数据会被写入一个与其惟一对应的 WAL channel 中。当 logger 承受到申请的时候,会为该申请调配一个全局惟一的逻辑序列号(LSN),并将该申请写入对应的 WAL channel 中。该 LSN 由 TSO(time service oracle)产生,各 logger 须要定期向 TSO 获取 LSN 并保留在本地。
为了保障低提早、细粒度的数据订阅,Manu 在 WAL 中对数据采纳行式存储,并由各订阅组件进行流式读取 。通常 WAL 能够用相似 Kafka 或者 Pulsar 的音讯队列实现。Data node 是订阅了 WAL 的组件之一, 它在获取到 WAL 中更新的数据之后,会将其从行式存储转换成列式,并存入 binlog。列式存储将同一列中的数据间断的存储在一起,这种形式对数据压缩和拜访都更加敌对。例如,index node 须要对某一列向量数据构建索引时,只需从 binlog 中读取该列向量,而无需拜访其余列中的数据。
索引构建
Manu 反对批量和流式两种索引构建形式。当用户对某个曾经有数据的数据集构建索引的时候,会触发批量索引构建。在这种状况下,index coordinator 将从 data coordinator 获取数据集中所有数据分片的存储门路,并调度各个 index node 为这些数据分片构建索引。如果用户在为数据集构建完索引之后持续向数据集插入新的数据则会触发流式索引构建。
当 data node 将一个新的数据分片写入 binlog 之后,data coordinator 会告诉 index coordinator 创立工作为新的数据分片构建索引。不论是以上的哪种形式,index node 构建完索引之后会将其保留到对象存储服务,并将对应的存储门路发送给 index coordinator。而 index coordinator 则会进一步的告诉 query coordinator 安顿相应的 query node 将索引的正本读取到其本地。
向量查问
为了并行的解决数据查问申请,Manu 将数据集中的数据划分成固定大小的数据分片,并将这些数据分片搁置在不同的 query node 上。Proxy 可向 query coordinator 查问数据分片在 query node 上的散布信息,并将其缓存在本地。当收到查问申请时,proxy 会将申请散发到寄存了相干数据分片的各个 query node 当中。这些 query node 会顺次对本地所有相干的分片进行查问,并将后果合并之后返回给 proxy。Proxy 在收到所有相干 query node 的后果之后则会进一步的将后果整合并返回给客户端。
Query node 中数据的起源次要由三个方面:binlog,索引文件和 WAL。对于存量的数据,query node 会从对象存储服务中读取相应的 binlog 或者索引文件。对于增量局部的数据,query node 会间接从 WAL 中流式获取。如果从 binlog 中获取增量数据,将会导致较大的查问可见提早,即数据从实现插入到可能被查问的工夫距离会比拟大,难以满足对一致性要求较高利用的需要。
前文中提到 Manu 为了可能让用户有更加灵便的一致性抉择而实现了 delta consistency。Delta consistency 要求零碎保障在收到数据更新(也包含插入和删除)申请后,最多通过 delta 个工夫单位更新的内容就能够被查问到。
为了实现 delta consistency,Manu 为所有的插入和查问申请都增加了 LSN,并在 LSN 中携带了工夫戳信息。在执行查问申请时,query node 会查看查问申请的工夫戳 Lr 和 query node 解决的最新的更新申请的工夫戳 Ls,仅当两个工夫的距离小于 delta 才能够执行查问工作,否则须要先解决 WAL 中记录的数据更新信息。为了防止用户长时间没有数据更新导致 Ls 绝对以后的零碎工夫太小从而阻断查问的执行,Manu 会定期的向 WAL 中插入特定的管制信息强制 query node 更新工夫戳。
性能评估
咱们在论文中结合实际应用场景对系统进行了综合的性能评估,在这里本文仅列出局部后果。
这张图中,咱们比拟了 Manu 和其余四个开源的向量检索系统(已匿名)的查问性能。能够看出 Manu 在 SIFT 和 DEEP 两个数据集上向量检索性能相比其余零碎均有显著劣势。
这张图中,咱们展现了 Manu 在不同 query node 数量时的查问性能。能够看到在不同数据集以及不同类似度指标下,Manu 的查问性能和 query node 数量都出现了近似线性的关系。
最初这张图中,咱们展现了用户在抉择不同的一致性要求下 Manu 的查问性能。图中的横坐标即为 delta consistency 中 delta 的值,不同的图例代表了向 WAL 中发送管制信息强制 query node 同步工夫的频率。从图中能够察看到 随着 delta 的增大,Manu 的查问提早疾速降落。因而用户须要依据本身利用对性能和一致性的要求抉择适合的 delta 值。
总结
最初,咱们来总结一下。
本次论文次要介绍了利用对向量数据库的非凡需要,以及论述了 Manu 零碎的设计理念、要害性能的运行流程。次要设计理念有两个方面:
- 将日志作为零碎骨干连贯零碎中各个组件,为各组件的独立弹性、性能演进以及资源和故障的隔离提供了较大的便当;
- 基于日志和 LSN 实现了 delta consistency,使得用户能够更加自在的在一致性、老本和性能之间进行抉择。
咱们此次 VLDB 论文中 最次要的奉献是介绍了用户对向量数据库的理论需要,并相应的设计了一个云原生向量数据库的根本架构。当然,目前这个框架下依然存在不少值得摸索的问题,例如:
- 如何对多个模态的向量数据进行联结检索;
- 如何更好的利用包含本地磁盘、云盘以及其余存储服务在内的云存储服务设计高效的数据检索计划;
- 如何利用 FPGA、GPU、RDMA、NVM、RDMA 等新型计算、存储和通信硬件设计极致性能的索引和检索计划等。
写在最初
最早产生写这篇文章的想法还是在一年前的时候,我和公司 CEO 星爵刚在西安加入完 SIGMOD,正筹备回上海加入 Milvus 2.0 GA 版本的发布会。谈起这次参会的感想,我和星爵都感触到云原生数据库正逐步成为了学术界钻研新的热点。刚巧 Milvus 2.0 就是咱们面向向量数据管理而设计的云原生数据库系统,于是咱们自然而然的产生了写下这篇文章的想法。
心愿咱们的工作可能起到一个抛砖引玉的作用,让更多的学者和业界的敌人可能和咱们一起对相干的课题进行摸索和钻研。
此外,这篇论文是由 Zilliz 团队和北方科技大学的数据库团队一起单干实现的,在此感激唐博老师、晏潇老师和向隆同学在工作中的奉献。
如果你感觉咱们分享的内容还不错,请不要悭吝给咱们一些激励:点赞、喜爱或者分享给你的小伙伴!
流动信息、技术分享和招聘速递请关注:https://zilliz.gitee.io/welcome/
如果你对咱们的我的项目感兴趣请关注:
用于存储向量并创立索引的数据库 Milvus
用于构建模型推理流水线的框架 Towhee