我有一个使用 Redis 搜索的 Redis 缓存和一个包含 float32 值的 512 个元素向量的 HNSW 索引。
它的定义是这样的:
schema = (
VectorField(
"vector",
"HNSW",
{
"TYPE": "FLOAT32",
"DIM": 512,
"DISTANCE_METRIC": "IP",
"EF_RUNTIME": 400,
"EPSILON": 0.4
},
as_name="vector"
),
)
definition = IndexDefinition(prefix=[REDIS_PREFIX], index_type=IndexType.HASH)
res = client.ft(REDIS_INDEX_NAME).create_index(
fields=schema, definition=definition
)
我可以通过直接将
vector.tobytes()
的结果写入其中来将 numpy float32 向量插入到该索引中。然后我可以使用向量相似性搜索准确地查询这些相同的向量。
尽管工作正常,但当我使用
client.hget(key, "vector")
从缓存中读取这些向量时,我得到的结果是可变数量的字节。当我插入这些向量时,它们肯定是 512 个元素,但有时它们返回的字节数甚至不是 4 的倍数!那时我无法将它们解码回 numpy 向量。
我无法判断这是一个错误,还是我做错了什么。不管怎样,有些事情显然是不对的。
编辑:我发现损坏的记录实际上并不在索引中(如果我解释正确的话)。
我通过运行检查记录是否在索引中
client.ft(REDIS_INDEX_NAME).execute_command("FT.SEARCH", REDIS_INDEX_NAME, "*", f"INKEYS", "1", key)
当记录不在索引中时,这不会返回任何内容。我现在怀疑我是否以某种方式用一段已修复的旧代码将一些损坏的记录写入了该数据库。这可能就是解释。
编辑 2:损坏的记录按插入时间均匀分布在整个数据库中,因此这不是一些有错误的旧代码的问题,现已修复。
我发现了这个问题。我一直在使用这个向量索引来进行重复数据删除(通过在添加新记录之前检查与新记录具有高余弦相似度的记录的索引)。在此过程中,我有时会更新记录上的其他字段。
如果发现接近重复的情况,我将更新记录上的非矢量字段并将其写回。问题是我在执行重复检查时读取了整个记录。 Redis 尝试将向量的原始字节表示形式解码为字符串,在大约 50% 的情况下,该向量无法解码为字符串。它不会引发错误,而是返回损坏的向量。
因为在将搜索返回的字段添加回索引之前我没有仔细修剪它们,所以我将损坏的向量添加回索引中。
我确实应该为此承担一些责任,但事实上,RedisSearch 无法解码字段,然后返回损坏的字段(没有任何错误消息),这对我来说似乎是一个错误。特别是因为这是该领域矢量搜索的结果。客户端很容易自动确定该字段不应被解码。