Files
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

3267 lines
98 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package task
import (
"context"
"math/rand"
"testing"
"time"
"github.com/cockroachdb/errors"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/internal/coordinator/snmanager"
"github.com/milvus-io/milvus/internal/json"
etcdkv "github.com/milvus-io/milvus/internal/kv/etcd"
"github.com/milvus-io/milvus/internal/metastore"
"github.com/milvus-io/milvus/internal/metastore/kv/querycoord"
"github.com/milvus-io/milvus/internal/querycoordv2/meta"
. "github.com/milvus-io/milvus/internal/querycoordv2/params"
"github.com/milvus-io/milvus/internal/querycoordv2/session"
"github.com/milvus-io/milvus/internal/querycoordv2/utils"
kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv"
"github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/kv"
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
"github.com/milvus-io/milvus/pkg/v3/proto/indexpb"
"github.com/milvus-io/milvus/pkg/v3/proto/querypb"
"github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/metricsinfo"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
type distribution struct {
NodeID int64
channels typeutil.Set[string]
segments typeutil.Set[int64]
}
type TaskSuite struct {
suite.Suite
// Data
collection int64
replica *meta.Replica
subChannels []string
unsubChannels []string
moveChannels []string
growingSegments map[string]int64
loadSegments []int64
releaseSegments []int64
moveSegments []int64
distributions map[int64]*distribution
// Dependencies
kv kv.MetaKv
store metastore.QueryCoordCatalog
meta *meta.Meta
dist *meta.DistributionManager
target *meta.TargetManager
broker *meta.MockBroker
nodeMgr *session.NodeManager
cluster *session.MockCluster
// Test object
scheduler *taskScheduler
ctx context.Context
}
func (suite *TaskSuite) SetupSuite() {
paramtable.Init()
suite.collection = 1000
suite.replica = meta.NewReplica(&querypb.Replica{
CollectionID: suite.collection,
ID: 10,
ResourceGroup: meta.DefaultResourceGroupName,
Nodes: []int64{1, 2, 3},
}, typeutil.NewUniqueSet(1, 2, 3))
suite.subChannels = []string{
"sub-0",
"sub-1",
}
suite.unsubChannels = []string{
"unsub-2",
"unsub-3",
}
suite.moveChannels = []string{
"move-4",
"move-5",
}
suite.growingSegments = map[string]int64{
"sub-0": 10,
"sub-1": 11,
}
suite.loadSegments = []int64{1, 2}
suite.releaseSegments = []int64{3, 4}
suite.moveSegments = []int64{5, 6}
suite.distributions = map[int64]*distribution{
1: {
NodeID: 1,
channels: typeutil.NewSet("unsub-2", "move-4"),
segments: typeutil.NewSet[int64](3, 5),
},
2: {
NodeID: 2,
channels: typeutil.NewSet("unsub-3", "move-5"),
segments: typeutil.NewSet[int64](4, 6),
},
3: {
NodeID: 3,
channels: typeutil.NewSet[string](),
segments: typeutil.NewSet[int64](),
},
}
}
func (suite *TaskSuite) TearDownSuite() {
paramtable.Get().Reset(paramtable.Get().EtcdCfg.Endpoints.Key)
}
func (suite *TaskSuite) SetupTest() {
snmanager.ResetDoNothingStreamingNodeManager(suite.T())
config := GenerateEtcdConfig()
suite.ctx = context.Background()
cli, _ := kvfactory.GetEtcdAndPath()
suite.kv = etcdkv.NewEtcdKV(cli, config.MetaRootPath.GetValue())
suite.store = querycoord.NewCatalog(suite.kv)
suite.meta = meta.NewMeta(RandomIncrementIDAllocator(), suite.store, session.NewNodeManager())
suite.meta.Put(suite.ctx, suite.replica)
suite.nodeMgr = session.NewNodeManager()
suite.dist = meta.NewDistributionManager(suite.nodeMgr)
suite.broker = meta.NewMockBroker(suite.T())
suite.target = meta.NewTargetManager(suite.broker, suite.meta)
suite.cluster = session.NewMockCluster(suite.T())
suite.scheduler = suite.newScheduler()
suite.scheduler.Start()
suite.scheduler.AddExecutor(1)
suite.scheduler.AddExecutor(2)
suite.scheduler.AddExecutor(3)
meta.GlobalFailedLoadCache = meta.NewFailedLoadCache()
}
func (suite *TaskSuite) BeforeTest(suiteName, testName string) {
for node := range suite.distributions {
suite.nodeMgr.Add(session.NewNodeInfo(session.ImmutableNodeInfo{
NodeID: node,
Address: "localhost",
Hostname: "localhost",
}))
}
switch testName {
case "TestSubscribeChannelTask",
"TestUnsubscribeChannelTask",
"TestLoadSegmentTask",
"TestLoadSegmentTaskNotIndex",
"TestSegmentTaskWaitsDistAfterLoadRPC",
"TestLoadSegmentTaskFailed",
"TestTaskCanceled",
"TestMoveSegmentTask",
"TestSubmitDuplicateLoadSegmentTask",
"TestSubmitDuplicateSubscribeChannelTask",
"TestLeaderTaskSet",
"TestLeaderTaskRemove",
"TestNoExecutor",
"TestTaskStaleByRONode",
"TestTaskStaleBySegmentInDist",
"TestLeaderTaskStaleByRONode":
suite.meta.PutCollection(suite.ctx, &meta.Collection{
CollectionLoadInfo: &querypb.CollectionLoadInfo{
CollectionID: suite.collection,
ReplicaNumber: 1,
Status: querypb.LoadStatus_Loading,
},
})
suite.meta.PutPartition(suite.ctx, &meta.Partition{
PartitionLoadInfo: &querypb.PartitionLoadInfo{
CollectionID: suite.collection,
PartitionID: 1,
},
})
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
}
}
func (suite *TaskSuite) TestSubscribeChannelTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partitions := []int64{100, 101}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).
RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestSubscribeChannelTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().DescribeDatabase(mock.Anything, mock.Anything).Return(&rootcoordpb.DescribeDatabaseResponse{}, nil)
for channel, segment := range suite.growingSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).
Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partitions[0],
InsertChannel: channel,
},
}, nil)
}
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
FieldID: 100,
IndexParams: []*commonpb.KeyValuePair{
{
Key: common.MetricTypeKey,
Value: "L2",
},
},
},
}, nil)
suite.cluster.EXPECT().WatchDmChannels(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test subscribe channel task
tasks := []Task{}
dmChannels := make([]*datapb.VchannelInfo, 0)
for _, channel := range suite.subChannels {
dmChannels = append(dmChannels, &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel,
UnflushedSegmentIds: []int64{suite.growingSegments[channel]},
})
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return(dmChannels, nil, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
suite.AssertTaskNum(0, len(suite.subChannels), len(suite.subChannels), 0)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(len(suite.subChannels), 0, len(suite.subChannels), 0)
// Process tasks done
// Dist contains channels
channels := []*meta.DmChannel{}
for _, channel := range suite.subChannels {
channels = append(channels, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel,
},
})
}
suite.dist.ChannelDistManager.Update(targetNode, channels...)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
Wait(ctx, timeout, tasks...)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestSubmitDuplicateSubscribeChannelTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
tasks := []Task{}
for _, channel := range suite.subChannels {
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
tasks = append(tasks, task)
}
channels := []*meta.DmChannel{}
for _, channel := range suite.subChannels {
channels = append(channels, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel,
},
})
}
suite.dist.ChannelDistManager.Update(targetNode, channels...)
for _, task := range tasks {
err := suite.scheduler.Add(task)
suite.Equal(TaskStatusCanceled, task.Status())
suite.Error(err)
}
}
func (suite *TaskSuite) TestUnsubscribeChannelTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(1)
// Expect
suite.cluster.EXPECT().UnsubDmChannel(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test unsubscribe channel task
tasks := []Task{}
dmChannels := make([]*datapb.VchannelInfo, 0)
for _, channel := range suite.unsubChannels {
dmChannels = append(dmChannels, &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel,
})
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
meta.NilReplica,
NewChannelAction(targetNode, ActionTypeReduce, channel),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return(dmChannels, nil, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
// Only first channel exists
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: suite.unsubChannels[0],
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: suite.unsubChannels[0],
},
})
suite.AssertTaskNum(0, len(suite.unsubChannels), len(suite.unsubChannels), 0)
// ProcessTasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(1, 0, 1, 0)
// Update dist
suite.dist.ChannelDistManager.Update(targetNode)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestLoadSegmentTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestLoadSegmentTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
for _, segment := range suite.loadSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
}
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: channel,
Node: targetNode,
Version: 1,
View: &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Status: &querypb.LeaderViewStatus{Serviceable: true},
},
})
tasks := []Task{}
segments := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.loadSegments {
segments = append(segments, &datapb.SegmentInfo{
ID: segment,
InsertChannel: channel.ChannelName,
PartitionID: 1,
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks done
// Dist contains channels
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Segments: map[int64]*querypb.SegmentDist{},
Channel: channel.ChannelName,
}
for _, segment := range suite.loadSegments {
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
}
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
return meta.SegmentFromInfo(info)
})
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: view,
})
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestLoadSegmentTaskNotIndex() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestLoadSegmentTaskNotIndex",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
for _, segment := range suite.loadSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, merr.WrapErrIndexNotFoundForSegments([]int64{segment}))
}
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
tasks := []Task{}
segments := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.loadSegments {
segments = append(segments, &datapb.SegmentInfo{
ID: segment,
InsertChannel: channel.ChannelName,
PartitionID: 1,
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks done
// Dist contains channels
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Segments: map[int64]*querypb.SegmentDist{},
Channel: channel.ChannelName,
}
for _, segment := range suite.loadSegments {
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
}
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
return meta.SegmentFromInfo(info)
})
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestLoadSegmentTaskFailed() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestLoadSegmentTaskNotIndex",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
for _, segment := range suite.loadSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, errors.New("index not ready"))
}
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
tasks := []Task{}
segments := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.loadSegments {
segments = append(segments, &datapb.SegmentInfo{
ID: segment,
PartitionID: 1,
InsertChannel: channel.ChannelName,
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks done
// Dist contains channels
time.Sleep(timeout)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusFailed, task.Status())
}
}
func (suite *TaskSuite) TestReleaseSegmentTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test load segment task
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Segments: make(map[int64]*querypb.SegmentDist),
}
segments := make([]*meta.Segment, 0)
tasks := []Task{}
for _, segment := range suite.releaseSegments {
segments = append(segments, &meta.Segment{
SegmentInfo: &datapb.SegmentInfo{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
})
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.dist.SegmentDistManager.Update(targetNode, segments...)
segmentsNum := len(suite.releaseSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks done
suite.dist.SegmentDistManager.Update(targetNode)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestReleaseGrowingSegmentTask() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
// Expect
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
tasks := []Task{}
for _, segment := range suite.releaseSegments {
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(targetNode, ActionTypeReduce, "", segment, querypb.DataScope_Streaming, 0),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
segmentsNum := len(suite.releaseSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks and Release done
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Tasks removed
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestSegmentTaskWaitsDistAfterLoadRPC() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
segmentID := suite.loadSegments[0]
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestSegmentTaskWaitsDistAfterLoadRPC",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{{CollectionID: suite.collection}}, nil)
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segmentID).Return([]*datapb.SegmentInfo{
{
ID: segmentID,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segmentID).Return(nil, nil)
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil).Once()
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: channel,
Node: targetNode,
Version: 1,
View: &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Status: &querypb.LeaderViewStatus{Serviceable: true},
},
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segmentID),
)
suite.NoError(err)
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, []*datapb.SegmentInfo{
{
ID: segmentID,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
suite.NoError(suite.scheduler.Add(task))
suite.AssertTaskNum(0, 1, 0, 1)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(1, 0, 0, 1)
suite.Equal(TaskStatusStarted, task.Status())
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(1, 0, 0, 1)
suite.Equal(TaskStatusStarted, task.Status())
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, partition, segmentID, targetNode, 1, channel.ChannelName))
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
func (suite *TaskSuite) TestSegmentTaskWaitsDistAfterReleaseRPC() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
segmentID := suite.releaseSegments[0]
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil).Once()
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segmentID),
)
suite.NoError(err)
suite.NoError(suite.scheduler.Add(task))
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, partition, segmentID, targetNode, 1, channel.ChannelName))
suite.AssertTaskNum(0, 1, 0, 1)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(1, 0, 0, 1)
suite.Equal(TaskStatusStarted, task.Status())
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(1, 0, 0, 1)
suite.Equal(TaskStatusStarted, task.Status())
suite.dist.SegmentDistManager.Update(targetNode)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
func (suite *TaskSuite) TestSegmentTaskChecksTimeoutWhileWaitingDist() {
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
defer cancel()
action := NewSegmentAction(3, ActionTypeGrow, "test-channel", suite.loadSegments[0])
action.rpcReturned.Store(true)
task, err := NewSegmentTask(
ctx,
time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
action,
)
suite.NoError(err)
time.Sleep(time.Millisecond)
shouldProcess := suite.scheduler.preProcess(task)
suite.False(shouldProcess)
suite.Equal(TaskStatusCanceled, task.Status())
suite.ErrorIs(task.Err(), context.DeadlineExceeded)
}
func (suite *TaskSuite) TestStreamingReduceWaitsForGrowingSegmentDist() {
nodeID := int64(3)
channelName := "test-channel"
segmentID := suite.growingSegments["sub-0"]
suite.dist.ChannelDistManager.Update(nodeID, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channelName,
},
Node: nodeID,
Version: 1,
View: &meta.LeaderView{
ID: nodeID,
CollectionID: suite.collection,
Channel: channelName,
GrowingSegments: map[int64]*meta.Segment{
segmentID: utils.CreateTestSegment(suite.collection, 1, segmentID, nodeID, 1, channelName),
},
},
})
action := NewSegmentActionWithScope(nodeID, ActionTypeReduce, channelName, segmentID, querypb.DataScope_Streaming, 0)
action.rpcReturned.Store(true)
suite.False(action.IsFinished(suite.dist))
suite.dist.ChannelDistManager.Update(nodeID)
suite.True(action.IsFinished(suite.dist))
}
func (suite *TaskSuite) TestMoveSegmentTask() {
ctx := context.Background()
timeout := 10 * time.Second
leader := int64(1)
sourceNode := int64(2)
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestMoveSegmentTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
for _, segment := range suite.moveSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
}
suite.cluster.EXPECT().LoadSegments(mock.Anything, leader, mock.Anything).Return(merr.Success(), nil)
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, leader, mock.Anything).Return(merr.Success(), nil)
vchannel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
}
suite.dist.ChannelDistManager.Update(leader, &meta.DmChannel{
VchannelInfo: vchannel,
Node: leader,
Version: 1,
View: &meta.LeaderView{
ID: leader,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Status: &querypb.LeaderViewStatus{Serviceable: true},
},
})
view := &meta.LeaderView{
ID: leader,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Segments: make(map[int64]*querypb.SegmentDist),
Status: &querypb.LeaderViewStatus{Serviceable: true},
}
tasks := []Task{}
segments := make([]*meta.Segment, 0)
segmentInfos := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.moveSegments {
segments = append(segments,
utils.CreateTestSegment(suite.collection, partition, segment, sourceNode, 1, channel.ChannelName))
segmentInfos = append(segmentInfos, &datapb.SegmentInfo{
ID: segment,
PartitionID: 1,
InsertChannel: channel.ChannelName,
})
view.Segments[segment] = &querypb.SegmentDist{NodeID: sourceNode, Version: 0}
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
NewSegmentAction(sourceNode, ActionTypeReduce, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{vchannel}, segmentInfos, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
suite.target.UpdateCollectionCurrentTarget(ctx, suite.collection)
suite.dist.SegmentDistManager.Update(sourceNode, segments...)
suite.dist.ChannelDistManager.Update(leader, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: leader,
View: view,
})
for _, task := range tasks {
err := suite.scheduler.Add(task)
suite.NoError(err)
}
segmentsNum := len(suite.moveSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks, target node contains the segment
view = view.Clone()
for _, segment := range suite.moveSegments {
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
}
distSegments := lo.Map(segmentInfos, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
return meta.SegmentFromInfo(info)
})
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
// First action done, execute the second action
suite.dispatchAndWait(sourceNode)
suite.dist.SegmentDistManager.Update(sourceNode)
// Check second action
suite.dispatchAndWait(sourceNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestTaskCanceled() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestSubscribeChannelTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
for _, segment := range suite.loadSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
}
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
tasks := []Task{}
segmentInfos := []*datapb.SegmentInfo{}
for _, segment := range suite.loadSegments {
segmentInfos = append(segmentInfos, &datapb.SegmentInfo{
ID: segment,
PartitionID: partition,
InsertChannel: channel.GetChannelName(),
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
)
suite.NoError(err)
tasks = append(tasks, task)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segmentInfos, nil)
suite.meta.PutPartition(ctx, utils.CreateTestPartition(suite.collection, partition))
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Cancel all tasks
for _, task := range tasks {
task.Cancel(errors.New("mock error"))
}
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusCanceled, task.Status())
suite.Error(task.Err())
}
}
func (suite *TaskSuite) TestChannelTaskReplace() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
for _, channel := range suite.subChannels {
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
task.SetPriority(TaskPriorityNormal)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
// Task with the same replica and segment,
// but without higher priority can't be added
for _, channel := range suite.subChannels {
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
task.SetPriority(TaskPriorityNormal)
err = suite.scheduler.Add(task)
suite.Error(err)
task.SetPriority(TaskPriorityLow)
err = suite.scheduler.Add(task)
suite.Error(err)
}
// Replace the task with one with higher priority
for _, channel := range suite.subChannels {
task, err := NewChannelTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
task.SetPriority(TaskPriorityHigh)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
channelNum := len(suite.subChannels)
suite.AssertTaskNum(0, channelNum, channelNum, 0)
}
func (suite *TaskSuite) TestLeaderTaskSet() {
ctx := context.Background()
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestLoadSegmentTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
for _, segment := range suite.loadSegments {
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
}
suite.cluster.EXPECT().SyncDistribution(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: channel,
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
tasks := []Task{}
segments := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.loadSegments {
segments = append(segments, &datapb.SegmentInfo{
ID: segment,
InsertChannel: channel.ChannelName,
PartitionID: 1,
})
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
suite.replica,
targetNode,
NewLeaderAction(targetNode, targetNode, ActionTypeGrow, channel.GetChannelName(), segment, 0),
)
tasks = append(tasks, task)
err := suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.GetChannelName(),
Segments: map[int64]*querypb.SegmentDist{},
Status: &querypb.LeaderViewStatus{Serviceable: true},
}
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.GetChannelName(),
},
Node: targetNode,
View: view,
})
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// Process tasks done
// Dist contains channels
view = &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.GetChannelName(),
Segments: map[int64]*querypb.SegmentDist{},
}
for _, segment := range suite.loadSegments {
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
}
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
return meta.SegmentFromInfo(info)
})
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.GetChannelName(),
},
Node: targetNode,
View: view,
})
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestCreateTaskBehavior() {
chanelTask, err := NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(chanelTask)
action := NewSegmentAction(0, 0, "", 0)
chanelTask, err = NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, action)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(chanelTask)
action1 := NewChannelAction(0, 0, "fake-channel1")
action2 := NewChannelAction(0, 0, "fake-channel2")
chanelTask, err = NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, action1, action2)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(chanelTask)
segmentTask, err := NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(segmentTask)
channelAction := NewChannelAction(0, 0, "fake-channel1")
segmentTask, err = NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW, channelAction)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(segmentTask)
segmentAction1 := NewSegmentAction(0, 0, "", 0)
segmentAction2 := NewSegmentAction(0, 0, "", 1)
segmentTask, err = NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW, segmentAction1, segmentAction2)
suite.ErrorIs(err, merr.ErrParameterInvalid)
suite.Nil(segmentTask)
leaderAction := NewLeaderAction(1, 2, ActionTypeGrow, "fake-channel1", 100, 0)
leaderTask := NewLeaderSegmentTask(context.TODO(), WrapIDSource(0), 0, meta.NilReplica, 1, leaderAction)
suite.NotNil(leaderTask)
}
func (suite *TaskSuite) TestSegmentTaskReplace() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
for _, segment := range suite.loadSegments {
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
)
suite.NoError(err)
task.SetPriority(TaskPriorityNormal)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
// Task with the same replica and segment,
// but without higher priority can't be added
for _, segment := range suite.loadSegments {
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
)
suite.NoError(err)
task.SetPriority(TaskPriorityNormal)
err = suite.scheduler.Add(task)
suite.Error(err)
task.SetPriority(TaskPriorityLow)
err = suite.scheduler.Add(task)
suite.Error(err)
}
// Replace the task with one with higher priority
for _, segment := range suite.loadSegments {
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
)
suite.NoError(err)
task.SetPriority(TaskPriorityHigh)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
segmentNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentNum, 0, segmentNum)
}
func (suite *TaskSuite) TestNoExecutor() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(-1)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
suite.meta.Put(ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3, -1}))
// Test load segment task
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: channel,
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
segments := make([]*datapb.SegmentInfo, 0)
for _, segment := range suite.loadSegments {
segments = append(segments, &datapb.SegmentInfo{
ID: segment,
PartitionID: 1,
InsertChannel: channel.ChannelName,
})
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
)
suite.NoError(err)
err = suite.scheduler.Add(task)
suite.NoError(err)
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
segmentsNum := len(suite.loadSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
}
func (suite *TaskSuite) TestRemoveByNodeWaitsForSchedule() {
ctx := context.Background()
targetNode := int64(1)
task, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, "ch-0", 999),
)
suite.NoError(err)
suite.NoError(suite.scheduler.Add(task))
suite.AssertTaskNum(0, 1, 0, 1)
suite.scheduler.scheduleMu.Lock()
locked := true
defer func() {
if locked {
suite.scheduler.scheduleMu.Unlock()
}
}()
done := make(chan struct{})
go func() {
defer close(done)
suite.scheduler.RemoveByNode(targetNode)
}()
select {
case <-done:
suite.FailNow("RemoveByNode should wait for scheduleMu")
case <-time.After(50 * time.Millisecond):
}
suite.AssertTaskNum(0, 1, 0, 1)
suite.scheduler.scheduleMu.Unlock()
locked = false
select {
case <-done:
case <-time.After(time.Second):
suite.FailNow("RemoveByNode blocked after scheduleMu was released")
}
suite.AssertTaskNum(0, 0, 0, 0)
}
func (suite *TaskSuite) AssertTaskNum(process, wait, channel, segment int) {
scheduler := suite.scheduler
suite.Equal(process, scheduler.processQueue.Len())
suite.Equal(wait, scheduler.waitQueue.Len())
suite.Equal(scheduler.segmentTasks.Len(), segment)
suite.Equal(scheduler.channelTasks.Len(), channel)
suite.Equal(scheduler.tasks.Len(), process+wait)
suite.Equal(scheduler.tasks.Len(), segment+channel)
}
func (suite *TaskSuite) dispatchAndWait(node int64) {
timeout := 10 * time.Second
suite.scheduler.Dispatch(node)
var keys []any
count := 0
for start := time.Now(); time.Since(start) < timeout; {
count = 0
keys = make([]any, 0)
suite.scheduler.executors.Range(func(_ int64, executor *Executor) bool {
executor.executingTasks.Range(func(taskIndex string) bool {
keys = append(keys, taskIndex)
count++
return true
})
return true
})
if count == 0 {
return
}
time.Sleep(200 * time.Millisecond)
}
suite.FailNow("executor hangs in executing tasks", "count=%d keys=%+v", count, keys)
}
func (suite *TaskSuite) TestLeaderTaskRemove() {
ctx := context.Background()
targetNode := int64(3)
partition := int64(100)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Expect
suite.cluster.EXPECT().SyncDistribution(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
// Test remove segment task
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Segments: make(map[int64]*querypb.SegmentDist),
Status: &querypb.LeaderViewStatus{Serviceable: true},
}
segments := make([]*meta.Segment, 0)
tasks := []Task{}
for _, segment := range suite.releaseSegments {
segments = append(segments, &meta.Segment{
SegmentInfo: &datapb.SegmentInfo{
ID: segment,
CollectionID: suite.collection,
PartitionID: partition,
InsertChannel: channel.ChannelName,
},
})
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
suite.replica,
targetNode,
NewLeaderAction(targetNode, targetNode, ActionTypeReduce, channel.GetChannelName(), segment, 0),
)
tasks = append(tasks, task)
err := suite.scheduler.Add(task)
suite.NoError(err)
}
suite.dist.SegmentDistManager.Update(targetNode, segments...)
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: view,
})
segmentsNum := len(suite.releaseSegments)
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
// Process tasks
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
// mock leader view which has removed all segments
view.Segments = make(map[int64]*querypb.SegmentDist)
// Process tasks done
suite.dispatchAndWait(targetNode)
suite.AssertTaskNum(0, 0, 0, 0)
for _, task := range tasks {
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
}
}
func (suite *TaskSuite) TestLeaderTaskUsesLeaderExecutor() {
ctx := context.Background()
leaderID := int64(1)
workerID := int64(2)
segmentID := suite.releaseSegments[0]
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-leader-executor",
}
suite.scheduler.RemoveExecutor(workerID)
suite.cluster.EXPECT().SyncDistribution(mock.Anything, leaderID, mock.MatchedBy(func(req *querypb.SyncDistributionRequest) bool {
return req.GetCollectionID() == suite.collection &&
req.GetChannel() == channel.GetChannelName() &&
len(req.GetActions()) == 1 &&
req.GetActions()[0].GetType() == querypb.SyncType_Remove &&
req.GetActions()[0].GetSegmentID() == segmentID &&
req.GetActions()[0].GetNodeID() == workerID
})).Return(merr.Success(), nil).Once()
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
suite.replica,
leaderID,
NewLeaderAction(leaderID, workerID, ActionTypeReduce, channel.GetChannelName(), segmentID, 0),
)
suite.NoError(suite.scheduler.Add(task))
suite.dispatchAndWait(leaderID)
suite.dispatchAndWait(leaderID)
suite.Equal(TaskStatusSucceeded, task.Status())
suite.NoError(task.Err())
suite.AssertTaskNum(0, 0, 0, 0)
}
func (suite *TaskSuite) newScheduler() *taskScheduler {
return NewScheduler(
context.Background(),
suite.meta,
suite.dist,
suite.target,
suite.broker,
suite.cluster,
suite.nodeMgr,
)
}
func createReplica(collection int64, nodes ...int64) *meta.Replica {
return meta.NewReplica(
&querypb.Replica{
ID: rand.Int63()/2 + 1,
CollectionID: collection,
Nodes: nodes,
},
typeutil.NewUniqueSet(nodes...),
)
}
func (suite *TaskSuite) TestBalanceChannelTask() {
ctx := context.Background()
collectionID := suite.collection
partitionID := int64(1)
channel := "channel-1"
vchannel := &datapb.VchannelInfo{
CollectionID: collectionID,
ChannelName: channel,
}
segments := []*datapb.SegmentInfo{
{
ID: 1,
CollectionID: collectionID,
PartitionID: partitionID,
InsertChannel: channel,
},
{
ID: 2,
CollectionID: collectionID,
PartitionID: partitionID,
InsertChannel: channel,
},
{
ID: 3,
CollectionID: collectionID,
PartitionID: partitionID,
InsertChannel: channel,
},
}
suite.meta.PutCollection(ctx, utils.CreateTestCollection(collectionID, 1), utils.CreateTestPartition(collectionID, 1))
suite.broker.ExpectedCalls = nil
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, mock.Anything).Return([]*datapb.VchannelInfo{vchannel}, segments, nil)
suite.target.UpdateCollectionNextTarget(ctx, collectionID)
suite.target.UpdateCollectionCurrentTarget(ctx, collectionID)
suite.target.UpdateCollectionNextTarget(ctx, collectionID)
suite.dist.ChannelDistManager.Update(2, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: collectionID,
ChannelName: channel,
},
Node: 2,
Version: 1,
View: &meta.LeaderView{
ID: 2,
CollectionID: collectionID,
Channel: channel,
Status: &querypb.LeaderViewStatus{Serviceable: true},
},
})
task, err := NewChannelTask(context.Background(),
10*time.Second,
WrapIDSource(2),
collectionID,
suite.replica,
NewChannelAction(1, ActionTypeGrow, channel),
NewChannelAction(2, ActionTypeReduce, channel),
)
suite.NoError(err)
// new delegator distribution hasn't updated, block balance
suite.scheduler.preProcess(task)
suite.Equal(0, task.step)
suite.dist.ChannelDistManager.Update(1, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: collectionID,
ChannelName: channel,
},
Node: 1,
Version: 2,
View: &meta.LeaderView{
ID: 1,
CollectionID: collectionID,
Channel: channel,
Version: 2,
Status: &querypb.LeaderViewStatus{Serviceable: true},
},
})
// new delegator distribution updated, task step up
suite.scheduler.preProcess(task)
suite.Equal(1, task.step)
suite.dist.ChannelDistManager.Update(2)
// old delegator removed
suite.scheduler.preProcess(task)
suite.Equal(2, task.step)
}
func (suite *TaskSuite) TestGetTasksJSON() {
ctx := context.Background()
scheduler := suite.newScheduler()
// Add some tasks to the scheduler
task1, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(1, ActionTypeGrow, "", 1),
)
suite.NoError(err)
err = scheduler.Add(task1)
suite.NoError(err)
task2, err := NewChannelTask(
ctx,
10*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
NewChannelAction(1, ActionTypeGrow, "channel-1"),
)
suite.NoError(err)
err = scheduler.Add(task2)
suite.NoError(err)
actualJSON := scheduler.GetTasksJSON()
var tasks []*metricsinfo.QueryCoordTask
err = json.Unmarshal([]byte(actualJSON), &tasks)
suite.NoError(err)
suite.Equal(2, len(tasks))
}
func (suite *TaskSuite) TestCalculateTaskDelta() {
ctx := context.Background()
scheduler := suite.newScheduler()
coll := int64(1001)
nodeID := int64(1)
channelName := "channel-1"
segmentID := int64(1)
// add segment task for collection
task1, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", segmentID, querypb.DataScope_Historical, 100),
)
task1.SetID(1)
suite.NoError(err)
err = scheduler.Add(task1)
suite.NoError(err)
task2, err := NewChannelTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
NewChannelAction(nodeID, ActionTypeGrow, channelName),
)
task2.SetID(2)
suite.NoError(err)
err = scheduler.Add(task2)
suite.NoError(err)
coll2 := int64(1005)
nodeID2 := int64(2)
channelName2 := "channel-2"
segmentID2 := int64(2)
task3, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll2,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(nodeID2, ActionTypeGrow, "", segmentID2, querypb.DataScope_Historical, 100),
)
suite.NoError(err)
task3.SetID(3)
err = scheduler.Add(task3)
suite.NoError(err)
task4, err := NewChannelTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll2,
suite.replica,
NewChannelAction(nodeID2, ActionTypeGrow, channelName2),
)
suite.NoError(err)
task4.SetID(4)
err = scheduler.Add(task4)
suite.NoError(err)
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll)
snapshot2 := scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll2)
// check task delta with collectionID and nodeID
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID))
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID, coll))
suite.Equal(100, snapshot2.GetByNodeInCollection(nodeID2))
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, coll2))
// check task delta with collectionID=-1
suite.Equal(100, snapshot.GetByNode(nodeID))
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID, -1))
suite.Equal(100, snapshot.GetByNode(nodeID2))
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, -1))
// check task delta with nodeID=-1
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID)+snapshot.GetByNodeInCollection(nodeID2))
suite.Equal(1, scheduler.GetChannelTaskDelta(-1, coll))
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID)+snapshot.GetByNodeInCollection(nodeID2))
suite.Equal(1, scheduler.GetChannelTaskDelta(-1, coll))
// check task delta with nodeID=-1 and collectionID=-1
suite.Equal(200, snapshot.GetByNode(nodeID)+snapshot.GetByNode(nodeID2))
suite.Equal(2, scheduler.GetChannelTaskDelta(-1, -1))
suite.Equal(200, snapshot.GetByNode(nodeID)+snapshot.GetByNode(nodeID2))
suite.Equal(2, scheduler.GetChannelTaskDelta(-1, -1))
scheduler.remove(task1)
scheduler.remove(task2)
scheduler.remove(task3)
scheduler.remove(task4)
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll)
snapshot2 = scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll2)
suite.Equal(0, snapshot.GetByNodeInCollection(nodeID))
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID, coll))
suite.Equal(0, snapshot2.GetByNodeInCollection(nodeID2))
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID2, coll2))
task5, err := NewChannelTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll2,
suite.replica,
NewChannelAction(nodeID2, ActionTypeGrow, channelName2),
)
suite.NoError(err)
task4.SetID(5)
scheduler.incExecutingTaskDelta(task5)
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, coll2))
scheduler.decExecutingTaskDelta(task5)
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID2, coll2))
}
func (suite *TaskSuite) TestSegmentTaskDeltaWithDistFilter() {
ctx := context.Background()
scheduler := suite.newScheduler()
coll := int64(1001)
partition := int64(100)
channel := "channel-1"
sourceNode := int64(1)
targetNode := int64(2)
growSegmentID := int64(101)
reduceSegmentID := int64(102)
rowCount := 100
growTask, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(targetNode, ActionTypeGrow, "", growSegmentID, querypb.DataScope_Historical, rowCount),
)
suite.NoError(err)
growTask.SetID(1)
scheduler.incExecutingTaskDelta(growTask)
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode}, coll)
suite.Equal(rowCount, snapshot.GetByNode(targetNode))
suite.Equal(rowCount, snapshot.GetByNodeInCollection(targetNode))
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode + 1}, coll)
suite.Equal(0, snapshot.GetByNode(targetNode))
suite.dist.SegmentDistManager.Update(targetNode,
utils.CreateTestSegment(coll, partition, growSegmentID, targetNode, 1, channel))
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode}, coll)
suite.Equal(0, snapshot.GetByNode(targetNode))
suite.Equal(0, snapshot.GetByNodeInCollection(targetNode))
scheduler.decExecutingTaskDelta(growTask)
suite.dist.SegmentDistManager.Update(sourceNode,
utils.CreateTestSegment(coll, partition, reduceSegmentID, sourceNode, 1, channel))
reduceTask, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(sourceNode, ActionTypeReduce, "", reduceSegmentID, querypb.DataScope_Historical, rowCount),
)
suite.NoError(err)
reduceTask.SetID(2)
scheduler.incExecutingTaskDelta(reduceTask)
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
suite.Equal(-rowCount, snapshot.GetByNode(sourceNode))
suite.Equal(-rowCount, snapshot.GetByNodeInCollection(sourceNode))
suite.dist.SegmentDistManager.Update(sourceNode)
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
suite.Equal(0, snapshot.GetByNode(sourceNode))
suite.Equal(0, snapshot.GetByNodeInCollection(sourceNode))
channelTask, err := NewChannelTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
NewChannelAction(targetNode, ActionTypeGrow, channel),
)
suite.NoError(err)
channelTask.SetID(3)
scheduler.incExecutingTaskDelta(channelTask)
suite.Equal(1, scheduler.GetChannelTaskDelta(targetNode, coll))
}
func (suite *TaskSuite) TestSegmentTaskDeltaSnapshotKeepsStreamingReduceUntilGrowingDistGone() {
ctx := context.Background()
scheduler := suite.newScheduler()
coll := int64(1001)
partition := int64(100)
channel := "channel-1"
sourceNode := int64(1)
segmentID := int64(102)
rowCount := 100
suite.dist.ChannelDistManager.Update(sourceNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: coll,
ChannelName: channel,
},
Node: sourceNode,
Version: 1,
View: &meta.LeaderView{
ID: sourceNode,
CollectionID: coll,
Channel: channel,
GrowingSegments: map[int64]*meta.Segment{
segmentID: utils.CreateTestSegment(coll, partition, segmentID, sourceNode, 1, channel),
},
},
})
reduceTask, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(sourceNode, ActionTypeReduce, channel, segmentID, querypb.DataScope_Streaming, rowCount),
)
suite.NoError(err)
reduceTask.SetID(1)
scheduler.incExecutingTaskDelta(reduceTask)
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
suite.Equal(-rowCount, snapshot.GetByNode(sourceNode))
suite.Equal(-rowCount, snapshot.GetByNodeInCollection(sourceNode))
suite.dist.ChannelDistManager.Update(sourceNode)
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
suite.Equal(0, snapshot.GetByNode(sourceNode))
suite.Equal(0, snapshot.GetByNodeInCollection(sourceNode))
scheduler.decExecutingTaskDelta(reduceTask)
}
func (suite *TaskSuite) TestChannelTaskDeltaCache() {
delta := NewChannelTaskDelta()
taskDelta := []int{1, 2, 3, 4, 5, -6, -7, -8, -9, -10}
nodeID := int64(1)
collectionID := int64(100)
tasks := make([]Task, 0)
for i := 0; i < len(taskDelta); i++ {
task, _ := NewChannelTask(
context.TODO(),
10*time.Second,
WrapIDSource(0),
collectionID,
suite.replica,
NewChannelAction(nodeID, ActionTypeGrow, "channel"),
)
task.SetID(int64(i))
tasks = append(tasks, task)
}
tasks = lo.Shuffle(tasks)
for i := 0; i < len(taskDelta); i++ {
delta.Add(tasks[i].(*ChannelTask))
}
tasks = lo.Shuffle(tasks)
for i := 0; i < len(taskDelta); i++ {
delta.Sub(tasks[i].(*ChannelTask))
}
suite.Equal(0, delta.Get(nodeID, collectionID))
suite.Equal(0, delta.Get(nodeID, -1))
suite.Equal(0, delta.Get(-1, -1))
}
func (suite *TaskSuite) TestRemoveTaskWithError() {
ctx := context.Background()
scheduler := suite.newScheduler()
mockTarget := meta.NewMockTargetManager(suite.T())
mockTarget.EXPECT().UpdateCollectionNextTarget(mock.Anything, mock.Anything).Return(nil)
scheduler.targetMgr = mockTarget
coll := int64(1001)
nodeID := int64(1)
// add segment task for collection
task1, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", 1, querypb.DataScope_Historical, 100),
)
suite.NoError(err)
err = scheduler.Add(task1)
suite.NoError(err)
task1.Fail(merr.ErrSegmentNotFound)
// when try to remove task with ErrSegmentNotFound, should trigger UpdateNextTarget
scheduler.remove(task1)
mockTarget.AssertExpectations(suite.T())
// test remove task with ErrSegmentRequestResourceFailed
task2, err := NewSegmentTask(
ctx,
10*time.Second,
WrapIDSource(0),
coll,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", 1, querypb.DataScope_Historical, 100),
)
suite.NoError(err)
err = scheduler.Add(task2)
suite.NoError(err)
task2.Fail(merr.ErrSegmentRequestResourceFailed)
paramtable.Get().Save(paramtable.Get().QueryCoordCfg.ResourceExhaustionPenaltyDuration.Key, "3")
scheduler.remove(task2)
suite.True(suite.nodeMgr.IsResourceExhausted(nodeID))
// expect the penalty duration is expired
time.Sleep(3 * time.Second)
suite.False(suite.nodeMgr.IsResourceExhausted(nodeID))
}
func TestTask(t *testing.T) {
suite.Run(t, new(TaskSuite))
}
func TestSegmentActionFinishStates(t *testing.T) {
dist := meta.NewDistributionManager(nil)
action := NewSegmentActionWithScope(1, ActionTypeGrow, "ch", 10, querypb.DataScope_Historical, 100)
assert.False(t, action.IsFinished(dist))
action.rpcReturned.Store(true)
assert.False(t, action.IsFinished(dist))
dist.SegmentDistManager.Update(1, utils.CreateTestSegment(100, 1, 10, 1, 1, "ch"))
assert.True(t, action.IsFinished(dist))
updateAction := NewSegmentActionWithScope(1, ActionTypeUpdate, "ch", 10, querypb.DataScope_Historical, 0)
updateAction.rpcReturned.Store(true)
assert.True(t, updateAction.IsFinished(dist))
}
func TestSegmentTaskDeltaSnapshotDefaults(t *testing.T) {
snapshot := NewSegmentTaskDeltaSnapshot(nil, nil)
assert.Equal(t, 0, snapshot.GetByNode(1))
assert.Equal(t, 0, snapshot.GetByNodeInCollection(1))
var nilSnapshot *SegmentTaskDeltaSnapshot
assert.Equal(t, 0, nilSnapshot.GetByNode(1))
assert.Equal(t, 0, nilSnapshot.GetByNodeInCollection(1))
}
func TestSegmentTaskDeltaDefensiveBranches(t *testing.T) {
replica := newReplicaDefaultRG(10)
segmentTask, err := NewSegmentTask(
context.Background(),
time.Second,
WrapIDSource(0),
100,
replica,
commonpb.LoadPriority_LOW,
NewSegmentActionWithScope(1, ActionTypeGrow, "ch", 10, querypb.DataScope_Historical, 100),
)
assert.NoError(t, err)
segmentTask.SetID(1)
delta := NewSegmentTaskDelta()
delta.Add(segmentTask)
delta.printDetailInfos()
delta.Add(segmentTask)
assert.Len(t, delta.records[segmentTask.ID()], 1)
delta.Sub(segmentTask)
delta.Sub(segmentTask)
assert.Empty(t, delta.records)
base := newBaseTask(context.Background(), WrapIDSource(0), 100, replica, "ch", "MalformedSegmentTask")
base.SetID(2)
base.actions = []Action{NewChannelAction(1, ActionTypeGrow, "ch")}
malformedTask := &SegmentTask{baseTask: base, segmentID: 10}
delta.Add(malformedTask)
assert.Empty(t, delta.records[malformedTask.ID()])
dist := meta.NewDistributionManager(nil)
assert.False(t, segmentDeltaRecord{segmentID: 0}.isSegmentDistMatched(dist))
assert.True(t, segmentDeltaRecord{nodeID: 1, segmentID: 10, actionType: ActionTypeUpdate}.isSegmentDistMatched(dist))
}
func TestChannelTaskDeltaDefensiveBranches(t *testing.T) {
replica := newReplicaDefaultRG(10)
channelTask, err := NewChannelTask(
context.Background(),
time.Second,
WrapIDSource(0),
100,
replica,
NewChannelAction(1, ActionTypeGrow, "ch"),
)
assert.NoError(t, err)
channelTask.SetID(1)
delta := NewChannelTaskDelta()
delta.Add(channelTask)
delta.printDetailInfos()
delta.Add(channelTask)
assert.Equal(t, 1, delta.Get(1, 100))
delta.Sub(channelTask)
delta.Sub(channelTask)
assert.Equal(t, 0, delta.Get(1, 100))
delta.taskIDRecords.Insert(channelTask.ID())
delete(delta.data, int64(1))
delta.Sub(channelTask)
assert.Equal(t, -1, delta.Get(1, 100))
}
func TestMockSchedulerGetSegmentTaskDeltaSnapshot(t *testing.T) {
nodes := []int64{1, 2}
expected := NewSegmentTaskDeltaSnapshot(map[int64]int{1: 10}, map[int64]int{1: 5})
mockScheduler := NewMockScheduler(t)
mockScheduler.EXPECT().
GetSegmentTaskDeltaSnapshot(nodes, int64(100)).
Run(func(nodeIDs []int64, collectionID int64) {
assert.Equal(t, nodes, nodeIDs)
assert.Equal(t, int64(100), collectionID)
}).
Return(expected).
Once()
assert.Same(t, expected, mockScheduler.GetSegmentTaskDeltaSnapshot(nodes, 100))
mockScheduler.EXPECT().
GetSegmentTaskDeltaSnapshot(mock.Anything, int64(101)).
RunAndReturn(func(nodeIDs []int64, collectionID int64) *SegmentTaskDeltaSnapshot {
assert.Equal(t, nodes, nodeIDs)
assert.Equal(t, int64(101), collectionID)
return NewSegmentTaskDeltaSnapshot(map[int64]int{2: 20}, map[int64]int{2: 15})
}).
Once()
snapshot := mockScheduler.GetSegmentTaskDeltaSnapshot(nodes, 101)
assert.Equal(t, 20, snapshot.GetByNode(2))
assert.Equal(t, 15, snapshot.GetByNodeInCollection(2))
}
func TestMockSchedulerGetSegmentTaskDeltaSnapshotPanicsWithoutReturn(t *testing.T) {
mockScheduler := NewMockScheduler(t)
mockScheduler.On("GetSegmentTaskDeltaSnapshot", mock.Anything, int64(100))
assert.Panics(t, func() {
mockScheduler.GetSegmentTaskDeltaSnapshot([]int64{1}, 100)
})
}
func newReplicaDefaultRG(replicaID int64) *meta.Replica {
return meta.NewReplica(
&querypb.Replica{
ID: replicaID,
ResourceGroup: meta.DefaultResourceGroupName,
},
typeutil.NewUniqueSet(),
)
}
func (suite *TaskSuite) TestSegmentTaskShardLeaderID() {
ctx := context.Background()
timeout := 10 * time.Second
// Create a segment task
action := NewSegmentActionWithScope(1, ActionTypeGrow, "", 100, querypb.DataScope_Historical, 100)
segmentTask, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
action,
)
suite.NoError(err)
// Test initial shard leader ID (should be -1)
suite.Equal(int64(-1), segmentTask.ShardLeaderID())
// Test setting shard leader ID
expectedLeaderID := int64(123)
segmentTask.SetShardLeaderID(expectedLeaderID)
suite.Equal(expectedLeaderID, segmentTask.ShardLeaderID())
// Test setting another value
anotherLeaderID := int64(456)
segmentTask.SetShardLeaderID(anotherLeaderID)
suite.Equal(anotherLeaderID, segmentTask.ShardLeaderID())
// Test with zero value
segmentTask.SetShardLeaderID(0)
suite.Equal(int64(0), segmentTask.ShardLeaderID())
}
func (suite *TaskSuite) TestExecutor_MoveSegmentTask() {
ctx := context.Background()
timeout := 10 * time.Second
sourceNode := int64(2)
targetNode := int64(3)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
suite.meta.PutCollection(ctx, utils.CreateTestCollection(suite.collection, 1))
suite.meta.Put(ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{sourceNode, targetNode}))
// Create move task with both grow and reduce actions to simulate TaskTypeMove
segmentID := suite.loadSegments[0]
growAction := NewSegmentAction(targetNode, ActionTypeGrow, channel.ChannelName, segmentID)
reduceAction := NewSegmentAction(sourceNode, ActionTypeReduce, channel.ChannelName, segmentID)
// Create a move task that has both actions
moveTask, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
growAction,
reduceAction,
)
suite.NoError(err)
// Mock cluster expectations for load segment
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, mock.Anything, mock.Anything).Return(merr.Success(), nil)
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
return &milvuspb.DescribeCollectionResponse{
Schema: &schemapb.CollectionSchema{
Name: "TestMoveSegmentTask",
Fields: []*schemapb.FieldSchema{
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
},
},
}, nil
})
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
{
CollectionID: suite.collection,
},
}, nil)
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segmentID).Return([]*datapb.SegmentInfo{
{
ID: segmentID,
CollectionID: suite.collection,
PartitionID: -1,
InsertChannel: channel.ChannelName,
},
}, nil)
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segmentID).Return(nil, nil)
// Set up distribution with leader view
view := &meta.LeaderView{
ID: targetNode,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Segments: make(map[int64]*querypb.SegmentDist),
Status: &querypb.LeaderViewStatus{Serviceable: true},
}
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: channel,
Node: targetNode,
Version: 1,
View: view,
})
// Add segments to original node distribution for release
segments := []*meta.Segment{
{
SegmentInfo: &datapb.SegmentInfo{
ID: segmentID,
CollectionID: suite.collection,
PartitionID: 1,
InsertChannel: channel.ChannelName,
},
},
}
suite.dist.SegmentDistManager.Update(sourceNode, segments...)
// Set up broker expectations
segmentInfos := []*datapb.SegmentInfo{
{
ID: segmentID,
CollectionID: suite.collection,
PartitionID: 1,
InsertChannel: channel.ChannelName,
},
}
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segmentInfos, nil)
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
// Test that move task sets shard leader ID during load step
suite.Equal(TaskTypeMove, GetTaskType(moveTask))
suite.Equal(int64(-1), moveTask.ShardLeaderID()) // Initial value
// Set up task executor
executor := NewExecutor(targetNode,
suite.meta,
suite.dist,
suite.broker,
suite.target,
suite.cluster,
suite.nodeMgr,
)
// Verify shard leader ID was set for load action in move task
executor.executeSegmentAction(moveTask, 0)
suite.Equal(targetNode, moveTask.ShardLeaderID())
suite.NoError(moveTask.Err())
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
suite.True(moveTask.actions[0].IsFinished(suite.dist))
// expect release action will execute successfully
executor.executeSegmentAction(moveTask, 1)
suite.Equal(targetNode, moveTask.ShardLeaderID())
suite.dist.SegmentDistManager.Update(sourceNode)
suite.True(moveTask.actions[1].IsFinished(suite.dist))
suite.NoError(moveTask.Err())
// test shard leader change before release action
newLeaderID := sourceNode
view1 := &meta.LeaderView{
ID: newLeaderID,
CollectionID: suite.collection,
Channel: channel.ChannelName,
Segments: make(map[int64]*querypb.SegmentDist),
Status: &querypb.LeaderViewStatus{Serviceable: true},
Version: 100,
}
suite.dist.ChannelDistManager.Update(newLeaderID, &meta.DmChannel{
VchannelInfo: channel,
Node: newLeaderID,
Version: 100,
View: view1,
})
// expect release action will skip and task will fail
suite.broker.ExpectedCalls = nil
executor.executeSegmentAction(moveTask, 1)
suite.True(moveTask.actions[1].IsFinished(suite.dist))
suite.ErrorContains(moveTask.Err(), "shard leader changed")
}
func (suite *TaskSuite) TestLeaderTaskStaleByRONode() {
ctx := context.Background()
leaderNode := int64(1)
workerNode := int64(3)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Test case 1: LeaderAction should NOT be stale when worker node is RO but leader is RW
// This is the fix for issue #46737: the checkStale should use leaderID instead of Node() for LeaderAction
suite.Run("WorkerRONodeLeaderRW", func() {
// Create replica with worker node (node 3) as RO node, leader node (node 1) as RW
replicaWithRONode := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{1, 2}, // RW nodes (leader is RW)
RoNodes: []int64{3}, // RO nodes (worker is RO)
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(1, 2))
suite.meta.Put(suite.ctx, replicaWithRONode)
// Set up channel distribution
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: leaderNode,
Version: 1,
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create a LeaderAction that syncs segment from worker (RO node) to leader (RW node)
// leaderID=1 (RW), workerID=3 (RO)
// Before fix: checkStale used action.Node() which returns workerNode (3, RO), causing false stale
// After fix: checkStale uses leaderID (1, RW), task should NOT be stale
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
replicaWithRONode,
leaderNode,
NewLeaderAction(leaderNode, workerNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0], 0),
)
// Add task should succeed because leader node is RW
err := suite.scheduler.Add(task)
suite.NoError(err)
// Task should NOT be stale because we check leader node status, not worker node
suite.Equal(TaskStatusStarted, task.Status())
suite.NoError(task.Err())
// Clean up
suite.scheduler.remove(task)
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
// Test case 2: LeaderAction should be stale when leader node becomes RO
suite.Run("LeaderRONode", func() {
// Set up channel distribution with leader on node 1
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: leaderNode,
Version: 1,
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create task with original replica (all RW nodes)
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
suite.replica,
leaderNode,
NewLeaderAction(leaderNode, workerNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0], 0),
)
// Add task should succeed
err := suite.scheduler.Add(task)
suite.NoError(err)
// Now change replica to make leader node (node 1) as RO node
replicaWithLeaderRO := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{2, 3}, // RW nodes
RoNodes: []int64{1}, // Leader becomes RO
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(2, 3))
suite.meta.Put(suite.ctx, replicaWithLeaderRO)
// Dispatch will trigger promote which calls checkStale
suite.dispatchAndWait(leaderNode)
// Task should be canceled because leader node is RO
suite.Equal(TaskStatusCanceled, task.Status())
suite.ErrorContains(task.Err(), "node becomes ro node")
// Restore original replica
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
// Test case 3: LeaderAction with Reduce type should NOT be affected by RO node
suite.Run("LeaderReduceActionNotAffected", func() {
// Create replica with leader node as RO
replicaWithLeaderRO := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{2, 3}, // RW nodes
RoNodes: []int64{1}, // Leader is RO
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(2, 3))
suite.meta.Put(suite.ctx, replicaWithLeaderRO)
// Set up channel distribution
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: leaderNode,
Version: 1,
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create LeaderAction with Reduce type
task := NewLeaderSegmentTask(
ctx,
WrapIDSource(0),
suite.collection,
replicaWithLeaderRO,
leaderNode,
NewLeaderAction(leaderNode, workerNode, ActionTypeReduce, channel.GetChannelName(), suite.releaseSegments[0], 0),
)
// Add task should succeed because Reduce action is not affected by RO node check
err := suite.scheduler.Add(task)
suite.NoError(err)
// Clean up
suite.scheduler.remove(task)
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
}
func (suite *TaskSuite) TestTaskStaleByRONode() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Test case 1: Task stale due to target node becomes RO node
suite.Run("RONode", func() {
// Set up channel distribution first with RW replica
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create task with original replica (all RW nodes)
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0]),
)
suite.NoError(err)
// Add task should succeed
err = suite.scheduler.Add(task)
suite.NoError(err)
// Now change replica to make node 3 as RO node (simulating node state change)
replicaWithRONode := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{1, 2}, // RW nodes
RoNodes: []int64{3}, // RO node
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(1, 2))
suite.meta.Put(suite.ctx, replicaWithRONode)
// Dispatch will trigger promote which calls checkStale
suite.dispatchAndWait(targetNode)
// Task should be canceled due to RO node
suite.Equal(TaskStatusCanceled, task.Status())
suite.ErrorContains(task.Err(), "node becomes ro node")
// Restore original replica for other tests
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
// Test case 2: Task stale due to target node becomes RO SQ node
suite.Run("ROSQNode", func() {
// Set up channel distribution
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create task with original replica (all RW nodes)
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[1]),
)
suite.NoError(err)
// Add task should succeed
err = suite.scheduler.Add(task)
suite.NoError(err)
// Now change replica to make node 3 as RO SQ node
replicaWithROSQNode := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{1, 2}, // RW nodes
RoSqNodes: []int64{3}, // RO SQ node
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(1, 2))
suite.meta.Put(suite.ctx, replicaWithROSQNode)
// Dispatch will trigger promote which calls checkStale
suite.dispatchAndWait(targetNode)
// Task should be canceled due to RO SQ node
suite.Equal(TaskStatusCanceled, task.Status())
suite.ErrorContains(task.Err(), "node becomes ro node")
// Restore original replica
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
// Test case 3: ActionTypeReduce should not be affected by RO node
suite.Run("ReduceActionNotAffected", func() {
// Create replica with node 3 as RO node
replicaWithRONode := meta.NewReplica(&querypb.Replica{
ID: suite.replica.GetID(),
CollectionID: suite.collection,
Nodes: []int64{1, 2}, // RW nodes
RoNodes: []int64{3}, // RO node
ResourceGroup: meta.DefaultResourceGroupName,
}, typeutil.NewUniqueSet(1, 2))
suite.meta.Put(suite.ctx, replicaWithRONode)
// Set up channel distribution
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
// Create a segment task with Reduce action targeting the RO node
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
replicaWithRONode,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), suite.releaseSegments[0]),
)
suite.NoError(err)
// Add task should succeed because Reduce action is not affected by RO node check
err = suite.scheduler.Add(task)
suite.NoError(err)
// Clean up
suite.scheduler.remove(task)
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
})
}
func (suite *TaskSuite) TestTaskStaleBySegmentInDist() {
ctx := context.Background()
timeout := 10 * time.Second
targetNode := int64(3)
channel := &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
}
// Test case: Grow task should be stale when segment already exists in dist
suite.Run("SegmentAlreadyInDist", func() {
// Set up channel distribution
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
segmentID := suite.loadSegments[0]
// Create a Grow task for the segment
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segmentID),
)
suite.NoError(err)
// Add task should succeed (segment not in dist yet)
err = suite.scheduler.Add(task)
suite.NoError(err)
// Now simulate segment being loaded: add segment to dist
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
// Dispatch will trigger promote which calls checkStale
suite.dispatchAndWait(targetNode)
// Task should be canceled because segment is already in dist
suite.Equal(TaskStatusCanceled, task.Status())
suite.ErrorContains(task.Err(), "segment already loaded in dist")
// Clean up
suite.dist.SegmentDistManager.Update(targetNode)
})
// Test case: Reduce task should not be affected by segment in dist check
suite.Run("ReduceTaskNotAffected", func() {
// Set up channel distribution
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
VchannelInfo: &datapb.VchannelInfo{
CollectionID: suite.collection,
ChannelName: channel.ChannelName,
},
Node: targetNode,
Version: 1,
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
})
segmentID := suite.releaseSegments[0]
// Add segment to dist first
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
// Create a Reduce task for the segment (should not be affected by dist check)
task, err := NewSegmentTask(
ctx,
timeout,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segmentID),
)
suite.NoError(err)
// Add task should succeed - Reduce task is not affected by "segment in dist" check
err = suite.scheduler.Add(task)
suite.NoError(err)
// Clean up without dispatching (to avoid mock issues)
suite.scheduler.remove(task)
suite.dist.SegmentDistManager.Update(targetNode)
})
}
func (suite *TaskSuite) TestTaskQueueRangePriority() {
queue := newTaskQueue()
nodeID := int64(1)
taskLow, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 400),
)
suite.NoError(err)
taskLow.SetID(31)
taskLow.SetPriority(TaskPriorityLow)
taskNormal, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 401),
)
suite.NoError(err)
taskNormal.SetID(32)
taskNormal.SetPriority(TaskPriorityNormal)
taskHigh, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 402),
)
suite.NoError(err)
taskHigh.SetID(33)
taskHigh.SetPriority(TaskPriorityHigh)
queue.Add(taskLow)
queue.Add(taskHigh)
queue.Add(taskNormal)
suite.Equal(3, queue.Len())
var visited []Priority
queue.Range(func(t Task) bool {
visited = append(visited, t.Priority())
return true
})
suite.Equal([]Priority{TaskPriorityHigh, TaskPriorityNormal, TaskPriorityLow}, visited)
queue.Remove(taskHigh)
suite.Equal(2, queue.Len())
}
func (suite *TaskSuite) TestNodeTaskQueueNodeBucketing() {
queue := newNodeTaskQueue()
task1, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(1, ActionTypeGrow, "ch-0", 100),
)
suite.NoError(err)
task1.SetID(1)
task2, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(2, ActionTypeGrow, "ch-0", 101),
)
suite.NoError(err)
task2.SetID(2)
task3, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(1, ActionTypeGrow, "ch-0", 102),
)
suite.NoError(err)
task3.SetID(3)
queue.Add(task1)
queue.Add(task2)
queue.Add(task3)
suite.Equal(3, queue.Len())
suite.Equal(2, queue.LenByNode(1))
suite.Equal(1, queue.LenByNode(2))
suite.Equal(0, queue.LenByNode(999))
var node1Tasks []Task
queue.RangeByNode(1, func(task Task) bool {
node1Tasks = append(node1Tasks, task)
return true
})
suite.Equal(2, len(node1Tasks))
var node2Tasks []Task
queue.RangeByNode(2, func(task Task) bool {
node2Tasks = append(node2Tasks, task)
return true
})
suite.Equal(1, len(node2Tasks))
suite.Equal(int64(2), node2Tasks[0].ID())
queue.Remove(task1)
suite.Equal(2, queue.Len())
suite.Equal(1, queue.LenByNode(1))
suite.Equal(1, queue.LenByNode(2))
}
func (suite *TaskSuite) TestNodeTaskQueueMoveTaskDualNode() {
queue := newNodeTaskQueue()
destNode := int64(1)
srcNode := int64(2)
segmentID := int64(200)
task, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(destNode, ActionTypeGrow, "ch-0", segmentID),
NewSegmentAction(srcNode, ActionTypeReduce, "ch-0", segmentID),
)
suite.NoError(err)
task.SetID(10)
queue.Add(task)
suite.Equal(1, queue.Len())
suite.Equal(1, queue.LenByNode(destNode))
suite.Equal(1, queue.LenByNode(srcNode))
var destTasks []Task
queue.RangeByNode(destNode, func(t Task) bool {
destTasks = append(destTasks, t)
return true
})
suite.Equal(1, len(destTasks))
suite.Equal(int64(10), destTasks[0].ID())
var srcTasks []Task
queue.RangeByNode(srcNode, func(t Task) bool {
srcTasks = append(srcTasks, t)
return true
})
suite.Equal(1, len(srcTasks))
suite.Equal(int64(10), srcTasks[0].ID())
queue.Remove(task)
suite.Equal(0, queue.Len())
suite.Equal(0, queue.LenByNode(destNode))
suite.Equal(0, queue.LenByNode(srcNode))
}
func (suite *TaskSuite) TestNodeTaskQueueLeaderActionDualNode() {
queue := newNodeTaskQueue()
leaderID := int64(1)
workerID := int64(2)
segmentID := int64(300)
action := NewLeaderAction(leaderID, workerID, ActionTypeGrow, "ch-0", segmentID, 1)
task := NewLeaderSegmentTask(suite.ctx, WrapIDSource(0), suite.collection, suite.replica, leaderID, action)
task.SetID(20)
queue.Add(task)
suite.Equal(1, queue.Len())
suite.Equal(1, queue.LenByNode(workerID))
suite.Equal(1, queue.LenByNode(leaderID))
var workerTasks []Task
queue.RangeByNode(workerID, func(t Task) bool {
workerTasks = append(workerTasks, t)
return true
})
suite.Equal(1, len(workerTasks))
suite.Equal(int64(20), workerTasks[0].ID())
var leaderTasks []Task
queue.RangeByNode(leaderID, func(t Task) bool {
leaderTasks = append(leaderTasks, t)
return true
})
suite.Equal(1, len(leaderTasks))
suite.Equal(int64(20), leaderTasks[0].ID())
queue.Remove(task)
suite.Equal(0, queue.Len())
suite.Equal(0, queue.LenByNode(workerID))
suite.Equal(0, queue.LenByNode(leaderID))
}
func (suite *TaskSuite) TestNodeTaskQueueRangeByNodePriority() {
queue := newNodeTaskQueue()
nodeID := int64(1)
taskLow, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 400),
)
suite.NoError(err)
taskLow.SetID(31)
taskLow.SetPriority(TaskPriorityLow)
taskNormal, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 401),
)
suite.NoError(err)
taskNormal.SetID(32)
taskNormal.SetPriority(TaskPriorityNormal)
taskHigh, err := NewSegmentTask(
suite.ctx,
5*time.Second,
WrapIDSource(0),
suite.collection,
suite.replica,
commonpb.LoadPriority_LOW,
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 402),
)
suite.NoError(err)
taskHigh.SetID(33)
taskHigh.SetPriority(TaskPriorityHigh)
queue.Add(taskLow)
queue.Add(taskHigh)
queue.Add(taskNormal)
suite.Equal(3, queue.LenByNode(nodeID))
var visited []Priority
queue.RangeByNode(nodeID, func(t Task) bool {
visited = append(visited, t.Priority())
return true
})
suite.Equal([]Priority{TaskPriorityHigh, TaskPriorityNormal, TaskPriorityLow}, visited)
}