一致性、分片与副本
本节目标
你将从单机 point 扩展到分布式 collection:理解 shard 与 replica、写确认与读一致性、并发 upsert/delete 的冲突、分片键与 fan-out、扩缩容/重平衡,并用可观察测试定义业务需要的语义。
Shard 与 replica 不是一回事
Shard
一个 collection 被拆成若干不相交数据子集。每个 point 通常只属于一个逻辑 shard。分片用于:
- 超过单机容量;
- 分摊查询/写入;
- tenant/partition 路由;
- 并行构建与维护。
Replica
同一 shard 的额外副本。副本用于:
- 节点故障时继续服务;
- 分担读取;
- 升级/维护;
- 降低单份数据丢失风险。
增加 shard 不自动增加容灾;增加 replica 不自动增加总唯一容量。Replication 也不是 backup:所有副本都可能复制误删除、逻辑损坏或恶意写入。
一次查询可能发生什么
- 接入节点接收 query 与 filter;
- 根据 collection topology 找到相关 shards;
- 每个 shard 在一个或多个 replica 上执行局部 top-k;
- 协调节点合并局部候选;
- 应用 filter/排序语义;
- 返回全局 top-k。
如果查询必须 fan-out 所有 shards,尾延迟受最慢 shard 影响。Custom shard key 可以缩小路由,但错误键会漏数据或形成热点。
Global top-k 不能简单取每 shard top-k 以外的任意少量而不验证;过滤和分数分布会影响合并需要的 oversampling。
为什么会看到旧结果
分布式系统中:
- 写入先到某 replica;
- 其他 replica 延迟应用;
- 查询读到尚未更新的副本;
- 并发两次 upsert 在不同 replica 顺序不同;
- 删除与旧 upsert 交错;
- 新索引/segment 尚未 searchable;
- shard 正在转移或恢复;
- cache 仍保留旧候选。
“写 API 返回成功”必须映射到明确确认级别,不能自然推导“所有后续查询都立刻只见新版”。
先写业务语义
不要先选 quorum/strong/eventual名词。先回答:
- 写成功需要几个副本或哪个 leader 确认?
- 同一用户写后立即搜,必须 read-your-writes 吗?
- 新 revision 最迟多久可搜索?
- 两个客户端并发写同一 point,按 source revision、leader order 还是 last-write-wins?
- Delete 与旧 upsert 乱序时谁赢?
- 节点/网络故障时优先可用还是拒绝不确定读写?
- 查询是否必须读到 majority/all 一致结果?
- Collection schema/alias 修改与 point 写入是否有相同保证?
- 索引构建未完成时是 exact fallback、旧索引还是拒绝?
用具体时间、版本和错误行为写 SLO,例如:
Source revision r8 提交成功后 30 秒内,授权查询不得再返回 r7;超时则撤下文档并告警。
这比“最终一致”更可验收。
写确认、读一致性与 ordering
产品可能提供:
- write acknowledgement count/factor;
- leader/strong ordering;
- read one/quorum/majority/all;
- wait until operation applied;
- optimistic row/version 条件;
- transaction(单表、单 shard 或更大范围)。
代价通常是更强确认需要更多活跃节点、更多网络往返,并在分区时降低可用性。具体语义和失败时“是否部分应用”必须读产品文档并故障测试。
截至 2026-07-22,Qdrant 当前文档明确区分 collection topology/structure 的 consensus 与 point operations,并提供 write consistency、read consistency、write ordering 选项;Points 文档还给出 payload version 条件更新。其默认与范围是该版本产品事实,不是所有向量库模型,条件更新也不会替应用理解业务 source identity。pgvector 则继承 PostgreSQL transaction/replication 体系,语义完全不同。
并发更新用版本解决业务冲突
数据库 ordering 只能决定执行顺序,不知道哪个 source revision 更正确。若 source_revision 是 opaque identity,就不能按字典序或数字后缀比较;但同一 point 跨生命周期仍必须由 canonical source 保证 identity 不复用。应用需要由 canonical source/工作流确定合法 successor,并记录:
source_revision;- tombstone 的
deleted_source_revision; - expected-current source revision;
- delete event ID / fence;
- published pointer。
规则示例:
- Writer A 读取当前
r7,用expected=r7发布候选r8;成功后,仍提交expected=r7的 writer 全部 CAS 失败; - Delete 只有在 expected-current 匹配时才能写 tombstone,并持久化被删 source 与 delete event;
- Tombstone 默认拒绝后续 upsert;显式 resurrection 必须匹配旧 source/delete fence,并使用不同的新 source identity;
- 同 revision 但 vector、payload 或 hash 不同进入冲突队列;
- ACL revision 可独立且不得回退;
- query 只读 published revision。
这使 replica 恢复、重放和 shard transfer 仍能检查语义新旧,但只保存“当前 source + 当前 tombstone”不能解决 ABA。若生命周期出现 r1 → delete → r2 → delete → r1,很旧的 delete(r1, d1) 可能重新匹配。生产系统需要不可复用 generation、单调 row/store version 或永久 history fence;append-only lifecycle/audit log 还要在 resurrection 移除当前 tombstone 后继续保留历史事件。
读己之写(Read-your-writes)
用户上传文档后立即查询,可能需要看到它。实现方式包括:
- 等待写入达到所需确认和 searchable 状态;
- Query 携带 minimum revision/fence;
- Session 暂时路由到已确认 replica;
- 在索引可见前合并小型 recent-write buffer;
- UI 明确“处理中”,完成后才开放搜索。
不要用固定 sleep 5 秒代替状态和超时。记录 operation/event ID,从写确认追踪到 search canary。
同一进程中的多个 repository/store 实例也可能持有不同快照;“单进程”不等于“单实例”。读路径若无法证明本地 snapshot revision 仍是当前值,就应重新加载一致快照,或像本库玩具项目一样抛出冲突并 fail-closed。尤其在 delete 或 ACL 收紧后,不得由 stale instance 返回旧 point;summary/no-op 也不能把过期缓存误报为当前成功状态。
分片键权衡
随机/哈希 point ID
优点:均匀分布。缺点:tenant 查询可能 fan-out 全 shards。
Tenant ID
优点:tenant 查询可定向,隔离直观。缺点:大租户成为热点或超过单 shard。
Tenant group / composite key
可将大租户拆 buckets、长尾租户分组;路由和重平衡更复杂。
选择依据:
- tenant size/增长长尾;
- query 是否跨 tenant;
- filter 组合;
- 热点与并发;
- 单 shard 容量/索引构建;
- reshard 能力和停机代价;
- data residency。
给当前数据均匀的键不一定适合两年后。建立每 shard point/bytes/QPS/P99/写入/索引大小监控和重分片阈值。
扩容与 shard transfer
扩容不是“加节点就自动变快”。需要确认:
- 新节点是否自动接收 shards;
- rebalance 手工还是自动;
- transfer 复制 records 还是 snapshot/index;
- 目标是否重新建 ANN/量化;
- transfer 期间写如何排队/重放;
- 切换 ordering;
- 峰值磁盘/网络;
- 失败回滚;
- downscale 前是否已移走所有唯一 shard。
Qdrant 当前文档列出多种 shard transfer 方法及不同 index/ordering/资源属性;这些细节会变化,应按实际版本运行演练。
副本健康与降级
监控:
- active/partial/dead replica;
- replication lag;
- write acknowledgement failures;
- shard under-replicated;
- leader/consensus changes;
- query partial failure;
- rebuild/optimization backlog;
- disk high watermark;
- top-k 返回数异常。
失败时定义:
- 是否允许 partial result;
- 是否降级 exact/关键词/缓存;
- 是否拒绝写;
- 是否撤下可能旧权限的数据;
- 如何通知调用方而不是伪装完整成功。
权限和删除场景通常不应以可用性为由返回不确定旧数据。
故障测试
| 场景 | 验证 |
|---|---|
| 一个 replica 停止 | 读写可用性、确认级别、告警 |
| 网络分区 | 哪一侧接受写、恢复后冲突 |
| 两个 writer 都基于 r7 更新 | 首个 CAS 成功,另一个 expected=r7 的写明确冲突 |
| delete fence 与迟到 upsert | Tombstone 默认拒绝复活;只有匹配 fence 的显式 resurrection 可进入下一版本 |
| ACL/delete 后 stale store instance 查询 | 读前验证 snapshot revision;变化时 reload 或 fail-closed,不返回旧权限/旧 point |
| source identity 跨生命周期复用 | Generation/history ledger 识别 ABA,旧事件不得因身份再次相等而成功 |
| shard transfer 中写入 | 无漏项/重复/旧 ACL |
| 新索引构建 | searchable lag 与 fallback |
| 磁盘满 | 写失败清楚,不损坏旧状态 |
| 协调节点超时 | partial results 不伪装完整 |
| 恢复旧 snapshot | 删除日志和新 revision 重放 |
使用测试数据和隔离环境,不在生产随意断节点。
常见错误与排查
- 加 replica 当备份:补独立快照、恢复和误删保护。
- 默认设置当业务保证:把写/读/ordering 映射到场景测试。
- 并发靠 last-write-wins:source revision/tombstone 才知道业务新旧。
- 把 opaque identity 当作可复用值:canonical source 保证不复用,并增加 generation/history fence 防 ABA。
- 单进程就忽略 stale read:多实例仍会缓存旧快照;search、summary 与 no-op 都要校验 revision。
- 按 tenant 分片后大户热点:拆 bucket 或专属 shard/collection。
- 加节点没有容量变化:shards 未转移/重平衡。
- 部分 shard 超时仍返回 200:定义 partial response 和上游降级。
- 睡眠后验证可见性:改 operation ID + revision canary。
- 权限更新期间优先可用:对不确定旧 ACL fail-closed。
练习
- 为“上传后 30 秒可搜,只能见最新 revision”写端到端验收。
- 画 4 shards × 2 replicas 的查询 fan-out 与合并。
- 不比较 revision 字符串大小,为“两个 writer 基于 r7 更新 + 一个绑定当前值的 delete event”设计所有乱序处理规则,再加入 identity 复用的 ABA 反例。
- 比较 tenant shard key 与随机 key 在长尾租户下的热点/fan-out。
- 写一次 shard transfer 演练的容量、写入、query 和回滚观察项。
- 为一个 replica 失联定义读写降级与安全边界。
掌握检查
- 我区分 shard 的容量/并行与 replica 的可用性。
- Replication 不代替 backup。
- 业务语义用 revision/时间/SLO 写清,不只用一致性名词。
- Database ordering 与 expected-current source CAS 共同防止乱序覆盖;opaque revision 不做字符串大小比较。
- Source identity 跨生命周期不可复用;generation/history ledger 防止只看当前值导致 ABA。
- Read-your-writes 用状态/fence,不用固定 sleep。
- Stale instance 在 search/summary/no-op 前校验 snapshot revision,权限与删除不确定时 fail-closed。
- 分片键基于 query、增长、热点、residency 与 reshard 能力。
- 扩容包含 shard transfer/rebuild/rebalance 验证。
- 故障时 partial/旧权限结果不会伪装成功。
小结与下一步
副本提高可用性,却会同步复制逻辑错误;真正的灾难恢复需要独立快照、保留与演练。下一步:[[Knowledge/AI Agent Engineer/docs-CN/向量数据库/06-备份恢复与迁移|备份、恢复与迁移]]。
参考资料
- Qdrant: Distributed deployment
- Qdrant: Points
- PostgreSQL: High Availability, Load Balancing, and Replication
[[Knowledge/AI Agent Engineer/docs-CN/知识库构建/00-目录|知识库构建]]
来源获取日期:2026-07-22。返回 [[Knowledge/AI Agent Engineer/docs-CN/向量数据库/00-目录|向量数据库目录]]。