Valkey findings
Background for D-5 and Q-DATA-1: Python integration tests against valkey/valkey-bundle, run on 2026-11-07 before the freeze. This run also covers the FT.* and ACL checks the plan review asked for.
Setup: throwaway containers, valkey/valkey-bundle:latest (digest sha256:c64007296a2a38398d4be63847e1c9890954e45574c16cbe607883ba71c6b985; Valkey 9.1.3 reporting redis_version:7.2.4; modules json 1.0.3, search 1.1.1 (ver 66049), bloom, ldap), Mosquitto 2, the unmodified example app plus tests/integration with redis-py (the Python code uses RESP3, protocol: 3 in core.py). Nothing was committed; temporary local edits made to probe causes were reverted.
| Area | Result |
|---|---|
JSON.SET/GET (documents, pipelines with transaction=True) |
Works. Dedupe, job order, work master and SFC execution DAOs pass |
Lua (EVALSHA scripts, redis.call('JSON.GET', ...) inside a script) |
Works |
Streams (XADD, consumer groups, XREAD BLOCK, XAUTOCLAIM) |
Works. The SFC push_command e2e test reaches job response, command dispatch and completion |
FT.CREATE ... ON JSON PREFIX ... SCHEMA $.path AS alias TAG, FT.INFO, FT._LIST |
Works. An unknown index returns Index with name '...' not found (an error reply, as the code expects) |
FT.SEARCH ... NOCONTENT LIMIT on RESP3 |
Different reply shape, breaks the Python code. Valkey-search returns the flat RESP2-style array [total, key1, key2, ...] even on a RESP3 connection. Redis returns a map (total_results, results, ...) there. redis-py either returns a raw list (code then fails with 'list' object has no attribute 'total') or raises in Result.from_resp3 ('list' object has no attribute 'items') |
| ACL | Works with a user limited to ~prefix:* plus the needed command categories and +ft.create +ft.search +ft.info. An FT.CREATE whose PREFIX is outside the user's key patterns is rejected with NOPERM (a good property) |
Integration run (24 tests; without the app and broker only the 4 dedupe tests run): with the example app and Mosquitto running, 17 failed and 7 passed; the app's publisher crashed in JobResponseDAO.retrieve_by_job_order_id on the FT.SEARCH reply and the failing tests are consequences of that. With a temporary list-shape workaround at the two FT.SEARCH call sites in redis.py, the SFC e2e test passed and 6 MES tests still failed at the same RESP3 parse step (Result.from_resp3, which expects a map). Conclusion: one root cause (the RESP3 FT.SEARCH shape); no other incompatibility seen in JSON.*, Lua, Streams or FT.* index creation. The MES tests were not re-run after the failure analysis, so they may hide further differences.
Consequences for the .NET port:
- StackExchange.Redis (RESP2 by default) sends
FT.SEARCHthroughExecute/ExecuteAsync, which returns the raw reply. Parse the flat array[total, key, ...]yourself and test it against valkey-search in S09. Do not rely on a map-shaped reply, and do not use RESP3 for search commands without re-checking the shape. Redis Stack/Redis 8 behave differently, so this parser is Valkey-specific and must be covered by an integration test. - Indexing is asynchronous in principle: in the probe a document written one second before the search was found, but timing was not measured. Poll with a bounded timeout in .NET tests instead of asserting immediately.
- Q-DATA-1:
JSON.SET/GETand the$.JobOrderID/$._scope/$._normalized_stateTAG index work against valkey-json 1.0.3 and valkey-search 1.1.1. Keeping JSON plus search through rawExecuteis viable; dropping them for plain strings plus Lua remains an option, now a design choice and not a necessity (S04 spike). - The image reports a Redis 7.2 compatibility version, so client feature probes by
redis_versionsee 7.2 (usevalkey_versionwhere a check is needed). - Housekeeping for the port: pin
valkey-bundleby digest in the .NET compose files; the Python test docstrings still say "Redis Stack" and need no change.