mirror of
https://github.com/hibiken/asynq.git
synced 2024-12-27 00:02:19 +08:00
Clear group if aggregation set empties the group
This commit is contained in:
parent
60a4dc1401
commit
74d2eea4e0
@ -993,16 +993,26 @@ func (r *RDB) ListGroups(qname string) ([]string, error) {
|
|||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add comment describing what the script does.
|
// aggregationCheckCmd checks the given group for whether to create an aggregation set.
|
||||||
|
// An aggregation set is created if one of the aggregation criteria is met:
|
||||||
|
// 1) group has reached or exceeded its max size
|
||||||
|
// 2) group's oldest task has reached or exceeded its max delay
|
||||||
|
// 3) group's latest task has reached or exceeded its grace period
|
||||||
|
// if aggreation criteria is met, the command moves those tasks from the group
|
||||||
|
// and put them in an aggregation set. Additionally, if the creation of aggregation set
|
||||||
|
// empties the group, it will clear the group name from the all groups set.
|
||||||
|
//
|
||||||
// KEYS[1] -> asynq:{<qname>}:g:<gname>
|
// KEYS[1] -> asynq:{<qname>}:g:<gname>
|
||||||
// KEYS[2] -> asynq:{<qname>}:g:<gname>:<aggregation_set_id>
|
// KEYS[2] -> asynq:{<qname>}:g:<gname>:<aggregation_set_id>
|
||||||
// KEYS[3] -> asynq:{<qname>}:aggregation_sets
|
// KEYS[3] -> asynq:{<qname>}:aggregation_sets
|
||||||
|
// KEYS[4] -> asynq:{<qname>}:groups
|
||||||
// -------
|
// -------
|
||||||
// ARGV[1] -> max group size
|
// ARGV[1] -> max group size
|
||||||
// ARGV[2] -> max group delay in unix time
|
// ARGV[2] -> max group delay in unix time
|
||||||
// ARGV[3] -> start time of the grace period
|
// ARGV[3] -> start time of the grace period
|
||||||
// ARGV[4] -> aggregation set expire time
|
// ARGV[4] -> aggregation set expire time
|
||||||
// ARGV[5] -> current time in unix time
|
// ARGV[5] -> current time in unix time
|
||||||
|
// ARGV[6] -> group name
|
||||||
//
|
//
|
||||||
// Output:
|
// Output:
|
||||||
// Returns 0 if no aggregation set was created
|
// Returns 0 if no aggregation set was created
|
||||||
@ -1020,6 +1030,9 @@ if maxSize ~= 0 and size >= maxSize then
|
|||||||
end
|
end
|
||||||
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
||||||
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
||||||
|
if size == maxSize then
|
||||||
|
redis.call("SREM", KEYS[4], ARGV[6])
|
||||||
|
end
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
local maxDelay = tonumber(ARGV[2])
|
local maxDelay = tonumber(ARGV[2])
|
||||||
@ -1035,6 +1048,9 @@ if maxDelay ~= 0 then
|
|||||||
end
|
end
|
||||||
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
||||||
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
||||||
|
if size <= maxSize then
|
||||||
|
redis.call("SREM", KEYS[4], ARGV[6])
|
||||||
|
end
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1048,6 +1064,9 @@ if latestEntryScore <= gracePeriodStartTime then
|
|||||||
end
|
end
|
||||||
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
redis.call("ZREMRANGEBYRANK", KEYS[1], 0, maxSize-1)
|
||||||
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
redis.call("ZADD", KEYS[3], ARGV[4], KEYS[2])
|
||||||
|
if size <= maxSize then
|
||||||
|
redis.call("SREM", KEYS[4], ARGV[6])
|
||||||
|
end
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
return 0
|
return 0
|
||||||
@ -1072,6 +1091,7 @@ func (r *RDB) AggregationCheck(qname, gname string, t time.Time, gracePeriod, ma
|
|||||||
base.GroupKey(qname, gname),
|
base.GroupKey(qname, gname),
|
||||||
base.AggregationSetKey(qname, gname, aggregationSetID),
|
base.AggregationSetKey(qname, gname, aggregationSetID),
|
||||||
base.AllAggregationSets(qname),
|
base.AllAggregationSets(qname),
|
||||||
|
base.AllGroups(qname),
|
||||||
}
|
}
|
||||||
argv := []interface{}{
|
argv := []interface{}{
|
||||||
maxSize,
|
maxSize,
|
||||||
@ -1079,6 +1099,7 @@ func (r *RDB) AggregationCheck(qname, gname string, t time.Time, gracePeriod, ma
|
|||||||
int64(gracePeriod.Seconds()),
|
int64(gracePeriod.Seconds()),
|
||||||
expireTime.Unix(),
|
expireTime.Unix(),
|
||||||
t.Unix(),
|
t.Unix(),
|
||||||
|
gname,
|
||||||
}
|
}
|
||||||
n, err := r.runScriptWithErrorCode(context.Background(), op, aggregationCheckCmd, keys, argv...)
|
n, err := r.runScriptWithErrorCode(context.Background(), op, aggregationCheckCmd, keys, argv...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3112,6 +3112,7 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
r.SetClock(timeutil.NewSimulatedClock(now))
|
r.SetClock(timeutil.NewSimulatedClock(now))
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
msg1 := h.NewTaskMessageBuilder().SetType("task1").SetGroup("mygroup").Build()
|
msg1 := h.NewTaskMessageBuilder().SetType("task1").SetGroup("mygroup").Build()
|
||||||
msg2 := h.NewTaskMessageBuilder().SetType("task2").SetGroup("mygroup").Build()
|
msg2 := h.NewTaskMessageBuilder().SetType("task2").SetGroup("mygroup").Build()
|
||||||
msg3 := h.NewTaskMessageBuilder().SetType("task3").SetGroup("mygroup").Build()
|
msg3 := h.NewTaskMessageBuilder().SetType("task3").SetGroup("mygroup").Build()
|
||||||
@ -3119,23 +3120,33 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
msg5 := h.NewTaskMessageBuilder().SetType("task5").SetGroup("mygroup").Build()
|
msg5 := h.NewTaskMessageBuilder().SetType("task5").SetGroup("mygroup").Build()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
groups map[string]map[string][]base.Z
|
// initial data
|
||||||
qname string
|
tasks []*taskData
|
||||||
gname string
|
groups map[string][]*redis.Z
|
||||||
gracePeriod time.Duration
|
allGroups map[string][]string
|
||||||
maxDelay time.Duration
|
|
||||||
maxSize int
|
// args
|
||||||
|
qname string
|
||||||
|
gname string
|
||||||
|
gracePeriod time.Duration
|
||||||
|
maxDelay time.Duration
|
||||||
|
maxSize int
|
||||||
|
|
||||||
|
// expectaions
|
||||||
shouldCreateSet bool // whether the check should create a new aggregation set
|
shouldCreateSet bool // whether the check should create a new aggregation set
|
||||||
wantAggregationSet []*base.TaskMessage
|
wantAggregationSet []*base.TaskMessage
|
||||||
wantGroups map[string]map[string][]base.Z
|
wantGroups map[string][]redis.Z
|
||||||
|
shouldClearGroup bool // whehter the check should clear the group from redis
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "with an empty group",
|
desc: "with an empty group",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{},
|
||||||
"default": {
|
groups: map[string][]*redis.Z{
|
||||||
"mygroup": {},
|
base.GroupKey("default", "mygroup"): {},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {},
|
||||||
},
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
@ -3144,25 +3155,32 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 5,
|
maxSize: 5,
|
||||||
shouldCreateSet: false,
|
shouldCreateSet: false,
|
||||||
wantAggregationSet: nil,
|
wantAggregationSet: nil,
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {},
|
||||||
"mygroup": {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with a group size reaching the max size",
|
desc: "with a group size reaching the max size",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-5 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
{msg: msg4, state: base.TaskStateAggregating},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
{msg: msg5, state: base.TaskStateAggregating},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-5 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 1 * time.Minute,
|
gracePeriod: 1 * time.Minute,
|
||||||
@ -3170,25 +3188,32 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 5,
|
maxSize: 5,
|
||||||
shouldCreateSet: true,
|
shouldCreateSet: true,
|
||||||
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3, msg4, msg5},
|
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3, msg4, msg5},
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {},
|
||||||
"mygroup": {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with group size greater than max size",
|
desc: "with group size greater than max size",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-5 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
{msg: msg4, state: base.TaskStateAggregating},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
{msg: msg5, state: base.TaskStateAggregating},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-5 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 2 * time.Minute,
|
gracePeriod: 2 * time.Minute,
|
||||||
@ -3196,26 +3221,31 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 3,
|
maxSize: 3,
|
||||||
shouldCreateSet: true,
|
shouldCreateSet: true,
|
||||||
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3},
|
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3},
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {
|
||||||
"mygroup": {
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with the most recent task older than grace period",
|
desc: "with the most recent task older than grace period",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-5 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-5 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 1 * time.Minute,
|
gracePeriod: 1 * time.Minute,
|
||||||
@ -3223,25 +3253,32 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 5,
|
maxSize: 5,
|
||||||
shouldCreateSet: true,
|
shouldCreateSet: true,
|
||||||
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3},
|
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3},
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {},
|
||||||
"mygroup": {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with the oldest task older than max delay",
|
desc: "with the oldest task older than max delay",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-15 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
{msg: msg4, state: base.TaskStateAggregating},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
{msg: msg5, state: base.TaskStateAggregating},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-15 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 2 * time.Minute,
|
gracePeriod: 2 * time.Minute,
|
||||||
@ -3249,25 +3286,32 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 30,
|
maxSize: 30,
|
||||||
shouldCreateSet: true,
|
shouldCreateSet: true,
|
||||||
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3, msg4, msg5},
|
wantAggregationSet: []*base.TaskMessage{msg1, msg2, msg3, msg4, msg5},
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {},
|
||||||
"mygroup": {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with unlimited size",
|
desc: "with unlimited size",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-15 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
{msg: msg4, state: base.TaskStateAggregating},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
{msg: msg5, state: base.TaskStateAggregating},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-15 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 1 * time.Minute,
|
gracePeriod: 1 * time.Minute,
|
||||||
@ -3275,25 +3319,38 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 0, // maxSize=0 indicates no size limit
|
maxSize: 0, // maxSize=0 indicates no size limit
|
||||||
shouldCreateSet: false,
|
shouldCreateSet: false,
|
||||||
wantAggregationSet: nil,
|
wantAggregationSet: nil,
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {
|
||||||
"mygroup": {},
|
{Member: msg1.ID, Score: float64(now.Add(-15 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "with unlimited delay",
|
desc: "with unlimited delay",
|
||||||
groups: map[string]map[string][]base.Z{
|
tasks: []*taskData{
|
||||||
"default": {
|
{msg: msg1, state: base.TaskStateAggregating},
|
||||||
"mygroup": {
|
{msg: msg2, state: base.TaskStateAggregating},
|
||||||
{Message: msg1, Score: now.Add(-15 * time.Minute).Unix()},
|
{msg: msg3, state: base.TaskStateAggregating},
|
||||||
{Message: msg2, Score: now.Add(-3 * time.Minute).Unix()},
|
{msg: msg4, state: base.TaskStateAggregating},
|
||||||
{Message: msg3, Score: now.Add(-2 * time.Minute).Unix()},
|
{msg: msg5, state: base.TaskStateAggregating},
|
||||||
{Message: msg4, Score: now.Add(-1 * time.Minute).Unix()},
|
},
|
||||||
{Message: msg5, Score: now.Add(-10 * time.Second).Unix()},
|
groups: map[string][]*redis.Z{
|
||||||
},
|
base.GroupKey("default", "mygroup"): {
|
||||||
|
{Member: msg1.ID, Score: float64(now.Add(-15 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
allGroups: map[string][]string{
|
||||||
|
base.AllGroups("default"): {"mygroup"},
|
||||||
|
},
|
||||||
qname: "default",
|
qname: "default",
|
||||||
gname: "mygroup",
|
gname: "mygroup",
|
||||||
gracePeriod: 1 * time.Minute,
|
gracePeriod: 1 * time.Minute,
|
||||||
@ -3301,58 +3358,71 @@ func TestAggregationCheck(t *testing.T) {
|
|||||||
maxSize: 10,
|
maxSize: 10,
|
||||||
shouldCreateSet: false,
|
shouldCreateSet: false,
|
||||||
wantAggregationSet: nil,
|
wantAggregationSet: nil,
|
||||||
wantGroups: map[string]map[string][]base.Z{
|
wantGroups: map[string][]redis.Z{
|
||||||
"default": {
|
base.GroupKey("default", "mygroup"): {
|
||||||
"mygroup": {},
|
{Member: msg1.ID, Score: float64(now.Add(-15 * time.Minute).Unix())},
|
||||||
|
{Member: msg2.ID, Score: float64(now.Add(-3 * time.Minute).Unix())},
|
||||||
|
{Member: msg3.ID, Score: float64(now.Add(-2 * time.Minute).Unix())},
|
||||||
|
{Member: msg4.ID, Score: float64(now.Add(-1 * time.Minute).Unix())},
|
||||||
|
{Member: msg5.ID, Score: float64(now.Add(-10 * time.Second).Unix())},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shouldClearGroup: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
h.FlushDB(t, r.client)
|
h.FlushDB(t, r.client)
|
||||||
h.SeedAllGroups(t, r.client, tc.groups)
|
|
||||||
|
|
||||||
aggregationSetID, err := r.AggregationCheck(tc.qname, tc.gname, now, tc.gracePeriod, tc.maxDelay, tc.maxSize)
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
if err != nil {
|
SeedTasks(t, r.client, tc.tasks)
|
||||||
t.Errorf("%s: AggregationCheck returned error: %v", tc.desc, err)
|
SeedZSets(t, r.client, tc.groups)
|
||||||
continue
|
SeedSets(t, r.client, tc.allGroups)
|
||||||
}
|
|
||||||
|
|
||||||
if !tc.shouldCreateSet && aggregationSetID != "" {
|
aggregationSetID, err := r.AggregationCheck(tc.qname, tc.gname, now, tc.gracePeriod, tc.maxDelay, tc.maxSize)
|
||||||
t.Errorf("%s: AggregationCheck returned non empty set ID. want empty ID", tc.desc)
|
if err != nil {
|
||||||
continue
|
t.Fatalf("AggregationCheck returned error: %v", err)
|
||||||
}
|
}
|
||||||
if tc.shouldCreateSet && aggregationSetID == "" {
|
|
||||||
t.Errorf("%s: AggregationCheck returned empty set ID. want non empty ID", tc.desc)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tc.shouldCreateSet {
|
if !tc.shouldCreateSet && aggregationSetID != "" {
|
||||||
continue // below checks are intended for aggregation set
|
t.Fatal("AggregationCheck returned non empty set ID. want empty ID")
|
||||||
}
|
}
|
||||||
|
if tc.shouldCreateSet && aggregationSetID == "" {
|
||||||
|
t.Fatal("AggregationCheck returned empty set ID. want non empty ID")
|
||||||
|
}
|
||||||
|
|
||||||
msgs, deadline, err := r.ReadAggregationSet(tc.qname, tc.gname, aggregationSetID)
|
if tc.shouldCreateSet {
|
||||||
if err != nil {
|
msgs, deadline, err := r.ReadAggregationSet(tc.qname, tc.gname, aggregationSetID)
|
||||||
t.Fatalf("%s: Failed to read aggregation set %q: %v", tc.desc, aggregationSetID, err)
|
if err != nil {
|
||||||
}
|
t.Fatalf("Failed to read aggregation set %q: %v", aggregationSetID, err)
|
||||||
if diff := cmp.Diff(tc.wantAggregationSet, msgs, h.SortMsgOpt); diff != "" {
|
}
|
||||||
t.Errorf("%s: Mismatch found in aggregation set: (-want,+got)\n%s", tc.desc, diff)
|
if diff := cmp.Diff(tc.wantAggregationSet, msgs, h.SortMsgOpt); diff != "" {
|
||||||
}
|
t.Errorf("Mismatch found in aggregation set: (-want,+got)\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
if wantDeadline := now.Add(aggregationTimeout); deadline.Unix() != wantDeadline.Unix() {
|
if wantDeadline := now.Add(aggregationTimeout); deadline.Unix() != wantDeadline.Unix() {
|
||||||
t.Errorf("%s: ReadAggregationSet returned deadline=%v, want=%v", tc.desc, deadline, wantDeadline)
|
t.Errorf("ReadAggregationSet returned deadline=%v, want=%v", deadline, wantDeadline)
|
||||||
}
|
|
||||||
|
|
||||||
for qname, groups := range tc.wantGroups {
|
|
||||||
for gname, want := range groups {
|
|
||||||
gotGroup := h.GetGroupEntries(t, r.client, qname, gname)
|
|
||||||
if diff := cmp.Diff(want, gotGroup, h.SortZSetEntryOpt); diff != "" {
|
|
||||||
t.Errorf("%s: Mismatch found in group zset: %q: (-want,+got)\n%s",
|
|
||||||
tc.desc, base.GroupKey(qname, gname), diff)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
AssertZSets(t, r.client, tc.wantGroups)
|
||||||
|
|
||||||
|
if tc.shouldClearGroup {
|
||||||
|
if key := base.GroupKey(tc.qname, tc.gname); r.client.Exists(ctx, key).Val() != 0 {
|
||||||
|
t.Errorf("group key %q still exists", key)
|
||||||
|
}
|
||||||
|
if r.client.SIsMember(ctx, base.AllGroups(tc.qname), tc.gname).Val() {
|
||||||
|
t.Errorf("all-group set %q still contains the group name %q", base.AllGroups(tc.qname), tc.gname)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if key := base.GroupKey(tc.qname, tc.gname); r.client.Exists(ctx, key).Val() == 0 {
|
||||||
|
t.Errorf("group key %q does not exists", key)
|
||||||
|
}
|
||||||
|
if !r.client.SIsMember(ctx, base.AllGroups(tc.qname), tc.gname).Val() {
|
||||||
|
t.Errorf("all-group set %q doesn't contains the group name %q", base.AllGroups(tc.qname), tc.gname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3704,6 +3774,16 @@ func SeedZSets(tb testing.TB, r redis.UniversalClient, zsets map[string][]*redis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SeedSets(tb testing.TB, r redis.UniversalClient, sets map[string][]string) {
|
||||||
|
for key, set := range sets {
|
||||||
|
for _, mem := range set {
|
||||||
|
if err := r.SAdd(context.Background(), key, mem).Err(); err != nil {
|
||||||
|
tb.Fatalf("Failed to seed set (key=%q): %v", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: move this helper somewhere more canonical
|
// TODO: move this helper somewhere more canonical
|
||||||
func AssertZSets(t *testing.T, r redis.UniversalClient, wantZSets map[string][]redis.Z) {
|
func AssertZSets(t *testing.T, r redis.UniversalClient, wantZSets map[string][]redis.Z) {
|
||||||
for key, want := range wantZSets {
|
for key, want := range wantZSets {
|
||||||
|
Loading…
Reference in New Issue
Block a user