论文《向量数据库中的目录感知查询与维护》的疑难杂症

本文精读论文《Directory-Aware Query and Maintenance in Vector Databases》,聚焦其核心创新:将目录层级关系作为向量数据库内建能力,提出解耦于ANN索引的可插拔目录语义层。该层以TRIEHI树结构组织目录骨架,用Roaring Bitmap压缩存储条目ID集合,通过entry-ID统一关联向量、目录和标量三层数据。查询时先由目录层圈定候选ID集合,再交由ANN在子集内执行相似性排序,保障范围正确性与性能可扩展性。

论文精读:《Directory-Aware Query and Maintenance in Vector Databases》-优快云博客

1、openviking 数据库里用的就是 triehi 树结构吗

不是。更准确的说法是:OpenViking 借鉴了 TRIEHI “先圈定目录 scope、再在 scope 内排序” 的设计思想与对外能力,但开源代码中并没有实现论文里那棵 TRIEHI trie 索引。

我把开源仓库 main 分支的源码翻了一遍,三层要分开看。

一、论文里的 TRIEHI 是什么(研究原型,跑在内部 C++ Viking 引擎)

Section IV.A 的 TRIEHI 是一套独立于 ANN 的前置候选集索引:

  • 每个目录是一个 TrieNode,节点存 Inc(v) = Local(v) ∪ ⋃ Inc(child(v)),即本节点条目并上所有后代条目的 inclusive entry-ID 集合(论文式 1);
  • 递归查询走树遍历,直接拿到目标子树的聚合 ID 集,不枚举后代;
  • MOVE 做子树重连、MERGE 局部调和,只更新受影响祖先的聚合;
  • 配合 Roaring Bitmap 做集合运算,与 PG/IVF 两种 ANN 解耦。

二、开源 OpenViking 里实际落地的是什么

开源代码里对应的只是一个过滤表达式节点 PathScope,不是 trie:

  • openviking/storage/expr.py:62:class PathScope 只有 field / path / depth 三个字段,自述 "Path prefix scope expression with optional depth control"(路径前缀范围表达式)。
  • 本地 cuVS 后端 cuvs_index.py:243 _path_matches:判断逻辑就是字符串前缀 ——value_path.startswith(expected_path + "/"),再算相对层级深度,没有树、没有节点聚合集、没有遍历。
  • 远程火山引擎后端 volcengine_collection.py:207:把它翻译成 op=prefix 的普通标量前缀条件下发(服务端是否另有优化属于闭源 VikingDB,开源侧看不到)。
  • 全仓库 Python/Rust 源码里搜不到 trie 节点、per-node inclusive set 这类结构。

也就是说,开源实现本质是URI 前缀的标量过滤(post-filter),和论文里 "trie 前置聚合候选集" 是两种机制。

三、对比小结

维度论文 TRIEHI(内部 C++ 原型)开源 OpenViking 的 PathScope
数据结构物理 trie,节点存 inclusive entry-ID 集一个 filter AST 节点(path + depth)
递归取候选树遍历读节点聚合集URI 字符串 startswith(prefix) 逐条过滤
DSM 维护子树重连、局部 MERGE、祖先聚合更新无对应索引维护逻辑
与 ANN 关系前置给出候选集,ANN 只排这个集前缀条件随标量 filter 下发
代码位置未开源(内部 Viking 引擎)expr.py / cuvs_index.py / volcengine_collection.py

四、为什么会这样

论文实验跑在内部 ByteDance Viking 引擎(C++,带 PG/IVF、Roaring Bitmap、trie 索引);开源 OpenViking 是面向 agent 的产品(Python/Rust + cuVS / 远程 VikingDB),只把 "目录递归检索、先 scope 后排序" 这个对外能力做出来,底层用现成的标量前缀过滤实现,没有移植那套研究型 trie 索引。README 的 Research 区说 "TrieHI, which OpenViking integrates to resolve directory scopes before vector ranking",指的是集成了这个设计思想和 scope 解析职责,不是照搬索引结构。

2、论文的研究目标是把原生目录语义作为向量数据库的一等能力来研究。意思是让向量数据库中的向量有目录层级吗

