土法炼钢兴趣小组的算法知识备份

【向量检索引擎】Proxy 与 Coordinator:接入面、TSO 与集群大脑

文章导航

分类入口
databasestorage
标签入口
#milvus#proxy#coordinator#tso#access-layer#mpp#vector-engine

目录

第 1 篇 给出四层地图;第 3 篇第 5 篇 把数据面落到 Channel / Streaming Node。控制面仍缺两块:无状态 Proxy 如何成为唯一对外入口,以及 单活跃 Coordinator 如何持有拓扑、时间戳与调度。多数排障案例的第一个错误假设,就是把这两层想成「一个反向代理 + 一个数据库主节点」——本文要纠正这个假设。

本文依据官方 Architecture OverviewStreaming/Data ProcessingTimestampTime Synchronization 文档,以及 internal/proxy/timestamp.go 源码,钉住 Access Layer 与 Coordinator 的职责清单与 TSO 全序机制。不展开一致性四级语义的产品用法全文(第 12 篇),不展开 Data Node 任务内部(第 10 篇)。

本文是「向量检索引擎」系列第 4 篇(共 19 篇)。→ 系列目录

版本锚定:Milvus 2.6.x


一、最小故事:同一个 Proxy,三条完全不同的路

假设你在维护一个小型 RAG 服务,用 pymilvus 做了三件事:

  1. 启动时执行一次 create_collection("docs", schema)
  2. 服务运行期间持续 insert(vectors) 写入新文档的向量。
  3. 每次用户提问时执行 search(query_vector, top_k=10)

三条调用都先落在同一个 Proxy 实例上,很容易得到一个错误直觉:「Proxy 收到请求后转给后面唯一的一个大脑处理」。官方文档给出的实际路径表(Architecture Overview · Data Flow and API Categories)否定了这个直觉:

类别 示例 经 Proxy 之后
DDL/DCL createCollection Coordinator
DML insert / delete / upsert Streaming Node
DQL search / query Streaming Node(再协同 Query Node)

也就是说,第 1 条请求确实进了「大脑」;第 2、3 条请求根本不经过 Coordinator 的数据路径,而是直接打到负责该 shard 的 Streaming Node。Coordinator 唯一会介入写路径的地方,是稍后第三节会讲的 时间戳分配——但那是一次很小的 RPC,不是数据本身的搬运。

flowchart TB
  client["Client SDK"]
  proxy["Proxy"]
  client --> proxy
  subgraph ddlPath ["DDL / DCL path"]
    coord["Coordinator<br/>schema, permission, TSO issue"]
  end
  subgraph dmlPath ["DML path"]
    sn1["Streaming Node<br/>assign TSO, append WAL"]
    growing["Growing segment"]
  end
  subgraph dqlPath ["DQL path"]
    sn2["Streaming Node<br/>Delegator"]
    qn["Query Node<br/>sealed segments"]
  end
  proxy -->|"createCollection<br/>dropPartition ..."| coord
  proxy -->|"insert / delete / upsert"| sn1 --> growing
  proxy -->|"search / query"| sn2
  sn2 --> qn

记住这张图就抓住了本文的骨架:Access Layer 是单一入口,但入口之后立刻分岔成三条职责完全不同的路。下面把这三条路和其中最容易被误解的 TSO 讲清楚。


二、Access Layer:无状态 Proxy 到底做什么

官方四层文档对 Access Layer 的定义:

Proxy 因此同时具备三种角色:

角色 具体动作 后果
协议边界 SDK/REST → 内部 RPC;请求校验 错误 schema、越权请求在此拦下
路由缓存持有者 优先用本地 routing cache;缺缓存才问 Coordinator 减少每次请求都打 Coordinator 的开销
最终归并器 跨 shard 的 Top-\(k\) / query 结果 reduce search 的最终 CPU/内存压力压在 Proxy 上

