github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/index.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package db
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"os"
    18  	"path"
    19  	"runtime"
    20  	"runtime/debug"
    21  	golangSort "sort"
    22  	"strings"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  
    29  	"github.com/go-openapi/strfmt"
    30  	"github.com/google/uuid"
    31  	"github.com/sirupsen/logrus"
    32  	"github.com/weaviate/weaviate/adapters/repos/db/aggregator"
    33  	"github.com/weaviate/weaviate/adapters/repos/db/helpers"
    34  	"github.com/weaviate/weaviate/adapters/repos/db/indexcheckpoint"
    35  	"github.com/weaviate/weaviate/adapters/repos/db/inverted"
    36  	"github.com/weaviate/weaviate/adapters/repos/db/inverted/stopwords"
    37  	"github.com/weaviate/weaviate/adapters/repos/db/sorter"
    38  	"github.com/weaviate/weaviate/adapters/repos/db/vector/hnsw"
    39  	"github.com/weaviate/weaviate/entities/additional"
    40  	"github.com/weaviate/weaviate/entities/aggregation"
    41  	"github.com/weaviate/weaviate/entities/autocut"
    42  	enterrors "github.com/weaviate/weaviate/entities/errors"
    43  	"github.com/weaviate/weaviate/entities/filters"
    44  	"github.com/weaviate/weaviate/entities/models"
    45  	"github.com/weaviate/weaviate/entities/multi"
    46  	"github.com/weaviate/weaviate/entities/schema"
    47  	"github.com/weaviate/weaviate/entities/search"
    48  	"github.com/weaviate/weaviate/entities/searchparams"
    49  	"github.com/weaviate/weaviate/entities/storobj"
    50  	"github.com/weaviate/weaviate/usecases/config"
    51  	"github.com/weaviate/weaviate/usecases/monitoring"
    52  	"github.com/weaviate/weaviate/usecases/objects"
    53  	"github.com/weaviate/weaviate/usecases/replica"
    54  	schemaUC "github.com/weaviate/weaviate/usecases/schema"
    55  	"github.com/weaviate/weaviate/usecases/sharding"
    56  )
    57  
    58  var (
    59  	errTenantNotFound  = errors.New("tenant not found")
    60  	errTenantNotActive = errors.New("tenant not active")
    61  
    62  	// Use runtime.GOMAXPROCS instead of runtime.NumCPU because NumCPU returns
    63  	// the physical CPU cores. However, in a containerization context, that might
    64  	// not be what we want. The physical node could have 128 cores, but we could
    65  	// be cgroup-limited to 2 cores. In that case, we want 2 to be our limit, not
    66  	// 128. It isn't guaranteed that MAXPROCS reflects the cgroup limit, but at
    67  	// least there is a chance that it was set correctly. If not, it defaults to
    68  	// NumCPU anyway, so we're not any worse off.
    69  	_NUMCPU          = runtime.GOMAXPROCS(0)
    70  	errShardNotFound = errors.New("shard not found")
    71  )
    72  
    73  // shardMap is a syn.Map which specialized in storing shards
    74  type shardMap sync.Map
    75  
    76  // Range calls f sequentially for each key and value present in the map.
    77  // If f returns an error, range stops the iteration
    78  func (m *shardMap) Range(f func(name string, shard ShardLike) error) (err error) {
    79  	(*sync.Map)(m).Range(func(key, value any) bool {
    80  		err = f(key.(string), value.(ShardLike))
    81  		return err == nil
    82  	})
    83  	return err
    84  }
    85  
    86  // RangeConcurrently calls f for each key and value present in the map with at
    87  // most _NUMCPU executors running in parallel. As opposed to [Range] it does
    88  // not guarantee an exit on the first error.
    89  func (m *shardMap) RangeConcurrently(logger logrus.FieldLogger, f func(name string, shard ShardLike) error) (err error) {
    90  	eg := enterrors.NewErrorGroupWrapper(logger)
    91  	eg.SetLimit(_NUMCPU)
    92  	(*sync.Map)(m).Range(func(key, value any) bool {
    93  		name, shard := key.(string), value.(ShardLike)
    94  		eg.Go(func() error {
    95  			return f(name, shard)
    96  		}, name, shard)
    97  		return true
    98  	})
    99  
   100  	return eg.Wait()
   101  }
   102  
   103  // Load returns the shard or nil if no shard is present.
   104  func (m *shardMap) Load(name string) ShardLike {
   105  	v, ok := (*sync.Map)(m).Load(name)
   106  	if !ok {
   107  		return nil
   108  	}
   109  	return v.(ShardLike)
   110  }
   111  
   112  // Store sets a shard giving its name and value
   113  func (m *shardMap) Store(name string, shard ShardLike) {
   114  	(*sync.Map)(m).Store(name, shard)
   115  }
   116  
   117  // Swap swaps the shard for a key and returns the previous value if any.
   118  // The loaded result reports whether the key was present.
   119  func (m *shardMap) Swap(name string, shard ShardLike) (previous ShardLike, loaded bool) {
   120  	v, ok := (*sync.Map)(m).Swap(name, shard)
   121  	if v == nil || !ok {
   122  		return nil, ok
   123  	}
   124  	return v.(ShardLike), ok
   125  }
   126  
   127  // CompareAndSwap swaps the old and new values for key if the value stored in the map is equal to old.
   128  func (m *shardMap) CompareAndSwap(name string, old, new ShardLike) bool {
   129  	return (*sync.Map)(m).CompareAndSwap(name, old, new)
   130  }
   131  
   132  // LoadAndDelete deletes the value for a key, returning the previous value if any.
   133  // The loaded result reports whether the key was present.
   134  func (m *shardMap) LoadAndDelete(name string) (ShardLike, bool) {
   135  	v, ok := (*sync.Map)(m).LoadAndDelete(name)
   136  	if v == nil || !ok {
   137  		return nil, ok
   138  	}
   139  	return v.(ShardLike), ok
   140  }
   141  
   142  // Index is the logical unit which contains all the data for one particular
   143  // class. An index can be further broken up into self-contained units, called
   144  // Shards, to allow for easy distribution across Nodes
   145  type Index struct {
   146  	classSearcher             inverted.ClassSearcher // to allow for nested by-references searches
   147  	shards                    shardMap
   148  	Config                    IndexConfig
   149  	vectorIndexUserConfig     schema.VectorIndexConfig
   150  	vectorIndexUserConfigLock sync.Mutex
   151  	vectorIndexUserConfigs    map[string]schema.VectorIndexConfig
   152  	getSchema                 schemaUC.SchemaGetter
   153  	logger                    logrus.FieldLogger
   154  	remote                    *sharding.RemoteIndex
   155  	stopwords                 *stopwords.Detector
   156  	replicator                *replica.Replicator
   157  
   158  	invertedIndexConfig     schema.InvertedIndexConfig
   159  	invertedIndexConfigLock sync.Mutex
   160  
   161  	// This lock should be used together with the db indexLock.
   162  	//
   163  	// The db indexlock locks the map that contains all indices against changes and should be used while iterating.
   164  	// This lock protects this specific index form being deleted while in use. Use Rlock to signal that it is in use.
   165  	// This way many goroutines can use a specific index in parallel. The delete-routine will try to acquire a RWlock.
   166  	//
   167  	// Usage:
   168  	// Lock the whole db using db.indexLock
   169  	// pick the indices you want and Rlock them
   170  	// unlock db.indexLock
   171  	// Use the indices
   172  	// RUnlock all picked indices
   173  	dropIndex sync.RWMutex
   174  
   175  	metrics          *Metrics
   176  	centralJobQueue  chan job
   177  	indexCheckpoints *indexcheckpoint.Checkpoints
   178  
   179  	partitioningEnabled bool
   180  
   181  	cycleCallbacks *indexCycleCallbacks
   182  
   183  	backupMutex backupMutex
   184  	lastBackup  atomic.Pointer[BackupState]
   185  
   186  	// canceled when either Shutdown or Drop called
   187  	closingCtx    context.Context
   188  	closingCancel context.CancelFunc
   189  
   190  	// always true if lazy shard loading is off, in the case of lazy shard
   191  	// loading will be set to true once the last shard was loaded.
   192  	allShardsReady atomic.Bool
   193  }
   194  
   195  func (i *Index) GetShards() []ShardLike {
   196  	var out []ShardLike
   197  	i.shards.Range(func(_ string, shard ShardLike) error {
   198  		out = append(out, shard)
   199  		return nil
   200  	})
   201  
   202  	return out
   203  }
   204  
   205  func (i *Index) ID() string {
   206  	return indexID(i.Config.ClassName)
   207  }
   208  
   209  func (i *Index) path() string {
   210  	return path.Join(i.Config.RootPath, i.ID())
   211  }
   212  
   213  type nodeResolver interface {
   214  	NodeHostname(nodeName string) (string, bool)
   215  }
   216  
   217  // NewIndex creates an index with the specified amount of shards, using only
   218  // the shards that are local to a node
   219  func NewIndex(ctx context.Context, cfg IndexConfig,
   220  	shardState *sharding.State, invertedIndexConfig schema.InvertedIndexConfig,
   221  	vectorIndexUserConfig schema.VectorIndexConfig,
   222  	vectorIndexUserConfigs map[string]schema.VectorIndexConfig,
   223  	sg schemaUC.SchemaGetter,
   224  	cs inverted.ClassSearcher, logger logrus.FieldLogger,
   225  	nodeResolver nodeResolver, remoteClient sharding.RemoteIndexClient,
   226  	replicaClient replica.Client,
   227  	promMetrics *monitoring.PrometheusMetrics, class *models.Class, jobQueueCh chan job,
   228  	indexCheckpoints *indexcheckpoint.Checkpoints,
   229  ) (*Index, error) {
   230  	sd, err := stopwords.NewDetectorFromConfig(invertedIndexConfig.Stopwords)
   231  	if err != nil {
   232  		return nil, errors.Wrap(err, "failed to create new index")
   233  	}
   234  
   235  	repl := replica.NewReplicator(cfg.ClassName.String(),
   236  		sg, nodeResolver, replicaClient, logger)
   237  
   238  	if cfg.QueryNestedRefLimit == 0 {
   239  		cfg.QueryNestedRefLimit = config.DefaultQueryNestedCrossReferenceLimit
   240  	}
   241  
   242  	index := &Index{
   243  		Config:                 cfg,
   244  		getSchema:              sg,
   245  		logger:                 logger,
   246  		classSearcher:          cs,
   247  		vectorIndexUserConfig:  vectorIndexUserConfig,
   248  		vectorIndexUserConfigs: vectorIndexUserConfigs,
   249  		invertedIndexConfig:    invertedIndexConfig,
   250  		stopwords:              sd,
   251  		replicator:             repl,
   252  		remote: sharding.NewRemoteIndex(cfg.ClassName.String(), sg,
   253  			nodeResolver, remoteClient),
   254  		metrics:             NewMetrics(logger, promMetrics, cfg.ClassName.String(), "n/a"),
   255  		centralJobQueue:     jobQueueCh,
   256  		partitioningEnabled: shardState.PartitioningEnabled,
   257  		backupMutex:         backupMutex{log: logger, retryDuration: mutexRetryDuration, notifyDuration: mutexNotifyDuration},
   258  		indexCheckpoints:    indexCheckpoints,
   259  	}
   260  	index.closingCtx, index.closingCancel = context.WithCancel(context.Background())
   261  
   262  	index.initCycleCallbacks()
   263  
   264  	if err := index.checkSingleShardMigration(shardState); err != nil {
   265  		return nil, errors.Wrap(err, "migrating sharding state from previous version")
   266  	}
   267  
   268  	if err := os.MkdirAll(index.path(), os.ModePerm); err != nil {
   269  		return nil, fmt.Errorf("init index %q: %w", index.ID(), err)
   270  	}
   271  
   272  	if err := index.initAndStoreShards(ctx, shardState, class, promMetrics); err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	index.cycleCallbacks.compactionCycle.Start()
   277  	index.cycleCallbacks.flushCycle.Start()
   278  
   279  	return index, nil
   280  }
   281  
   282  func (i *Index) initAndStoreShards(ctx context.Context, shardState *sharding.State, class *models.Class,
   283  	promMetrics *monitoring.PrometheusMetrics,
   284  ) error {
   285  	if i.Config.DisableLazyLoadShards {
   286  
   287  		eg := enterrors.NewErrorGroupWrapper(i.logger)
   288  		eg.SetLimit(_NUMCPU)
   289  
   290  		for _, shardName := range shardState.AllLocalPhysicalShards() {
   291  			physical := shardState.Physical[shardName]
   292  			if physical.ActivityStatus() != models.TenantActivityStatusHOT {
   293  				// do not instantiate inactive shard
   294  				continue
   295  			}
   296  
   297  			shardName := shardName // prevent loop variable capture
   298  			eg.Go(func() error {
   299  				shard, err := NewShard(ctx, promMetrics, shardName, i, class, i.centralJobQueue, i.indexCheckpoints)
   300  				if err != nil {
   301  					return fmt.Errorf("init shard %s of index %s: %w", shardName, i.ID(), err)
   302  				}
   303  
   304  				i.shards.Store(shardName, shard)
   305  				return nil
   306  			}, shardName)
   307  		}
   308  
   309  		if err := eg.Wait(); err != nil {
   310  			return err
   311  		}
   312  
   313  		i.allShardsReady.Store(true)
   314  		return nil
   315  	}
   316  
   317  	for _, shardName := range shardState.AllLocalPhysicalShards() {
   318  		physical := shardState.Physical[shardName]
   319  		if physical.ActivityStatus() != models.TenantActivityStatusHOT {
   320  			// do not instantiate inactive shard
   321  			continue
   322  		}
   323  
   324  		shard := NewLazyLoadShard(ctx, promMetrics, shardName, i, class, i.centralJobQueue, i.indexCheckpoints)
   325  		i.shards.Store(shardName, shard)
   326  	}
   327  
   328  	f := func() {
   329  		ticker := time.NewTicker(time.Second)
   330  		defer ticker.Stop()
   331  		defer i.allShardsReady.Store(true)
   332  		i.ForEachShard(func(name string, shard ShardLike) error {
   333  			// prioritize closingCtx over ticker:
   334  			// check closing again in case of ticker was selected when both
   335  			// cases where available
   336  			select {
   337  			case <-i.closingCtx.Done():
   338  				// break loop by returning error
   339  				return i.closingCtx.Err()
   340  			case <-ticker.C:
   341  				select {
   342  				case <-i.closingCtx.Done():
   343  					// break loop by returning error
   344  					return i.closingCtx.Err()
   345  				default:
   346  					shard.(*LazyLoadShard).Load(context.Background())
   347  					return nil
   348  				}
   349  			}
   350  		})
   351  	}
   352  	enterrors.GoWrapper(f, i.logger)
   353  
   354  	return nil
   355  }
   356  
   357  func (i *Index) initAndStoreShard(ctx context.Context, shardName string, class *models.Class,
   358  	promMetrics *monitoring.PrometheusMetrics,
   359  ) error {
   360  	shard, err := i.initShard(ctx, shardName, class, promMetrics)
   361  	if err != nil {
   362  		return err
   363  	}
   364  	i.shards.Store(shardName, shard)
   365  	return nil
   366  }
   367  
   368  func (i *Index) initShard(ctx context.Context, shardName string, class *models.Class,
   369  	promMetrics *monitoring.PrometheusMetrics,
   370  ) (ShardLike, error) {
   371  	if i.Config.DisableLazyLoadShards {
   372  		shard, err := NewShard(ctx, promMetrics, shardName, i, class, i.centralJobQueue, i.indexCheckpoints)
   373  		if err != nil {
   374  			return nil, fmt.Errorf("init shard %s of index %s: %w", shardName, i.ID(), err)
   375  		}
   376  		return shard, nil
   377  	}
   378  
   379  	shard := NewLazyLoadShard(ctx, promMetrics, shardName, i, class, i.centralJobQueue, i.indexCheckpoints)
   380  	return shard, nil
   381  }
   382  
   383  // Iterate over all objects in the index, applying the callback function to each one.  Adding or removing objects during iteration is not supported.
   384  func (i *Index) IterateObjects(ctx context.Context, cb func(index *Index, shard ShardLike, object *storobj.Object) error) (err error) {
   385  	return i.ForEachShard(func(_ string, shard ShardLike) error {
   386  		wrapper := func(object *storobj.Object) error {
   387  			return cb(i, shard, object)
   388  		}
   389  		bucket := shard.Store().Bucket(helpers.ObjectsBucketLSM)
   390  		return bucket.IterateObjects(ctx, wrapper)
   391  	})
   392  }
   393  
   394  func (i *Index) ForEachShard(f func(name string, shard ShardLike) error) error {
   395  	return i.shards.Range(f)
   396  }
   397  
   398  func (i *Index) ForEachShardConcurrently(f func(name string, shard ShardLike) error) error {
   399  	return i.shards.RangeConcurrently(i.logger, f)
   400  }
   401  
   402  // Iterate over all objects in the shard, applying the callback function to each one.  Adding or removing objects during iteration is not supported.
   403  func (i *Index) IterateShards(ctx context.Context, cb func(index *Index, shard ShardLike) error) (err error) {
   404  	return i.ForEachShard(func(key string, shard ShardLike) error {
   405  		return cb(i, shard)
   406  	})
   407  }
   408  
   409  func (i *Index) addProperty(ctx context.Context, prop *models.Property) error {
   410  	eg := enterrors.NewErrorGroupWrapper(i.logger)
   411  	eg.SetLimit(_NUMCPU)
   412  
   413  	i.ForEachShard(func(key string, shard ShardLike) error {
   414  		shard.createPropertyIndex(ctx, prop, eg)
   415  		return nil
   416  	})
   417  	if err := eg.Wait(); err != nil {
   418  		return errors.Wrapf(err, "extend idx '%s' with property '%s", i.ID(), prop.Name)
   419  	}
   420  	return nil
   421  }
   422  
   423  func (i *Index) addUUIDProperty(ctx context.Context) error {
   424  	return i.ForEachShard(func(name string, shard ShardLike) error {
   425  		err := shard.addIDProperty(ctx)
   426  		if err != nil {
   427  			return errors.Wrapf(err, "add id property to shard %q", name)
   428  		}
   429  		return nil
   430  	})
   431  }
   432  
   433  func (i *Index) addDimensionsProperty(ctx context.Context) error {
   434  	return i.ForEachShard(func(name string, shard ShardLike) error {
   435  		if err := shard.addDimensionsProperty(ctx); err != nil {
   436  			return errors.Wrapf(err, "add dimensions property to shard %q", name)
   437  		}
   438  		return nil
   439  	})
   440  }
   441  
   442  func (i *Index) addTimestampProperties(ctx context.Context) error {
   443  	return i.ForEachShard(func(name string, shard ShardLike) error {
   444  		if err := shard.addTimestampProperties(ctx); err != nil {
   445  			return errors.Wrapf(err, "add timestamp properties to shard %q", name)
   446  		}
   447  		return nil
   448  	})
   449  }
   450  
   451  func (i *Index) updateVectorIndexConfig(ctx context.Context,
   452  	updated schema.VectorIndexConfig,
   453  ) error {
   454  	// an updated is not specific to one shard, but rather all
   455  	err := i.ForEachShard(func(name string, shard ShardLike) error {
   456  		// At the moment, we don't do anything in an update that could fail, but
   457  		// technically this should be part of some sort of a two-phase commit  or
   458  		// have another way to rollback if we have updates that could potentially
   459  		// fail in the future. For now that's not a realistic risk.
   460  		if err := shard.UpdateVectorIndexConfig(ctx, updated); err != nil {
   461  			return errors.Wrapf(err, "shard %s", name)
   462  		}
   463  		return nil
   464  	})
   465  	if err != nil {
   466  		return err
   467  	}
   468  	i.vectorIndexUserConfigLock.Lock()
   469  	defer i.vectorIndexUserConfigLock.Unlock()
   470  
   471  	i.vectorIndexUserConfig = updated
   472  
   473  	return nil
   474  }
   475  
   476  func (i *Index) updateVectorIndexConfigs(ctx context.Context,
   477  	updated map[string]schema.VectorIndexConfig,
   478  ) error {
   479  	err := i.ForEachShard(func(name string, shard ShardLike) error {
   480  		if err := shard.UpdateVectorIndexConfigs(ctx, updated); err != nil {
   481  			return fmt.Errorf("shard %q: %w", name, err)
   482  		}
   483  		return nil
   484  	})
   485  	if err != nil {
   486  		return err
   487  	}
   488  
   489  	i.vectorIndexUserConfigLock.Lock()
   490  	defer i.vectorIndexUserConfigLock.Unlock()
   491  
   492  	for targetName, targetCfg := range updated {
   493  		i.vectorIndexUserConfigs[targetName] = targetCfg
   494  	}
   495  
   496  	return nil
   497  }
   498  
   499  func (i *Index) getInvertedIndexConfig() schema.InvertedIndexConfig {
   500  	i.invertedIndexConfigLock.Lock()
   501  	defer i.invertedIndexConfigLock.Unlock()
   502  
   503  	return i.invertedIndexConfig
   504  }
   505  
   506  func (i *Index) updateInvertedIndexConfig(ctx context.Context,
   507  	updated schema.InvertedIndexConfig,
   508  ) error {
   509  	i.invertedIndexConfigLock.Lock()
   510  	defer i.invertedIndexConfigLock.Unlock()
   511  
   512  	i.invertedIndexConfig = updated
   513  
   514  	return nil
   515  }
   516  
   517  type IndexConfig struct {
   518  	RootPath                  string
   519  	ClassName                 schema.ClassName
   520  	QueryMaximumResults       int64
   521  	QueryNestedRefLimit       int64
   522  	ResourceUsage             config.ResourceUsage
   523  	MemtablesFlushDirtyAfter  int
   524  	MemtablesInitialSizeMB    int
   525  	MemtablesMaxSizeMB        int
   526  	MemtablesMinActiveSeconds int
   527  	MemtablesMaxActiveSeconds int
   528  	ReplicationFactor         int64
   529  	AvoidMMap                 bool
   530  	DisableLazyLoadShards     bool
   531  
   532  	TrackVectorDimensions bool
   533  }
   534  
   535  func indexID(class schema.ClassName) string {
   536  	return strings.ToLower(string(class))
   537  }
   538  
   539  func (i *Index) determineObjectShard(id strfmt.UUID, tenant string) (string, error) {
   540  	className := i.Config.ClassName.String()
   541  	if tenant != "" {
   542  		if shard, status := i.getSchema.TenantShard(className, tenant); shard != "" {
   543  			if status == models.TenantActivityStatusHOT {
   544  				return shard, nil
   545  			}
   546  			return "", objects.NewErrMultiTenancy(fmt.Errorf("%w: '%s'", errTenantNotActive, tenant))
   547  		}
   548  		return "", objects.NewErrMultiTenancy(fmt.Errorf("%w: %q", errTenantNotFound, tenant))
   549  	}
   550  
   551  	uuid, err := uuid.Parse(id.String())
   552  	if err != nil {
   553  		return "", fmt.Errorf("parse uuid: %q", id.String())
   554  	}
   555  
   556  	uuidBytes, err := uuid.MarshalBinary() // cannot error
   557  	if err != nil {
   558  		return "", fmt.Errorf("marshal uuid: %q", id.String())
   559  	}
   560  
   561  	return i.getSchema.ShardFromUUID(className, uuidBytes), nil
   562  }
   563  
   564  func (i *Index) putObject(ctx context.Context, object *storobj.Object,
   565  	replProps *additional.ReplicationProperties,
   566  ) error {
   567  	if err := i.validateMultiTenancy(object.Object.Tenant); err != nil {
   568  		return err
   569  	}
   570  
   571  	if i.Config.ClassName != object.Class() {
   572  		return fmt.Errorf("cannot import object of class %s into index of class %s",
   573  			object.Class(), i.Config.ClassName)
   574  	}
   575  
   576  	shardName, err := i.determineObjectShard(object.ID(), object.Object.Tenant)
   577  	if err != nil {
   578  		return objects.NewErrInvalidUserInput("determine shard: %v", err)
   579  	}
   580  
   581  	if i.replicationEnabled() {
   582  		if replProps == nil {
   583  			replProps = defaultConsistency()
   584  		}
   585  		cl := replica.ConsistencyLevel(replProps.ConsistencyLevel)
   586  		if err := i.replicator.PutObject(ctx, shardName, object, cl); err != nil {
   587  			return fmt.Errorf("replicate insertion: shard=%q: %w", shardName, err)
   588  		}
   589  		return nil
   590  	}
   591  
   592  	// no replication, remote shard
   593  	if i.localShard(shardName) == nil {
   594  		if err := i.remote.PutObject(ctx, shardName, object); err != nil {
   595  			return fmt.Errorf("put remote object: shard=%q: %w", shardName, err)
   596  		}
   597  		return nil
   598  	}
   599  
   600  	// no replication, local shard
   601  	i.backupMutex.RLock()
   602  	defer i.backupMutex.RUnlock()
   603  	err = errShardNotFound
   604  	if shard := i.localShard(shardName); shard != nil { // does shard still exist
   605  		err = shard.PutObject(ctx, object)
   606  	}
   607  	if err != nil {
   608  		return fmt.Errorf("put local object: shard=%q: %w", shardName, err)
   609  	}
   610  
   611  	return nil
   612  }
   613  
   614  func (i *Index) IncomingPutObject(ctx context.Context, shardName string,
   615  	object *storobj.Object,
   616  ) error {
   617  	i.backupMutex.RLock()
   618  	defer i.backupMutex.RUnlock()
   619  	localShard := i.localShard(shardName)
   620  	if localShard == nil {
   621  		return errShardNotFound
   622  	}
   623  
   624  	// This is a bit hacky, the problem here is that storobj.Parse() currently
   625  	// misses date fields as it has no way of knowing that a date-formatted
   626  	// string was actually a date type. However, adding this functionality to
   627  	// Parse() would break a lot of code, because it currently
   628  	// schema-independent. To find out if a field is a date or date[], we need to
   629  	// involve the schema, thus why we are doing it here. This was discovered as
   630  	// part of https://github.com/weaviate/weaviate/issues/1775
   631  	if err := i.parseDateFieldsInProps(object.Object.Properties); err != nil {
   632  		return err
   633  	}
   634  
   635  	if err := localShard.PutObject(ctx, object); err != nil {
   636  		return err
   637  	}
   638  
   639  	return nil
   640  }
   641  
   642  func (i *Index) replicationEnabled() bool {
   643  	return i.Config.ReplicationFactor > 1
   644  }
   645  
   646  // parseDateFieldsInProps checks the schema for the current class for which
   647  // fields are date fields, then - if they are set - parses them accordingly.
   648  // Works for both date and date[].
   649  func (i *Index) parseDateFieldsInProps(props interface{}) error {
   650  	if props == nil {
   651  		return nil
   652  	}
   653  
   654  	propMap, ok := props.(map[string]interface{})
   655  	if !ok {
   656  		// don't know what to do with this
   657  		return nil
   658  	}
   659  
   660  	schemaModel := i.getSchema.GetSchemaSkipAuth().Objects
   661  	c, err := schema.GetClassByName(schemaModel, i.Config.ClassName.String())
   662  	if err != nil {
   663  		return err
   664  	}
   665  
   666  	for _, prop := range c.Properties {
   667  		if prop.DataType[0] == string(schema.DataTypeDate) {
   668  			raw, ok := propMap[prop.Name]
   669  			if !ok {
   670  				// prop is not set, nothing to do
   671  				continue
   672  			}
   673  
   674  			parsed, err := parseAsStringToTime(raw)
   675  			if err != nil {
   676  				return errors.Wrapf(err, "time prop %q", prop.Name)
   677  			}
   678  
   679  			propMap[prop.Name] = parsed
   680  		}
   681  
   682  		if prop.DataType[0] == string(schema.DataTypeDateArray) {
   683  			raw, ok := propMap[prop.Name]
   684  			if !ok {
   685  				// prop is not set, nothing to do
   686  				continue
   687  			}
   688  
   689  			asSlice, ok := raw.([]string)
   690  			if !ok {
   691  				return errors.Errorf("parse as time array, expected []interface{} got %T",
   692  					raw)
   693  			}
   694  			parsedSlice := make([]interface{}, len(asSlice))
   695  			for j := range asSlice {
   696  				parsed, err := parseAsStringToTime(interface{}(asSlice[j]))
   697  				if err != nil {
   698  					return errors.Wrapf(err, "time array prop %q at pos %d", prop.Name, j)
   699  				}
   700  
   701  				parsedSlice[j] = parsed
   702  			}
   703  			propMap[prop.Name] = parsedSlice
   704  
   705  		}
   706  	}
   707  
   708  	return nil
   709  }
   710  
   711  func parseAsStringToTime(in interface{}) (time.Time, error) {
   712  	var parsed time.Time
   713  	var err error
   714  
   715  	asString, ok := in.(string)
   716  	if !ok {
   717  		return parsed, errors.Errorf("parse as time, expected string got %T", in)
   718  	}
   719  
   720  	parsed, err = time.Parse(time.RFC3339, asString)
   721  	if err != nil {
   722  		return parsed, err
   723  	}
   724  
   725  	return parsed, nil
   726  }
   727  
   728  // return value []error gives the error for the index with the positions
   729  // matching the inputs
   730  func (i *Index) putObjectBatch(ctx context.Context, objects []*storobj.Object,
   731  	replProps *additional.ReplicationProperties,
   732  ) []error {
   733  	type objsAndPos struct {
   734  		objects []*storobj.Object
   735  		pos     []int
   736  	}
   737  	out := make([]error, len(objects))
   738  	if i.replicationEnabled() && replProps == nil {
   739  		replProps = defaultConsistency()
   740  	}
   741  
   742  	byShard := map[string]objsAndPos{}
   743  	for pos, obj := range objects {
   744  		if err := i.validateMultiTenancy(obj.Object.Tenant); err != nil {
   745  			out[pos] = err
   746  			continue
   747  		}
   748  		shardName, err := i.determineObjectShard(obj.ID(), obj.Object.Tenant)
   749  		if err != nil {
   750  			out[pos] = err
   751  			continue
   752  		}
   753  
   754  		group := byShard[shardName]
   755  		group.objects = append(group.objects, obj)
   756  		group.pos = append(group.pos, pos)
   757  		byShard[shardName] = group
   758  	}
   759  
   760  	wg := &sync.WaitGroup{}
   761  	for shardName, group := range byShard {
   762  		shardName := shardName
   763  		group := group
   764  		wg.Add(1)
   765  		f := func() {
   766  			defer wg.Done()
   767  
   768  			defer func() {
   769  				err := recover()
   770  				if err != nil {
   771  					for pos := range group.pos {
   772  						out[pos] = fmt.Errorf("an unexpected error occurred: %s", err)
   773  					}
   774  					fmt.Fprintf(os.Stderr, "panic: %s\n", err)
   775  					debug.PrintStack()
   776  				}
   777  			}()
   778  			var errs []error
   779  			if replProps != nil {
   780  				errs = i.replicator.PutObjects(ctx, shardName, group.objects,
   781  					replica.ConsistencyLevel(replProps.ConsistencyLevel))
   782  			} else if i.localShard(shardName) == nil {
   783  				errs = i.remote.BatchPutObjects(ctx, shardName, group.objects)
   784  			} else {
   785  				i.backupMutex.RLockGuard(func() error {
   786  					if shard := i.localShard(shardName); shard != nil {
   787  						errs = shard.PutObjectBatch(ctx, group.objects)
   788  					} else {
   789  						errs = duplicateErr(errShardNotFound, len(group.objects))
   790  					}
   791  					return nil
   792  				})
   793  			}
   794  			for i, err := range errs {
   795  				desiredPos := group.pos[i]
   796  				out[desiredPos] = err
   797  			}
   798  		}
   799  		enterrors.GoWrapper(f, i.logger)
   800  	}
   801  
   802  	wg.Wait()
   803  
   804  	return out
   805  }
   806  
   807  func duplicateErr(in error, count int) []error {
   808  	out := make([]error, count)
   809  	for i := range out {
   810  		out[i] = in
   811  	}
   812  
   813  	return out
   814  }
   815  
   816  func (i *Index) IncomingBatchPutObjects(ctx context.Context, shardName string,
   817  	objects []*storobj.Object,
   818  ) []error {
   819  	i.backupMutex.RLock()
   820  	defer i.backupMutex.RUnlock()
   821  	localShard := i.localShard(shardName)
   822  	if localShard == nil {
   823  		return duplicateErr(errShardNotFound, len(objects))
   824  	}
   825  
   826  	// This is a bit hacky, the problem here is that storobj.Parse() currently
   827  	// misses date fields as it has no way of knowing that a date-formatted
   828  	// string was actually a date type. However, adding this functionality to
   829  	// Parse() would break a lot of code, because it currently
   830  	// schema-independent. To find out if a field is a date or date[], we need to
   831  	// involve the schema, thus why we are doing it here. This was discovered as
   832  	// part of https://github.com/weaviate/weaviate/issues/1775
   833  	for j := range objects {
   834  		if err := i.parseDateFieldsInProps(objects[j].Object.Properties); err != nil {
   835  			return duplicateErr(err, len(objects))
   836  		}
   837  	}
   838  
   839  	return localShard.PutObjectBatch(ctx, objects)
   840  }
   841  
   842  // return value map[int]error gives the error for the index as it received it
   843  func (i *Index) AddReferencesBatch(ctx context.Context, refs objects.BatchReferences,
   844  	replProps *additional.ReplicationProperties,
   845  ) []error {
   846  	type refsAndPos struct {
   847  		refs objects.BatchReferences
   848  		pos  []int
   849  	}
   850  	if i.replicationEnabled() && replProps == nil {
   851  		replProps = defaultConsistency()
   852  	}
   853  
   854  	byShard := map[string]refsAndPos{}
   855  	out := make([]error, len(refs))
   856  
   857  	for pos, ref := range refs {
   858  		if err := i.validateMultiTenancy(ref.Tenant); err != nil {
   859  			out[pos] = err
   860  			continue
   861  		}
   862  		shardName, err := i.determineObjectShard(ref.From.TargetID, ref.Tenant)
   863  		if err != nil {
   864  			out[pos] = err
   865  			continue
   866  		}
   867  
   868  		group := byShard[shardName]
   869  		group.refs = append(group.refs, ref)
   870  		group.pos = append(group.pos, pos)
   871  		byShard[shardName] = group
   872  	}
   873  
   874  	for shardName, group := range byShard {
   875  		var errs []error
   876  		if i.replicationEnabled() {
   877  			errs = i.replicator.AddReferences(ctx, shardName, group.refs,
   878  				replica.ConsistencyLevel(replProps.ConsistencyLevel))
   879  		} else if i.localShard(shardName) == nil {
   880  			errs = i.remote.BatchAddReferences(ctx, shardName, group.refs)
   881  		} else {
   882  			i.backupMutex.RLockGuard(func() error {
   883  				if shard := i.localShard(shardName); shard != nil {
   884  					errs = shard.AddReferencesBatch(ctx, group.refs)
   885  				} else {
   886  					errs = duplicateErr(errShardNotFound, len(group.refs))
   887  				}
   888  				return nil
   889  			})
   890  		}
   891  		for i, err := range errs {
   892  			desiredPos := group.pos[i]
   893  			out[desiredPos] = err
   894  		}
   895  	}
   896  
   897  	return out
   898  }
   899  
   900  func (i *Index) IncomingBatchAddReferences(ctx context.Context, shardName string,
   901  	refs objects.BatchReferences,
   902  ) []error {
   903  	i.backupMutex.RLock()
   904  	defer i.backupMutex.RUnlock()
   905  	localShard := i.localShard(shardName)
   906  	if localShard == nil {
   907  		return duplicateErr(errShardNotFound, len(refs))
   908  	}
   909  
   910  	return localShard.AddReferencesBatch(ctx, refs)
   911  }
   912  
   913  func (i *Index) objectByID(ctx context.Context, id strfmt.UUID,
   914  	props search.SelectProperties, addl additional.Properties,
   915  	replProps *additional.ReplicationProperties, tenant string,
   916  ) (*storobj.Object, error) {
   917  	if err := i.validateMultiTenancy(tenant); err != nil {
   918  		return nil, err
   919  	}
   920  
   921  	shardName, err := i.determineObjectShard(id, tenant)
   922  	if err != nil {
   923  		switch err.(type) {
   924  		case objects.ErrMultiTenancy:
   925  			return nil, objects.NewErrMultiTenancy(fmt.Errorf("determine shard: %w", err))
   926  		default:
   927  			return nil, objects.NewErrInvalidUserInput("determine shard: %v", err)
   928  		}
   929  	}
   930  
   931  	var obj *storobj.Object
   932  
   933  	if i.replicationEnabled() {
   934  		if replProps == nil {
   935  			replProps = defaultConsistency()
   936  		}
   937  		if replProps.NodeName != "" {
   938  			obj, err = i.replicator.NodeObject(ctx, replProps.NodeName, shardName, id, props, addl)
   939  		} else {
   940  			obj, err = i.replicator.GetOne(ctx,
   941  				replica.ConsistencyLevel(replProps.ConsistencyLevel), shardName, id, props, addl)
   942  		}
   943  		return obj, err
   944  	}
   945  
   946  	if shard := i.localShard(shardName); shard != nil {
   947  		obj, err = shard.ObjectByID(ctx, id, props, addl)
   948  		if err != nil {
   949  			return obj, fmt.Errorf("get local object: shard=%s: %w", shardName, err)
   950  		}
   951  	} else {
   952  		obj, err = i.remote.GetObject(ctx, shardName, id, props, addl)
   953  		if err != nil {
   954  			return obj, fmt.Errorf("get remote object: shard=%s: %w", shardName, err)
   955  		}
   956  	}
   957  
   958  	return obj, nil
   959  }
   960  
   961  func (i *Index) IncomingGetObject(ctx context.Context, shardName string,
   962  	id strfmt.UUID, props search.SelectProperties,
   963  	additional additional.Properties,
   964  ) (*storobj.Object, error) {
   965  	shard := i.localShard(shardName)
   966  	if shard == nil {
   967  		return nil, errShardNotFound
   968  	}
   969  
   970  	return shard.ObjectByID(ctx, id, props, additional)
   971  }
   972  
   973  func (i *Index) IncomingMultiGetObjects(ctx context.Context, shardName string,
   974  	ids []strfmt.UUID,
   975  ) ([]*storobj.Object, error) {
   976  	shard := i.localShard(shardName)
   977  	if shard == nil {
   978  		return nil, errShardNotFound
   979  	}
   980  
   981  	return shard.MultiObjectByID(ctx, wrapIDsInMulti(ids))
   982  }
   983  
   984  func (i *Index) multiObjectByID(ctx context.Context,
   985  	query []multi.Identifier, tenant string,
   986  ) ([]*storobj.Object, error) {
   987  	if err := i.validateMultiTenancy(tenant); err != nil {
   988  		return nil, err
   989  	}
   990  
   991  	type idsAndPos struct {
   992  		ids []multi.Identifier
   993  		pos []int
   994  	}
   995  
   996  	byShard := map[string]idsAndPos{}
   997  	for pos, id := range query {
   998  		shardName, err := i.determineObjectShard(strfmt.UUID(id.ID), tenant)
   999  		if err != nil {
  1000  			return nil, objects.NewErrInvalidUserInput("determine shard: %v", err)
  1001  		}
  1002  
  1003  		group := byShard[shardName]
  1004  		group.ids = append(group.ids, id)
  1005  		group.pos = append(group.pos, pos)
  1006  		byShard[shardName] = group
  1007  	}
  1008  
  1009  	out := make([]*storobj.Object, len(query))
  1010  
  1011  	for shardName, group := range byShard {
  1012  
  1013  		var objects []*storobj.Object
  1014  		var err error
  1015  
  1016  		if shard := i.localShard(shardName); shard != nil {
  1017  			objects, err = shard.MultiObjectByID(ctx, group.ids)
  1018  			if err != nil {
  1019  				return nil, errors.Wrapf(err, "shard %s", shard.ID())
  1020  			}
  1021  		} else {
  1022  			objects, err = i.remote.MultiGetObjects(ctx, shardName, extractIDsFromMulti(group.ids))
  1023  			if err != nil {
  1024  				return nil, errors.Wrapf(err, "remote shard %s", shardName)
  1025  			}
  1026  		}
  1027  
  1028  		for i, obj := range objects {
  1029  			desiredPos := group.pos[i]
  1030  			out[desiredPos] = obj
  1031  		}
  1032  	}
  1033  
  1034  	return out, nil
  1035  }
  1036  
  1037  func extractIDsFromMulti(in []multi.Identifier) []strfmt.UUID {
  1038  	out := make([]strfmt.UUID, len(in))
  1039  
  1040  	for i, id := range in {
  1041  		out[i] = strfmt.UUID(id.ID)
  1042  	}
  1043  
  1044  	return out
  1045  }
  1046  
  1047  func wrapIDsInMulti(in []strfmt.UUID) []multi.Identifier {
  1048  	out := make([]multi.Identifier, len(in))
  1049  
  1050  	for i, id := range in {
  1051  		out[i] = multi.Identifier{ID: string(id)}
  1052  	}
  1053  
  1054  	return out
  1055  }
  1056  
  1057  func (i *Index) exists(ctx context.Context, id strfmt.UUID,
  1058  	replProps *additional.ReplicationProperties, tenant string,
  1059  ) (bool, error) {
  1060  	if err := i.validateMultiTenancy(tenant); err != nil {
  1061  		return false, err
  1062  	}
  1063  
  1064  	shardName, err := i.determineObjectShard(id, tenant)
  1065  	if err != nil {
  1066  		switch err.(type) {
  1067  		case objects.ErrMultiTenancy:
  1068  			return false, objects.NewErrMultiTenancy(fmt.Errorf("determine shard: %w", err))
  1069  		default:
  1070  			return false, objects.NewErrInvalidUserInput("determine shard: %v", err)
  1071  		}
  1072  	}
  1073  
  1074  	var exists bool
  1075  	if i.replicationEnabled() {
  1076  		if replProps == nil {
  1077  			replProps = defaultConsistency()
  1078  		}
  1079  		cl := replica.ConsistencyLevel(replProps.ConsistencyLevel)
  1080  		return i.replicator.Exists(ctx, cl, shardName, id)
  1081  
  1082  	}
  1083  	if shard := i.localShard(shardName); shard != nil {
  1084  		exists, err = shard.Exists(ctx, id)
  1085  		if err != nil {
  1086  			err = fmt.Errorf("exists locally: shard=%q: %w", shardName, err)
  1087  		}
  1088  	} else {
  1089  		exists, err = i.remote.Exists(ctx, shardName, id)
  1090  		if err != nil {
  1091  			err = fmt.Errorf("exists remotely: shard=%q: %w", shardName, err)
  1092  		}
  1093  	}
  1094  	return exists, err
  1095  }
  1096  
  1097  func (i *Index) IncomingExists(ctx context.Context, shardName string,
  1098  	id strfmt.UUID,
  1099  ) (bool, error) {
  1100  	shard := i.localShard(shardName)
  1101  	if shard == nil {
  1102  		return false, errShardNotFound
  1103  	}
  1104  
  1105  	return shard.Exists(ctx, id)
  1106  }
  1107  
  1108  func (i *Index) objectSearch(ctx context.Context, limit int, filters *filters.LocalFilter,
  1109  	keywordRanking *searchparams.KeywordRanking, sort []filters.Sort, cursor *filters.Cursor,
  1110  	addlProps additional.Properties, replProps *additional.ReplicationProperties, tenant string, autoCut int,
  1111  ) ([]*storobj.Object, []float32, error) {
  1112  	if err := i.validateMultiTenancy(tenant); err != nil {
  1113  		return nil, nil, err
  1114  	}
  1115  
  1116  	shardNames, err := i.targetShardNames(tenant)
  1117  	if err != nil || len(shardNames) == 0 {
  1118  		return nil, nil, err
  1119  	}
  1120  
  1121  	// If the request is a BM25F with no properties selected, use all possible properties
  1122  	if keywordRanking != nil && keywordRanking.Type == "bm25" && len(keywordRanking.Properties) == 0 {
  1123  
  1124  		cl, err := schema.GetClassByName(
  1125  			i.getSchema.GetSchemaSkipAuth().Objects,
  1126  			i.Config.ClassName.String())
  1127  		if err != nil {
  1128  			return nil, nil, err
  1129  		}
  1130  
  1131  		propHash := cl.Properties
  1132  		// Get keys of hash
  1133  		for _, v := range propHash {
  1134  			if inverted.PropertyHasSearchableIndex(i.getSchema.GetSchemaSkipAuth().Objects,
  1135  				i.Config.ClassName.String(), v.Name) {
  1136  
  1137  				keywordRanking.Properties = append(keywordRanking.Properties, v.Name)
  1138  			}
  1139  		}
  1140  
  1141  		// WEAVIATE-471 - error if we can't find a property to search
  1142  		if len(keywordRanking.Properties) == 0 {
  1143  			return nil, []float32{}, errors.New(
  1144  				"No properties provided, and no indexed properties found in class")
  1145  		}
  1146  	}
  1147  
  1148  	outObjects, outScores, err := i.objectSearchByShard(ctx, limit,
  1149  		filters, keywordRanking, sort, cursor, addlProps, shardNames)
  1150  	if err != nil {
  1151  		return nil, nil, err
  1152  	}
  1153  
  1154  	if len(outObjects) == len(outScores) {
  1155  		if keywordRanking != nil && keywordRanking.Type == "bm25" {
  1156  			for ii := range outObjects {
  1157  				oo := outObjects[ii]
  1158  
  1159  				if oo.AdditionalProperties() == nil {
  1160  					oo.Object.Additional = make(map[string]interface{})
  1161  				}
  1162  
  1163  				// Additional score is filled in by the top level function
  1164  
  1165  				// Collect all keys starting with "BM25F" and add them to the Additional
  1166  				if keywordRanking.AdditionalExplanations {
  1167  					explainScore := ""
  1168  					for k, v := range oo.Object.Additional {
  1169  						if strings.HasPrefix(k, "BM25F") {
  1170  
  1171  							explainScore = fmt.Sprintf("%v, %v:%v", explainScore, k, v)
  1172  							delete(oo.Object.Additional, k)
  1173  						}
  1174  					}
  1175  					oo.Object.Additional["explainScore"] = explainScore
  1176  				}
  1177  			}
  1178  		}
  1179  	}
  1180  
  1181  	if len(sort) > 0 {
  1182  		if len(shardNames) > 1 {
  1183  			var err error
  1184  			outObjects, outScores, err = i.sort(outObjects, outScores, sort, limit)
  1185  			if err != nil {
  1186  				return nil, nil, errors.Wrap(err, "sort")
  1187  			}
  1188  		}
  1189  	} else if keywordRanking != nil {
  1190  		outObjects, outScores = i.sortKeywordRanking(outObjects, outScores)
  1191  	} else if len(shardNames) > 1 && !addlProps.ReferenceQuery {
  1192  		// sort only for multiple shards (already sorted for single)
  1193  		// and for not reference nested query (sort is applied for root query)
  1194  		outObjects, outScores = i.sortByID(outObjects, outScores)
  1195  	}
  1196  
  1197  	if autoCut > 0 {
  1198  		cutOff := autocut.Autocut(outScores, autoCut)
  1199  		outObjects = outObjects[:cutOff]
  1200  		outScores = outScores[:cutOff]
  1201  	}
  1202  
  1203  	// if this search was caused by a reference property
  1204  	// search, we should not limit the number of results.
  1205  	// for example, if the query contains a where filter
  1206  	// whose operator is `And`, and one of the operands
  1207  	// contains a path to a reference prop, the Search
  1208  	// caused by such a ref prop being limited can cause
  1209  	// the `And` to return no results where results would
  1210  	// be expected. we won't know that unless we search
  1211  	// and return all referenced object properties.
  1212  	if !addlProps.ReferenceQuery && len(outObjects) > limit {
  1213  		if len(outObjects) == len(outScores) {
  1214  			outScores = outScores[:limit]
  1215  		}
  1216  		outObjects = outObjects[:limit]
  1217  	}
  1218  
  1219  	if i.replicationEnabled() {
  1220  		if replProps == nil {
  1221  			replProps = defaultConsistency(replica.One)
  1222  		}
  1223  		l := replica.ConsistencyLevel(replProps.ConsistencyLevel)
  1224  		err = i.replicator.CheckConsistency(ctx, l, outObjects)
  1225  		if err != nil {
  1226  			i.logger.WithField("action", "object_search").
  1227  				Errorf("failed to check consistency of search results: %v", err)
  1228  		}
  1229  	}
  1230  
  1231  	return outObjects, outScores, nil
  1232  }
  1233  
  1234  func (i *Index) objectSearchByShard(ctx context.Context, limit int, filters *filters.LocalFilter,
  1235  	keywordRanking *searchparams.KeywordRanking, sort []filters.Sort, cursor *filters.Cursor,
  1236  	addlProps additional.Properties, shards []string,
  1237  ) ([]*storobj.Object, []float32, error) {
  1238  	resultObjects, resultScores := objectSearchPreallocate(limit, shards)
  1239  
  1240  	eg := enterrors.NewErrorGroupWrapper(i.logger, "filters:", filters)
  1241  	eg.SetLimit(_NUMCPU * 2)
  1242  	shardResultLock := sync.Mutex{}
  1243  	for _, shardName := range shards {
  1244  		shardName := shardName
  1245  
  1246  		eg.Go(func() error {
  1247  			var (
  1248  				objs     []*storobj.Object
  1249  				scores   []float32
  1250  				nodeName string
  1251  				err      error
  1252  			)
  1253  
  1254  			if shard := i.localShard(shardName); shard != nil {
  1255  				nodeName = i.getSchema.NodeName()
  1256  				objs, scores, err = shard.ObjectSearch(ctx, limit, filters, keywordRanking, sort, cursor, addlProps)
  1257  				if err != nil {
  1258  					return fmt.Errorf(
  1259  						"local shard object search %s: %w", shard.ID(), err)
  1260  				}
  1261  			} else {
  1262  				objs, scores, nodeName, err = i.remote.SearchShard(
  1263  					ctx, shardName, nil, "", limit, filters, keywordRanking,
  1264  					sort, cursor, nil, addlProps, i.replicationEnabled())
  1265  				if err != nil {
  1266  					return fmt.Errorf(
  1267  						"remote shard object search %s: %w", shardName, err)
  1268  				}
  1269  			}
  1270  
  1271  			if i.replicationEnabled() {
  1272  				storobj.AddOwnership(objs, nodeName, shardName)
  1273  			}
  1274  
  1275  			shardResultLock.Lock()
  1276  			resultObjects = append(resultObjects, objs...)
  1277  			resultScores = append(resultScores, scores...)
  1278  			shardResultLock.Unlock()
  1279  
  1280  			return nil
  1281  		}, shardName)
  1282  	}
  1283  	if err := eg.Wait(); err != nil {
  1284  		return nil, nil, err
  1285  	}
  1286  
  1287  	if len(resultObjects) == len(resultScores) {
  1288  
  1289  		// Force a stable sort order by UUID
  1290  
  1291  		type resultSortable struct {
  1292  			object *storobj.Object
  1293  			score  float32
  1294  		}
  1295  		objs := resultObjects
  1296  		scores := resultScores
  1297  		var results []resultSortable = make([]resultSortable, len(objs))
  1298  		for i := range objs {
  1299  			results[i] = resultSortable{
  1300  				object: objs[i],
  1301  				score:  scores[i],
  1302  			}
  1303  		}
  1304  
  1305  		golangSort.Slice(results, func(i, j int) bool {
  1306  			if results[i].score == results[j].score {
  1307  				return results[i].object.Object.ID > results[j].object.Object.ID
  1308  			}
  1309  
  1310  			return results[i].score > results[j].score
  1311  		})
  1312  
  1313  		var finalObjs []*storobj.Object = make([]*storobj.Object, len(results))
  1314  		var finalScores []float32 = make([]float32, len(results))
  1315  		for i, result := range results {
  1316  
  1317  			finalObjs[i] = result.object
  1318  			finalScores[i] = result.score
  1319  		}
  1320  
  1321  		return finalObjs, finalScores, nil
  1322  	}
  1323  
  1324  	return resultObjects, resultScores, nil
  1325  }
  1326  
  1327  func (i *Index) sortByID(objects []*storobj.Object, scores []float32,
  1328  ) ([]*storobj.Object, []float32) {
  1329  	return newIDSorter().sort(objects, scores)
  1330  }
  1331  
  1332  func (i *Index) sortKeywordRanking(objects []*storobj.Object,
  1333  	scores []float32,
  1334  ) ([]*storobj.Object, []float32) {
  1335  	return newScoresSorter().sort(objects, scores)
  1336  }
  1337  
  1338  func (i *Index) sort(objects []*storobj.Object, scores []float32,
  1339  	sort []filters.Sort, limit int,
  1340  ) ([]*storobj.Object, []float32, error) {
  1341  	return sorter.NewObjectsSorter(i.getSchema.GetSchemaSkipAuth()).
  1342  		Sort(objects, scores, limit, sort)
  1343  }
  1344  
  1345  func (i *Index) mergeGroups(objects []*storobj.Object, dists []float32,
  1346  	groupBy *searchparams.GroupBy, limit, shardCount int,
  1347  ) ([]*storobj.Object, []float32, error) {
  1348  	return newGroupMerger(objects, dists, groupBy).Do()
  1349  }
  1350  
  1351  func (i *Index) singleLocalShardObjectVectorSearch(ctx context.Context, searchVector []float32,
  1352  	targetVector string, dist float32, limit int, filters *filters.LocalFilter,
  1353  	sort []filters.Sort, groupBy *searchparams.GroupBy, additional additional.Properties,
  1354  	shardName string,
  1355  ) ([]*storobj.Object, []float32, error) {
  1356  	shard := i.localShard(shardName)
  1357  	res, resDists, err := shard.ObjectVectorSearch(
  1358  		ctx, searchVector, targetVector, dist, limit, filters, sort, groupBy, additional)
  1359  	if err != nil {
  1360  		return nil, nil, errors.Wrapf(err, "shard %s", shard.ID())
  1361  	}
  1362  
  1363  	return res, resDists, nil
  1364  }
  1365  
  1366  // to be called after validating multi-tenancy
  1367  func (i *Index) targetShardNames(tenant string) ([]string, error) {
  1368  	className := i.Config.ClassName.String()
  1369  	if !i.partitioningEnabled {
  1370  		shardingState := i.getSchema.CopyShardingState(className)
  1371  		return shardingState.AllPhysicalShards(), nil
  1372  	}
  1373  	if tenant != "" {
  1374  		if shard, status := i.getSchema.TenantShard(className, tenant); shard != "" {
  1375  			if status == models.TenantActivityStatusHOT {
  1376  				return []string{shard}, nil
  1377  			}
  1378  			return nil, objects.NewErrMultiTenancy(fmt.Errorf("%w: '%s'", errTenantNotActive, tenant))
  1379  		}
  1380  	}
  1381  	return nil, objects.NewErrMultiTenancy(fmt.Errorf("%w: %q", errTenantNotFound, tenant))
  1382  }
  1383  
  1384  func (i *Index) objectVectorSearch(ctx context.Context, searchVector []float32,
  1385  	targetVector string, dist float32, limit int, filters *filters.LocalFilter, sort []filters.Sort,
  1386  	groupBy *searchparams.GroupBy, additional additional.Properties,
  1387  	replProps *additional.ReplicationProperties, tenant string,
  1388  ) ([]*storobj.Object, []float32, error) {
  1389  	if err := i.validateMultiTenancy(tenant); err != nil {
  1390  		return nil, nil, err
  1391  	}
  1392  	shardNames, err := i.targetShardNames(tenant)
  1393  	if err != nil || len(shardNames) == 0 {
  1394  		return nil, nil, err
  1395  	}
  1396  
  1397  	if len(shardNames) == 1 {
  1398  		if i.localShard(shardNames[0]) != nil {
  1399  			return i.singleLocalShardObjectVectorSearch(ctx, searchVector, targetVector, dist, limit, filters,
  1400  				sort, groupBy, additional, shardNames[0])
  1401  		}
  1402  	}
  1403  
  1404  	// a limit of -1 is used to signal a search by distance. if that is
  1405  	// the case we have to adjust how we calculate the output capacity
  1406  	var shardCap int
  1407  	if limit < 0 {
  1408  		shardCap = len(shardNames) * hnsw.DefaultSearchByDistInitialLimit
  1409  	} else {
  1410  		shardCap = len(shardNames) * limit
  1411  	}
  1412  
  1413  	eg := enterrors.NewErrorGroupWrapper(i.logger, "tenant:", tenant)
  1414  	eg.SetLimit(_NUMCPU * 2)
  1415  	m := &sync.Mutex{}
  1416  
  1417  	out := make([]*storobj.Object, 0, shardCap)
  1418  	dists := make([]float32, 0, shardCap)
  1419  	for _, shardName := range shardNames {
  1420  		shardName := shardName
  1421  		eg.Go(func() error {
  1422  			var (
  1423  				res      []*storobj.Object
  1424  				resDists []float32
  1425  				nodeName string
  1426  				err      error
  1427  			)
  1428  
  1429  			if shard := i.localShard(shardName); shard != nil {
  1430  				nodeName = i.getSchema.NodeName()
  1431  				res, resDists, err = shard.ObjectVectorSearch(
  1432  					ctx, searchVector, targetVector, dist, limit, filters, sort, groupBy, additional)
  1433  				if err != nil {
  1434  					return errors.Wrapf(err, "shard %s", shard.ID())
  1435  				}
  1436  
  1437  			} else {
  1438  				res, resDists, nodeName, err = i.remote.SearchShard(ctx,
  1439  					shardName, searchVector, targetVector, limit, filters,
  1440  					nil, sort, nil, groupBy, additional, i.replicationEnabled())
  1441  				if err != nil {
  1442  					return errors.Wrapf(err, "remote shard %s", shardName)
  1443  				}
  1444  			}
  1445  			if i.replicationEnabled() {
  1446  				storobj.AddOwnership(res, nodeName, shardName)
  1447  			}
  1448  
  1449  			m.Lock()
  1450  			out = append(out, res...)
  1451  			dists = append(dists, resDists...)
  1452  			m.Unlock()
  1453  
  1454  			return nil
  1455  		}, shardName)
  1456  	}
  1457  
  1458  	if err := eg.Wait(); err != nil {
  1459  		return nil, nil, err
  1460  	}
  1461  
  1462  	if len(shardNames) == 1 {
  1463  		return out, dists, nil
  1464  	}
  1465  
  1466  	if len(shardNames) > 1 && groupBy != nil {
  1467  		return i.mergeGroups(out, dists, groupBy, limit, len(shardNames))
  1468  	}
  1469  
  1470  	if len(shardNames) > 1 && len(sort) > 0 {
  1471  		return i.sort(out, dists, sort, limit)
  1472  	}
  1473  
  1474  	out, dists = newDistancesSorter().sort(out, dists)
  1475  	if limit > 0 && len(out) > limit {
  1476  		out = out[:limit]
  1477  		dists = dists[:limit]
  1478  	}
  1479  
  1480  	if i.replicationEnabled() {
  1481  		if replProps == nil {
  1482  			replProps = defaultConsistency(replica.One)
  1483  		}
  1484  		l := replica.ConsistencyLevel(replProps.ConsistencyLevel)
  1485  		err = i.replicator.CheckConsistency(ctx, l, out)
  1486  		if err != nil {
  1487  			i.logger.WithField("action", "object_vector_search").
  1488  				Errorf("failed to check consistency of search results: %v", err)
  1489  		}
  1490  	}
  1491  
  1492  	return out, dists, nil
  1493  }
  1494  
  1495  func (i *Index) IncomingSearch(ctx context.Context, shardName string,
  1496  	searchVector []float32, targetVector string, distance float32, limit int,
  1497  	filters *filters.LocalFilter, keywordRanking *searchparams.KeywordRanking,
  1498  	sort []filters.Sort, cursor *filters.Cursor, groupBy *searchparams.GroupBy,
  1499  	additional additional.Properties,
  1500  ) ([]*storobj.Object, []float32, error) {
  1501  	shard := i.localShard(shardName)
  1502  	if shard == nil {
  1503  		return nil, nil, errShardNotFound
  1504  	}
  1505  
  1506  	if searchVector == nil {
  1507  		res, scores, err := shard.ObjectSearch(ctx, limit, filters, keywordRanking, sort, cursor, additional)
  1508  		if err != nil {
  1509  			return nil, nil, err
  1510  		}
  1511  
  1512  		return res, scores, nil
  1513  	}
  1514  
  1515  	res, resDists, err := shard.ObjectVectorSearch(
  1516  		ctx, searchVector, targetVector, distance, limit, filters, sort, groupBy, additional)
  1517  	if err != nil {
  1518  		return nil, nil, errors.Wrapf(err, "shard %s", shard.ID())
  1519  	}
  1520  
  1521  	return res, resDists, nil
  1522  }
  1523  
  1524  func (i *Index) deleteObject(ctx context.Context, id strfmt.UUID,
  1525  	replProps *additional.ReplicationProperties, tenant string,
  1526  ) error {
  1527  	if err := i.validateMultiTenancy(tenant); err != nil {
  1528  		return err
  1529  	}
  1530  
  1531  	shardName, err := i.determineObjectShard(id, tenant)
  1532  	if err != nil {
  1533  		return objects.NewErrInvalidUserInput("determine shard: %v", err)
  1534  	}
  1535  
  1536  	if i.replicationEnabled() {
  1537  		if replProps == nil {
  1538  			replProps = defaultConsistency()
  1539  		}
  1540  		cl := replica.ConsistencyLevel(replProps.ConsistencyLevel)
  1541  		if err := i.replicator.DeleteObject(ctx, shardName, id, cl); err != nil {
  1542  			return fmt.Errorf("replicate deletion: shard=%q %w", shardName, err)
  1543  		}
  1544  		return nil
  1545  	}
  1546  
  1547  	// no replication, remote shard
  1548  	if i.localShard(shardName) == nil {
  1549  		if err := i.remote.DeleteObject(ctx, shardName, id); err != nil {
  1550  			return fmt.Errorf("delete remote object: shard=%q: %w", shardName, err)
  1551  		}
  1552  		return nil
  1553  	}
  1554  
  1555  	// no replication, local shard
  1556  	i.backupMutex.RLock()
  1557  	defer i.backupMutex.RUnlock()
  1558  	err = errShardNotFound
  1559  	if shard := i.localShard(shardName); shard != nil {
  1560  		err = shard.DeleteObject(ctx, id)
  1561  	}
  1562  	if err != nil {
  1563  		return fmt.Errorf("delete local object: shard=%q: %w", shardName, err)
  1564  	}
  1565  	return nil
  1566  }
  1567  
  1568  func (i *Index) IncomingDeleteObject(ctx context.Context, shardName string,
  1569  	id strfmt.UUID,
  1570  ) error {
  1571  	i.backupMutex.RLock()
  1572  	defer i.backupMutex.RUnlock()
  1573  	shard := i.localShard(shardName)
  1574  	if shard == nil {
  1575  		return errShardNotFound
  1576  	}
  1577  	return shard.DeleteObject(ctx, id)
  1578  }
  1579  
  1580  func (i *Index) localShard(name string) ShardLike {
  1581  	return i.shards.Load(name)
  1582  }
  1583  
  1584  func (i *Index) mergeObject(ctx context.Context, merge objects.MergeDocument,
  1585  	replProps *additional.ReplicationProperties, tenant string,
  1586  ) error {
  1587  	if err := i.validateMultiTenancy(tenant); err != nil {
  1588  		return err
  1589  	}
  1590  
  1591  	shardName, err := i.determineObjectShard(merge.ID, tenant)
  1592  	if err != nil {
  1593  		return objects.NewErrInvalidUserInput("determine shard: %v", err)
  1594  	}
  1595  
  1596  	if i.replicationEnabled() {
  1597  		if replProps == nil {
  1598  			replProps = defaultConsistency()
  1599  		}
  1600  		cl := replica.ConsistencyLevel(replProps.ConsistencyLevel)
  1601  		if err := i.replicator.MergeObject(ctx, shardName, &merge, cl); err != nil {
  1602  			return fmt.Errorf("replicate single update: %w", err)
  1603  		}
  1604  		return nil
  1605  	}
  1606  
  1607  	// no replication, remote shard
  1608  	if i.localShard(shardName) == nil {
  1609  		if err := i.remote.MergeObject(ctx, shardName, merge); err != nil {
  1610  			return fmt.Errorf("update remote object: shard=%q: %w", shardName, err)
  1611  		}
  1612  		return nil
  1613  	}
  1614  
  1615  	// no replication, local shard
  1616  	i.backupMutex.RLock()
  1617  	defer i.backupMutex.RUnlock()
  1618  	err = errShardNotFound
  1619  	if shard := i.localShard(shardName); shard != nil {
  1620  		err = shard.MergeObject(ctx, merge)
  1621  	}
  1622  	if err != nil {
  1623  		return fmt.Errorf("update local object: shard=%q: %w", shardName, err)
  1624  	}
  1625  
  1626  	return nil
  1627  }
  1628  
  1629  func (i *Index) IncomingMergeObject(ctx context.Context, shardName string,
  1630  	mergeDoc objects.MergeDocument,
  1631  ) error {
  1632  	i.backupMutex.RLock()
  1633  	defer i.backupMutex.RUnlock()
  1634  	shard := i.localShard(shardName)
  1635  	if shard == nil {
  1636  		return errShardNotFound
  1637  	}
  1638  
  1639  	return shard.MergeObject(ctx, mergeDoc)
  1640  }
  1641  
  1642  func (i *Index) aggregate(ctx context.Context,
  1643  	params aggregation.Params,
  1644  ) (*aggregation.Result, error) {
  1645  	if err := i.validateMultiTenancy(params.Tenant); err != nil {
  1646  		return nil, err
  1647  	}
  1648  
  1649  	shardNames, err := i.targetShardNames(params.Tenant)
  1650  	if err != nil || len(shardNames) == 0 {
  1651  		return nil, err
  1652  	}
  1653  
  1654  	results := make([]*aggregation.Result, len(shardNames))
  1655  	for j, shardName := range shardNames {
  1656  		var err error
  1657  		var res *aggregation.Result
  1658  		if shard := i.localShard(shardName); shard != nil {
  1659  			res, err = shard.Aggregate(ctx, params)
  1660  		} else {
  1661  			res, err = i.remote.Aggregate(ctx, shardName, params)
  1662  		}
  1663  		if err != nil {
  1664  			return nil, errors.Wrapf(err, "shard %s", shardName)
  1665  		}
  1666  
  1667  		results[j] = res
  1668  	}
  1669  
  1670  	return aggregator.NewShardCombiner().Do(results), nil
  1671  }
  1672  
  1673  func (i *Index) IncomingAggregate(ctx context.Context, shardName string,
  1674  	params aggregation.Params,
  1675  ) (*aggregation.Result, error) {
  1676  	shard := i.localShard(shardName)
  1677  	if shard == nil {
  1678  		return nil, errShardNotFound
  1679  	}
  1680  
  1681  	return shard.Aggregate(ctx, params)
  1682  }
  1683  
  1684  func (i *Index) drop() error {
  1685  	i.closingCancel()
  1686  
  1687  	eg := enterrors.NewErrorGroupWrapper(i.logger)
  1688  	eg.SetLimit(_NUMCPU * 2)
  1689  	fields := logrus.Fields{"action": "drop_shard", "class": i.Config.ClassName}
  1690  	dropShard := func(name string, shard ShardLike) error {
  1691  		if shard == nil {
  1692  			return nil
  1693  		}
  1694  		eg.Go(func() error {
  1695  			if err := shard.drop(); err != nil {
  1696  				logrus.WithFields(fields).WithField("id", shard.ID()).Error(err)
  1697  			}
  1698  			return nil
  1699  		})
  1700  		return nil
  1701  	}
  1702  
  1703  	i.backupMutex.RLock()
  1704  	defer i.backupMutex.RUnlock()
  1705  
  1706  	i.shards.Range(dropShard)
  1707  	if err := eg.Wait(); err != nil {
  1708  		return err
  1709  	}
  1710  
  1711  	// Dropping the shards only unregisters the shards callbacks, but we still
  1712  	// need to stop the cycle managers that those shards used to register with.
  1713  	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  1714  	defer cancel()
  1715  
  1716  	if err := i.stopCycleManagers(ctx, "drop"); err != nil {
  1717  		return err
  1718  	}
  1719  
  1720  	return os.RemoveAll(i.path())
  1721  }
  1722  
  1723  // dropShards deletes shards in a transactional manner.
  1724  // To confirm the deletion, the user must call Commit(true).
  1725  // To roll back the deletion, the user must call Commit(false)
  1726  func (i *Index) dropShards(names []string) (commit func(success bool), err error) {
  1727  	shards := make(map[string]ShardLike, len(names))
  1728  	i.backupMutex.RLock()
  1729  	defer i.backupMutex.RUnlock()
  1730  
  1731  	// mark deleted shards
  1732  	for _, name := range names {
  1733  		prev, ok := i.shards.Swap(name, nil) // mark
  1734  		if !ok {                             // shard doesn't exit
  1735  			i.shards.LoadAndDelete(name) // rollback nil value created by swap()
  1736  			continue
  1737  		}
  1738  		if prev != nil {
  1739  			shards[name] = prev
  1740  		}
  1741  	}
  1742  
  1743  	rollback := func() {
  1744  		for name, shard := range shards {
  1745  			i.shards.CompareAndSwap(name, nil, shard)
  1746  		}
  1747  	}
  1748  
  1749  	eg := enterrors.NewErrorGroupWrapper(i.logger)
  1750  	eg.SetLimit(_NUMCPU * 2)
  1751  	commit = func(success bool) {
  1752  		if !success {
  1753  			rollback()
  1754  			return
  1755  		}
  1756  		// detach shards
  1757  		for name := range shards {
  1758  			i.shards.LoadAndDelete(name)
  1759  		}
  1760  
  1761  		// drop shards
  1762  		for _, shard := range shards {
  1763  			shard := shard
  1764  			eg.Go(func() error {
  1765  				if err := shard.drop(); err != nil {
  1766  					i.logger.WithField("action", "drop_shard").
  1767  						WithField("shard", shard.ID()).Error(err)
  1768  				}
  1769  				return nil
  1770  			}, shard)
  1771  		}
  1772  	}
  1773  
  1774  	return commit, eg.Wait()
  1775  }
  1776  
  1777  func (i *Index) Shutdown(ctx context.Context) error {
  1778  	i.closingCancel()
  1779  
  1780  	i.backupMutex.RLock()
  1781  	defer i.backupMutex.RUnlock()
  1782  
  1783  	// TODO allow every resource cleanup to run, before returning early with error
  1784  	if err := i.ForEachShardConcurrently(func(name string, shard ShardLike) error {
  1785  		if err := shard.Shutdown(ctx); err != nil {
  1786  			return errors.Wrapf(err, "shutdown shard %q", name)
  1787  		}
  1788  		return nil
  1789  	}); err != nil {
  1790  		return err
  1791  	}
  1792  	if err := i.stopCycleManagers(ctx, "shutdown"); err != nil {
  1793  		return err
  1794  	}
  1795  
  1796  	return nil
  1797  }
  1798  
  1799  func (i *Index) stopCycleManagers(ctx context.Context, usecase string) error {
  1800  	if err := i.cycleCallbacks.compactionCycle.StopAndWait(ctx); err != nil {
  1801  		return fmt.Errorf("%s: stop compaction cycle: %w", usecase, err)
  1802  	}
  1803  	if err := i.cycleCallbacks.flushCycle.StopAndWait(ctx); err != nil {
  1804  		return fmt.Errorf("%s: stop flush cycle: %w", usecase, err)
  1805  	}
  1806  	if err := i.cycleCallbacks.vectorCommitLoggerCycle.StopAndWait(ctx); err != nil {
  1807  		return fmt.Errorf("%s: stop vector commit logger cycle: %w", usecase, err)
  1808  	}
  1809  	if err := i.cycleCallbacks.vectorTombstoneCleanupCycle.StopAndWait(ctx); err != nil {
  1810  		return fmt.Errorf("%s: stop vector tombstone cleanup cycle: %w", usecase, err)
  1811  	}
  1812  	if err := i.cycleCallbacks.geoPropsCommitLoggerCycle.StopAndWait(ctx); err != nil {
  1813  		return fmt.Errorf("%s: stop geo props commit logger cycle: %w", usecase, err)
  1814  	}
  1815  	if err := i.cycleCallbacks.geoPropsTombstoneCleanupCycle.StopAndWait(ctx); err != nil {
  1816  		return fmt.Errorf("%s: stop geo props tombstone cleanup cycle: %w", usecase, err)
  1817  	}
  1818  	return nil
  1819  }
  1820  
  1821  func (i *Index) getShardsQueueSize(ctx context.Context, tenant string) (map[string]int64, error) {
  1822  	shardsQueueSize := make(map[string]int64)
  1823  
  1824  	shardState := i.getSchema.CopyShardingState(i.Config.ClassName.String())
  1825  	shardNames := shardState.AllPhysicalShards()
  1826  
  1827  	for _, shardName := range shardNames {
  1828  		if tenant != "" && shardName != tenant {
  1829  			continue
  1830  		}
  1831  		var err error
  1832  		var size int64
  1833  		if !shardState.IsLocalShard(shardName) {
  1834  			size, err = i.remote.GetShardQueueSize(ctx, shardName)
  1835  		} else {
  1836  			shard := i.localShard(shardName)
  1837  			if shard == nil {
  1838  				err = errors.Errorf("shard %s does not exist", shardName)
  1839  			} else {
  1840  				if shard.hasTargetVectors() {
  1841  					for _, queue := range shard.Queues() {
  1842  						size += queue.Size()
  1843  					}
  1844  				} else {
  1845  					size = shard.Queue().Size()
  1846  				}
  1847  			}
  1848  		}
  1849  		if err != nil {
  1850  			return nil, errors.Wrapf(err, "shard %s", shardName)
  1851  		}
  1852  
  1853  		shardsQueueSize[shardName] = size
  1854  	}
  1855  
  1856  	return shardsQueueSize, nil
  1857  }
  1858  
  1859  func (i *Index) IncomingGetShardQueueSize(ctx context.Context, shardName string) (int64, error) {
  1860  	shard := i.localShard(shardName)
  1861  	if shard == nil {
  1862  		return 0, errShardNotFound
  1863  	}
  1864  	if !shard.hasTargetVectors() {
  1865  		return shard.Queue().Size(), nil
  1866  	}
  1867  	size := int64(0)
  1868  	for _, queue := range shard.Queues() {
  1869  		size += queue.Size()
  1870  	}
  1871  	return size, nil
  1872  }
  1873  
  1874  func (i *Index) getShardsStatus(ctx context.Context, tenant string) (map[string]string, error) {
  1875  	shardsStatus := make(map[string]string)
  1876  
  1877  	shardState := i.getSchema.CopyShardingState(i.Config.ClassName.String())
  1878  	shardNames := shardState.AllPhysicalShards()
  1879  
  1880  	for _, shardName := range shardNames {
  1881  		if tenant != "" && shardName != tenant {
  1882  			continue
  1883  		}
  1884  		var err error
  1885  		var status string
  1886  		if !shardState.IsLocalShard(shardName) {
  1887  			status, err = i.remote.GetShardStatus(ctx, shardName)
  1888  		} else {
  1889  			shard := i.localShard(shardName)
  1890  			if shard == nil {
  1891  				err = errors.Errorf("shard %s does not exist", shardName)
  1892  			} else {
  1893  				status = shard.GetStatus().String()
  1894  			}
  1895  		}
  1896  		if err != nil {
  1897  			return nil, errors.Wrapf(err, "shard %s", shardName)
  1898  		}
  1899  
  1900  		shardsStatus[shardName] = status
  1901  	}
  1902  
  1903  	return shardsStatus, nil
  1904  }
  1905  
  1906  func (i *Index) IncomingGetShardStatus(ctx context.Context, shardName string) (string, error) {
  1907  	shard := i.localShard(shardName)
  1908  	if shard == nil {
  1909  		return "", errShardNotFound
  1910  	}
  1911  	return shard.GetStatus().String(), nil
  1912  }
  1913  
  1914  func (i *Index) updateShardStatus(ctx context.Context, shardName, targetStatus string) error {
  1915  	if shard := i.localShard(shardName); shard != nil {
  1916  		return shard.UpdateStatus(targetStatus)
  1917  	}
  1918  	return i.remote.UpdateShardStatus(ctx, shardName, targetStatus)
  1919  }
  1920  
  1921  func (i *Index) IncomingUpdateShardStatus(ctx context.Context, shardName, targetStatus string) error {
  1922  	shard := i.localShard(shardName)
  1923  	if shard == nil {
  1924  		return errShardNotFound
  1925  	}
  1926  	return shard.UpdateStatus(targetStatus)
  1927  }
  1928  
  1929  func (i *Index) notifyReady() {
  1930  	i.ForEachShard(func(name string, shard ShardLike) error {
  1931  		shard.NotifyReady()
  1932  		return nil
  1933  	})
  1934  }
  1935  
  1936  func (i *Index) findUUIDs(ctx context.Context,
  1937  	filters *filters.LocalFilter, tenant string,
  1938  ) (map[string][]strfmt.UUID, error) {
  1939  	before := time.Now()
  1940  	defer i.metrics.BatchDelete(before, "filter_total")
  1941  
  1942  	if err := i.validateMultiTenancy(tenant); err != nil {
  1943  		return nil, err
  1944  	}
  1945  
  1946  	shardNames, err := i.targetShardNames(tenant)
  1947  	if err != nil {
  1948  		return nil, err
  1949  	}
  1950  
  1951  	results := make(map[string][]strfmt.UUID)
  1952  	for _, shardName := range shardNames {
  1953  		var err error
  1954  		var res []strfmt.UUID
  1955  		if shard := i.localShard(shardName); shard != nil {
  1956  			res, err = shard.FindUUIDs(ctx, filters)
  1957  		} else {
  1958  			res, err = i.remote.FindUUIDs(ctx, shardName, filters)
  1959  		}
  1960  		if err != nil {
  1961  			return nil, fmt.Errorf("find matching doc ids in shard %q: %w", shardName, err)
  1962  		}
  1963  
  1964  		results[shardName] = res
  1965  	}
  1966  
  1967  	return results, nil
  1968  }
  1969  
  1970  func (i *Index) IncomingFindUUIDs(ctx context.Context, shardName string,
  1971  	filters *filters.LocalFilter,
  1972  ) ([]strfmt.UUID, error) {
  1973  	shard := i.localShard(shardName)
  1974  	if shard == nil {
  1975  		return nil, errShardNotFound
  1976  	}
  1977  
  1978  	return shard.FindUUIDs(ctx, filters)
  1979  }
  1980  
  1981  func (i *Index) batchDeleteObjects(ctx context.Context, shardUUIDs map[string][]strfmt.UUID,
  1982  	dryRun bool, replProps *additional.ReplicationProperties,
  1983  ) (objects.BatchSimpleObjects, error) {
  1984  	before := time.Now()
  1985  	defer i.metrics.BatchDelete(before, "delete_from_shards_total")
  1986  
  1987  	type result struct {
  1988  		objs objects.BatchSimpleObjects
  1989  	}
  1990  
  1991  	if i.replicationEnabled() && replProps == nil {
  1992  		replProps = defaultConsistency()
  1993  	}
  1994  
  1995  	wg := &sync.WaitGroup{}
  1996  	ch := make(chan result, len(shardUUIDs))
  1997  	for shardName, uuids := range shardUUIDs {
  1998  		uuids := uuids
  1999  		shardName := shardName
  2000  		wg.Add(1)
  2001  		f := func() {
  2002  			defer wg.Done()
  2003  
  2004  			var objs objects.BatchSimpleObjects
  2005  			if i.replicationEnabled() {
  2006  				objs = i.replicator.DeleteObjects(ctx, shardName, uuids,
  2007  					dryRun, replica.ConsistencyLevel(replProps.ConsistencyLevel))
  2008  			} else if i.localShard(shardName) == nil {
  2009  				objs = i.remote.DeleteObjectBatch(ctx, shardName, uuids, dryRun)
  2010  			} else {
  2011  				i.backupMutex.RLockGuard(func() error {
  2012  					if shard := i.localShard(shardName); shard != nil {
  2013  						objs = shard.DeleteObjectBatch(ctx, uuids, dryRun)
  2014  					} else {
  2015  						objs = objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: errShardNotFound}}
  2016  					}
  2017  					return nil
  2018  				})
  2019  			}
  2020  			ch <- result{objs}
  2021  		}
  2022  		enterrors.GoWrapper(f, i.logger)
  2023  	}
  2024  
  2025  	wg.Wait()
  2026  	close(ch)
  2027  
  2028  	var out objects.BatchSimpleObjects
  2029  	for res := range ch {
  2030  		out = append(out, res.objs...)
  2031  	}
  2032  
  2033  	return out, nil
  2034  }
  2035  
  2036  func (i *Index) IncomingDeleteObjectBatch(ctx context.Context, shardName string,
  2037  	uuids []strfmt.UUID, dryRun bool,
  2038  ) objects.BatchSimpleObjects {
  2039  	i.backupMutex.RLock()
  2040  	defer i.backupMutex.RUnlock()
  2041  	shard := i.localShard(shardName)
  2042  	if shard == nil {
  2043  		return objects.BatchSimpleObjects{
  2044  			objects.BatchSimpleObject{Err: errShardNotFound},
  2045  		}
  2046  	}
  2047  
  2048  	return shard.DeleteObjectBatch(ctx, uuids, dryRun)
  2049  }
  2050  
  2051  func defaultConsistency(l ...replica.ConsistencyLevel) *additional.ReplicationProperties {
  2052  	rp := &additional.ReplicationProperties{}
  2053  	if len(l) != 0 {
  2054  		rp.ConsistencyLevel = string(l[0])
  2055  	} else {
  2056  		rp.ConsistencyLevel = string(replica.Quorum)
  2057  	}
  2058  	return rp
  2059  }
  2060  
  2061  func objectSearchPreallocate(limit int, shards []string) ([]*storobj.Object, []float32) {
  2062  	perShardLimit := config.DefaultQueryMaximumResults
  2063  	if perShardLimit > int64(limit) {
  2064  		perShardLimit = int64(limit)
  2065  	}
  2066  	capacity := perShardLimit * int64(len(shards))
  2067  	objects := make([]*storobj.Object, 0, capacity)
  2068  	scores := make([]float32, 0, capacity)
  2069  
  2070  	return objects, scores
  2071  }
  2072  
  2073  func (i *Index) addNewShard(ctx context.Context,
  2074  	class *models.Class, shardName string,
  2075  ) error {
  2076  	if shard := i.localShard(shardName); shard != nil {
  2077  		return fmt.Errorf("shard %q exists already", shardName)
  2078  	}
  2079  
  2080  	// TODO: metrics
  2081  	return i.initAndStoreShard(ctx, shardName, class, i.metrics.baseMetrics)
  2082  }
  2083  
  2084  func (i *Index) validateMultiTenancy(tenant string) error {
  2085  	if i.partitioningEnabled && tenant == "" {
  2086  		return objects.NewErrMultiTenancy(
  2087  			fmt.Errorf("class %s has multi-tenancy enabled, but request was without tenant", i.Config.ClassName),
  2088  		)
  2089  	} else if !i.partitioningEnabled && tenant != "" {
  2090  		return objects.NewErrMultiTenancy(
  2091  			fmt.Errorf("class %s has multi-tenancy disabled, but request was with tenant", i.Config.ClassName),
  2092  		)
  2093  	}
  2094  	return nil
  2095  }
  2096  
  2097  func convertToVectorIndexConfig(config interface{}) schema.VectorIndexConfig {
  2098  	if config == nil {
  2099  		return nil
  2100  	}
  2101  	// in case legacy vector config was set as an empty map/object instead of nil
  2102  	if empty, ok := config.(map[string]interface{}); ok && len(empty) == 0 {
  2103  		return nil
  2104  	}
  2105  	return config.(schema.VectorIndexConfig)
  2106  }
  2107  
  2108  func convertToVectorIndexConfigs(configs map[string]models.VectorConfig) map[string]schema.VectorIndexConfig {
  2109  	if len(configs) > 0 {
  2110  		vectorIndexConfigs := make(map[string]schema.VectorIndexConfig)
  2111  		for targetVector, vectorConfig := range configs {
  2112  			if vectorIndexConfig, ok := vectorConfig.VectorIndexConfig.(schema.VectorIndexConfig); ok {
  2113  				vectorIndexConfigs[targetVector] = vectorIndexConfig
  2114  			}
  2115  		}
  2116  		return vectorIndexConfigs
  2117  	}
  2118  	return nil
  2119  }