2.4 数据存储与管理


2.4 数据存储与管理

本节摘要:前几节都在讲"快",这一节补上"稳"。Qdrant 用段把数据组织成不可变的小块,配合预写日志兜住崩溃时的未落盘数据,用快照做整库备份与迁移,再用分片和副本实现水平扩展与高可用。本节讲清这四块机制各自解决什么问题、怎么配合,以及在一致性和可用性之间该如何取舍。

本节导读

阅读完本节,你应当能够:

  1. 解释段机制如何用"不可变 + 后台合并"换来读写分离和高效恢复。
  2. 说明预写日志在崩溃恢复中扮演的角色。
  3. 区分快照与副本在"备份"这件事上的不同用途。
  4. 描述分片与副本如何实现水平扩展和高可用。
  5. 理解在一致性与可用性之间做取舍的基本思路。

一、问题与直觉:快是锦上添花,稳是底线

一个向量数据库能毫秒级返回结果当然好,但如果一断电数据就没了、一宕机服务就断了,再快也没人敢用在生产上。存储与管理这层,要回答的是几个朴素却致命的问题:机器突然断电,内存里还没写盘的数据怎么办?磁盘坏了、节点挂了,服务还能不能继续扛?数据要迁移到另一个环境,怎么整体搬走?

这三个问题分别对应预写日志、副本、快照三样东西。它们不直接提升查询速度,但决定了这套系统能不能"睡得着觉"。我见过不少团队把精力全花在调 HNSW 参数上,直到一次停电打回原形,才意识到数据持久化这块根本没认真配。所以这一节虽然排在本章最后,却是生产化的第一道门槛。

Qdrant 在这层的设计思路很清晰:把"写"和"读"解耦,把"临时"和"持久"分层,把"单点"和"集群"分开。拆开来看,就是下面要讲的段、预写日志、快照、分片与副本。

二、核心原理:四块拼图

段:不可变的小块

Qdrant 不会把整个集合写进一个巨大的文件里,而是拆成一个个段。新数据先进内存里的可变段,攒到一定规模或时间就冻结成不可变段,落盘。更新和删除不在原段上改动,而是在新段里记一笔——更新等于"新点 + 旧点标记删除",删除等于"直接标记删除"。这些逻辑删除的"墓碑"由后台合并定期清理,把多个小段合成大段,顺手把已删点和重复点物理移除。

这套"不可变 + 合并"的玩法,好处是实打实的:读操作面对的是稳定不变的段,写操作只碰可变段,两者天然隔离,不用为并发加一堆锁;崩溃后恢复也简单,因为落盘的段本身是完整一致的,只需结合日志补上没落盘的那部分。

段合并的触发与代价

段攒多了,查询要扫的段也变多,性能会下降,所以后台会定期把多个小段合成大段。合并不是免费的:它要读旧段、写新段,占用 IO 和 CPU,合并高峰时可能和在线查询抢资源,所以通常放在低峰触发、并限制并发。理解了"合并有代价",你就明白为什么刚做完大批量删除后,查询可能短暂变慢——那是后台正在清理墓碑、重整段结构。

预写日志:给崩溃留一条后路

预写日志(Write-Ahead Log,简称 WAL)的规则很简单:任何修改在真正落到数据文件之前,先追加写进日志。这样哪怕写内存写到一半断电了,重启后只要重放日志,就能把内存状态恢复到最后一次成功操作之后的样子。日志是顺序追加的,写起来极快,对性能的影响很小,却把"崩溃丢数据"的风险兜住了大半。

预写日志的落盘时机

日志要真正"写穿"到磁盘才算数,不能只留在操作系统的页缓存里——否则断电时页缓存一丢,日志等于白写。这里的取舍在于:每条写入都强制刷盘最安全,但磁盘同步是慢操作,会明显拖吞吐;把刷盘频率降低、攒一批再刷,速度快,但崩溃时可能丢最后一小段。Qdrant 的默认行为偏向"够快也够稳"的中间态,对数据零丢失有强要求的场景,再显式收紧刷盘策略。

快照:给整库拍一张照片

快照是某个时间点上集合数据和索引状态的完整副本,用途是备份、迁移和搭建测试环境。它和预写日志的区别在于粒度:日志是一条条操作,快照是一整个集合。要搬库、要恢复一个确定的历史状态,快照比逐条重放日志快得多。快照可以落在本地,也可以落到远程存储。

快照的全量与增量

快照可以是全量的,也可以是增量的。全量快照每次把整个集合打包,简单但慢、占空间;增量快照只记录相对上次快照的变化,省空间但恢复时要叠加多层。生产上常见的是"定期全量加频繁增量"的组合:全量做锚点,增量补细节。迁移到新环境通常用全量,日常备份用增量即可。

分片与副本:把单点变成集群

