DOC-7015 Stop HSCAN/HVALS examples depending on hash field order - #3899
Merged
Conversation
Contributor
Contributor
🧠 Redis MemoryFound 7 related items from repository history (2 new this commit):
Memory updated at 7012a20 |
Merged
2 tasks
Redis doesn't guarantee hash field order under hashtable encoding. Forcing hash-max-listpack-entries to 0 (verified across 5 trials per client, both encodings) confirmed six still-fragile assertions across the five clients this ticket named: - redis-py: cmds_generic scan4's HSCAN NOVALUES list, cmds_hash's HVALS - node-redis: cmds_generic scan4's HSCAN entries and NOVALUES list, cmds_hash's HVALS - predis: cmds_hash's HVALS - ruby: cmds_hash's HVALS Fixed each by sorting (field-name lists and HVALS values) or pairing HSCAN's structured entries into an object (node-redis's scan4, matching the already-established ioredis/Go pattern of never trusting position). lettuce-reactive needed no changes — its scan4 and HVALS were already sorted; DOC-6968's list of five affected clients had that one stale, or it was fixed in a commit this ticket's description didn't track. Folded in from DOC-7014 (see that ticket's PR for the finding): dt_hash.rb and dt_stream.rb documented Ruby Hash#inspect output as `"key"=>"value"` (no spaces), but Ruby 4.0 prints `"key" => "value"`. Fixed all 27 lines, verified against a live run. This branch predates DOC-7014's merge, so these exact lines will conflict with DOC-7014's `>>> ` prefix change — trivial to resolve (apply both edits), but whoever merges second should expect it. Two unrelated bugs surfaced while verifying and are NOT fixed here: 1. cmds_generic's scan2 step asserts the last of five sequential SCAN calls returns exactly 18 matches (`assert len(keys) == 18` and equivalents). That split is not guaranteed — reproduced 0,0,0,0,19 and 1,0,0,1,17 splits across repeated fresh Redis 8.8 instances, never landing on DOC-6968's originally observed 0,0,0,1,18. This blocks every full-script run of cmds_generic for redis-py, lettuce-reactive and predis (uncaught exception halts the script before scan4 ever runs); node-redis is accidentally immune because its equivalent check uses `console.assert`, which doesn't throw. Verifying this ticket's actual scan4/HVALS targets required isolated snippets that skip scan1-3 entirely, bypassing this gate rather than fixing it. 2. Maven's surefire runs whatever compiled classes already sit in work/lettuce-reactive/target/test-classes/, not just the one file run_maven_java just copied — a stale HashExample.class from an earlier, unrelated build was still there and got executed alongside CmdsHashExample, producing a spurious failure with no connection to this change. Removing the stale .class file fixed it for this session; the harness doesn't clean target/ between runs, so this will recur. Learned: DOC-6968's assumption that scan2's five-call split is deterministically 0,0,0,1,18 doesn't hold — reproduced two different splits (0,0,0,0,19 and 1,0,0,1,17) across repeated fresh Redis 8.8 instances, so any assertion pinned to a specific per-call count is inherently flaky Constraint: don't add hash-max-listpack-entries=0 verification runs to CI without also fixing scan2's split assumption first — it will make cmds_generic flaky for redis-py, lettuce-reactive and predis regardless of this fix Gaps: run_maven_java's rm -f only clears stale .java sources, not compiled classes in target/test-classes — a class from an unrelated earlier example run can survive and get executed by surefire alongside the current one; worth its own harness fix Gaps: the scan2 cursor-split assumption (see Learned above) is a separate, real bug affecting at least redis-py, lettuce-reactive and predis's cmds_generic — worth its own ticket Ticket: DOC-7015
andy-stark-redis
force-pushed
the
DOC-7015-hscan-field-order
branch
from
August 28, 2026 14:02
889e69e to
7012a20
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DOC-7015
What
Redis doesn't guarantee hash field order under hashtable encoding. Forced
hash-max-listpack-entriesto0and confirmed (5 trials each, both encodings) six still-fragile assertions across the five clients this ticket named:cmds_genericscan4's HSCAN NOVALUES list,cmds_hash's HVALScmds_genericscan4's HSCAN entries and NOVALUES list,cmds_hash's HVALScmds_hash's HVALScmds_hash's HVALSFixed each by sorting (field-name lists and HVALS values) or pairing HSCAN's structured entries into an object (node-redis's scan4), matching the established ioredis/Go pattern from DOC-6968. lettuce-reactive needed no changes — its scan4 and HVALS were already sorted; DOC-6968's list of five affected clients had that one stale.
Folded in from DOC-7014
dt_hash.rbanddt_stream.rbdocumented RubyHash#inspectoutput as"key"=>"value"(no spaces), but Ruby 4.0 prints"key" => "value". Fixed all 27 lines, verified against a live run.>>>prefix change when both land — trivial to resolve (apply both edits), but flagging so it's not a surprise.Two unrelated bugs found, not fixed here
cmds_generic's scan2 step is inherently flaky. It asserts the last of five sequential SCAN calls returns exactly 18 matches. That split isn't guaranteed — reproduced0,0,0,0,19and1,0,0,1,17splits across repeated fresh Redis 8.8 instances, never landing on DOC-6968's originally observed0,0,0,1,18. This blocks every full-script run ofcmds_genericfor redis-py, lettuce-reactive and predis (an uncaught exception halts the script before scan4 ever runs) — node-redis is accidentally immune because its equivalent check usesconsole.assert, which doesn't throw. Verifying this ticket's actual scan4/HVALS targets required isolated snippets that skip scan1-3 entirely.work/lettuce-reactive/target/test-classes/still had aHashExample.classfrom an unrelated, earlier build;run_maven_java's cleanup only removes stale.javasources, not compiled classes, so surefire ran it alongside the current test and produced a spurious, unrelated failure. Removing the stale.classfile fixed it for this session — the harness doesn't cleantarget/between runs, so it will recur.Both are captured as
Gapstrailers on the commit for follow-up tickets.Verification
hash-max-listpack-entries=0) against a fresh Redis 8.8 container — all pass.build/example-test-harness/run.shunder both encodings (cmds_hashfor all 4 fixed clients,hash_tutorial/stream_tutorialfor ruby) — all PASS.🤖 Generated with Claude Code
Note
Low Risk
Changes are limited to local example scripts and expected-output comments; no application or library runtime behavior is modified.
Overview
Makes Redis client documentation examples deterministic where Redis does not guarantee hash field order, and updates Ruby sample output for Ruby 4.0
Hash#inspectformatting.HSCAN / HVALS (DOC-7015): In
cmds_genericscan4, node-redis now builds an object from HSCAN entries instead of asserting entry order, and both node-redis and redis-py sort field-name lists for NOVALUES-style checks. HVALS steps in node-redis, redis-py, predis, and ruby sort returned values (with brief comments) before print/assert, matching the existing ioredis/Go approach.Ruby inspect (folded from DOC-7014): Comment-only fixes in
dt_hash.rbanddt_stream.rbchange documentedinspectstrings from"key"=>"value"to"key" => "value"so examples match Ruby 4.0 output; runtime assertions are unchanged.Reviewed by Cursor Bugbot for commit 7012a20. Bugbot is set up for automated code reviews on this repo. Configure here.