Skip to content

Commit e63ccd8

Browse files
Derk SchooltinkDerkSch
authored andcommitted
bring back lua scripts for queues
1 parent 1ea03e1 commit e63ccd8

1 file changed

Lines changed: 70 additions & 96 deletions

File tree

backend/valkey/queue.go

Lines changed: 70 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import (
1414
)
1515

1616
var (
17-
prepareCmd *valkey.Lua
18-
//enqueueCmd *valkey.Lua
19-
//completeCmd *valkey.Lua
20-
//recoverCmd *valkey.Lua
21-
//sizeCmd *valkey.Lua
17+
prepareCmd *valkey.Lua
18+
enqueueCmd *valkey.Lua
19+
completeCmd *valkey.Lua
20+
recoverCmd *valkey.Lua
21+
sizeCmd *valkey.Lua
2222
)
2323

2424
type taskQueue[T any] struct {
@@ -65,7 +65,11 @@ func newTaskQueue[T any](keyPrefix, tasktype, workerName string) (*taskQueue[T],
6565

6666
// Load all Lua scripts
6767
cmdMapping := map[string]**valkey.Lua{
68-
"queue/prepare.lua": &prepareCmd,
68+
"queue/prepare.lua": &prepareCmd,
69+
"queue/size.lua": &sizeCmd,
70+
"queue/recover.lua": &recoverCmd,
71+
"queue/enqueue.lua": &enqueueCmd,
72+
"queue/complete.lua": &completeCmd,
6973
}
7074

7175
if err := loadScripts(cmdMapping); err != nil {
@@ -97,24 +101,27 @@ func (q *taskQueue[T]) Keys(queue workflow.Queue) KeyInfo {
97101
}
98102

99103
func (q *taskQueue[T]) Size(ctx context.Context, client valkey.Client) (map[workflow.Queue]int64, error) {
100-
members, err := client.Do(ctx, client.B().Smembers().Key(q.queueSetKey).Build()).AsStrSlice()
104+
sizeData, err := sizeCmd.Exec(ctx, client, []string{q.queueSetKey}, []string{}).ToArray()
101105
if err != nil {
102106
return nil, fmt.Errorf("getting queue size: %w", err)
103107
}
104108

105109
res := map[workflow.Queue]int64{}
106-
for _, queueSetKey := range members {
107-
size, err := client.Do(ctx, client.B().Scard().Key(queueSetKey).Build()).AsInt64()
110+
for i := 0; i < len(sizeData); i += 2 {
111+
queueName, err := sizeData[i].ToString()
108112
if err != nil {
109-
return nil, fmt.Errorf("getting queue size: %w", err)
113+
return nil, fmt.Errorf("parsing queue name: %w", err)
110114
}
111115

112-
trimmed := strings.TrimPrefix(queueSetKey, q.keyPrefix)
113-
lastIdx := strings.LastIndex(trimmed, ":")
114-
if lastIdx == -1 || lastIdx == len(trimmed)-1 {
115-
return nil, fmt.Errorf("unexpected set key format: %s", queueSetKey)
116+
queueName = strings.TrimPrefix(queueName, q.keyPrefix)
117+
queueName = strings.Split(queueName, ":")[1] // queue name is the third part of the key (0-indexed)
118+
119+
queue := workflow.Queue(queueName)
120+
size, err := sizeData[i+1].AsInt64()
121+
if err != nil {
122+
return nil, fmt.Errorf("parsing queue size: %w", err)
116123
}
117-
queue := workflow.Queue(trimmed[lastIdx+1:])
124+
118125
res[queue] = size
119126
}
120127

@@ -129,24 +136,8 @@ func (q *taskQueue[T]) Enqueue(ctx context.Context, client valkey.Client, queue
129136

130137
keys := q.Keys(queue)
131138

132-
// Add to set to track uniqueness
133-
err = client.Do(ctx, client.B().Sadd().Key(q.queueSetKey).Member(keys.SetKey).Build()).Error()
134-
if err != nil {
135-
return err
136-
}
137-
138-
// Add to set for this queue
139-
added, err := client.Do(ctx, client.B().Sadd().Key(keys.SetKey).Member(id).Build()).AsInt64()
140-
if err != nil {
141-
return err
142-
}
143-
144-
// Only add to stream if it's a new task
145-
if added > 0 {
146-
err = client.Do(ctx, client.B().Xadd().Key(keys.StreamKey).Id("*").FieldValue().FieldValue("id", id).FieldValue("data", string(ds)).Build()).Error()
147-
if err != nil {
148-
return err
149-
}
139+
if err := enqueueCmd.Exec(ctx, client, []string{q.queueSetKey, keys.SetKey, keys.StreamKey}, []string{q.groupName, id, string(ds)}).Error(); err != nil {
140+
return fmt.Errorf("enqueueing task: %w", err)
150141
}
151142

152143
return nil
@@ -209,84 +200,67 @@ func (q *taskQueue[T]) Extend(ctx context.Context, client valkey.Client, queue w
209200
}
210201

211202
func (q *taskQueue[T]) Complete(ctx context.Context, client valkey.Client, queue workflow.Queue, taskID string) error {
212-
keyInfo := q.Keys(queue)
213-
214-
// Get the task to find the ID
215-
msgs, err := client.Do(ctx, client.B().Xrange().Key(keyInfo.StreamKey).Start(taskID).End(taskID).Build()).AsXRange()
216-
if err != nil {
217-
// Check if error is due to no data available (nil response)
218-
if valkey.IsValkeyNil(err) {
219-
return nil
220-
}
203+
err := completeCmd.Exec(ctx, client, []string{
204+
q.Keys(queue).SetKey,
205+
q.Keys(queue).StreamKey,
206+
}, []string{taskID, q.groupName}).Error()
207+
if err != nil && !valkey.IsValkeyNil(err) {
221208
return fmt.Errorf("completing task: %w", err)
222209
}
223210

224-
if len(msgs) == 0 {
225-
return nil
226-
}
227-
228-
msg := msgs[0]
229-
id, ok := msg.FieldValues["id"]
230-
if !ok {
231-
return fmt.Errorf("completing task: missing id field")
232-
}
211+
return nil
212+
}
233213

234-
// Remove from set
235-
err = client.Do(ctx, client.B().Srem().Key(keyInfo.SetKey).Member(id).Build()).Error()
236-
if err != nil {
237-
return fmt.Errorf("completing task: %w", err)
214+
func (q *taskQueue[T]) recover(ctx context.Context, client valkey.Client, queues []workflow.Queue, idleTimeout time.Duration) (*TaskItem[T], error) {
215+
var keys []string
216+
for _, queue := range queues {
217+
keys = append(keys, q.Keys(queue).StreamKey)
238218
}
239219

240-
// Acknowledge in consumer group
241-
err = client.Do(ctx, client.B().Xack().Key(keyInfo.StreamKey).Group(q.groupName).Id(taskID).Build()).Error()
220+
r, err := recoverCmd.Exec(ctx, client, keys, []string{q.groupName, q.workerName, strconv.FormatInt(idleTimeout.Milliseconds(), 10), "0"}).ToArray()
242221
if err != nil {
243-
return fmt.Errorf("completing task: %w", err)
244-
}
222+
if valkey.IsValkeyNil(err) {
223+
return nil, nil
224+
}
245225

246-
// Delete from stream
247-
err = client.Do(ctx, client.B().Xdel().Key(keyInfo.StreamKey).Id(taskID).Build()).Error()
248-
if err != nil {
249-
return fmt.Errorf("completing task: %w", err)
226+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
250227
}
251228

252-
return nil
253-
}
254-
255-
func (q *taskQueue[T]) recover(ctx context.Context, client valkey.Client, queues []workflow.Queue, idleTimeout time.Duration) (*TaskItem[T], error) {
256-
for _, queue := range queues {
257-
streamKey := q.Keys(queue).StreamKey
258-
259-
// Try to recover abandoned tasks
260-
cmd := client.B().Xautoclaim().Key(streamKey).Group(q.groupName).Consumer(q.workerName).MinIdleTime(strconv.FormatInt(idleTimeout.Milliseconds(), 10)).Start("0").Count(1)
261-
msgs, err := client.Do(ctx, cmd.Build()).ToArray()
229+
if len(r) > 1 {
230+
msgs, err := r[1].ToArray()
262231
if err != nil {
263-
// Check if error is due to no data available (nil response)
264-
if valkey.IsValkeyNil(err) {
265-
continue
266-
}
267232
return nil, fmt.Errorf("recovering abandoned task: %w", err)
268233
}
269-
270-
if len(msgs) >= 2 {
271-
entries, _ := msgs[1].ToArray()
272-
for _, entry := range entries {
273-
arr, _ := entry.ToArray()
274-
if len(arr) == 2 {
275-
id, _ := arr[0].ToString()
276-
fieldsArr, _ := arr[1].ToArray()
277-
fieldValues := map[string]string{}
278-
for i := 0; i+1 < len(fieldsArr); i += 2 {
279-
key, _ := fieldsArr[i].ToString()
280-
val, _ := fieldsArr[i+1].ToString()
281-
fieldValues[key] = val
282-
}
283-
xEntry := valkey.XRangeEntry{
284-
ID: id,
285-
FieldValues: fieldValues,
286-
}
287-
return msgToTaskItem[T](xEntry)
234+
if len(msgs) > 0 && !msgs[0].IsNil() {
235+
msgData, err := msgs[0].ToArray()
236+
if err != nil {
237+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
238+
}
239+
id, err := msgData[0].ToString()
240+
if err != nil {
241+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
242+
}
243+
rawValues, err := msgData[1].ToArray()
244+
if err != nil {
245+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
246+
}
247+
values := make(map[string]string)
248+
for i := 0; i < len(rawValues); i += 2 {
249+
key, err := rawValues[i].ToString()
250+
if err != nil {
251+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
252+
}
253+
value, err := rawValues[i+1].ToString()
254+
if err != nil {
255+
return nil, fmt.Errorf("recovering abandoned task: %w", err)
288256
}
257+
values[key] = value
289258
}
259+
260+
return msgToTaskItem[T](valkey.XRangeEntry{
261+
ID: id,
262+
FieldValues: values,
263+
})
290264
}
291265
}
292266

0 commit comments

Comments
 (0)