先纠正一个容易混的点:向量本身不分级,条目归属目录

论文里带 embedding 的是 entry(条目:文档 / 记忆块 / 代码文件),目录是独立于向量的 scope(范围)抽象。层级关系存在于目录与目录之间(父子包含),条目挂在某个目录下,向量本身没有 "层级"。所以准确说法是:被检索的条目被组织进一棵目录树,而不是向量向量本身有层级。

"是不是让向量有目录层级?"——只是表面像。

确实,东西被放进了文件夹里;但论文真正的重点不是 "给每张卡片写个文件夹名"(这个现在就能做到),而是:

让数据库自己认识这些文件夹的上下级关系,自己会圈范围、自己会搬家,不用程序在外面手动干这些。

一句话区分

  • 存一个 path=/HR/Policies/benefits.pdf 字段,查询时 LIKE '/HR/%' 过滤 → 这是扁平标量属性,论文认为不够;
  • 数据库内部知道 /HR/ 是一个可递归展开的 scope、能在排序前给出候选集、并能原子地把整个子树 MOVE/MERGE → 这才是论文说的 "原生目录语义一等能力"。

所以你的理解可以修正为:研究目标不是给向量 "贴文件夹标签",而是把目录的层级包含关系、范围查询和结构维护,从应用层搬进向量数据库内核,做成与 ANN 排序解耦的一等算子。

3、形式化定义两个核心算子,并在一个解耦于 ANN 索引的 "可插拔目录语义层" 上实现、对比三种方案。解耦于 ANN 索引是什么意思

圈范围和搜内容分成两个独立零件

  • ANN 索引:数据库里真正做 "意思相近搜索" 的那个零件(论文里试了两种:PG 图索引、IVF 倒排索引)。
  • 目录语义层:加在搜索零件前面的一个独立小模块,专门负责 "先把范围圈出来"。
  • 解耦 / 可插拔:这两个零件分开、互不绑定 ——
    • 换搜索零件(图索引 ↔ 倒排索引),圈范围的模块不用改;
    • 圈范围的模块自己换一种做法,搜索零件也不用动。

就像文件柜里 "负责圈抽屉的人" 和 "负责在抽屉里翻找的人" 是两个人配合,各干各的;你换个翻找的人,圈抽屉的人照常干活。论文把这条单独列为一条要求:目录层不去改 ANN 算法本身。

4、层级语义也是存在向量数据库吗

分三层说,关键是把 "路径字符串" 和 "层级关系" 分开

三样东西,存放位置不一样

  1. 向量(内容的数值表示):存在库里的 ANN 索引里,负责 "意思相近"。
  2. 路径字符串(如 /HR/政策/福利.pdf):现在就作为条目的一个普通字段存在向量库里。
  3. 层级关系(谁是谁的上级、这个文件夹包含哪些子文件夹和条目):这才是你问的 "层级语义"。

现在:层级关系不在向量数据库里

库里只存了那串路径文字,但 "HR 下面有哪些子文件夹、每个子文件夹管着哪些条目" 这套上下级关系,是放在数据库外面的(应用自己维护的一份目录记录,或文件系统)。

库看到 /HR/政策/ 只当一串普通字符,并不认识它是层级;圈范围、搬家都得靠库外那本登记簿来算。

论文主张:把层级关系也收进数据库内部管

TRIEHI 就是在库里单独维护一棵目录树,每个节点记下它自己+后代包含哪些条目。这样圈范围、搬家都由库自己完成,不再依赖库外登记簿。

一个细节:层级不混进向量数值里

即使收进库,层级也是存在数据库单独的目录 / 元数据层,和存向量的 ANN 索引分开(就是前面说的 "解耦、可插拔"),不会把树结构塞进向量本身。

打个比方

  • 向量 = 每张卡片的内容编号;
  • 路径字符串 = 卡片上手写的一行地址;
  • 层级关系 = 文件夹之间的装订关系。

现在装订关系记在柜子外面的一本登记簿上,柜子里只有卡片上写的地址字;论文想把这本登记簿也搬进柜子,让柜子自己认得装订结构。

