Skip to content

Commit 55280f0

Browse files
Derk SchooltinkDerkSch
authored andcommitted
attempt to solve crossslot problems in valkey clusters when dequeueing
1 parent e89c1d1 commit 55280f0

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

backend/valkey/queue.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2530
type 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

7691
func (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

Comments
 (0)