@@ -20,6 +20,11 @@ type taskQueue[T any] struct {
2020 groupName string
2121 workerName string
2222 queueSetKey string
23+ // hashTag is a Redis Cluster hash tag ensuring all keys used together
24+ // (across different queues for the same task type) map to the same slot.
25+ // This avoids CrossSlot errors when Valkey is running in clustered/serverless modes
26+ // and XREADGROUP is called on multiple stream keys.
27+ hashTag string
2328}
2429
2530type TaskItem [T any ] struct {
@@ -48,12 +53,22 @@ func newTaskQueue[T any](keyPrefix, tasktype, workerName string) (*taskQueue[T],
4853 workerName = uuid .NewString ()
4954 }
5055
56+ // Use a stable Redis Cluster hash tag so that all keys for this task type
57+ // hash to the same slot regardless of the specific queue name. Only the
58+ // substring within {...} is used for hashing.
59+ // Example generated keys:
60+ // <prefix>{task:<tasktype>}:task-stream:<queue>
61+ // <prefix>{task:<tasktype>}:task-set:<queue>
62+ // <prefix>{task:<tasktype>}:<tasktype>:queues
63+ hashTag := fmt .Sprintf ("{task:%s}" , tasktype )
64+
5165 tq := & taskQueue [T ]{
5266 keyPrefix : keyPrefix ,
5367 tasktype : tasktype ,
5468 groupName : "task-workers" ,
5569 workerName : workerName ,
56- queueSetKey : fmt .Sprintf ("%s%s:queues" , keyPrefix , tasktype ),
70+ queueSetKey : fmt .Sprintf ("%s%s:%s:queues" , keyPrefix , hashTag , tasktype ),
71+ hashTag : hashTag ,
5772 }
5873
5974 return tq , nil
@@ -75,8 +90,8 @@ func (q *taskQueue[T]) Prepare(ctx context.Context, client glide.Client, queues
7590
7691func (q * taskQueue [T ]) Keys (queue workflow.Queue ) KeyInfo {
7792 return KeyInfo {
78- StreamKey : fmt .Sprintf ("%stask -stream:%s:%s " , q .keyPrefix , queue , q . tasktype ),
79- SetKey : fmt .Sprintf ("%stask -set:%s:%s " , q .keyPrefix , queue , q . tasktype ),
93+ StreamKey : fmt .Sprintf ("%s%s:task -stream:%s" , q .keyPrefix , q . hashTag , queue ),
94+ SetKey : fmt .Sprintf ("%s%s:task -set:%s" , q .keyPrefix , q . hashTag , queue ),
8095 }
8196}
8297
@@ -93,13 +108,12 @@ func (q *taskQueue[T]) Size(ctx context.Context, client glide.Client) (map[workf
93108 return nil , fmt .Errorf ("getting queue size: %w" , err )
94109 }
95110
96- // Parse queue name from key
97- queueName := strings .TrimPrefix (queueSetKey , q .keyPrefix )
98- parts := strings .Split (queueName , ":" ) // task-set:<queue>:<tasktype>
99- if len (parts ) < 3 {
111+ trimmed := strings .TrimPrefix (queueSetKey , q .keyPrefix )
112+ lastIdx := strings .LastIndex (trimmed , ":" )
113+ if lastIdx == - 1 || lastIdx == len (trimmed )- 1 {
100114 return nil , fmt .Errorf ("unexpected set key format: %s" , queueSetKey )
101115 }
102- queue := workflow .Queue (parts [ 1 ])
116+ queue := workflow .Queue (trimmed [ lastIdx + 1 : ])
103117 res [queue ] = size
104118 }
105119
0 commit comments