对象层级关系存哪是不是在向量数据库里
现在的普通向量数据库(现状)存在库外(应用自己的目录记录 / 文件系统),库里只有一行路径字符串不是
论文提出的 TRIEHI 方案(它自己实现并测了)存进库内部,单独的目录树 / 元数据层是
开源 OpenViking 产品只存 URI 路径做前缀过滤,没有完整目录树只做到一半

一句话:路径文字现在就在库里;真正的层级关系,现在在库外,论文主张把它也存进库内部单独管理。

5、当层级关系存进向量数据库内部时,也是以向量形式吗

不是。层级关系完全不用向量形式存,它和向量是库里分开的两套东西。

库里其实存了三类东西

  1. 向量:条目内容的数值表示(一串浮点数),只存在 ANN 索引里,用来算 "意思相近"。
  2. 层级关系:目录树结构 + "这个节点管哪些条目编号",存在单独的目录 / 元数据层,是树和编号名册,不是向量。
  3. 普通标量字段:路径、类型、时间这类文字 / 数字。

论文里层级到底存成什么

TRIEHI 的每个树节点存的是条目编号集合(用 Roaring Bitmap 压缩的 ID 位图),记录 "本节点自己的条目 + 所有后代条目"。全程是拓扑关系和编号,没有一个浮点数向量,也不做距离计算。

一条查询怎么把两层串起来

以 "搜 HR 文件夹(含子文件夹)里意思相近的内容" 为例:

  1. 层级层(不看意思):在目录树上走一遍,查出 HR 这一摊有哪些条目编号;
  2. 向量层(才看意思):把这批编号交给 ANN 索引,只在它们里面算相似度、排序。

第一步纯靠树和编号,第二步才动用向量。这也是论文强调 "目录层和 ANN 索引解耦" 的原因 —— 两套各干各的。

回到类比

  • 向量 = 卡片的 "意思编号",用来比像不像;
  • 层级树 = 夹子的装订关系 + "这个夹子里夹了哪几张卡片的编号",是张名册。

名册不需要变成意思编号,它只负责告诉你 "先看哪几张卡",比像不像交给后面的向量去做。

一句话:向量管 "意思像不像",层级管 "先看哪一摊",后者是树和编号集合,不是向量。

6、上一个问题你说库里其实存了三类东西,这三类怎么联系起来,以什么形式存在库里面

联系三类东西的纽带只有一个:全局统一的条目编号(entry-ID)

库里所有东西都挂在同一个 entry-ID 上,三层各自存、靠这个号对起来。

一、先有一个 "条目" 作为主体

每个条目 = 一个唯一编号 ID + 一段向量 + 几个标量字段(路径、类型、层级等)。这个 ID 就是论文里的 entry identifier,是向量数据库索引的逻辑载荷。三类存储都拿这个号互相找对方。

二、三层分别以什么形式存在库里

第 1 层:向量层(ANN 索引)

  • 形式:浮点数组,组织成图(PG)或倒排表(IVF),按 ID 存放。
  • 只管一件事:给它一批 ID,在这些向量里算相似度、排个序。

第 2 层:目录层(独立的元数据层,和 ANN 分开) 这一层不存向量,只存 "编号和目录的关系",用压缩的 ID 集合(Roaring Bitmap 位图)表示。三种方案形式不同:

表格

方案目录层存什么(p.4 /p.6)
PE-Online① 条目→直接父目录映射;② 路径→直接下属条目集合的倒排表;③ 辅助目录索引(全部路径 key,支持前缀枚举、查直接子目录)
PE-Offline条目→父目录;每个祖先路径→该目录及所有后代条目的物化倒排表
TRIEHI一棵目录树:每个节点存 段名 segment、子节点 hash 表 children、父指针 parent、inclusive_ids(本节点+后代的 ID 位图,式 1);另配一张 entry-to-node 目录记每个条目的父节点

第 3 层:标量字段

  • 形式:条目记录里的普通字段(path、type、level、时间等),按 ID 附着在条目上。

