ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Milvus SegCore 执行计划中的 Visitor 模式:从 VectorPlanNode 到标量表达式访问器

Milvus SegCore 执行计划中的 Visitor 模式:从 VectorPlanNode 到标量表达式访问器 Milvus SegCore 执行计划中的 Visitor 模式从 VectorPlanNode 到标量表达式访问器【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvusVisitor访问者模式是 Milvus 的 C 存储与检索内核 SegCore 用来解析并执行查询执行计划Execution Plan的核心手段向量搜索节点VectorPlanNode与标量过滤表达式树通过统一的accept()入口将结构遍历与行为实现解耦。本文基于仓库归档文档 docs/archive/milvus-2.0/segcore/visitor.md 展开结合当前仓库 internal/core/src/query 下真实源码讲解物理计划节点PlanNode/VectorPlanNode与标量表达式节点TermExpr、RangeExpr、CompareExpr、LogicalBinaryExpr、LogicalUnaryExpr的类型体系、访问器接口与执行器实现读完即可理解一次 ANN 检索请求在 Milvus 内核中如何被访问并最终落到向量搜索与过滤执行。一、Visitor 模式在 SegCore 中的定位Milvus 的查询链路大体是Proxy 将用户的搜索/查询请求编译为 protobuf 形式的执行计划并序列化下发QueryNode 侧的 SegCore 再把计划解析成内存中的物理计划与表达式树最后针对 Segment数据段逐段执行。在这个链条的中段SegCore 采用了经典的Visitor 模式把遍历哪些节点对象结构与对节点做什么算法行为分开从而让同一套节点结构可以被不同的访问器复用——例如负责真正执行物理计划的ExecPlanNodeVisitor以及内部用于收集统计信息、改写/抽取过滤条件的其他逻辑。Visitor 模式在此场景下带来的直接收益是新增加一种可执行节点或一种新访问器时不需要改动原有节点类的内部逻辑只需补一个visit(...)重载即可满足编译期约束纯虚接口强制全部实现。二、物理执行计划节点query 命名空间下的 PlanNode 体系归档文档中写到${internal/core}/src/query/PlanNode.h内包含向量搜索的物理计划。当前仓库中该文件依然存在位于 internal/core/src/query/PlanNode.h其内容比归档时代更加丰富// Base of all Nodes struct PlanNode { public: virtual ~PlanNode() default; virtual void accept(PlanNodeVisitor) 0; PlanOptions plan_options_; }; struct VectorPlanNode : PlanNode { public: void accept(PlanNodeVisitor) override; SearchInfo search_info_; std::string placeholder_tag_; std::shared_ptrmilvus::plan::PlanNode plannodes_; }; struct RetrievePlanNode : PlanNode { public: void accept(PlanNodeVisitor) override; std::shared_ptrmilvus::plan::PlanNode plannodes_; int64_t limit_; bool has_order_by_ false; std::vectorFieldId deferred_field_ids_; std::vectorFieldId pipeline_field_ids_; std::optionalQueryIteratorCursor query_iterator_cursor_; };PlanNode基类声明纯虚函数accept(PlanNodeVisitor)这是 Visitor 模式中被访问对象一侧的统一入口同时持有PlanOptions其中expr_use_json_stats默认true控制过滤执行是否尝试利用 JSON 字段统计信息做剪枝。VectorPlanNode向量搜索ANNS执行节点即归档文档中所说的核心节点。它携带search_info_搜索参数如 topK、度量方式、索引搜索的相关配置placeholder_tag_与占位符组PlaceholderGroup对应的标签用于把 float/binary 向量查询数据绑定到计划上plannodes_指向milvus::plan::PlanNode逻辑计划树的智能指针——向量节点在顶层其内部实际挂着一个负责标量过滤、投影等工作的逻辑计划片段。RetrievePlanNode归档后新增的检索query/retrieve执行节点负责按主键或表达式取行、limit、order by以及延迟物化two-project 模式等逻辑。与之配套的PlanNodeVisitor访问器基类在 internal/core/src/query/PlanNodeVisitor.h 中定义文件头标注为Generated File / DO NOT EDIT说明它由代码生成器维护保证与节点类同步class PlanNodeVisitor { public: virtual ~PlanNodeVisitor() default; virtual void visit(VectorPlanNode) 0; virtual void visit(RetrievePlanNode) 0; };可见新增一个visit(VectorPlanNode)之外的节点类型就必须同步扩展该接口编译器会强制所有访问器补全实现——这正是 Visitor 模式在此处的模式约束力。三、标量过滤表达式的节点类型体系归档文档第 2 点指出${internal/core}/src/query/Expr.h包含标量表达式的物理计划并列出五类节点及其语义节点语义示例TermExpr成员判断col in [1, 2, 3]RangeExpr与常量做范围比较a 5、1 b 2CompareExpr不同列之间的比较a bLogicalBinaryExpr逻辑与 / 或and、orLogicalUnaryExpr逻辑非not在演进后的当前仓库中query/Expr.h已被重构这部分逻辑表达式计划节点统一收敛到 internal/core/src/expr/ITypeExpr.h属于milvus::expr命名空间。文件内定义了统一的类型推导基类ITypeExpr与智能指针别名TypedExprPtr在其之上继承出两类关键层级输入/列访问类ColumnExpr读列、FieldAccessTypeExpr、InputTypeExpr等负责把列数据引入表达式求值过滤类ITypeFilterExpr及其子类即归档文档中那五类节点的现代形态TermFilterExpr对应原TermExprITypeExpr.h成员column_、vals_一组proto::plan::GenericValue常量值、is_in_field_直接对应col in [1, 2, 3]的语义并支持NOT IN由外层逻辑非表达UnaryRangeFilterExpr/BinaryRangeFilterExpr共同对应原RangeExpr分别见 ITypeExpr.h 与 ITypeExpr.h一元形式用op_type_等于/不等于/大于/小于等proto::plan::OpType加单值val_表达a 5二元形式用lower_val_/upper_val_与lower_inclusive_/upper_inclusive_两个开闭标志表达1 b 2CompareExpr对应原CompareExprITypeExpr.h两侧均为可求值表达式支撑列与列直接比较a bLogicalBinaryExprITypeExpr.hOpType枚举为Invalid / And / Or通过inputs_[0]、inputs_[1]持有左右子树LogicalUnaryExprITypeExpr.hOpType枚举为Invalid / LogicalNot通过inputs_[0]持有被取反的子表达式。此外该体系还持续扩充了AlwaysTrueExpr、ExistsExpr、BinaryArithOpEvalRangeExpr列与算术运算结果比较、TimestamptzArithCompareExpr、NullExpr、JsonContainsExpr、MatchExpr、GISFunctionFilterExpr、RoaringFilterExpr、BloomFilterExpr等过滤节点可支撑exists、JSON 包含、全文 match、GIS 等丰富过滤语义——这比归档文档列出的五类节点覆盖面广得多但归档中Term/Range/Compare/And-Or/Not五类仍是整个标量过滤的基础骨架。值得强调的是表达式类型定义与表达式执行器是分离的两层上述节点只负责刻画语法与类型每个节点还实现了ToString()便于打印调试以及GatherInfo()用于向物化视图/索引侧汇报该表达式命中了哪些列的哪些常量值。真正逐行求值、产出 bitset 的执行器则位于 internal/core/src/exec/expression例如exec/expression/TermExpr.h 中的PhyTermFilterExpr接收milvus::expr::TermFilterExpr逻辑表达式内部通过TermIndexFunc把成员判断映射为标量索引的In(n, val)批量查询从而在有标量索引时走索引快速过滤exec/expression/BinaryRangeExpr.cpp、exec/expression/CompareExpr.cpp、exec/expression/LogicalBinaryExpr.cpp、exec/expression/LogicalUnaryExpr.cpp 等文件实现各自执行逻辑。配套的单元测试分散在同目录的*Test.cpp如ExprTermTest.cpp、ExprRangeTest.cpp、ExprCompareTest.cpp以及 ExprTestBase.h 提供测试底座可据此学习每种过滤语义的精确行为。四、逻辑计划片段与向量搜索节点的装配归档文档把向量搜索与标量过滤分别放在PlanNode.h与Expr.h中但二者在现代架构里通过片段fragment机制被有机组装逻辑计划侧internal/core/src/plan/PlanNode.h 的milvus::plan::PlanNode是所有逻辑节点的基类其子节点如带过滤条件的IterativeFilterNode等直接引用expr::TypedExprPtr表达式树从源头上把表达式作为计划树的一等公民VectorPlanNode::plannodes_指向的正是这类逻辑计划片段。在 query/Plan.cpp 与 query/PlanProto.cpp 中CreateSearchPlanByExpr/CreateSearchPlanFromPlanNode/ParsePlanNodeProto会把 Proxy 下发的 protobuf 计划proto::plan::PlanNode解析成上述物理节点与逻辑片段ParsePlaceholderGroup负责把随请求带来的查询向量占位数据反序列化成PlaceholderGroup。也就是说访问器最终访问的VectorPlanNode是一个复合头节点search_info_placeholder_tag_描述向量检索本身plannodes_挂载过滤/投影子计划一次accept()即可驱动完整执行。五、执行访问器 ExecPlanNodeVisitor归档文档第 3 点提到在query目录下目前存在物理计划执行器ExecPlanNodeVistor且当时仅支持 ANNS 节点。这一结论在演进后的仓库中仍可验证其架构思路只是范围已扩大当前 internal/core/src/query/ExecPlanNodeVisitor.h 中的ExecPlanNodeVisitor同时实现了visit(VectorPlanNode)与visit(RetrievePlanNode)支持 query 检索可见新增一种可执行节点、扩展一个 visit 重载正是该模式设计的自然扩展方式。其构造参数体现了执行所需上下文包括segment目标数据段、timestamp可见性时间戳、placeholder_group查询向量数据、cancel_tokenfolly 取消令牌支持查询取消、consistency_level一致性级别、collection_ttl与entity_ttl_physical_time_usTTL 过滤、trace_span链路追踪。结果通过get_moved_result/get_retrieve_result取出它们先断言结果为空调用node.accept(*this)触发执行再移动出search_result_opt_/retrieve_result_opt_。visit(VectorPlanNode) 的执行主流程核心实现位于 internal/core/src/query/ExecPlanNodeVisitor.cpp从源码可归纳出完整流程段类型校验将SegmentInterface动态转换为SegmentInternalInterface仅支持段内执行路径计算活跃行数segment-get_active_count(timestamp_)得到在指定时间戳下可见的行数filter-only 模式若启用filter_only_则尝试用ProtoParser::ExtractFilterOnlyPlan抽取计划中的预过滤子树单独执行仅算过滤后的有效行数valid_count不真正做 ANN 搜索若无法抽取则保守地降级为valid_count active_count该模式可用于优化器/空段判断等场景空段短路active_count 0时直接依据占位符数量与元素层级构造空SearchResult跳过全部计算构建执行片段由node.plannodes_构造plan::PlanFragment并创建exec::QueryContext灌入search_info_、placeholder_group_、plan_options_、QueryConfig与 TTL/一致性参数同时设置可选的表达式缓存开关enable_expr_cache_与OpContext执行并取结果调用ExecuteTask(plan_fragment, query_context)其内部同文件ExecPlanNodeVisitor::ExecuteTaskExecPlanNodeVisitor.cpp创建milvus::exec::Task并调度到执行引擎最终把query_context-get_search_result()存入search_result_opt_供上层读取。测试专用的ExecuteQueryExpr辅助函数见 ExecPlanNodeVisitor.h展示了表达式执行语义它构造一个plan::PlanFragment并执行取出唯一子节点的ColumnVector作为 bitset 视图后执行flip()再返回——即内部约定 bit1 表示该行被过滤掉对外翻转为命中行。理解这一约定有助于阅读过滤/向量搜索联合执行的相关代码。六、从归档文档到当前实现的阅读路线如果读者想顺着归档文档的原始目录线索继续深入建议按下面的映射关系阅读源码归档文档中的位置历史当前仓库中的对应实现${internal/core}/src/query/PlanNode.h向量搜索物理节点query/PlanNode.hPlanNode/VectorPlanNode/RetrievePlanNode${internal/core}/src/query/Expr.h标量表达式物理计划expr/ITypeExpr.h逻辑类型化表达式 exec/expression物理执行器query 目录下的ExecPlanNodeVistorquery/ExecPlanNodeVisitor.h 与 query/ExecPlanNodeVisitor.cpp在此基础上还可以延伸阅读 query/PlanImpl.h计划内部表示与占位符定义、query/PlanProto.cppprotobuf ↔ 内部节点互转以及向量检索落点 query/SearchOnSealed.cpp、query/SearchOnGrowing.cpp、query/SearchOnIndex.cpp配合 query/ExecPlanNodeVisitor.cpp 追踪一次 ANN 搜索从VectorPlanNode被访问到最终产出SearchResult的完整链路。七、小结Visitor 模式在 Milvus SegCore 中的价值可以概括为三点一是统一入口所有可执行节点都只需实现accept执行器用visit分派新增节点/访问器互不侵入二是结构清晰向量搜索VectorPlanNode与标量过滤Term/Range/Compare/LogicalBinary/LogicalUnary等表达式作为独立类型体系通过逻辑计划片段milvus::plan::PlanNode装配职责边界一目了然三是便于横向扩展从归档时仅支持 ANNS 的ExecPlanNodeVisitor到如今同时覆盖RetrievePlanNode、filter-only 模式、表达式缓存与取消令牌都是顺着这一模式自然生长的结果。对于想要为 Milvus 增加新过滤算子或新执行形态的开发者理解这套访问器骨架是迈出第一步最直接的切入点。【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表