Migration executor cannot read an FT.AGGREGATE cursor over a RESP3 connection, raising KeyError
维护者通常 2 天内回复
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 68/100
调研方向
从 redisvl/migration/executor.py 中的 _enumerate_with_aggregate 和 _enumerate_indexed_keys 开始,然后对比 redisvl/migration/async_executor.py 中对应的匹配逻辑。使用 RESP3 客户端复现,并检查 FT.AGGREGATE 的回复与 RESP2 的回复有何不同;同时审查所引用的连接默认值。完成的标准是:在 RESP2 和 RESP3 下都能正常枚举索引键,并且在 aggregate 枚举失败时,SCAN fallback 仍然可用。
由索引模型根据 Issue 内容生成。
描述
MigrationExecutor._enumerate_with_aggregate cannot read an aggregate cursor over a RESP3 connection. It raises KeyError: slice(1, None, None), and because the caller catches only ResponseError the exception escapes the SCAN fallback and aborts the enumeration outright.
Reproduction
Measured on Redis 8.4.6 with redis-py 8.1.0, RedisVL at edcc78e.
KeyError: slice(1, None, None)
redisvl/migration/executor.py:335, in _enumerate_with_aggregate
for item in results_data[1:]:
redisvl/migration/executor.py:317 issues the command through the client rather than through the search helper:
result = client.execute_command(
"FT.AGGREGATE", index_name, "*", "LOAD", "1", "__key",
"WITHCURSOR", "COUNT", str(batch_size), "MAXIDLE", "300000",
)
Raw execute_command on the client gets no Search module response callbacks, because redis-py registers those on the object returned by client.ft(). The reply therefore arrives in whatever shape the protocol delivered it. Measured replies for one index holding three indexed documents:
protocol=2
[[1, [b'__key', b'f:ok0'], [b'__key', b'f:ok1'], [b'__key', b'f:ok2']], 0]
protocol=3, and byte for byte the same with no explicit protocol argument on redis-py 8
[{b'attributes': [], b'format': b'STRING',
b'results': [{b'extra_attributes': {b'__key': b'f:ok0'}, b'values': []}, ...],
b'total_results': 3, b'warning': []}, 0]
Both are two-element lists, so results_data, cursor_id = result at line 332 succeeds either way. The failure lands one line later: under RESP3 results_data is a dict, and dict[1:] raises KeyError with the slice object as the key. redisvl/migration/async_executor.py:184 and :187 are the same code and fail identically.
Worth noting that this is not a TypeError. dict.__getitem__ accepts a slice quite happily and reports it as a missing key, which is a weaker diagnostic than a type error would have been and makes the traceback read as though an index were absent.
Why this is more than a corner case
redis-py 8 negotiates RESP3 by default, so a caller who constructs a client with no protocol argument at all gets the failing shape. That is measured: the second reply above came from Redis.from_url(url) with no protocol keyword.
RedisVL currently masks the problem by forcing kwargs.setdefault("protocol", 2) at five points in redisvl/redis/connection.py (lines 640, 681, 734, 760 and 770), but that default reaches only clients RedisVL builds itself. A caller-supplied client keeps whatever protocol it negotiated. The default is also transitional, and this bug blocks removing it: the moment RedisVL stops pinning RESP2, every migration that reaches aggregate enumeration hits this path.
The fallback does not catch it
_enumerate_indexed_keys wraps the aggregate call in except ResponseError at around line 286 of executor.py. KeyError is not a ResponseError, so the SCAN fallback that exists to rescue a failed aggregate enumeration never runs and the caller sees the raw traceback.
Suggested fix
Issue the command through client.ft(index_name).aggregate(...) so that redis-py's own callbacks normalise the reply. If the raw command must stay, branch on the reply shape and read results and extra_attributes when handed a dict. Widening the caller's except clause beyond ResponseError is worth doing as well, though on its own it would only convert a crash into a quiet fall back to SCAN, which hides a genuine defect rather than fixing it.
Related
The readiness check guarding this same enumeration has an independent RESP3 bug, #713, and the two are worth fixing in one pass. They cannot both surface under RESP2, because a correct readiness check diverts to SCAN before the aggregate code runs. Under RESP3 the first removes the guard and this one then raises.
A smaller adjacent gap
_parse_sentinel_url in redisvl/redis/connection.py:929 reads only netloc and path, never query, so a Sentinel URL that specifies a protocol has it silently ignored:
>>> RedisConnectionFactory._parse_sentinel_url("redis+sentinel://host:26379/mymaster?protocol=3")
([('host', 26379)], 'mymaster', None, None, None)
Measured on the same build. The protocol=3 parameter is dropped without a warning. This is a separate defect from the cursor bug and could reasonably be split out, but it belongs to the same cluster of inconsistent protocol handling across connection paths.
- 主要语言
- Python
- 星标
- 432
- 派生
- 111
- 平均合并
- 4 天 22 小时
- 30 天内合并 PR
- 11
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
redis/redis-vl-python 的其他 Issue
-
exists() depends on exact FT.INFO error wording; an unrecognised "missing index" message makes create() never create the index (regression from #672)可能已有人在做 @nandanadileep 于 3 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 75/100
redis/redis-vl-python#773 ·
维护者通常 2 天内回复
-
Vector field with index_missing=True cannot be created可能已有人在做 @RaphaelFakhri 于 8 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
redis/redis-vl-python#770 ·
维护者通常 2 天内回复
-
Add Python 3.15 support可能已有人在做 @vishal-bala 于 10 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 65/100
redis/redis-vl-python#757 · 1 条评论 · 1 个 reaction ·
维护者通常 2 天内回复
-
SearchIndex.drop_keys does not validate cluster hash-tag co-location (inconsistent with drop_documents)可能已有人在做 @Hashim1999164 于 7 天前认领。 未关闭bug good first issue index
难度 2/5 1-3 小时 新手友好度 78/100
redis/redis-vl-python#601 ·
维护者通常 2 天内回复
-
documentation
难度 1/5 1 小时以内 新手友好度 62/100
redis/redis-vl-python#300 ·
维护者通常 2 天内回复
查看 redis/redis-vl-python 的全部 Issue
相似的 Issue
-
难度 1/5 1 小时以内 新手友好度 85/100
yzhao062/awesome-auditable-ai#45 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
-
难度 2/5 1-3 小时 新手友好度 65/100
NVIDIA/flare-local-inference#90 ·
维护者通常 1 天内回复
-
area/streaming comp/gateway P2 platform/telegram type/bug
难度 2/5 1-3 小时 新手友好度 78/100
NousResearch/hermes-agent#144897 ·
维护者通常 1 天内回复
-
request-theme
难度 1/5 1 小时以内 新手友好度 66/100
LizardByte/ThemerrDB#9575 ·
维护者通常 1 天内回复