Migration readiness checks read FT.INFO with string keys, so under RESP3 they always report a healthy index

クローズ
#713 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 5 日以内に返信

@Kuang-xianxin がすでに取り組んでいます。

2026年9月6日 から。

  • #730 @Kuang-xianxin による — オープン

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
74/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python, redis
領域
databases

調査の方向性

redisvl/migration/executor.py:254 の readiness checks と、redisvl/migration/async_executor.py の対応するロジックから始め、両者の生の FT.INFO 処理を SearchIndex.info() および planner.py:216 と比較します。RESP3 の応答が実際の failure と indexing の値を返し、失敗したインデックスが同期および非同期の両方のマイグレーションパスで既存の SCAN フォールバックを使用すれば完了です。

索引モデルが issue の本文から書いたものです。

説明

On a RESP3 connection the migration executor's readiness check concludes that every index is fully built with zero indexing failures, whatever the server actually reports. It therefore skips the SCAN fallback that exists precisely to avoid missing documents. This is a silent wrong answer rather than a crash, which is what makes it worth fixing ahead of louder failures: the check passes, the migration proceeds, and documents the server could not index are never enumerated.

Reproduction

Measured on Redis 8.4.6 with redis-py 8.1.0, RedisVL at edcc78e. The index covers five hashes: three index cleanly, and two carry a malformed vector blob, so the server reports hash_indexing_failures of 2.

=== RESP2 ===
  actual hash_indexing_failures on server: 2
  info.get("hash_indexing_failures", 0) as the executor reads it: 2
  keys enumerated (5): ['f:bad0', 'f:bad1', 'f:ok0', 'f:ok1', 'f:ok2']

=== RESP3 ===
  actual hash_indexing_failures on server: 2
  info.get("hash_indexing_failures", 0) as the executor reads it: 0
  keys enumerated: none, raises KeyError: slice(1, None, None)

Under RESP2 the guard fires, logs Index 'fail_idx' has 2 indexing failures. Using SCAN for complete enumeration., and enumerates all five keys including the two that failed to index. Under RESP3 the guard reads zero failures, skips SCAN, and hands control to the aggregate path.

Cause

FT.INFO comes back with bytes keys under RESP3. Measured on both redis-py 6.3.0 and 8.1.0:

[b'index_name', b'index_options', b'index_definition', b'attributes', b'num_docs', ...]

redisvl/migration/executor.py:254 reads that dict with string keys:

failures = int(info.get("hash_indexing_failures", 0) or 0)
percent_indexed = float(info.get("percent_indexed", 1.0) or 1.0)

Both lookups miss, so both defaults apply. The defaults were chosen to mean "nothing is wrong", so the guard concludes the index is complete and failure-free and takes the fast path. redisvl/migration/async_executor.py:107 and :108 are the same code, and async_executor.py repeats the shape around line 222, where _enumerate_with_scan reads prefixes out of the same raw info reply.

The proximate cause is that these sites call client.ft(index_name).info() directly rather than going through RedisVL's own wrapper. SearchIndex._info in redisvl/index/index.py passes the reply through convert_bytes, so SearchIndex.info() returns string keys and the right value on the same RESP3 client:

>>> index.info()["hash_indexing_failures"]
2

Measured on the same build and the same index that produced the zero above. The planner already uses that wrapper, since redisvl/migration/planner.py:216 calls index.info(), so its readiness checks at lines 157 and 174 read correctly. Only the executor's raw calls are wrong, in both the sync and async variants.

Suggested fix

Route the executor's readiness checks through convert_bytes, or through SearchIndex.info(), so that the executor and the planner read FT.INFO the same way. Reading one field under two conventions in two files of the same package is the underlying problem, and normalising at a single point fixes every call site at once. The defaults are worth revisiting too: a lookup that misses should not be indistinguishable from a server reporting good news.

Related

The path this bug hands control to is itself broken on RESP3: see #714, where the aggregate cursor read raises KeyError, which is the exception shown in the reproduction above. The two cannot both surface under RESP2, because a correct readiness check diverts to SCAN before the aggregate code runs. Under RESP3 this bug removes the guard and #714 then raises.

主要言語
Python
スター
432
フォーク
116
平均マージ
8日 1時間
マージ済み PR(30日)
29

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

redis/redis-vl-python のほかの issue

redis/redis-vl-python の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。