Skip to content

Let Redis queue users choose LIST or ZSET storage - #1447

Open
danielgerlag wants to merge 4 commits into
masterfrom
cursor/redis-queue-zset-8044
Open

danielgerlag wants to merge 4 commits into
masterfrom
cursor/redis-queue-zset-8044

Conversation

@danielgerlag

@danielgerlag danielgerlag commented Sep 8, 2026 •

Copy link
Copy Markdown
Owner

Describe the change
UseRedisQueues now takes an explicit storage implementation so Redis queue users can choose LIST vs ZSET. This is not a forced LIST→ZSET cutover.

  • RedisQueueStorage.List (default): same {prefix}-workflows|events|index LIST keys, with the atomic Lua uniqueness from Harden Redis queue uniqueness with atomic Lua enqueue #1446 (LINSERT / RPUSH / LREM in one EVAL). Dequeue stays LPOP. Rolling-upgrade friendly with existing LIST deployments.
  • RedisQueueStorage.SortedSet: opt-in ZSET path (ZADD NX + Redis TIME scores; dequeue is Lua ZRANGE + ZREM). Start() migrates leftover LIST items only when this mode is selected.

IQueueProvider is unchanged. WorkflowOptions.EnableWorkflows|EnableEvents|EnableIndexes still gate which queues are used (#1154). Issue #1417 / QueueConsumer._secondPasses is not addressed here.

#1446’s LIST Lua uniqueness is folded in, so that PR is unnecessary if this lands.

Describe your implementation or design

cfg.UseRedisQueues("localhost:6379", "app-name"); // LIST (default)
cfg.UseRedisQueues("localhost:6379", "app-name", RedisQueueStorage.SortedSet);
Mode Redis type Enqueue Dequeue Start()
List (default) LIST Lua LINSERT/RPUSH/LREM LPOP connect only
SortedSet ZSET Lua ZADD NX + TIME µs Lua ZRANGE 0 0 + ZREM migrate LIST→ZSET in one EVAL (first occurrence kept)

Queue key names are unchanged. Do not mix modes on the same key prefix — Redis cannot store LIST and ZSET on one key (WRONGTYPE), and mixed hosts will split or lose work.

Rejected: forcing ZSET as the only path (hard cutover for every existing LIST deployment).

Rebased onto current master so the WorkflowOptions constructor / enable-flag path from #1154 is preserved.

Tests
RedisQueueProviderListFixture and RedisQueueProviderSortedSetFixture share uniqueness/FIFO coverage, then add mode-specific cases:

  • Both: concurrent unique enqueue, re-queue while pending is a no-op, FIFO dequeue, recycle after pop, concurrent unique pops, written Redis type matches the selected mode
  • LIST: default constructor writes LIST; preexisting duplicate LIST entries are not grown and drain via LPOP; Start() does not migrate LIST→ZSET
  • SortedSet: Start() migrates a leftover LIST (dedupes, keeps FIFO, new enqueues go behind); existing ZSET is left untouched

Local: 23/23 passed against Redis 7.0.15 after the master rebase (WORKFLOWCORE_REDIS=localhost:6379, dotnet test test/WorkflowCore.Tests.Redis --filter FullyQualifiedName~RedisQueueProvider).

Breaking change
No for the default. UseRedisQueues(connectionString, prefix) stays LIST.

The remaining break applies only when opting into RedisQueueStorage.SortedSet: those hosts change the Redis value type on the existing queue keys from LIST to ZSET. Coordinated cutover for that prefix (stop every host, deploy with SortedSet, start; Start() migrates leftover LIST items). A flush is not required unless you choose to drop in-flight work.

Compatibility

  • Default (List): same keys, same LIST type, uniqueness now actually atomic. Existing LIST keys (including those that already contain duplicates) need no migration. Rolling upgrades with older LIST hosts share the same LIST; an old process can still race its own multi-command sequence until every node is upgraded.
  • Opt-in (SortedSet): Do not mix with List (or older LIST-only hosts) on the same prefix. Mixed processes will WRONGTYPE or ignore each other's pending work.
  • Score semantics (SortedSet): Redis server TIME in microseconds. If two enqueues share the same µs, Redis orders those members lexicographically by id — so SortedSet FIFO is slightly weaker than LIST under burst enqueue. After pop, a later ZADD of the same id gets a new (later) score.
  • Redis version: List is Lua 2.6+ / LINSERT 2.2+. SortedSet / ZADD NX requires Redis 3.0.2+ (Lua 2.6+ is not enough). SortedSet dequeue does not call native ZPOPMIN (Redis 5+).
  • LIST→ZSET migrate: Start() loads the whole LIST into one Lua EVAL (can briefly block Redis on a huge backlog); acceptable for a one-time cutover.
  • IQueueProvider / other providers: unchanged.

Docs
The main MkDocs site now has a Redis providers page (docs/redis.md) covering List vs SortedSet, linked from nav, multi-node clusters, and persistence. Full detail remains in the Redis provider README.

Additional context
Supersedes #1446 (LIST Lua uniqueness is included here). Prefer this PR if both are open.

Queue key format is unchanged: {prefix}-workflows, {prefix}-events, {prefix}-index.

Open in Web Open in Cursor 

@danielgerlag
danielgerlag marked this pull request as ready for review September 8, 2026 20:35
@cursor cursor Bot changed the title Replace Redis queue LIST with unique ZSET pending work Let Redis queue users choose LIST or ZSET storage Sep 14, 2026
@cursor
cursor Bot force-pushed the cursor/redis-queue-zset-8044 branch from 9cb6d38 to 5843671 Compare September 14, 2026 15:24
Keep LIST as the default with atomic Lua uniqueness so existing
deployments can roll forward. SortedSet remains an opt-in that
migrates leftover LIST keys on Start(). Do not mix modes on the
same prefix. Preserve WorkflowOptions Enable* queue flags.

Co-authored-by: Daniel Gerlag <danielgerlag@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/redis-queue-zset-8044 branch from 5843671 to 50be521 Compare September 14, 2026 15:24
cursoragent and others added 2 commits September 15, 2026 22:58
Add a Redis providers page covering UseRedisQueues defaults, the SortedSet
opt-in, key names, and the do-not-mix cutover rule, and link it from nav,
clusters, and persistence.

Co-authored-by: Daniel Gerlag <danielgerlag@users.noreply.github.com>
A missing blank line before the UseRedis* bullets collapsed them into a
single paragraph on the MkDocs site.

Co-authored-by: Daniel Gerlag <danielgerlag@users.noreply.github.com>
@wfcore-manager

Copy link
Copy Markdown
Collaborator

Review: Approve (nits only)

Nits (non-blocking, docs follow-up):

  1. SortedSet needs Redis 3.0.2+ for ZADD NX (not merely Lua 2.6+).
  2. Same-µs Redis TIME scores tie-break lexicographically — SortedSet FIFO is slightly weaker than LIST under burst enqueue.
  3. LIST→ZSET migrate loads the full LIST into one EVAL; fine for one-time cutover, worth documenting for huge backlogs.

Optional later: monotonic ZSET score if strict FIFO is required.

SortedSet needs ZADD NX (Redis 3.0.2+), not Lua 2.6+ alone. Same-µs
TIME scores order lexicographically by id. Start() migrate loads the
whole LIST in one EVAL.

Co-authored-by: Daniel Gerlag <danielgerlag@users.noreply.github.com>

This branch has not been deployed

No deployments
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.

3 participants