三、一次查询怎么把三层串起来(以递归搜 HR 为例)

  1. 目录层:用树 / 倒排表查出 HR 这一摊的候选 ID 集合(这一步不看意思,只看编号归属);
  2. 向量层:把这批 ID 交给 ANN,只在这些 ID 的向量里算相似度、排序;
  3. 标量层:按排好的 ID 取回路径、内容等字段展示。

四、打个比方

  • ID = 每张卡片的统一编号;
  • 向量层 = 按编号放的 "内容意思档案柜";
  • 目录层 = 按编号记的 "哪张卡在哪个夹子" 的名册 / 树;
  • 标量层 = 卡片上写的地址、署名。

查的时候先翻名册圈出一叠编号,再到意思档案柜里比像不像,最后按编号把卡片内容取出来。三层各存各的,全靠编号对上。

一句话:条目是主体,ID 是公共钥匙;向量以浮点数组存、目录以树 / 倒排表+ID 位图存、属性以标量字段存,三者用同一个 entry-ID 关联。

7、我迷糊了,我一直认为向量的每一维是指它的标签属性,实际上是什么。我一直认为它的任一标量字段也可以表示为向量的某一维

你记的是 "老式手工特征向量",现代向量库里的 embedding 是另一回事,这是两个概念

一、你以为的:每一维 = 一个具名属性

这是传统机器学习的手工特征向量:比如把 "颜色" 编一维、"价格" 编一维、"类别" 编一维,每一维人都知道代表什么,标量属性确实能直接摆进某一维。

但现代 RAG / 向量数据库里的向量不是这样。

二、实际的:embedding 向量每一维是模型吐出来的抽象语义坐标,不对应任何属性

一篇文档(比如 "HR 福利政策")被 bge-m3 这类模型整体压成一串 1024 个浮点数:

[ 0.021, -0.884, 0.137, 0.502, -0.091, ... 共1024个数 ]
  • 这 1024 维里没有一位叫 "部门"、没有一位叫 "价格"、也没有一位叫 "路径";
  • 每一维是神经网络内部学出来的抽象坐标,人看不懂它具体代表什么;
  • 把两篇文档的向量比距离(cosine),反映的是 "内容意思相不相近",不是属性等不等。

三、标量字段是另一套独立存在,不能塞进某一维

像下面这些,是存在条目记录里的具名精确字段,跟那串 1024 维向量并排放在一起,互不混入:

path  = /HR/政策/福利.pdf
dept  = HR
type  = file
date  = 2024-05-01
  • 向量负责模糊:"意思和医疗保险相近的有哪些";
  • 标量负责精确:"只要 HR 部门的"" 价格小于 100";
  • price=100、dept=HR 这些不会、也不能变成向量里的某一位,因为向量位没有显式含义,塞进去也没法比较。

四、放一篇具体文档看两套表示

文档《公司医保报销办法》:

表格

表示长什么样干什么用
向量(1024 维浮点)[0.02, -0.88, 0.13, …] 抽象坐标找意思相近的
标量字段{path, dept, type, date}精确条件过滤

查询 "医保怎么报销"→ 走向量找意思近;限定 "只看 HR 目录"→ 走标量 / 目录名册圈范围。论文做的就是后者这一步:先圈范围,再让向量在范围内比意思。

一句话

老式特征向量:一维对应一个属性;现代 embedding:一长串无显式语义的抽象坐标,属性是单独存的标量字段,两者是一枚条目的两套独立表示,不互相充当维度。 你把这层分开,前面问的 "三类东西怎么联系" 就通了。

8、回到正题,目录层的数据结构是不是数组或链表,编号存在二者之一里面

都不是。目录层要拆成 "骨架" 和 "编号集合" 两部分看,它既不是数组也不是链表。

一、先分清两个部分

目录层 = 目录骨架(树 / 表,管上下级) + 每个范围里的编号集合(管 "有哪些条目号")。这两部分用的结构不一样。

二、骨架部分:TRIEHI 是一棵树,不是数组 / 链表

论文 TRIEHI(p.6)的每个节点(TrieNode)存四样:

  1. segment:本级目录名;
  2. children:一个 hash map(字典),从子目录名直接查到子节点;
  3. parent:指向父节点的指针;
  4. inclusive_ids:本节点+后代的条目编号集合。

骨架形态是一个节点分叉出多个子节点的前缀树,子节点靠字典登记,靠 parent 指针往回走 —— 不是排成一排的数组,也不是一根线串到底的链表(链表只有前后一个方向,表达不了一个文件夹下面挂多个子文件夹的分叉)。

两种旧方案 PE-Online / PE-Offline(p.4–5)连树都不用,就是几张映射表 + 倒排表:条目→父目录、路径→条目编号清单。

三、编号集合部分:Roaring Bitmap 压缩位图,不是裸数组 / 链表

论文 Implementation Details(p.9)明确:entry-ID 集合用 Roaring Bitmaps [39] 存储,用来做压缩的并集、交集、差集运算。

它不是把编号一个接一个塞进数组,也不是用链表节点串。简单说它是一张精简版编号花名册:把编号按段切块,连续的号用位图(一位代表一个号在不在)表示,稀疏的块用数组补,整体压缩,而且并 / 交 / 差算得很快。

四、三种方案目录层结构对照

表格

方案骨架编号集合
PE-Online条目→父目录映射表 + 路径→直接下属倒排表 + 路径辅助索引倒排表里的编号清单(位图压缩)
PE-Offline同上,另加祖先路径物化倒排表每个祖先路径对应的编号清单(位图压缩)
TRIEHI前缀树:节点 + children 字典 + parent 指针每节点的 inclusive_ids,Roaring Bitmap

五、为什么不用数组或链表

  • 数组按位置取号方便,但 "按目录名找子节点"" 整段子树编号合并 " 都不擅长;
  • 链表插入删除方便,但查一个名字要从头遍历,且做编号并 / 交 / 差很慢;
  • 目录层高频操作是按名字找子级、整集合并 / 差、省空间,所以用树(骨架)+位图(编号集)。

一句话:骨架是树(节点挂字典)或映射表,编号是 Roaring Bitmap 压缩位图;数组和链表都不是这里用的结构。

9、什么是Roaring Bitmap 压缩位图?

一、先从普通位图(Bitmap)讲起

把所有可能的编号排成一排,一个编号占一个开关位,1 = 在集合里,0 = 不在。

假设编号范围是 0~9,集合 S={3,7,8},位图就是一排 10 个开关:

编号:  0  1  2  3  4  5  6  7  8  9
位:    0  0  0  1  0  0  0  1  1  0

判断 "3 在不在",直接看第 3 位是不是 1,O (1);两个集合合并就是对应的位做 OR,取交集做 AND,非常快。

痛点:如果编号到几百万甚至上千万,纯位图要占上千万个开关位,而很多位一直是 0(稀疏),浪费严重。

二、Roaring Bitmap 的做法:切块,稀疏用数组、稠密用位图,混合存

Roaring("咆哮")把整个编号空间按固定大小切成很多小块(标准每块管 65536 个号),每块单独看密度决定怎么存:

  • 这一块里编号很少(稀疏):不用位图,直接用一个数组把这几个号列出来,比如 [3,7,8,250],更省;
  • 这一块里编号很多(稠密):才转成这一小块的位图,用开关位表示。

块头只记 "这一块用数组还是位图"。这样既避免稀疏块浪费,又保留稠密块位运算快的优点。

三、结合一个具体例子

假设一块管 0~9(简化),有两个目录子树:

节点A的ID集 = {3, 7, 8}
节点B的ID集 = {7, 250, 300}
  • 稀疏时:直接数组存 [3,7,8]、[7,250,300],几个号就几格,不铺满地开关。
  • 稠密时(一块里上万个号都在):换成位图,几万个号用一片开关位表示,合并就是位 OR。

两个集合合并(递归圈范围):

A OR B = {3,7,8,250,300}   ← 重复的 7 自动去重

求差(只要本层、排除子树):

A AND NOT B = {3,8}         ← 7 被减掉

四、回到论文里它用在哪

TRIEHI 每个节点的 inclusive_ids(本节点+后代的条目编号集)就用 Roaring Bitmap 存:

  • 递归查询:沿树把各子节点的集合 OR 起来,一次拿到整棵子树的所有候选 ID;
  • 非递归查询:本层集合 AND NOT 掉直接子树集合,留下 "只属于本层、不含子级" 的 ID;
  • 这些并 / 交 / 差都在位图 / 数组块上直接算,不用逐个编号去重或比对。

一句话

普通位图是 "一个号一个开关",快但稀疏时浪费;Roaring Bitmap 把号切块,稀疏块用数组、稠密块用位图,既省空间又让并 / 交 / 差运算很快,论文就用它存目录节点里的条目编号集合。

10、数据结构中,树和图不是用数组或者链表表示吗,如果不是,帮我复习一下

先点破混淆点:树 / 图和数组 / 链表不在同一个层面,它们不互斥

你把两类东西放一起比了。正确的分法是两层:

  • 逻辑结构:元素之间是什么关系 —— 线性、树(一对多)、图(多对多)、集合。
  • 存储结构(物理实现):这些关系在内存里到底怎么摆 —— 数组(顺序)、链表(链式)、hash 表、索引。

一种逻辑结构,可以选多种存储结构来实现。 树当然可以用数组实现,也可以用链表实现,这正是下面要复习的。

一、逻辑结构(讲 "关系")

逻辑结构关系例子
线性一对一,排成一串数组、链表、栈、队列
树一对多,一个父节点挂多个子节点,有唯一根、无环文件夹目录、家族树
图多对多,节点任意互连社交网络、路网
集合元素间无关系哈希集合

二、存储结构(讲 "怎么摆进内存")

存储结构特点
顺序存储(数组)连续内存,按下标直接取
链式存储(链表 / 指针)节点离散存放,靠指针 / 引用连起来
散列(hash 表 / 字典)按键直接定位
索引额外建一张键→位置的对照表

三、同一棵树 / 一张图,分别能怎么存(这就是你要的复习)

树的两种常见实现:

  1. 数组(顺序):二叉树层号存在数组里,根在下标 1,节点 i 的左孩子 2i、右孩子 2i+1。
  2. 链表 / 指针(链式):每个节点存数据 + 指向孩子的指针。多叉树常用 "孩子链表":节点挂一串孩子;或 "孩子兄弟表示法" 用两个指针模拟。
  3. 字典(hash):多叉树 /trie 最常用,children = {"HR": 节点A, "Finance": 节点B},按键名直接拿子节点。

图的两种常见实现:

  1. 邻接矩阵(二维数组):A[i][j]=1 表示 i 和 j 相连。
  2. 邻接表(数组 + 链表):一个顶点数组,每个顶点后面挂一条边的链表 / 列表。

四、回到论文 TRIEHI,把上一轮的话说精确

TRIEHI 在逻辑上是一棵树(目录一对多分叉);在物理上它不是靠单一数组或单链表摆出来的,而是:

  • 节点是内存对象;
  • children 用 hash 字典挂子节点(按目录名找,比遍历链表快);
  • parent 是指针引用;
  • 每个节点的 inclusive_ids 编号集用 Roaring Bitmap(压缩位图,内部混合了位图块和数组块)。

所以我上一轮说 "不是数组或链表",准确含义是:它的主关系和编号集没有用裸的线性数组 / 单链表来表示,而不是说树能脱离内存容器存在 —— 树本身当然要靠某种容器落地,这里选了字典 + 位图。

一句话

树、图是 "关系长什么样",数组、链表、hash 是 "内存怎么摆";前者选后者来实现,二者是 "逻辑 vs 物理" 的搭配关系,不是二选一。 论文的树逻辑 + 字典 / 位图物理,正是这种搭配的一个实例。

11、没懂字典和索引

字典(hash 表 / 字典 / Map):按键名直接拿东西,不用一个个翻

类比:手机通讯录。

你想找 "张三" 的号码,不用从第 1 个联系人往下翻到底,而是在搜索框输入 "张三",直接跳到他那一条。这就是字典的工作方式:存的是一对一对 键 → 值(key → value),给键,立刻拿到值。

字典 children:
"HR"      → 节点A
"Finance" → 节点B
"Legal"   → 节点C

给一个键 "HR",直接取出节点 A,不用遍历这一串(链表才需要从头翻)。

论文里用在哪:TRIEHI 每个节点的 children 就是个字典 —— 子目录名当键,子节点对象当值。你说 "进到 HR",字典立刻给你 HR 那个节点,而不是把所有子目录挨个比一遍名字。

二、索引:预先建一张 "键 → 位置" 的对照表,免得每次翻全量

类比:一本书末尾的 "关键词索引"。

书几百页,你想找 "报销" 出现在第几页,不用逐页读,直接翻到书末索引,它写着 "报销 → 第 12、47、88 页",按页码直接去。这张额外的对照表就是索引 ——为了快速查找,事先单独建好的一张清单。

再比如图书馆:不把所有书摊地上找,而是先查书名卡片柜(索引),卡片告诉你这本书在哪个书架第几层。

论文里用在哪(PE-Online,p.4):

  • entry-to-directory mapping:条目号 → 它的父目录,一张对照表;
  • 路径倒排表:路径 key → 这个目录下有哪些条目号;
  • auxiliary directory index:把所有路径 key 预先登记好,支持 "列出某前缀下的子目录",不用每次扫全部条目。

这些都是为了查得快,事先建好的对照表,本身不存向量内容,只存 "键对应在哪"。

三、字典和索引怎么区分(容易混)

  • 字典是一种具体实现:靠哈希把键直接定位到值,查找平均一步到位;
  • 索引是一种思想:任何 "预先建键→位置对照表来加速查找" 的结构都叫索引,字典可以是索引的一种实现,倒排表、书末目录也都是索引。

简单记:字典强调 "按键直接取",索引强调 "预先建对照表免翻全量";论文里 children 是字典,路径对照表 / 倒排表是索引。

一句话

字典 = 按名字直接取号的通讯录;索引 = 书末那张 "关键词→页码" 的预先对照表。 两个都是为了 "不用挨个翻",论文用前者挂子节点、用后者加速查目录和条目。

12、论文是先在目录层找文件,再在向量数据库中 ann 找相似吗

是的,顺序就是:目录层先圈出候选条目集合,ANN 只在这个集合里比相似、排序

文字版流程(对应论文 Fig.4 的 Recursive DSQ workflow)

查询请求(带目录范围 + 语义问题)
        ↓
① 目录层:按目录条件圈出候选 entry-ID 集合(这一步不看意思)
        ↓
② ANN:只在这批编号的向量里算相似度、排序
        ↓
③ 按排好的编号取回条目内容,返回 top-k

这正是论文第一条要求 scope correctness:目录范围必须在向量排序之前解析好(p.3、p.9 的 directory-only latency 就是单独测量第①步的时间)。

用词修正一下:不是 "找文件",是 "圈编号集合"

第①步并没有去打开、读取文件内容,而是目录层根据 /HR/ 这种范围条件,算出 "这次允许参与的是哪几个条目编号"(TRIEHI 走树读 inclusive_ids,PE-Online 枚举后并表,PE-Offline 一次查祖先表)。ANN 拿到的只是这批编号。

关键区别:论文反对 "先全库搜、再过滤"

另一种常见做法(扁平 scalar filter)顺序相反:

ANN 先在全库召回一批 → 再用 path 条件把不合目录的踢掉

问题是全库召回时没考虑目录,可能真正属于该范围的条目根本没进前 k,事后过滤就补不回来了。论文把目录放在 ANN 之前先圈定,保证 ANN 只在对的那一摊里比意思。

三种方案只是第①步的做法不同,第②③步完全一样

  • PE-Online:查询时临时枚举子树再并表;
  • PE-Offline:查一次物化好的祖先表;
  • TRIEHI:走树直接读节点编号集。

三者都把候选集交给同一个 ANN 排序,这也是论文说 "目录层与 ANN 解耦、可插拔" 的原因。

一句话:先在目录层把 "哪些条目有资格参加" 定下来,再让 ANN 在这个小范围里比谁更像,而不是全库比完再筛。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值