v2.38.0 - 2026-09-08
π Features
- feat: implement Dragonfly's
CL.THROTTLErate-limiting command, ported from Dragonfly's own code so
the quirks match β millisecond-ceiling time fields,remainingtruncating towards zero, and an
over-sized request reportingretry_after = -1without writing the key (#536) - feat(dragonfly): match its errors, validation and limits β backticked unknown-command errors,
unsignednumkeys/COUNT,SCAN COUNT 0falling back to the default batch size,SMOVEchecking
its destination type up front, noLCSat all, a relative expiry past2**28-1seconds clamped
while an absolute one is rejected, hash-field TTLs capped at2**26seconds, and strings capped at
256MB rather than Redis' 512MB (#555, #556) - feat(dragonfly): match the replies it sends β doubles from
INCRBYFLOAT/HINCRBYFLOAT, empty arrays
from a timed-out blocking pop and from anXREAD/XREADGROUPthat matched nothing,ZPOPMIN/
ZPOPMAXalways paired under RESP3, flatWITHSCORESfromZUNION/ZINTER, a null
first-entry/last-entryfor an empty stream,-1asXINFO GROUPS' unknown-lag sentinel,
XPENDINGlooking up the key before the group, and one namespace for shard and plain pub/sub
channels (#557, #558, #559) - feat(dragonfly): match its command behaviour β
SORTwithout->hash-field patterns and without
Redis' weight tie-break,COPYwithout aDBoption,GEORADIUS/GEOSEARCHin geohash order
unless sorted, a watched key dirtied by any write, Lua 5.4 scripting (non-integral numbers as
doubles, Redis 6-style error wrapping, admin commands refused inside scripts), and its own JSON
quirks (legacy paths wrapped like JSONPath, no filter expressions) (#560, #561, #562, #563)
π Bug Fixes
- fix:
redis.callin a Lua script now returns RESP2 shapes whatever protocol the client negotiated,
matching real Redis, where a script must opt into RESP3.redis.setresp(2)/redis.setresp(3)are
now supported and reset to RESP2 for each run. Dragonfly has noredis.setresp, so fakeredis does
not expose one when emulating it, and a script returning{double=...}there answers with an empty
array the way the real server does (#543) - fix:
XCLAIM/XAUTOCLAIMnow move an entry's pending count to the claiming consumer instead of
leaving it on the previous owner, so a laterXACKno longer drives that count negative (#548) - fix:
XACKno longer raises a raw PythonKeyErrorwhen a pending entry has no live consumer, and
XGROUP DELCONSUMERnow removes the entries that consumer owned, returning how many it dropped
(#549) - fix:
XCLAIMnow honoursRETRYCOUNT, andXCLAIM/XAUTOCLAIMhonourJUSTID, when updating an
entry's delivery counter.RETRYCOUNTtakes precedence overJUSTID, and a negativeRETRYCOUNT
means "not given" (#544) - fix: cancelling a blocking async command (
BRPOP,BLPOP,XREAD BLOCK 0, ...) no longer leaves
the connection unusable β it went back to the pool with its socket still paused, so every later
command on it hung (#471) - fix:
XAUTOCLAIMnow returns the cursor its next scan should start from, and0-0once the pending
list is exhausted (#547) - fix:
SMOVEno longer raisesWRONGTYPEfor a wrongly typed destination when the source key is
missing,BITCOUNTreports the syntax error before decoding its range arguments on a pre-7.0
server, andGEORADIUSreturns an unsorted reply in geohash order rather than insertion order
(#553) - fix: reap
TcpFakeServerhandler threads when a client disconnects (#541)
π§° Maintenance
- docs:
docs/dragonfly-support.mdnow writes up every Dragonfly divergence fakeredis emulates, and
the handful it does not (#564) - test(hypothesis): the stateful machines now run against Dragonfly in CI, with the non-deterministic
corners kept out (skiplist-encoded sorted sets, hash-order aggregation,SAVE, short-circuiting set
intersections) (#564)
New Contributors
- @ProchazkaDavid made their first contribution in #547
- @arththebird made their first contribution in #541
Full Changelog: v2.37.1...v2.38.0