Replace Redis queue LIST with unique ZSET pending work - #1447
Open
danielgerlag wants to merge 1 commit into
Open
Replace Redis queue LIST with unique ZSET pending work#1447danielgerlag wants to merge 1 commit into
danielgerlag wants to merge 1 commit into
Conversation
Pending ids are now ZADD NX on the existing queue keys, scored by Redis TIME for FIFO. Dequeue uses atomic ZRANGE+ZREM. Start() migrates leftover LIST entries in place so a coordinated cutover does not need a flush. Co-authored-by: Daniel Gerlag <danielgerlag@users.noreply.github.com>
danielgerlag
marked this pull request as ready for review
September 8, 2026 20:35
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.
Describe the change
RedisQueueProvidernow stores pending work in a Redis sorted set (ZSET) instead of a LIST. Uniqueness isZADD NX(at most one pending member per id). FIFO is the sort score. Dequeue is atomic LuaZRANGE+ZREM(aZPOPMINequivalent).IQueueProvideris unchanged; this is Redis-provider only.This is a single coherent Redis queue algorithm. It replaces the LIST + LINSERT/RPUSH/LREM path (including the atomic Lua LIST enqueue in #1446). Do not land both.
Issue #1417 /
QueueConsumer._secondPassesis not addressed here.Describe your implementation or design
Choice: same key names + in-place LIST→ZSET migrate on
Start()(option B). No new{prefix}-*-zkeys, no config flag, no dual-read/dual-write.{prefix}-workflows{prefix}-events{prefix}-indexZADD KEYS[1] NX <Redis TIME µs> id. Re-queue while pending is a no-op and keeps the existing score/position. After pop, the same id may beZADDagain with a new score.TIME(seconds * 1e6 + microseconds), not client clocks. No app-node clock skew. Same-microsecond ties are lexicographic by member. Single-key script, so Redis Cluster safe (anINCRseq key in the sameEVALwould need hash tags).ZRANGE 0 0+ZREM. Equivalent toZPOPMINwithout a Redis 5+ floor.Start()runs a one-time Lua migrate when the key is still a LIST:LRANGE→DEL→ZADD NXwith scores1..n(first occurrence kept). Migrated items dequeue before later TIME-scored enqueues.Rejected: new key names (silent work-split during rolling upgrade) and a long dual-read window.
Tests
RedisQueueProviderFixture(existing Squadron Redis collection;WORKFLOWCORE_REDIScan point at a running instance):QueueWorkof a missing id leaves exactly one pending memberQueueWorkwhile pending is a no-op and preserves FIFO positionnullStart()migrates a legacy LIST (dedupes, keeps FIFO, new enqueues go behind)Start()leaves an existing ZSET untouchedLocal: 10/10 passed against Redis 7.0.15 (
WORKFLOWCORE_REDIS=localhost:6379,dotnet test … --filter FullyQualifiedName~RedisQueueProviderFixture).CI (02672eb): Redis-Tests passed. Unit-Tests, Mongo/MySQL/PostgreSQL/SQLServer/Oracle/Azure/Elasticsearch, and AppVeyor passed. Integration-Tests failed on
RetrySagaWithUserTaskScenario(Dictionaryenumeration race inGetOpenUserActionswhile polling). That suite does not use Redis; the same test passed on another TFM in the same job. Unrelated flake — rerun Integration-Tests; no ZSET change needed.Breaking change
Yes — Redis value type on the existing queue keys changes from LIST to ZSET. See Compatibility.
Compatibility
{prefix}-workflows|events|index. AZADDagainst a leftover LIST (orLPUSH/LPOPagainst a ZSET) isWRONGTYPE.WRONGTYPEor ignore each other's pending work.UseRedisQueues, deploy this build, start hosts.Start()migrates leftover LIST items in place (first occurrence kept). A flush is not required unless you choose to drop in-flight work (DELthose three keys) instead of migrating.TIMEin microseconds. Not clientDateTime. No app-node clock skew. Equal scores (same µs) order lexicographically by id. AfterZPOPMIN-equivalent pop, a laterZADDof the same id gets a new (later) score.ZADD NXno-op — existing score and position are preserved.ZPOPMINneeds Redis 5+; this provider does not call it, so Redis 5+ is not required. The previous LIST path already needed Lua (2.6+).IQueueProvider/ other providers: unchanged.Additional context
Conflicts with #1446 (LIST Lua uniqueness). Prefer this ZSET implementation if both are considered.
Queue key names are unchanged:
{prefix}-workflows,{prefix}-events,{prefix}-index.