Migration executor cannot read an FT.AGGREGATE cursor over a RESP3 connection, raising KeyError
Los mantenedores suelen responder en 5 días
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 68/100
Línea de trabajo
Empieza en redisvl/migration/executor.py, en _enumerate_with_aggregate y _enumerate_indexed_keys, y después compara la lógica de coincidencia correspondiente en redisvl/migration/async_executor.py. Reprodúcelo con un cliente RESP3 e inspecciona en qué se diferencian las respuestas de FT.AGGREGATE de las de RESP2; revisa también los valores predeterminados de conexión referenciados. Se considera terminado cuando la enumeración de claves indexadas funciona con RESP2 y RESP3, y el fallback de SCAN sigue disponible cuando falla la enumeración mediante aggregate.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- Python
- Estrellas
- 431
- Forks
- 107
- Merge medio
- 5 d 17 h
- PR fusionados (30 d)
- 16
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de redis/redis-vl-python
-
exists() depends on exact FT.INFO error wording; an unrecognised "missing index" message makes create() never create the index (regression from #672)Quizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
redis/redis-vl-python#773 ·
Los mantenedores suelen responder en 5 días
-
Vector field with index_missing=True cannot be createdPosiblemente ocupada @RaphaelFakhri la tomó hace 3 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
redis/redis-vl-python#770 ·
Los mantenedores suelen responder en 5 días
-
Add Python 3.15 supportPosiblemente ocupada @vishal-bala la tomó hace 5 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
redis/redis-vl-python#757 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 5 días
-
SearchIndex.drop_keys does not validate cluster hash-tag co-location (inconsistent with drop_documents)Posiblemente ocupada @Hashim1999164 la tomó hace 2 días. Abiertobug good first issue index
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
redis/redis-vl-python#601 ·
Los mantenedores suelen responder en 5 días
-
documentation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 62/100
redis/redis-vl-python#300 ·
Los mantenedores suelen responder en 5 días
Todos los issues de redis/redis-vl-python
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
ansys/pyansys-common-mcp#213 ·
Los mantenedores suelen responder en 3 días
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
freedomofpress/dangerzone#1594 ·
Los mantenedores suelen responder en 3 días
-
priority:P1 status:in-review type:bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
randomparity/kdive#3317 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area:ci
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
mesonbuild/meson#16281 ·
Los mantenedores suelen responder en 1 día