milvus/internal/rootcoord/ddl_callbacks.go
Zhen Ye 7c575a18b0
enhance: support AckSyncUp for broadcaster, and enable it in truncate api (#46313)
issue: #43897
also for issue: #46166

add ack_sync_up flag into broadcast message header, which indicates that
whether the broadcast operation is need to be synced up between the
streaming node and the coordinator.
If the ack_sync_up is false, the broadcast operation will be acked once
the recovery storage see the message at current vchannel, the fast ack
operation can be applied to speed up the broadcast operation.
If the ack_sync_up is true, the broadcast operation will be acked after
the checkpoint of current vchannel reach current message.
The fast ack operation can not be applied to speed up the broadcast
operation, because the ack operation need to be synced up with streaming
node.
e.g. if truncate collection operation want to call ack once callback
after the all segment are flushed at current vchannel, it should set the
ack_sync_up to be true.

TODO: current implementation doesn't promise the ack sync up semantic,
it only promise FastAck operation will not be applied, wait for 3.0 to
implement the ack sync up semantic. only for truncate api now.

---------

Signed-off-by: chyezh <chyezh@outlook.com>
2025-12-17 16:55:17 +08:00

184 lines
7.7 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rootcoord
import (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster"
"github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/broadcast"
"github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry"
"github.com/milvus-io/milvus/internal/util/proxyutil"
"github.com/milvus-io/milvus/pkg/v2/proto/messagespb"
"github.com/milvus-io/milvus/pkg/v2/streaming/util/message"
"github.com/milvus-io/milvus/pkg/v2/streaming/util/message/ce"
"github.com/milvus-io/milvus/pkg/v2/util/typeutil"
)
// RegisterDDLCallbacks registers the ddl callbacks.
func RegisterDDLCallbacks(core *Core) {
ddlCallback := &DDLCallback{
Core: core,
}
ddlCallback.registerCollectionCallbacks()
ddlCallback.registerPartitionCallbacks()
ddlCallback.registerRBACCallbacks()
ddlCallback.registerDatabaseCallbacks()
ddlCallback.registerAliasCallbacks()
}
// registerRBACCallbacks registers the rbac callbacks.
func (c *DDLCallback) registerRBACCallbacks() {
registry.RegisterAlterUserV2AckCallback(c.alterUserV2AckCallback)
registry.RegisterDropUserV2AckCallback(c.dropUserV2AckCallback)
registry.RegisterAlterRoleV2AckCallback(c.alterRoleV2AckCallback)
registry.RegisterDropRoleV2AckCallback(c.dropRoleV2AckCallback)
registry.RegisterAlterUserRoleV2AckCallback(c.alterUserRoleV2AckCallback)
registry.RegisterDropUserRoleV2AckCallback(c.dropUserRoleV2AckCallback)
registry.RegisterAlterPrivilegeV2AckCallback(c.alterPrivilegeV2AckCallback)
registry.RegisterDropPrivilegeV2AckCallback(c.dropPrivilegeV2AckCallback)
registry.RegisterAlterPrivilegeGroupV2AckCallback(c.alterPrivilegeGroupV2AckCallback)
registry.RegisterDropPrivilegeGroupV2AckCallback(c.dropPrivilegeGroupV2AckCallback)
registry.RegisterRestoreRBACV2AckCallback(c.restoreRBACV2AckCallback)
}
// registerDatabaseCallbacks registers the database callbacks.
func (c *DDLCallback) registerDatabaseCallbacks() {
registry.RegisterCreateDatabaseV2AckCallback(c.createDatabaseV1AckCallback)
registry.RegisterAlterDatabaseV2AckCallback(c.alterDatabaseV1AckCallback)
registry.RegisterDropDatabaseV2AckCallback(c.dropDatabaseV1AckCallback)
}
// registerAliasCallbacks registers the alias callbacks.
func (c *DDLCallback) registerAliasCallbacks() {
registry.RegisterAlterAliasV2AckCallback(c.alterAliasV2AckCallback)
registry.RegisterDropAliasV2AckCallback(c.dropAliasV2AckCallback)
}
// registerCollectionCallbacks registers the collection callbacks.
func (c *DDLCallback) registerCollectionCallbacks() {
registry.RegisterCreateCollectionV1AckCallback(c.createCollectionV1AckCallback)
registry.RegisterAlterCollectionV2AckCallback(c.alterCollectionV2AckCallback)
registry.RegisterDropCollectionV1AckCallback(c.dropCollectionV1AckCallback)
registry.RegisterTruncateCollectionV2AckCallback(c.truncateCollectionV2AckCallback)
registry.RegisterTruncateCollectionV2AckOnceCallback(c.truncateCollectionV2AckOnceCallback)
}
// registerPartitionCallbacks registers the partition callbacks.
func (c *DDLCallback) registerPartitionCallbacks() {
registry.RegisterCreatePartitionV1AckCallback(c.createPartitionV1AckCallback)
registry.RegisterDropPartitionV1AckCallback(c.dropPartitionV1AckCallback)
}
// DDLCallback is the callback of ddl.
type DDLCallback struct {
*Core
}
// CacheExpirationsGetter is the getter of cache expirations.
type CacheExpirationsGetter interface {
GetCacheExpirations() *message.CacheExpirations
}
// ExpireCaches handles the cache
func (c *DDLCallback) ExpireCaches(ctx context.Context, expirations any) error {
var cacheExpirations *message.CacheExpirations
if g, ok := expirations.(CacheExpirationsGetter); ok {
cacheExpirations = g.GetCacheExpirations()
} else if g, ok := expirations.(*message.CacheExpirations); ok {
cacheExpirations = g
} else if g, ok := expirations.(*ce.CacheExpirationsBuilder); ok {
cacheExpirations = g.Build()
} else {
panic(fmt.Sprintf("invalid getter type: %T", expirations))
}
for _, cacheExpiration := range cacheExpirations.CacheExpirations {
if err := c.expireCache(ctx, cacheExpiration); err != nil {
return err
}
}
return nil
}
func (c *DDLCallback) expireCache(ctx context.Context, cacheExpiration *message.CacheExpiration) error {
ts, err := c.tsoAllocator.GenerateTSO(1)
if err != nil {
return errors.Wrap(err, "failed to generate timestamp")
}
switch cacheExpiration.Cache.(type) {
case *messagespb.CacheExpiration_LegacyProxyCollectionMetaCache:
legacyProxyCollectionMetaCache := cacheExpiration.GetLegacyProxyCollectionMetaCache()
return c.Core.ExpireMetaCache(
ctx,
legacyProxyCollectionMetaCache.DbName,
[]string{legacyProxyCollectionMetaCache.CollectionName},
legacyProxyCollectionMetaCache.CollectionId,
legacyProxyCollectionMetaCache.PartitionName,
ts,
proxyutil.SetMsgType(legacyProxyCollectionMetaCache.MsgType))
}
return nil
}
// startBroadcastWithRBACLock starts a broadcast for rbac.
func startBroadcastWithRBACLock(ctx context.Context) (broadcaster.BroadcastAPI, error) {
api, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusivePrivilegeResourceKey())
if err != nil {
return nil, errors.Wrap(err, "failed to start broadcast with rbac lock")
}
return api, nil
}
// startBroadcastWithDatabaseLock starts a broadcast with database lock.
func startBroadcastWithDatabaseLock(ctx context.Context, dbName string) (broadcaster.BroadcastAPI, error) {
broadcaster, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusiveDBNameResourceKey(dbName))
if err != nil {
return nil, errors.Wrap(err, "failed to start broadcast with database lock")
}
return broadcaster, nil
}
// startBroadcastWithCollectionLock starts a broadcast with collection lock.
// CreateCollection and DropCollection can only be called with collection name itself, not alias.
// So it's safe to use collection name directly for those API.
func (*Core) startBroadcastWithCollectionLock(ctx context.Context, dbName string, collectionName string) (broadcaster.BroadcastAPI, error) {
broadcaster, err := broadcast.StartBroadcastWithResourceKeys(ctx,
message.NewSharedDBNameResourceKey(dbName),
message.NewExclusiveCollectionNameResourceKey(dbName, collectionName),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start broadcast with collection lock")
}
return broadcaster, nil
}
// startBroadcastWithAliasOrCollectionLock starts a broadcast with alias or collection lock.
// 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)
if err != nil {
return nil, errors.Wrap(err, "failed to get collection by name")
}
return c.startBroadcastWithCollectionLock(ctx, dbName, coll.Name)
}