我们在《Milvus在大规模向量检索场景下的数据管理》这篇文章说过,当向量数据不断地进入 Milvus 时,系统后台会持续地将插入缓冲区中的数据写入磁盘,形成很多小文件。我们称这些文件为数据段。大量零碎的数据段有两个明显缺点:
- 不利于元数据管理,对SQLite/MySQL的访问频繁
- 索引过于分散,影响查询的性能
因此Milvus后台落盘任务会不断地把这些小数据段合并成大数据段,直到合并后的数据段大小超过index_file_size(默认1024MB)这个阈值。
(一)旧版本合并机制的缺点
在0.9.0版本以前,数据段的合并策略是简单粗暴的:先从元数据拿到一批需要被合并的数据段,然后循环遍历合并。如下图所示:
假设拿到7个数据段,从第一个开始合并到第三个,segment_8已经超过1024MB的大小,就停止对segment_8的合并;接着从第四个合并到第六个,合并为segment_9;合并完成后将前六个数据段标记为软删除,最终剩下三个数据段:segment_7,segment_8,segment_9。
这种合并机制有一个很大的缺点:占用过多的磁盘空间。
《Milvus在大规模向量检索场景下的数据管理》这篇文章介绍过,数据段的删除是分为两个阶段的:软删除和硬删除。当数据段被标记为软删除后,并不会立刻从磁盘中清理掉,而是会保留一段时间,直到后台清理线程将其硬删除,才会被清理掉。因此,在某些特定场景下,这种合并机制会造成巨大的磁盘空间浪费。
假设有一个256维的集合,index_file_size为1024M, 每条向量的大小为1KB。该集合中已存在一个100MB的数据段,客户端以每秒1条的频率插入向量。系统配置的auto_flush_interval为1秒,也就是每秒写一次磁盘。我们来看插入三次过程中后台的合并行为:
经过三次操作后,数据合并为segment_7,大小为 100MB+3KB。而其他六个数据段被标记为软删除状态,但它们的文件仍然在存在于硬盘中,这些文件的总大小超过了400MB。在三次插入以及合并过程中,写入磁盘的数据量为300MB+9KB。可以看到,余留的文件大小是实际数据量(100MB)的4倍,磁盘写入的数据量是实际数据量的3倍。我们称这种现象为“写放大”,根本原因是因为被合并的数据段的大小差距过大。
(二)0.9.x版本的合并策略
1)层级合并策略
为了缓解“写放大”问题,我们在0.9.0中进行了改进。在介绍新的策略之前,我们先来玩一个游戏,游戏名叫做“2048”,可能很多人都玩过,没玩过的戳这里:
玩过的都知道,这游戏里,数字2只可以跟数字2合并,但不能跟其他数字合并。同样的,数字4只能跟数字4合并,数字8只能跟数字8合并,以此类推。
新的合并策略将数据段按大小划分为几层:0MB~4MB,4MB~16MB,16MB~64MB,64MB~256MB,大于1GB的归为一层。合并的时候,仅对层内数据段进行合并,这样就避免了小数据段和大数据段的合并,减少磁盘写入量,减少过大的临时文件。那么我们来看一下在上一节的场景下,使用新的合并策略后,磁盘的使用量有没有缓解:
可以看到,三次插入和合并操作完成后,数据合并为segment_6,但segment_1没有参与合并,其他四个数据段被标记为软删除。磁盘占用量为 100MB+8KB,磁盘写入数据量为 8KB。基本缓解了“写放大”问题。
2)适配合并策略
上面这种分层合并策略是在落盘任务完成之后触发的,我们可以看到对于在不同层级的数据段没有得到合并(比如上面场景中的segment_1和segment_6)。在对集合建索引之前,要尽可能地把数据段合并到index_file_size指定的大小,这就需要另一种合并策略。适配合并策略就是用来做这个事情的,实际上就是按数据段大小往index_file_size上“凑”。比如,对于下面一组数据段,index_file_size为1024MB,适配合并策略的效果如下:
这里最终得到了segment_8、segment_9、segment_10三个数据段,前两个数据段的大小和index_file_size很接近。
(三)新的合并策略的触发条件
合并操作触发的时机有以下几个:
- Milvus服务启动时(适配合并策略)
- 落盘任务完成之后(层级合并策略)
- 建索引之前(适配合并策略)
- 删除索引之后(适配合并策略)
由于“写放大”问题主要出现在持续插入数据以及落盘过程中,因此,仅在落盘任务完成时使用了层级合并策略,其他几个都使用了适配合并策略。
来源:oschina
链接:https://my.oschina.net/u/4209276/blog/4339011