enhance: improve the preformance of create partitions (#47486)

issue: https://github.com/milvus-io/milvus/issues/47403
this pr contains performance optimization for concurrent
CreatePartition:
1. add a version cache on proxy for partition level cache
2. avoid repeated updates of target when there're many CreatePartition
requests.
3. avoid unnecessary copy of meta data.

---------

Signed-off-by: sunby <sunbingyi1992@gmail.com>
This commit is contained in:
Bingyi Sun
2026-05-13 14:12:11 +08:00
committed by GitHub
parent 4b190e5c16
commit f484f3c8cf
67 changed files with 2676 additions and 954 deletions
+1
View File
@@ -377,6 +377,7 @@ proxy:
slowQuerySpanInSeconds: 1 # threshold for slow query detection in seconds. For Search/HybridSearch requests, the time is divided by nq for more accurate per-query measurement. Triggers slow log, WebUI display, and metrics.
queryNodePooling:
size: 10 # the size for shardleader(querynode) client pool
metaCacheGCTimeInterval: 300 # the time interval for meta cache GC, in seconds
partialResultRequiredDataRatio: 1 # partial result required data ratio, default to 1 which means disable partial result, otherwise, it will be used as the minimum data ratio for partial result
http:
enabled: true # Whether to enable the http server
+4
View File
@@ -491,6 +491,10 @@ func (s *mixCoordImpl) CreatePartition(ctx context.Context, req *milvuspb.Create
return s.rootcoordServer.CreatePartition(ctx, req)
}
func (s *mixCoordImpl) CreatePartitionV2(ctx context.Context, req *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
return s.rootcoordServer.CreatePartitionV2(ctx, req)
}
func (s *mixCoordImpl) DropPartition(ctx context.Context, req *milvuspb.DropPartitionRequest) (*commonpb.Status, error) {
return s.rootcoordServer.DropPartition(ctx, req)
}
+4
View File
@@ -392,6 +392,10 @@ func (m *mockMixCoord) CreatePartition(ctx context.Context, req *milvuspb.Create
panic("not implemented") // TODO: Implement
}
func (m *mockMixCoord) CreatePartitionV2(ctx context.Context, req *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
panic("not implemented") // TODO: Implement
}
func (m *mockMixCoord) DropPartition(ctx context.Context, req *milvuspb.DropPartitionRequest) (*commonpb.Status, error) {
panic("not implemented") // TODO: Implement
}
@@ -394,6 +394,18 @@ func (c *Client) CreatePartition(ctx context.Context, in *milvuspb.CreatePartiti
})
}
// CreatePartitionV2 create partition and return partition ID
func (c *Client) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error) {
in = typeutil.Clone(in)
commonpbutil.UpdateMsgBase(
in.GetBase(),
commonpbutil.FillMsgBaseFromClient(paramtable.GetNodeID(), commonpbutil.WithTargetID(c.grpcClient.GetNodeID())),
)
return wrapGrpcCall(ctx, c, func(client MixCoordClient) (*rootcoordpb.CreatePartitionResponse, error) {
return client.CreatePartitionV2(ctx, in)
})
}
// DropPartition drop partition
func (c *Client) DropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
in = typeutil.Clone(in)
+4
View File
@@ -415,6 +415,10 @@ func (s *Server) CreatePartition(ctx context.Context, in *milvuspb.CreatePartiti
return s.mixCoord.CreatePartition(ctx, in)
}
func (s *Server) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
return s.mixCoord.CreatePartitionV2(ctx, in)
}
// DropPartition drops the specified partition.
func (s *Server) DropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest) (*commonpb.Status, error) {
return s.mixCoord.DropPartition(ctx, in)
+59
View File
@@ -1875,6 +1875,65 @@ func (_c *MixCoord_CreatePartition_Call) RunAndReturn(run func(context.Context,
return _c
}
// CreatePartitionV2 provides a mock function with given fields: _a0, _a1
func (_m *MixCoord) CreatePartitionV2(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for CreatePartitionV2")
}
var r0 *rootcoordpb.CreatePartitionResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest) *rootcoordpb.CreatePartitionResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*rootcoordpb.CreatePartitionResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePartitionRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MixCoord_CreatePartitionV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePartitionV2'
type MixCoord_CreatePartitionV2_Call struct {
*mock.Call
}
// CreatePartitionV2 is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.CreatePartitionRequest
func (_e *MixCoord_Expecter) CreatePartitionV2(_a0 interface{}, _a1 interface{}) *MixCoord_CreatePartitionV2_Call {
return &MixCoord_CreatePartitionV2_Call{Call: _e.mock.On("CreatePartitionV2", _a0, _a1)}
}
func (_c *MixCoord_CreatePartitionV2_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest)) *MixCoord_CreatePartitionV2_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.CreatePartitionRequest))
})
return _c
}
func (_c *MixCoord_CreatePartitionV2_Call) Return(_a0 *rootcoordpb.CreatePartitionResponse, _a1 error) *MixCoord_CreatePartitionV2_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MixCoord_CreatePartitionV2_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error)) *MixCoord_CreatePartitionV2_Call {
_c.Call.Return(run)
return _c
}
// CreatePrivilegeGroup provides a mock function with given fields: _a0, _a1
func (_m *MixCoord) CreatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
+74
View File
@@ -2378,6 +2378,80 @@ func (_c *MockMixCoordClient_CreatePartition_Call) RunAndReturn(run func(context
return _c
}
// CreatePartitionV2 provides a mock function with given fields: ctx, in, opts
func (_m *MockMixCoordClient) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for CreatePartitionV2")
}
var r0 *rootcoordpb.CreatePartitionResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) *rootcoordpb.CreatePartitionResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*rootcoordpb.CreatePartitionResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockMixCoordClient_CreatePartitionV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePartitionV2'
type MockMixCoordClient_CreatePartitionV2_Call struct {
*mock.Call
}
// CreatePartitionV2 is a helper method to define mock.On call
// - ctx context.Context
// - in *milvuspb.CreatePartitionRequest
// - opts ...grpc.CallOption
func (_e *MockMixCoordClient_Expecter) CreatePartitionV2(ctx interface{}, in interface{}, opts ...interface{}) *MockMixCoordClient_CreatePartitionV2_Call {
return &MockMixCoordClient_CreatePartitionV2_Call{Call: _e.mock.On("CreatePartitionV2",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *MockMixCoordClient_CreatePartitionV2_Call) Run(run func(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption)) *MockMixCoordClient_CreatePartitionV2_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*milvuspb.CreatePartitionRequest), variadicArgs...)
})
return _c
}
func (_c *MockMixCoordClient_CreatePartitionV2_Call) Return(_a0 *rootcoordpb.CreatePartitionResponse, _a1 error) *MockMixCoordClient_CreatePartitionV2_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockMixCoordClient_CreatePartitionV2_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error)) *MockMixCoordClient_CreatePartitionV2_Call {
_c.Call.Return(run)
return _c
}
// CreatePrivilegeGroup provides a mock function with given fields: ctx, in, opts
func (_m *MockMixCoordClient) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
_va := make([]interface{}, len(opts))
+59
View File
@@ -1216,6 +1216,65 @@ func (_c *MockRootCoord_CreatePartition_Call) RunAndReturn(run func(context.Cont
return _c
}
// CreatePartitionV2 provides a mock function with given fields: _a0, _a1
func (_m *MockRootCoord) CreatePartitionV2(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for CreatePartitionV2")
}
var r0 *rootcoordpb.CreatePartitionResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest) *rootcoordpb.CreatePartitionResponse); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*rootcoordpb.CreatePartitionResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePartitionRequest) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRootCoord_CreatePartitionV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePartitionV2'
type MockRootCoord_CreatePartitionV2_Call struct {
*mock.Call
}
// CreatePartitionV2 is a helper method to define mock.On call
// - _a0 context.Context
// - _a1 *milvuspb.CreatePartitionRequest
func (_e *MockRootCoord_Expecter) CreatePartitionV2(_a0 interface{}, _a1 interface{}) *MockRootCoord_CreatePartitionV2_Call {
return &MockRootCoord_CreatePartitionV2_Call{Call: _e.mock.On("CreatePartitionV2", _a0, _a1)}
}
func (_c *MockRootCoord_CreatePartitionV2_Call) Run(run func(_a0 context.Context, _a1 *milvuspb.CreatePartitionRequest)) *MockRootCoord_CreatePartitionV2_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*milvuspb.CreatePartitionRequest))
})
return _c
}
func (_c *MockRootCoord_CreatePartitionV2_Call) Return(_a0 *rootcoordpb.CreatePartitionResponse, _a1 error) *MockRootCoord_CreatePartitionV2_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRootCoord_CreatePartitionV2_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error)) *MockRootCoord_CreatePartitionV2_Call {
_c.Call.Return(run)
return _c
}
// CreatePrivilegeGroup provides a mock function with given fields: _a0, _a1
func (_m *MockRootCoord) CreatePrivilegeGroup(_a0 context.Context, _a1 *milvuspb.CreatePrivilegeGroupRequest) (*commonpb.Status, error) {
ret := _m.Called(_a0, _a1)
+74
View File
@@ -1558,6 +1558,80 @@ func (_c *MockRootCoordClient_CreatePartition_Call) RunAndReturn(run func(contex
return _c
}
// CreatePartitionV2 provides a mock function with given fields: ctx, in, opts
func (_m *MockRootCoordClient) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for CreatePartitionV2")
}
var r0 *rootcoordpb.CreatePartitionResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error)); ok {
return rf(ctx, in, opts...)
}
if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) *rootcoordpb.CreatePartitionResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*rootcoordpb.CreatePartitionResponse)
}
}
if rf, ok := ret.Get(1).(func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRootCoordClient_CreatePartitionV2_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePartitionV2'
type MockRootCoordClient_CreatePartitionV2_Call struct {
*mock.Call
}
// CreatePartitionV2 is a helper method to define mock.On call
// - ctx context.Context
// - in *milvuspb.CreatePartitionRequest
// - opts ...grpc.CallOption
func (_e *MockRootCoordClient_Expecter) CreatePartitionV2(ctx interface{}, in interface{}, opts ...interface{}) *MockRootCoordClient_CreatePartitionV2_Call {
return &MockRootCoordClient_CreatePartitionV2_Call{Call: _e.mock.On("CreatePartitionV2",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *MockRootCoordClient_CreatePartitionV2_Call) Run(run func(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption)) *MockRootCoordClient_CreatePartitionV2_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*milvuspb.CreatePartitionRequest), variadicArgs...)
})
return _c
}
func (_c *MockRootCoordClient_CreatePartitionV2_Call) Return(_a0 *rootcoordpb.CreatePartitionResponse, _a1 error) *MockRootCoordClient_CreatePartitionV2_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRootCoordClient_CreatePartitionV2_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePartitionRequest, ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error)) *MockRootCoordClient_CreatePartitionV2_Call {
_c.Call.Return(run)
return _c
}
// CreatePrivilegeGroup provides a mock function with given fields: ctx, in, opts
func (_m *MockRootCoordClient) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
_va := make([]interface{}, len(opts))
+6 -2
View File
@@ -385,8 +385,10 @@ func TestRemoveCollectionByID_CleansUpAliases(t *testing.T) {
"alias3": {collectionName: "other_collection", cachedAt: time.Now()},
},
},
collectionCacheVersion: make(map[UniqueID]uint64),
sfGlobal: conc.Singleflight[*collectionInfo]{},
collectionCacheVersion: make(map[UniqueID]uint64),
sfGlobal: conc.Singleflight[*collectionInfo]{},
collLevelPartitionCache: NewVersionCache[string, *partitionInfos](),
partitionCache: NewVersionCache[string, *partitionInfo](),
}
// Remove collection by ID
@@ -457,6 +459,8 @@ func TestRemoveDatabase_CleansUpAliases(t *testing.T) {
dbInfo: map[string]*databaseInfo{
"mydb": {},
},
collLevelPartitionCache: NewVersionCache[string, *partitionInfos](),
partitionCache: NewVersionCache[string, *partitionInfo](),
}
cache.RemoveDatabase(ctx, "mydb")
+1 -5
View File
@@ -183,11 +183,7 @@ func (node *Proxy) InvalidateCollectionMetaCache(ctx context.Context, request *p
log.Warn("invalidate collection meta cache failed. partitionName is empty")
return &commonpb.Status{ErrorCode: commonpb.ErrorCode_UnexpectedError}, nil
}
// drop all the alias as well
if request.CollectionID != UniqueID(0) {
aliasName = globalMetaCache.RemoveCollectionsByID(ctx, collectionID, request.GetBase().GetTimestamp(), false)
}
globalMetaCache.RemoveCollection(ctx, request.GetDbName(), collectionName, request.GetBase().GetTimestamp())
globalMetaCache.RemovePartition(ctx, request.GetDbName(), collectionID, collectionName, request.GetPartitionName(), request.GetBase().GetTimestamp())
log.Info("complete to invalidate collection meta cache", zap.String("type", request.GetBase().GetMsgType().String()))
case commonpb.MsgType_DropDatabase:
node.shardMgr.RemoveDatabase(request.GetDbName())
+4
View File
@@ -979,6 +979,10 @@ func TestProxyDropDatabase(t *testing.T) {
t.Run("drop database ok", func(t *testing.T) {
mix := mocks.NewMockMixCoordClient(t)
mix.EXPECT().DropDatabase(mock.Anything, mock.Anything).Return(merr.Success(), nil)
mix.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
mix.EXPECT().ShowLoadCollections(mock.Anything, mock.Anything).Return(&querypb.ShowCollectionsResponse{}, nil).Maybe()
node.mixCoord = mix
node.UpdateStateCode(commonpb.StateCode_Healthy)
+322 -58
View File
@@ -99,13 +99,15 @@ type Cache interface {
GetDatabaseInfo(ctx context.Context, database string) (*databaseInfo, error)
// AllocID is only using on requests that need to skip timestamp allocation, don't overuse it.
AllocID(ctx context.Context) (int64, error)
RemovePartition(ctx context.Context, database string, collectionID UniqueID, collectionName string, partitionName string, version uint64)
Close()
}
type collectionInfo struct {
collID typeutil.UniqueID
dbName string
schema *schemaInfo
partInfo *partitionInfos
createdTimestamp uint64
createdUtcTimestamp uint64
consistencyLevel commonpb.ConsistencyLevel
@@ -374,6 +376,15 @@ type MetaCache struct {
IDLock sync.RWMutex
collectionCacheVersion map[UniqueID]uint64 // collectionID -> cacheVersion
partitionCache *VersionCache[string, *partitionInfo] // partitionName -> partitionInfo
collLevelPartitionCache *VersionCache[string, *partitionInfos] // collectionName -> partitionInfos
sfPartitionCache conc.Singleflight[*partitionInfo]
sfCollLevelPartitionCache conc.Singleflight[*partitionInfos]
stopCh chan struct{}
closeOnce sync.Once
}
// globalMetaCache is singleton instance of Cache
@@ -402,16 +413,22 @@ func InitMetaCache(ctx context.Context, mixCoord types.MixCoordClient) error {
// NewMetaCache creates a MetaCache with provided RootCoord and QueryNode
func NewMetaCache(mixCoord types.MixCoordClient) (*MetaCache, error) {
return &MetaCache{
mixCoord: mixCoord,
dbInfo: map[string]*databaseInfo{},
collInfo: map[string]map[string]*collectionInfo{},
aliasInfo: map[string]map[string]*aliasEntry{},
credMap: map[string]*internalpb.CredentialInfo{},
privilegeInfos: map[string]struct{}{},
userToRoles: map[string]map[string]struct{}{},
collectionCacheVersion: make(map[UniqueID]uint64),
}, nil
metaCache := &MetaCache{
mixCoord: mixCoord,
dbInfo: map[string]*databaseInfo{},
aliasInfo: map[string]map[string]*aliasEntry{},
collInfo: map[string]map[string]*collectionInfo{},
credMap: map[string]*internalpb.CredentialInfo{},
privilegeInfos: map[string]struct{}{},
userToRoles: map[string]map[string]struct{}{},
collectionCacheVersion: make(map[UniqueID]uint64),
partitionCache: NewVersionCache[string, *partitionInfo](),
collLevelPartitionCache: NewVersionCache[string, *partitionInfos](),
stopCh: make(chan struct{}),
closeOnce: sync.Once{},
}
metaCache.backgroundGCLoop(metaCache.stopCh)
return metaCache, nil
}
func (m *MetaCache) getCollection(database, collectionName string, collectionID UniqueID) (*collectionInfo, bool) {
@@ -455,27 +472,6 @@ func (m *MetaCache) update(ctx context.Context, database, collectionName string,
return nil, err
}
partitions, err := m.showPartitions(ctx, database, collectionName, collectionID)
if err != nil {
return nil, err
}
// check partitionID, createdTimestamp and utcstamp has sam element numbers
if len(partitions.PartitionNames) != len(partitions.CreatedTimestamps) || len(partitions.PartitionNames) != len(partitions.CreatedUtcTimestamps) {
return nil, merr.WrapErrParameterInvalidMsg("partition names and timestamps number is not aligned, response: %s", partitions.String())
}
defaultPartitionName := Params.CommonCfg.DefaultPartitionName.GetValue()
infos := lo.Map(partitions.GetPartitionIDs(), func(partitionID int64, idx int) *partitionInfo {
return &partitionInfo{
name: partitions.PartitionNames[idx],
partitionID: partitions.PartitionIDs[idx],
createdTimestamp: partitions.CreatedTimestamps[idx],
createdUtcTimestamp: partitions.CreatedUtcTimestamps[idx],
isDefault: partitions.PartitionNames[idx] == defaultPartitionName,
}
})
realName := collection.Schema.GetName()
originalName := collectionName
isAlias := collectionName != "" && realName != "" && realName != collectionName
@@ -505,7 +501,6 @@ func (m *MetaCache) update(ctx context.Context, database, collectionName string,
collID: collection.CollectionID,
dbName: collection.GetDbName(),
schema: schemaInfo,
partInfo: parsePartitionsInfo(infos, schemaInfo.hasPartitionKeyField),
createdTimestamp: collection.CreatedTimestamp,
createdUtcTimestamp: collection.CreatedUtcTimestamp,
consistencyLevel: collection.ConsistencyLevel,
@@ -538,7 +533,6 @@ func (m *MetaCache) update(ctx context.Context, database, collectionName string,
collID: collection.CollectionID,
dbName: collection.GetDbName(),
schema: schemaInfo,
partInfo: parsePartitionsInfo(infos, schemaInfo.hasPartitionKeyField),
createdTimestamp: collection.CreatedTimestamp,
createdUtcTimestamp: collection.CreatedUtcTimestamp,
consistencyLevel: collection.ConsistencyLevel,
@@ -556,7 +550,6 @@ func (m *MetaCache) update(ctx context.Context, database, collectionName string,
log.Ctx(ctx).Info("meta update success", zap.String("database", database), zap.String("collectionName", collectionName),
zap.String("actual collection Name", collection.Schema.GetName()), zap.Int64("collectionID", collection.CollectionID),
zap.Strings("partition", partitions.PartitionNames), zap.Uint64("currentVersion", curVersion),
zap.Uint64("version", collection.GetRequestTime()), zap.Any("aliases", collection.Aliases),
zap.Bool("partition key isolation", isolation), zap.String("queryMode", queryMode),
)
@@ -575,6 +568,10 @@ func buildSfKeyById(database string, collectionID UniqueID) string {
return database + "--" + fmt.Sprint(collectionID)
}
func buildPartitionSfKey(database, collectionName, partitionName string) string {
return database + "-" + collectionName + "-" + partitionName
}
func (m *MetaCache) UpdateByName(ctx context.Context, database, collectionName string) (*collectionInfo, error) {
collection, err, _ := m.sfGlobal.Do(buildSfKeyByName(database, collectionName), func() (*collectionInfo, error) {
return m.update(ctx, database, collectionName, 0)
@@ -824,24 +821,55 @@ func (m *MetaCache) GetPartitions(ctx context.Context, database, collectionName
}
func (m *MetaCache) GetPartitionInfo(ctx context.Context, database, collectionName string, partitionName string) (*partitionInfo, error) {
partitions, err := m.GetPartitionInfos(ctx, database, collectionName)
// Handle empty partitionName - use default partition
if partitionName == "" {
partitionName = Params.CommonCfg.DefaultPartitionName.GetValue()
}
key := buildPartitionSfKey(database, collectionName, partitionName)
entry, ok, release := m.partitionCache.Lookup(key)
defer release(entry)
if ok && entry.state == EntryStateActive && entry.value != nil {
return entry.value, nil
}
collectionKey := buildSfKeyByName(database, collectionName)
_, err, _ := m.sfPartitionCache.Do(collectionKey, func() (*partitionInfo, error) {
// as rootcoord does not support show partitions by partition name, we need to get all partitions first.
resp, err := m.showPartitions(ctx, database, collectionName, 0)
if err != nil {
return nil, err
}
keys := make([]string, 0)
values := make([]*partitionInfo, 0)
versions := make([]uint64, 0)
var ret *partitionInfo
for i := range resp.PartitionNames {
keys = append(keys, buildPartitionSfKey(database, collectionName, resp.PartitionNames[i]))
values = append(values, &partitionInfo{
name: resp.PartitionNames[i],
partitionID: resp.PartitionIDs[i],
createdTimestamp: resp.CreatedTimestamps[i],
createdUtcTimestamp: resp.CreatedUtcTimestamps[i],
})
versions = append(versions, resp.CreatedTimestamps[i])
if resp.PartitionNames[i] == partitionName {
ret = values[i]
}
}
m.partitionCache.InsertBatchWithoutRef(keys, values, versions)
return ret, nil
})
if err != nil {
return nil, err
}
if partitionName == "" {
for _, info := range partitions.partitionInfos {
if info.isDefault {
return info, nil
}
}
entry, ok, release = m.partitionCache.Lookup(key)
defer release(entry)
if ok && entry.state == EntryStateActive && entry.value != nil {
return entry.value, nil
}
info, ok := partitions.name2Info[partitionName]
if !ok {
return nil, merr.WrapErrPartitionNotFound(partitionName)
}
return info, nil
return nil, merr.WrapErrPartitionNotFound(partitionName)
}
func (m *MetaCache) GetPartitionsIndex(ctx context.Context, database, collectionName string) ([]string, error) {
@@ -859,21 +887,47 @@ func (m *MetaCache) GetPartitionsIndex(ctx context.Context, database, collection
func (m *MetaCache) GetPartitionInfos(ctx context.Context, database, collectionName string) (*partitionInfos, error) {
method := "GetPartitionInfo"
collInfo, ok := m.getCollection(database, collectionName, 0)
if !ok {
tr := timerecord.NewTimeRecorder("UpdateCache")
metrics.ProxyCacheStatsCounter.WithLabelValues(paramtable.GetStringNodeID(), method, metrics.CacheMissLabel).Inc()
collInfo, err := m.UpdateByName(ctx, database, collectionName)
key := buildSfKeyByName(database, collectionName)
entry, ok, release := m.collLevelPartitionCache.Lookup(key)
defer release(entry)
if ok && entry.state == EntryStateActive && entry.value != nil {
return entry.value, nil
}
tr := timerecord.NewTimeRecorder("UpdateCache")
metrics.ProxyCacheStatsCounter.WithLabelValues(paramtable.GetStringNodeID(), method, metrics.CacheMissLabel).Inc()
partitionsInfo, err, _ := m.sfCollLevelPartitionCache.Do(key, func() (*partitionInfos, error) {
collection, err := m.describeCollection(ctx, database, collectionName, 0)
if err != nil {
return nil, err
}
schemaInfo := newSchemaInfo(collection.Schema)
resp, err := m.showPartitions(ctx, database, collectionName, 0)
if err != nil {
return nil, err
}
partitions := make([]*partitionInfo, 0)
for i, name := range resp.PartitionNames {
partitions = append(partitions, &partitionInfo{
name: name,
partitionID: resp.PartitionIDs[i],
createdTimestamp: resp.CreatedTimestamps[i],
createdUtcTimestamp: resp.CreatedUtcTimestamps[i],
})
}
partitionsInfo := parsePartitionsInfo(partitions, schemaInfo.IsPartitionKeyCollection())
entry, release := m.collLevelPartitionCache.Insert(key, partitionsInfo, collection.RequestTime)
defer release(entry)
metrics.ProxyUpdateCacheLatency.WithLabelValues(paramtable.GetStringNodeID(), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return collInfo.partInfo, nil
return entry.value, nil
})
if err != nil {
return nil, err
}
return collInfo.partInfo, nil
if partitionsInfo == nil {
return nil, merr.WrapErrServiceInternal("partition info not found")
}
return partitionsInfo, nil
}
// Get the collection information from rootcoord.
@@ -927,6 +981,13 @@ func (m *MetaCache) showPartitions(ctx context.Context, dbName string, collectio
return nil, fmt.Errorf("partition ids len: %d doesn't equal Partition name len %d",
len(partitions.PartitionIDs), len(partitions.PartitionNames))
}
if len(partitions.PartitionNames) != len(partitions.CreatedTimestamps) ||
len(partitions.PartitionNames) != len(partitions.CreatedUtcTimestamps) {
return nil, merr.WrapErrParameterInvalidMsg(
"partition names and timestamps number is not aligned, response: %s",
partitions.String(),
)
}
return partitions, nil
}
@@ -1032,13 +1093,22 @@ func (m *MetaCache) removeCollectionByID(ctx context.Context, collectionID Uniqu
if version == 0 || curVersion <= version {
delete(m.collInfo[database], k)
collNames = append(collNames, k)
m.sfGlobal.Forget(buildSfKeyByName(database, k))
collectionKey := buildSfKeyByName(database, k)
m.sfGlobal.Forget(collectionKey)
m.sfGlobal.Forget(buildSfKeyById(database, v.collID))
realName := k
if v.schema != nil && v.schema.GetName() != "" {
realName = v.schema.GetName()
}
m.removeAliasesForCollectionLocked(database, realName)
m.sfCollLevelPartitionCache.Forget(collectionKey)
m.collLevelPartitionCache.Stale(collectionKey, version)
partitionPrefix := database + "-" + realName + "-"
m.sfPartitionCache.Forget(collectionKey)
m.partitionCache.StaleIf(func(key string) bool {
return strings.HasPrefix(key, partitionPrefix)
}, version)
}
}
}
@@ -1058,9 +1128,28 @@ func (m *MetaCache) RemoveDatabase(ctx context.Context, database string) {
log.Ctx(ctx).Debug("remove database", zap.String("name", database))
m.mu.Lock()
defer m.mu.Unlock()
// Forget singleflight keys for all collections in this database
if db, ok := m.collInfo[database]; ok {
for collectionName := range db {
collectionKey := buildSfKeyByName(database, collectionName)
m.sfCollLevelPartitionCache.Forget(collectionKey)
m.sfPartitionCache.Forget(collectionKey)
}
}
delete(m.collInfo, database)
delete(m.dbInfo, database)
delete(m.aliasInfo, database)
// Clean up partition cache
prefix := database + "-"
m.collLevelPartitionCache.StaleIf(func(key string) bool {
return strings.HasPrefix(key, prefix)
}, 0)
m.partitionCache.StaleIf(func(key string) bool {
return strings.HasPrefix(key, prefix)
}, 0)
}
func (m *MetaCache) HasDatabase(ctx context.Context, database string) bool {
@@ -1129,3 +1218,178 @@ func (m *MetaCache) AllocID(ctx context.Context) (int64, error) {
m.IDIndex++
return id, nil
}
func (m *MetaCache) RemovePartition(ctx context.Context, database string, collectionID UniqueID, collectionName string, partitionName string, version uint64) {
m.ensureCollectionForPartitionInvalidation(ctx, database, collectionID, collectionName)
m.mu.Lock()
defer m.mu.Unlock()
names := make(map[string]struct{})
realNames := make(map[string]struct{})
for _, dbName := range partitionInvalidationDatabases(database) {
m.collectPartitionCacheNamesLocked(dbName, collectionID, collectionName, names, realNames)
for name := range names {
if coll, ok := m.getCollectionLocked(dbName, name); ok && coll.schema != nil {
realName := coll.schema.GetName()
if realName != "" {
realNames[realName] = struct{}{}
}
}
}
if aliases, ok := m.aliasInfo[dbName]; ok {
for alias, entry := range aliases {
if entry == nil || entry.collectionName == "" {
continue
}
if _, ok := realNames[entry.collectionName]; ok {
names[alias] = struct{}{}
names[entry.collectionName] = struct{}{}
}
}
}
for name := range names {
m.stalePartitionCacheLocked(dbName, name, partitionName, version)
}
clear(names)
clear(realNames)
}
log.Ctx(ctx).Debug("remove partition", zap.String("db", database), zap.Int64("collectionID", collectionID), zap.String("collection", collectionName), zap.String("partition", partitionName), zap.Uint64("version", version))
}
func (m *MetaCache) ensureCollectionForPartitionInvalidation(ctx context.Context, database string, collectionID UniqueID, collectionName string) {
if collectionID != 0 {
for _, dbName := range partitionInvalidationDatabases(database) {
if _, ok := m.getCollection(dbName, "", collectionID); ok {
return
}
}
fetchDB := database
if fetchDB == "" {
fetchDB = defaultDB
}
if _, err := m.UpdateByID(ctx, fetchDB, collectionID); err != nil {
log.Ctx(ctx).Debug("failed to refresh collection cache before partition invalidation",
zap.String("db", fetchDB),
zap.Int64("collectionID", collectionID),
zap.Error(err))
}
return
}
if collectionName == "" {
return
}
for _, dbName := range partitionInvalidationDatabases(database) {
if _, ok := m.getCollection(dbName, collectionName, 0); ok {
return
}
}
fetchDB := database
if fetchDB == "" {
fetchDB = defaultDB
}
if _, err := m.UpdateByName(ctx, fetchDB, collectionName); err != nil {
log.Ctx(ctx).Debug("failed to refresh collection cache by name before partition invalidation",
zap.String("db", fetchDB),
zap.String("collection", collectionName),
zap.Error(err))
}
}
func partitionInvalidationDatabases(database string) []string {
if database == "" {
return []string{database, defaultDB}
}
return []string{database}
}
func (m *MetaCache) getCollectionLocked(database, collectionName string) (*collectionInfo, bool) {
db, ok := m.collInfo[database]
if !ok {
return nil, false
}
collection, ok := db[collectionName]
return collection, ok
}
func (m *MetaCache) collectPartitionCacheNamesLocked(database string, collectionID UniqueID, collectionName string, names, realNames map[string]struct{}) {
if collectionName != "" {
names[collectionName] = struct{}{}
if coll, ok := m.getCollectionLocked(database, collectionName); ok && coll.schema != nil {
realName := coll.schema.GetName()
if realName != "" {
names[realName] = struct{}{}
realNames[realName] = struct{}{}
}
for _, alias := range coll.aliases {
names[alias] = struct{}{}
}
}
}
if collectionID == 0 {
return
}
if db, ok := m.collInfo[database]; ok {
for name, coll := range db {
if coll.collID != collectionID {
continue
}
names[name] = struct{}{}
if coll.schema != nil {
realName := coll.schema.GetName()
if realName != "" {
names[realName] = struct{}{}
realNames[realName] = struct{}{}
}
}
for _, alias := range coll.aliases {
names[alias] = struct{}{}
}
}
}
}
func (m *MetaCache) stalePartitionCacheLocked(database, collectionName, partitionName string, version uint64) {
collectionKey := buildSfKeyByName(database, collectionName)
m.sfPartitionCache.Forget(collectionKey)
m.partitionCache.Stale(buildPartitionSfKey(database, collectionName, partitionName), version)
m.sfCollLevelPartitionCache.Forget(collectionKey)
m.collLevelPartitionCache.Stale(collectionKey, version)
}
func (m *MetaCache) backgroundGCLoop(stopCh <-chan struct{}) {
go func() {
interval := Params.ProxyCfg.MetaCacheGCTimeInterval.GetAsDuration(time.Second)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
m.partitionCache.Prune()
m.collLevelPartitionCache.Prune()
newInterval := Params.ProxyCfg.MetaCacheGCTimeInterval.GetAsDuration(time.Second)
if newInterval != interval {
interval = newInterval
ticker.Reset(interval)
}
case <-stopCh:
return
}
}
}()
}
func (m *MetaCache) Close() {
m.closeOnce.Do(func() {
close(m.stopCh)
})
}
+423 -14
View File
@@ -1106,13 +1106,6 @@ func TestMetaCacheGetCollectionWithUpdate(t *testing.T) {
PhysicalChannelNames: []string{"by-dev-rootcoord-dml_1"},
VirtualChannelNames: []string{"by-dev-rootcoord-dml_1_1v0"},
}, nil).Once()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []typeutil.UniqueID{11},
PartitionNames: []string{"p1"},
CreatedTimestamps: []uint64{11},
CreatedUtcTimestamps: []uint64{11},
}, nil).Once()
c, err := globalMetaCache.GetCollectionInfo(ctx, "foo", "bar", 1)
assert.NoError(t, err)
assert.Equal(t, c.collID, int64(1))
@@ -1140,13 +1133,6 @@ func TestMetaCacheGetCollectionWithUpdate(t *testing.T) {
PhysicalChannelNames: []string{"by-dev-rootcoord-dml_1"},
VirtualChannelNames: []string{"by-dev-rootcoord-dml_1_1v0"},
}, nil).Once()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []typeutil.UniqueID{11},
PartitionNames: []string{"p1"},
CreatedTimestamps: []uint64{11},
CreatedUtcTimestamps: []uint64{11},
}, nil).Once()
c, err := globalMetaCache.GetCollectionInfo(ctx, "foo", "hoo", 0)
assert.NoError(t, err)
assert.Equal(t, c.collID, int64(1))
@@ -2072,3 +2058,426 @@ func TestMetaCache_GetShardLeaderList(t *testing.T) {
t.Skip("GetShardLeaderList has been moved to ShardClientMgr in shardclient package")
// Test body removed - functionality moved to shardclient package
}
func TestVersionCache(t *testing.T) {
t.Run("Lookup_Miss", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
entry, ok, release := cache.Lookup("key1")
assert.False(t, ok)
assert.Nil(t, entry)
release(entry)
})
t.Run("Insert_And_Lookup", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
value := 100
entry, release := cache.Insert("key1", &value, 1000)
assert.NotNil(t, entry)
assert.Equal(t, 100, *entry.value)
release(entry)
entry2, ok, release2 := cache.Lookup("key1")
assert.True(t, ok)
assert.Equal(t, 100, *entry2.value)
release2(entry2)
})
t.Run("Insert_Higher_Version_Overwrites", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
value1 := 100
entry1, release1 := cache.Insert("key1", &value1, 1000)
release1(entry1)
value2 := 200
entry2, release2 := cache.Insert("key1", &value2, 2000)
assert.Equal(t, 200, *entry2.value)
release2(entry2)
entry3, ok, release3 := cache.Lookup("key1")
assert.True(t, ok)
assert.Equal(t, 200, *entry3.value)
release3(entry3)
})
t.Run("Insert_Lower_Version_Ignored", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
value1 := 100
entry1, release1 := cache.Insert("key1", &value1, 2000)
release1(entry1)
value2 := 200
entry2, release2 := cache.Insert("key1", &value2, 1000)
assert.Equal(t, 100, *entry2.value)
release2(entry2)
})
t.Run("Stale_Erase_When_No_Refs", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
value := 100
entry, release := cache.Insert("key1", &value, 1000)
release(entry)
cache.Stale("key1", 2000)
_, ok, release2 := cache.Lookup("key1")
assert.False(t, ok)
release2(nil)
})
t.Run("Stale_Marks_Entry_With_Active_Refs", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
value := 100
entry, release := cache.Insert("key1", &value, 1000)
assert.Equal(t, EntryStateActive, entry.state)
cache.Stale("key1", 2000)
// Entry should be marked as stale
entry2, ok, release2 := cache.Lookup("key1")
assert.True(t, ok)
assert.Equal(t, EntryStateStale, entry2.state)
release2(entry2)
release(entry)
// After release, Stale should erase
cache.Stale("key1", 3000)
_, ok, release3 := cache.Lookup("key1")
assert.False(t, ok)
release3(nil)
})
t.Run("Prune_Only_Stale_Entries", func(t *testing.T) {
cache := NewVersionCache[string, *int]()
// key1: Active, ref=0 - should NOT be pruned
value1 := 100
entry1, release1 := cache.Insert("key1", &value1, 1000)
release1(entry1)
// key2: Stale, ref=0 - should be pruned
value2 := 200
entry2, release2 := cache.Insert("key2", &value2, 2000)
release2(entry2)
cache.Stale("key2", 2500)
// key3: Stale, ref=1 - should NOT be pruned
value3 := 300
entry3, _ := cache.Insert("key3", &value3, 3000)
cache.Stale("key3", 3500)
cache.Prune()
// key1 should still exist (Active, ref=0)
entry1Found, ok1, release1Found := cache.Lookup("key1")
assert.True(t, ok1)
assert.Equal(t, 100, *entry1Found.value)
release1Found(entry1Found)
// key2 should be gone (Stale, ref=0)
_, ok2, release2Found := cache.Lookup("key2")
assert.False(t, ok2)
release2Found(nil)
// key3 should still exist (Stale, ref=1)
entry3Found, ok3, release3Found := cache.Lookup("key3")
assert.True(t, ok3)
assert.Equal(t, EntryStateStale, entry3Found.state)
release3Found(entry3Found)
// Release the ref on entry3
release1(entry3)
})
}
func TestMetaCache_GetPartitionInfo_CacheHit(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{100},
PartitionNames: []string{"par1"},
CreatedTimestamps: []uint64{1000},
CreatedUtcTimestamps: []uint64{1000},
}, nil).Once()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
info1, err := cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.NoError(t, err)
assert.Equal(t, int64(100), info1.partitionID)
assert.Equal(t, "par1", info1.name)
info2, err := cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.NoError(t, err)
assert.Equal(t, info1, info2)
}
func TestMetaCache_GetPartitionInfo_DefaultPartition(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
defaultPartitionName := Params.CommonCfg.DefaultPartitionName.GetValue()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.MatchedBy(func(req *milvuspb.ShowPartitionsRequest) bool {
return len(req.PartitionNames) == 0
})).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{1},
PartitionNames: []string{defaultPartitionName},
CreatedTimestamps: []uint64{1000},
CreatedUtcTimestamps: []uint64{1000},
}, nil).Once()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
info, err := cache.GetPartitionInfo(ctx, "db", "collection", "")
assert.NoError(t, err)
assert.Equal(t, int64(1), info.partitionID)
assert.Equal(t, defaultPartitionName, info.name)
}
func TestMetaCache_GetPartitionInfo_Error(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).Return(nil, errors.New("connection failed")).Once()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
_, err = cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.Error(t, err)
}
func TestMetaCache_GetPartitionInfos_CacheHit(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
rootCoord.EXPECT().DescribeCollection(mock.Anything, mock.Anything).Return(&milvuspb.DescribeCollectionResponse{
Status: merr.Success(),
CollectionID: 1,
Schema: &schemapb.CollectionSchema{
Name: "collection",
Fields: []*schemapb.FieldSchema{},
},
RequestTime: 1000,
}, nil).Once()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{100, 101},
PartitionNames: []string{"par1", "par2"},
CreatedTimestamps: []uint64{1000, 1001},
CreatedUtcTimestamps: []uint64{1000, 1001},
}, nil).Once()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
infos1, err := cache.GetPartitionInfos(ctx, "db", "collection")
assert.NoError(t, err)
assert.Equal(t, 2, len(infos1.partitionInfos))
infos2, err := cache.GetPartitionInfos(ctx, "db", "collection")
assert.NoError(t, err)
assert.Equal(t, infos1, infos2)
}
func TestMetaCache_RemovePartition(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
rootCoord.EXPECT().DescribeCollection(mock.Anything, mock.Anything).Return(&milvuspb.DescribeCollectionResponse{
Status: merr.Success(),
CollectionID: 1,
Schema: &schemapb.CollectionSchema{
Name: "collection",
Fields: []*schemapb.FieldSchema{},
},
Aliases: []string{"alias"},
RequestTime: 1000,
}, nil).Once()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).Return(&milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{100},
PartitionNames: []string{"par1"},
CreatedTimestamps: []uint64{1000},
CreatedUtcTimestamps: []uint64{1000},
}, nil).Times(4)
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
_, err = cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.NoError(t, err)
_, err = cache.GetPartitionInfo(ctx, "db", "alias", "par1")
assert.NoError(t, err)
cache.RemovePartition(ctx, "db", 1, "alias", "par1", 2000)
_, err = cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.NoError(t, err)
_, err = cache.GetPartitionInfo(ctx, "db", "alias", "par1")
assert.NoError(t, err)
}
func TestMetaCache_PartitionCache_Concurrent(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
var callCount atomic.Int32
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).RunAndReturn(
func(ctx context.Context, req *milvuspb.ShowPartitionsRequest, opts ...grpc.CallOption) (*milvuspb.ShowPartitionsResponse, error) {
callCount.Add(1)
time.Sleep(50 * time.Millisecond)
return &milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{100},
PartitionNames: []string{"par1"},
CreatedTimestamps: []uint64{1000},
CreatedUtcTimestamps: []uint64{1000},
}, nil
})
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
numGoroutines := 10
var wg sync.WaitGroup
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := cache.GetPartitionInfo(ctx, "db", "collection", "par1")
assert.NoError(t, err)
}()
}
wg.Wait()
assert.Equal(t, int32(1), callCount.Load(), "Singleflight should merge concurrent requests")
}
func TestMetaCache_GetPartitionInfos_SingleflightKeyIncludesDatabase(t *testing.T) {
ctx := context.Background()
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
rootCoord.EXPECT().DescribeCollection(mock.Anything, mock.Anything).RunAndReturn(
func(ctx context.Context, req *milvuspb.DescribeCollectionRequest, opts ...grpc.CallOption) (*milvuspb.DescribeCollectionResponse, error) {
time.Sleep(50 * time.Millisecond)
return &milvuspb.DescribeCollectionResponse{
Status: merr.Success(),
CollectionID: 1,
Schema: &schemapb.CollectionSchema{
Name: req.GetCollectionName(),
Fields: []*schemapb.FieldSchema{},
},
RequestTime: 1000,
}, nil
}).Twice()
rootCoord.EXPECT().ShowPartitions(mock.Anything, mock.Anything).RunAndReturn(
func(ctx context.Context, req *milvuspb.ShowPartitionsRequest, opts ...grpc.CallOption) (*milvuspb.ShowPartitionsResponse, error) {
return &milvuspb.ShowPartitionsResponse{
Status: merr.Success(),
PartitionIDs: []int64{100},
PartitionNames: []string{req.GetDbName() + "_par"},
CreatedTimestamps: []uint64{1000},
CreatedUtcTimestamps: []uint64{1000},
}, nil
}).Twice()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
defer cache.Close()
type result struct {
db string
infos *partitionInfos
err error
}
results := make(chan result, 2)
var wg sync.WaitGroup
start := make(chan struct{})
for _, db := range []string{"db1", "db2"} {
wg.Add(1)
go func(db string) {
defer wg.Done()
<-start
infos, err := cache.GetPartitionInfos(ctx, db, "collection")
results <- result{
db: db,
infos: infos,
err: err,
}
}(db)
}
close(start)
wg.Wait()
close(results)
for result := range results {
assert.NoError(t, result.err)
if assert.NotNil(t, result.infos) && assert.Len(t, result.infos.partitionInfos, 1) {
assert.Equal(t, result.db+"_par", result.infos.partitionInfos[0].name)
}
}
}
func TestMetaCache_Close(t *testing.T) {
rootCoord := mocks.NewMockMixCoordClient(t)
rootCoord.EXPECT().ListPolicy(mock.Anything, mock.Anything).Return(&internalpb.ListPolicyResponse{
Status: merr.Success(),
}, nil).Maybe()
cache, err := NewMetaCache(rootCoord)
assert.NoError(t, err)
cache.Close()
cache.Close()
}
+70
View File
@@ -77,6 +77,38 @@ func (_c *MockCache_AllocID_Call) RunAndReturn(run func(context.Context) (int64,
return _c
}
// Close provides a mock function with no fields
func (_m *MockCache) Close() {
_m.Called()
}
// MockCache_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type MockCache_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *MockCache_Expecter) Close() *MockCache_Close_Call {
return &MockCache_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *MockCache_Close_Call) Run(run func()) *MockCache_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockCache_Close_Call) Return() *MockCache_Close_Call {
_c.Call.Return()
return _c
}
func (_c *MockCache_Close_Call) RunAndReturn(run func()) *MockCache_Close_Call {
_c.Call.Return(run)
return _c
}
// GetCollectionID provides a mock function with given fields: ctx, database, collectionName
func (_m *MockCache) GetCollectionID(ctx context.Context, database string, collectionName string) (int64, error) {
ret := _m.Called(ctx, database, collectionName)
@@ -875,6 +907,44 @@ func (_c *MockCache_RemoveDatabase_Call) RunAndReturn(run func(context.Context,
return _c
}
// RemovePartition provides a mock function with given fields: ctx, database, collectionID, collectionName, partitionName, version
func (_m *MockCache) RemovePartition(ctx context.Context, database string, collectionID UniqueID, collectionName string, partitionName string, version uint64) {
_m.Called(ctx, database, collectionID, collectionName, partitionName, version)
}
// MockCache_RemovePartition_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemovePartition'
type MockCache_RemovePartition_Call struct {
*mock.Call
}
// RemovePartition is a helper method to define mock.On call
// - ctx context.Context
// - database string
// - collectionID UniqueID
// - collectionName string
// - partitionName string
// - version uint64
func (_e *MockCache_Expecter) RemovePartition(ctx interface{}, database interface{}, collectionID interface{}, collectionName interface{}, partitionName interface{}, version interface{}) *MockCache_RemovePartition_Call {
return &MockCache_RemovePartition_Call{Call: _e.mock.On("RemovePartition", ctx, database, collectionID, collectionName, partitionName, version)}
}
func (_c *MockCache_RemovePartition_Call) Run(run func(ctx context.Context, database string, collectionID UniqueID, collectionName string, partitionName string, version uint64)) *MockCache_RemovePartition_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(UniqueID), args[3].(string), args[4].(string), args[5].(uint64))
})
return _c
}
func (_c *MockCache_RemovePartition_Call) Return() *MockCache_RemovePartition_Call {
_c.Call.Return()
return _c
}
func (_c *MockCache_RemovePartition_Call) RunAndReturn(run func(context.Context, string, UniqueID, string, string, uint64)) *MockCache_RemovePartition_Call {
_c.Run(run)
return _c
}
// ResolveCollectionAlias provides a mock function with given fields: ctx, database, nameOrAlias
func (_m *MockCache) ResolveCollectionAlias(ctx context.Context, database string, nameOrAlias string) (string, error) {
ret := _m.Called(ctx, database, nameOrAlias)
+4
View File
@@ -339,6 +339,10 @@ func (node *Proxy) Stop() error {
node.resourceManager.Close()
}
if globalMetaCache != nil {
globalMetaCache.Close()
}
node.cancel()
node.wg.Wait()
+4
View File
@@ -678,6 +678,10 @@ func (coord *MixCoordMock) CreatePartition(ctx context.Context, req *milvuspb.Cr
return merr.Success(), nil
}
func (coord *MixCoordMock) CreatePartitionV2(ctx context.Context, req *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error) {
return &rootcoordpb.CreatePartitionResponse{}, nil
}
func (coord *MixCoordMock) DropPartition(ctx context.Context, req *milvuspb.DropPartitionRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
code := coord.state.Load().(commonpb.StateCode)
if code != commonpb.StateCode_Healthy {
+7 -8
View File
@@ -2224,7 +2224,11 @@ func (t *createPartitionTask) PreExecute(ctx context.Context) error {
}
func (t *createPartitionTask) Execute(ctx context.Context) (err error) {
t.result, err = t.mixCoord.CreatePartition(ctx, t.CreatePartitionRequest)
resp, err := t.mixCoord.CreatePartitionV2(ctx, t.CreatePartitionRequest)
if err != nil {
return err
}
t.result = resp.Status
if err := merr.CheckRPCCall(t.result, err); err != nil {
return err
}
@@ -2233,15 +2237,10 @@ func (t *createPartitionTask) Execute(ctx context.Context) (err error) {
t.result = merr.Status(err)
return err
}
partitionID, err := globalMetaCache.GetPartitionID(ctx, t.GetDbName(), t.GetCollectionName(), t.GetPartitionName())
if err != nil {
t.result = merr.Status(err)
return err
}
t.result, err = t.mixCoord.SyncNewCreatedPartition(ctx, &querypb.SyncNewCreatedPartitionRequest{
Base: commonpbutil.NewMsgBase(commonpbutil.WithMsgType(commonpb.MsgType_ReleasePartitions)),
Base: commonpbutil.NewMsgBase(commonpbutil.WithMsgType(commonpb.MsgType_CreatePartition)),
CollectionID: collectionID,
PartitionID: partitionID,
PartitionID: resp.PartitionID,
})
return merr.CheckRPCCall(t.result, err)
}
+5 -2
View File
@@ -49,6 +49,7 @@ import (
"github.com/milvus-io/milvus/pkg/v3/proto/indexpb"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"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/commonpbutil"
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
@@ -2491,8 +2492,10 @@ func TestCreatePartitionTask(t *testing.T) {
// setup global meta cache
mockCache.EXPECT().GetCollectionID(mock.Anything, mock.Anything, mock.Anything).Return(100, nil).Once()
mockCache.EXPECT().GetPartitionID(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(1000, nil).Once()
rc.EXPECT().CreatePartition(mock.Anything, mock.Anything).Return(merr.Success(), nil).Once()
rc.EXPECT().CreatePartitionV2(mock.Anything, mock.Anything).Return(&rootcoordpb.CreatePartitionResponse{
Status: merr.Success(),
PartitionID: 1000,
}, nil).Once()
rc.EXPECT().SyncNewCreatedPartition(mock.Anything, mock.Anything).Return(merr.Success(), nil).Once()
err := task.Execute(ctx)
assert.NoError(t, err)
+207
View File
@@ -0,0 +1,207 @@
// 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 proxy
import "sync"
// EntryState represents the state of a cache entry.
type EntryState int
const (
EntryStateActive EntryState = iota
EntryStateStale
)
type VersionTable[K comparable, V any] struct {
entries map[K]*VersionEntry[K, V]
}
func NewVersionTable[K comparable, V any]() *VersionTable[K, V] {
return &VersionTable[K, V]{
entries: make(map[K]*VersionEntry[K, V]),
}
}
type VersionEntry[key comparable, V any] struct {
key key
value V
version uint64
state EntryState
}
func (t *VersionTable[K, V]) Lookup(key K) (*VersionEntry[K, V], bool) {
entry, ok := t.entries[key]
if !ok {
return nil, false
}
return entry, true
}
func (t *VersionTable[K, V]) Insert(key K, value V, version uint64) *VersionEntry[K, V] {
entry, ok := t.entries[key]
if !ok || version > entry.version {
newEntry := &VersionEntry[K, V]{
key: key,
value: value,
version: version,
state: EntryStateActive,
}
t.entries[key] = newEntry
return newEntry
}
return entry
}
func (t *VersionTable[K, V]) Erase(key K) {
delete(t.entries, key)
}
func (t *VersionTable[K, V]) Stale(key K, version uint64) {
var v V
newEntry := &VersionEntry[K, V]{
key: key,
value: v,
version: version,
state: EntryStateStale,
}
t.entries[key] = newEntry
}
type RefCount[K comparable] map[K]uint64
func (r RefCount[K]) Inc(key K) {
r[key]++
}
func (r RefCount[K]) Dec(key K) {
r[key]--
}
func (r RefCount[K]) Count(key K) uint64 {
return r[key]
}
func (r RefCount[K]) Erase(key K) {
delete(r, key)
}
type VersionCache[K comparable, V any] struct {
sync.Mutex
table *VersionTable[K, V]
refs RefCount[K]
}
// ReleaseFunc is a function that releases the entry.
type ReleaseFunc[K comparable, V any] func(entry *VersionEntry[K, V])
// Lookup returns the entry if it exists and increments the reference count.
// Caller must call Release to decrement the reference count after using the entry.
func (c *VersionCache[K, V]) Lookup(key K) (*VersionEntry[K, V], bool, ReleaseFunc[K, V]) {
c.Lock()
defer c.Unlock()
entry, ok := c.table.Lookup(key)
if ok {
c.refs.Inc(key)
return entry, true, c.Release
}
return nil, false, c.Release
}
// Insert inserts a new entry into the cache and increments the reference count.
// Caller must call Release to decrement the reference count after using the entry.
func (c *VersionCache[K, V]) Insert(key K, value V, version uint64) (*VersionEntry[K, V], ReleaseFunc[K, V]) {
c.Lock()
defer c.Unlock()
entry := c.table.Insert(key, value, version)
c.refs.Inc(key)
return entry, c.Release
}
func (c *VersionCache[K, V]) InsertBatchWithoutRef(keys []K, values []V, versions []uint64) {
c.Lock()
defer c.Unlock()
for i := range keys {
c.table.Insert(keys[i], values[i], versions[i])
}
}
// Release decrements the reference count for the entry
// Users should always call this function to decrement the reference count after using the entry.
func (c *VersionCache[K, V]) Release(entry *VersionEntry[K, V]) {
if entry == nil {
return
}
c.Lock()
defer c.Unlock()
c.refs.Dec(entry.key)
}
// Stale marks the entry as stale or erases it if the reference count is 0.
func (c *VersionCache[K, V]) Stale(key K, version uint64) {
c.Lock()
defer c.Unlock()
if c.refs.Count(key) == 0 {
c.table.Erase(key)
c.refs.Erase(key)
} else {
c.table.Stale(key, version)
}
}
// StaleIf marks entries as stale or erases them based on the predicate.
// For each entry where predicate returns true:
// - if ref count is 0, erase it directly
// - otherwise, mark it as Stale
func (c *VersionCache[K, V]) StaleIf(predicate func(K) bool, version uint64) {
c.Lock()
defer c.Unlock()
for key := range c.table.entries {
if predicate(key) {
if c.refs.Count(key) == 0 {
c.table.Erase(key)
c.refs.Erase(key)
} else {
c.table.Stale(key, version)
}
}
}
}
// Prune erases all entries that are stale and have reference count 0.
// Users should call this function if they care about memory usage.
func (c *VersionCache[K, V]) Prune() {
c.Lock()
defer c.Unlock()
for key, entry := range c.table.entries {
if c.refs.Count(key) == 0 && entry.state == EntryStateStale {
c.table.Erase(key)
c.refs.Erase(key)
}
}
}
func NewVersionCache[K comparable, V any]() *VersionCache[K, V] {
return &VersionCache[K, V]{
table: NewVersionTable[K, V](),
refs: make(RefCount[K]),
}
}
+1 -2
View File
@@ -74,7 +74,6 @@ func (job *SyncNewCreatedPartitionJob) Execute() error {
if collection == nil || collection.GetLoadType() == querypb.LoadType_LoadPartition {
return nil
}
// check if partition already existed
if partition := job.meta.GetPartition(job.ctx, job.req.GetPartitionID()); partition != nil {
return nil
@@ -96,5 +95,5 @@ func (job *SyncNewCreatedPartitionJob) Execute() error {
return errors.Wrap(err, msg)
}
return WaitCurrentTargetUpdated(job.ctx, job.targetObserver, job.req.GetCollectionID())
return WaitUpdatePartition(job.ctx, job.targetObserver, job.req.GetCollectionID(), job.req.GetPartitionID())
}
+27
View File
@@ -104,6 +104,33 @@ func WaitCurrentTargetUpdated(ctx context.Context, targetObserver *observers.Tar
// accelerate check
targetObserver.TriggerUpdateCurrentTarget(collection)
// wait current target ready
select {
case <-ready:
return nil
case <-ctx.Done():
return errors.Wrapf(ctx.Err(), "context error while waiting for current target updated, collection=%d", collection)
case <-time.After(waitCollectionReleasedTimeout):
return errors.Errorf("wait current target updated timeout, collection=%d", collection)
}
}
func WaitUpdatePartition(ctx context.Context, targetObserver *observers.TargetObserver, collection int64, partition int64) error {
// manual trigger update next target
ready, err := targetObserver.UpdatePartition(collection, partition)
if err != nil {
return errors.Wrapf(err, "failed to update next target, collection=%d", collection)
}
select {
case <-ready:
return nil
default:
}
// accelerate check
targetObserver.TriggerUpdateCurrentTarget(collection)
// wait current target ready
select {
case <-ready:
@@ -436,6 +436,13 @@ func (m *CollectionManager) GetAllPartitions(ctx context.Context) []*Partition {
return lo.Values(m.partitions)
}
func (m *CollectionManager) GetPartitionIDsByCollection(ctx context.Context, collectionID typeutil.UniqueID) []int64 {
m.rwmutex.RLock()
defer m.rwmutex.RUnlock()
return m.collectionPartitions[collectionID].Collect()
}
func (m *CollectionManager) GetPartitionsByCollection(ctx context.Context, collectionID typeutil.UniqueID) []*Partition {
m.rwmutex.RLock()
defer m.rwmutex.RUnlock()
@@ -503,6 +510,13 @@ func (m *CollectionManager) PutPartitionWithoutSave(ctx context.Context, partiti
}
func (m *CollectionManager) putPartition(ctx context.Context, partitions []*Partition, withSave bool) error {
// Check all partitions' collections exist (prevent orphan partitions)
for _, partition := range partitions {
if _, ok := m.collections[partition.GetCollectionID()]; !ok {
return merr.WrapErrCollectionNotLoaded(partition.GetCollectionID())
}
}
if withSave {
loadInfos := lo.Map(partitions, func(partition *Partition, _ int) *querypb.PartitionLoadInfo {
return partition.PartitionLoadInfo
+7 -8
View File
@@ -153,15 +153,14 @@ func (mgr *TargetManager) UpdateCollectionNextTarget(ctx context.Context, collec
return err
}
partitions := mgr.meta.GetPartitionsByCollection(ctx, collectionID)
partitionIDs := lo.Map(partitions, func(partition *Partition, i int) int64 {
return partition.PartitionID
})
segments := make(map[int64]*datapb.SegmentInfo, 0)
partitionSet := typeutil.NewUniqueSet(partitionIDs...)
partitionIDs := mgr.meta.GetPartitionIDsByCollection(ctx, collectionID)
segments := make(map[int64]*datapb.SegmentInfo, len(segmentInfos))
partitionSet := make(map[int64]struct{}, len(partitionIDs))
for _, partitionID := range partitionIDs {
partitionSet[partitionID] = struct{}{}
}
for _, segmentInfo := range segmentInfos {
if partitionSet.Contain(segmentInfo.GetPartitionID()) || segmentInfo.GetPartitionID() == common.AllPartitionsID {
if _, ok := partitionSet[segmentInfo.GetPartitionID()]; ok || segmentInfo.GetPartitionID() == common.AllPartitionsID {
segments[segmentInfo.GetID()] = segmentInfo
}
}
@@ -61,6 +61,7 @@ const (
UpdateCollection targetOp = iota + 1
ReleaseCollection
ReleasePartition
UpdatePartition
)
type targetUpdateRequest struct {
@@ -243,6 +244,39 @@ func (ob *TargetObserver) schedule(ctx context.Context) {
ob.targetMgr.RemovePartitionFromNextTarget(ctx, req.CollectionID, req.PartitionIDs...)
ob.keylocks.Unlock(req.CollectionID)
req.Notifier <- nil
case UpdatePartition:
// Fast path: check with read lock first
ob.keylocks.RLock(req.CollectionID)
exists := ob.targetMgr.IsCurrentTargetExist(ctx, req.CollectionID, req.PartitionIDs[0])
ob.keylocks.RUnlock(req.CollectionID)
if exists {
close(req.ReadyNotifier)
req.Notifier <- nil
} else {
// Slow path: need to update next target
ob.keylocks.Lock(req.CollectionID)
// Double check after acquiring write lock
if ob.targetMgr.IsCurrentTargetExist(ctx, req.CollectionID, req.PartitionIDs[0]) {
close(req.ReadyNotifier)
req.Notifier <- nil
} else {
err := ob.updateNextTarget(ctx, req.CollectionID)
if err != nil {
log.Warn("failed to manually update next target",
zap.Int64("collectionID", req.CollectionID),
zap.String("opType", req.opType.String()),
zap.Error(err))
close(req.ReadyNotifier)
} else {
ob.mut.Lock()
ob.readyNotifiers[req.CollectionID] = append(ob.readyNotifiers[req.CollectionID], req.ReadyNotifier)
ob.mut.Unlock()
}
req.Notifier <- err
}
ob.keylocks.Unlock(req.CollectionID)
}
}
log.Info("manually trigger update target done",
zap.Int64("collectionID", req.CollectionID),
@@ -324,6 +358,20 @@ func (ob *TargetObserver) UpdateNextTarget(collectionID int64) (chan struct{}, e
return readyCh, <-notifier
}
func (ob *TargetObserver) UpdatePartition(collectionID int64, partitionID int64) (chan struct{}, error) {
notifier := make(chan error)
readyCh := make(chan struct{})
defer close(notifier)
ob.updateChan <- targetUpdateRequest{
CollectionID: collectionID,
PartitionIDs: []int64{partitionID},
opType: UpdatePartition,
Notifier: notifier,
ReadyNotifier: readyCh,
}
return readyCh, <-notifier
}
func (ob *TargetObserver) ReleaseCollection(collectionID int64) {
notifier := make(chan error)
defer close(notifier)
+19 -1
View File
@@ -482,7 +482,25 @@ func (s *Server) SyncNewCreatedPartition(ctx context.Context, req *querypb.SyncN
}
syncJob := job.NewSyncNewCreatedPartitionJob(ctx, req, s.meta, s.broker, s.targetObserver, s.targetMgr)
s.jobScheduler.Add(syncJob)
go func() {
defer func() {
syncJob.PostExecute()
syncJob.Done()
}()
err := syncJob.PreExecute()
if err != nil {
log.Warn(failedMsg, zap.Error(err))
syncJob.SetError(err)
return
}
err = syncJob.Execute()
if err != nil {
log.Warn(failedMsg, zap.Error(err))
syncJob.SetError(err)
return
}
}()
err := syncJob.Wait()
if err != nil {
log.Warn(failedMsg, zap.Error(err))
+1 -1
View File
@@ -629,7 +629,7 @@ func (t *createCollectionTask) validateIfCollectionExists(ctx context.Context) e
}
// Check if the collection already exists.
existedCollInfo, err := t.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp)
existedCollInfo, err := t.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err == nil {
newCollInfo := newCollectionModel(t.header, t.body, 0)
if equal := existedCollInfo.Equal(*newCollInfo); !equal {
@@ -1649,7 +1649,7 @@ func Test_createCollectionTask_Prepare(t *testing.T) {
}, nil)
meta.EXPECT().GetGeneralCount(mock.Anything).Return(0)
meta.EXPECT().DescribeAlias(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
paramtable.Get().Save(Params.QuotaConfig.MaxCollectionNum.Key, strconv.Itoa(math.MaxInt64))
defer paramtable.Get().Reset(Params.QuotaConfig.MaxCollectionNum.Key)
@@ -1742,7 +1742,7 @@ func TestCreateCollectionTask_Prepare_WithProperty(t *testing.T) {
}).Once()
meta.EXPECT().GetGeneralCount(mock.Anything).Return(0).Once()
meta.EXPECT().DescribeAlias(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
defer cleanTestEnv()
collectionName := funcutil.GenRandomStr()
@@ -1805,7 +1805,7 @@ func Test_createCollectionTask_PartitionKey(t *testing.T) {
}, nil)
meta.EXPECT().GetGeneralCount(mock.Anything).Return(0)
meta.EXPECT().DescribeAlias(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
paramtable.Get().Save(Params.QuotaConfig.MaxCollectionNum.Key, strconv.Itoa(math.MaxInt64))
defer paramtable.Get().Reset(Params.QuotaConfig.MaxCollectionNum.Key)
+1 -1
View File
@@ -175,7 +175,7 @@ func (*Core) startBroadcastWithCollectionLock(ctx context.Context, dbName string
// Some API like AlterCollection can be called with alias or collection name,
// so we need to get the real collection name to add resource key lock.
func (c *Core) startBroadcastWithAliasOrCollectionLock(ctx context.Context, dbName string, collectionNameOrAlias string) (broadcaster.BroadcastAPI, error) {
coll, err := c.meta.GetCollectionByName(ctx, dbName, collectionNameOrAlias, typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, dbName, collectionNameOrAlias, typeutil.MaxTimestamp, true)
if err != nil {
return nil, errors.Wrap(err, "failed to get collection by name")
}
@@ -88,7 +88,7 @@ func TestDDLCallbacksAliasDDL(t *testing.T) {
})
require.NoError(t, merr.CheckRPCCall(status, err))
coll, err := core.meta.GetCollectionByName(context.Background(), "test", "test_alias", typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(context.Background(), "test", "test_alias", typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.NotZero(t, coll.CollectionID)
require.Equal(t, "test_collection", coll.Name)
@@ -46,7 +46,7 @@ func (c *Core) broadcastCreateAlias(ctx context.Context, req *milvuspb.CreateAli
if err != nil {
return err
}
collection, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
collection, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -84,7 +84,7 @@ func (c *Core) broadcastAlterAlias(ctx context.Context, req *milvuspb.AlterAlias
if err != nil {
return err
}
collection, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
collection, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -28,7 +28,7 @@ func (c *Core) broadcastAlterCollectionForAddField(ctx context.Context, req *mil
defer broadcaster.Close()
// check if the collection is created.
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -115,7 +115,7 @@ func getFieldSchema(fieldName string) []byte {
}
func assertFieldExists(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, fieldName string, fieldID int64) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
for _, field := range coll.Fields {
if field.Name == fieldName {
@@ -21,7 +21,7 @@ func (c *Core) broadcastAlterCollectionV2ForAlterCollectionField(ctx context.Con
}
defer broadcaster.Close()
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -126,7 +126,7 @@ func TestDDLCallbacksAlterCollectionField(t *testing.T) {
}
func assertFieldPropertiesNotFound(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, fieldName string, key string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
for _, field := range coll.Fields {
if field.Name == fieldName {
@@ -140,7 +140,7 @@ func assertFieldPropertiesNotFound(t *testing.T, ctx context.Context, core *Core
}
func assertFieldProperties(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, fieldName string, key string, val string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
for _, field := range coll.Fields {
if field.Name == fieldName {
@@ -57,7 +57,7 @@ func (c *Core) broadcastAlterCollectionForRenameCollection(ctx context.Context,
if err != nil {
return err
}
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetOldName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetOldName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -80,10 +80,10 @@ func TestDDLCallbacksAlterCollectionName(t *testing.T) {
NewName: newCollectionName,
})
require.NoError(t, merr.CheckRPCCall(resp, err))
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.ErrorIs(t, err, merr.ErrCollectionNotFound)
require.Nil(t, coll)
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.NotNil(t, coll)
require.Equal(t, coll.Name, newCollectionName)
@@ -100,10 +100,10 @@ func TestDDLCallbacksAlterCollectionName(t *testing.T) {
NewName: newCollectionName,
})
require.NoError(t, merr.CheckRPCCall(resp, err))
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp, false)
require.ErrorIs(t, err, merr.ErrCollectionNotFound)
require.Nil(t, coll)
coll, err = core.meta.GetCollectionByName(ctx, newDbName, newCollectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, newDbName, newCollectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.NotNil(t, coll)
require.Equal(t, coll.Name, newCollectionName)
@@ -117,16 +117,16 @@ func TestDDLCallbacksAlterCollectionName(t *testing.T) {
NewName: collectionName,
})
require.NoError(t, merr.CheckRPCCall(resp, err))
coll, err = core.meta.GetCollectionByName(ctx, newDbName, newCollectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, newDbName, newCollectionName, typeutil.MaxTimestamp, false)
require.ErrorIs(t, err, merr.ErrCollectionNotFound)
require.Nil(t, coll)
coll, err = core.meta.GetCollectionByName(ctx, newDbName, collectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, newDbName, collectionName, typeutil.MaxTimestamp, false)
require.ErrorIs(t, err, merr.ErrCollectionNotFound)
require.Nil(t, coll)
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, newCollectionName, typeutil.MaxTimestamp, false)
require.ErrorIs(t, err, merr.ErrCollectionNotFound)
require.Nil(t, coll)
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.NotNil(t, coll)
require.Equal(t, coll.Name, collectionName)
@@ -71,7 +71,7 @@ func (c *Core) broadcastAlterCollectionForAlterCollection(ctx context.Context, r
defer broadcaster.Close()
// check if the collection exists
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -210,7 +210,7 @@ func (c *Core) broadcastAlterCollectionForAlterDynamicField(ctx context.Context,
}
defer broadcaster.Close()
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -279,7 +279,7 @@ func (c *Core) broadcastAlterCollectionForAlterDynamicField(ctx context.Context,
// getCacheExpireForCollection gets the cache expirations for collection.
func (c *Core) getCacheExpireForCollection(ctx context.Context, dbName string, collectionNameOrAlias string) (*message.CacheExpirations, error) {
coll, err := c.meta.GetCollectionByName(ctx, dbName, collectionNameOrAlias, typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, dbName, collectionNameOrAlias, typeutil.MaxTimestamp, false)
if err != nil {
return nil, err
}
@@ -552,13 +552,13 @@ func TestDDLCallbacksAlterCollectionProperties_TTLFieldPreservesExternalSpec(t *
}
func assertExternalSource(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, expectedSource string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, expectedSource, coll.ExternalSource)
}
func assertExternalSpec(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, expectedSpec string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, expectedSpec, coll.ExternalSpec)
}
@@ -745,7 +745,7 @@ func createCollectionAndAliasForTest(t *testing.T, ctx context.Context, core *Co
}
func assertReplicaNumber(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, replicaNumber int64) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
replicaNum, err := common.CollectionLevelReplicaNumber(coll.Properties)
if replicaNumber == 0 {
@@ -757,7 +757,7 @@ func assertReplicaNumber(t *testing.T, ctx context.Context, core *Core, dbName s
}
func assertResourceGroups(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, resourceGroups []string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
rgs, err := common.CollectionLevelResourceGroups(coll.Properties)
if len(resourceGroups) == 0 {
@@ -769,25 +769,25 @@ func assertResourceGroups(t *testing.T, ctx context.Context, core *Core, dbName
}
func assertConsistencyLevel(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, consistencyLevel commonpb.ConsistencyLevel) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, consistencyLevel, coll.ConsistencyLevel)
}
func assertDescription(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, description string) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, description, coll.Description)
}
func assertSchemaVersion(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, schemaVersion int32) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, schemaVersion, coll.SchemaVersion)
}
func assertDynamicSchema(t *testing.T, ctx context.Context, core *Core, dbName string, collectionName string, dynamicSchema bool) {
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, dynamicSchema, coll.EnableDynamicField)
if !dynamicSchema {
@@ -38,7 +38,7 @@ func (c *Core) broadcastAlterCollectionSchema(ctx context.Context, req *milvuspb
return err
}
defer broadcaster.Close()
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -145,7 +145,7 @@ func (c *Core) broadcastAlterCollectionForAlterFunction(ctx context.Context, req
}
defer broadcaster.Close()
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -165,7 +165,7 @@ func (c *Core) broadcastAlterCollectionForDropFunction(ctx context.Context, req
}
defer broadcaster.Close()
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -212,7 +212,7 @@ func (c *Core) broadcastAlterCollectionForAddFunction(ctx context.Context, req *
}
defer broadcaster.Close()
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
oldColl, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -142,7 +142,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestCallAlterCollection_Su
core.meta = mockMeta
// Mock meta calls for getCacheExpireForCollection
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
mockMeta.EXPECT().ListAliases(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp).Return([]string{}, nil)
// Mock broadcaster
@@ -167,7 +167,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestCallAlterCollection_Ge
core.meta = mockMeta
// Mock meta calls to return error
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp).Return(nil, errors.New("cache expire error"))
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp, mock.Anything).Return(nil, errors.New("cache expire error"))
err := callAlterCollection(ctx, core, suite.mockBroadcaster, coll, dbName, collectionName)
suite.Error(err)
@@ -186,7 +186,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestCallAlterCollection_Br
core.meta = mockMeta
// Mock meta calls for getCacheExpireForCollection
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
mockMeta.EXPECT().ListAliases(mock.Anything, dbName, collectionName, typeutil.MaxTimestamp).Return([]string{}, nil)
// Mock broadcaster to return error
@@ -460,7 +460,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestBroadcastAlterCollecti
coll := suite.createTestCollection()
mockMeta := mockrootcoord.NewIMetaTable(suite.T())
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
suite.core.meta = mockMeta
req := &milvuspb.AlterCollectionFunctionRequest{
@@ -486,7 +486,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestBroadcastAlterCollecti
coll := suite.createTestCollection()
mockMeta := mockrootcoord.NewIMetaTable(suite.T())
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
suite.core.meta = mockMeta
req := &milvuspb.DropCollectionFunctionRequest{
@@ -522,7 +522,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestBroadcastAlterCollecti
coll := suite.createTestCollection()
mockMeta := mockrootcoord.NewIMetaTable(suite.T())
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
suite.core.meta = mockMeta
req := &milvuspb.AddCollectionFunctionRequest{
@@ -546,7 +546,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestBroadcastAlterCollecti
suite.Run("function input field not exists", func() {
coll := suite.createTestCollection()
mockMeta := mockrootcoord.NewIMetaTable(suite.T())
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
suite.core.meta = mockMeta
req := &milvuspb.AddCollectionFunctionRequest{
@@ -572,7 +572,7 @@ func (suite *DDLCallbacksCollectionFunctionTestSuite) TestBroadcastAlterCollecti
coll := suite.createTestCollection()
mockMeta := mockrootcoord.NewIMetaTable(suite.T())
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp).Return(coll, nil)
mockMeta.EXPECT().GetCollectionByName(mock.Anything, "test_db", "test_collection", typeutil.MaxTimestamp, mock.Anything).Return(coll, nil)
suite.core.meta = mockMeta
req := &milvuspb.AddCollectionFunctionRequest{
@@ -87,7 +87,7 @@ func TestDDLCallbacksCollectionDDL(t *testing.T) {
Schema: schemaBytes,
})
require.NoError(t, merr.CheckRPCCall(status, err))
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err := core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, coll.Name, collectionName)
// create a collection with same schema should be idempotent.
@@ -105,7 +105,7 @@ func TestDDLCallbacksCollectionDDL(t *testing.T) {
PartitionName: partitionName,
})
require.NoError(t, merr.CheckRPCCall(status, err))
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Len(t, coll.Partitions, 2)
require.Contains(t, lo.Map(coll.Partitions, func(p *model.Partition, _ int) string { return p.PartitionName }), partitionName)
@@ -116,7 +116,7 @@ func TestDDLCallbacksCollectionDDL(t *testing.T) {
PartitionName: partitionName,
})
require.NoError(t, merr.CheckRPCCall(status, err))
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Len(t, coll.Partitions, 2)
@@ -148,7 +148,7 @@ func TestDDLCallbacksCollectionDDL(t *testing.T) {
})
require.NoError(t, merr.CheckRPCCall(resp.GetStatus(), err))
// verify collection still exists after truncate
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
coll, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.NoError(t, err)
require.Equal(t, coll.Name, collectionName)
require.Equal(t, 1, len(coll.ShardInfos))
@@ -163,7 +163,7 @@ func TestDDLCallbacksCollectionDDL(t *testing.T) {
CollectionName: collectionName,
})
require.NoError(t, merr.CheckRPCCall(status, err))
_, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp)
_, err = core.meta.GetCollectionByName(ctx, dbName, collectionName, typeutil.MaxTimestamp, false)
require.Error(t, err)
// drop a dropped collection should be idempotent.
status, err = core.DropCollection(ctx, &milvuspb.DropCollectionRequest{
@@ -33,35 +33,33 @@ import (
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
func (c *Core) broadcastCreatePartition(ctx context.Context, in *milvuspb.CreatePartitionRequest) error {
func (c *Core) broadcastCreatePartition(ctx context.Context, in *milvuspb.CreatePartitionRequest) (int64, error) {
broadcaster, err := c.startBroadcastWithAliasOrCollectionLock(ctx, in.GetDbName(), in.GetCollectionName())
if err != nil {
return err
return 0, err
}
defer broadcaster.Close()
collMeta, err := c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), typeutil.MaxTimestamp)
collMeta, err := c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), typeutil.MaxTimestamp, true)
if err != nil {
return err
return 0, err
}
if err := checkGeneralCapacity(ctx, 0, 1, 0, c); err != nil {
return err
return 0, err
}
// idempotency check here.
for _, partition := range collMeta.Partitions {
if partition.PartitionName == in.GetPartitionName() {
return errIgnoerdCreatePartition
}
// idempotency check using partition name index (O(1) instead of O(n))
if partitionID, exists := c.meta.GetPartitionIDByName(collMeta.CollectionID, in.GetPartitionName()); exists {
return partitionID, errIgnoerdCreatePartition
}
cfgMaxPartitionNum := Params.RootCoordCfg.MaxPartitionNum.GetAsInt()
if len(collMeta.Partitions) >= cfgMaxPartitionNum {
return fmt.Errorf("partition number (%d) exceeds max configuration (%d), collection: %s",
return 0, fmt.Errorf("partition number (%d) exceeds max configuration (%d), collection: %s",
len(collMeta.Partitions), cfgMaxPartitionNum, collMeta.Name)
}
partID, err := c.idAllocator.AllocOne()
if err != nil {
return errors.Wrap(err, "failed to allocate partition ID")
return 0, errors.Wrap(err, "failed to allocate partition ID")
}
channels := make([]string, 0, collMeta.ShardsNum+1)
@@ -86,7 +84,7 @@ func (c *Core) broadcastCreatePartition(ctx context.Context, in *milvuspb.Create
WithBroadcast(channels).
MustBuildBroadcast()
_, err = broadcaster.Broadcast(ctx, msg)
return err
return partID, err
}
func (c *DDLCallback) createPartitionV1AckCallback(ctx context.Context, result message.BroadcastResultCreatePartitionMessageV1) error {
@@ -43,7 +43,7 @@ func (c *Core) broadcastDropPartition(ctx context.Context, in *milvuspb.DropPart
}
defer broadcaster.Close()
collMeta, err := c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), typeutil.MaxTimestamp)
collMeta, err := c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
// Is this idempotent?
return err
@@ -38,7 +38,7 @@ func (c *Core) broadcastTruncateCollection(ctx context.Context, req *milvuspb.Tr
defer broadcaster.Close()
// get collection info
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, req.GetDbName(), req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
return err
}
@@ -157,7 +157,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -185,7 +185,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -213,7 +213,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -244,7 +244,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -277,7 +277,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -298,7 +298,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -332,7 +332,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
},
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -361,7 +361,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
meta := mockrootcoord.NewIMetaTable(t)
core := newTestCore(withMeta(meta))
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -417,7 +417,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
},
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -470,7 +470,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
ObjectName: util.AnyWord,
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -518,7 +518,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
ObjectName: util.AnyWord,
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -572,7 +572,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
ObjectName: "b",
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
@@ -630,7 +630,7 @@ func TestDescribeCollectionsAuth(t *testing.T) {
GroupName: "privilege_group",
},
}, nil).Once()
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
Name: "test coll",
DBID: 1,
+1 -1
View File
@@ -49,7 +49,7 @@ func (t *dropCollectionTask) validate(ctx context.Context) error {
// we cannot handle case that
// dropping collection with `ts1` but a collection exists in catalog with newer ts which is bigger than `ts1`.
// fortunately, if ddls are promised to execute in sequence, then everything is OK. The `ts1` will always be latest.
collMeta, err := t.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp)
collMeta, err := t.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp, false)
if err != nil {
if errors.Is(err, merr.ErrCollectionNotFound) || errors.Is(err, merr.ErrDatabaseNotFound) {
return errIgnoredDropCollection
@@ -59,7 +59,7 @@ func Test_dropCollectionTask_Prepare(t *testing.T) {
collectionName := funcutil.GenRandomStr()
meta := mockrootcoord.NewIMetaTable(t)
meta.EXPECT().IsAlias(mock.Anything, mock.Anything, mock.Anything).Return(false)
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, merr.ErrCollectionNotFound)
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, merr.ErrCollectionNotFound)
core := newTestCore(withMeta(meta))
task := &dropCollectionTask{
Core: core,
@@ -75,7 +75,7 @@ func Test_dropCollectionTask_Prepare(t *testing.T) {
collectionName := funcutil.GenRandomStr()
meta := mockrootcoord.NewIMetaTable(t)
meta.EXPECT().IsAlias(mock.Anything, mock.Anything, mock.Anything).Return(false)
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
DBID: 1,
State: pb.CollectionState_CollectionCreated,
@@ -102,7 +102,7 @@ func Test_dropCollectionTask_Prepare(t *testing.T) {
mock.Anything,
mock.Anything,
).Return(false)
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
meta.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&model.Collection{
CollectionID: 1,
DBName: "db1",
DBID: 1,
+1 -1
View File
@@ -43,7 +43,7 @@ func (t *hasCollectionTask) Execute(ctx context.Context) error {
t.Rsp.Status = merr.Success()
ts := getTravelTs(t.Req)
// TODO: what if err != nil && common.IsCollectionNotExistError == false, should we consider this RPC as failure?
_, err := t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), ts)
_, err := t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), ts, false)
t.Rsp.Value = err == nil
return nil
}
@@ -80,6 +80,7 @@ func Test_hasCollectionTask_Execute(t *testing.T) {
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
).Return(nil, nil)
core := newTestCore(withMeta(meta))
+1 -1
View File
@@ -44,7 +44,7 @@ func (t *hasPartitionTask) Execute(ctx context.Context) error {
t.Rsp.Status = merr.Success()
t.Rsp.Value = false
// TODO: why HasPartitionRequest doesn't contain Timestamp but other requests do.
coll, err := t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.CollectionName, typeutil.MaxTimestamp)
coll, err := t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.CollectionName, typeutil.MaxTimestamp, false)
if err != nil {
t.Rsp.Status = merr.Status(err)
return err
@@ -59,7 +59,7 @@ func Test_hasPartitionTask_Prepare(t *testing.T) {
func Test_hasPartitionTask_Execute(t *testing.T) {
t.Run("fail to get collection", func(t *testing.T) {
metaTable := mockrootcoord.NewIMetaTable(t)
metaTable.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, "test coll", mock.Anything).Return(nil, merr.WrapErrCollectionNotFound("test coll"))
metaTable.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, "test coll", mock.Anything, mock.Anything).Return(nil, merr.WrapErrCollectionNotFound("test coll"))
core := newTestCore(withMeta(metaTable))
task := &hasPartitionTask{
baseTask: newBaseTask(context.Background(), core),
@@ -85,6 +85,7 @@ func Test_hasPartitionTask_Execute(t *testing.T) {
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
).Return(&model.Collection{
Partitions: []*model.Partition{
{
@@ -118,6 +119,7 @@ func Test_hasPartitionTask_Execute(t *testing.T) {
mock.Anything,
mock.Anything,
mock.Anything,
mock.Anything,
).Return(&model.Collection{
Partitions: []*model.Partition{
{
+82 -13
View File
@@ -90,7 +90,7 @@ type IMetaTable interface {
// If the collection does not exist, it will return InvalidCollectionID.
// Please use the function with caution.
GetCollectionID(ctx context.Context, dbName string, collectionName string) UniqueID
GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp) (*model.Collection, error)
GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error)
GetCollectionByID(ctx context.Context, dbName string, collectionID UniqueID, ts Timestamp, allowUnavailable bool) (*model.Collection, error)
GetCollectionByIDWithMaxTs(ctx context.Context, collectionID UniqueID) (*model.Collection, error)
ListCollections(ctx context.Context, dbName string, ts Timestamp, onlyAvail bool) ([]*model.Collection, error)
@@ -102,6 +102,7 @@ type IMetaTable interface {
GetCollectionVirtualChannels(ctx context.Context, colID int64) []string
GetPChannelInfo(ctx context.Context, pchannel string) *rootcoordpb.GetPChannelInfoResponse
AddPartition(ctx context.Context, partition *model.Partition) error
GetPartitionIDByName(collectionID int64, partitionName string) (int64, bool)
DropPartition(ctx context.Context, collectionID UniqueID, partitionID UniqueID, ts Timestamp) error
RemovePartition(ctx context.Context, collectionID UniqueID, partitionID UniqueID, ts Timestamp) error
@@ -167,6 +168,9 @@ type MetaTable struct {
dbName2Meta map[string]*model.Database // database name -> db meta
collID2Meta map[typeutil.UniqueID]*model.Collection // collection id -> collection meta
// partition name index: collectionID -> partitionName -> partitionID
partitionName2ID map[int64]map[string]int64
fileResourceName2Meta map[string]*internalpb.FileResourceInfo // file resource name -> file resource meta
fileResourceID2Meta map[int64]*internalpb.FileResourceInfo // file resource id -> file resource meta
fileResourceRefCnt map[int64]int // file resource id -> reference count
@@ -202,6 +206,7 @@ func (mt *MetaTable) reload() error {
record := timerecord.NewTimeRecorder("rootcoord")
mt.dbName2Meta = make(map[string]*model.Database)
mt.collID2Meta = make(map[UniqueID]*model.Collection)
mt.partitionName2ID = make(map[int64]map[string]int64)
mt.fileResourceRefCnt = make(map[int64]int)
mt.names = newNameDb()
mt.aliases = newNameDb()
@@ -262,6 +267,13 @@ func (mt *MetaTable) reload() error {
}
collection.DBName = dbName // some collections may not have db name or its dbname is not correct, we should fix it here.
mt.collID2Meta[collection.CollectionID] = collection
// Build partition name index
mt.partitionName2ID[collection.CollectionID] = make(map[string]int64)
for _, partition := range collection.Partitions {
if partition.Available() {
mt.partitionName2ID[collection.CollectionID][partition.PartitionName] = partition.PartitionID
}
}
if collection.Available() {
mt.names.insert(dbName, collection.Name, collection.CollectionID)
for _, fileResourceID := range collection.FileResourceIds {
@@ -560,6 +572,13 @@ func (mt *MetaTable) AddCollection(ctx context.Context, coll *model.Collection)
mt.collID2Meta[coll.CollectionID] = coll.Clone()
mt.names.insert(coll.DBName, coll.Name, coll.CollectionID)
// Build partition name index for the new collection
mt.partitionName2ID[coll.CollectionID] = make(map[string]int64)
for _, partition := range coll.Partitions {
if partition.Available() {
mt.partitionName2ID[coll.CollectionID][partition.PartitionName] = partition.PartitionID
}
}
pn := coll.GetPartitionNum(true)
mt.generalCnt += pn * int(coll.ShardsNum)
@@ -677,6 +696,7 @@ func (mt *MetaTable) removeAllNamesIfMatchedInternal(ctx context.Context, collec
func (mt *MetaTable) removeCollectionByIDInternal(ctx context.Context, collectionID UniqueID) {
delete(mt.collID2Meta, collectionID)
delete(mt.partitionName2ID, collectionID)
log.Ctx(ctx).Info("delete from collID2Meta",
zap.Int64("collectionID", collectionID),
)
@@ -756,7 +776,7 @@ func (mt *MetaTable) getLatestCollectionByIDInternal(ctx context.Context, collec
return nil, merr.WrapErrCollectionNotFound(collectionID)
}
if allowUnavailable {
return coll.Clone(), nil
return coll.ShallowClone(), nil
}
if !coll.Available() {
return nil, merr.WrapErrCollectionNotFound(collectionID)
@@ -801,7 +821,7 @@ func (mt *MetaTable) getCollectionByIDInternal(ctx context.Context, dbName strin
}
if allowUnavailable {
return coll.Clone(), nil
return coll.ShallowClone(), nil
}
if !coll.Available() {
@@ -811,10 +831,10 @@ func (mt *MetaTable) getCollectionByIDInternal(ctx context.Context, dbName strin
return filterUnavailablePartition(coll), nil
}
func (mt *MetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp) (*model.Collection, error) {
func (mt *MetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
mt.ddLock.RLock()
defer mt.ddLock.RUnlock()
return mt.getCollectionByNameInternal(ctx, dbName, collectionName, ts)
return mt.getCollectionByNameInternal(ctx, dbName, collectionName, ts, allowUnavailable)
}
// GetCollectionID retrieves the corresponding collectionID based on the collectionName.
@@ -849,7 +869,7 @@ func (mt *MetaTable) GetCollectionID(ctx context.Context, dbName string, collect
// Note: The returned model.Collection is read-only. Do NOT modify it directly,
// as it may cause unexpected behavior or inconsistencies.
func (mt *MetaTable) getCollectionByNameInternal(ctx context.Context, dbName string, collectionName string, ts Timestamp) (*model.Collection, error) {
func (mt *MetaTable) getCollectionByNameInternal(ctx context.Context, dbName string, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
// backward compatibility for rolling upgrade
if dbName == "" {
log.Ctx(ctx).Warn("db name is empty", zap.String("collectionName", collectionName), zap.Uint64("ts", ts))
@@ -863,12 +883,12 @@ func (mt *MetaTable) getCollectionByNameInternal(ctx context.Context, dbName str
collectionID, ok := mt.aliases.get(dbName, collectionName)
if ok {
return mt.getCollectionByIDInternal(ctx, dbName, collectionID, ts, false)
return mt.getCollectionByIDInternal(ctx, dbName, collectionID, ts, allowUnavailable)
}
collectionID, ok = mt.names.get(dbName, collectionName)
if ok {
return mt.getCollectionByIDInternal(ctx, dbName, collectionID, ts, false)
return mt.getCollectionByIDInternal(ctx, dbName, collectionID, ts, allowUnavailable)
}
if isMaxTs(ts) {
@@ -885,6 +905,9 @@ func (mt *MetaTable) getCollectionByNameInternal(ctx context.Context, dbName str
if coll == nil || !coll.Available() {
return nil, merr.WrapErrCollectionNotFoundWithDB(dbName, collectionName)
}
if allowUnavailable {
return coll.ShallowClone(), nil
}
return filterUnavailablePartition(coll), nil
}
@@ -1179,7 +1202,7 @@ func (mt *MetaTable) CheckIfCollectionRenamable(ctx context.Context, dbName stri
}
// check new collection already exists
coll, err := mt.getCollectionByNameInternal(ctx, newDBName, newName, typeutil.MaxTimestamp)
coll, err := mt.getCollectionByNameInternal(ctx, newDBName, newName, typeutil.MaxTimestamp, false)
if coll != nil {
log.Warn("duplicated new collection name, already taken by another collection or alias.")
return fmt.Errorf("duplicated new collection name %s:%s with other collection name or alias", newDBName, newName)
@@ -1190,7 +1213,7 @@ func (mt *MetaTable) CheckIfCollectionRenamable(ctx context.Context, dbName stri
}
// get old collection meta
oldColl, err := mt.getCollectionByNameInternal(ctx, dbName, oldName, typeutil.MaxTimestamp)
oldColl, err := mt.getCollectionByNameInternal(ctx, dbName, oldName, typeutil.MaxTimestamp, false)
if err != nil {
log.Warn("fail to find collection with old name", zap.Error(err))
return err
@@ -1272,7 +1295,18 @@ func (mt *MetaTable) AddPartition(ctx context.Context, partition *model.Partitio
if err := mt.catalog.CreatePartition(ctx, coll.DBID, partition, partition.PartitionCreatedTimestamp); err != nil {
return err
}
mt.collID2Meta[partition.CollectionID].Partitions = append(mt.collID2Meta[partition.CollectionID].Partitions, partition.Clone())
// COW: create new slice to avoid data race with concurrent readers
oldPartitions := coll.Partitions
newPartitions := make([]*model.Partition, len(oldPartitions)+1)
copy(newPartitions, oldPartitions)
newPartitions[len(oldPartitions)] = partition.Clone()
coll.Partitions = newPartitions
// Update partition name index
if mt.partitionName2ID[partition.CollectionID] == nil {
mt.partitionName2ID[partition.CollectionID] = make(map[string]int64)
}
mt.partitionName2ID[partition.CollectionID][partition.PartitionName] = partition.PartitionID
log.Ctx(ctx).Info("add partition to meta table",
zap.Int64("collection", partition.CollectionID), zap.String("partition", partition.PartitionName),
@@ -1284,6 +1318,20 @@ func (mt *MetaTable) AddPartition(ctx context.Context, partition *model.Partitio
return nil
}
// GetPartitionIDByName returns partition ID by collection ID and partition name.
// Returns (partitionID, true) if found, (0, false) if not found.
func (mt *MetaTable) GetPartitionIDByName(collectionID int64, partitionName string) (int64, bool) {
mt.ddLock.RLock()
defer mt.ddLock.RUnlock()
if partitions, ok := mt.partitionName2ID[collectionID]; ok {
if partitionID, exists := partitions[partitionName]; exists {
return partitionID, true
}
}
return 0, false
}
func (mt *MetaTable) DropPartition(ctx context.Context, collectionID UniqueID, partitionID UniqueID, ts Timestamp) error {
mt.ddLock.Lock()
defer mt.ddLock.Unlock()
@@ -1304,7 +1352,17 @@ func (mt *MetaTable) DropPartition(ctx context.Context, collectionID UniqueID, p
if err := mt.catalog.AlterPartition(ctx1, coll.DBID, part, clone, metastore.MODIFY, ts); err != nil {
return err
}
mt.collID2Meta[collectionID].Partitions[idx] = clone
// Copy-on-write so snapshots returned by ShallowClone never observe
// in-place updates through a shared partitions backing array.
newPartitions := make([]*model.Partition, len(coll.Partitions))
copy(newPartitions, coll.Partitions)
newPartitions[idx] = clone
mt.collID2Meta[collectionID].Partitions = newPartitions
// Remove from partition name index when dropping
if mt.partitionName2ID[collectionID] != nil {
delete(mt.partitionName2ID[collectionID], part.PartitionName)
}
log.Ctx(ctx).Info("drop partition", zap.Int64("collection", collectionID),
zap.Int64("partition", partitionID),
@@ -1348,7 +1406,18 @@ func (mt *MetaTable) RemovePartition(ctx context.Context, collectionID UniqueID,
if err := mt.catalog.DropPartition(ctx1, coll.DBID, collectionID, partitionID, ts); err != nil {
return err
}
coll.Partitions = append(coll.Partitions[:loc], coll.Partitions[loc+1:]...)
// COW: create new slice to avoid data race with concurrent readers
oldPartitions := coll.Partitions
newPartitions := make([]*model.Partition, len(oldPartitions)-1)
copy(newPartitions[:loc], oldPartitions[:loc])
copy(newPartitions[loc:], oldPartitions[loc+1:])
coll.Partitions = newPartitions
// Remove from partition name index
if mt.partitionName2ID[collectionID] != nil {
delete(mt.partitionName2ID[collectionID], partition.PartitionName)
}
log.Ctx(ctx).Info("remove partition", zap.Int64("collection", collectionID), zap.Int64("partition", partitionID), zap.Uint64("ts", ts))
return nil
}
+66 -8
View File
@@ -29,6 +29,7 @@ import (
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
etcdkv "github.com/milvus-io/milvus/internal/kv/etcd"
"github.com/milvus-io/milvus/internal/metastore"
"github.com/milvus-io/milvus/internal/metastore/kv/rootcoord"
"github.com/milvus-io/milvus/internal/metastore/mocks"
"github.com/milvus-io/milvus/internal/metastore/model"
@@ -712,7 +713,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
},
}
ctx := context.Background()
_, err := meta.GetCollectionByName(ctx, "not_exist", "name", 101)
_, err := meta.GetCollectionByName(ctx, "not_exist", "name", 101, false)
assert.Error(t, err)
})
t.Run("get by alias", func(t *testing.T) {
@@ -734,7 +735,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
}
meta.aliases.insert(util.DefaultDBName, "alias", 100)
ctx := context.Background()
coll, err := meta.GetCollectionByName(ctx, "", "alias", 101)
coll, err := meta.GetCollectionByName(ctx, "", "alias", 101, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(coll.Partitions))
assert.Equal(t, UniqueID(11), coll.Partitions[0].PartitionID)
@@ -761,7 +762,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
}
meta.names.insert(util.DefaultDBName, "name", 100)
ctx := context.Background()
coll, err := meta.GetCollectionByName(ctx, "", "name", 101)
coll, err := meta.GetCollectionByName(ctx, "", "name", 101, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(coll.Partitions))
assert.Equal(t, UniqueID(11), coll.Partitions[0].PartitionID)
@@ -786,7 +787,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
catalog: catalog,
}
ctx := context.Background()
_, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101)
_, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101, false)
assert.Error(t, err)
})
@@ -808,7 +809,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
catalog: catalog,
}
ctx := context.Background()
_, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101)
_, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101, false)
assert.Error(t, err)
assert.ErrorIs(t, err, merr.ErrCollectionNotFound)
})
@@ -839,7 +840,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
catalog: catalog,
}
ctx := context.Background()
coll, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101)
coll, err := meta.GetCollectionByName(ctx, util.DefaultDBName, "name", 101, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(coll.Partitions))
assert.Equal(t, UniqueID(11), coll.Partitions[0].PartitionID)
@@ -855,7 +856,7 @@ func TestMetaTable_GetCollectionByName(t *testing.T) {
names: newNameDb(),
aliases: newNameDb(),
}
_, err := meta.GetCollectionByName(ctx, "", "not_exist", typeutil.MaxTimestamp)
_, err := meta.GetCollectionByName(ctx, "", "not_exist", typeutil.MaxTimestamp, false)
assert.Error(t, err)
assert.ErrorIs(t, err, merr.ErrCollectionNotFound)
})
@@ -1380,6 +1381,62 @@ func TestMetaTable_DropCollection_GrantCleanup(t *testing.T) {
})
}
func TestMetaTable_DropPartition_CopyOnWrite(t *testing.T) {
catalog := mocks.NewRootCoordCatalog(t)
originalPart := &model.Partition{
PartitionID: 100,
PartitionName: "p1",
CollectionID: 100,
State: pb.PartitionState_PartitionCreated,
}
catalog.On("AlterPartition",
mock.Anything,
int64(10),
originalPart,
mock.MatchedBy(func(newPart *model.Partition) bool {
return newPart != nil &&
newPart != originalPart &&
newPart.PartitionID == originalPart.PartitionID &&
newPart.PartitionName == originalPart.PartitionName &&
newPart.CollectionID == originalPart.CollectionID &&
newPart.State == pb.PartitionState_PartitionDropping
}),
metastore.MODIFY,
uint64(9999),
).Return(nil).Once()
meta := &MetaTable{
catalog: catalog,
collID2Meta: map[typeutil.UniqueID]*model.Collection{
100: {
CollectionID: 100,
DBID: 10,
State: pb.CollectionState_CollectionCreated,
Partitions: []*model.Partition{originalPart},
},
},
partitionName2ID: map[int64]map[string]int64{
100: {"p1": 100},
},
}
snapshot, err := meta.GetCollectionByID(context.Background(), "", 100, typeutil.MaxTimestamp, true)
require.NoError(t, err)
require.Same(t, originalPart, snapshot.Partitions[0])
err = meta.DropPartition(context.Background(), 100, 100, 9999)
require.NoError(t, err)
require.Same(t, originalPart, snapshot.Partitions[0])
assert.Equal(t, pb.PartitionState_PartitionCreated, snapshot.Partitions[0].State)
require.Len(t, meta.collID2Meta[100].Partitions, 1)
assert.NotSame(t, originalPart, meta.collID2Meta[100].Partitions[0])
assert.Equal(t, pb.PartitionState_PartitionDropping, meta.collID2Meta[100].Partitions[0].State)
assert.Equal(t, pb.PartitionState_PartitionCreated, originalPart.State)
assert.NotContains(t, meta.partitionName2ID[100], "p1")
}
func TestMetaTable_RemovePartition(t *testing.T) {
t.Run("catalog error", func(t *testing.T) {
catalog := mocks.NewRootCoordCatalog(t)
@@ -1765,6 +1822,7 @@ func TestMetaTable_AddPartition(t *testing.T) {
collID2Meta: map[typeutil.UniqueID]*model.Collection{
100: {Name: "test", CollectionID: 100},
},
partitionName2ID: make(map[int64]map[string]int64),
}
err := meta.AddPartition(context.TODO(), &model.Partition{CollectionID: 100, State: pb.PartitionState_PartitionCreated})
assert.NoError(t, err)
@@ -2336,7 +2394,7 @@ func TestMetaTable_EmtpyDatabaseName(t *testing.T) {
}
mt.aliases.insert(util.DefaultDBName, "aliases", 1)
ret, err := mt.getCollectionByNameInternal(context.TODO(), "", "aliases", typeutil.MaxTimestamp)
ret, err := mt.getCollectionByNameInternal(context.TODO(), "", "aliases", typeutil.MaxTimestamp, false)
assert.NoError(t, err)
assert.Equal(t, int64(1), ret.CollectionID)
})
+4 -4
View File
@@ -67,7 +67,7 @@ type mockMetaTable struct {
ListDatabasesFunc func(ctx context.Context, ts Timestamp) ([]*model.Database, error)
ListCollectionsFunc func(ctx context.Context, ts Timestamp) ([]*model.Collection, error)
AddCollectionFunc func(ctx context.Context, coll *model.Collection) error
GetCollectionByNameFunc func(ctx context.Context, collectionName string, ts Timestamp) (*model.Collection, error)
GetCollectionByNameFunc func(ctx context.Context, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error)
GetCollectionByIDFunc func(ctx context.Context, collectionID UniqueID, ts Timestamp, allowUnavailable bool) (*model.Collection, error)
ChangeCollectionStateFunc func(ctx context.Context, collectionID UniqueID, state pb.CollectionState, ts Timestamp) error
RemoveCollectionFunc func(ctx context.Context, collectionID UniqueID, ts Timestamp) error
@@ -125,8 +125,8 @@ func (m mockMetaTable) AddCollection(ctx context.Context, coll *model.Collection
return m.AddCollectionFunc(ctx, coll)
}
func (m mockMetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp) (*model.Collection, error) {
return m.GetCollectionByNameFunc(ctx, collectionName, ts)
func (m mockMetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
return m.GetCollectionByNameFunc(ctx, collectionName, ts, allowUnavailable)
}
func (m mockMetaTable) GetCollectionByID(ctx context.Context, dbName string, collectionID UniqueID, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
@@ -494,7 +494,7 @@ func withInvalidMeta() Opt {
meta.ListCollectionsFunc = func(ctx context.Context, ts Timestamp) ([]*model.Collection, error) {
return nil, errors.New("error mock ListCollections")
}
meta.GetCollectionByNameFunc = func(ctx context.Context, collectionName string, ts Timestamp) (*model.Collection, error) {
meta.GetCollectionByNameFunc = func(ctx context.Context, collectionName string, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
return nil, errors.New("error mock GetCollectionByName")
}
meta.GetCollectionByIDFunc = func(ctx context.Context, collectionID typeutil.UniqueID, ts Timestamp, allowUnavailable bool) (*model.Collection, error) {
+72 -14
View File
@@ -1966,9 +1966,9 @@ func (_c *IMetaTable_GetCollectionByIDWithMaxTs_Call) RunAndReturn(run func(cont
return _c
}
// GetCollectionByName provides a mock function with given fields: ctx, dbName, collectionName, ts
func (_m *IMetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts uint64) (*model.Collection, error) {
ret := _m.Called(ctx, dbName, collectionName, ts)
// GetCollectionByName provides a mock function with given fields: ctx, dbName, collectionName, ts, allowUnavailable
func (_m *IMetaTable) GetCollectionByName(ctx context.Context, dbName string, collectionName string, ts uint64, allowUnavailable bool) (*model.Collection, error) {
ret := _m.Called(ctx, dbName, collectionName, ts, allowUnavailable)
if len(ret) == 0 {
panic("no return value specified for GetCollectionByName")
@@ -1976,19 +1976,19 @@ func (_m *IMetaTable) GetCollectionByName(ctx context.Context, dbName string, co
var r0 *model.Collection
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) (*model.Collection, error)); ok {
return rf(ctx, dbName, collectionName, ts)
if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64, bool) (*model.Collection, error)); ok {
return rf(ctx, dbName, collectionName, ts, allowUnavailable)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64) *model.Collection); ok {
r0 = rf(ctx, dbName, collectionName, ts)
if rf, ok := ret.Get(0).(func(context.Context, string, string, uint64, bool) *model.Collection); ok {
r0 = rf(ctx, dbName, collectionName, ts, allowUnavailable)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Collection)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, uint64) error); ok {
r1 = rf(ctx, dbName, collectionName, ts)
if rf, ok := ret.Get(1).(func(context.Context, string, string, uint64, bool) error); ok {
r1 = rf(ctx, dbName, collectionName, ts, allowUnavailable)
} else {
r1 = ret.Error(1)
}
@@ -2006,13 +2006,14 @@ type IMetaTable_GetCollectionByName_Call struct {
// - dbName string
// - collectionName string
// - ts uint64
func (_e *IMetaTable_Expecter) GetCollectionByName(ctx interface{}, dbName interface{}, collectionName interface{}, ts interface{}) *IMetaTable_GetCollectionByName_Call {
return &IMetaTable_GetCollectionByName_Call{Call: _e.mock.On("GetCollectionByName", ctx, dbName, collectionName, ts)}
// - allowUnavailable bool
func (_e *IMetaTable_Expecter) GetCollectionByName(ctx interface{}, dbName interface{}, collectionName interface{}, ts interface{}, allowUnavailable interface{}) *IMetaTable_GetCollectionByName_Call {
return &IMetaTable_GetCollectionByName_Call{Call: _e.mock.On("GetCollectionByName", ctx, dbName, collectionName, ts, allowUnavailable)}
}
func (_c *IMetaTable_GetCollectionByName_Call) Run(run func(ctx context.Context, dbName string, collectionName string, ts uint64)) *IMetaTable_GetCollectionByName_Call {
func (_c *IMetaTable_GetCollectionByName_Call) Run(run func(ctx context.Context, dbName string, collectionName string, ts uint64, allowUnavailable bool)) *IMetaTable_GetCollectionByName_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(uint64))
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(uint64), args[4].(bool))
})
return _c
}
@@ -2022,7 +2023,7 @@ func (_c *IMetaTable_GetCollectionByName_Call) Return(_a0 *model.Collection, _a1
return _c
}
func (_c *IMetaTable_GetCollectionByName_Call) RunAndReturn(run func(context.Context, string, string, uint64) (*model.Collection, error)) *IMetaTable_GetCollectionByName_Call {
func (_c *IMetaTable_GetCollectionByName_Call) RunAndReturn(run func(context.Context, string, string, uint64, bool) (*model.Collection, error)) *IMetaTable_GetCollectionByName_Call {
_c.Call.Return(run)
return _c
}
@@ -2398,6 +2399,63 @@ func (_c *IMetaTable_GetPChannelInfo_Call) RunAndReturn(run func(context.Context
return _c
}
// GetPartitionIDByName provides a mock function with given fields: collectionID, partitionName
func (_m *IMetaTable) GetPartitionIDByName(collectionID int64, partitionName string) (int64, bool) {
ret := _m.Called(collectionID, partitionName)
if len(ret) == 0 {
panic("no return value specified for GetPartitionIDByName")
}
var r0 int64
var r1 bool
if rf, ok := ret.Get(0).(func(int64, string) (int64, bool)); ok {
return rf(collectionID, partitionName)
}
if rf, ok := ret.Get(0).(func(int64, string) int64); ok {
r0 = rf(collectionID, partitionName)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(int64, string) bool); ok {
r1 = rf(collectionID, partitionName)
} else {
r1 = ret.Get(1).(bool)
}
return r0, r1
}
// IMetaTable_GetPartitionIDByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPartitionIDByName'
type IMetaTable_GetPartitionIDByName_Call struct {
*mock.Call
}
// GetPartitionIDByName is a helper method to define mock.On call
// - collectionID int64
// - partitionName string
func (_e *IMetaTable_Expecter) GetPartitionIDByName(collectionID interface{}, partitionName interface{}) *IMetaTable_GetPartitionIDByName_Call {
return &IMetaTable_GetPartitionIDByName_Call{Call: _e.mock.On("GetPartitionIDByName", collectionID, partitionName)}
}
func (_c *IMetaTable_GetPartitionIDByName_Call) Run(run func(collectionID int64, partitionName string)) *IMetaTable_GetPartitionIDByName_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(int64), args[1].(string))
})
return _c
}
func (_c *IMetaTable_GetPartitionIDByName_Call) Return(_a0 int64, _a1 bool) *IMetaTable_GetPartitionIDByName_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *IMetaTable_GetPartitionIDByName_Call) RunAndReturn(run func(int64, string) (int64, bool)) *IMetaTable_GetPartitionIDByName_Call {
_c.Call.Return(run)
return _c
}
// GetPrivilegeGroupRoles provides a mock function with given fields: ctx, groupName
func (_m *IMetaTable) GetPrivilegeGroupRoles(ctx context.Context, groupName string) ([]*milvuspb.RoleEntity, error) {
ret := _m.Called(ctx, groupName)
+38 -6
View File
@@ -1169,7 +1169,7 @@ func (c *Core) getCollectionIDStr(ctx context.Context, db, collectionName string
return strconv.FormatInt(collectionID, 10)
}
coll, err := c.meta.GetCollectionByName(ctx, db, collectionName, typeutil.MaxTimestamp)
coll, err := c.meta.GetCollectionByName(ctx, db, collectionName, typeutil.MaxTimestamp, false)
if err != nil {
return "-1"
}
@@ -1179,7 +1179,7 @@ func (c *Core) getCollectionIDStr(ctx context.Context, db, collectionName string
func (c *Core) describeCollection(ctx context.Context, in *milvuspb.DescribeCollectionRequest, allowUnavailable bool) (*model.Collection, error) {
ts := getTravelTs(in)
if in.GetCollectionName() != "" {
return c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), ts)
return c.meta.GetCollectionByName(ctx, in.GetDbName(), in.GetCollectionName(), ts, false)
}
return c.meta.GetCollectionByID(ctx, in.GetDbName(), in.GetCollectionID(), ts, allowUnavailable)
}
@@ -1197,10 +1197,11 @@ func convertModelToDesc(collInfo *model.Collection, aliases []string, dbName str
resp.CollectionID = collInfo.CollectionID
resp.VirtualChannelNames = collInfo.VirtualChannelNames
resp.PhysicalChannelNames = collInfo.PhysicalChannelNames
if collInfo.ShardsNum == 0 {
collInfo.ShardsNum = int32(len(collInfo.VirtualChannelNames))
shardsNum := collInfo.ShardsNum
if shardsNum == 0 {
shardsNum = int32(len(collInfo.VirtualChannelNames))
}
resp.ShardsNum = collInfo.ShardsNum
resp.ShardsNum = shardsNum
resp.ConsistencyLevel = collInfo.ConsistencyLevel
resp.CreatedTimestamp = collInfo.CreateTime
@@ -1613,7 +1614,7 @@ func (c *Core) CreatePartition(ctx context.Context, in *milvuspb.CreatePartition
zap.String("partitionName", in.GetPartitionName()))
logger.Info("received request to create partition")
if err := c.broadcastCreatePartition(ctx, in); err != nil {
if _, err := c.broadcastCreatePartition(ctx, in); err != nil {
if errors.Is(err, errIgnoerdCreatePartition) {
logger.Info("create partition that already exists, ignore it")
metrics.RootCoordDDLReqCounter.WithLabelValues("CreatePartition", metrics.SuccessLabel).Inc()
@@ -1630,6 +1631,37 @@ func (c *Core) CreatePartition(ctx context.Context, in *milvuspb.CreatePartition
return merr.Success(), nil
}
func (c *Core) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest) (*rootcoordpb.CreatePartitionResponse, error) {
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
return &rootcoordpb.CreatePartitionResponse{Status: merr.Status(err)}, nil
}
metrics.RootCoordDDLReqCounter.WithLabelValues("CreatePartition", metrics.TotalLabel).Inc()
tr := timerecord.NewTimeRecorder("CreatePartition")
logger := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole),
zap.String("dbName", in.GetDbName()),
zap.String("collectionName", in.GetCollectionName()),
zap.String("partitionName", in.GetPartitionName()))
logger.Info("received request to create partition")
partitionID, err := c.broadcastCreatePartition(ctx, in)
if err != nil {
if errors.Is(err, errIgnoerdCreatePartition) {
logger.Info("create partition that already exists, ignore it")
metrics.RootCoordDDLReqCounter.WithLabelValues("CreatePartition", metrics.SuccessLabel).Inc()
return &rootcoordpb.CreatePartitionResponse{Status: merr.Success(), PartitionID: partitionID}, nil
}
logger.Info("failed to create partition", zap.Error(err))
metrics.RootCoordDDLReqCounter.WithLabelValues("CreatePartition", metrics.FailLabel).Inc()
return &rootcoordpb.CreatePartitionResponse{Status: merr.Status(err)}, nil
}
metrics.RootCoordDDLReqCounter.WithLabelValues("CreatePartition", metrics.SuccessLabel).Inc()
metrics.RootCoordDDLReqLatency.WithLabelValues("CreatePartition").Observe(float64(tr.ElapseSpan().Milliseconds()))
logger.Info("done to create partition")
return &rootcoordpb.CreatePartitionResponse{Status: merr.Success(), PartitionID: partitionID}, nil
}
// DropPartition drop partition
func (c *Core) DropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest) (*commonpb.Status, error) {
if err := merr.CheckHealthy(c.GetStateCode()); err != nil {
+6 -5
View File
@@ -90,11 +90,12 @@ func initStreamingSystemAndCore(t *testing.T) *Core {
tso.EXPECT().GenerateTSO(mock.Anything).Return(uint64(1), nil).Maybe()
core := newTestCore(withHealthyCode(),
withMeta(&MetaTable{
catalog: rootcoord.NewCatalog(catalogKV),
names: testDB,
aliases: newNameDb(),
dbName2Meta: make(map[string]*model.Database),
collID2Meta: collID2Meta,
catalog: rootcoord.NewCatalog(catalogKV),
names: testDB,
aliases: newNameDb(),
dbName2Meta: make(map[string]*model.Database),
collID2Meta: collID2Meta,
partitionName2ID: make(map[int64]map[string]int64),
}),
withValidMixCoord(),
withValidProxyManager(),
+1 -1
View File
@@ -50,7 +50,7 @@ func (t *showPartitionTask) Execute(ctx context.Context) error {
if t.Req.GetCollectionName() == "" {
coll, err = t.core.meta.GetCollectionByID(ctx, t.Req.GetDbName(), t.Req.GetCollectionID(), typeutil.MaxTimestamp, t.allowUnavailable)
} else {
coll, err = t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp)
coll, err = t.core.meta.GetCollectionByName(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp, false)
}
if err != nil {
t.Rsp.Status = merr.Status(err)
@@ -60,7 +60,7 @@ func Test_showPartitionTask_Prepare(t *testing.T) {
func Test_showPartitionTask_Execute(t *testing.T) {
t.Run("failed to list collections by name", func(t *testing.T) {
metaTable := mockrootcoord.NewIMetaTable(t)
metaTable.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, "test coll", mock.Anything).Return(nil, merr.WrapErrCollectionNotFound("test coll"))
metaTable.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, "test coll", mock.Anything, mock.Anything).Return(nil, merr.WrapErrCollectionNotFound("test coll"))
core := newTestCore(withMeta(metaTable))
task := &showPartitionTask{
baseTask: newBaseTask(context.Background(), core),
+6 -6
View File
@@ -74,8 +74,8 @@ func TestLockerKey(t *testing.T) {
func TestGetLockerKey(t *testing.T) {
t.Run("describe collection task locker key", func(t *testing.T) {
metaMock := mockrootcoord.NewIMetaTable(t)
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64) (*model.Collection, error) {
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64, allowUnavailable bool) (*model.Collection, error) {
return nil, errors.New("not found")
})
c := &Core{
@@ -128,8 +128,8 @@ func TestGetLockerKey(t *testing.T) {
})
t.Run("has partition task locker key", func(t *testing.T) {
metaMock := mockrootcoord.NewIMetaTable(t)
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64) (*model.Collection, error) {
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64, allowUnavailable bool) (*model.Collection, error) {
return &model.Collection{
Name: "real" + s2,
CollectionID: 111,
@@ -165,8 +165,8 @@ func TestGetLockerKey(t *testing.T) {
})
t.Run("show partition task locker key", func(t *testing.T) {
metaMock := mockrootcoord.NewIMetaTable(t)
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64) (*model.Collection, error) {
metaMock.EXPECT().GetCollectionByName(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
RunAndReturn(func(ctx context.Context, s string, s2 string, u uint64, allowUnavailable bool) (*model.Collection, error) {
return &model.Collection{
Name: "real" + s2,
CollectionID: 111,
@@ -170,6 +170,10 @@ func (m *GrpcRootCoordClient) CreatePartition(ctx context.Context, in *milvuspb.
return &commonpb.Status{}, m.Err
}
func (m *GrpcRootCoordClient) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*rootcoordpb.CreatePartitionResponse, error) {
return &rootcoordpb.CreatePartitionResponse{}, m.Err
}
func (m *GrpcRootCoordClient) DropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
return &commonpb.Status{}, m.Err
}
+6
View File
@@ -109,6 +109,7 @@ service RootCoord {
* @return Status
*/
rpc CreatePartition(milvus.CreatePartitionRequest) returns (common.Status) {}
rpc CreatePartitionV2(milvus.CreatePartitionRequest) returns (CreatePartitionResponse) {}
/**
* @brief This method is used to drop partition
@@ -315,3 +316,8 @@ message ShowCollectionIDsResponse {
common.Status status = 1;
repeated DBCollections db_collections = 2;
}
message CreatePartitionResponse {
common.Status status = 1;
int64 partitionID = 2;
}
File diff suppressed because it is too large Load Diff
@@ -47,6 +47,7 @@ const (
RootCoord_AlterCollectionFunction_FullMethodName = "/milvus.proto.rootcoord.RootCoord/AlterCollectionFunction"
RootCoord_DropCollectionFunction_FullMethodName = "/milvus.proto.rootcoord.RootCoord/DropCollectionFunction"
RootCoord_CreatePartition_FullMethodName = "/milvus.proto.rootcoord.RootCoord/CreatePartition"
RootCoord_CreatePartitionV2_FullMethodName = "/milvus.proto.rootcoord.RootCoord/CreatePartitionV2"
RootCoord_DropPartition_FullMethodName = "/milvus.proto.rootcoord.RootCoord/DropPartition"
RootCoord_HasPartition_FullMethodName = "/milvus.proto.rootcoord.RootCoord/HasPartition"
RootCoord_ShowPartitions_FullMethodName = "/milvus.proto.rootcoord.RootCoord/ShowPartitions"
@@ -174,6 +175,7 @@ type RootCoordClient interface {
//
// @return Status
CreatePartition(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*commonpb.Status, error)
CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*CreatePartitionResponse, error)
// *
// @brief This method is used to drop partition
//
@@ -467,6 +469,15 @@ func (c *rootCoordClient) CreatePartition(ctx context.Context, in *milvuspb.Crea
return out, nil
}
func (c *rootCoordClient) CreatePartitionV2(ctx context.Context, in *milvuspb.CreatePartitionRequest, opts ...grpc.CallOption) (*CreatePartitionResponse, error) {
out := new(CreatePartitionResponse)
err := c.cc.Invoke(ctx, RootCoord_CreatePartitionV2_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *rootCoordClient) DropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest, opts ...grpc.CallOption) (*commonpb.Status, error) {
out := new(commonpb.Status)
err := c.cc.Invoke(ctx, RootCoord_DropPartition_FullMethodName, in, out, opts...)
@@ -968,6 +979,7 @@ type RootCoordServer interface {
//
// @return Status
CreatePartition(context.Context, *milvuspb.CreatePartitionRequest) (*commonpb.Status, error)
CreatePartitionV2(context.Context, *milvuspb.CreatePartitionRequest) (*CreatePartitionResponse, error)
// *
// @brief This method is used to drop partition
//
@@ -1113,6 +1125,9 @@ func (UnimplementedRootCoordServer) DropCollectionFunction(context.Context, *mil
func (UnimplementedRootCoordServer) CreatePartition(context.Context, *milvuspb.CreatePartitionRequest) (*commonpb.Status, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePartition not implemented")
}
func (UnimplementedRootCoordServer) CreatePartitionV2(context.Context, *milvuspb.CreatePartitionRequest) (*CreatePartitionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePartitionV2 not implemented")
}
func (UnimplementedRootCoordServer) DropPartition(context.Context, *milvuspb.DropPartitionRequest) (*commonpb.Status, error) {
return nil, status.Errorf(codes.Unimplemented, "method DropPartition not implemented")
}
@@ -1698,6 +1713,24 @@ func _RootCoord_CreatePartition_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _RootCoord_CreatePartitionV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(milvuspb.CreatePartitionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RootCoordServer).CreatePartitionV2(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RootCoord_CreatePartitionV2_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RootCoordServer).CreatePartitionV2(ctx, req.(*milvuspb.CreatePartitionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RootCoord_DropPartition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(milvuspb.DropPartitionRequest)
if err := dec(in); err != nil {
@@ -2647,6 +2680,10 @@ var RootCoord_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreatePartition",
Handler: _RootCoord_CreatePartition_Handler,
},
{
MethodName: "CreatePartitionV2",
Handler: _RootCoord_CreatePartitionV2_Handler,
},
{
MethodName: "DropPartition",
Handler: _RootCoord_DropPartition_Handler,
+11
View File
@@ -2052,6 +2052,8 @@ type proxyConfig struct {
QueryNodePoolingSize ParamItem `refreshable:"false"`
HybridSearchRequeryPolicy ParamItem `refreshable:"true"`
MetaCacheGCTimeInterval ParamItem `refreshable:"true"`
}
func (p *proxyConfig) init(base *BaseTable) {
@@ -2594,6 +2596,15 @@ Disabled if the value is less or equal to 0.`,
Export: true,
}
p.QueryNodePoolingSize.Init(base.mgr)
p.MetaCacheGCTimeInterval = ParamItem{
Key: "proxy.metaCacheGCTimeInterval",
Version: "2.6.13",
Doc: "the time interval for meta cache GC, in seconds",
DefaultValue: "300",
Export: true,
}
p.MetaCacheGCTimeInterval.Init(base.mgr)
}
// /////////////////////////////////////////////////////////////////////////////