Skip to content

Redis 8.12 BLESS (eviction protection) support - #3261

Merged
mgravell merged 1 commit into
8.12from
marc/bless
Nov 9, 2026
Merged

mgravell merged 1 commit into
8.12from
marc/bless

Conversation

@mgravell

@mgravell mgravell commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Adds client support for the Redis 8.12 BLESS command family (redis/redis#15649), gated behind SER011 (Server_8_12).

API

[Flags] public enum BlessFlags { None = 0, NoEvict = 1 }

// IDatabase / IDatabaseAsync
bool       KeyBless(RedisKey key, BlessFlags bless, CommandFlags flags = CommandFlags.None);   // BLESS SET
bool       KeyUnbless(RedisKey key, BlessFlags bless, CommandFlags flags = CommandFlags.None); // BLESS CLEAR
BlessFlags KeyBlessFlags(RedisKey key, CommandFlags flags = CommandFlags.None);                // BLESS GET

// IServer
IEnumerable<RedisKey> BlessedKeys(BlessFlags bless, int database = -1, int pageSize = 250, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None); // BLESS SCAN

Also: RedisFeatures.Bless, and KeyNotificationType.Bless / Unbless for the bless / unbless keyspace events (class g). After merging main post-#3312 these sit at 66/67, after the SetBit..SDiffStore range that PR added, and both tokens are in the parser test table.

Notes

  • BLESS is replica-eligible, so GET can be served by a replica; SET/CLEAR are raised to primary-only per message.
  • Retry: SET/CLEAR are write-checked (the flag state converges), GET is read-only, SCAN uses the usual cursor rule.
  • BlessFlags.None and undefined bits throw client-side. Unknown tokens in a GET reply are ignored.
  • BLESS SCAN always sends COUNT, because the server default (1024) isn't our page size.
  • BLESS is excluded from the Twemproxy and Envoy command maps.
  • Design notes and deviations from the cross-client HLD: redis-developer/client-designs redis-oss/client-hld/bless/se-redis.md.

Testing

  • Unit tests: result processor, exact wire bytes, routing/retry, key-prefix forwarding.
  • Live tests (require: v8_12_0) pass on RESP2 and RESP3 against redislabs/client-libs-test:8.12-m02-int.
  • 8.12 RC2 (2026-11-03): all Bless tests (live, round-trip, processor, key-prefix) pass against redislabs/client-libs-test:8.12-rc2. The NO-EVICT token, BLESS SCAN argument order, the missing-key no such key errors and the bless/unbless keyspace events were all re-verified by hand on RC2 and match the implementation.

Resolved (was: open before marking ready)

  • RedisFeatures.v8_12_0 is now pinned to 8.11.241, which is what 8.12 RC2 reports — the 8.12.0 placeholder would indeed have made the live tests skip on RCs. The docker topology / CI default image is now redislabs/client-libs-test:8.12-rc2, with cluster-bus-port-protected-mode no added to the test cluster configs (RC2 introduces that check and otherwise refuses to start a plaintext cluster node).

RC2 ZSCAN duplicates (server behavior change, handled)

The only CI failures on the RC2 image are the 8 ScanTests.SortedSetScanLarge cases (all page sizes, RESP2+RESP3): a full ZSCAN iteration over a stable 2000-element sorted set returns 2001-2003 entries, with a few members duplicated. Reproduced with pure redis-cli against redislabs/client-libs-test:8.12-rc2 (deterministic per dataset; ZCARD is 2000, distinct members 2000), clean on redis:8.2, and HSCAN/SSCAN are unaffected — a ZSCAN behavior change in the 8.12 btree zset encoding, reported upstream as redis/redis#16158. SortedSetScanLarge now asserts the distinct member count, so CI is expected green; revisit if redis/redis#16158 is fixed before GA. Everything else was already green (6700+ tests).

@mgravell
mgravell marked this pull request as ready for review October 5, 2026 13:39
@mgravell
mgravell changed the base branch from main to 8.12 November 9, 2026 10:59
mgravell added a commit that referenced this pull request Nov 9, 2026
The hash-field-TTL idea (7.4's HEXPIRE) brought to sets (redis/redis#15938,
8.12-rc2), mirroring the HashField* family:

- SetAddAndSetExpiry (SADDEX): add members and/or (re)set their expiration in
  one atomic call; When.NotExists/Exists map to MNX/MXX, which are
  all-or-nothing across the batch, exactly as HSETEX's FNX/FXX; the bare form
  discards an existing TTL, KEEPTTL keeps it
- SetMemberExpire (SEXPIRE/SPEXPIRE/SEXPIREAT/SPEXPIREAT by precision) ->
  ExpireResult[], per member
- SetMemberGetTimeToLive (SPTTL) and SetMemberGetExpireDateTime (SPEXPIRETIME)
  -> long[]
- SetMemberPersist (SPERSIST) -> PersistResult[]
- KeyNotificationType.SExpired ('sexpired', side-effect block), so the 8.12
  notification - including on the subkey channels, whose payload names the
  lapsed member - parses as a known type; GetSubKeys() already handles the
  payload

Gated [Experimental] behind SER011/Experiments.Server_8_12 and
RedisFeatures.v8_12_0 (8.11.241), both shared with #3261/#3315; the test
docker image moves to client-libs-test:8.12-rc2 as in those PRs.

Round-trip tests pin the exact wire bytes; the live tests (HashFieldTests
mirror) pass against the 8.12-rc2 topology on both protocols. Plan document
in notes/set-member-expiry/.
mgravell added a commit that referenced this pull request Nov 9, 2026
…ST on IDatabase (#3340)

* Initial support for Redis 8.12 set-member expiration

The hash-field-TTL idea (7.4's HEXPIRE) brought to sets (redis/redis#15938,
8.12-rc2), mirroring the HashField* family:

- SetAddAndSetExpiry (SADDEX): add members and/or (re)set their expiration in
  one atomic call; When.NotExists/Exists map to MNX/MXX, which are
  all-or-nothing across the batch, exactly as HSETEX's FNX/FXX; the bare form
  discards an existing TTL, KEEPTTL keeps it
- SetMemberExpire (SEXPIRE/SPEXPIRE/SEXPIREAT/SPEXPIREAT by precision) ->
  ExpireResult[], per member
- SetMemberGetTimeToLive (SPTTL) and SetMemberGetExpireDateTime (SPEXPIRETIME)
  -> long[]
- SetMemberPersist (SPERSIST) -> PersistResult[]
- KeyNotificationType.SExpired ('sexpired', side-effect block), so the 8.12
  notification - including on the subkey channels, whose payload names the
  lapsed member - parses as a known type; GetSubKeys() already handles the
  payload

Gated [Experimental] behind SER011/Experiments.Server_8_12 and
RedisFeatures.v8_12_0 (8.11.241), both shared with #3261/#3315; the test
docker image moves to client-libs-test:8.12-rc2 as in those PRs.

Round-trip tests pin the exact wire bytes; the live tests (HashFieldTests
mirror) pass against the 8.12-rc2 topology on both protocols. Plan document
in notes/set-member-expiry/.

* SADDEX rides SetAdd: two Expiration overloads replace the four-per-arity SetAddAndSetExpiry surface

Review feedback (see the PR): Expiration already encodes EX/PX/EXAT/PXAT/KEEPTTL - exactly SADDEX's
expiration block - so the TimeSpan?/DateTime/keepTtl splits collapse and the surface is
SetAdd(key, value, Expiration, When, flags) and SetAdd(key, values, Expiration, When, flags), both
bool (+async): 8 members become 4, with no new method name. A required Expiration keeps overload
resolution away from the existing SetAdd members.

- Expiration.Persist is rejected (SADDEX has no PERSIST; SetMemberPersist is the way to remove
  member expirations) - previously it would have been sent as a bogus token.
- The overloads always send SADDEX: plain SADD is not an equivalent fallback for the no-expiry
  form, because bare SADDEX discards an existing member's TTL where SADD leaves existing members
  untouched; the XML docs call this out, and that the multi-value form reports only whether
  anything changed, not a count.
- The SetMemberExpire/GetTimeToLive/GetExpireDateTime/Persist family is unchanged (ExpireWhen's
  GT/LT cannot ride Expiration, and it mirrors the HashField shape).
- plan.md carries a revision note; SER011.md updated; all 50 SetMemberExpiry tests pass live
  against 8.12-rc2, 525 KeyPrefixed tests pass.

* Each set-member-expiry test deletes its key up front: SADDEX's changed-anything reply and the MNX/NX conditions make residue from a previous run (or the other protocol) look like a failure
Adds KeyBless/KeyUnbless/KeyBlessFlags (+async) on IDatabase and
IServer.BlessedKeys (+async) over BLESS SCAN, with the BlessFlags enum,
gated behind SER011 (Server_8_12). SET/CLEAR are raised to primary-only;
GET stays replica-eligible. Adds bless/unbless keyspace notification types.
@mgravell
mgravell merged commit 59ca0f1 into 8.12 Nov 9, 2026
3 checks passed
@mgravell
mgravell deleted the marc/bless branch November 9, 2026 16:23
mgravell added a commit that referenced this pull request Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant