2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-12-26 07:42:17 +08:00

Improve performance of enqueueing tasks (#946)

* Improve performance of enqueueing tasks

Add an in-memory cache to keep track of all the queues. Use this cache
to avoid sending an SADD since after the first call, that extra network
call isn't necessary.

The cache will expire every 10 secs so for cases where the queue is
deleted from asynq:queues set, it can be added again next time a task is
enqueued to it.

* Use sync.Map to simplify the conditional SADD

* Cleanup queuePublished in RemoveQueue

---------

Co-authored-by: Yousif <753751+yousifh@users.noreply.github.com>
This commit is contained in:
Pior Bastida 2024-10-30 06:25:35 +01:00 committed by GitHub
parent 02c6dae7eb
commit 3dbda60333
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 89 additions and 15 deletions

View File

@ -10,9 +10,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/redis/go-redis/v9"
"github.com/hibiken/asynq/internal/base" "github.com/hibiken/asynq/internal/base"
"github.com/hibiken/asynq/internal/errors" "github.com/hibiken/asynq/internal/errors"
"github.com/redis/go-redis/v9"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
@ -1832,6 +1832,7 @@ func (r *RDB) RemoveQueue(qname string, force bool) error {
if err := r.client.SRem(context.Background(), base.AllQueues, qname).Err(); err != nil { if err := r.client.SRem(context.Background(), base.AllQueues, qname).Err(); err != nil {
return errors.E(op, errors.Unknown, err) return errors.E(op, errors.Unknown, err)
} }
r.queuesPublished.Delete(qname)
return nil return nil
case -1: case -1:
return errors.E(op, errors.NotFound, &errors.QueueNotEmptyError{Queue: qname}) return errors.E(op, errors.NotFound, &errors.QueueNotEmptyError{Queue: qname})

View File

@ -9,6 +9,7 @@ import (
"context" "context"
"fmt" "fmt"
"math" "math"
"sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -26,8 +27,9 @@ const LeaseDuration = 30 * time.Second
// RDB is a client interface to query and mutate task queues. // RDB is a client interface to query and mutate task queues.
type RDB struct { type RDB struct {
client redis.UniversalClient client redis.UniversalClient
clock timeutil.Clock clock timeutil.Clock
queuesPublished sync.Map
} }
// NewRDB returns a new instance of RDB. // NewRDB returns a new instance of RDB.
@ -112,8 +114,11 @@ func (r *RDB) Enqueue(ctx context.Context, msg *base.TaskMessage) error {
if err != nil { if err != nil {
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err)) return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
base.TaskKey(msg.Queue, msg.ID), base.TaskKey(msg.Queue, msg.ID),
@ -174,8 +179,11 @@ func (r *RDB) EnqueueUnique(ctx context.Context, msg *base.TaskMessage, ttl time
if err != nil { if err != nil {
return errors.E(op, errors.Internal, "cannot encode task message: %v", err) return errors.E(op, errors.Internal, "cannot encode task message: %v", err)
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
msg.UniqueKey, msg.UniqueKey,
@ -529,8 +537,11 @@ func (r *RDB) AddToGroup(ctx context.Context, msg *base.TaskMessage, groupKey st
if err != nil { if err != nil {
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err)) return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
base.TaskKey(msg.Queue, msg.ID), base.TaskKey(msg.Queue, msg.ID),
@ -591,8 +602,11 @@ func (r *RDB) AddToGroupUnique(ctx context.Context, msg *base.TaskMessage, group
if err != nil { if err != nil {
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err)) return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
base.TaskKey(msg.Queue, msg.ID), base.TaskKey(msg.Queue, msg.ID),
@ -648,8 +662,11 @@ func (r *RDB) Schedule(ctx context.Context, msg *base.TaskMessage, processAt tim
if err != nil { if err != nil {
return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err)) return errors.E(op, errors.Unknown, fmt.Sprintf("cannot encode message: %v", err))
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
base.TaskKey(msg.Queue, msg.ID), base.TaskKey(msg.Queue, msg.ID),
@ -707,8 +724,11 @@ func (r *RDB) ScheduleUnique(ctx context.Context, msg *base.TaskMessage, process
if err != nil { if err != nil {
return errors.E(op, errors.Internal, fmt.Sprintf("cannot encode task message: %v", err)) return errors.E(op, errors.Internal, fmt.Sprintf("cannot encode task message: %v", err))
} }
if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil { if _, found := r.queuesPublished.Load(msg.Queue); !found {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err}) if err := r.client.SAdd(ctx, base.AllQueues, msg.Queue).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "sadd", Err: err})
}
r.queuesPublished.Store(msg.Queue, true)
} }
keys := []string{ keys := []string{
msg.UniqueKey, msg.UniqueKey,

View File

@ -160,6 +160,59 @@ func TestEnqueueTaskIdConflictError(t *testing.T) {
} }
} }
func TestEnqueueQueueCache(t *testing.T) {
r := setup(t)
defer r.Close()
t1 := h.NewTaskMessageWithQueue("sync1", nil, "low")
enqueueTime := time.Now()
clock := timeutil.NewSimulatedClock(enqueueTime)
r.SetClock(clock)
err := r.Enqueue(context.Background(), t1)
if err != nil {
t.Fatalf("(*RDB).Enqueue(msg) = %v, want nil", err)
}
// Check queue is in the AllQueues set.
if !r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
t.Fatalf("%q is not a member of SET %q", t1.Queue, base.AllQueues)
}
if _, ok := r.queuesPublished.Load(t1.Queue); !ok {
t.Fatalf("%q is not cached in queuesPublished", t1.Queue)
}
t.Run("remove-queue", func(t *testing.T) {
err := r.RemoveQueue(t1.Queue, true)
if err != nil {
t.Errorf("(*RDB).RemoveQueue(%q, %t) = %v, want nil", t1.Queue, true, err)
}
if _, ok := r.queuesPublished.Load(t1.Queue); ok {
t.Fatalf("%q is still cached in queuesPublished", t1.Queue)
}
if r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
t.Fatalf("%q is a member of SET %q", t1.Queue, base.AllQueues)
}
err = r.Enqueue(context.Background(), t1)
if err != nil {
t.Fatalf("(*RDB).Enqueue(msg) = %v, want nil", err)
}
// Check queue is in the AllQueues set.
if !r.client.SIsMember(context.Background(), base.AllQueues, t1.Queue).Val() {
t.Fatalf("%q is not a member of SET %q", t1.Queue, base.AllQueues)
}
if _, ok := r.queuesPublished.Load(t1.Queue); !ok {
t.Fatalf("%q is not cached in queuesPublished", t1.Queue)
}
})
}
func TestEnqueueUnique(t *testing.T) { func TestEnqueueUnique(t *testing.T) {
r := setup(t) r := setup(t)
defer r.Close() defer r.Close()