跳转至

数据模型

本文件系统性梳理 GoVector 的数据模型,重点围绕以下核心结构展开:PointStruct、ScoredPoint、Filter、Payload 及其在协议缓冲区中的映射;解释字段语义、数据类型、验证规则与业务约束;说明协议缓冲区的消息格式、序列化机制与版本兼容性;展示向量、元数据、过滤条件的组合使用方式;并给出数据生命周期管理、缓存策略与性能考量,以及实际使用建议与排错指引。

项目结构与数据模型定位

  • 协议缓冲区定义位于 core/proto/point.proto,生成的 Go 代码为 core/proto/point.pb.go。
  • 核心 Go 数据模型位于 core/models.go,包含与 protobuf 对应的 Go 结构体及过滤器逻辑。
  • 存储层负责持久化与反持久化,涉及 Protobuf 序列化、BoltDB 存取、可选的向量量化。
  • 集合层 Collection 负责索引选择、Upsert/Search/Delete 等操作的编排。
  • 数学工具提供距离度量(Cosine/Euclid/Dot),用于相似度计算与排序。
graph TB
subgraph "协议缓冲区"
P1["point.proto
定义消息结构"] P2["point.pb.go
生成的 Go 代码"] end subgraph "核心模型" M1["models.go
PointStruct/ScoredPoint/Filter/Payload"] M2["models_test.go
过滤器单元测试"] end subgraph "存储与序列化" S1["storage.go
PB 序列化/反序列化
BoltDB 持久化"] end subgraph "集合与索引" C1["collection.go
Collection 编排"] I1["index.go
VectorIndex 接口"] end subgraph "数学工具" D1["math.go
距离度量实现"] end P1 --> P2 P2 --> S1 M1 --> S1 M1 --> C1 C1 --> I1 C1 --> D1 S1 --> C1

核心数据结构总览

  • PointStruct:单条向量记录,包含唯一标识、向量数组、版本号与可选元数据。
  • ScoredPoint:检索结果,包含匹配点的标识、版本、相似度分数与元数据。
  • Filter:过滤条件容器,支持 must/must_not 条件列表。
  • Payload:键值对元数据,值类型为 any,在持久化时通过 Protobuf oneof 映射到具体类型。
  • Condition/MatchValue/RangeValue:过滤器的条件定义,支持精确匹配、范围、前缀、包含、正则等。

协议缓冲区(Protocol Buffers)定义

  • 消息结构
  • PointStruct:包含 id、vector(float 列表)、payload(字符串到 Value 的映射)。
  • Value:oneof 包含 string/int64/double/bool/bytes 六种基础类型。
  • ScoredPoint:包含 id、version、score、payload。
  • Condition:包含 key 与 MatchValue。
  • MatchValue:oneof 包含 string/int64/double/bool/bytes。
  • Filter:包含 must 与 must_not 两个 Condition 列表。
  • 序列化与反序列化
  • 存储层使用 google.golang.org/protobuf/proto 进行 Marshal/Unmarshal。
  • 读取时先从 BoltDB 读取字节,再 Unmarshal 至 PB 消息,再转换为 Go 结构体。
  • 写入时先将 Go 结构体转换为 PB 消息,再 Marshal 为字节写入。
  • 版本兼容性
  • 使用 proto3 语法,新增字段以可选方式添加,不破坏现有二进制格式。
  • oneof 字段确保同一时刻仅设置一种类型,避免歧义。
  • 生成的 point.pb.go 提供标准的序列化接口与字段访问器。

数据模型详解

PointStruct(向量记录)

  • 字段与类型
  • id: string,唯一标识符(建议使用稳定字符串或数字字符串)。
  • version: uint64,版本号,用于一致性与并发控制。
  • vector: []float32,向量嵌入,维度需与集合配置一致。
  • payload: map[string]any,可选元数据,支持任意 JSON 兼容类型。
  • 业务约束
  • 向量长度必须与集合的 VectorLen 一致,否则插入会失败。
  • 版本号由 Upsert 流程统一生成,保证幂等与顺序。
  • payload 在持久化时会被转换为 Protobuf Value,仅 oneof 支持的类型会被保留。
  • 验证规则
  • 插入前校验向量维度;持久化时忽略不支持类型。
  • 性能影响
  • 大向量会增加内存与磁盘占用;可结合 SQ8 量化降低存储体积。

ScoredPoint(检索结果)

  • 字段与类型
  • id: string,匹配点的标识。
  • version: uint64,匹配点的版本。
  • score: float32,相似度或距离分数(由所选度量决定“越小/越大”更相似)。
  • payload: map[string]any,可选元数据。
  • 业务约束
  • score 由底层索引根据 Metric 计算,Cosine/Dot 越大越相似,Euclid 越小越相似。
  • 返回数量受 topK 限制。
  • 性能影响
  • 结果集大小直接影响内存与网络传输开销。

Filter(过滤器)

  • 组成
  • must: []Condition,所有条件都必须满足。
  • must_not: []Condition,任何条件都不允许满足。
  • Condition
  • key,payload 中的键名。
  • value,payload 中的值。type: 枚举,支持 exact、range、prefix、contains、regex。
  • match: MatchValue,用于精确匹配或前缀/包含/正则的值。
  • range: RangeValue,用于数值范围匹配(gt/gte/lt/lte)。
  • 评估规则
  • 若 filter 为空,视为匹配全部。
  • must 列表中任一不满足即整体不匹配。
  • must_not 列表中任一满足即整体不匹配。
  • 不存在的 key 在 must 中视为不匹配,在 must_not 中视为匹配(即“非存在即通过”)。
  • 性能影响
  • 过滤器在索引层执行,must_not 通常更昂贵,建议尽量用 must 限定范围。

Payload(元数据)

  • 类型与用途
  • map[string]any,用于检索过滤与二次处理。
  • 在持久化时转换为 Protobuf Value,仅 oneof 支持的类型会被保存。
  • 常见键建议
  • 分类、价格、布尔状态、标签数组、时间戳等。
  • 注意事项
  • 不支持类型在持久化时会被忽略。
  • 过滤器对非字符串值的前缀/正则/包含匹配有特定行为(见下节)。

数据流与生命周期

插入流程(Upsert)

sequenceDiagram
participant App as "应用"
participant Col as "Collection"
participant St as "Storage"
participant Idx as "VectorIndex"
App->>Col : Upsert(points)
Col->>Col : 校验向量维度/生成版本号
Col->>St : UpsertPoints(按ID序列化PB)
St-->>Col : 成功/失败
Col->>Idx : Upsert(points)
Idx-->>Col : 成功/失败
Col-->>App : 返回结果
sequenceDiagram
participant App as "应用"
participant Col as "Collection"
participant Idx as "VectorIndex"
App->>Col : Search(query, filter, topK)
Col->>Col : 校验查询向量维度
Col->>Idx : Search(query, filter, topK)
Idx-->>Col : []ScoredPoint
Col-->>App : 返回结果

删除流程(Delete)

sequenceDiagram
participant App as "应用"
participant Col as "Collection"
participant Idx as "VectorIndex"
participant St as "Storage"
App->>Col : Delete(ids或filter)
Col->>Col : 解析目标ID列表
Col->>St : DeletePoints(按ID删除)
St-->>Col : 成功/失败
Col->>Idx : Delete(each id)
Idx-->>Col : 成功/失败
Col-->>App : 返回删除计数

加载与恢复

  • 启动时加载集合元数据与点集,校验维度一致性,批量重建内存索引。
  • 若启用量化,加载时自动解量化向量。

架构关系图

classDiagram
  class PointStruct {

      +string id
      +uint64 version
      +[]float32 vector
      +Payload payload

  }
  class ScoredPoint {

      +string id
      +uint64 version
      +float32 score
      +Payload payload

  }
  class Filter {

      +[]Condition must
      +[]Condition must_not

  }
  class Condition {

      +string key
      +ConditionType type
      +MatchValue match
      +RangeValue range

  }
  class MatchValue {
      +any value
  }
  class RangeValue {
      +any gt
  +any gte
  +any lt
  +any lte
  }
  class Payload {
      <>
}
PointStruct --> Payload : "包含"
ScoredPoint --> Payload : "包含"
Filter --> Condition : "包含"
Condition --> MatchValue : "使用"
Condition --> RangeValue : "使用"

过滤器与查询逻辑

过滤器评估算法

flowchart TD
Start(["开始"]) --> NilCheck{"filter 是否为空?"}
NilCheck --> |是| PassAll["返回 true匹配全部"]
NilCheck --> |否| MustLoop["遍历 must 列表"]
MustLoop --> MustEval["逐个评估条件"]
MustEval --> MustFail{"任一不满足?"}
MustFail --> |是| Fail["返回 false"]
MustFail --> |否| MustNotLoop["遍历 must_not 列表"]
MustNotLoop --> MustNotEval["逐个评估条件"]
MustNotEval --> MustNotFail{"任一满足?"}
MustNotFail --> |是| Fail
MustNotFail --> |否| Pass["返回 true"]

条件类型与匹配规则

  • exact:严格相等比较。
  • range:支持整数与浮点数,同时接受 int/float64 混合比较;gt/gte/lt/lte 可组合。
  • prefix:仅对字符串有效,要求前缀长度不超过字符串长度。
  • contains:支持 []any、[]string、[]int、string;空串在字符串场景下不匹配。
  • regex:仅对字符串有效,非法正则表达式视为不匹配。

性能与存储优化

距离度量与排序

  • Cosine/Dot:值越大越相似;适合归一化向量与方向敏感任务。
  • Euclid:值越小越相似;适合绝对距离敏感任务。
  • 默认度量为 Cosine。

索引策略

  • Flat:暴力搜索,适合小规模或低延迟要求的场景。
  • HNSW:近似最近邻,支持大规模高维向量,吞吐更高、延迟更低。

持久化与序列化

  • 使用 Protobuf 将 PointStruct 序列化为字节,写入 BoltDB。
  • 读取时反序列化为 PB 消息,再转回 Go 结构体。
  • 不支持类型在持久化时被忽略,注意数据完整性。

量化(SQ8)

  • 可选启用,将向量压缩存储,减少磁盘占用。
  • 加载时自动解量化,不影响检索精度与性能。

并发与一致性

  • Collection 使用读写锁保护内部状态。
  • Upsert 先持久化后更新索引,失败时尝试回滚持久化以保持一致性。
  • 版本号基于纳秒时间戳,保证插入顺序与幂等。

最佳实践与常见问题

字段设计建议

  • id:建议使用稳定字符串(如业务主键),避免频繁变更。
  • vector:确保与集合配置的维度一致;预归一化向量以提升 Cosine 效果。
  • payload:键名简洁明确;数值范围使用 range;文本匹配优先 exact/prefix/contains/regex。

过滤器使用建议

  • 优先使用 must 限定范围,减少 must_not 的使用。
  • 对高频过滤键建立索引(若未来扩展)。
  • 正则表达式复杂度较高,谨慎使用。

性能调优

  • 大规模数据优先选择 HNSW 索引。
  • 启用 SQ8 量化以降低存储与IO压力。
  • 控制 topK 与过滤器复杂度,避免全表扫描。

常见问题排查

  • 插入报错“维度不匹配”:检查集合 VectorLen 与向量长度是否一致。
  • 过滤不生效:确认 key 名称正确且类型匹配;must_not 对不存在键默认通过。
  • 查询超时:检查索引类型与参数;适当缩小过滤范围或降低 topK。

结论

GoVector 的数据模型以 PointStruct/ScoredPoint/Filter/Payload 为核心,通过 Protobuf 实现跨语言与跨进程的稳定序列化,并结合 BoltDB 提供持久化能力。配合 HNSW 索引与 SQ8 量化,可在大规模场景下实现高性能与低资源占用。合理设计 id/向量/元数据与过滤器,遵循 Upsert/Search/Delete 的生命周期流程,可获得稳定可靠的检索体验。