数据量大到单机装不下,就按点 ID 哈希把集合切成多个分片,分片分散到不同节点,存储和计算能力就能水平扩展。为了扛住节点故障,每个分片再配多个副本,副本落在不同物理机上互为备份;某个节点挂了,它的分片能被其他节点上的副本接管,服务不中断。

分片哈希与数据倾斜

分片大多按点 ID 哈希,让数据均匀撒到各分片。但哈希均匀不等于负载均匀——如果热点集中在某几类点,或某类查询频繁打某个分片,就会出现倾斜。分片数也不是越多越好:分片多了,跨分片查询的扇出和合并开销会变大。选分片数要看数据规模、节点数和查询模式,通常先保守、后扩容,避免过早拆得太碎。提前按业务主键设计好点 ID 的分布,能显著降低后期倾斜的概率;实在倾斜了,也要先分清是数据分布问题还是查询热点问题,再决定是否重分片。

一致性:副本同步绕不开的取舍

副本要同步,就必然要回答"读到的是不是最新的"。强一致读最省心,但代价是每次读都可能要等多数副本确认,延迟高、可用性差;最终一致读得快、扛得住故障,但可能短暂读到旧数据。Qdrant 走的是偏实用的路线——多数写入场景追求快速的写入确认和良好的副本同步,读路径的取舍留给具体配置。理解这一点,就理解了"为什么有些分布式数据库写快读慢、有些反过来"。

落到副本同步上,常见做法是多数派确认:一次写入只要过半副本确认就算成功,剩下副本异步追赶。这样既避免了"两个副本各持一半最新状态"的分裂脑,又把写延迟控制在多数派的往返时间内。副本数选三,通常意味着写要等两副本确认、能容忍一个节点挂掉;这是成本和可靠性之间一个很常见的平衡点。

三、工程实践要点:怎么把这层配稳

三样保护机制,各管一摊

很多人把预写日志、快照、副本混为一谈,其实它们的职责边界很清晰。

机制 解决的问题 粒度 典型用途
预写日志 崩溃时丢内存数据 操作级 逐条重放 每次写入 默认开启
快照 整体备份与迁移 集合级 时间点 定时备份 环境迁移
副本 节点故障 高可用 分片级 自动切换 持续同步 扛单点故障

💡 关键直觉:日志、快照、副本不是三选一,而是三道不同方向的保险。日志防"半路断电",快照防"想回到过去",副本防"一台机器死了"。生产环境里它们应该同时存在、各司其职,而不是指望其中某一个包打天下。

快照要有策略,别想起来才拍

快照的价值在"定时"。隔多久拍一次、保留几份,应该按数据的重要性和可承受的丢失窗口来定。拍得太密占存储、拖性能;拍得太疏,恢复时丢得就多。把快照落到远程存储(而不是和主数据同一块盘)是基本动作,否则磁盘一起坏,快照也白拍。

副本数要权衡成本和可靠性

副本不是越多越好。每个副本都是实打实的存储和同步开销。多数生产场景用三副本能扛住一台机器故障,同时把写放大控制在可接受范围。具体选几副本,取决于你能承受几个节点同时挂、以及愿意为同步付出多少写延迟。

先确认写入成功,再往下走

写入路径上,WAL 是最后一道闸。若应用对"数据确实落稳了"有强要求,写入操作应等确认返回后再继续,而不是"发出去就不管"。异步写虽然快,但等于把一致性风险推给了应用层,自己心里要有数。

⚠️ 常见坑:把快照当成副本的替代品,认为"我定时快照了,节点挂了大不了恢复快照"。快照是时间点备份,两次快照之间的新数据照样会丢,而且恢复快照是"停机式"的,做不到副本那种秒级无感切换。高可用靠副本,快照只是兜底的最后一张牌。

下面这段概念代码展示了创建集合时如何配置副本数。

from qdrant_client import QdrantClient, models client = QdrantClient(":memory:") client.create_collection( collection_name="products", vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE), # 集群模式下,配置副本数来获得高可用 replication_factor=3, )

要点速记

  • 稳是底线:再快的查询,扛不住断电和宕机就没有生产价值。
  • 段用不可变换并发:不可变小段 + 后台合并,读写天然隔离、恢复简单。
  • 更新删除靠墓碑:不原地改,新段记一笔、旧点标删,合并时物理清理。
  • 预写日志兜崩溃:任何修改先落日志,重启重放即可恢复内存状态。
  • 快照管备份迁移:集合级时间点副本,用于搬库、回滚和搭测试环境。
  • 副本管高可用:分片多副本分散节点,单点故障自动切换、服务不中断。
  • 一致性有取舍:强一致读省心但慢,最终一致读快但可能短暂读到旧值。
  • 三样保险并存:日志防断电、快照防回不去、副本防单点,别指望一个包打天下。

到这里,本章的"数据生命周期"就闭环了:模型给身份、索引给速度、查询给条件、存储给稳定。下一章我们将离开原理,动手写客户端——看看这些概念在 Python 里是怎么变成一行行可执行的增删改查的。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U