mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 10:15:43 +00:00
Use tsoutil.AddPhysicalDurationOnTs when calculating DataCoord segment assignment expiration timestamps, instead of manually parsing and composing the physical and logical parts. This keeps expiration arithmetic on the shared hybrid timestamp helper and avoids converting large valid physical timestamps through time.Time.UnixNano(). Signed-off-by: yangxuan <xuan.yang@zilliz.com>
762 lines
26 KiB
Go
762 lines
26 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 datacoord
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/samber/lo"
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
|
|
"github.com/milvus-io/milvus/internal/datacoord/allocator"
|
|
"github.com/milvus-io/milvus/internal/storage"
|
|
"github.com/milvus-io/milvus/internal/storagev2/packed"
|
|
"github.com/milvus-io/milvus/pkg/v3/common"
|
|
"github.com/milvus-io/milvus/pkg/v3/mlog"
|
|
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/lock"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/merr"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/metautil"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/retry"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/tsoutil"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
|
|
)
|
|
|
|
// allocPool pool of Allocation, to reduce allocation of Allocation
|
|
var allocPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &Allocation{}
|
|
},
|
|
}
|
|
|
|
// getAllocation unifies way to retrieve allocation struct
|
|
func getAllocation(numOfRows int64) *Allocation {
|
|
v := allocPool.Get()
|
|
a, ok := v.(*Allocation)
|
|
if !ok {
|
|
a = &Allocation{}
|
|
}
|
|
if a == nil {
|
|
return &Allocation{
|
|
NumOfRows: numOfRows,
|
|
}
|
|
}
|
|
a.NumOfRows = numOfRows
|
|
a.ExpireTime = 0
|
|
a.SegmentID = 0
|
|
return a
|
|
}
|
|
|
|
// putAllocation puts an allocation for recycling
|
|
func putAllocation(a *Allocation) {
|
|
allocPool.Put(a)
|
|
}
|
|
|
|
type AllocNewGrowingSegmentRequest struct {
|
|
CollectionID UniqueID
|
|
PartitionID UniqueID
|
|
SegmentID UniqueID
|
|
ChannelName string
|
|
StorageVersion int64
|
|
IsCreatedByStreaming bool
|
|
SchemaVersion int32
|
|
}
|
|
|
|
// Manager manages segment related operations.
|
|
//
|
|
//go:generate mockery --name=Manager --structname=MockManager --output=./ --filename=mock_segment_manager.go --with-expecter --inpackage
|
|
type Manager interface {
|
|
// CreateSegment create new segment when segment not exist
|
|
|
|
// Deprecated: AllocSegment allocates rows and record the allocation, will be deprecated after enabling streamingnode.
|
|
AllocSegment(ctx context.Context, collectionID, partitionID UniqueID, channelName string, requestRows int64, storageVersion int64) ([]*Allocation, error)
|
|
|
|
// AllocNewGrowingSegment allocates segment for streaming node.
|
|
AllocNewGrowingSegment(ctx context.Context, req AllocNewGrowingSegmentRequest) (*SegmentInfo, error)
|
|
|
|
// DropSegment drops the segment from manager.
|
|
DropSegment(ctx context.Context, channel string, segmentID UniqueID)
|
|
// SealAllSegments seals all segments of collection with collectionID and return sealed segments.
|
|
// If segIDs is not empty, also seals segments in segIDs.
|
|
SealAllSegments(ctx context.Context, channel string, segIDs []UniqueID) ([]UniqueID, error)
|
|
// GetFlushableSegments returns flushable segment ids
|
|
GetFlushableSegments(ctx context.Context, channel string, ts Timestamp) ([]UniqueID, error)
|
|
// ExpireAllocations notifies segment status to expire old allocations
|
|
ExpireAllocations(ctx context.Context, channel string, ts Timestamp)
|
|
// DropSegmentsOfChannel drops all segments in a channel
|
|
DropSegmentsOfChannel(ctx context.Context, channel string)
|
|
// CleanZeroSealedSegmentsOfChannel try to clean real empty sealed segments in a channel
|
|
CleanZeroSealedSegmentsOfChannel(ctx context.Context, channel string, cpTs Timestamp)
|
|
// DropSegmentsOfPartition drops all segments in a partition
|
|
DropSegmentsOfPartition(ctx context.Context, channel string, partitionID []int64)
|
|
}
|
|
|
|
// Allocation records the allocation info
|
|
type Allocation struct {
|
|
SegmentID UniqueID
|
|
NumOfRows int64
|
|
ExpireTime Timestamp
|
|
}
|
|
|
|
func (alloc *Allocation) String() string {
|
|
t, _ := tsoutil.ParseTS(alloc.ExpireTime)
|
|
return fmt.Sprintf("SegmentID: %d, NumOfRows: %d, ExpireTime: %v", alloc.SegmentID, alloc.NumOfRows, t)
|
|
}
|
|
|
|
// make sure SegmentManager implements Manager
|
|
var _ Manager = (*SegmentManager)(nil)
|
|
|
|
// SegmentManager handles L1 segment related logic
|
|
type SegmentManager struct {
|
|
meta *meta
|
|
allocator allocator.Allocator
|
|
helper allocHelper
|
|
|
|
channelLock *lock.KeyLock[string]
|
|
channel2Growing *typeutil.ConcurrentMap[string, typeutil.UniqueSet]
|
|
channel2Sealed *typeutil.ConcurrentMap[string, typeutil.UniqueSet]
|
|
|
|
// Policies
|
|
estimatePolicy calUpperLimitPolicy
|
|
allocPolicy AllocatePolicy
|
|
segmentSealPolicies []SegmentSealPolicy
|
|
channelSealPolicies []channelSealPolicy
|
|
flushPolicy flushPolicy
|
|
}
|
|
|
|
type allocHelper struct {
|
|
afterCreateSegment func(segment *datapb.SegmentInfo) error
|
|
}
|
|
|
|
// allocOption allocation option applies to `SegmentManager`
|
|
type allocOption interface {
|
|
apply(manager *SegmentManager)
|
|
}
|
|
|
|
// allocFunc function shortcut for allocOption
|
|
type allocFunc func(manager *SegmentManager)
|
|
|
|
// implement allocOption
|
|
func (f allocFunc) apply(manager *SegmentManager) {
|
|
f(manager)
|
|
}
|
|
|
|
// get allocOption with allocHelper setting
|
|
func withAllocHelper(helper allocHelper) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) { manager.helper = helper })
|
|
}
|
|
|
|
// get default allocHelper, which does nothing
|
|
func defaultAllocHelper() allocHelper {
|
|
return allocHelper{
|
|
afterCreateSegment: func(segment *datapb.SegmentInfo) error { return nil },
|
|
}
|
|
}
|
|
|
|
// get allocOption with estimatePolicy
|
|
func withCalUpperLimitPolicy(policy calUpperLimitPolicy) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) { manager.estimatePolicy = policy })
|
|
}
|
|
|
|
// get allocOption with allocPolicy
|
|
func withAllocPolicy(policy AllocatePolicy) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) { manager.allocPolicy = policy })
|
|
}
|
|
|
|
// get allocOption with segmentSealPolicies
|
|
func withSegmentSealPolices(policies ...SegmentSealPolicy) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) {
|
|
// do override instead of append, to override default options
|
|
manager.segmentSealPolicies = policies
|
|
})
|
|
}
|
|
|
|
// get allocOption with channelSealPolicies
|
|
func withChannelSealPolices(policies ...channelSealPolicy) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) {
|
|
// do override instead of append, to override default options
|
|
manager.channelSealPolicies = policies
|
|
})
|
|
}
|
|
|
|
// get allocOption with flushPolicy
|
|
func withFlushPolicy(policy flushPolicy) allocOption {
|
|
return allocFunc(func(manager *SegmentManager) { manager.flushPolicy = policy })
|
|
}
|
|
|
|
func defaultCalUpperLimitPolicy() calUpperLimitPolicy {
|
|
return calBySchemaPolicy
|
|
}
|
|
|
|
func defaultAllocatePolicy() AllocatePolicy {
|
|
return AllocatePolicyL1
|
|
}
|
|
|
|
func defaultSegmentSealPolicy() []SegmentSealPolicy {
|
|
return []SegmentSealPolicy{
|
|
sealL1SegmentByBinlogFileNumber(Params.DataCoordCfg.SegmentMaxBinlogFileNumber.GetAsInt()),
|
|
sealL1SegmentByLifetime(),
|
|
sealL1SegmentByCapacity(Params.DataCoordCfg.SegmentSealProportion.GetAsFloat()),
|
|
sealL1SegmentByIdleTime(Params.DataCoordCfg.SegmentMaxIdleTime.GetAsDuration(time.Second), Params.DataCoordCfg.SegmentMinSizeFromIdleToSealed.GetAsFloat(), Params.DataCoordCfg.SegmentMaxSize.GetAsFloat()),
|
|
}
|
|
}
|
|
|
|
func defaultChannelSealPolicy(meta *meta) []channelSealPolicy {
|
|
return []channelSealPolicy{
|
|
sealByTotalGrowingSegmentsSize(),
|
|
sealByBlockingL0(meta),
|
|
}
|
|
}
|
|
|
|
func defaultFlushPolicy() flushPolicy {
|
|
return flushPolicyL1
|
|
}
|
|
|
|
// newSegmentManager should be the only way to retrieve SegmentManager.
|
|
func newSegmentManager(meta *meta, allocator allocator.Allocator, opts ...allocOption) (*SegmentManager, error) {
|
|
manager := &SegmentManager{
|
|
meta: meta,
|
|
allocator: allocator,
|
|
helper: defaultAllocHelper(),
|
|
channelLock: lock.NewKeyLock[string](),
|
|
channel2Growing: typeutil.NewConcurrentMap[string, typeutil.UniqueSet](),
|
|
channel2Sealed: typeutil.NewConcurrentMap[string, typeutil.UniqueSet](),
|
|
estimatePolicy: defaultCalUpperLimitPolicy(),
|
|
allocPolicy: defaultAllocatePolicy(),
|
|
segmentSealPolicies: defaultSegmentSealPolicy(),
|
|
channelSealPolicies: defaultChannelSealPolicy(meta),
|
|
flushPolicy: defaultFlushPolicy(),
|
|
}
|
|
for _, opt := range opts {
|
|
opt.apply(manager)
|
|
}
|
|
latestTs, err := manager.genLastExpireTsForSegments()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
manager.loadSegmentsFromMeta(latestTs)
|
|
return manager, nil
|
|
}
|
|
|
|
// loadSegmentsFromMeta generate corresponding segment status for each segment from meta
|
|
func (s *SegmentManager) loadSegmentsFromMeta(latestTs Timestamp) {
|
|
unflushed := s.meta.GetUnFlushedSegments()
|
|
unflushed = lo.Filter(unflushed, func(segment *SegmentInfo, _ int) bool {
|
|
return segment.Level != datapb.SegmentLevel_L0
|
|
})
|
|
channel2Segments := lo.GroupBy(unflushed, func(segment *SegmentInfo) string {
|
|
return segment.GetInsertChannel()
|
|
})
|
|
for channel, segmentInfos := range channel2Segments {
|
|
growing := typeutil.NewUniqueSet()
|
|
sealed := typeutil.NewUniqueSet()
|
|
for _, segment := range segmentInfos {
|
|
// for all sealed and growing segments, need to reset last expire
|
|
if segment != nil && segment.GetState() == commonpb.SegmentState_Growing {
|
|
s.meta.SetLastExpire(segment.GetID(), latestTs)
|
|
growing.Insert(segment.GetID())
|
|
}
|
|
if segment != nil && segment.GetState() == commonpb.SegmentState_Sealed {
|
|
sealed.Insert(segment.GetID())
|
|
}
|
|
}
|
|
s.channel2Growing.Insert(channel, growing)
|
|
s.channel2Sealed.Insert(channel, sealed)
|
|
}
|
|
}
|
|
|
|
func (s *SegmentManager) genLastExpireTsForSegments() (Timestamp, error) {
|
|
var latestTs uint64
|
|
allocateErr := retry.Do(context.Background(), func() error {
|
|
ts, tryErr := s.genExpireTs(context.Background())
|
|
if tryErr != nil {
|
|
mlog.Warn(context.TODO(), "failed to get ts from rootCoord for globalLastExpire", mlog.Err(tryErr))
|
|
return tryErr
|
|
}
|
|
latestTs = ts
|
|
return nil
|
|
}, retry.Attempts(Params.DataCoordCfg.AllocLatestExpireAttempt.GetAsUint()), retry.Sleep(200*time.Millisecond))
|
|
if allocateErr != nil {
|
|
mlog.Warn(context.TODO(), "cannot allocate latest lastExpire from rootCoord", mlog.Err(allocateErr))
|
|
return 0, merr.WrapErrServiceInternalMsg("global max expire ts is unavailable for segment manager")
|
|
}
|
|
return latestTs, nil
|
|
}
|
|
|
|
// AllocSegment allocate segment per request collcation, partication, channel and rows
|
|
func (s *SegmentManager) AllocSegment(ctx context.Context, collectionID UniqueID,
|
|
partitionID UniqueID, channelName string, requestRows int64, storageVersion int64,
|
|
) ([]*Allocation, error) {
|
|
_, sp := otel.Tracer(typeutil.DataCoordRole).Start(ctx, "Alloc-Segment")
|
|
defer sp.End()
|
|
|
|
s.channelLock.Lock(channelName)
|
|
defer s.channelLock.Unlock(channelName)
|
|
|
|
// filter segments
|
|
segmentInfos := make([]*SegmentInfo, 0)
|
|
growing, _ := s.channel2Growing.Get(channelName)
|
|
growing.Range(func(segmentID int64) bool {
|
|
segment := s.meta.GetHealthySegment(ctx, segmentID)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "failed to get segment, remove it", mlog.String("channel", channelName), mlog.Int64("segmentID", segmentID))
|
|
growing.Remove(segmentID)
|
|
return true
|
|
}
|
|
if segment.GetPartitionID() != partitionID {
|
|
return true
|
|
}
|
|
segmentInfos = append(segmentInfos, segment)
|
|
return true
|
|
})
|
|
|
|
// Apply allocation policy.
|
|
maxCountPerSegment, err := s.estimateMaxNumOfRows(collectionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newSegmentAllocations, existedSegmentAllocations := s.allocPolicy(segmentInfos,
|
|
requestRows, int64(maxCountPerSegment), datapb.SegmentLevel_L1)
|
|
|
|
// create new segments and add allocations
|
|
expireTs, err := s.genExpireTs(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, allocation := range newSegmentAllocations {
|
|
segment, err := s.openNewSegment(ctx, collectionID, partitionID, channelName, storageVersion)
|
|
if err != nil {
|
|
mlog.Error(context.TODO(), "Failed to open new segment for segment allocation")
|
|
return nil, err
|
|
}
|
|
allocation.ExpireTime = expireTs
|
|
allocation.SegmentID = segment.GetID()
|
|
if err := s.meta.AddAllocation(segment.GetID(), allocation); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for _, allocation := range existedSegmentAllocations {
|
|
allocation.ExpireTime = expireTs
|
|
if err := s.meta.AddAllocation(allocation.SegmentID, allocation); err != nil {
|
|
mlog.Error(context.TODO(), "Failed to add allocation to existed segment", mlog.Int64("segmentID", allocation.SegmentID))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
allocations := append(newSegmentAllocations, existedSegmentAllocations...)
|
|
return allocations, nil
|
|
}
|
|
|
|
func (s *SegmentManager) genExpireTs(ctx context.Context) (Timestamp, error) {
|
|
ts, err := s.allocator.AllocTimestamp(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return tsoutil.AddPhysicalDurationOnTs(
|
|
ts,
|
|
Params.DataCoordCfg.SegAssignmentExpiration.GetAsDuration(time.Millisecond),
|
|
), nil
|
|
}
|
|
|
|
// AllocNewGrowingSegment allocates segment for streaming node.
|
|
func (s *SegmentManager) AllocNewGrowingSegment(ctx context.Context, req AllocNewGrowingSegmentRequest) (*SegmentInfo, error) {
|
|
s.channelLock.Lock(req.ChannelName)
|
|
defer s.channelLock.Unlock(req.ChannelName)
|
|
return s.openNewSegmentWithGivenSegmentID(ctx, req)
|
|
}
|
|
|
|
func (s *SegmentManager) openNewSegment(ctx context.Context, collectionID UniqueID, partitionID UniqueID, channelName string, storageVersion int64) (*SegmentInfo, error) {
|
|
ctx, sp := otel.Tracer(typeutil.DataCoordRole).Start(ctx, "open-Segment")
|
|
defer sp.End()
|
|
id, err := s.allocator.AllocID(ctx)
|
|
if err != nil {
|
|
mlog.Error(context.TODO(), "failed to open new segment while AllocID", mlog.Err(err))
|
|
return nil, err
|
|
}
|
|
return s.openNewSegmentWithGivenSegmentID(ctx, AllocNewGrowingSegmentRequest{
|
|
CollectionID: collectionID,
|
|
PartitionID: partitionID,
|
|
SegmentID: id,
|
|
ChannelName: channelName,
|
|
StorageVersion: storageVersion,
|
|
IsCreatedByStreaming: false,
|
|
})
|
|
}
|
|
|
|
func (s *SegmentManager) openNewSegmentWithGivenSegmentID(ctx context.Context, req AllocNewGrowingSegmentRequest) (*SegmentInfo, error) {
|
|
var maxNumOfRows int
|
|
if !req.IsCreatedByStreaming {
|
|
var err error
|
|
maxNumOfRows, err = s.estimateMaxNumOfRows(req.CollectionID)
|
|
if err != nil {
|
|
mlog.Error(context.TODO(), "failed to open new segment while estimateMaxNumOfRows", mlog.Err(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var manifestPath string
|
|
if req.StorageVersion == storage.StorageV3 {
|
|
k := metautil.JoinIDPath(req.CollectionID, req.PartitionID, req.SegmentID)
|
|
basePath := path.Join(paramtable.Get().MinioCfg.RootPath.GetValue(), common.SegmentInsertLogPath, k)
|
|
manifestPath = packed.MarshalManifestPath(basePath, packed.ManifestEarliest)
|
|
}
|
|
|
|
segmentInfo := &datapb.SegmentInfo{
|
|
ID: req.SegmentID,
|
|
CollectionID: req.CollectionID,
|
|
PartitionID: req.PartitionID,
|
|
InsertChannel: req.ChannelName,
|
|
NumOfRows: 0,
|
|
State: commonpb.SegmentState_Growing,
|
|
MaxRowNum: int64(maxNumOfRows), // deprecated properties, we use binary size to limit the segment size but not estimate rows.
|
|
Level: datapb.SegmentLevel_L1,
|
|
LastExpireTime: 0,
|
|
StorageVersion: req.StorageVersion,
|
|
IsCreatedByStreaming: req.IsCreatedByStreaming,
|
|
ManifestPath: manifestPath,
|
|
SchemaVersion: req.SchemaVersion,
|
|
}
|
|
segment := NewSegmentInfo(segmentInfo)
|
|
if err := s.meta.AddSegment(ctx, segment); err != nil {
|
|
mlog.Error(context.TODO(), "failed to add segment to DataCoord", mlog.Err(err))
|
|
return nil, err
|
|
}
|
|
growing, _ := s.channel2Growing.GetOrInsert(req.ChannelName, typeutil.NewUniqueSet())
|
|
growing.Insert(req.SegmentID)
|
|
mlog.Info(context.TODO(), "datacoord: estimateTotalRows: ",
|
|
mlog.Int64("CollectionID", segmentInfo.CollectionID),
|
|
mlog.Int64("SegmentID", segmentInfo.ID),
|
|
mlog.String("Channel", segmentInfo.InsertChannel),
|
|
mlog.Bool("IsCreatedByStreaming", segmentInfo.IsCreatedByStreaming),
|
|
mlog.Int32("SchemaVersion", segmentInfo.SchemaVersion),
|
|
)
|
|
|
|
return segment, s.helper.afterCreateSegment(segmentInfo)
|
|
}
|
|
|
|
func (s *SegmentManager) estimateMaxNumOfRows(collectionID UniqueID) (int, error) {
|
|
// it's ok to use meta.GetCollection here, since collection meta is set before using segmentManager
|
|
collMeta := s.meta.GetCollection(collectionID)
|
|
if collMeta == nil {
|
|
return -1, merr.WrapErrServiceInternalMsg("failed to get collection %d", collectionID)
|
|
}
|
|
return s.estimatePolicy(collMeta.Schema)
|
|
}
|
|
|
|
// DropSegment drop the segment from manager.
|
|
func (s *SegmentManager) DropSegment(ctx context.Context, channel string, segmentID UniqueID) {
|
|
_, sp := otel.Tracer(typeutil.DataCoordRole).Start(ctx, "Drop-Segment")
|
|
defer sp.End()
|
|
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
if growing, ok := s.channel2Growing.Get(channel); ok {
|
|
growing.Remove(segmentID)
|
|
}
|
|
if sealed, ok := s.channel2Sealed.Get(channel); ok {
|
|
sealed.Remove(segmentID)
|
|
}
|
|
|
|
segment := s.meta.GetHealthySegment(ctx, segmentID)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "Failed to get segment", mlog.Int64("id", segmentID))
|
|
return
|
|
}
|
|
s.meta.SetAllocations(segmentID, []*Allocation{})
|
|
for _, allocation := range segment.allocations {
|
|
putAllocation(allocation)
|
|
}
|
|
}
|
|
|
|
// SealAllSegments seals all segments of collection with collectionID and return sealed segments
|
|
func (s *SegmentManager) SealAllSegments(ctx context.Context, channel string, segIDs []UniqueID) ([]UniqueID, error) {
|
|
_, sp := otel.Tracer(typeutil.DataCoordRole).Start(ctx, "Seal-Segments")
|
|
defer sp.End()
|
|
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
sealed, _ := s.channel2Sealed.GetOrInsert(channel, typeutil.NewUniqueSet())
|
|
growing, _ := s.channel2Growing.Get(channel)
|
|
|
|
var (
|
|
sealedSegments []int64
|
|
growingSegments []int64
|
|
)
|
|
|
|
if len(segIDs) != 0 {
|
|
sealedSegments = s.meta.GetSegments(segIDs, func(segment *SegmentInfo) bool {
|
|
return isSegmentHealthy(segment) && segment.State == commonpb.SegmentState_Sealed
|
|
})
|
|
growingSegments = s.meta.GetSegments(segIDs, func(segment *SegmentInfo) bool {
|
|
return isSegmentHealthy(segment) && segment.State == commonpb.SegmentState_Growing
|
|
})
|
|
} else {
|
|
sealedSegments = s.meta.GetSegments(sealed.Collect(), isSegmentHealthy)
|
|
growingSegments = s.meta.GetSegments(growing.Collect(), isSegmentHealthy)
|
|
}
|
|
|
|
var ret []UniqueID
|
|
ret = append(ret, sealedSegments...)
|
|
|
|
for _, id := range growingSegments {
|
|
if err := s.meta.SetState(ctx, id, commonpb.SegmentState_Sealed); err != nil {
|
|
return nil, err
|
|
}
|
|
sealed.Insert(id)
|
|
growing.Remove(id)
|
|
ret = append(ret, id)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// GetFlushableSegments get segment ids with Sealed State and flushable (meets flushPolicy)
|
|
func (s *SegmentManager) GetFlushableSegments(ctx context.Context, channel string, t Timestamp) ([]UniqueID, error) {
|
|
_, sp := otel.Tracer(typeutil.DataCoordRole).Start(ctx, "Get-Segments")
|
|
defer sp.End()
|
|
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
// TODO:move tryToSealSegment and dropEmptySealedSegment outside
|
|
if err := s.tryToSealSegment(ctx, t, channel); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sealed, ok := s.channel2Sealed.Get(channel)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
ret := make([]UniqueID, 0, sealed.Len())
|
|
sealed.Range(func(segmentID int64) bool {
|
|
info := s.meta.GetHealthySegment(ctx, segmentID)
|
|
if info == nil {
|
|
return true
|
|
}
|
|
if s.flushPolicy(info, t) {
|
|
ret = append(ret, segmentID)
|
|
}
|
|
return true
|
|
})
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// ExpireAllocations notify segment status to expire old allocations
|
|
func (s *SegmentManager) ExpireAllocations(ctx context.Context, channel string, ts Timestamp) {
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
growing, ok := s.channel2Growing.Get(channel)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
growing.Range(func(id int64) bool {
|
|
segment := s.meta.GetHealthySegment(ctx, id)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "failed to get segment, remove it", mlog.String("channel", channel), mlog.Int64("segmentID", id))
|
|
growing.Remove(id)
|
|
return true
|
|
}
|
|
allocations := make([]*Allocation, 0, len(segment.allocations))
|
|
for i := 0; i < len(segment.allocations); i++ {
|
|
if segment.allocations[i].ExpireTime <= ts {
|
|
a := segment.allocations[i]
|
|
putAllocation(a)
|
|
} else {
|
|
allocations = append(allocations, segment.allocations[i])
|
|
}
|
|
}
|
|
s.meta.SetAllocations(segment.GetID(), allocations)
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (s *SegmentManager) CleanZeroSealedSegmentsOfChannel(ctx context.Context, channel string, cpTs Timestamp) {
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
sealed, ok := s.channel2Sealed.Get(channel)
|
|
if !ok {
|
|
mlog.Info(context.TODO(), "try remove empty sealed segment after channel cp updated failed to get channel", mlog.String("channel", channel))
|
|
return
|
|
}
|
|
sealed.Range(func(id int64) bool {
|
|
segment := s.meta.GetHealthySegment(ctx, id)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "try remove empty sealed segment, failed to get segment, remove it in channel2Sealed", mlog.String("channel", channel), mlog.Int64("segmentID", id))
|
|
sealed.Remove(id)
|
|
return true
|
|
}
|
|
// Check if segment is empty
|
|
if segment.GetLastExpireTime() > 0 && segment.GetLastExpireTime() < cpTs && segment.GetNumOfRows() == 0 {
|
|
mlog.Info(context.TODO(), "try remove empty sealed segment after channel cp updated",
|
|
mlog.Int64("collection", segment.CollectionID), mlog.Int64("segment", id),
|
|
mlog.String("channel", channel), mlog.Any("cpTs", cpTs))
|
|
if err := s.meta.SetState(ctx, id, commonpb.SegmentState_Dropped); err != nil {
|
|
mlog.Warn(context.TODO(), "try remove empty sealed segment after channel cp updated, failed to set segment state to dropped", mlog.String("channel", channel),
|
|
mlog.Int64("segmentID", id), mlog.Err(err))
|
|
} else {
|
|
sealed.Remove(id)
|
|
mlog.Info(context.TODO(), "succeed to remove empty sealed segment",
|
|
mlog.Int64("collection", segment.CollectionID), mlog.Int64("segment", id),
|
|
mlog.String("channel", channel), mlog.Any("cpTs", cpTs), mlog.Any("expireTs", segment.GetLastExpireTime()))
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
// tryToSealSegment applies segment & channel seal policies
|
|
func (s *SegmentManager) tryToSealSegment(ctx context.Context, ts Timestamp, channel string) error {
|
|
growing, ok := s.channel2Growing.Get(channel)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
sealed, _ := s.channel2Sealed.GetOrInsert(channel, typeutil.NewUniqueSet())
|
|
|
|
channelSegmentInfos := make([]*SegmentInfo, 0, len(growing))
|
|
sealedSegments := make(map[int64]struct{})
|
|
|
|
var setStateErr error
|
|
growing.Range(func(id int64) bool {
|
|
info := s.meta.GetHealthySegment(ctx, id)
|
|
if info == nil {
|
|
return true
|
|
}
|
|
channelSegmentInfos = append(channelSegmentInfos, info)
|
|
// change shouldSeal to segment seal policy logic
|
|
for _, policy := range s.segmentSealPolicies {
|
|
if shouldSeal, reason := policy.ShouldSeal(info, ts); shouldSeal {
|
|
mlog.Info(context.TODO(), "Seal Segment for policy matched", mlog.Int64("segmentID", info.GetID()), mlog.String("reason", reason))
|
|
if err := s.meta.SetState(ctx, id, commonpb.SegmentState_Sealed); err != nil {
|
|
setStateErr = err
|
|
return false
|
|
}
|
|
sealedSegments[id] = struct{}{}
|
|
sealed.Insert(id)
|
|
growing.Remove(id)
|
|
break
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
if setStateErr != nil {
|
|
return setStateErr
|
|
}
|
|
|
|
for _, policy := range s.channelSealPolicies {
|
|
vs, reason := policy(channel, channelSegmentInfos, ts)
|
|
for _, info := range vs {
|
|
if _, ok := sealedSegments[info.GetID()]; ok {
|
|
continue
|
|
}
|
|
if err := s.meta.SetState(ctx, info.GetID(), commonpb.SegmentState_Sealed); err != nil {
|
|
return err
|
|
}
|
|
mlog.Info(context.TODO(), "seal segment for channel seal policy matched",
|
|
mlog.Int64("segmentID", info.GetID()), mlog.String("channel", channel), mlog.String("reason", reason))
|
|
sealedSegments[info.GetID()] = struct{}{}
|
|
sealed.Insert(info.GetID())
|
|
growing.Remove(info.GetID())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DropSegmentsOfChannel drops all segments in a channel
|
|
func (s *SegmentManager) DropSegmentsOfChannel(ctx context.Context, channel string) {
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
|
|
s.channel2Sealed.Remove(channel)
|
|
growing, ok := s.channel2Growing.Get(channel)
|
|
if !ok {
|
|
return
|
|
}
|
|
growing.Range(func(sid int64) bool {
|
|
segment := s.meta.GetHealthySegment(ctx, sid)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "failed to get segment, remove it", mlog.String("channel", channel), mlog.Int64("segmentID", sid))
|
|
growing.Remove(sid)
|
|
return true
|
|
}
|
|
s.meta.SetAllocations(sid, nil)
|
|
for _, allocation := range segment.allocations {
|
|
putAllocation(allocation)
|
|
}
|
|
return true
|
|
})
|
|
s.channel2Growing.Remove(channel)
|
|
}
|
|
|
|
func (s *SegmentManager) DropSegmentsOfPartition(ctx context.Context, channel string, partitionIDs []int64) {
|
|
s.channelLock.Lock(channel)
|
|
defer s.channelLock.Unlock(channel)
|
|
if growing, ok := s.channel2Growing.Get(channel); ok {
|
|
for sid := range growing {
|
|
segment := s.meta.GetHealthySegment(ctx, sid)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "failed to get segment, remove it",
|
|
mlog.String("channel", channel),
|
|
mlog.Int64("segmentID", sid))
|
|
growing.Remove(sid)
|
|
continue
|
|
}
|
|
|
|
if contains(partitionIDs, segment.GetPartitionID()) {
|
|
growing.Remove(sid)
|
|
}
|
|
s.meta.SetAllocations(sid, nil)
|
|
for _, allocation := range segment.allocations {
|
|
putAllocation(allocation)
|
|
}
|
|
}
|
|
}
|
|
|
|
if sealed, ok := s.channel2Sealed.Get(channel); ok {
|
|
for sid := range sealed {
|
|
segment := s.meta.GetHealthySegment(ctx, sid)
|
|
if segment == nil {
|
|
mlog.Warn(context.TODO(), "failed to get segment, remove it",
|
|
mlog.String("channel", channel),
|
|
mlog.Int64("segmentID", sid))
|
|
sealed.Remove(sid)
|
|
continue
|
|
}
|
|
if contains(partitionIDs, segment.GetPartitionID()) {
|
|
sealed.Remove(sid)
|
|
}
|
|
s.meta.SetAllocations(sid, nil)
|
|
for _, allocation := range segment.allocations {
|
|
putAllocation(allocation)
|
|
}
|
|
}
|
|
}
|
|
}
|