Skip to content

DOC-7015 Stop HSCAN/HVALS examples depending on hash field order - #3899

Merged
andy-stark-redis merged 1 commit into
mainfrom
DOC-7015-hscan-field-order
Aug 28, 2026
Merged

DOC-7015 Stop HSCAN/HVALS examples depending on hash field order#3899
andy-stark-redis merged 1 commit into
mainfrom
DOC-7015-hscan-field-order

Conversation

@andy-stark-redis

@andy-stark-redis andy-stark-redis commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

DOC-7015

What

Redis doesn't guarantee hash field order under hashtable encoding. Forced hash-max-listpack-entries to 0 and confirmed (5 trials each, both encodings) 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 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.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 when both land — trivial to resolve (apply both edits), but flagging so it's not a surprise.

Two unrelated bugs found, not fixed here

  1. 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 — 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 (an 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.
  2. Maven's surefire runs stale compiled classes. work/lettuce-reactive/target/test-classes/ still had a HashExample.class from an unrelated, earlier build; run_maven_java's cleanup only removes stale .java sources, not compiled classes, so surefire ran it alongside the current test and produced a spurious, unrelated failure. Removing the stale .class file fixed it for this session — the harness doesn't clean target/ between runs, so it will recur.

Both are captured as Gaps trailers on the commit for follow-up tickets.

Verification

  • Each fix isolated and run 5 times under forced hashtable encoding (hash-max-listpack-entries=0) against a fresh Redis 8.8 container — all pass.
  • Re-verified under default (listpack) encoding — all pass.
  • Ran the full modified files through build/example-test-harness/run.sh under both encodings (cmds_hash for all 4 fixed clients, hash_tutorial/stream_tutorial for ruby) — all PASS.
  • Local Redis restored to its original 7.2.7 state afterward.

🤖 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#inspect formatting.

HSCAN / HVALS (DOC-7015): In cmds_generic scan4, 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.rb and dt_stream.rb change documented inspect strings 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.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

DOC-7015

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

🧠 Redis Memory

Found 7 related items from repository history (2 new this commit):

Memory updated at 7012a20

@dwdougherty dwdougherty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

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
andy-stark-redis force-pushed the DOC-7015-hscan-field-order branch from 889e69e to 7012a20 Compare August 28, 2026 14:02
@andy-stark-redis
andy-stark-redis merged commit 62383fc into main Aug 28, 2026
92 checks passed
@andy-stark-redis
andy-stark-redis deleted the DOC-7015-hscan-field-order branch August 28, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clients Client library docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants