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 }