无状态的直接后果:扩 Proxy 副本不迁移本地段数据;会话亲和性不是正确性前提。代价是:每次 search 的最终 reduce 压在 Proxy CPU/内存上——超大 topk、高并发时要把 Proxy 当成独立容量维度(第 17 篇排障)。


三、Coordinator:集群大脑与它实际管的四件事

官方表述:Coordinator 是 Milvus 的大脑;任一时刻全集群恰好一个活跃 Coordinator,负责维护拓扑、调度各类任务、承诺集群级一致性相关职责。

任务清单(官方列举):

职责域 内容 本系列衔接
DDL/DCL/TSO 创建/删除 Collection、Partition、Index;管理 TSO 与 time ticker 第 5 篇 Message 顺序;第 12 篇一致性
Streaming 管理 绑定 WAL 与 Streaming Node;流服务发现 第 5 篇 Streaming Coordinator
Query 管理 Query Node 拓扑与负载均衡;维护 serving query views 指导路由 第 7、9、14 篇
历史数据管理 向 Data Node 分发 compaction / 建索引;管理 segment 与 data view 拓扑 第 6、10 篇;handoff

Worker(Streaming / Query / Data)被描述为 dumb executors:听 Coordinator 指令执行;因存算分离而可无状态扩缩(四层文档)。

flowchart LR
  proxy["Proxy"]
  coord["Coordinator<br/>single active"]
  sn["Streaming Node"]
  qn["Query Node"]
  dn["Data Node"]
  proxy -->|"DDL/DCL"| coord
  proxy -->|"DML/DQL"| sn
  coord --> sn
  coord --> qn
  coord --> dn

3.1 为什么是「单活跃」大脑

拓扑、TSO、query view、离线任务队列若多主并发写,容易出现双视图。官方选择 单活跃 Coordinator,用元存储(etcd)支撑高可用元数据与服务注册(四层文档 · Meta storage)。故障时切换活跃实例属于运维面;客户端经 Proxy 的 Wait for Ready / 重试与 WAL 迁移窗口类似(第 5、14 篇)。

3.2 「dumb executors」具体意味着什么

官方把 Streaming / Query / Data Node 统称为 dumb executors——这个词容易被读成「简单、不重要」,实际含义更精确:它们不持有集群级真值,只持有自己被分配到的那部分数据与状态。一个 Query Node 重启后,不需要跟别的 Query Node 协商谁是权威版本,它只需要重新问 Coordinator「我现在该加载哪些 segment」,再从对象存储把数据拉回来——集群级的「谁该持有什么」这件事从头到尾只有 Coordinator 说了算。这也是为什么 Worker 层可以做到无状态水平扩缩:状态的权威来源从来不在 Worker 本地磁盘上。

3.3 MixCoord 合并(阅读提示)

2.5→2.6 的官方升级说明明确写出:历史上拆分的 RootCoord / QueryCoord / DataCoord 已合并为单一 MixCoord,元数据管理与任务调度收进一个协调服务,同时新增专职的 Streaming Node 承接原来散落在 Proxy / DataNode / QueryNode 里的实时职责。本系列以 2.6.x 文档中的「Coordinator」职责表为准;读源码或旧博客时看到 RootCoord/QueryCoord/DataCoord 字样,理解为同一角色的历史分包,不要因为名字不同而怀疑架构变了。


四、TSO 与 timetick:全序从哪里来(它不是墙上时钟)

4.1 为什么不能用本地时钟

Time Synchronization 文档用一个例子说明问题:两个用户在不同节点上先后执行 DDL/DML/DQL,如果各自用本地挂钟盖时间戳,会遇到两个麻烦——不同节点的时钟本身不同步;网络延迟会让「更晚发起的操作」反而先落盘。因此 Milvus 引入 Timestamp Oracle(TSO):所有事件必须从 TSO 服务领取时间戳,而不是读本地时钟。

4.2 时间戳的物理结构

TSO 时间戳是一个 uint64:高 46 位是物理时间(UTC 毫秒),低 18 位是逻辑计数——同一毫秒内可以再切出 \(2^{18}\) 个严格递增的序号。TSO 服务由(Mix)Coordinator 提供,客户端可以在一次分配请求里批量领取多个时间戳。

