在AI基础设施的部署实践中,向量数据库(VDB)的性能和存储效率至关重要。许多用户在使用 Milvus、Qdrant 或 Weaviate 等VDB时会遇到一个令人困惑的问题:执行了大量Delete操作后,磁盘空间并没有按预期释放。这些未释放的空间就是我们常说的“墓碑数据”(Tombstone Data)。
本文将深入探讨VDB中墓碑数据产生的原因,并提供实操性极强的方法来彻底清理这些数据,释放宝贵的存储资源。
1. 墓碑数据产生的原因:逻辑删除与Segment机制
向量数据库为了追求极高的写入和查询性能,通常不会立即执行物理删除。它们普遍采用基于Segment或Partition的架构,这些Segment在创建后是不可变的(Immutable)。
当你执行一个Delete操作时,VDB实际上执行的是“逻辑删除”:
- 添加墓碑标记: 系统在内部索引或日志中,为被删除的向量记录一个“墓碑”或“删除标记”。
- 数据段不变: 包含该向量的原始数据Segment文件不会被修改或重写。
这意味着,只要包含该墓碑数据的原始Segment存在,即使该向量逻辑上已删除,它仍然占据着磁盘空间。这种设计提升了写入速度,但导致了磁盘空间滞后释放的问题。
2. 解决方案:强制执行 Compaction(数据压缩)
释放这些被墓碑标记占用的空间的唯一方法是执行Compaction(数据压缩或合并)。Compaction是VDB在后台执行的一个资源密集型过程,它会扫描一个或多个 Segment,将其中所有仍然有效(非墓碑)的数据合并到一个新的、干净的 Segment 中,然后将旧的、包含墓碑数据的 Segment 物理删除。
虽然许多VDB(如Qdrant)会自动在后台调度Compaction,但在大规模或高频删除操作后,为了立即释放空间,通常需要手动触发或调整Compaction策略。
以下以 Milvus(作为代表性的向量数据库)为例,展示如何手动触发Compaction。
3. 实操示例:使用 PyMilvus 触发手动 Compaction
Milvus 2.x 版本提供了明确的API接口来管理和执行Compaction任务。这对于需要精细控制资源释放的生产环境至关重要。
步骤一:安装依赖和建立连接
pip install pymilvus
from pymilvus import connections, utility
import time
# 替换为你的 Milvus 连接信息
HOST = "127.0.0.1"
PORT = "19530"
COLLECTION_NAME = "product_recommendations"
# 建立连接
try:
connections.connect(host=HOST, port=PORT)
print(f"成功连接到 Milvus: {HOST}:{PORT}")
except Exception as e:
print(f"连接 Milvus 失败: {e}")
exit()
# 假设我们已经执行了大量的删除操作,现在需要释放空间
print(f"开始对集合 '{COLLECTION_NAME}' 进行 Compaction...")
步骤二:执行手动 Compaction 任务
手动 Compaction 是一个异步操作,系统会返回一个任务ID,你需要跟踪其状态直到完成。
# 这是一个耗时的操作,建议在业务低峰期执行
try:
# utility.do_manual_compaction 触发 Compaction
compaction_id = utility.do_manual_compaction(COLLECTION_NAME, timeout=300)
print(f"Compaction 任务已提交,ID: {compaction_id}")
# 检查 Compaction 任务状态
while True:
state = utility.get_compaction_state(compaction_id)
# state.state.name 会返回 'Completed', 'Executing', 'Failed', 'Timeout' 等状态
print(f"\n当前 Compaction 状态: {state.state.name}")
# state.released_disk_size (新版本字段) 或通过监控指标查看空间释放情况
if hasattr(state, 'released_disk_size'):
# 将字节转换为兆字节进行显示
released_mb = state.released_disk_size / (1024 * 1024)
print(f"预估已释放/待释放的磁盘空间: {released_mb:.2f} MB")
if state.state.name == "Completed":
print("--- Compaction 任务成功完成,磁盘空间已释放!---")
break
elif state.state.name in ["Failed", "Timeout"]:
print(f"--- Compaction 任务失败或超时,请检查日志。状态: {state.state.name} ---")
break
time.sleep(10) # 暂停等待 Compaction 完成
except Exception as e:
print(f"执行 Compaction 失败: {e}")
4. Compaction 的最佳实践和考量因素
性能开销
Compaction 是一个 I/O 密集型和 CPU 密集型操作,因为它涉及到大量的数据读取、合并和写入。在执行手动 Compaction 时,应注意以下几点:
- 时机选择: 尽量在业务低峰期执行,以减少对线上查询性能的影响。
- 监控资源: 密切监控 VDB 实例的磁盘 I/O 和 CPU 使用率。
自动 Compaction 配置
如果你的VDB支持配置自动Compaction的阈值,建议根据你的删除频率进行调整。例如,设置当某个 Segment 中墓碑数据的占比(Deletion Ratio)达到 20% 时,自动触发合并。
避免频繁小规模删除
频繁地执行小规模的删除操作,会产生大量的微小墓碑标记,反而可能增加 Compaction 的负担。如果业务模式允许,考虑批量删除,然后集中执行一次 Compaction,效率更高。
汤不热吧