先把当时的情况摆出来。用 qdrant-client 1.12 把两千条 1024 维向量一次 upsert 进去,客户端设了 30 秒超时,每次都在第 27 秒左右抛 ReadTimeout,但进控制台看 points_count 之后还在涨,重跑一次又提示主键已存在。

原因不止一个,得分开看。单次请求体过大,服务端解析与建索引的时间超过客户端读超时。也可能是集合同时开了标量索引和 payload 索引,写入时同步维护代价被放大。

另外,客户端超时后重试同一批 id,撞上服务端还在处理的请求。少数情况下是磁盘写入落在网络盘,fsync 延迟把每次批量写拖长。

上来先按 500 条一批拆分并打印每批耗时

python upsert_batches.py --batch 500 --collection faq --input vectors.npy

给客户端把超时调到 120 秒再试小批量

python -c "from qdrant_client import QdrantClient;c=QdrantClient('localhost',6333,timeout=120);print(c.get_collection('faq').points_count)"

接着用 wait=false 让写入异步完成再看计数

curl -s -X PUT 'http://localhost:6333/collections/faq/points?wait=false' -H 'Content-Type: application/json' -d @batch.json | jq '.result.status'

末了把主键改成幂等生成方式避免重跑报冲突

python -c "import uuid;print(uuid.uuid5(uuid.NAMESPACE_URL,'doc-1024#3'))"

怎么算真的好了,看的是同一批现象。所有批次都返回 completed,points_count 等于去重后的总条数,重跑同一批数据计数不增加也不再报主键冲突。

这里还有个坑。写超时不代表服务端失败,先看计数再决定要不要重试,用确定性 id 能让重试变成幂等操作。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。

作者 9823189