4.3 分配路径:一次 RPC,不是一次网络转发

源码 internal/proxy/timestamp.gotimestampAllocator.alloc 显示得很直接:Proxy 向 Coordinator 发一次 AllocTimestampRequest{Count: n},Coordinator 返回起始时间戳与数量,Proxy 在本地把 start, start+1, ..., start+n-1 分给这批操作。这意味着:DML 的数据不经过 Coordinator,但 DML 的时间戳分配要经过 Coordinator——这是 Coordinator 唯一真正介入写路径的地方,也是它作为热点的来源之一(第 17 篇排障时如果看到 Coordinator CPU 随写入 QPS 联动上升,先查这条 RPC,而不是急着去查 Streaming Node)。

批量 insert 时,Proxy 会一次性申请一批时间戳(count = n),而不是为每一行单独发一次 RPC——这也是为什么「单条反复 insert」比「攒批 insert」更容易把 Coordinator 的 TSO 分配 RPC 数量顶起来:前者每次调用都要走一次 AllocTimestampRequest,后者用一次 RPC换来一整批时间戳。批大小与 RPC 频率的权衡,直接决定 Coordinator 在写密集场景下的负载形态。

4.4 timetick:把「乱序到达」变成「可安全消费」

Time Synchronization 文档进一步说明:同一个 Proxy 发出的 InsertMsg 时间戳一定递增,但不同 Proxy 之间没有这个保证。于是引入 timetick 机制:

  1. 每个 Proxy 每隔(默认)200 ms 向(Mix)Coordinator 报告它在某个消息流上看到的最大时间戳;
  2. Coordinator 取该消息流上所有 Proxy 报告值的 最小值,作为 timetick 写回该流;
  3. 下游组件(Streaming Node 等)读到这个 timetick 后就能确认:所有时间戳小于该 timetick 的消息都已经到齐,可以安全按序消费。
sequenceDiagram
  participant P as Proxy
  participant C as Coordinator (TSO)
  participant S as MsgStream / Streaming Node
  P->>C: AllocTimestampRequest(count=n)
  C-->>P: start, count
  P->>P: assign start..start+n-1 to InsertMsg batch
  P->>S: send InsertMsg (with TSO)
  loop every 200 ms
    P->>C: report max seen timestamp on this stream
  end
  C->>C: take min(reported) across all proxies
  C->>S: insert timetick = min
  S->>S: safe to consume all msgs < timetick in order

这张时序图对应第二节故事里最容易被跳过的一步:insert 之所以「立刻能被搜到」而不乱序,靠的不是某个节点本地判断,而是 Coordinator 汇总多个 Proxy 的时间戳水位后广播的 timetick。

4.5 查询侧的时间戳语义

对查询侧,官方引入若干时间戳相关概念(产品层多通过 一致性级别 间接配置,用户通常不手写原始 TSO):

概念 含义(官方)
Guarantee_timestamp 保证该时刻之前的 DML 更新在本次 search/query 中可见;未配置时默认取请求发起时刻
Service_timestamp 由查询数据面维护,表示其已完成执行的 DML 时间线进度
Graceful_time 配置项:可容忍的短暂不可见窗口;与 Guarantee/Service 比较决定是否立即执行或等待

Service_timestamp(加可选 Graceful_time)尚不满足 Guarantee_timestamp 时,查询侧 推迟 执行,避免在不完整视图上搜(Timestamp 文档 Scenario 描述)。一致性四级如何映射到 GuaranteeTs,见第 12 篇;本篇只钉住:时间语义的权威原料来自 Coordinator 管理的 TSO/timetick 体系,查询可见性用 Guarantee/Service 比较落地。


五、Query view 与路由:视图滞后是什么感觉

