498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
1949 lines
66 KiB
Go
1949 lines
66 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 querynodev2
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/cockroachdb/errors"
|
||
"github.com/samber/lo"
|
||
"go.opentelemetry.io/otel/trace"
|
||
"golang.org/x/sync/errgroup"
|
||
"google.golang.org/protobuf/proto"
|
||
|
||
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
|
||
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
|
||
"github.com/milvus-io/milvus-proto/go-api/v3/msgpb"
|
||
"github.com/milvus-io/milvus/internal/distributed/streaming"
|
||
"github.com/milvus-io/milvus/internal/flushcommon/syncmgr"
|
||
"github.com/milvus-io/milvus/internal/querynodev2/delegator"
|
||
"github.com/milvus-io/milvus/internal/querynodev2/segments"
|
||
"github.com/milvus-io/milvus/internal/querynodev2/tasks"
|
||
"github.com/milvus-io/milvus/internal/storage"
|
||
"github.com/milvus-io/milvus/internal/storagev2"
|
||
"github.com/milvus-io/milvus/internal/streamingnode/client/handler"
|
||
"github.com/milvus-io/milvus/internal/streamingnode/client/handler/registry"
|
||
"github.com/milvus-io/milvus/internal/util/analyzer"
|
||
"github.com/milvus-io/milvus/internal/util/fileresource"
|
||
"github.com/milvus-io/milvus/internal/util/searchutil/scheduler"
|
||
streamingstatus "github.com/milvus-io/milvus/internal/util/streamingutil/status"
|
||
"github.com/milvus-io/milvus/internal/util/streamrpc"
|
||
"github.com/milvus-io/milvus/internal/util/textmatch"
|
||
"github.com/milvus-io/milvus/pkg/v3/common"
|
||
"github.com/milvus-io/milvus/pkg/v3/metrics"
|
||
"github.com/milvus-io/milvus/pkg/v3/mlog"
|
||
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
|
||
"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/util/commonpbutil"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/conc"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/contextutil"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/hardware"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/merr"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/metricsinfo"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/timerecord"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/tsoutil"
|
||
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
|
||
)
|
||
|
||
type segmentDetacher interface {
|
||
DetachStreaming(ctx context.Context, segmentID typeutil.UniqueID) int
|
||
}
|
||
|
||
// GetComponentStates returns information about whether the node is healthy
|
||
func (node *QueryNode) GetComponentStates(ctx context.Context, req *milvuspb.GetComponentStatesRequest) (*milvuspb.ComponentStates, error) {
|
||
stats := &milvuspb.ComponentStates{
|
||
Status: merr.Success(),
|
||
}
|
||
|
||
code := node.lifetime.GetState()
|
||
nodeID := common.NotRegisteredID
|
||
|
||
if node.session != nil && node.session.Registered() {
|
||
nodeID = node.GetNodeID()
|
||
}
|
||
mlog.Debug(ctx, "QueryNode current state", mlog.Int64("NodeID", nodeID), mlog.String("StateCode", code.String()))
|
||
|
||
info := &milvuspb.ComponentInfo{
|
||
NodeID: nodeID,
|
||
Role: typeutil.QueryNodeRole,
|
||
StateCode: code,
|
||
}
|
||
stats.State = info
|
||
return stats, nil
|
||
}
|
||
|
||
// GetTimeTickChannel returns the time tick channel
|
||
// TimeTickChannel contains many time tick messages, which will be sent by query nodes
|
||
func (node *QueryNode) GetTimeTickChannel(ctx context.Context, req *internalpb.GetTimeTickChannelRequest) (*milvuspb.StringResponse, error) {
|
||
return &milvuspb.StringResponse{
|
||
Status: merr.Success(),
|
||
Value: paramtable.Get().CommonCfg.QueryCoordTimeTick.GetValue(),
|
||
}, nil
|
||
}
|
||
|
||
// GetStatisticsChannel returns the statistics channel
|
||
// Statistics channel contains statistics infos of query nodes, such as segment infos, memory infos
|
||
func (node *QueryNode) GetStatisticsChannel(ctx context.Context, req *internalpb.GetStatisticsChannelRequest) (*milvuspb.StringResponse, error) {
|
||
return &milvuspb.StringResponse{
|
||
Status: merr.Success(),
|
||
}, nil
|
||
}
|
||
|
||
// GetStatistics returns loaded statistics of collection.
|
||
func (node *QueryNode) GetStatistics(ctx context.Context, req *querypb.GetStatisticsRequest) (*internalpb.GetStatisticsResponse, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("msgID", req.GetReq().GetBase().GetMsgID()),
|
||
mlog.Strings("vChannels", req.GetDmlChannels()),
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()),
|
||
mlog.Uint64("guaranteeTimestamp", req.GetReq().GetGuaranteeTimestamp()),
|
||
mlog.Uint64("timeTravel", req.GetReq().GetTravelTimestamp()),
|
||
)
|
||
log.Debug(ctx, "received GetStatisticsRequest")
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &internalpb.GetStatisticsResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
failRet := &internalpb.GetStatisticsResponse{
|
||
Status: merr.Success(),
|
||
}
|
||
|
||
var toReduceResults []*internalpb.GetStatisticsResponse
|
||
var mu sync.Mutex
|
||
runningGp, runningCtx := errgroup.WithContext(ctx)
|
||
for _, ch := range req.GetDmlChannels() {
|
||
ch := ch
|
||
req := &querypb.GetStatisticsRequest{
|
||
Req: req.Req,
|
||
DmlChannels: []string{ch},
|
||
SegmentIDs: req.SegmentIDs,
|
||
FromShardLeader: req.FromShardLeader,
|
||
Scope: req.Scope,
|
||
}
|
||
runningGp.Go(func() error {
|
||
ret, err := node.getChannelStatistics(runningCtx, req, ch)
|
||
if err == nil {
|
||
err = merr.Error(ret.GetStatus())
|
||
}
|
||
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
if err != nil {
|
||
failRet.Status = merr.Status(err)
|
||
return err
|
||
}
|
||
toReduceResults = append(toReduceResults, ret)
|
||
return nil
|
||
})
|
||
}
|
||
if err := runningGp.Wait(); err != nil {
|
||
return failRet, nil
|
||
}
|
||
|
||
ret, err := reduceStatisticResponse(toReduceResults)
|
||
if err != nil {
|
||
failRet.Status = merr.Status(err)
|
||
return failRet, nil
|
||
}
|
||
log.Debug(ctx, "reduce statistic result done")
|
||
|
||
return ret, nil
|
||
}
|
||
|
||
// WatchDmChannels create consumers on dmChannels to receive Incremental data,which is the important part of real-time query
|
||
func (node *QueryNode) WatchDmChannels(ctx context.Context, req *querypb.WatchDmChannelsRequest) (status *commonpb.Status, e error) {
|
||
defer node.updateDistributionModifyTS()
|
||
|
||
channel := req.GetInfos()[0]
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.String("channel", channel.GetChannelName()),
|
||
mlog.Int64("currentNodeID", node.GetNodeID()),
|
||
)
|
||
|
||
log.Info(ctx, "received watch channel request",
|
||
mlog.Int64("version", req.GetVersion()),
|
||
)
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// check index
|
||
if len(req.GetIndexInfoList()) == 0 {
|
||
err := merr.WrapErrIndexNotFoundForCollection(req.GetSchema().GetName())
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
if !node.subscribingChannels.Insert(channel.GetChannelName()) {
|
||
msg := "channel subscribing..."
|
||
log.Warn(ctx,
|
||
msg)
|
||
return merr.Success(), nil
|
||
}
|
||
defer node.subscribingChannels.Remove(channel.GetChannelName())
|
||
|
||
// to avoid concurrent watch/unwatch
|
||
if node.unsubscribingChannels.Contain(channel.GetChannelName()) {
|
||
err := merr.WrapErrChannelReduplicate(channel.GetChannelName(), "the other same channel is unsubscribing")
|
||
log.Warn(ctx, "failed to unsubscribe channel", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
_, exist := node.delegators.Get(channel.GetChannelName())
|
||
if exist {
|
||
log.Info(ctx, "channel already subscribed")
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
err := node.manager.Collection.PutOrRef(req.GetCollectionID(), req.GetSchema(),
|
||
segments.ComposeIndexMeta(ctx, req.GetIndexInfoList(), req.Schema), req.GetLoadMeta())
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to ref collection", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
defer func() {
|
||
if !merr.Ok(status) {
|
||
node.manager.Collection.Unref(req.GetCollectionID(), 1)
|
||
}
|
||
}()
|
||
|
||
queryView := delegator.NewChannelQueryView(
|
||
channel.GetUnflushedSegmentIds(),
|
||
req.GetSealedSegmentRowCount(),
|
||
req.GetPartitionIDs(),
|
||
req.GetTargetVersion(),
|
||
)
|
||
|
||
delegator, err := delegator.NewShardDelegator(
|
||
ctx,
|
||
req.GetCollectionID(),
|
||
req.GetReplicaID(),
|
||
channel.GetChannelName(),
|
||
req.GetVersion(),
|
||
node.clusterManager,
|
||
node.manager,
|
||
node.loader,
|
||
channel.GetSeekPosition().GetTimestamp(),
|
||
node.queryHook,
|
||
node.chunkManager,
|
||
queryView,
|
||
node.binlogSaver,
|
||
)
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to create shard delegator", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
node.delegators.Insert(channel.GetChannelName(), delegator)
|
||
defer func() {
|
||
if err != nil {
|
||
node.delegators.GetAndRemove(channel.GetChannelName())
|
||
delegator.Close()
|
||
}
|
||
}()
|
||
|
||
pipeline, err := node.pipelineManager.Add(req.GetCollectionID(), channel.GetChannelName())
|
||
if err != nil {
|
||
msg := "failed to create pipeline"
|
||
log.Warn(ctx,
|
||
msg, mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
defer func() {
|
||
if err != nil {
|
||
node.pipelineManager.Remove(channel.GetChannelName())
|
||
}
|
||
}()
|
||
|
||
growingInfo := lo.SliceToMap(channel.GetUnflushedSegmentIds(), func(id int64) (int64, uint64) {
|
||
info := req.GetSegmentInfos()[id]
|
||
return id, info.GetDmlPosition().GetTimestamp()
|
||
})
|
||
delegator.AddExcludedSegments(growingInfo)
|
||
|
||
flushedInfo := lo.SliceToMap(channel.GetFlushedSegmentIds(), func(id int64) (int64, uint64) {
|
||
return id, typeutil.MaxTimestamp
|
||
})
|
||
delegator.AddExcludedSegments(flushedInfo)
|
||
|
||
droppedInfo := lo.SliceToMap(channel.GetDroppedSegmentIds(), func(id int64) (int64, uint64) {
|
||
return id, typeutil.MaxTimestamp
|
||
})
|
||
delegator.AddExcludedSegments(droppedInfo)
|
||
|
||
defer func() {
|
||
if err != nil {
|
||
// remove legacy growing
|
||
node.manager.Segment.RemoveBy(ctx, segments.WithChannel(channel.GetChannelName()),
|
||
segments.WithType(segments.SegmentTypeGrowing))
|
||
}
|
||
}()
|
||
|
||
err = loadL0Segments(ctx, delegator, req)
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to load l0 segments", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
err = loadGrowingSegments(ctx, delegator, req)
|
||
if err != nil {
|
||
msg := "failed to load growing segments"
|
||
log.Warn(ctx,
|
||
msg, mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// Use seekPosition directly to start consuming the message stream.
|
||
//
|
||
// Background:
|
||
// - seekPosition: channel checkpoint from DataCoord, represents the position where data has been persisted
|
||
// - deleteCheckpoint: the minimum startPosition among all L0 segments, indicates where unpersisted
|
||
// delete records begin
|
||
//
|
||
// Why we can use seekPosition directly:
|
||
// - L0 segments have already been loaded above (loadL0Segments), which contain delete records
|
||
// from [deleteCheckpoint, L0.endPosition]
|
||
// - The message stream will capture new delete records from [seekPosition, ∞)
|
||
// - DataCoord ensures that seekPosition is calculated based on channel checkpoint, which is updated
|
||
// after data (including deletes) is flushed, so L0 segments should cover up to seekPosition
|
||
// - Using seekPosition avoids redundant message consumption when seekPosition > deleteCheckpoint
|
||
log.Info(ctx, "use channel seek position to seek",
|
||
mlog.Time("seekPosition", tsoutil.PhysicalTime(channel.GetSeekPosition().GetTimestamp())),
|
||
mlog.Time("deleteCheckpoint", tsoutil.PhysicalTime(channel.GetDeleteCheckpoint().GetTimestamp())),
|
||
)
|
||
err = pipeline.ConsumeMsgStream(ctx, channel.GetSeekPosition())
|
||
if err != nil {
|
||
err = merr.WrapErrServiceUnavailable(err.Error(), "InitPipelineFailed")
|
||
log.Warn(ctx,
|
||
err.Error(),
|
||
mlog.Int64("collectionID", channel.CollectionID),
|
||
mlog.String("channel", channel.ChannelName),
|
||
)
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// start pipeline
|
||
pipeline.Start()
|
||
// delegator after all steps done
|
||
delegator.Start()
|
||
log.Info(ctx, "watch dml channel success")
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) UnsubDmChannel(ctx context.Context, req *querypb.UnsubDmChannelRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.String("channel", req.GetChannelName()),
|
||
mlog.Int64("currentNodeID", node.GetNodeID()),
|
||
)
|
||
|
||
log.Info(ctx, "received unsubscribe channel request")
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
node.unsubscribingChannels.Insert(req.GetChannelName())
|
||
defer node.unsubscribingChannels.Remove(req.GetChannelName())
|
||
_, ok := node.delegators.Get(req.GetChannelName())
|
||
if ok {
|
||
growingSegmentIDs := node.localGrowingSegmentIDs(req.GetChannelName(), nil)
|
||
prepared, err := node.prepareReleaseManualFlush(ctx, req.GetCollectionID(), req.GetChannelName(), growingSegmentIDs)
|
||
prepareSkipped := false
|
||
if err != nil {
|
||
if isReleaseManualFlushPrepareUnavailable(err) {
|
||
log.Warn(ctx, "release manual flush prepare unavailable before unsubscribing channel, continue unsubscribe",
|
||
mlog.Int64s("segmentIDs", growingSegmentIDs),
|
||
mlog.Err(err))
|
||
prepared = false
|
||
prepareSkipped = true
|
||
} else {
|
||
log.Warn(ctx, "failed to prepare release manual flush before unsubscribing channel",
|
||
mlog.Int64s("segmentIDs", growingSegmentIDs),
|
||
mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
}
|
||
if prepareSkipped {
|
||
log.Info(ctx, "release manual flush prepare skipped before unsubscribing channel",
|
||
mlog.Int64s("segmentIDs", growingSegmentIDs),
|
||
mlog.Bool("prepared", prepared))
|
||
} else {
|
||
log.Info(ctx, "release manual flush prepare result before unsubscribing channel",
|
||
mlog.Int64s("segmentIDs", growingSegmentIDs),
|
||
mlog.Bool("prepared", prepared))
|
||
}
|
||
|
||
delegator, ok := node.delegators.GetAndRemove(req.GetChannelName())
|
||
if !ok {
|
||
log.Info(ctx, "channel already unsubscribed")
|
||
return merr.Success(), nil
|
||
}
|
||
node.pipelineManager.Remove(req.GetChannelName())
|
||
preparedGrowingSourceSegments := syncmgr.DefaultGrowingSourceRegistry().ReleasePreparedSegments(req.GetChannelName())
|
||
// close the delegator first to block all coming query/search requests
|
||
delegator.Close()
|
||
|
||
if detacher, ok := node.manager.Segment.(segmentDetacher); ok {
|
||
for _, segmentID := range preparedGrowingSourceSegments {
|
||
detacher.DetachStreaming(ctx, segmentID)
|
||
syncmgr.DefaultGrowingSourceRegistry().MarkReleaseDetached(req.GetChannelName(), segmentID)
|
||
syncmgr.DefaultGrowingSourceRegistry().ClearReleasePrepared(req.GetChannelName(), segmentID)
|
||
}
|
||
}
|
||
node.manager.Segment.RemoveBy(ctx, segments.WithChannel(req.GetChannelName()), segments.WithType(segments.SegmentTypeGrowing))
|
||
node.manager.Collection.Unref(req.GetCollectionID(), 1)
|
||
}
|
||
log.Info(ctx, "unsubscribed channel")
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) LoadPartitions(ctx context.Context, req *querypb.LoadPartitionsRequest) (*commonpb.Status, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.Int64s("partitionIDs", req.GetPartitionIDs()),
|
||
)
|
||
|
||
log.Info(ctx, "received load partitions request")
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthyOrStopping); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
collection := node.manager.Collection.Get(req.GetCollectionID())
|
||
if collection != nil {
|
||
collection.AddPartition(req.GetPartitionIDs()...)
|
||
}
|
||
|
||
log.Info(ctx, "load partitions done")
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// LoadSegments load historical data into query node, historical data can be vector data or index
|
||
func (node *QueryNode) LoadSegments(ctx context.Context, req *querypb.LoadSegmentsRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
segment := req.GetInfos()[0]
|
||
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", segment.GetCollectionID()),
|
||
mlog.Int64("partitionID", segment.GetPartitionID()),
|
||
mlog.String("shard", segment.GetInsertChannel()),
|
||
mlog.Int64("segmentID", segment.GetSegmentID()),
|
||
mlog.String("level", segment.GetLevel().String()),
|
||
mlog.Int64("currentNodeID", node.GetNodeID()),
|
||
mlog.Int64("dstNodeID", req.GetDstNodeID()),
|
||
)
|
||
|
||
log.Info(ctx, "received load segments request",
|
||
mlog.Int64("version", req.GetVersion()),
|
||
mlog.Bool("needTransfer", req.GetNeedTransfer()),
|
||
mlog.String("loadScope", req.GetLoadScope().String()))
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// check index
|
||
if len(req.GetIndexInfoList()) == 0 {
|
||
err := merr.WrapErrIndexNotFoundForCollection(req.GetSchema().GetName())
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// fallback binlog memory size to log size when it is zero
|
||
fallbackBinlogMemorySize := func(binlogs []*datapb.FieldBinlog) {
|
||
for _, insertBinlogs := range binlogs {
|
||
for _, b := range insertBinlogs.GetBinlogs() {
|
||
if b.GetMemorySize() == 0 {
|
||
b.MemorySize = b.GetLogSize()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for _, s := range req.GetInfos() {
|
||
fallbackBinlogMemorySize(s.GetBinlogPaths())
|
||
fallbackBinlogMemorySize(s.GetStatslogs())
|
||
fallbackBinlogMemorySize(s.GetDeltalogs())
|
||
}
|
||
|
||
// Delegates request to workers
|
||
if req.GetNeedTransfer() {
|
||
delegator, ok := node.delegators.Get(segment.GetInsertChannel())
|
||
if !ok {
|
||
msg := "failed to load segments, delegator not found"
|
||
log.Warn(ctx,
|
||
msg)
|
||
err := merr.WrapErrChannelNotFound(segment.GetInsertChannel())
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
if len(req.GetInfos()) > 0 && req.GetInfos()[0].Level == datapb.SegmentLevel_L0 {
|
||
// force l0 segment to load on delegator
|
||
if req.DstNodeID != node.GetNodeID() {
|
||
log.Info(ctx, "unexpected L0 segment load on non-delegator node, force to load on delegator")
|
||
req.DstNodeID = node.GetNodeID()
|
||
}
|
||
}
|
||
|
||
req.NeedTransfer = false
|
||
err := delegator.LoadSegments(ctx, req)
|
||
if err != nil {
|
||
log.Warn(ctx, "delegator failed to load segments", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
err := node.manager.Collection.PutOrRef(req.GetCollectionID(), req.GetSchema(),
|
||
segments.ComposeIndexMeta(ctx, req.GetIndexInfoList(), req.GetSchema()), req.GetLoadMeta())
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to ref collection", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.manager.Collection.Unref(req.GetCollectionID(), 1)
|
||
|
||
switch req.GetLoadScope() {
|
||
case querypb.LoadScope_Delta:
|
||
return node.loadDeltaLogs(ctx, req), nil
|
||
case querypb.LoadScope_Index:
|
||
return node.loadIndex(ctx, req), nil
|
||
case querypb.LoadScope_Stats:
|
||
return node.reopenSegments(ctx, req), nil
|
||
case querypb.LoadScope_Reopen:
|
||
return node.reopenSegments(ctx, req), nil
|
||
}
|
||
|
||
// Actual load segment
|
||
log.Info(ctx, "start to load segments...")
|
||
loaded, err := node.loader.Load(ctx,
|
||
req.GetCollectionID(),
|
||
segments.SegmentTypeSealed,
|
||
req.GetVersion(),
|
||
req.GetInfos()...,
|
||
)
|
||
if err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
node.manager.Collection.Ref(req.GetCollectionID(), uint32(len(loaded)))
|
||
|
||
log.Info(ctx, "load segments done...",
|
||
mlog.Int64s("segments", lo.Map(loaded, func(s segments.Segment, _ int) int64 { return s.ID() })))
|
||
|
||
// Publish filesystem metrics after load task completion
|
||
// Use default filesystem (empty path) for load tasks
|
||
storagev2.PublishDefaultFilesystemMetrics()
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// UpdateSchema updates the schema of the collection on the querynode.
|
||
func (node *QueryNode) UpdateSchema(ctx context.Context, req *querypb.UpdateSchemaRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.Uint64("schemaBarrierTs", req.GetSchemaBarrierTs()),
|
||
mlog.Int32("schemaVersion", req.GetSchema().GetVersion()),
|
||
)
|
||
|
||
log.Info(ctx, "querynode received update schema request")
|
||
|
||
// Pass the barrier timestamp through; collectionManager derives the logical
|
||
// schema version from the schema payload when it is present.
|
||
err := node.manager.Collection.UpdateSchema(req.GetCollectionID(), req.GetSchema(), req.GetSchemaBarrierTs())
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to update schema", mlog.Err(err))
|
||
}
|
||
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// ReleaseCollection clears all data related to this collection on the querynode
|
||
func (node *QueryNode) ReleaseCollection(ctx context.Context, in *querypb.ReleaseCollectionRequest) (*commonpb.Status, error) {
|
||
if err := node.lifetime.Add(merr.IsHealthyOrStopping); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// ReleasePartitions clears all data related to this partition on the querynode
|
||
func (node *QueryNode) ReleasePartitions(ctx context.Context, req *querypb.ReleasePartitionsRequest) (*commonpb.Status, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collection", req.GetCollectionID()),
|
||
mlog.Int64s("partitions", req.GetPartitionIDs()),
|
||
)
|
||
|
||
log.Info(ctx, "received release partitions request")
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
collection := node.manager.Collection.Get(req.GetCollectionID())
|
||
if collection != nil {
|
||
for _, partition := range req.GetPartitionIDs() {
|
||
collection.RemovePartition(partition)
|
||
}
|
||
}
|
||
|
||
log.Info(ctx, "release partitions done")
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// ReleaseSegments remove the specified segments from query node according segmentIDs, partitionIDs, and collectionID
|
||
func (node *QueryNode) ReleaseSegments(ctx context.Context, req *querypb.ReleaseSegmentsRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.String("shard", req.GetShard()),
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()),
|
||
mlog.Int64("currentNodeID", node.GetNodeID()),
|
||
)
|
||
|
||
log.Info(ctx, "received release segment request",
|
||
mlog.String("scope", req.GetScope().String()),
|
||
mlog.Bool("needTransfer", req.GetNeedTransfer()),
|
||
)
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthyOrStopping); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
if req.GetNeedTransfer() {
|
||
shardDelegator, ok := node.delegators.Get(req.GetShard())
|
||
if !ok {
|
||
msg := "failed to release segment, delegator not found"
|
||
log.Warn(ctx,
|
||
msg)
|
||
err := merr.WrapErrChannelNotFound(req.GetShard())
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
req.NeedTransfer = false
|
||
err := shardDelegator.ReleaseSegments(ctx, req, false)
|
||
if err != nil {
|
||
log.Warn(ctx, "delegator failed to release segment", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
log.Info(ctx, "start to release segments")
|
||
sealedCount := 0
|
||
for _, id := range req.GetSegmentIDs() {
|
||
_, count := node.manager.Segment.Remove(ctx, id, req.GetScope())
|
||
sealedCount += count
|
||
}
|
||
node.manager.Collection.Unref(req.GetCollectionID(), uint32(sealedCount))
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) localGrowingSegmentIDs(channel string, segmentIDs []int64) []int64 {
|
||
filters := []segments.SegmentFilter{
|
||
segments.WithChannel(channel),
|
||
segments.WithType(segments.SegmentTypeGrowing),
|
||
}
|
||
if len(segmentIDs) > 0 {
|
||
filters = append(filters, segments.WithIDs(segmentIDs...))
|
||
}
|
||
return lo.Map(node.manager.Segment.GetBy(filters...), func(segment segments.Segment, _ int) int64 {
|
||
return segment.ID()
|
||
})
|
||
}
|
||
|
||
func (node *QueryNode) prepareReleaseManualFlush(ctx context.Context, collectionID int64, channel string, segmentIDs []int64) (bool, error) {
|
||
segmentIDs = lo.Uniq(lo.Filter(segmentIDs, func(segmentID int64, _ int) bool {
|
||
return segmentID > 0 && !syncmgr.DefaultGrowingSourceRegistry().IsReleasePrepared(channel, segmentID, 0)
|
||
}))
|
||
if len(segmentIDs) == 0 {
|
||
return false, nil
|
||
}
|
||
wal := streaming.WAL()
|
||
if wal == nil {
|
||
return false, merr.WrapErrServiceUnavailable("streaming WAL is not initialized")
|
||
}
|
||
return wal.PrepareReleaseManualFlush(ctx, collectionID, channel, segmentIDs)
|
||
}
|
||
|
||
func isReleaseManualFlushPrepareUnavailable(err error) bool {
|
||
if err == nil {
|
||
return false
|
||
}
|
||
if errors.Is(err, merr.ErrServiceUnavailable) ||
|
||
errors.Is(err, handler.ErrClientClosed) ||
|
||
errors.Is(err, registry.ErrNoStreamingNodeDeployed) ||
|
||
errors.Is(err, registry.ErrNoReleaseManualFlushPreparer) {
|
||
return true
|
||
}
|
||
return streamingstatus.AsStreamingError(err).IsOnShutdown()
|
||
}
|
||
|
||
// GetSegmentInfo returns segment information of the collection on the queryNode, and the information includes memSize, numRow, indexName, indexID ...
|
||
func (node *QueryNode) GetSegmentInfo(ctx context.Context, in *querypb.GetSegmentInfoRequest) (*querypb.GetSegmentInfoResponse, error) {
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &querypb.GetSegmentInfoResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
var segmentInfos []*querypb.SegmentInfo
|
||
for _, segmentID := range in.GetSegmentIDs() {
|
||
segment := node.manager.Segment.Get(segmentID)
|
||
if segment == nil {
|
||
continue
|
||
}
|
||
collection := node.manager.Collection.Get(segment.Collection())
|
||
if collection == nil {
|
||
continue
|
||
}
|
||
|
||
// TODO(yah01): now Milvus supports only 1 vector field
|
||
vecFields := funcutil.GetVecFieldIDs(collection.Schema())
|
||
var (
|
||
indexName string
|
||
indexID int64
|
||
indexInfos []*querypb.FieldIndexInfo
|
||
)
|
||
for _, field := range vecFields {
|
||
indexes := segment.GetIndex(field)
|
||
if indexes != nil {
|
||
if len(indexes) != 1 {
|
||
mlog.Error(ctx, "only support one index for vector field", mlog.Int64("fieldID", field), mlog.Int("index count", len(indexes)))
|
||
return &querypb.GetSegmentInfoResponse{
|
||
Status: merr.Status(merr.WrapErrServiceInternal("only support one index for vector field")),
|
||
}, nil
|
||
}
|
||
index := indexes[0]
|
||
indexName = index.IndexInfo.GetIndexName()
|
||
indexID = index.IndexInfo.GetIndexID()
|
||
indexInfos = append(indexInfos, index.IndexInfo)
|
||
}
|
||
}
|
||
|
||
info := &querypb.SegmentInfo{
|
||
SegmentID: segment.ID(),
|
||
SegmentState: segment.Type(),
|
||
DmChannel: segment.Shard().VirtualName(),
|
||
PartitionID: segment.Partition(),
|
||
CollectionID: segment.Collection(),
|
||
NodeID: node.GetNodeID(),
|
||
NodeIds: []int64{node.GetNodeID()},
|
||
MemSize: segment.MemSize(),
|
||
NumRows: segment.InsertCount(),
|
||
IndexName: indexName,
|
||
IndexID: indexID,
|
||
IndexInfos: indexInfos,
|
||
StorageVersion: segment.LoadInfo().GetStorageVersion(),
|
||
}
|
||
segmentInfos = append(segmentInfos, info)
|
||
}
|
||
|
||
return &querypb.GetSegmentInfoResponse{
|
||
Status: merr.Success(),
|
||
Infos: segmentInfos,
|
||
}, nil
|
||
}
|
||
|
||
// SearchSegments performs search on segments.
|
||
// If req.FilterOnly is true, only executes filter and returns valid count per segment (Stage 1 of two-stage search).
|
||
// If req.FilterOnly is false, performs normal vector search and returns search results.
|
||
func (node *QueryNode) SearchSegments(ctx context.Context, req *querypb.SearchRequest) (*internalpb.SearchResults, error) {
|
||
channel := req.GetDmlChannels()[0]
|
||
log := mlog.With(
|
||
mlog.Int64("msgID", req.GetReq().GetBase().GetMsgID()),
|
||
mlog.Int64("collectionID", req.Req.GetCollectionID()),
|
||
mlog.String("channel", channel),
|
||
mlog.String("scope", req.GetScope().String()),
|
||
)
|
||
channelsMvcc := make(map[string]uint64)
|
||
for _, ch := range req.GetDmlChannels() {
|
||
channelsMvcc[ch] = req.GetReq().GetMvccTimestamp()
|
||
}
|
||
resp := &internalpb.SearchResults{
|
||
ChannelsMvcc: channelsMvcc,
|
||
}
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
nodeIDStr := paramtable.GetStringNodeID()
|
||
collIDStr := strconv.FormatInt(req.GetReq().GetCollectionID(), 10)
|
||
metrics.QueryNodeSQCount.WithLabelValues(nodeIDStr, metrics.SearchLabel, metrics.TotalLabel, metrics.FromLeader, collIDStr).Inc()
|
||
defer func() {
|
||
if !merr.Ok(resp.GetStatus()) {
|
||
metrics.QueryNodeSQCount.WithLabelValues(nodeIDStr, metrics.SearchLabel, metrics.FailLabel, metrics.FromLeader, collIDStr).Inc()
|
||
}
|
||
}()
|
||
|
||
log.Debug(ctx, "start to search segments on worker",
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()),
|
||
)
|
||
searchCtx, cancel := context.WithCancel(ctx)
|
||
defer cancel()
|
||
|
||
tr := timerecord.NewTimeRecorder("searchSegments")
|
||
log.Debug(ctx, "search segments...")
|
||
|
||
if !node.manager.Collection.Ref(req.Req.GetCollectionID(), 1) {
|
||
err := merr.WrapErrCollectionNotLoaded(req.GetReq().GetCollectionID())
|
||
log.Warn(ctx, "failed to search segments", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
collection := node.manager.Collection.Get(req.Req.GetCollectionID())
|
||
defer func() {
|
||
node.manager.Collection.Unref(req.GetReq().GetCollectionID(), 1)
|
||
}()
|
||
|
||
task := tasks.NewSearchTask(searchCtx, collection, node.manager, req, node.serverID)
|
||
|
||
if err := node.scheduler.Add(task); err != nil {
|
||
log.Warn(ctx, "failed to search channel", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
err := task.Wait()
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to search segments", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
tr.CtxElapse(ctx, "search segments done")
|
||
|
||
latency := tr.ElapseSpan()
|
||
metrics.QueryNodeSQReqLatency.WithLabelValues(nodeIDStr, metrics.SearchLabel, metrics.FromLeader).Observe(float64(latency.Milliseconds()))
|
||
metrics.QueryNodeSQCount.WithLabelValues(nodeIDStr, metrics.SearchLabel, metrics.SuccessLabel, metrics.FromLeader, collIDStr).Inc()
|
||
|
||
resp = task.SearchResult()
|
||
resp.GetCostAggregation().ResponseTime = tr.ElapseSpan().Milliseconds()
|
||
resp.GetCostAggregation().TotalNQ = node.scheduler.GetWaitingTaskTotalNQ()
|
||
if req.GetReq().GetIsTopkReduce() {
|
||
resp.IsTopkReduce = true
|
||
}
|
||
resp.IsRecallEvaluation = req.GetReq().GetIsRecallEvaluation()
|
||
return resp, nil
|
||
}
|
||
|
||
// Search performs replica search tasks.
|
||
func (node *QueryNode) Search(ctx context.Context, req *querypb.SearchRequest) (*internalpb.SearchResults, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetReq().GetCollectionID()),
|
||
mlog.Strings("channels", req.GetDmlChannels()),
|
||
mlog.Int64("nq", req.GetReq().GetNq()),
|
||
)
|
||
|
||
log.Debug(ctx, "Received SearchRequest",
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()),
|
||
mlog.Uint64("guaranteeTimestamp", req.GetReq().GetGuaranteeTimestamp()),
|
||
mlog.Uint64("mvccTimestamp", req.GetReq().GetMvccTimestamp()))
|
||
|
||
tr := timerecord.NewTimeRecorderWithTrace(ctx, "SearchRequest")
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &internalpb.SearchResults{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
resp := &internalpb.SearchResults{
|
||
Status: merr.Success(),
|
||
}
|
||
collection := node.manager.Collection.Get(req.GetReq().GetCollectionID())
|
||
if collection == nil {
|
||
resp.Status = merr.Status(merr.WrapErrCollectionNotLoaded(req.GetReq().GetCollectionID()))
|
||
return resp, nil
|
||
}
|
||
|
||
if len(req.GetDmlChannels()) != 1 {
|
||
// internal protocol assertion: the proxy always targets exactly one
|
||
// channel per request, so a violation is a Milvus bug, not user input
|
||
err := merr.WrapErrServiceInternalMsg("count of channels to be searched should only be 1, got %d, wrong code", len(req.GetDmlChannels()))
|
||
resp.Status = merr.Status(err)
|
||
log.Warn(ctx, "got wrong number of channels to be searched", mlog.Err(err))
|
||
return resp, nil
|
||
}
|
||
|
||
ch := req.GetDmlChannels()[0]
|
||
channelReq := &querypb.SearchRequest{
|
||
Req: req.Req,
|
||
DmlChannels: []string{ch},
|
||
SegmentIDs: req.SegmentIDs,
|
||
Scope: req.Scope,
|
||
TotalChannelNum: req.TotalChannelNum,
|
||
}
|
||
ret, err := node.searchChannel(ctx, channelReq, ch)
|
||
if err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
ret.Status = merr.Success()
|
||
|
||
metrics.QueryNodeExecuteCounter.WithLabelValues(strconv.FormatInt(node.GetNodeID(), 10), metrics.SearchLabel).
|
||
Add(float64(proto.Size(req)))
|
||
|
||
if ret.GetCostAggregation() != nil {
|
||
ret.GetCostAggregation().ResponseTime = tr.ElapseSpan().Milliseconds()
|
||
}
|
||
return ret, nil
|
||
}
|
||
|
||
// only used for delegator query segments from worker
|
||
func (node *QueryNode) QuerySegments(ctx context.Context, req *querypb.QueryRequest) (*internalpb.RetrieveResults, error) {
|
||
resp := &internalpb.RetrieveResults{
|
||
Status: merr.Success(),
|
||
}
|
||
msgID := req.Req.Base.GetMsgID()
|
||
traceID := trace.SpanFromContext(ctx).SpanContext().TraceID()
|
||
channel := req.GetDmlChannels()[0]
|
||
|
||
queryLabel := req.GetReq().GetQueryLabel()
|
||
if queryLabel == "" {
|
||
queryLabel = metrics.QueryLabel
|
||
}
|
||
ctx = contextutil.WithQueryLabel(ctx, queryLabel)
|
||
|
||
log := mlog.With(
|
||
mlog.Int64("msgID", msgID),
|
||
mlog.Int64("collectionID", req.GetReq().GetCollectionID()),
|
||
mlog.String("channel", channel),
|
||
mlog.String("scope", req.GetScope().String()),
|
||
mlog.String("queryLabel", queryLabel),
|
||
)
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.TotalLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
defer func() {
|
||
if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success {
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.FailLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
}
|
||
}()
|
||
|
||
log.Debug(ctx, "start do query segments", mlog.Int64s("segmentIDs", req.GetSegmentIDs()))
|
||
// add cancel when error occurs
|
||
queryCtx, cancel := context.WithCancel(ctx)
|
||
defer cancel()
|
||
|
||
tr := timerecord.NewTimeRecorder("querySegments")
|
||
if !node.manager.Collection.Ref(req.Req.GetCollectionID(), 1) {
|
||
err := merr.WrapErrCollectionNotLoaded(req.GetReq().GetCollectionID())
|
||
log.Warn(ctx, "failed to query segments", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
collection := node.manager.Collection.Get(req.Req.GetCollectionID())
|
||
defer func() {
|
||
node.manager.Collection.Unref(req.GetReq().GetCollectionID(), 1)
|
||
}()
|
||
// Send task to scheduler and wait until it finished.
|
||
task := tasks.NewQueryTask(queryCtx, collection, node.manager, req)
|
||
if err := node.scheduler.Add(task); err != nil {
|
||
log.Warn(ctx, "failed to add query task into scheduler", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
err := task.Wait()
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to query channel", mlog.Err(err))
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
tr.CtxElapse(ctx, fmt.Sprintf("do query done, traceID = %s, vChannel = %s, segmentIDs = %v",
|
||
traceID,
|
||
channel,
|
||
req.GetSegmentIDs(),
|
||
))
|
||
|
||
// TODO QueryNodeSQLatencyInQueue QueryNodeReduceLatency
|
||
latency := tr.ElapseSpan()
|
||
metrics.QueryNodeSQReqLatency.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.FromLeader).Observe(float64(latency.Milliseconds()))
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.SuccessLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
result := task.Result()
|
||
result.GetCostAggregation().ResponseTime = latency.Milliseconds()
|
||
result.GetCostAggregation().TotalNQ = node.scheduler.GetWaitingTaskTotalNQ()
|
||
return result, nil
|
||
}
|
||
|
||
// Query performs replica query tasks.
|
||
func (node *QueryNode) Query(ctx context.Context, req *querypb.QueryRequest) (*internalpb.RetrieveResults, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetReq().GetCollectionID()),
|
||
mlog.Strings("shards", req.GetDmlChannels()),
|
||
)
|
||
log.Debug(ctx, "received query request",
|
||
mlog.Int64s("outputFields", req.GetReq().GetOutputFieldsId()),
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()), // should be empty
|
||
mlog.Uint64("guaranteeTimestamp", req.GetReq().GetGuaranteeTimestamp()),
|
||
mlog.Uint64("mvccTimestamp", req.GetReq().GetMvccTimestamp()),
|
||
)
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &internalpb.RetrieveResults{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
if !node.manager.Collection.Ref(req.GetReq().GetCollectionID(), 1) {
|
||
err := merr.WrapErrCollectionNotLoaded(req.GetReq().GetCollectionID())
|
||
log.Warn(ctx, "failed to query collection", mlog.Err(err))
|
||
return &internalpb.RetrieveResults{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer func() {
|
||
node.manager.Collection.Unref(req.GetReq().GetCollectionID(), 1)
|
||
}()
|
||
if len(req.GetDmlChannels()) != 1 {
|
||
// internal protocol assertion: the proxy always targets exactly one
|
||
// channel per request, so a violation is a Milvus bug, not user input
|
||
return &internalpb.RetrieveResults{
|
||
Status: merr.Status(merr.WrapErrServiceInternalMsg("query request to querynode should "+
|
||
"only target at one channel, but got:%d", len(req.GetDmlChannels()))),
|
||
}, nil
|
||
}
|
||
tr := timerecord.NewTimeRecorderWithTrace(ctx, "QueryRequest")
|
||
defer tr.CtxElapse(ctx, fmt.Sprintf("do query with channel done, vChannel = %s, segmentIDs = %v",
|
||
req.GetDmlChannels()[0],
|
||
req.GetSegmentIDs(),
|
||
))
|
||
res, err := node.queryChannel(ctx, req, req.GetDmlChannels()[0])
|
||
if err != nil {
|
||
return &internalpb.RetrieveResults{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
func (node *QueryNode) QueryStream(req *querypb.QueryRequest, srv querypb.QueryNode_QueryStreamServer) error {
|
||
ctx := srv.Context()
|
||
queryLabel := req.GetReq().GetQueryLabel()
|
||
if queryLabel == "" {
|
||
queryLabel = metrics.QueryLabel
|
||
}
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetReq().GetCollectionID()),
|
||
mlog.Strings("shards", req.GetDmlChannels()),
|
||
mlog.String("queryLabel", queryLabel),
|
||
)
|
||
concurrentSrv := streamrpc.NewConcurrentQueryStreamServer(srv)
|
||
|
||
log.Debug(ctx, "received query stream request",
|
||
mlog.Int64s("outputFields", req.GetReq().GetOutputFieldsId()),
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIDs()),
|
||
mlog.Uint64("guaranteeTimestamp", req.GetReq().GetGuaranteeTimestamp()),
|
||
mlog.Uint64("mvccTimestamp", req.GetReq().GetMvccTimestamp()),
|
||
)
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
concurrentSrv.Send(&internalpb.RetrieveResults{Status: merr.Status(err)})
|
||
return nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
runningGp, runningCtx := errgroup.WithContext(ctx)
|
||
|
||
for _, ch := range req.GetDmlChannels() {
|
||
ch := ch
|
||
req := &querypb.QueryRequest{
|
||
Req: req.Req,
|
||
DmlChannels: []string{ch},
|
||
SegmentIDs: req.SegmentIDs,
|
||
Scope: req.Scope,
|
||
}
|
||
|
||
runningGp.Go(func() error {
|
||
err := node.queryChannelStream(runningCtx, req, ch, concurrentSrv)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
if err := runningGp.Wait(); err != nil {
|
||
concurrentSrv.Send(&internalpb.RetrieveResults{
|
||
Status: merr.Status(err),
|
||
})
|
||
return nil
|
||
}
|
||
|
||
metrics.QueryNodeExecuteCounter.WithLabelValues(strconv.FormatInt(node.GetNodeID(), 10), queryLabel).Add(float64(proto.Size(req)))
|
||
return nil
|
||
}
|
||
|
||
func (node *QueryNode) QueryStreamSegments(req *querypb.QueryRequest, srv querypb.QueryNode_QueryStreamSegmentsServer) error {
|
||
ctx := srv.Context()
|
||
msgID := req.Req.Base.GetMsgID()
|
||
traceID := trace.SpanFromContext(ctx).SpanContext().TraceID()
|
||
channel := req.GetDmlChannels()[0]
|
||
concurrentSrv := streamrpc.NewConcurrentQueryStreamServer(srv)
|
||
|
||
queryLabel := req.GetReq().GetQueryLabel()
|
||
if queryLabel == "" {
|
||
queryLabel = metrics.QueryLabel
|
||
}
|
||
|
||
log := mlog.With(
|
||
mlog.Int64("msgID", msgID),
|
||
mlog.Int64("collectionID", req.GetReq().GetCollectionID()),
|
||
mlog.String("channel", channel),
|
||
mlog.String("scope", req.GetScope().String()),
|
||
mlog.String("queryLabel", queryLabel),
|
||
)
|
||
|
||
resp := &internalpb.RetrieveResults{}
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.TotalLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
defer func() {
|
||
if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success {
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.FailLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
}
|
||
}()
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
resp.Status = merr.Status(err)
|
||
concurrentSrv.Send(resp)
|
||
return nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
log.Debug(ctx, "start do query with channel", mlog.Int64s("segmentIDs", req.GetSegmentIDs()))
|
||
|
||
tr := timerecord.NewTimeRecorder("queryChannel")
|
||
|
||
err := node.queryStreamSegments(ctx, req, concurrentSrv)
|
||
if err != nil {
|
||
resp.Status = merr.Status(err)
|
||
concurrentSrv.Send(resp)
|
||
return nil
|
||
}
|
||
|
||
tr.CtxElapse(ctx, fmt.Sprintf("do query done, traceID = %s, vChannel = %s, segmentIDs = %v",
|
||
traceID,
|
||
channel,
|
||
req.GetSegmentIDs(),
|
||
))
|
||
|
||
// TODO QueryNodeSQLatencyInQueue QueryNodeReduceLatency
|
||
latency := tr.ElapseSpan()
|
||
metrics.QueryNodeSQReqLatency.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.FromLeader).Observe(float64(latency.Milliseconds()))
|
||
metrics.QueryNodeSQCount.WithLabelValues(fmt.Sprint(node.GetNodeID()), queryLabel, metrics.SuccessLabel, metrics.FromLeader, fmt.Sprint(req.GetReq().GetCollectionID())).Inc()
|
||
return nil
|
||
}
|
||
|
||
// SyncReplicaSegments syncs replica node & segments states
|
||
func (node *QueryNode) SyncReplicaSegments(ctx context.Context, req *querypb.SyncReplicaSegmentsRequest) (*commonpb.Status, error) {
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// ShowConfigurations returns the configurations of queryNode matching req.Pattern
|
||
func (node *QueryNode) ShowConfigurations(ctx context.Context, req *internalpb.ShowConfigurationsRequest) (*internalpb.ShowConfigurationsResponse, error) {
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
mlog.Warn(ctx, "QueryNode.ShowConfigurations failed",
|
||
mlog.Int64("nodeId", node.GetNodeID()),
|
||
mlog.String("req", req.Pattern),
|
||
mlog.Err(err))
|
||
|
||
return &internalpb.ShowConfigurationsResponse{
|
||
Status: merr.Status(err),
|
||
Configuations: nil,
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
configList := make([]*commonpb.KeyValuePair, 0)
|
||
for key, value := range paramtable.Get().GetComponentConfigurations("querynode", req.Pattern) {
|
||
configList = append(configList,
|
||
&commonpb.KeyValuePair{
|
||
Key: key,
|
||
Value: value,
|
||
})
|
||
}
|
||
|
||
return &internalpb.ShowConfigurationsResponse{
|
||
Status: merr.Success(),
|
||
Configuations: configList,
|
||
}, nil
|
||
}
|
||
|
||
// GetMetrics return system infos of the query node, such as total memory, memory usage, cpu usage ...
|
||
func (node *QueryNode) GetMetrics(ctx context.Context, req *milvuspb.GetMetricsRequest) (*milvuspb.GetMetricsResponse, error) {
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
mlog.Warn(ctx, "QueryNode.GetMetrics failed",
|
||
mlog.Int64("nodeId", node.GetNodeID()),
|
||
mlog.String("req", req.Request),
|
||
mlog.Err(err))
|
||
|
||
return &milvuspb.GetMetricsResponse{
|
||
Status: merr.Status(err),
|
||
Response: "",
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
resp := &milvuspb.GetMetricsResponse{
|
||
Status: merr.Success(),
|
||
ComponentName: metricsinfo.ConstructComponentName(typeutil.QueryNodeRole, paramtable.GetNodeID()),
|
||
}
|
||
|
||
ret, err := node.metricsRequest.ExecuteMetricsRequest(ctx, req)
|
||
if err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
resp.Response = ret
|
||
return resp, nil
|
||
}
|
||
|
||
func (node *QueryNode) GetDataDistribution(ctx context.Context, req *querypb.GetDataDistributionRequest) (*querypb.GetDataDistributionResponse, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("msgID", req.GetBase().GetMsgID()),
|
||
mlog.Int64("nodeID", node.GetNodeID()),
|
||
)
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
log.Warn(ctx, "QueryNode.GetDataDistribution failed",
|
||
mlog.Err(err))
|
||
|
||
return &querypb.GetDataDistributionResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
lastModifyTs := node.getDistributionModifyTS()
|
||
distributionChange := func() bool {
|
||
if req.GetLastUpdateTs() == 0 {
|
||
return true
|
||
}
|
||
|
||
return req.GetLastUpdateTs() < lastModifyTs
|
||
}
|
||
|
||
if !distributionChange() {
|
||
return &querypb.GetDataDistributionResponse{
|
||
Status: merr.Success(),
|
||
NodeID: node.GetNodeID(),
|
||
LastModifyTs: lastModifyTs,
|
||
}, nil
|
||
}
|
||
|
||
sealedSegments := node.manager.Segment.GetBy(segments.WithType(commonpb.SegmentState_Sealed))
|
||
segmentVersionInfos := make([]*querypb.SegmentVersionInfo, 0, len(sealedSegments))
|
||
for _, s := range sealedSegments {
|
||
segmentVersionInfos = append(segmentVersionInfos, &querypb.SegmentVersionInfo{
|
||
ID: s.ID(),
|
||
Collection: s.Collection(),
|
||
Partition: s.Partition(),
|
||
Channel: s.Shard().VirtualName(),
|
||
Version: s.Version(),
|
||
Level: s.Level(),
|
||
IsSorted: s.IsSorted(),
|
||
LastDeltaTimestamp: s.LastDeltaTimestamp(),
|
||
IndexInfo: lo.SliceToMap(s.Indexes(), func(info *segments.IndexedFieldInfo) (int64, *querypb.FieldIndexInfo) {
|
||
return info.IndexInfo.IndexID, info.IndexInfo
|
||
}),
|
||
JsonStatsInfo: s.GetFieldJSONIndexStats(),
|
||
ManifestPath: s.LoadInfo().GetManifestPath(),
|
||
DataVersion: proto.Int32(s.LoadInfo().GetDataVersion()),
|
||
})
|
||
}
|
||
|
||
channelVersionInfos := make([]*querypb.ChannelVersionInfo, 0)
|
||
leaderViews := make([]*querypb.LeaderView, 0)
|
||
|
||
node.delegators.Range(func(key string, delegator delegator.ShardDelegator) bool {
|
||
if !delegator.Serviceable() {
|
||
return true
|
||
}
|
||
channelVersionInfos = append(channelVersionInfos, &querypb.ChannelVersionInfo{
|
||
Channel: key,
|
||
Collection: delegator.Collection(),
|
||
Version: delegator.Version(),
|
||
})
|
||
|
||
sealed, growing := delegator.GetSegmentInfo(false)
|
||
sealedSegments := make(map[int64]*querypb.SegmentDist)
|
||
for _, item := range sealed {
|
||
for _, segment := range item.Segments {
|
||
sealedSegments[segment.SegmentID] = &querypb.SegmentDist{
|
||
NodeID: item.NodeID,
|
||
Version: segment.Version,
|
||
}
|
||
}
|
||
}
|
||
|
||
numOfGrowingRows := int64(0)
|
||
growingSegments := make(map[int64]*msgpb.MsgPosition)
|
||
for _, entry := range growing {
|
||
segment := node.manager.Segment.GetWithType(entry.SegmentID, segments.SegmentTypeGrowing)
|
||
if segment == nil {
|
||
log.Warn(ctx, "leader view growing not found", mlog.String("channel", key), mlog.Int64("segmentID", entry.SegmentID))
|
||
growingSegments[entry.SegmentID] = &msgpb.MsgPosition{}
|
||
continue
|
||
}
|
||
growingSegments[entry.SegmentID] = segment.StartPosition()
|
||
numOfGrowingRows += segment.InsertCount()
|
||
}
|
||
|
||
queryView := delegator.GetChannelQueryView()
|
||
leaderViews = append(leaderViews, &querypb.LeaderView{
|
||
Collection: delegator.Collection(),
|
||
Channel: key,
|
||
SegmentDist: sealedSegments,
|
||
GrowingSegments: growingSegments,
|
||
NumOfGrowingRows: numOfGrowingRows,
|
||
PartitionStatsVersions: delegator.GetPartitionStatsVersions(ctx),
|
||
TargetVersion: queryView.GetVersion(),
|
||
Status: &querypb.LeaderViewStatus{
|
||
Serviceable: queryView.Serviceable(),
|
||
CatchingUpStreamingData: delegator.CatchingUpStreamingData(),
|
||
},
|
||
})
|
||
return true
|
||
})
|
||
|
||
return &querypb.GetDataDistributionResponse{
|
||
Status: merr.Success(),
|
||
NodeID: node.GetNodeID(),
|
||
Segments: segmentVersionInfos,
|
||
Channels: channelVersionInfos,
|
||
LeaderViews: leaderViews,
|
||
LastModifyTs: lastModifyTs,
|
||
MemCapacityInMB: float64(hardware.GetMemoryCount() / 1024 / 1024),
|
||
CpuNum: int64(hardware.GetCPUNum()),
|
||
}, nil
|
||
}
|
||
|
||
func (node *QueryNode) SyncDistribution(ctx context.Context, req *querypb.SyncDistributionRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
|
||
log := mlog.With(mlog.Int64("collectionID", req.GetCollectionID()),
|
||
mlog.String("channel", req.GetChannel()), mlog.Int64("currentNodeID", node.GetNodeID()))
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// get shard delegator
|
||
shardDelegator, ok := node.delegators.Get(req.GetChannel())
|
||
if !ok {
|
||
err := merr.WrapErrChannelNotFound(req.GetChannel())
|
||
log.Warn(ctx, "failed to find shard cluster when sync")
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// translate segment action
|
||
removeActions := make([]*querypb.SyncAction, 0)
|
||
group, ctx := errgroup.WithContext(ctx)
|
||
for _, action := range req.GetActions() {
|
||
log := mlog.With(mlog.String("Action",
|
||
action.GetType().String()))
|
||
switch action.GetType() {
|
||
case querypb.SyncType_Remove:
|
||
log.Info(ctx, "sync action", mlog.Int64("segmentID", action.SegmentID))
|
||
removeActions = append(removeActions, action)
|
||
case querypb.SyncType_Set:
|
||
log.Info(ctx, "sync action", mlog.Int64("segmentID", action.SegmentID))
|
||
if action.GetInfo() == nil {
|
||
log.Warn(ctx, "sync request from legacy querycoord without load info, skip")
|
||
continue
|
||
}
|
||
|
||
// to pass segment'version, we call load segment one by one
|
||
action := action
|
||
group.Go(func() error {
|
||
return shardDelegator.LoadSegments(ctx, &querypb.LoadSegmentsRequest{
|
||
Base: commonpbutil.NewMsgBase(
|
||
commonpbutil.WithMsgType(commonpb.MsgType_LoadSegments),
|
||
commonpbutil.WithMsgID(req.Base.GetMsgID()),
|
||
),
|
||
Infos: []*querypb.SegmentLoadInfo{action.GetInfo()},
|
||
Schema: req.GetSchema(),
|
||
LoadMeta: req.GetLoadMeta(),
|
||
CollectionID: req.GetCollectionID(),
|
||
ReplicaID: req.GetReplicaID(),
|
||
DstNodeID: action.GetNodeID(),
|
||
Version: action.GetVersion(),
|
||
NeedTransfer: false,
|
||
LoadScope: querypb.LoadScope_Delta,
|
||
IndexInfoList: req.GetIndexInfoList(),
|
||
})
|
||
})
|
||
case querypb.SyncType_UpdateVersion:
|
||
// Version compatibility check: reject messages with inconsistent sealed segment fields
|
||
// In v2.6, SealedInTarget and SealedSegmentRowCount have consistent keys (same length)
|
||
// A mismatch indicates the message is from v2.5 which lacks SealedSegmentRowCount
|
||
if len(action.GetSealedInTarget()) != len(action.GetSealedSegmentRowCount()) {
|
||
log.Warn(ctx, "Reject syncTargetVersion from older version Coordinator",
|
||
mlog.String("channel", req.GetChannel()),
|
||
mlog.Int("sealedInTarget", len(action.GetSealedInTarget())),
|
||
mlog.Int("sealedSegmentRowCount", len(action.GetSealedSegmentRowCount())),
|
||
)
|
||
continue
|
||
}
|
||
|
||
log.Info(ctx, "sync action",
|
||
mlog.Int64("TargetVersion", action.GetTargetVersion()),
|
||
mlog.Time("checkPoint", tsoutil.PhysicalTime(action.GetCheckpoint().GetTimestamp())),
|
||
mlog.Time("deleteCP", tsoutil.PhysicalTime(action.GetDeleteCP().GetTimestamp())),
|
||
mlog.Int64s("partitions", req.GetLoadMeta().GetPartitionIDs()))
|
||
droppedInfos := lo.SliceToMap(action.GetDroppedInTarget(), func(id int64) (int64, uint64) {
|
||
if action.GetCheckpoint() == nil {
|
||
return id, typeutil.MaxTimestamp
|
||
}
|
||
return id, action.GetCheckpoint().Timestamp
|
||
})
|
||
shardDelegator.AddExcludedSegments(droppedInfos)
|
||
flushedInfo := lo.SliceToMap(action.GetSealedInTarget(), func(id int64) (int64, uint64) {
|
||
if action.GetCheckpoint() == nil {
|
||
return id, typeutil.MaxTimestamp
|
||
}
|
||
return id, action.GetCheckpoint().Timestamp
|
||
})
|
||
shardDelegator.AddExcludedSegments(flushedInfo)
|
||
shardDelegator.SyncTargetVersion(action, req.GetLoadMeta().GetPartitionIDs())
|
||
case querypb.SyncType_UpdatePartitionStats:
|
||
log.Info(ctx, "sync update partition stats versions")
|
||
shardDelegator.SyncPartitionStats(ctx, action.PartitionStatsVersions)
|
||
default:
|
||
return merr.Status(merr.WrapErrServiceInternal("unknown action type", action.GetType().String())), nil
|
||
}
|
||
}
|
||
|
||
err := group.Wait()
|
||
if err != nil {
|
||
log.Warn(ctx, "failed to sync distribution", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
// in case of target node offline, when try to remove segment from leader's distribution, use wildcardNodeID(-1) to skip nodeID check
|
||
for _, action := range removeActions {
|
||
shardDelegator.ReleaseSegments(ctx, &querypb.ReleaseSegmentsRequest{
|
||
NodeID: -1,
|
||
SegmentIDs: []int64{action.GetSegmentID()},
|
||
Scope: querypb.DataScope_Historical,
|
||
CollectionID: req.GetCollectionID(),
|
||
}, true)
|
||
}
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// Delete is used to forward delete message between delegator and workers.
|
||
func (node *QueryNode) Delete(ctx context.Context, req *querypb.DeleteRequest) (*commonpb.Status, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionId()),
|
||
mlog.String("channel", req.GetVchannelName()),
|
||
mlog.Int64("segmentID", req.GetSegmentId()),
|
||
mlog.String("scope", req.GetScope().String()),
|
||
)
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
log.Debug(ctx, "QueryNode received worker delete detail", mlog.Stringer("info", &deleteRequestStringer{DeleteRequest: req}))
|
||
|
||
filters := []segments.SegmentFilter{
|
||
segments.WithID(req.GetSegmentId()),
|
||
}
|
||
|
||
// do not add filter for Unknown & All scope, for backward cap
|
||
switch req.GetScope() {
|
||
case querypb.DataScope_Historical:
|
||
filters = append(filters, segments.WithType(segments.SegmentTypeSealed))
|
||
case querypb.DataScope_Streaming:
|
||
filters = append(filters, segments.WithType(segments.SegmentTypeGrowing))
|
||
}
|
||
|
||
segments := node.manager.Segment.GetBy(filters...)
|
||
if len(segments) == 0 {
|
||
err := merr.WrapErrSegmentNotFound(req.GetSegmentId())
|
||
log.Warn(ctx, "segment not found for delete")
|
||
return merr.Status(err), nil
|
||
}
|
||
|
||
pks := storage.ParseIDs2PrimaryKeysBatch(req.GetPrimaryKeys())
|
||
var err error
|
||
for _, segment := range segments {
|
||
if req.GetUseLoad() {
|
||
var dd *storage.DeltaData
|
||
dd, err = storage.NewDeltaDataWithData(pks, req.GetTimestamps())
|
||
if err == nil {
|
||
err = segment.LoadDeltaData(ctx, dd)
|
||
}
|
||
} else {
|
||
err = segment.Delete(ctx, pks, req.GetTimestamps())
|
||
}
|
||
if err != nil {
|
||
log.Warn(ctx, "segment delete failed", mlog.Err(err))
|
||
return merr.Status(err), nil
|
||
}
|
||
}
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
// DeleteBatch is the API to apply same delete data into multiple segments.
|
||
// it's basically same as `Delete` but cost less memory pressure.
|
||
func (node *QueryNode) DeleteBatch(ctx context.Context, req *querypb.DeleteBatchRequest) (*querypb.DeleteBatchResponse, error) {
|
||
log := mlog.With(
|
||
mlog.Int64("collectionID", req.GetCollectionId()),
|
||
mlog.String("channel", req.GetVchannelName()),
|
||
mlog.Int64s("segmentIDs", req.GetSegmentIds()),
|
||
mlog.String("scope", req.GetScope().String()),
|
||
)
|
||
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &querypb.DeleteBatchResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// log.Debug(ctx, "QueryNode received worker delete detail", mlog.Stringer("info", &deleteRequestStringer{DeleteRequest: req}))
|
||
|
||
filters := []segments.SegmentFilter{
|
||
segments.WithIDs(req.GetSegmentIds()...),
|
||
}
|
||
|
||
// do not add filter for Unknown & All scope, for backward cap
|
||
switch req.GetScope() {
|
||
case querypb.DataScope_Historical:
|
||
filters = append(filters, segments.WithType(segments.SegmentTypeSealed))
|
||
case querypb.DataScope_Streaming:
|
||
filters = append(filters, segments.WithType(segments.SegmentTypeGrowing))
|
||
}
|
||
|
||
segs := node.manager.Segment.GetBy(filters...)
|
||
|
||
hitIDs := lo.Map(segs, func(segment segments.Segment, _ int) int64 {
|
||
return segment.ID()
|
||
})
|
||
// calculate missing ids, continue to delete existing ones.
|
||
missingIDs := typeutil.NewSet(req.GetSegmentIds()...).Complement(typeutil.NewSet(hitIDs...))
|
||
if missingIDs.Len() > 0 {
|
||
log.Warn(ctx, "Delete batch find missing ids", mlog.Int64s("missing_ids", missingIDs.Collect()))
|
||
}
|
||
|
||
pks := storage.ParseIDs2PrimaryKeysBatch(req.GetPrimaryKeys())
|
||
|
||
// control the execution batch parallel with P number
|
||
// maybe it shall be lower in case of heavy CPU usage may impacting search/query
|
||
pool := segments.GetDeletePool()
|
||
futures := make([]*conc.Future[struct{}], 0, len(segs))
|
||
errSet := typeutil.NewConcurrentSet[int64]()
|
||
|
||
for _, segment := range segs {
|
||
segment := segment
|
||
futures = append(futures, pool.Submit(func() (struct{}, error) {
|
||
// TODO @silverxia, add interface to use same data struct for segment delete
|
||
// current implementation still copys pks into protobuf(or arrow) struct
|
||
err := segment.Delete(ctx, pks, req.GetTimestamps())
|
||
if err != nil {
|
||
errSet.Insert(segment.ID())
|
||
log.Warn(ctx, "segment delete failed",
|
||
mlog.Int64("segmentID", segment.ID()),
|
||
mlog.Err(err))
|
||
return struct{}{}, err
|
||
}
|
||
return struct{}{}, nil
|
||
}))
|
||
}
|
||
|
||
// ignore error returned, since error segment is recorded into error set
|
||
_ = conc.AwaitAll(futures...)
|
||
|
||
// return merr.Success(), nil
|
||
return &querypb.DeleteBatchResponse{
|
||
Status: merr.Success(),
|
||
FailedIds: errSet.Collect(),
|
||
}, nil
|
||
}
|
||
|
||
func (node *QueryNode) runAnalyzer(req *querypb.RunAnalyzerRequest) ([]*milvuspb.AnalyzerResult, error) {
|
||
tokenizer, err := analyzer.NewAnalyzer(req.GetAnalyzerParams(), "")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
defer tokenizer.Destroy()
|
||
|
||
results := make([]*milvuspb.AnalyzerResult, len(req.GetPlaceholder()))
|
||
for i, text := range req.GetPlaceholder() {
|
||
stream := tokenizer.NewTokenStream(string(text))
|
||
|
||
results[i] = &milvuspb.AnalyzerResult{
|
||
Tokens: make([]*milvuspb.AnalyzerToken, 0),
|
||
}
|
||
|
||
for stream.Advance() {
|
||
var token *milvuspb.AnalyzerToken
|
||
if req.GetWithDetail() {
|
||
token = stream.DetailedToken()
|
||
} else {
|
||
token = &milvuspb.AnalyzerToken{Token: stream.Token()}
|
||
}
|
||
|
||
if req.GetWithHash() {
|
||
token.Hash = typeutil.HashString2LessUint32(token.GetToken())
|
||
}
|
||
results[i].Tokens = append(results[i].Tokens, token)
|
||
}
|
||
stream.Destroy()
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
func (node *QueryNode) RunAnalyzer(ctx context.Context, req *querypb.RunAnalyzerRequest) (*milvuspb.RunAnalyzerResponse, error) {
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &milvuspb.RunAnalyzerResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// build and run analyzer by analyzer params
|
||
// if channel not set
|
||
if req.GetChannel() == "" {
|
||
result, err := node.runAnalyzer(req)
|
||
return &milvuspb.RunAnalyzerResponse{
|
||
Status: merr.Status(err),
|
||
Results: result,
|
||
}, nil
|
||
}
|
||
|
||
// run analyzer by delegator
|
||
// get delegator
|
||
sd, ok := node.delegators.Get(req.GetChannel())
|
||
if !ok {
|
||
err := merr.WrapErrChannelNotFound(req.GetChannel())
|
||
mlog.Warn(ctx, "RunAnalyzer failed, failed to get shard delegator", mlog.Err(err))
|
||
return &milvuspb.RunAnalyzerResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
// run analyzer
|
||
results, err := sd.RunAnalyzer(ctx, req)
|
||
if err != nil {
|
||
mlog.Warn(ctx, "failed to search on delegator", mlog.Err(err))
|
||
return &milvuspb.RunAnalyzerResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
|
||
return &milvuspb.RunAnalyzerResponse{
|
||
Status: merr.Status(nil),
|
||
Results: results,
|
||
}, nil
|
||
}
|
||
|
||
func (node *QueryNode) ValidateAnalyzer(ctx context.Context, req *querypb.ValidateAnalyzerRequest) (*querypb.ValidateAnalyzerResponse, error) {
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &querypb.ValidateAnalyzerResponse{Status: merr.Status(err)}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
resourceSet := typeutil.NewSet[int64]()
|
||
|
||
for _, info := range req.AnalyzerInfos {
|
||
ids, err := analyzer.ValidateAnalyzer(info.GetParams(), "")
|
||
if err != nil {
|
||
if info.GetName() != "" {
|
||
return &querypb.ValidateAnalyzerResponse{Status: merr.Status(merr.WrapErrParameterInvalidMsg("validate analyzer failed for field: %s, name: %s, error: %v", info.GetField(), info.GetName(), err))}, nil
|
||
}
|
||
return &querypb.ValidateAnalyzerResponse{Status: merr.Status(merr.WrapErrParameterInvalidMsg("validate analyzer failed for field: %s, error: %v", info.GetField(), err))}, nil
|
||
}
|
||
resourceSet.Insert(ids...)
|
||
}
|
||
|
||
return &querypb.ValidateAnalyzerResponse{Status: merr.Status(nil), ResourceIds: resourceSet.Collect()}, nil
|
||
}
|
||
|
||
type deleteRequestStringer struct {
|
||
*querypb.DeleteRequest
|
||
}
|
||
|
||
func (req *deleteRequestStringer) String() string {
|
||
var pkInfo string
|
||
switch {
|
||
case req.GetPrimaryKeys().GetIntId() != nil:
|
||
ids := req.GetPrimaryKeys().GetIntId().GetData()
|
||
pkInfo = fmt.Sprintf("Pks range[%d-%d], len: %d", ids[0], ids[len(ids)-1], len(ids))
|
||
case req.GetPrimaryKeys().GetStrId() != nil:
|
||
ids := req.GetPrimaryKeys().GetStrId().GetData()
|
||
pkInfo = fmt.Sprintf("Pks range[%s-%s], len: %d", ids[0], ids[len(ids)-1], len(ids))
|
||
}
|
||
tss := req.GetTimestamps()
|
||
return fmt.Sprintf("%s, timestamp range: [%d-%d]", pkInfo, tss[0], tss[len(tss)-1])
|
||
}
|
||
|
||
func (node *QueryNode) updateDistributionModifyTS() {
|
||
node.lastModifyLock.Lock()
|
||
defer node.lastModifyLock.Unlock()
|
||
|
||
node.lastModifyTs = time.Now().UnixNano()
|
||
}
|
||
|
||
func (node *QueryNode) getDistributionModifyTS() int64 {
|
||
node.lastModifyLock.RLock()
|
||
defer node.lastModifyLock.RUnlock()
|
||
return node.lastModifyTs
|
||
}
|
||
|
||
func (node *QueryNode) DropIndex(ctx context.Context, req *querypb.DropIndexRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
if req.GetNeedTransfer() {
|
||
shardDelegator, ok := node.delegators.Get(req.GetChannel())
|
||
if !ok {
|
||
return merr.Status(merr.WrapErrChannelNotFound(req.GetChannel())), nil
|
||
}
|
||
req.NeedTransfer = false
|
||
if err := shardDelegator.DropIndex(ctx, req); err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
}
|
||
segments, err := node.manager.Segment.GetAndPinBy(segments.WithID(req.GetSegmentID()))
|
||
if err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
if len(segments) == 0 {
|
||
return merr.Success(), nil
|
||
}
|
||
defer node.manager.Segment.Unpin(segments)
|
||
|
||
segment := segments[0]
|
||
indexIDs := req.GetIndexIDs()
|
||
for _, indexID := range indexIDs {
|
||
segment.DropIndex(ctx, indexID)
|
||
}
|
||
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) UpdateIndex(ctx context.Context, req *querypb.UpdateIndexRequest) (*commonpb.Status, error) {
|
||
defer node.updateDistributionModifyTS()
|
||
// UpdateIndex is currently a placeholder implementation
|
||
// The actual logic should handle AddIndex and DropIndex actions
|
||
// For now, return success to satisfy the interface
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) GetHighlight(ctx context.Context, req *querypb.GetHighlightRequest) (*querypb.GetHighlightResponse, error) {
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &querypb.GetHighlightResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
// get delegator
|
||
sd, ok := node.delegators.Get(req.GetChannel())
|
||
if !ok {
|
||
err := merr.WrapErrChannelNotFound(req.GetChannel())
|
||
mlog.Warn(ctx, "GetHighlight failed, failed to get shard delegator", mlog.Err(err))
|
||
return &querypb.GetHighlightResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
|
||
results, err := sd.GetHighlight(ctx, req)
|
||
if err != nil {
|
||
mlog.Warn(ctx, "GetHighlight failed, delegator run failed", mlog.Err(err))
|
||
return &querypb.GetHighlightResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
|
||
return &querypb.GetHighlightResponse{
|
||
Status: merr.Success(),
|
||
Results: results,
|
||
}, nil
|
||
}
|
||
|
||
func (node *QueryNode) SyncFileResource(ctx context.Context, req *internalpb.SyncFileResourceRequest) (*commonpb.Status, error) {
|
||
log := mlog.With(mlog.Uint64("version", req.GetVersion()))
|
||
log.Info(ctx, "sync file resource")
|
||
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
log.Warn(ctx, "failed to sync file resource, QueryNode is not healthy")
|
||
return merr.Status(err), nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
err := fileresource.Sync(req.GetVersion(), req.GetResources())
|
||
if err != nil {
|
||
return merr.Status(err), nil
|
||
}
|
||
return merr.Success(), nil
|
||
}
|
||
|
||
func (node *QueryNode) ClearReadTaskQueue(ctx context.Context, req *internalpb.ClearReadTaskQueueRequest) (*internalpb.ClearReadTaskQueueResponse, error) {
|
||
resp := &internalpb.ClearReadTaskQueueResponse{Status: merr.Success()}
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
filter, err := queryNodeReadTaskFilter(req.GetTaskType())
|
||
if err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
result, err := node.scheduler.ClearQueued(ctx, filter, req.GetReason())
|
||
if err != nil {
|
||
resp.Status = merr.Status(err)
|
||
return resp, nil
|
||
}
|
||
|
||
resp.QuerynodeQueuedCleared = result.QueuedCleared
|
||
resp.QueuedNqCleared = result.QueuedNQCleared
|
||
resp.Results = append(resp.Results, &internalpb.ClearReadTaskQueueComponentResult{
|
||
Status: merr.Success(),
|
||
Role: typeutil.QueryNodeRole,
|
||
NodeID: node.GetNodeID(),
|
||
QueuedCleared: result.QueuedCleared,
|
||
QueuedNqCleared: result.QueuedNQCleared,
|
||
})
|
||
mlog.Info(ctx, "cleared querynode read task queue",
|
||
mlog.String("taskType", req.GetTaskType()),
|
||
mlog.String("reason", req.GetReason()),
|
||
mlog.Int64("queuedCleared", result.QueuedCleared),
|
||
mlog.Int64("queuedNQCleared", result.QueuedNQCleared))
|
||
return resp, nil
|
||
}
|
||
|
||
func queryNodeReadTaskFilter(taskType string) (scheduler.TaskFilter, error) {
|
||
switch taskType {
|
||
case "", "all":
|
||
return nil, nil
|
||
case "search":
|
||
return func(task scheduler.Task) bool {
|
||
_, ok := task.(*tasks.SearchTask)
|
||
return ok
|
||
}, nil
|
||
case "query":
|
||
return func(task scheduler.Task) bool {
|
||
_, ok := task.(*tasks.QueryTask)
|
||
return ok
|
||
}, nil
|
||
case "query_stream":
|
||
return func(task scheduler.Task) bool {
|
||
_, ok := task.(*tasks.QueryStreamTask)
|
||
return ok
|
||
}, nil
|
||
default:
|
||
return nil, merr.WrapErrParameterInvalidMsg("unsupported task_type %q", taskType)
|
||
}
|
||
}
|
||
|
||
func (node *QueryNode) ComputePhraseMatchSlop(ctx context.Context, req *querypb.ComputePhraseMatchSlopRequest) (*querypb.ComputePhraseMatchSlopResponse, error) {
|
||
// check node healthy
|
||
if err := node.lifetime.Add(merr.IsHealthy); err != nil {
|
||
return &querypb.ComputePhraseMatchSlopResponse{
|
||
Status: merr.Status(err),
|
||
}, nil
|
||
}
|
||
defer node.lifetime.Done()
|
||
|
||
return node.computePhraseMatchSlopByParams(req)
|
||
}
|
||
|
||
func (node *QueryNode) computePhraseMatchSlopByParams(req *querypb.ComputePhraseMatchSlopRequest) (*querypb.ComputePhraseMatchSlopResponse, error) {
|
||
query := req.GetQueryText()
|
||
datas := req.GetDataTexts()
|
||
|
||
if query == "" || len(datas) == 0 {
|
||
return &querypb.ComputePhraseMatchSlopResponse{
|
||
Status: merr.Success(), // Empty result
|
||
}, nil
|
||
}
|
||
|
||
isMatches := make([]bool, len(datas))
|
||
slops := make([]int64, len(datas))
|
||
|
||
for i, data := range datas {
|
||
slop, err := textmatch.ComputePhraseMatchSlop(req.GetAnalyzerParams(), query, data)
|
||
if err != nil {
|
||
isMatches[i] = false
|
||
slops[i] = -1
|
||
} else {
|
||
isMatches[i] = true
|
||
slops[i] = int64(slop)
|
||
}
|
||
}
|
||
|
||
return &querypb.ComputePhraseMatchSlopResponse{
|
||
Status: merr.Success(),
|
||
IsMatch: isMatches,
|
||
Slops: slops,
|
||
}, nil
|
||
}
|