Coordinator 的 Query 管理职责包括提供 serving query views 指导路由(Architecture Overview)。结合 Proxy「routing cache,缺则问 Coordinator」:

  1. handoff / 负载均衡改变「哪个 Query Node 持有哪些 Sealed」;
  2. Coordinator 更新可服务视图;
  3. Proxy / Delegator 按视图把历史检索打到正确节点。

若视图滞后,会出现「段已 flush 但查询仍只打 Growing」或「打到未加载节点」类症状——排障时区分 控制面视图(Coordinator 认为谁持有什么)与 Worker 本地加载状态(Query Node 实际加载了什么)是两件事,视图更新与实际加载完成之间总有一个短窗口(第 14、17 篇)。

一个具体场景:某个 Sealed segment 刚完成 handoff,Coordinator 的 query view 已经把它标记为「归属 Query Node B」,但 Query Node B 从对象存储加载该段(及索引)需要一段时间。这段时间里,如果 Proxy 的 routing cache 还没刷新到最新视图,请求可能仍打向旧的持有者,甚至短暂出现「两边都查不到完整数据」的窗口——这不是数据丢失,是 控制面视图更新数据面加载完成 之间天然存在的异步间隙,第 17 篇的排障清单会把这类现象和真正的数据丢失区分开。


六、常见误解

6.1 「Proxy 只是一个反向代理」

不是。Proxy 除了转发,还持有 routing cache、做请求校验、并且是跨 shard 结果的 最终归并器(MPP reduce)。把 Proxy 当成纯网络层配置容量,会低估它在大 topk、高并发下的 CPU/内存开销。

6.2 「TSO 就是当前墙上时间,可以拿来做跨系统时间对齐」

不是。TSO 的高 46 位确实是毫秒级 UTC 时间,但低 18 位逻辑计数会让同一毫秒内产生多个不同的 TSO 值;TSO 保证的是 单调递增与全序,不是「与真实时钟一一对应」。拿 TSO 直接换算成业务时间戳、再跨系统与别的时钟源比较,是不安全的。

6.3 「单活跃 Coordinator 意味着所有写入吞吐都被它限制」

不完全对。DML/DQL 的数据本身走 Streaming Node / Query Node,不经过 Coordinator;Coordinator 只承担 DDL、TSO 分配 RPC、拓扑与任务调度。真正会被 Coordinator 限制的,是 TSO 分配的 RPC 速率(第 4.3 节)和 元数据/任务队列规模,不是原始数据搬运带宽。误把「单活跃」等同于「单点吞吐瓶颈」,会让排障方向跑偏到 Streaming/Query Node 反而找不到真正的热点。


七、学术谱系与工程间隙

7.1 谱系:从中心化协调者到混合逻辑时钟

阶段 代表 核心思想 与 Milvus 的关系
经典分布式 DBMS 单一协调者 / master 管元数据与调度 简化一致性,代价是单点 Coordinator 的直接前身范式
Spanner TrueTime Corbett et al., Spanner: Google’s Globally-Distributed Database, OSDI 2012 用有界时钟误差 + 等待,换取外部一致性,不依赖单点分配器 目标相同(全序/因果),手段不同(Milvus 走中心化分配而非误差等待)
混合逻辑时钟(HLC) Kulkarni et al., Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases, OPODIS 2014 物理时间 + 逻辑计数复合成单调时钟,不需要严格时钟同步 Milvus TSO 的「46 位物理 + 18 位逻辑」正是这一类复合时钟的具体实例
TiDB PD TSO PingCAP 官方文档 同样 46 位物理毫秒 + 18 位逻辑计数,由中心化 Placement Driver 分配 与 Milvus TSO 位布局几乎一致,说明这是该类系统里的常见收敛设计,而非 Milvus 独创
Milvus 2.6.x 本文 Coordinator 集中分配 TSO;timetick 汇总各 Proxy 水位 用中心化分配换简单性,用 timetick 解决多 Proxy 乱序

Wang et al. (SIGMOD 2021) 已强调分布式与动态更新;2.x/2.6 把协调职责收束到文档化的 Coordinator 任务表,并把流批 Worker 拆开。TSO 的位格式与 TiDB PD 高度相似,是数据库工程里对 HLC 思想的独立收敛,而不是巧合式抄袭——两者都需要「不依赖严格时钟同步、又要全序、又要能批量分配」的同一组约束。

7.2 争论:中心化 TSO vs 无中心 HLC

Milvus 选择前者:TSO 分配是一次可批量、可缓存(Proxy 一次可申请多个)的 RPC,工程上换来的是 timetick 机制的简单性;权衡点在于 Coordinator 的 TSO RPC 吞吐与延迟必须纳入容量规划(7.3 节)。

7.3 工程间隙

7.4 开放问题

  1. 超大规模下单 Coordinator 的 TSO RPC 吞吐、任务队列与 segment 元数据规模上限如何联合建模?
  2. Proxy 最终 reduce 是否应下沉到两级树形归并以降低单点带宽?
  3. routing cache 的失效策略如何在正确性与 Coordinator QPS 之间自动调参?
  4. 若未来引入多活跃 TSO 分配点(类似部分 HLC 实践),Milvus 现有 timetick 汇总协议需要如何改造才能保持全序语义?

八、小结

三句话小结

  1. Proxy 是无状态入口:DDL 走 Coordinator,DML/DQL 走 Streaming Node,不是「都进大脑」。
  2. Coordinator 单活跃,集中管 TSO、拓扑与离线调度;TSO 分配 RPC 是它嵌入写路径的关键点。
  3. TSO + timetick 把多 Proxy 乱序到达变成可按序消费,与 Spanner/TiDB 同一族时间戳思路。

下一篇看持久化形态:对象存储上的 Segment 布局

把本文三条误解对应到排障动作,方便复用:看到「写入延迟异常」先分清是 Proxy 侧 reduce 慢、TSO 分配 RPC 慢,还是 Streaming Node 本身慢——这三者对应完全不同的扩容/调参方向,而它们恰好就是本文第 6.1、6.3 节纠正的两个误解所指向的组件。


参考资料

  1. Milvus Documentation v2.6.x, Architecture Overview(四层、API 路径、Coordinator 任务表)。
  2. Milvus Documentation v2.6.x, Timestamp(Guarantee / Service / Graceful)。
  3. Milvus Documentation v2.6.x, Time Synchronization(TSO 格式、timetick 机制、200 ms 上报周期)。
  4. Milvus Documentation v2.6.x, Consistency Level(四级与 GuaranteeTs 映射;第 12 篇展开)。
  5. milvus-io/milvus, internal/proxy/timestamp.gotimestampAllocator.alloc / AllocTimestampRequest(TSO 分配 RPC 路径;源码,随 release 演进)。
  6. Milvus Blog, How to Safely Upgrade from Milvus 2.5.x to Milvus 2.6.x(MixCoord 合并、Streaming Node 引入;B 级)。
  7. Corbett, James C. et al., Spanner: Google’s Globally-Distributed Database, OSDI 2012。
  8. Kulkarni, Sandeep S. et al., Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases, OPODIS 2014。
  9. PingCAP, TimeStamp Oracle (TSO) in TiDBtikv/pd Wiki Timestamp Oracle(B 级,位格式对照)。
  10. Wang, Jianguo et al., Milvus: A Purpose-Built Vector Data Management System, SIGMOD 2021。
  11. 第 1、3、5 篇系列 index

返回 系列目录 | 上一篇:Segment 状态机 | 下一篇:对象存储布局

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-12 · database / storage

【向量检索引擎】一致性模型:四级 GuaranteeTs 与 PACELC 的延迟账

按官方 Consistency Level 与 Timestamp 文档拆解 Strong/Bounded/Session/Eventually 如何映射到 GuaranteeTs,用最小故事、四级时间轴与 Strong 等待时序图说明「一致性」在 Milvus 里首先是一笔延迟账;对照 Abadi PACELC 定理与 Bailis PBS,说明 Bounded 是定性旋钮而非概率保证。


By .