Repository: weaviate/weaviate
Pull request #7626 (2025-03-24 16:15:33)

Sync main with stable/v1.30
### What's being changed:


### Review checklist

- [ ] Documentation has been updated, if necessary. Link to changed documentation:
- [ ] Chaos pipeline run or not necessary. Link to pipeline:
- [ ] All new code is covered by tests where it is reasonable.
- [ ] Performance tests have been run or not necessary.

<!-- Uncomment the following section if this PR requires changes in related projects (e.g., documentation, client libraries).

GitHub actions will automatically create an issue in the corresponding repository for each checked box below. (See `.github/workflows/create-cross-functional-issues.yml`)

### Cross-functional impact

- [ ] This change requires public documentation (weaviate-io) to be updated. Check the box to automatically create a corresponding issue.
- Does it require a change in the client libraries? If yes, please check the boxes for the affected client libraries.
    - [ ] Python (weaviate-python-client)
    - [ ] JavaScript/TypeScript (typescript-client)
    - [ ] Go (weaviate-go-client)
    - [ ] Java (java-client)

-->



Patch:
diff --git a/adapters/handlers/rest/authz/handlers_authz.go b/adapters/handlers/rest/authz/handlers_authz.go
index d3ad614b47..ecda0a81ba 100644
--- a/adapters/handlers/rest/authz/handlers_authz.go
+++ b/adapters/handlers/rest/authz/handlers_authz.go
@@ -602,6 +602,8 @@ func (h *authZHandlers) getRolesForUser(params authz.GetRolesForUserParams, prin
 		}
 	}
 
+	includeFullRoles := params.IncludeFullRoles != nil && *params.IncludeFullRoles
+
 	userType, err := validateUserTypeInput(params.UserType)
 	if err != nil {
 		return authz.NewGetRolesForUserBadRequest().WithPayload(cerrors.ErrPayloadFromSingleErr(fmt.Errorf("unknown userType: %v", params.UserType)))
@@ -620,32 +622,32 @@ func (h *authZHandlers) getRolesForUser(params authz.GetRolesForUserParams, prin
 		return authz.NewGetRolesForUserInternalServerError().WithPayload(cerrors.ErrPayloadFromSingleErr(fmt.Errorf("GetRolesForUser: %w", err)))
 	}
 
-	response := []*models.Role{}
-	var authErr error
+	var roles []*models.Role
+	var authErrs []error
 	for roleName, policies := range existingRoles {
 		perms, err := conv.PoliciesToPermission(policies...)
 		if err != nil {
 			return authz.NewGetRolesForUserInternalServerError().WithPayload(cerrors.ErrPayloadFromSingleErr(fmt.Errorf("PoliciesToPermission: %w", err)))
 		}
 
-		if !ownUser {
-			if err := h.authorizeRoleScopes(principal, authorization.READ, nil, roleName); err != nil {
-				authErr = err
-				continue
+		role := &models.Role{Name: &roleName}
+		if includeFullRoles {
+			if !ownUser {
+				if err := h.authorizeRoleScopes(principal, authorization.READ, nil, roleName); err != nil {
+					authErrs = append(authErrs, err)
+					continue
+				}
 			}
+			role.Permissions = perms
 		}
-
-		response = append(response, &models.Role{
-			Name:        &roleName,
-			Permissions: perms,
-		})
+		roles = append(roles, role)
 	}
 
-	if len(existingRoles) != 0 && len(response) == 0 {
-		return authz.NewGetRolesForUserForbidden().WithPayload(cerrors.ErrPayloadFromSingleErr(authErr))
+	if len(authErrs) > 0 {
+		return authz.NewGetRolesForUserForbidden().WithPayload(cerrors.ErrPayloadFromSingleErr(errors.Join(authErrs...)))
 	}
 
-	sortByName(response)
+	sortByName(roles)
 
 	h.logger.WithFields(logrus.Fields{
 		"action":                "get_roles_for_user",
@@ -654,7 +656,7 @@ func (h *authZHandlers) getRolesForUser(params authz.GetRolesForUserParams, prin
 		"user_to_get_roles_for": params.ID,
 	}).Info("roles requested")
 
-	return authz.NewGetRolesForUserOK().WithPayload(response)
+	return authz.NewGetRolesForUserOK().WithPayload(roles)
 }
 
 func (h *authZHandlers) getUsersForRole(params authz.GetUsersForRoleParams, principal *models.Principal) middleware.Responder {
diff --git a/adapters/handlers/rest/configure_api.go b/adapters/handlers/rest/configure_api.go
index 0e0ff0b713..fc4bcfb6f4 100644
--- a/adapters/handlers/rest/configure_api.go
+++ b/adapters/handlers/rest/configure_api.go
@@ -209,6 +209,9 @@ func MakeAppState(ctx context.Context, options *swag.CommandLineOptionsGroup) *s
 
 	appState := startupRoutine(ctx, options)
 
+	// initializing at the top to reflect the config changes before we pass on to different components.
+	initRuntimeOverrides(appState)
+
 	if appState.ServerConfig.Config.Monitoring.Enabled {
 		appState.HTTPServerMetrics = monitoring.NewHTTPServerMetrics(monitoring.DefaultMetricsNamespace, prometheus.DefaultRegisterer)
 		appState.GRPCServerMetrics = monitoring.NewGRPCServerMetrics(monitoring.DefaultMetricsNamespace, prometheus.DefaultRegisterer)
@@ -525,35 +528,6 @@ func MakeAppState(ctx context.Context, options *swag.CommandLineOptionsGroup) *s
 		appState.Logger,
 	)
 
-	// Enable runtime config manager
-	if appState.ServerConfig.Config.RuntimeOverrides.Enabled {
-		cm, err := configRuntime.NewConfigManager(
-			appState.ServerConfig.Config.RuntimeOverrides.Path,
-			config.ParseYaml,
-			appState.ServerConfig.Config.RuntimeOverrides.LoadInterval,
-			appState.Logger,
-			prometheus.DefaultRegisterer)
-		if err != nil {
-			appState.Logger.WithField("action", "startup").WithError(err).Fatal("could not create runtime config manager")
-			os.Exit(1)
-		}
-
-		enterrors.GoWrapper(func() {
-			// NOTE: Not using parent `ctx` because that is getting cancelled in the caller even during startup.
-			ctx, cancel := context.WithCancel(context.Background())
-			defer cancel()
-
-			if err := cm.Run(ctx); err != nil {
-				appState.Logger.WithField("action", "runtime config manager startup ").WithError(err).
-					Fatal("runtime config manager stopped")
-			}
-		}, appState.Logger)
-		rc := config.NewWeaviateRuntimeConfig(cm)
-		appState.ServerConfig.Config.SchemaHandlerConfig.MaximumAllowedCollectionsCountFn = rc.GetMaximumAllowedCollectionsCount
-		appState.ServerConfig.Config.AutoSchema.EnabledFn = rc.GetAutoSchemaEnabled
-		appState.ServerConfig.Config.Replication.AsyncReplicationDisabledFn = rc.GetAsyncReplicationDisabled
-	}
-
 	schemaManager, err := schemaUC.NewManager(migrator,
 		appState.ClusterService.Raft,
 		appState.ClusterService.SchemaReader(),
@@ -1763,3 +1737,34 @@ func (m membership) LeaderID() string {
 	_, id := m.raft.LeaderWithID()
 	return id
 }
+
+func initRuntimeOverrides(appState *state.State) {
+	// Enable runtime config manager
+	if appState.ServerConfig.Config.RuntimeOverrides.Enabled {
+		cm, err := configRuntime.NewConfigManager(
+			appState.ServerConfig.Config.RuntimeOverrides.Path,
+			config.ParseYaml,
+			appState.ServerConfig.Config.RuntimeOverrides.LoadInterval,
+			appState.Logger,
+			prometheus.DefaultRegisterer)
+		if err != nil {
+			appState.Logger.WithField("action", "startup").WithError(err).Fatal("could not create runtime config manager")
+			os.Exit(1)
+		}
+
+		enterrors.GoWrapper(func() {
+			// NOTE: Not using parent `ctx` because that is getting cancelled in the caller even during startup.
+			ctx, cancel := context.WithCancel(context.Background())
+			defer cancel()
+
+			if err := cm.Run(ctx); err != nil {
+				appState.Logger.WithField("action", "runtime config manager startup ").WithError(err).
+					Fatal("runtime config manager stopped")
+			}
+		}, appState.Logger)
+		rc := config.NewWeaviateRuntimeConfig(cm)
+		appState.ServerConfig.Config.SchemaHandlerConfig.MaximumAllowedCollectionsCountFn = rc.GetMaximumAllowedCollectionsCount
+		appState.ServerConfig.Config.AutoSchema.EnabledFn = rc.GetAutoSchemaEnabled
+		appState.ServerConfig.Config.Replication.AsyncReplicationDisabledFn = rc.GetAsyncReplicationDisabled
+	}
+}
diff --git a/adapters/handlers/rest/doc.go b/adapters/handlers/rest/doc.go
index ccdb16fef1..45fe288362 100644
--- a/adapters/handlers/rest/doc.go
+++ b/adapters/handlers/rest/doc.go
@@ -27,7 +27,7 @@
 //	  https
 //	Host: localhost
 //	BasePath: /v1
-//	Version: 1.31.0-dev
+//	Version: 1.30.0-rc.0
 //	Contact: Weaviate<hello@weaviate.io> https://github.com/weaviate
 //
 //	Consumes:
diff --git a/adapters/handlers/rest/embedded_spec.go b/adapters/handlers/rest/embedded_spec.go
index 3891592403..623994be74 100644
--- a/adapters/handlers/rest/embedded_spec.go
+++ b/adapters/handlers/rest/embedded_spec.go
@@ -48,7 +48,7 @@ func init() {
       "url": "https://github.com/weaviate",
       "email": "hello@weaviate.io"
     },
-    "version": "1.31.0-dev"
+    "version": "1.30.0-rc.0"
   },
   "basePath": "/v1",
   "paths": {
@@ -1054,6 +1054,13 @@ func init() {
             "name": "userType",
             "in": "path",
             "required": true
+          },
+          {
+            "type": "boolean",
+            "default": false,
+            "description": "Whether to include detailed role information needed the roles permission",
+            "name": "includeFullRoles",
+            "in": "query"
           }
         ],
         "responses": {
@@ -7284,7 +7291,7 @@ func init() {
       "url": "https://github.com/weaviate",
       "email": "hello@weaviate.io"
     },
-    "version": "1.31.0-dev"
+    "version": "1.30.0-rc.0"
   },
   "basePath": "/v1",
   "paths": {
@@ -8290,6 +8297,13 @@ func init() {
             "name": "userType",
             "in": "path",
             "required": true
+          },
+          {
+            "type": "boolean",
+            "default": false,
+            "description": "Whether to include detailed role information needed the roles permission",
+            "name": "includeFullRoles",
+            "in": "query"
           }
         ],
         "responses": {
diff --git a/adapters/handlers/rest/operations/authz/get_roles_for_user_parameters.go b/adapters/handlers/rest/operations/authz/get_roles_for_user_parameters.go
index 177c75f3d0..4d8d6ede41 100644
--- a/adapters/handlers/rest/operations/authz/get_roles_for_user_parameters.go
+++ b/adapters/handlers/rest/operations/authz/get_roles_for_user_parameters.go
@@ -20,17 +20,26 @@ import (
 	"net/http"
 
 	"github.com/go-openapi/errors"
+	"github.com/go-openapi/runtime"
 	"github.com/go-openapi/runtime/middleware"
 	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
 	"github.com/go-openapi/validate"
 )
 
 // NewGetRolesForUserParams creates a new GetRolesForUserParams object
-//
-// There are no default values defined in the spec.
+// with the default values initialized.
 func NewGetRolesForUserParams() GetRolesForUserParams {
 
-	return GetRolesForUserParams{}
+	var (
+		// initialize parameters with default values
+
+		includeFullRolesDefault = bool(false)
+	)
+
+	return GetRolesForUserParams{
+		IncludeFullRoles: &includeFullRolesDefault,
+	}
 }
 
 // GetRolesForUserParams contains all the bound params for the get roles for user operation
@@ -47,6 +56,11 @@ type GetRolesForUserParams struct {
 	  In: path
 	*/
 	ID string
+	/*Whether to include detailed role information needed the roles permission
+	  In: query
+	  Default: false
+	*/
+	IncludeFullRoles *bool
 	/*The type of user
 	  Required: true
 	  In: path
@@ -63,11 +77,18 @@ func (o *GetRolesForUserParams) BindRequest(r *http.Request, route *middleware.M
 
 	o.HTTPRequest = r
 
+	qs := runtime.Values(r.URL.Query())
+
 	rID, rhkID, _ := route.Params.GetOK("id")
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
 
+	qIncludeFullRoles, qhkIncludeFullRoles, _ := qs.GetOK("includeFullRoles")
+	if err := o.bindIncludeFullRoles(qIncludeFullRoles, qhkIncludeFullRoles, route.Formats); err != nil {
+		res = append(res, err)
+	}
+
 	rUserType, rhkUserType, _ := route.Params.GetOK("userType")
 	if err := o.bindUserType(rUserType, rhkUserType, route.Formats); err != nil {
 		res = append(res, err)
@@ -92,6 +113,30 @@ func (o *GetRolesForUserParams) bindID(rawData []string, hasKey bool, formats st
 	return nil
 }
 
+// bindIncludeFullRoles binds and validates parameter IncludeFullRoles from query.
+func (o *GetRolesForUserParams) bindIncludeFullRoles(rawData []string, hasKey bool, formats strfmt.Registry) error {
+	var raw string
+	if len(rawData) > 0 {
+		raw = rawData[len(rawData)-1]
+	}
+
+	// Required: false
+	// AllowEmptyValue: false
+
+	if raw == "" { // empty values pass all other validations
+		// Default values have been previously initialized by NewGetRolesForUserParams()
+		return nil
+	}
+
+	value, err := swag.ConvertBool(raw)
+	if err != nil {
+		return errors.InvalidType("includeFullRoles", "query", "bool", raw)
+	}
+	o.IncludeFullRoles = &value
+
+	return nil
+}
+
 // bindUserType binds and validates parameter UserType from path.
 func (o *GetRolesForUserParams) bindUserType(rawData []string, hasKey bool, formats strfmt.Registry) error {
 	var raw string
diff --git a/adapters/handlers/rest/operations/authz/get_roles_for_user_urlbuilder.go b/adapters/handlers/rest/operations/authz/get_roles_for_user_urlbuilder.go
index 8a60f6873b..02cc42049b 100644
--- a/adapters/handlers/rest/operations/authz/get_roles_for_user_urlbuilder.go
+++ b/adapters/handlers/rest/operations/authz/get_roles_for_user_urlbuilder.go
@@ -21,6 +21,8 @@ import (
 	"net/url"
 	golangswaggerpaths "path"
 	"strings"
+
+	"github.com/go-openapi/swag"
 )
 
 // GetRolesForUserURL generates an URL for the get roles for user operation
@@ -28,6 +30,8 @@ type GetRolesForUserURL struct {
 	ID       string
 	UserType string
 
+	IncludeFullRoles *bool
+
 	_basePath string
 	// avoid unkeyed usage
 	_ struct{}
@@ -74,6 +78,18 @@ func (o *GetRolesForUserURL) Build() (*url.URL, error) {
 	}
 	_result.Path = golangswaggerpaths.Join(_basePath, _path)
 
+	qs := make(url.Values)
+
+	var includeFullRolesQ string
+	if o.IncludeFullRoles != nil {
+		includeFullRolesQ = swag.FormatBool(*o.IncludeFullRoles)
+	}
+	if includeFullRolesQ != "" {
+		qs.Set("includeFullRoles", includeFullRolesQ)
+	}
+
+	_result.RawQuery = qs.Encode()
+
 	return &_result, nil
 }
 
diff --git a/adapters/repos/db/helpers/tokenizer.go b/adapters/repos/db/helpers/tokenizer.go
index 1876d92ed7..f1c13e6244 100644
--- a/adapters/repos/db/helpers/tokenizer.go
+++ b/adapters/repos/db/helpers/tokenizer.go
@@ -64,7 +64,7 @@ func init() {
 	numParallelStr := os.Getenv("TOKENIZER_CONCURRENCY_COUNT")
 	if numParallelStr != "" {
 		x, err := strconv.Atoi(numParallelStr)
-		if err != nil {
+		if err == nil {
 			numParallel = x
 		}
 	}
diff --git a/adapters/repos/db/queue/scheduler.go b/adapters/repos/db/queue/scheduler.go
index 5d3c06a0c1..2a45d7ac3b 100644
--- a/adapters/repos/db/queue/scheduler.go
+++ b/adapters/repos/db/queue/scheduler.go
@@ -441,7 +441,11 @@ func (s *Scheduler) dispatchQueue(q *queueState) (int64, error) {
 
 				q.m.Lock()
 				counter--
-				if counter == 0 {
+				c := counter
+				q.m.Unlock()
+				if c == 0 {
+					// It is important to unlock the queue here
+					// to avoid a deadlock when the last worker calls Done.
 					batch.Done()
 					s.Logger.
 						WithField("queue_id", q.q.ID()).
@@ -449,7 +453,6 @@ func (s *Scheduler) dispatchQueue(q *queueState) (int64, error) {
 						WithField("count", taskCount).
 						Debug("tasks processed")
 				}
-				q.m.Unlock()
 			},
 			onCanceled: func() {
 				q.activeTasks.Decr()
diff --git a/adapters/repos/db/shard_write_batch_objects.go b/adapters/repos/db/shard_write_batch_objects.go
index ac63ee58a9..ec7ce101f0 100644
--- a/adapters/repos/db/shard_write_batch_objects.go
+++ b/adapters/repos/db/shard_write_batch_objects.go
@@ -155,37 +155,24 @@ func (ob *objectsBatcher) storeSingleBatchInLSM(ctx context.Context,
 		return errs
 	}
 
-	wg := &sync.WaitGroup{}
-	concurrencyLimit := make(chan struct{}, _NUMCPU)
+	eg := enterrors.NewErrorGroupWrapper(ob.shard.Index().logger)
+	eg.SetLimit(_NUMCPU)
 
 	for j, object := range batch {
-		wg.Add(1)
 		object := object
 		index := j
-		f := func() {
-			defer wg.Done()
-
-			// Acquire a semaphore to control the concurrency. Otherwise we would
-			// spawn one routine per object here. With very large batch sizes (e.g.
-			// 1000 or 10000+), this isn't helpuful and just leads to more lock
-			// contention down the line – especially when there's lots of text to be
-			// indexed in the inverted index.
-			concurrencyLimit <- struct{}{}
-			defer func() {
-				// Release the semaphore when the goroutine is done.
-				<-concurrencyLimit
-			}()
-
+		f := func() error {
 			if err := ob.storeObjectOfBatchInLSM(ctx, index, object); err != nil {
 				errLock.Lock()
 				errs[index] = err
 				errLock.Unlock()
 			}
+			return nil
 		}
-		enterrors.GoWrapper(f, ob.shard.Index().logger)
+		eg.Go(f)
 
 	}
-	wg.Wait()
+	_ = eg.Wait() // no errors can happen here, this is just for concurrency control
 
 	return errs
 }
diff --git a/adapters/repos/db/vector/cache/cache.go b/adapters/repos/db/vector/cache/cache.go
index 5b6e160f88..3cc7a8331c 100644
--- a/adapters/repos/db/vector/cache/cache.go
+++ b/adapters/repos/db/vector/cache/cache.go
@@ -20,10 +20,9 @@ const DefaultDeletionInterval = 3 * time.Second
 
 type MultiCache[T any] interface {
 	PreloadMulti(docID uint64, ids []uint64, vecs [][]T)
+	PreloadPassage(id uint64, docID uint64, relativeID uint64, vec []T)
 	GetKeys(id uint64) (uint64, uint64)
 	SetKeys(id uint64, docID uint64, relativeID uint64)
-	GrowMultiCache(id uint64)
-	AllMulti() [][][]T
 }
 
 type Cache[T any] interface {
diff --git a/adapters/repos/db/vector/cache/sharded_lock_cache.go b/adapters/repos/db/vector/cache/sharded_lock_cache.go
index a6cd847173..fca160c9d9 100644
--- a/adapters/repos/db/vector/cache/sharded_lock_cache.go
+++ b/adapters/repos/db/vector/cache/sharded_lock_cache.go
@@ -390,10 +390,6 @@ func (s *shardedLockCache[T]) GetKeys(id uint64) (uint64, uint64) {
 	panic("not implemented")
 }
 
-func (s *shardedLockCache[T]) GrowMultiCache(id uint64) {
-	panic("not implemented")
-}
-
 func (s *shardedLockCache[T]) PreloadMulti(docID uint64, ids []uint64, vecs [][]T) {
 	panic("not implemented")
 }
@@ -402,7 +398,7 @@ func (s *shardedLockCache[T]) SetKeys(id uint64, docID uint64, relativeID uint64
 	panic("not implemented")
 }
 
-func (s *shardedLockCache[T]) AllMulti() [][][]T {
+func (s *shardedLockCache[T]) PreloadPassage(id uint64, docID uint64, relativeID uint64, vec []T) {
 	panic("not implemented")
 }
 
@@ -424,9 +420,9 @@ type CacheKeys struct {
 	RelativeID uint64
 }
 
-type shardedMultipleLockCache[T float32 | uint64] struct {
+type shardedMultipleLockCache[T float32 | uint64 | byte] struct {
 	shardedLocks        *common.ShardedRWLocks
-	cache               [][][]T
+	cache               [][]T
 	multipleVectorForID common.MultipleVectorForID[T]
 	normalizeOnRead     bool
 	maxSize             int64
@@ -459,10 +455,7 @@ func NewShardedMultiFloat32LockCache(multipleVecForID common.VectorForID[[]float
 		return vec, nil
 	}
 
-	cache := make([][][]float32, InitialSize)
-	for i := range cache {
-		cache[i] = make([][]float32, RelativeInitialSize)
-	}
+	cache := make([][]float32, InitialSize)
 
 	vc := &shardedMultipleLockCache[float32]{
 		multipleVectorForID: multipleVecForIDValue,
@@ -496,10 +489,7 @@ func NewShardedMultiUInt64LockCache(multipleVecForID common.VectorForID[uint64],
 		return vec, nil
 	}
 
-	cache := make([][][]uint64, InitialSize)
-	for i := range cache {
-		cache[i] = make([][]uint64, RelativeInitialSize)
-	}
+	cache := make([][]uint64, InitialSize)
 
 	vc := &shardedMultipleLockCache[uint64]{
 		multipleVectorForID: multipleVecForIDValue,
@@ -520,11 +510,40 @@ func NewShardedMultiUInt64LockCache(multipleVecForID common.VectorForID[uint64],
 	return vc
 }
 
-func (s *shardedMultipleLockCache[T]) All() [][]T {
-	panic("not implemented")
+func NewShardedMultiByteLockCache(multipleVecForID common.VectorForID[byte], maxSize int,
+	logger logrus.FieldLogger, deletionInterval time.Duration,
+	allocChecker memwatch.AllocChecker,
+) Cache[byte] {
+	multipleVecForIDValue := func(ctx context.Context, id uint64, relativeID uint64) ([]byte, error) {
+		vec, err := multipleVecForID(ctx, id)
+		if err != nil {
+			return nil, err
+		}
+		return vec, nil
+	}
+
+	cache := make([][]byte, InitialSize)
+
+	vc := &shardedMultipleLockCache[byte]{
+		multipleVectorForID: multipleVecForIDValue,
+		cache:               cache,
+		count:               0,
+		maxSize:             int64(maxSize),
+		logger:              logger,
+		shardedLocks:        common.NewDefaultShardedRWLocks(),
+		maintenanceLock:     sync.RWMutex{},
+		deletionInterval:    deletionInterval,
+		allocChecker:        allocChecker,
+		vectorDocID:         make([]CacheKeys, InitialSize),
+	}
+
+	vc.ctx, vc.cancelFn = context.WithCancel(context.Background())
+
+	vc.watchForDeletion()
+	return vc
 }
 
-func (s *shardedMultipleLockCache[T]) AllMulti() [][][]T {
+func (s *shardedMultipleLockCache[T]) All() [][]T {
 	return s.cache
 }
 
@@ -548,18 +567,13 @@ func (s *shardedMultipleLockCache[T]) GetKeysNoLock(id uint64) (uint64, uint64)
 }
 
 func (s *shardedMultipleLockCache[T]) Get(ctx context.Context, id uint64) ([]T, error) {
-	docID, relativeID := s.GetKeys(id)
-
-	s.shardedLocks.RLock(docID)
-	docVecs := s.cache[docID]
-	var vec []T
-	if int(relativeID) < len(docVecs) {
-		vec = docVecs[relativeID]
-	}
-	s.shardedLocks.RUnlock(docID)
+	s.shardedLocks.RLock(id)
+	vec := s.cache[id]
+	s.shardedLocks.RUnlock(id)
 
 	if len(vec) == 0 {
-		return s.handleMultipleCacheMiss(ctx, docID, relativeID)
+		docID, relativeID := s.GetKeys(id)
+		return s.handleMultipleCacheMiss(ctx, id, docID, relativeID)
 	}
 
 	return vec, nil
@@ -570,19 +584,13 @@ func (s *shardedMultipleLockCache[T]) MultiGet(ctx context.Context, ids []uint64
 	errs := make([]error, len(ids))
 
 	for i, id := range ids {
-		var vec []T
-
-		docID, relativeID := s.GetKeys(id)
-
-		s.shardedLocks.RLock(docID)
-		docVecs := s.cache[docID]
-		if int(relativeID) < len(docVecs) {
-			vec = docVecs[relativeID]
-		}
-		s.shardedLocks.RUnlock(docID)
 
+		s.shardedLocks.RLock(id)
+		vec := s.cache[id]
+		s.shardedLocks.RUnlock(id)
 		if len(vec) == 0 {
-			vec, errs[i] = s.handleMultipleCacheMiss(ctx, docID, relativeID)
+			docID, relativeID := s.GetKeys(id)
+			vec, errs[i] = s.handleMultipleCacheMiss(ctx, id, docID, relativeID)
 		}
 
 		out[i] = vec
@@ -592,23 +600,19 @@ func (s *shardedMultipleLockCache[T]) MultiGet(ctx context.Context, ids []uint64
 }
 
 func (s *shardedMultipleLockCache[T]) Delete(ctx context.Context, id uint64) {
-	if int(id) >= len(s.vectorDocID) {
-		return
-	}
-
-	docID, relativeID := s.GetKeys(id)
-	s.shardedLocks.Lock(docID)
-	defer s.shardedLocks.Unlock(docID)
+	s.shardedLocks.Lock(id)
+	defer s.shardedLocks.Unlock(id)
 
-	if s.cache[docID][relativeID] == nil {
+	if int(id) >= len(s.cache) || len(s.cache[id]) == 0 {
 		return
 	}
 
-	s.cache[docID][relativeID] = nil
+	s.cache[id] = nil
+	s.vectorDocID[id] = CacheKeys{}
 	atomic.AddInt64(&s.count, -1)
 }
 
-func (s *shardedMultipleLockCache[T]) handleMultipleCacheMiss(ctx context.Context, docID uint64, relativeID uint64) ([]T, error) {
+func (s *shardedMultipleLockCache[T]) handleMultipleCacheMiss(ctx context.Context, id uint64, docID uint64, relativeID uint64) ([]T, error) {
 	if s.allocChecker != nil {
 		// we don't really know the exact size here, but we don't have to be
 		// accurate. If mem pressure is this high, we basically want to prevent any
@@ -636,14 +640,11 @@ func (s *shardedMultipleLockCache[T]) handleMultipleCacheMiss(ctx context.Contex
 	}
 
 	atomic.AddInt64(&s.count, 1)
-	s.shardedLocks.Lock(docID)
-	if len(s.cache[docID]) <= int(relativeID) {
-		newCacheLine := make([][]T, relativeID+MinimumRelativeGrowthDelta)
-		copy(newCacheLine, s.cache[docID])
-		s.cache[docID] = newCacheLine
+	if len(vec) != 0 {
+		s.shardedLocks.Lock(id)
+		s.cache[id] = vec
+		s.shardedLocks.Unlock(id)
 	}
-	s.cache[docID][relativeID] = vec
-	s.shardedLocks.Unlock(docID)
 
 	return vec, nil
 }
@@ -658,23 +659,30 @@ func (s *shardedMultipleLockCache[T]) UnlockAll() {
 
 func (s *shardedMultipleLockCache[T]) Prefetch(id uint64) {
 	s.shardedLocks.RLock(id)
-	docID, _ := s.GetKeysNoLock(id)
 	defer s.shardedLocks.RUnlock(id)
 
-	prefetchFunc(uintptr(unsafe.Pointer(&s.cache[docID])))
+	prefetchFunc(uintptr(unsafe.Pointer(&s.cache[id])))
 }
 
 func (s *shardedMultipleLockCache[T]) PreloadMulti(docID uint64, ids []uint64, vecs [][]T) {
-	s.shardedLocks.Lock(docID)
-	defer s.shardedLocks.Unlock(docID)
-
 	atomic.AddInt64(&s.count, int64(len(ids)))
-	s.cache[docID] = vecs
 	for i, id := range ids {
+		s.shardedLocks.Lock(id)
+		s.cache[id] = vecs[i]
 		s.vectorDocID[id] = CacheKeys{DocID: docID, RelativeID: uint64(i)}
+		s.shardedLocks.Unlock(id)
 	}
 }
 
+func (s *shardedMultipleLockCache[T]) PreloadPassage(id uint64, docID uint64, relativeID uint64, vec []T) {
+	s.shardedLocks.Lock(id)
+	defer s.shardedLocks.Unlock(id)
+
+	s.cache[id] = vec
+	s.vectorDocID[id] = CacheKeys{DocID: docID, RelativeID: relativeID}
+	atomic.AddInt64(&s.count, int64(1))
+}
+
 func (s *shardedMultipleLockCache[T]) Preload(docID uint64, vec []T) {
 	panic("not implemented")
 }
@@ -711,30 +719,7 @@ func (s *shardedMultipleLockCache[T]) Grow(node uint64) {
 	newVectorDocID := make([]CacheKeys, newSizeVector)
 	copy(newVectorDocID, s.vectorDocID)
 	s.vectorDocID = newVectorDocID
-}
-
-func (s *shardedMultipleLockCache[T]) GrowMultiCache(id uint64) {
-	s.maintenanceLock.RLock()
-	if id < uint64(len(s.cache)) {
-		s.maintenanceLock.RUnlock()
-		return
-	}
-	s.maintenanceLock.RUnlock()
-
-	s.maintenanceLock.Lock()
-	defer s.maintenanceLock.Unlock()
-
-	// make sure cache still needs growing
-	// (it could have grown while waiting for maintenance lock)
-	if id < uint64(len(s.cache)) {
-		return
-	}
-
-	s.shardedLocks.LockAll()
-	defer s.shardedLocks.UnlockAll()
-
-	newSizeCache := id + MinimumIndexGrowthDelta
-	newCache := make([][][]T, newSizeCache)
+	newCache := make([][]T, newSizeVector)
 	copy(newCache, s.cache)
 	s.cache = newCache
 }
@@ -764,7 +749,8 @@ func (s *shardedMultipleLockCache[T]) deleteAllVectors() {
 	s.logger.WithField("action", "hnsw_delete_vector_cache").
 		Debug("deleting full vector cache")
 	for i := range s.cache {
-		s.cache[i] = make([][]T, RelativeInitialSize)
+		s.cache[i] = nil
+		s.vectorDocID[i] = CacheKeys{}
 	}
 
 	atomic.StoreInt64(&s.count, 0)
diff --git a/adapters/repos/db/vector/common/vector_id.go b/adapters/repos/db/vector/common/vector_id.go
index 0748e69bca..0ddd5a8a71 100644
--- a/adapters/repos/db/vector/common/vector_id.go
+++ b/adapters/repos/db/vector/common/vector_id.go
@@ -110,7 +110,7 @@ type VectorUint64Slice struct {
 
 type (
 	VectorForID[T []float32 | []uint64 | float32 | byte | uint64] func(ctx context.Context, id uint64) ([]T, error)
-	MultipleVectorForID[T float32 | uint64]                       func(ctx context.Context, id uint64, relativeID uint64) ([]T, error)
+	MultipleVectorForID[T float32 | uint64 | byte]                func(ctx context.Context, id uint64, relativeID uint64) ([]T, error)
 	TempVectorForID[T []float32 | float32]                        func(ctx context.Context, id uint64, container *VectorSlice) ([]T, error)
 	MultiVectorForID                                              func(ctx context.Context, ids []uint64) ([][]float32, []error)
 )
diff --git a/adapters/repos/db/vector/compressionhelpers/compression.go b/adapters/repos/db/vector/compressionhelpers/compression.go
index 48c1e686f4..58b4c988c8 100644
--- a/adapters/repos/db/vector/compressionhelpers/compression.go
+++ b/adapters/repos/db/vector/compressionhelpers/compression.go
@@ -48,17 +48,18 @@ type CompressionStats interface {
 type VectorCompressor interface {
 	Drop() error
 	GrowCache(size uint64)
-	GrowMultiCache(id uint64)
 	SetCacheMaxSize(size int64)
 	GetCacheMaxSize() int64
 	Delete(ctx context.Context, id uint64)
 	Preload(id uint64, vector []float32)
 	PreloadMulti(docID uint64, ids []uint64, vecs [][]float32)
+	PreloadPassage(id uint64, docID uint64, relativeID uint64, vec []float32)
 	GetKeys(id uint64) (uint64, uint64)
 	SetKeys(id uint64, docID uint64, relativeID uint64)
 	Prefetch(id uint64)
 	CountVectors() int64
 	PrefillCache()
+	PrefillMultiCache(docIDVectors map[uint64][]uint64)
 
 	DistanceBetweenCompressedVectorsFromIDs(ctx context.Context, x, y uint64) (float32, error)
 	NewDistancer(vector []float32) (CompressorDistancer, ReturnDistancerFn)
@@ -87,10 +88,6 @@ func (compressor *quantizedVectorsCompressor[T]) GrowCache(size uint64) {
 	compressor.cache.Grow(size)
 }
 
-func (compressor *quantizedVectorsCompressor[T]) GrowMultiCache(id uint64) {
-	compressor.cache.GrowMultiCache(id)
-}
-
 func (compressor *quantizedVectorsCompressor[T]) SetCacheMaxSize(size int64) {
 	compressor.cache.UpdateMaxSize(size)
 }
@@ -130,15 +127,28 @@ func (compressor *quantizedVectorsCompressor[T]) PreloadMulti(docID uint64, ids
 	for i, vector := range vecs {
 		compressedVectors[i] = compressor.quantizer.Encode(vector)
 	}
+	maxID := ids[0]
 	for i, id := range ids {
 		idBytes := make([]byte, 8)
 		compressor.storeId(idBytes, id)
 		compressor.compressedStore.Bucket(helpers.VectorsCompressedBucketLSM).Put(idBytes, compressor.quantizer.CompressedBytes(compressedVectors[i]))
-		compressor.cache.Grow(id)
+		if id > maxID {
+			maxID = id
+		}
 	}
+	compressor.cache.Grow(maxID)
 	compressor.cache.PreloadMulti(docID, ids, compressedVectors)
 }
 
+func (compressor *quantizedVectorsCompressor[T]) PreloadPassage(id, docID, relativeID uint64, vec []float32) {
+	compressedVector := compressor.quantizer.Encode(vec)
+	idBytes := make([]byte, 8)
+	compressor.storeId(idBytes, id)
+	compressor.compressedStore.Bucket(helpers.VectorsCompressedBucketLSM).Put(idBytes, compressor.quantizer.CompressedBytes(compressedVector))
+	compressor.cache.Grow(id)
+	compressor.cache.PreloadPassage(id, docID, relativeID, compressedVector)
+}
+
 func (compressor *quantizedVectorsCompressor[T]) GetKeys(id uint64) (uint64, uint64) {
 	return compressor.cache.GetKeys(id)
 }
@@ -297,6 +307,58 @@ func (compressor *quantizedVectorsCompressor[T]) PrefillCache() {
 	}).Info("prefilled compressed vector cache")
 }
 
+func (compressor *quantizedVectorsCompressor[T]) PrefillMultiCache(docIDVectors map[uint64][]uint64) {
+	before := time.Now()
+
+	parallel := 2 * runtime.GOMAXPROCS(0)
+	maxID := uint64(0)
+	vecs := make([]VecAndID[T], 0, 10_000)
+
+	it := NewParallelIterator(
+		compressor.compressedStore.Bucket(helpers.VectorsCompressedBucketLSM),
+		parallel, compressor.loadId, compressor.quantizer.FromCompressedBytesWithSubsliceBuffer,
+		compressor.logger)
+	channel := it.IterateAll()
+	if channel == nil {
+		return // nothing to do
+	}
+
+	for v := range channel {
+		vecs = append(vecs, v...)
+	}
+
+	for i := range vecs {
+		if vecs[i].Id > maxID {
+			maxID = vecs[i].Id
+		}
+	}
+
+	compressor.cache.Grow(maxID)
+
+	nodeIDMappings := make(map[uint64]cache.CacheKeys, len(vecs))
+	for docID := range docIDVectors {
+		for relativeID, id := range docIDVectors[docID] {
+			nodeIDMappings[id] = cache.CacheKeys{
+				DocID:      docID,
+				RelativeID: uint64(relativeID),
+			}
+		}
+	}
+	for _, vec := range vecs {
+		docID := nodeIDMappings[vec.Id].DocID
+		relativeID := nodeIDMappings[vec.Id].RelativeID
+		compressor.cache.PreloadPassage(vec.Id, docID, relativeID, vec.Vec)
+	}
+
+	took := time.Since(before)
+	compressor.logger.WithFields(logrus.Fields{
+		"action": "hnsw_compressed_vector_cache_prefill",
+		"count":  len(vecs),
+		"maxID":  maxID,
+		"took":   took,
+	}).Info("prefilled compressed vector cache for multivector")
+}
+
 func (compressor *quantizedVectorsCompressor[T]) PersistCompression(logger CommitLogger) {
 	compressor.quantizer.PersistCompression(logger)
 }
@@ -362,6 +424,67 @@ func RestoreHNSWPQCompressor(
 	return pqVectorsCompressor, nil
 }
 
+func NewHNSWPQMultiCompressor(
+	cfg hnsw.PQConfig,
+	distance distancer.Provider,
+	dimensions int,
+	vectorCacheMaxObjects int,
+	logger logrus.FieldLogger,
+	data [][]float32,
+	store *lsmkv.Store,
+	allocChecker memwatch.AllocChecker,
+) (VectorCompressor, error) {
+	quantizer, err := NewProductQuantizer(cfg, distance, dimensions, logger)
+	if err != nil {
+		return nil, err
+	}
+	pqVectorsCompressor := &quantizedVectorsCompressor[byte]{
+		quantizer:       quantizer,
+		compressedStore: store,
+		storeId:         binary.LittleEndian.PutUint64,
+		loadId:          binary.LittleEndian.Uint64,
+		logger:          logger,
+	}
+	pqVectorsCompressor.initCompressedStore()
+	pqVectorsCompressor.cache = cache.NewShardedMultiByteLockCache(
+		pqVectorsCompressor.getCompressedVectorForID, vectorCacheMaxObjects, logger,
+		0, allocChecker)
+	pqVectorsCompressor.cache.Grow(uint64(len(data)))
+	err = quantizer.Fit(data)
+	if err != nil {
+		return nil, err
+	}
+	return pqVectorsCompressor, nil
+}
+
+func RestoreHNSWPQMultiCompressor(
+	cfg hnsw.PQConfig,
+	distance distancer.Provider,
+	dimensions int,
+	vectorCacheMaxObjects int,
+	logger logrus.FieldLogger,
+	encoders []PQEncoder,
+	store *lsmkv.Store,
+	allocChecker memwatch.AllocChecker,
+) (VectorCompressor, error) {
+	quantizer, err := NewProductQuantizerWithEncoders(cfg, distance, dimensions, encoders, logger)
+	if err != nil {
+		return nil, err
+	}
+	pqVectorsCompressor := &quantizedVectorsCompressor[byte]{
+		quantizer:       quantizer,
+		compressedStore: store,
+		storeId:         binary.LittleEndian.PutUint64,
+		loadId:          binary.LittleEndian.Uint64,
+		logger:          logger,
+	}
+	pqVectorsCompressor.initCompressedStore()
+	pqVectorsCompressor.cache = cache.NewShardedMultiByteLockCache(
+		pqVectorsCompressor.getCompressedVectorForID, vectorCacheMaxObjects, logger, 0,
+		allocChecker)
+	return pqVectorsCompressor, nil
+}
+
 func NewBQCompressor(
 	distance distancer.Provider,
 	vectorCacheMaxObjects int,
@@ -457,6 +580,57 @@ func RestoreHNSWSQCompressor(
 	return sqVectorsCompressor, nil
 }
 
+func NewHNSWSQMultiCompressor(
+	distance distancer.Provider,
+	vectorCacheMaxObjects int,
+	logger logrus.FieldLogger,
+	data [][]float32,
+	store *lsmkv.Store,
+	allocChecker memwatch.AllocChecker,
+) (VectorCompressor, error) {
+	quantizer := NewScalarQuantizer(data, distance)
+	sqVectorsCompressor := &quantizedVectorsCompressor[byte]{
+		quantizer:       quantizer,
+		compressedStore: store,
+		storeId:         binary.BigEndian.PutUint64,
+		loadId:          binary.BigEndian.Uint64,
+		logger:          logger,
+	}
+	sqVectorsCompressor.initCompressedStore()
+	sqVectorsCompressor.cache = cache.NewShardedMultiByteLockCache(
+		sqVectorsCompressor.getCompressedVectorForID, vectorCacheMaxObjects, logger,
+		0, allocChecker)
+	sqVectorsCompressor.cache.Grow(uint64(len(data)))
+	return sqVectorsCompressor, nil
+}
+
+func RestoreHNSWSQMultiCompressor(
+	distance distancer.Provider,
+	vectorCacheMaxObjects int,
+	logger logrus.FieldLogger,
+	a, b float32,
+	dimensions uint16,
+	store *lsmkv.Store,
+	allocChecker memwatch.AllocChecker,
+) (VectorCompressor, error) {
+	quantizer, err := RestoreScalarQuantizer(a, b, dimensions, distance)
+	if err != nil {
+		return nil, err
+	}
+	sqVectorsCompressor := &quantizedVectorsCompressor[byte]{
+		quantizer:       quantizer,
+		compressedStore: store,
+		storeId:         binary.BigEndian.PutUint64,
+		loadId:          binary.BigEndian.Uint64,
+		logger:          logger,
+	}
+	sqVectorsCompressor.initCompressedStore()
+	sqVectorsCompressor.cache = cache.NewShardedMultiByteLockCache(
+		sqVectorsCompressor.getCompressedVectorForID, vectorCacheMaxObjects, logger,
+		0, allocChecker)
+	return sqVectorsCompressor, nil
+}
+
 type quantizedCompressorDistancer[T byte | uint64] struct {
 	compressor *quantizedVectorsCompressor[T]
 	distancer  quantizerDistancer[T]
diff --git a/adapters/repos/db/vector/hnsw/compress.go b/adapters/repos/db/vector/hnsw/compress.go
index 45bb2c7c9c..fd3904336b 100644
--- a/adapters/repos/db/vector/hnsw/compress.go
+++ b/adapters/repos/db/vector/hnsw/compress.go
@@ -26,9 +26,6 @@ func (h *hnsw) compress(cfg ent.UserConfig) error {
 	if !cfg.PQ.Enabled && !cfg.BQ.Enabled && !cfg.SQ.Enabled {
 		return nil
 	}
-	if h.Multivector() {
-		return errors.New("compression is not supported in multivector mode")
-	}
 	h.compressActionLock.Lock()
 	defer h.compressActionLock.Unlock()
 	data := h.cache.All()
@@ -78,18 +75,30 @@ func (h *hnsw) compress(cfg ent.UserConfig) error {
 			}
 
 			var err error
-			h.compressor, err = compressionhelpers.NewHNSWPQCompressor(
-				cfg.PQ, h.distancerProvider, dims, 1e12, h.logger, cleanData, h.store,
-				h.allocChecker)
+			if !h.multivector.Load() {
+				h.compressor, err = compressionhelpers.NewHNSWPQCompressor(
+					cfg.PQ, h.distancerProvider, dims, 1e12, h.logger, cleanData, h.store,
+					h.allocChecker)
+			} else {
+				h.compressor, err = compressionhelpers.NewHNSWPQMultiCompressor(
+					cfg.PQ, h.distancerProvider, dims, 1e12, h.logger, cleanData, h.store,
+					h.allocChecker)
+			}
 			if err != nil {
 				h.pqConfig.Enabled = false
 				return fmt.Errorf("compressing vectors: %w", err)
 			}
 		} else if cfg.SQ.Enabled {
 			var err error
-			h.compressor, err = compressionhelpers.NewHNSWSQCompressor(
-				h.distancerProvider, 1e12, h.logger, cleanData, h.store,
-				h.allocChecker)
+			if !h.multivector.Load() {
+				h.compressor, err = compressionhelpers.NewHNSWSQCompressor(
+					h.distancerProvider, 1e12, h.logger, cleanData, h.store,
+					h.allocChecker)
+			} else {
+				h.compressor, err = compressionhelpers.NewHNSWSQMultiCompressor(
+					h.distancerProvider, 1e12, h.logger, cleanData, h.store,
+					h.allocChecker)
+			}
 			if err != nil {
 				h.sqConfig.Enabled = false
 				return fmt.Errorf("compressing vectors: %w", err)
@@ -98,19 +107,35 @@ func (h *hnsw) compress(cfg ent.UserConfig) error {
 		h.compressor.PersistCompression(h.commitLog)
 	} else {
 		var err error
-		h.compressor, err = compressionhelpers.NewBQCompressor(
-			h.distancerProvider, 1e12, h.logger, h.store, h.allocChecker)
+		if !h.multivector.Load() {
+			h.compressor, err = compressionhelpers.NewBQCompressor(
+				h.distancerProvider, 1e12, h.logger, h.store, h.allocChecker)
+		} else {
+			h.compressor, err = compressionhelpers.NewBQMultiCompressor(
+				h.distancerProvider, 1e12, h.logger, h.store, h.allocChecker)
+		}
 		if err != nil {
 			return err
 		}
 	}
-	compressionhelpers.Concurrently(h.logger, uint64(len(data)),
-		func(index uint64) {
-			if data[index] == nil {
-				return
-			}
-			h.compressor.Preload(index, data[index])
-		})
+	if !h.multivector.Load() {
+		compressionhelpers.Concurrently(h.logger, uint64(len(data)),
+			func(index uint64) {
+				if data[index] == nil {
+					return
+				}
+				h.compressor.Preload(index, data[index])
+			})
+	} else {
+		compressionhelpers.Concurrently(h.logger, uint64(len(data)),
+			func(index uint64) {
+				if len(data[index]) == 0 {
+					return
+				}
+				docID, relativeID := h.cache.GetKeys(index)
+				h.compressor.PreloadPassage(index, docID, relativeID, data[index])
+			})
+	}
 
 	h.compressed.Store(true)
 	h.cache.Drop()
diff --git a/adapters/repos/db/vector/hnsw/index.go b/adapters/repos/db/vector/hnsw/index.go
index 85c8b1a34e..9acdd84741 100644
--- a/adapters/repos/db/vector/hnsw/index.go
+++ b/adapters/repos/db/vector/hnsw/index.go
@@ -315,15 +315,17 @@ func New(cfg Config, uc ent.UserConfig,
 
 	index.multivector.Store(uc.Multivector.Enabled)
 
-	if uc.Multivector.Enabled && (uc.PQ.Enabled || uc.SQ.Enabled || uc.BQ.Enabled) {
-		return nil, errors.New("compression is not supported in multivector mode")
-	}
-
 	if uc.BQ.Enabled {
 		var err error
-		index.compressor, err = compressionhelpers.NewBQCompressor(
-			index.distancerProvider, uc.VectorCacheMaxObjects, cfg.Logger, store,
-			cfg.AllocChecker)
+		if !uc.Multivector.Enabled {
+			index.compressor, err = compressionhelpers.NewBQCompressor(
+				index.distancerProvider, uc.VectorCacheMaxObjects, cfg.Logger, store,
+				cfg.AllocChecker)
+		} else {
+			index.compressor, err = compressionhelpers.NewBQMultiCompressor(
+				index.distancerProvider, uc.VectorCacheMaxObjects, cfg.Logger, store,
+				cfg.AllocChecker)
+		}
 		if err != nil {
 			return nil, err
 		}
diff --git a/adapters/repos/db/vector/hnsw/insert.go b/adapters/repos/db/vector/hnsw/insert.go
index d16944f214..ed9f255fb2 100644
--- a/adapters/repos/db/vector/hnsw/insert.go
+++ b/adapters/repos/db/vector/hnsw/insert.go
@@ -204,23 +204,6 @@ func (h *hnsw) AddMultiBatch(ctx context.Context, docIDs []uint64, vectors [][][
 			h.Unlock()
 		}
 
-		h.RLock()
-		if docID >= h.maxDocID {
-			h.RUnlock()
-			h.Lock()
-			if docID >= h.maxDocID {
-				h.maxDocID = docID
-				if h.compressed.Load() {
-					h.compressor.GrowMultiCache(docID)
-				} else {
-					h.cache.GrowMultiCache(docID)
-				}
-			}
-			h.Unlock()
-		} else {
-			h.RUnlock()
-		}
-
 		ids := make([]uint64, numVectors)
 		for id := range ids {
 			ids[id] = counter + uint64(id)
diff --git a/adapters/repos/db/vector/hnsw/startup.go b/adapters/repos/db/vector/hnsw/startup.go
index f346b72712..aa3d6941c2 100644
--- a/adapters/repos/db/vector/hnsw/startup.go
+++ b/adapters/repos/db/vector/hnsw/startup.go
@@ -147,17 +147,30 @@ func (h *hnsw) restoreFromDisk() error {
 				if h.pqConfig.Segments == 0 {
 					h.pqConfig.Segments = int(data.Dimensions)
 				}
-				h.compressor, err = compressionhelpers.RestoreHNSWPQCompressor(
-					h.pqConfig,
-					h.distancerProvider,
-					int(data.Dimensions),
-					// ToDo: we need to read this value from somewhere
-					1e12,
-					h.logger,
-					data.Encoders,
-					h.store,
-					h.allocChecker,
-				)
+				if !h.multivector.Load() {
+					h.compressor, err = compressionhelpers.RestoreHNSWPQCompressor(
+						h.pqConfig,
+						h.distancerProvider,
+						int(data.Dimensions),
+						// ToDo: we need to read this value from somewhere
+						1e12,
+						h.logger,
+						data.Encoders,
+						h.store,
+						h.allocChecker,
+					)
+				} else {
+					h.compressor, err = compressionhelpers.RestoreHNSWPQMultiCompressor(
+						h.pqConfig,
+						h.distancerProvider,
+						int(data.Dimensions),
+						1e12,
+						h.logger,
+						data.Encoders,
+						h.store,
+						h.allocChecker,
+					)
+				}
 				if err != nil {
 					return errors.Wrap(err, "Restoring compressed data.")
 				}
@@ -165,16 +178,29 @@ func (h *hnsw) restoreFromDisk() error {
 		} else if state.CompressionSQData != nil {
 			data := state.CompressionSQData
 			h.dims = int32(data.Dimensions)
-			h.compressor, err = compressionhelpers.RestoreHNSWSQCompressor(
-				h.distancerProvider,
-				1e12,
-				h.logger,
-				data.A,
-				data.B,
-				data.Dimensions,
-				h.store,
-				h.allocChecker,
-			)
+			if !h.multivector.Load() {
+				h.compressor, err = compressionhelpers.RestoreHNSWSQCompressor(
+					h.distancerProvider,
+					1e12,
+					h.logger,
+					data.A,
+					data.B,
+					data.Dimensions,
+					h.store,
+					h.allocChecker,
+				)
+			} else {
+				h.compressor, err = compressionhelpers.RestoreHNSWSQMultiCompressor(
+					h.distancerProvider,
+					1e12,
+					h.logger,
+					data.A,
+					data.B,
+					data.Dimensions,
+					h.store,
+					h.allocChecker,
+				)
+			}
 			if err != nil {
 				return errors.Wrap(err, "Restoring compressed data.")
 			}
@@ -219,9 +245,7 @@ func (h *hnsw) restoreDocMappings() error {
 			relativeID = 0
 			prevDocID = docID
 		}
-		if h.compressed.Load() {
-			h.compressor.SetKeys(node.ID(), docID, relativeID)
-		} else {
+		if !h.compressed.Load() {
 			h.cache.SetKeys(node.ID(), docID, relativeID)
 		}
 		h.Lock()
@@ -245,11 +269,6 @@ func (h *hnsw) restoreDocMappings() error {
 	h.vecIDcounter = maxNodeID + 1
 	h.maxDocID = maxDocID
 	h.Unlock()
-	if h.compressed.Load() {
-		h.compressor.GrowMultiCache(maxDocID)
-	} else {
-		h.cache.GrowMultiCache(maxDocID)
-	}
 	return nil
 }
 
@@ -313,7 +332,11 @@ func (h *hnsw) prefillCache() {
 
 		var err error
 		if h.compressed.Load() {
-			h.compressor.PrefillCache()
+			if !h.multivector.Load() {
+				h.compressor.PrefillCache()
+			} else {
+				h.compressor.PrefillMultiCache(h.docIDVectors)
+			}
 		} else {
 			err = newVectorCachePrefiller(h.cache, h, h.logger).Prefill(ctx, limit)
 		}
diff --git a/client/authz/get_roles_for_user_parameters.go b/client/authz/get_roles_for_user_parameters.go
index 24da77f44a..835fd8706b 100644
--- a/client/authz/get_roles_for_user_parameters.go
+++ b/client/authz/get_roles_for_user_parameters.go
@@ -25,6 +25,7 @@ import (
 	"github.com/go-openapi/runtime"
 	cr "github.com/go-openapi/runtime/client"
 	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
 )
 
 // NewGetRolesForUserParams creates a new GetRolesForUserParams object,
@@ -78,6 +79,12 @@ type GetRolesForUserParams struct {
 	*/
 	ID string
 
+	/* IncludeFullRoles.
+
+	   Whether to include detailed role information needed the roles permission
+	*/
+	IncludeFullRoles *bool
+
 	/* UserType.
 
 	   The type of user
@@ -101,7 +108,18 @@ func (o *GetRolesForUserParams) WithDefaults() *GetRolesForUserParams {
 //
 // All values with no default are reset to their zero value.
 func (o *GetRolesForUserParams) SetDefaults() {
-	// no default values defined for this parameter
+	var (
+		includeFullRolesDefault = bool(false)
+	)
+
+	val := GetRolesForUserParams{
+		IncludeFullRoles: &includeFullRolesDefault,
+	}
+
+	val.timeout = o.timeout
+	val.Context = o.Context
+	val.HTTPClient = o.HTTPClient
+	*o = val
 }
 
 // WithTimeout adds the timeout to the get roles for user params
@@ -148,6 +166,17 @@ func (o *GetRolesForUserParams) SetID(id string) {
 	o.ID = id
 }
 
+// WithIncludeFullRoles adds the includeFullRoles to the get roles for user params
+func (o *GetRolesForUserParams) WithIncludeFullRoles(includeFullRoles *bool) *GetRolesForUserParams {
+	o.SetIncludeFullRoles(includeFullRoles)
+	return o
+}
+
+// SetIncludeFullRoles adds the includeFullRoles to the get roles for user params
+func (o *GetRolesForUserParams) SetIncludeFullRoles(includeFullRoles *bool) {
+	o.IncludeFullRoles = includeFullRoles
+}
+
 // WithUserType adds the userType to the get roles for user params
 func (o *GetRolesForUserParams) WithUserType(userType string) *GetRolesForUserParams {
 	o.SetUserType(userType)
@@ -172,6 +201,23 @@ func (o *GetRolesForUserParams) WriteToRequest(r runtime.ClientRequest, reg strf
 		return err
 	}
 
+	if o.IncludeFullRoles != nil {
+
+		// query param includeFullRoles
+		var qrIncludeFullRoles bool
+
+		if o.IncludeFullRoles != nil {
+			qrIncludeFullRoles = *o.IncludeFullRoles
+		}
+		qIncludeFullRoles := swag.FormatBool(qrIncludeFullRoles)
+		if qIncludeFullRoles != "" {
+
+			if err := r.SetQueryParam("includeFullRoles", qIncludeFullRoles); err != nil {
+				return err
+			}
+		}
+	}
+
 	// path param userType
 	if err := r.SetPathParam("userType", o.UserType); err != nil {
 		return err
diff --git a/go.mod b/go.mod
index 74ed8cec37..4e0735bf86 100644
--- a/go.mod
+++ b/go.mod
@@ -32,7 +32,7 @@ require (
 	github.com/go-openapi/strfmt v0.23.0
 	github.com/go-openapi/swag v0.23.0
 	github.com/go-openapi/validate v0.24.0
-	github.com/golang-jwt/jwt/v4 v4.5.1
+	github.com/golang-jwt/jwt/v4 v4.5.2
 	github.com/google/uuid v1.6.0
 	github.com/googleapis/gax-go/v2 v2.14.1
 	github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
diff --git a/go.sum b/go.sum
index cb9b06c87b..5e98659c13 100644
--- a/go.sum
+++ b/go.sum
@@ -267,8 +267,8 @@ github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
-github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
+github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/openapi-specs/schema.json b/openapi-specs/schema.json
index e6240f3536..0b8d86f2e2 100644
--- a/openapi-specs/schema.json
+++ b/openapi-specs/schema.json
@@ -2507,7 +2507,7 @@
     },
     "description": "# Introduction\n Weaviate is an open source, AI-native vector database that helps developers create intuitive and reliable AI-powered applications. \n ### Base Path \nThe base path for the Weaviate server is structured as `[YOUR-WEAVIATE-HOST]:[PORT]/v1`. As an example, if you wish to access the `schema` endpoint on a local instance, you would navigate to `http://localhost:8080/v1/schema`. Ensure you replace `[YOUR-WEAVIATE-HOST]` and `[PORT]` with your actual server host and port number respectively. \n ### Questions? \nIf you have any comments or questions, please feel free to reach out to us at the community forum [https://forum.weaviate.io/](https://forum.weaviate.io/). \n### Issues? \nIf you find a bug or want to file a feature request, please open an issue on our GitHub repository for [Weaviate](https://github.com/weaviate/weaviate). \n### Want more documentation? \nFor a quickstart, code examples, concepts and more, please visit our [documentation page](https://weaviate.io/developers/weaviate).",
     "title": "Weaviate",
-    "version": "1.31.0-dev"
+    "version": "1.30.0-rc.0"
   },
   "parameters": {
     "CommonAfterParameterQuery": {
@@ -3728,6 +3728,14 @@
             "type": "string",
             "enum": ["oidc", "db"],
             "description": "The type of user"
+          },
+          {
+            "in": "query",
+            "name": "includeFullRoles",
+            "required": false,
+            "type": "boolean",
+            "default": false,
+            "description": "Whether to include detailed role information needed the roles permission"
           }
         ],
         "responses": {
diff --git a/tools/dev/config.runtime-overrides.yaml b/tools/dev/config.runtime-overrides.yaml
new file mode 100644
index 0000000000..0577806eeb
--- /dev/null
+++ b/tools/dev/config.runtime-overrides.yaml
@@ -0,0 +1,3 @@
+maximum_allowed_collections_count: 8
+auto_schema_enabled: true
+async_replication_disabled: false
diff --git a/tools/dev/run_dev_server.sh b/tools/dev/run_dev_server.sh
index 90556a7113..6c76107353 100755
--- a/tools/dev/run_dev_server.sh
+++ b/tools/dev/run_dev_server.sh
@@ -59,6 +59,9 @@ case $CONFIG in
       CLUSTER_GOSSIP_BIND_PORT="7100" \
       CLUSTER_DATA_BIND_PORT="7101" \
       RAFT_BOOTSTRAP_EXPECT=1 \
+      RUNTIME_OVERRIDES_ENABLED=true \
+      RUNTIME_OVERRIDES_PATH="${PWD}/tools/dev/config.runtime-overrides.yaml" \
+      RUNTIME_OVERRIDES_LOAD_INTERVAL=30s \
       go_run ./cmd/weaviate-server \
         --scheme http \
         --host "127.0.0.1" \
@@ -981,7 +984,7 @@ case $CONFIG in
     ;;
   local-prometheus)
     echo "Starting monitoring setup..."
-      
+
     cleanup() {
       echo "Cleaning up existing containers and volumes..."
       docker stop prometheus grafana 2>/dev/null || true
@@ -990,14 +993,14 @@ case $CONFIG in
       docker network rm monitoring 2>/dev/null || true
       echo "Cleanup complete."
     }
-    
+
     cleanup
 
     echo "Creating new setup..."
-    
+
     docker network create monitoring 2>/dev/null || true
 
-    
+
     echo "Starting Prometheus..."
     docker run -d \
       --name prometheus \
@@ -1007,7 +1010,7 @@ case $CONFIG in
       -v "$(pwd)/tools/dev/prometheus_config/prometheus.yml:/etc/prometheus/prometheus.yml" \
       prom/prometheus
 
-    
+
     echo "Starting Grafana..."
     docker run -d \
       --name grafana \
@@ -1019,7 +1022,7 @@ case $CONFIG in
       -v "$(pwd)/tools/dev/grafana/dashboards/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml" \
       -v "$(pwd)/tools/dev/grafana/dashboards:/etc/grafana/dashboards" \
       grafana/grafana
-    
+
     echo "Waiting for services to start..."
     sleep 5
 
diff --git a/usecases/config/runtime/manager.go b/usecases/config/runtime/manager.go
index 7cbc665526..52839a54fe 100644
--- a/usecases/config/runtime/manager.go
+++ b/usecases/config/runtime/manager.go
@@ -177,7 +177,7 @@ func (cm *ConfigManager[T]) loop(ctx context.Context) error {
 		select {
 		case <-ticker.C:
 			if err := cm.loadConfig(); err != nil {
-				cm.log.Error("loading runtime config every %s failed, using old config: %w", cm.interval, err)
+				cm.log.Errorf("loading runtime config every %s failed, using old config: %w", cm.interval, err)
 			}
 		case <-sighup:
 			if err := cm.loadConfig(); err != nil {
diff --git a/usecases/config/runtimeconfig.go b/usecases/config/runtimeconfig.go
index 3ff2d4046f..7074861483 100644
--- a/usecases/config/runtimeconfig.go
+++ b/usecases/config/runtimeconfig.go
@@ -13,6 +13,8 @@ package config
 
 import (
 	"bytes"
+	"errors"
+	"io"
 
 	"gopkg.in/yaml.v2"
 )
@@ -27,7 +29,7 @@ type WeaviateRuntimeConfig struct {
 	// AutoSchemaEnabled marking it as pointer type to differentiate default values.
 	AutoSchemaEnabled *bool `json:"auto_schema_enabled" yaml:"auto_schema_enabled"`
 
-	AsyncReplicationDisabled bool `json:"async_replication_disabled" yaml:"async_replication_disabled"`
+	AsyncReplicationDisabled *bool `json:"async_replication_disabled" yaml:"async_replication_disabled"`
 
 	// config manager that keep the runtime config up to date
 	cm ConfigManager
@@ -55,7 +57,7 @@ func (rc *WeaviateRuntimeConfig) GetAutoSchemaEnabled() *bool {
 
 func (rc *WeaviateRuntimeConfig) GetAsyncReplicationDisabled() *bool {
 	if cfg, err := rc.cm.Config(); err == nil {
-		return &cfg.AsyncReplicationDisabled
+		return cfg.AsyncReplicationDisabled
 	}
 	return nil
 }
@@ -65,7 +67,10 @@ func ParseYaml(buf []byte) (*WeaviateRuntimeConfig, error) {
 
 	dec := yaml.NewDecoder(bytes.NewReader(buf))
 	dec.SetStrict(true)
-	if err := dec.Decode(&conf); err != nil {
+
+	// Am empty runtime yaml file is still a valid file. So treating io.EOF as
+	// non-error case returning default values of conf.
+	if err := dec.Decode(&conf); err != nil && !errors.Is(err, io.EOF) {
 		return nil, err
 	}
 	return &conf, nil
diff --git a/usecases/objects/validation/vector_validation.go b/usecases/objects/validation/vector_validation.go
index abe5fb92c2..2943442913 100644
--- a/usecases/objects/validation/vector_validation.go
+++ b/usecases/objects/validation/vector_validation.go
@@ -42,12 +42,18 @@ func (v *Validator) vector(ctx context.Context, class *models.Class,
 		return fmt.Errorf("collection %v configuration does not have single vector index", class.Class)
 	}
 
-	if len(class.VectorConfig) == 0 && len(incomingObject.Vectors) > 0 {
-		var targetVectors []string
-		for name := range incomingObject.Vectors {
-			targetVectors = append(targetVectors, name)
+	var incomingTargetVectors []string
+	for name := range incomingObject.Vectors {
+		_, ok := class.VectorConfig[name]
+		if !ok {
+			return fmt.Errorf("collection %v does not have configuration for vector %s", class.Class, name)
 		}
-		return fmt.Errorf("collection %v is configured without multiple named vectors, but received named vectors: %v", class.Class, targetVectors)
+
+		incomingTargetVectors = append(incomingTargetVectors, name)
+	}
+
+	if len(class.VectorConfig) == 0 && len(incomingTargetVectors) > 0 {
+		return fmt.Errorf("collection %v is configured without multiple named vectors, but received named vectors: %v", class.Class, incomingTargetVectors)
 	}
 
 	return nil


Test patch:
diff --git a/adapters/handlers/rest/authz/handlers_authz_get_roles_for_user_test.go b/adapters/handlers/rest/authz/handlers_authz_get_roles_for_user_test.go
index 240b8c969b..d4b250f43d 100644
--- a/adapters/handlers/rest/authz/handlers_authz_get_roles_for_user_test.go
+++ b/adapters/handlers/rest/authz/handlers_authz_get_roles_for_user_test.go
@@ -44,6 +44,8 @@ func TestGetRolesForUserSuccess(t *testing.T) {
 	returnedPolices := map[string][]authorization.Policy{
 		"testRole": policies,
 	}
+	truep := true
+	falseP := false
 	tests := []struct {
 		name        string
 		params      authz.GetRolesForUserParams
@@ -53,8 +55,19 @@ func TestGetRolesForUserSuccess(t *testing.T) {
 		{
 			name: "success",
 			params: authz.GetRolesForUserParams{
-				ID:       "testUser",
-				UserType: string(userType),
+				ID:               "testUser",
+				UserType:         string(userType),
+				IncludeFullRoles: &truep,
+			},
+			principal:   &models.Principal{Username: "user1", UserType: models.UserTypeDb},
+			expectAuthz: true,
+		},
+		{
+			name: "success without roles",
+			params: authz.GetRolesForUserParams{
+				ID:               "testUser",
+				UserType:         string(userType),
+				IncludeFullRoles: &falseP,
 			},
 			principal:   &models.Principal{Username: "user1", UserType: models.UserTypeDb},
 			expectAuthz: true,
@@ -62,8 +75,9 @@ func TestGetRolesForUserSuccess(t *testing.T) {
 		{
 			name: "success for own user",
 			params: authz.GetRolesForUserParams{
-				ID:       "user1",
-				UserType: string(userType),
+				ID:               "user1",
+				UserType:         string(userType),
+				IncludeFullRoles: &truep,
 			},
 			principal:   &models.Principal{Username: "user1", UserType: models.UserTypeDb},
 			expectAuthz: false,
@@ -74,7 +88,9 @@ func TestGetRolesForUserSuccess(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			if tt.expectAuthz {
 				authorizer.On("Authorize", tt.principal, authorization.READ, authorization.Users(tt.params.ID)[0]).Return(nil)
-				authorizer.On("Authorize", tt.principal, authorization.VerbWithScope(authorization.READ, authorization.ROLE_SCOPE_ALL), authorization.Roles("testRole")[0]).Return(nil)
+				if *tt.params.IncludeFullRoles {
+					authorizer.On("Authorize", tt.principal, authorization.VerbWithScope(authorization.READ, authorization.ROLE_SCOPE_ALL), authorization.Roles("testRole")[0]).Return(nil)
+				}
 			}
 			controller.On("GetRolesForUser", tt.params.ID, models.UserTypeDb).Return(returnedPolices, nil)
 			controller.On("GetUsers", tt.params.ID).Return(map[string]*apikey.User{"testUser": {}}, nil)
@@ -99,7 +115,12 @@ func TestGetRolesForUserSuccess(t *testing.T) {
 				},
 			}
 			expectedRoles := models.RolesListResponse(roles)
-			assert.Equal(t, expectedRoles, parsed.Payload)
+			if *tt.params.IncludeFullRoles {
+				assert.Equal(t, expectedRoles, parsed.Payload)
+			} else {
+				assert.Nil(t, parsed.Payload[0].Permissions)
+			}
+			assert.Equal(t, *roles[0].Name, *parsed.Payload[0].Name)
 		})
 	}
 }
@@ -112,13 +133,15 @@ func TestGetRolesForUserForbidden(t *testing.T) {
 		authorizeErr  error
 		expectedError string
 	}
+	truep := true
 	userType := models.UserTypeDb
 	tests := []testCase{
 		{
 			name: "authorization error no access to role",
 			params: authz.GetRolesForUserParams{
-				ID:       "testUser",
-				UserType: string(userType),
+				ID:               "testUser",
+				UserType:         string(userType),
+				IncludeFullRoles: &truep,
 			},
 			principal:     &models.Principal{Username: "user1", UserType: userType},
 			authorizeErr:  fmt.Errorf("authorization error"),
diff --git a/adapters/repos/db/shard_test.go b/adapters/repos/db/shard_test.go
index cfeb93aa49..41bc79dc28 100644
--- a/adapters/repos/db/shard_test.go
+++ b/adapters/repos/db/shard_test.go
@@ -25,6 +25,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/go-openapi/strfmt"
 	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -36,6 +37,7 @@ import (
 	schemaConfig "github.com/weaviate/weaviate/entities/schema/config"
 	"github.com/weaviate/weaviate/entities/storagestate"
 	"github.com/weaviate/weaviate/entities/storobj"
+	"github.com/weaviate/weaviate/entities/vectorindex/dynamic"
 	"github.com/weaviate/weaviate/entities/vectorindex/flat"
 	"github.com/weaviate/weaviate/entities/vectorindex/hnsw"
 )
@@ -850,3 +852,54 @@ func TestShard_resetDimensionsLSM(t *testing.T) {
 	require.Nil(t, idx.drop())
 	require.Nil(t, os.RemoveAll(idx.Config.RootPath))
 }
+
+func TestShard_UpgradeIndex(t *testing.T) {
+	t.Setenv("ASYNC_INDEXING", "true")
+	t.Setenv("QUEUE_SCHEDULER_INTERVAL", "1ms")
+
+	cfg := dynamic.NewDefaultUserConfig()
+	cfg.Threshold = 1000
+
+	ctx := context.Background()
+	className := "SomeClass"
+	var opts []func(*Index)
+	opts = append(opts, func(i *Index) {
+		i.vectorIndexUserConfig = cfg
+	})
+
+	shd, _ := testShardWithSettings(t, ctx, &models.Class{Class: className}, cfg, false, true /* withCheckpoints */, opts...)
+
+	defer func(path string) {
+		err := os.RemoveAll(path)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}(shd.Index().Config.RootPath)
+
+	amount := 1000
+	for i := 0; i < 3; i++ {
+		objs := make([]*storobj.Object, 0, amount)
+		for j := 0; j < amount; j++ {
+			objs = append(objs, &storobj.Object{
+				MarshallerVersion: 1,
+				Object: models.Object{
+					ID:    strfmt.UUID(uuid.NewString()),
+					Class: className,
+				},
+				Vector: make([]float32, 1536),
+			})
+		}
+
+		errs := shd.PutObjectBatch(ctx, objs)
+		for _, err := range errs {
+			require.Nil(t, err)
+		}
+	}
+
+	q := shd.Queue()
+
+	// wait for the queue to be empty
+	require.EventuallyWithT(t, func(t *assert.CollectT) {
+		assert.Zero(t, q.Size())
+	}, 300*time.Second, 1*time.Second)
+}
diff --git a/adapters/repos/db/vector/cache/sharded_lock_cache_test.go b/adapters/repos/db/vector/cache/sharded_lock_cache_test.go
index d253c1684d..61eb094745 100644
--- a/adapters/repos/db/vector/cache/sharded_lock_cache_test.go
+++ b/adapters/repos/db/vector/cache/sharded_lock_cache_test.go
@@ -249,3 +249,112 @@ func TestGetAllInCurrentLock(t *testing.T) {
 		}
 	})
 }
+
+func TestMultiVectorCacheGrowth(t *testing.T) {
+	logger, _ := test.NewNullLogger()
+	var multivecForId common.VectorForID[[]float32] = nil
+	id := 100_000
+	expectedCount := int64(0)
+
+	vectorCache := NewShardedMultiFloat32LockCache(multivecForId, 1_000_000, logger, false, time.Duration(10_000), nil)
+	initialSize := vectorCache.Len()
+	assert.Less(t, int(initialSize), id)
+	assert.Equal(t, expectedCount, vectorCache.CountVectors())
+
+	vectorCache.Grow(uint64(id))
+	size1stGrow := vectorCache.Len()
+	assert.Greater(t, int(size1stGrow), id)
+	assert.Equal(t, expectedCount, vectorCache.CountVectors())
+
+	vectorCache.Grow(uint64(id))
+	size2ndGrow := vectorCache.Len()
+	assert.Equal(t, size1stGrow, size2ndGrow)
+	assert.Equal(t, expectedCount, vectorCache.CountVectors())
+}
+
+func TestMultiCache_ParallelGrowth(t *testing.T) {
+	// no asserts
+	// ensures there is no "index out of range" panic on get
+
+	logger, _ := test.NewNullLogger()
+	var multivecForId common.VectorForID[[]float32] = func(context.Context, uint64) ([][]float32, error) { return nil, nil }
+	vectorCache := NewShardedMultiFloat32LockCache(multivecForId, 1_000_000, logger, false, time.Second, nil)
+
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	count := 10_000
+	maxNode := 100_000
+
+	wg := new(sync.WaitGroup)
+	wg.Add(count)
+	for i := 0; i < count; i++ {
+		node := uint64(r.Intn(maxNode))
+		go func(node uint64) {
+			defer wg.Done()
+
+			vectorCache.Grow(node)
+		}(node)
+	}
+
+	wg.Wait()
+}
+
+func TestMultiCacheCleanup(t *testing.T) {
+	logger, _ := test.NewNullLogger()
+	var multivecForId common.VectorForID[[]float32] = nil
+
+	maxSize := 10
+	batchSize := maxSize - 1
+	deletionInterval := 200 * time.Millisecond // overwrite default deletionInterval of 3s
+	sleepMs := deletionInterval + 100*time.Millisecond
+
+	t.Run("count is not reset on unnecessary deletion", func(t *testing.T) {
+		vectorCache := NewShardedMultiFloat32LockCache(multivecForId, maxSize, logger, false, deletionInterval, nil)
+		shardedLockCache, ok := vectorCache.(*shardedMultipleLockCache[float32])
+		assert.True(t, ok)
+
+		for i := 0; i < batchSize; i++ {
+			shardedLockCache.PreloadMulti(uint64(i), []uint64{uint64(i)}, [][]float32{{float32(i), float32(i)}})
+		}
+		time.Sleep(sleepMs) // wait for deletion to fire
+
+		assert.Equal(t, batchSize, int(shardedLockCache.CountVectors()))
+		assert.Equal(t, batchSize, countMultiCached(shardedLockCache))
+
+		shardedLockCache.Drop()
+
+		assert.Equal(t, 0, int(shardedLockCache.count))
+		assert.Equal(t, 0, countMultiCached(shardedLockCache))
+	})
+
+	t.Run("deletion clears cache and counter when maxSize exceeded", func(t *testing.T) {
+		vectorCache := NewShardedMultiFloat32LockCache(multivecForId, maxSize, logger, false, deletionInterval, nil)
+		shardedLockCache, ok := vectorCache.(*shardedMultipleLockCache[float32])
+		assert.True(t, ok)
+
+		for b := 0; b < 2; b++ {
+			for i := 0; i < batchSize; i++ {
+				id := b*batchSize + i
+				shardedLockCache.PreloadMulti(uint64(id), []uint64{uint64(id)}, [][]float32{{float32(id), float32(id)}})
+			}
+			time.Sleep(sleepMs) // wait for deletion to fire, 2nd should clean the cache
+		}
+
+		assert.Equal(t, 0, int(shardedLockCache.CountVectors()))
+		assert.Equal(t, 0, countMultiCached(shardedLockCache))
+
+		shardedLockCache.Drop()
+	})
+}
+
+func countMultiCached(c *shardedMultipleLockCache[float32]) int {
+	c.shardedLocks.LockAll()
+	defer c.shardedLocks.UnlockAll()
+
+	count := 0
+	for _, vec := range c.cache {
+		if vec != nil {
+			count++
+		}
+	}
+	return count
+}
diff --git a/adapters/repos/db/vector/hnsw/multivector_hnsw_test.go b/adapters/repos/db/vector/hnsw/multivector_hnsw_test.go
index 633a7739f2..e8d841d9d9 100644
--- a/adapters/repos/db/vector/hnsw/multivector_hnsw_test.go
+++ b/adapters/repos/db/vector/hnsw/multivector_hnsw_test.go
@@ -162,25 +162,28 @@ func TestMultiVectorCompressHnsw(t *testing.T) {
 
 	userConfigTest := []ent.UserConfig{
 		{
-			MaxConnections: maxConnections,
-			EFConstruction: efConstruction,
-			EF:             ef,
-			Multivector:    ent.MultivectorConfig{Enabled: true},
-			BQ:             ent.BQConfig{Enabled: true},
-		},
-		{
-			MaxConnections: maxConnections,
-			EFConstruction: efConstruction,
-			EF:             ef,
-			Multivector:    ent.MultivectorConfig{Enabled: true},
-			PQ:             ent.PQConfig{Enabled: true},
+			MaxConnections:        maxConnections,
+			EFConstruction:        efConstruction,
+			EF:                    ef,
+			VectorCacheMaxObjects: 1e12,
+			Multivector:           ent.MultivectorConfig{Enabled: true},
+			PQ: ent.PQConfig{
+				Enabled: true,
+				Encoder: ent.PQEncoder{
+					Type:         ent.PQEncoderTypeKMeans,
+					Distribution: ent.PQEncoderDistributionLogNormal,
+				},
+				Centroids:     256,
+				TrainingLimit: 100_000,
+			},
 		},
 		{
-			MaxConnections: maxConnections,
-			EFConstruction: efConstruction,
-			EF:             ef,
-			Multivector:    ent.MultivectorConfig{Enabled: true},
-			SQ:             ent.SQConfig{Enabled: true},
+			MaxConnections:        maxConnections,
+			EFConstruction:        efConstruction,
+			EF:                    ef,
+			VectorCacheMaxObjects: 1e12,
+			Multivector:           ent.MultivectorConfig{Enabled: true},
+			SQ:                    ent.SQConfig{Enabled: true},
 		},
 	}
 
@@ -192,14 +195,16 @@ func TestMultiVectorCompressHnsw(t *testing.T) {
 				MakeCommitLoggerThunk: MakeNoopCommitLogger,
 				DistanceProvider:      distancer.NewDotProductProvider(),
 				VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) {
-					docID, relativeID := vectorIndex.cache.GetKeys(id)
-					return multiVectors[docID][relativeID], nil
+					return []float32{0}, errors.New("can not use VectorForIDThunk with multivector")
+				},
+				MultiVectorForIDThunk: func(ctx context.Context, id uint64) ([][]float32, error) {
+					return multiVectors[id], nil
 				},
 				TempMultiVectorForIDThunk: func(ctx context.Context, id uint64, container *common.VectorSlice) ([][]float32, error) {
 					return multiVectors[id], nil
 				},
 			}, userConfig, cyclemanager.NewCallbackGroupNoop(), testinghelpers.NewDummyStore(t))
-			require.ErrorContains(t, err, "compression is not supported in multivector mode")
+			require.Nil(t, err)
 		}
 	})
 
@@ -211,8 +216,10 @@ func TestMultiVectorCompressHnsw(t *testing.T) {
 				MakeCommitLoggerThunk: MakeNoopCommitLogger,
 				DistanceProvider:      distancer.NewDotProductProvider(),
 				VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) {
-					docID, relativeID := vectorIndex.cache.GetKeys(id)
-					return multiVectors[docID][relativeID], nil
+					return []float32{0}, errors.New("can not use VectorForIDThunk with multivector")
+				},
+				MultiVectorForIDThunk: func(ctx context.Context, id uint64) ([][]float32, error) {
+					return multiVectors[id], nil
 				},
 				TempMultiVectorForIDThunk: func(ctx context.Context, id uint64, container *common.VectorSlice) ([][]float32, error) {
 					return multiVectors[id], nil
@@ -226,8 +233,91 @@ func TestMultiVectorCompressHnsw(t *testing.T) {
 				cyclemanager.NewCallbackGroupNoop(), testinghelpers.NewDummyStore(t))
 			require.Nil(t, err)
 			vectorIndex = index
+			err = vectorIndex.AddMulti(context.Background(), 0, multiVectors[0])
+			require.Nil(t, err)
 			err = vectorIndex.compress(userConfig)
-			require.ErrorContains(t, err, "compression is not supported in multivector mode")
+			require.Nil(t, err)
+		}
+	})
+}
+
+func TestMultiVectorBQHnsw(t *testing.T) {
+	var vectorIndex *hnsw
+	ctx := context.Background()
+	maxConnections := 8
+	efConstruction := 64
+	ef := 64
+	k := 10
+
+	t.Run("importing into hnsw", func(t *testing.T) {
+		index, err := New(Config{
+			RootPath:              "doesnt-matter-as-committlogger-is-mocked-out",
+			ID:                    "recallbenchmark",
+			MakeCommitLoggerThunk: MakeNoopCommitLogger,
+			DistanceProvider:      distancer.NewDotProductProvider(),
+			VectorForIDThunk: func(ctx context.Context, id uint64) ([]float32, error) {
+				docID, relativeID := vectorIndex.cache.GetKeys(id)
+				return multiVectors[docID][relativeID], nil
+			},
+			TempMultiVectorForIDThunk: func(ctx context.Context, id uint64, container *common.VectorSlice) ([][]float32, error) {
+				return multiVectors[id], nil
+			},
+		}, ent.UserConfig{
+			MaxConnections: maxConnections,
+			EFConstruction: efConstruction,
+			EF:             ef,
+			Multivector:    ent.MultivectorConfig{Enabled: true},
+			// BQ:             ent.BQConfig{Enabled: true},
+		}, cyclemanager.NewCallbackGroupNoop(), testinghelpers.NewDummyStore(t))
+		require.Nil(t, err)
+		vectorIndex = index
+
+		for i, vec := range multiVectors {
+			err := vectorIndex.AddMulti(ctx, uint64(i), vec)
+			require.Nil(t, err)
+		}
+		uc := ent.UserConfig{
+			MaxConnections: maxConnections,
+			EFConstruction: efConstruction,
+			EF:             ef,
+			Multivector: ent.MultivectorConfig{
+				Enabled: true,
+			},
+			BQ: ent.BQConfig{
+				Enabled: true,
+			},
+		}
+		err = vectorIndex.compress(uc)
+		require.Nil(t, err)
+	})
+
+	t.Run("inspect a query", func(t *testing.T) {
+		for i, query := range multiQueries {
+			ids, _, err := vectorIndex.SearchByMultiVector(ctx, query, k, nil)
+			require.Nil(t, err)
+			require.Equal(t, expectedResults[i], ids)
+		}
+	})
+
+	t.Run("delete some nodes", func(t *testing.T) {
+		// Delete the first node and then add back
+		newExpectedResults := [][]uint64{
+			{1, 2},
+			{2, 1},
+		}
+		err := vectorIndex.DeleteMulti(0)
+		require.Nil(t, err)
+		for i, query := range multiQueries {
+			ids, _, err := vectorIndex.SearchByMultiVector(ctx, query, k, nil)
+			require.Nil(t, err)
+			require.Equal(t, newExpectedResults[i], ids)
+		}
+		err = vectorIndex.AddMulti(ctx, 0, multiVectors[0])
+		require.Nil(t, err)
+		for i, query := range multiQueries {
+			ids, _, err := vectorIndex.SearchByMultiVector(ctx, query, k, nil)
+			require.Nil(t, err)
+			require.Equal(t, expectedResults[i], ids)
 		}
 	})
 }
diff --git a/adapters/repos/db/vector/hnsw/vector_cache_prefiller_test.go b/adapters/repos/db/vector/hnsw/vector_cache_prefiller_test.go
index cbb7faf55e..9e58532b00 100644
--- a/adapters/repos/db/vector/hnsw/vector_cache_prefiller_test.go
+++ b/adapters/repos/db/vector/hnsw/vector_cache_prefiller_test.go
@@ -163,15 +163,11 @@ func (f *fakeCache) SetKeys(id uint64, docID uint64, relativeID uint64) {
 	panic("not implemented")
 }
 
-func (f *fakeCache) GrowMultiCache(id uint64) {
-	panic("not implemented")
-}
-
 func (f *fakeCache) PreloadMulti(docID uint64, ids []uint64, vecs [][]float32) {
 	panic("not implemented")
 }
 
-func (f *fakeCache) AllMulti() [][][]float32 {
+func (f *fakeCache) PreloadPassage(id uint64, docID uint64, relativeID uint64, vec []float32) {
 	panic("not implemented")
 }
 
diff --git a/test/acceptance/authz/authz_deprecated_test.go b/test/acceptance/authz/authz_deprecated_test.go
index 086a7f86f1..ed93fd045c 100644
--- a/test/acceptance/authz/authz_deprecated_test.go
+++ b/test/acceptance/authz/authz_deprecated_test.go
@@ -71,7 +71,7 @@ func TestDeprecatedEndpoints(t *testing.T) {
 		)
 		require.NoError(t, err)
 
-		RolesDbUser := helper.GetRolesForUser(t, customUser, adminKey)
+		RolesDbUser := helper.GetRolesForUser(t, customUser, adminKey, true)
 		require.Len(t, RolesDbUser, 1)
 		require.Equal(t, testRoleName, *RolesDbUser[0].Name)
 	})
@@ -85,7 +85,7 @@ func TestDeprecatedEndpoints(t *testing.T) {
 		helper.AssignRoleToUser(t, adminKey, testRoleName, customUser)
 		helper.AssignRoleToUserOIDC(t, adminKey, testRoleName, customUser)
 
-		RolesDbUser := helper.GetRolesForUser(t, customUser, adminKey)
+		RolesDbUser := helper.GetRolesForUser(t, customUser, adminKey, true)
 		require.Len(t, RolesDbUser, 1)
 		require.Equal(t, testRoleName, *RolesDbUser[0].Name)
 
@@ -107,7 +107,7 @@ func TestDeprecatedEndpoints(t *testing.T) {
 
 		helper.AssignRoleToUser(t, adminKey, testRoleName, customUser)
 
-		resp, err := helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser), clientAuth)
+		resp, err := helper.Client(t).Authz.GetRolesForUserDeprecated(authz.NewGetRolesForUserDeprecatedParams().WithID(customUser), clientAuth)
 		require.NoError(t, err)
 		require.NotNil(t, resp)
 		require.Len(t, resp.Payload, 1)
@@ -120,7 +120,7 @@ func TestDeprecatedEndpoints(t *testing.T) {
 
 		// no duplicates after also assigning to OIDC
 		helper.AssignRoleToUserOIDC(t, adminKey, testRoleName, customUser)
-		resp, err = helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser), clientAuth)
+		resp, err = helper.Client(t).Authz.GetRolesForUserDeprecated(authz.NewGetRolesForUserDeprecatedParams().WithID(customUser), clientAuth)
 		require.NoError(t, err)
 		require.NotNil(t, resp)
 		require.Len(t, resp.Payload, 1)
@@ -133,7 +133,7 @@ func TestDeprecatedEndpoints(t *testing.T) {
 
 		// remove from DB user, OIDC still has role
 		helper.RevokeRoleFromUser(t, adminKey, testRoleName, customUser)
-		resp, err = helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser), clientAuth)
+		resp, err = helper.Client(t).Authz.GetRolesForUserDeprecated(authz.NewGetRolesForUserDeprecatedParams().WithID(customUser), clientAuth)
 		require.NoError(t, err)
 		require.NotNil(t, resp)
 		require.Len(t, resp.Payload, 1)
diff --git a/test/acceptance/authz/oidc_test.go b/test/acceptance/authz/oidc_test.go
index f1452b3ede..936b8068eb 100644
--- a/test/acceptance/authz/oidc_test.go
+++ b/test/acceptance/authz/oidc_test.go
@@ -124,7 +124,7 @@ func TestRbacWithOIDC(t *testing.T) {
 				var notFound *authz.GetRolesForUserNotFound
 				require.True(t, errors.As(err, &notFound))
 			} else {
-				rolesDB := helper.GetRolesForUser(t, customUser, tokenAdmin)
+				rolesDB := helper.GetRolesForUser(t, customUser, tokenAdmin, true)
 				require.Len(t, rolesDB, 0)
 			}
 
diff --git a/test/acceptance/authz/roles_test.go b/test/acceptance/authz/roles_test.go
index 88db15fc9a..686c76d08d 100644
--- a/test/acceptance/authz/roles_test.go
+++ b/test/acceptance/authz/roles_test.go
@@ -15,7 +15,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"sort"
 	"sync"
 	"testing"
 	"time"
@@ -251,17 +250,12 @@ func TestAuthzRolesJourney(t *testing.T) {
 		res, err := helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(adminUser).WithUserType(string(models.UserTypeDb)), clientAuth)
 		require.Nil(t, err)
 		require.Equal(t, 2, len(res.Payload))
-
-		names := make([]string, 2)
-		for i, role := range res.Payload {
-			names[i] = *role.Name
+		names := make([]string, len(res.Payload))
+		for i := range res.Payload {
+			names[i] = *res.Payload[i].Name
 		}
-		sort.Strings(names)
-
-		roles := []string{existingRole, testRoleName}
-		sort.Strings(roles)
 
-		require.Equal(t, roles, names)
+		require.ElementsMatch(t, names, []string{existingRole, testRoleName})
 	})
 
 	t.Run("get users for role after assignment", func(t *testing.T) {
@@ -344,7 +338,7 @@ func TestAuthzRolesRemoveAlsoAssignments(t *testing.T) {
 	})
 
 	t.Run("get role assigned to user", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, testUser, adminKey)
+		roles := helper.GetRolesForUser(t, testUser, adminKey, true)
 		require.Equal(t, 1, len(roles))
 	})
 
@@ -357,7 +351,7 @@ func TestAuthzRolesRemoveAlsoAssignments(t *testing.T) {
 	})
 
 	t.Run("get role assigned to user expected none", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, testUser, adminKey)
+		roles := helper.GetRolesForUser(t, testUser, adminKey, false)
 		require.Equal(t, 0, len(roles))
 	})
 }
@@ -763,7 +757,7 @@ func TestAuthzRoleScopeMatching(t *testing.T) {
 		helper.AssignRoleToUser(t, adminKey, limitedRole, limitedUser)
 
 		// Verify role assignment and permissions
-		roles := helper.GetRolesForUser(t, limitedUser, adminKey)
+		roles := helper.GetRolesForUser(t, limitedUser, adminKey, false)
 		require.Equal(t, 1, len(roles))
 		require.Equal(t, limitedRole, *roles[0].Name)
 	})
@@ -1128,3 +1122,107 @@ func TestRolesUserExistence(t *testing.T) {
 		require.Error(t, err)
 	})
 }
+
+func TestGetRolesForUserPermission(t *testing.T) {
+	adminKey := "admin-key"
+	adminUser := "admin-user"
+
+	customUser := "custom-user"
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+	compose, err := docker.New().WithWeaviate().WithApiKey().WithUserApiKey(adminUser, adminKey).WithUserApiKey(customUser, "a").WithDynamicUsers().
+		WithRBAC().WithRbacAdmins(adminUser).Start(ctx)
+	require.Nil(t, err)
+
+	defer func() {
+		helper.ResetClient()
+		require.NoError(t, compose.Terminate(ctx))
+		cancel()
+	}()
+	helper.SetupClient(compose.GetWeaviate().URI())
+
+	all := "*"
+
+	userRoleName := "userRole"
+	helper.DeleteRole(t, adminKey, userRoleName)
+	defer helper.DeleteRole(t, adminKey, userRoleName)
+	helper.CreateRole(t, adminKey, &models.Role{
+		Name: &userRoleName,
+		Permissions: []*models.Permission{
+			{
+				Action: String(authorization.ReadUsers),
+				Users: &models.PermissionUsers{
+					Users: &all,
+				},
+			},
+		},
+	})
+
+	roleRoleName := "roleRole"
+	helper.DeleteRole(t, adminKey, roleRoleName)
+	defer helper.DeleteRole(t, adminKey, roleRoleName)
+	helper.CreateRole(t, adminKey, &models.Role{
+		Name: &roleRoleName,
+		Permissions: []*models.Permission{
+			{
+				Action: String(authorization.ReadRoles),
+				Roles: &models.PermissionRoles{
+					Role: &all,
+				},
+			},
+		},
+	})
+
+	helper.AssignRoleToUser(t, adminKey, userRoleName, customUser)
+
+	userName := "user"
+	userKey := helper.CreateUser(t, userName, adminKey)
+	defer helper.DeleteUser(t, userName, adminKey)
+
+	falsep := false
+	truep := true
+	t.Run("No permissions", func(t *testing.T) {
+		_, err := helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser).WithUserType(string(models.UserTypeDb)), helper.CreateAuth(userKey))
+		require.Error(t, err)
+	})
+
+	t.Run("With user permission", func(t *testing.T) {
+		helper.AssignRoleToUser(t, adminKey, userRoleName, userName)
+		defer helper.RevokeRoleFromUser(t, adminKey, userRoleName, userName)
+
+		// can get role names
+		resp, err := helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser).WithUserType(string(models.UserTypeDb)).WithIncludeFullRoles(&falsep), helper.CreateAuth(userKey))
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		require.Len(t, resp.Payload, 1)
+		require.Equal(t, userRoleName, *resp.Payload[0].Name)
+		require.Nil(t, resp.Payload[0].Permissions)
+
+		// cannot get all roles
+		_, err = helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser).WithUserType(string(models.UserTypeDb)).WithIncludeFullRoles(&truep), helper.CreateAuth(userKey))
+		require.Error(t, err)
+	})
+
+	t.Run("With user and role permission", func(t *testing.T) {
+		helper.AssignRoleToUser(t, adminKey, userRoleName, userName)
+		helper.AssignRoleToUser(t, adminKey, roleRoleName, userName)
+		defer helper.RevokeRoleFromUser(t, adminKey, userRoleName, userName)
+		defer helper.RevokeRoleFromUser(t, adminKey, roleRoleName, userName)
+
+		// can get role names
+		resp, err := helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser).WithUserType(string(models.UserTypeDb)).WithIncludeFullRoles(&falsep), helper.CreateAuth(userKey))
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		require.Len(t, resp.Payload, 1)
+		require.Equal(t, userRoleName, *resp.Payload[0].Name)
+		require.Nil(t, resp.Payload[0].Permissions)
+
+		// can get all roles
+		resp, err = helper.Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(customUser).WithUserType(string(models.UserTypeDb)).WithIncludeFullRoles(&truep), helper.CreateAuth(userKey))
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		require.Len(t, resp.Payload, 1)
+		require.Equal(t, userRoleName, *resp.Payload[0].Name)
+		require.NotNil(t, resp.Payload[0].Permissions)
+	})
+}
diff --git a/test/acceptance/authz/users_test.go b/test/acceptance/authz/users_test.go
index b1e36f21a0..54bcc773df 100644
--- a/test/acceptance/authz/users_test.go
+++ b/test/acceptance/authz/users_test.go
@@ -49,12 +49,12 @@ func TestAuthzRolesForUsers(t *testing.T) {
 	})
 
 	t.Run("role exists for admin", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, adminUser, adminKey)
+		roles := helper.GetRolesForUser(t, adminUser, adminKey, false)
 		require.Equal(t, 1, len(roles))
 	})
 
 	t.Run("get empty roles for existing user without role", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, customUser, adminKey)
+		roles := helper.GetRolesForUser(t, customUser, adminKey, false)
 		require.Equal(t, 0, len(roles))
 	})
 
@@ -96,7 +96,7 @@ func TestAuthzRolesAndUserHaveTheSameName(t *testing.T) {
 		require.Equal(t, authorization.CreateCollections, *role.Permissions[0].Action)
 		require.Equal(t, "*", *role.Permissions[0].Collections.Collection)
 
-		roles := helper.GetRolesForUser(t, similar, adminKey)
+		roles := helper.GetRolesForUser(t, similar, adminKey, true)
 		require.Equal(t, 1, len(roles))
 		require.NotNil(t, role)
 		require.Equal(t, similar, *role.Name)
@@ -199,9 +199,11 @@ func TestUserPermissions(t *testing.T) {
 		helper.AssignRoleToUser(t, adminKey, roleNameReadRoles, customUser)
 
 		// revoking works after user has appropriate rights
-		require.Len(t, helper.GetRolesForUser(t, customUser, adminKey), 3)
+		roles := helper.GetRolesForUser(t, customUser, customKey, true)
+		require.Len(t, roles, 3)
 		helper.RevokeRoleFromUser(t, customKey, otherRoleName, customUser)
-		require.Len(t, helper.GetRolesForUser(t, customUser, adminKey), 2)
+		roles = helper.GetRolesForUser(t, customUser, customKey, true)
+		require.Len(t, roles, 2)
 
 		helper.RevokeRoleFromUser(t, adminKey, roleNameUpdate, customUser)
 		helper.RevokeRoleFromUser(t, adminKey, roleNameReadRoles, customUser)
@@ -264,13 +266,13 @@ func TestReadUserPermissions(t *testing.T) {
 	helper.AssignRoleToUser(t, adminKey, otherRoleName, secondUser)
 
 	t.Run("admin can return roles", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, secondUser, adminKey)
+		roles := helper.GetRolesForUser(t, secondUser, adminKey, true)
 		require.NotNil(t, roles)
 		require.Len(t, roles, 1)
 	})
 
 	t.Run("user can return roles for themselves", func(t *testing.T) {
-		roles := helper.GetRolesForUser(t, secondUser, secondKey)
+		roles := helper.GetRolesForUser(t, secondUser, secondKey, true)
 		require.NotNil(t, roles)
 		require.Len(t, roles, 1)
 	})
@@ -285,7 +287,7 @@ func TestReadUserPermissions(t *testing.T) {
 	t.Run("add permission", func(t *testing.T) {
 		helper.AssignRoleToUser(t, adminKey, roleNameReadUsers, customUser)
 		helper.AssignRoleToUser(t, adminKey, roleNameReadRoles, customUser)
-		roles := helper.GetRolesForUser(t, secondUser, customKey)
+		roles := helper.GetRolesForUser(t, secondUser, customKey, false)
 		require.NotNil(t, roles)
 		require.Len(t, roles, 1)
 
@@ -296,7 +298,7 @@ func TestReadUserPermissions(t *testing.T) {
 	t.Run("check returns", func(t *testing.T) {
 		helper.RevokeRoleFromUser(t, adminKey, roleNameReadUsers, customUser)
 		helper.AssignRoleToUser(t, adminKey, roleNameReadUsers, customUser)
-		roles := helper.GetRolesForUser(t, customUser, customKey)
+		roles := helper.GetRolesForUser(t, customUser, customKey, true)
 		require.NotNil(t, roles)
 		require.Len(t, roles, 1)
 		require.Len(t, roles[0].Permissions, 1)
@@ -478,14 +480,14 @@ func TestUserEndpoint(t *testing.T) {
 		// create user and assign roles
 		helper.CreateUser(t, testUserName, adminKey)
 		helper.AssignRoleToUser(t, adminKey, deleteUserRoleName, testUserName)
-		testUserRoles := helper.GetRolesForUser(t, testUserName, adminKey)
-		require.Len(t, testUserRoles, 1)
+		rolesRet := helper.GetRolesForUser(t, testUserName, adminKey, false)
+		require.Len(t, rolesRet, 1)
 
 		// delete user and recreate with same name => role assignment should be gone
 		helper.DeleteUser(t, testUserName, adminKey)
 		helper.CreateUser(t, testUserName, adminKey)
-		testUserRolesNew := helper.GetRolesForUser(t, testUserName, adminKey)
-		require.Len(t, testUserRolesNew, 0)
+		rolesRet = helper.GetRolesForUser(t, testUserName, adminKey, false)
+		require.Len(t, rolesRet, 0)
 	})
 }
 
diff --git a/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_objects_mixed.go b/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_objects_mixed.go
index da58793d4c..33af121e01 100644
--- a/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_objects_mixed.go
+++ b/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_objects_mixed.go
@@ -13,11 +13,14 @@ package test_suits
 
 import (
 	"context"
+	"fmt"
 	"testing"
 
+	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	wvt "github.com/weaviate/weaviate-go-client/v5/weaviate"
+	"github.com/weaviate/weaviate-go-client/v5/weaviate/fault"
 	"github.com/weaviate/weaviate/entities/models"
 	"github.com/weaviate/weaviate/entities/schema"
 )
@@ -209,6 +212,54 @@ func testCreateSchemaWithMixedVectorizers(host string) func(t *testing.T) {
 					assert.Equal(t, vector, models.Vector(afterUpdateVectors[targetVector]))
 				}
 			})
+
+			t.Run("check BYOV vector names for existence when inserting", func(t *testing.T) {
+				_, err = client.Data().Creator().
+					WithClassName(className).
+					WithID(id3).
+					WithProperties(map[string]interface{}{
+						"text": "Lorem ipsum dolor sit amet",
+					}).
+					WithVectors(models.Vectors{
+						"non_existent_vector": []float32{0.1, 0.2, 0.3},
+					}).
+					Do(ctx)
+				require.Error(t, err)
+				var clientError *fault.WeaviateClientError
+				require.True(t, errors.As(err, &clientError))
+				require.Equal(t, 422, clientError.StatusCode)
+				require.Contains(t, clientError.Msg, fmt.Sprintf("collection %s does not have configuration for vector non_existent_vector", className))
+			})
+
+			t.Run("check BYOV vector names for existence when updating", func(t *testing.T) {
+				err = client.Data().Updater().
+					WithClassName(className).
+					WithID(id1).
+					WithVectors(models.Vectors{
+						"non_existent_vector": []float32{0.1, 0.2, 0.3},
+					}).
+					Do(ctx)
+				require.Error(t, err)
+				var clientError *fault.WeaviateClientError
+				require.True(t, errors.As(err, &clientError))
+				require.Equal(t, 422, clientError.StatusCode)
+				require.Contains(t, clientError.Msg, fmt.Sprintf("collection %s does not have configuration for vector non_existent_vector", className))
+			})
+
+			t.Run("check BYOV vector names for existence when merge updating", func(t *testing.T) {
+				err = client.Data().Updater().WithMerge().
+					WithClassName(className).
+					WithID(id1).
+					WithVectors(models.Vectors{
+						"non_existent_vector": []float32{0.1, 0.2, 0.3},
+					}).
+					Do(ctx)
+				require.Error(t, err)
+				var clientError *fault.WeaviateClientError
+				require.True(t, errors.As(err, &clientError))
+				require.Equal(t, 422, clientError.StatusCode)
+				require.Contains(t, clientError.Msg, fmt.Sprintf("collection %s does not have configuration for vector non_existent_vector", className))
+			})
 		})
 	}
 }
diff --git a/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_test_data.go b/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_test_data.go
index 1dd100cb92..aa5cfee330 100644
--- a/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_test_data.go
+++ b/test/acceptance_with_go_client/named_vectors_tests/test_suits/named_vectors_test_data.go
@@ -47,6 +47,7 @@ var (
 	text2vecTransformers                = "text2vec-transformers"
 	id1                                 = "00000000-0000-0000-0000-000000000001"
 	id2                                 = "00000000-0000-0000-0000-000000000002"
+	id3                                 = "00000000-0000-0000-0000-000000000003"
 )
 
 var targetVectors = []string{
diff --git a/test/helper/rbac.go b/test/helper/rbac.go
index 212de4ba7a..0aead16a44 100644
--- a/test/helper/rbac.go
+++ b/test/helper/rbac.go
@@ -37,10 +37,10 @@ func GetRoles(t *testing.T, key string) []*models.Role {
 	return resp.Payload
 }
 
-func GetRolesForUser(t *testing.T, user, key string) []*models.Role {
+func GetRolesForUser(t *testing.T, user, key string, includeRoles bool) []*models.Role {
 	t.Helper()
 	userType := models.UserTypeDb
-	resp, err := Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(user).WithUserType(string(userType)), CreateAuth(key))
+	resp, err := Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(user).WithUserType(string(userType)).WithIncludeFullRoles(&includeRoles), CreateAuth(key))
 	AssertRequestOk(t, resp, err, nil)
 	require.Nil(t, err)
 	return resp.Payload
@@ -48,8 +48,9 @@ func GetRolesForUser(t *testing.T, user, key string) []*models.Role {
 
 func GetRolesForUserOIDC(t *testing.T, user, key string) []*models.Role {
 	t.Helper()
+	truep := true
 	userType := models.UserTypeOidc
-	resp, err := Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(user).WithUserType(string(userType)), CreateAuth(key))
+	resp, err := Client(t).Authz.GetRolesForUser(authz.NewGetRolesForUserParams().WithID(user).WithUserType(string(userType)).WithIncludeFullRoles(&truep), CreateAuth(key))
 	AssertRequestOk(t, resp, err, nil)
 	require.Nil(t, err)
 	return resp.Payload
diff --git a/usecases/config/runtimeconfig_test.go b/usecases/config/runtimeconfig_test.go
index 2f0c661f96..3cea86c472 100644
--- a/usecases/config/runtimeconfig_test.go
+++ b/usecases/config/runtimeconfig_test.go
@@ -53,6 +53,38 @@ func TestRuntimeConfig(t *testing.T) {
 		val := rm.GetMaximumAllowedCollectionsCount()
 		require.Nil(t, val)
 	})
+
+	t.Run("async replicsation disabled not being set should return nil", func(t *testing.T) {
+		cm.c.AsyncReplicationDisabled = nil
+		val := rm.GetAsyncReplicationDisabled()
+		require.Nil(t, val)
+	})
+}
+
+func TestParseYaml(t *testing.T) {
+	t.Run("empty bytes shouldn't return error", func(t *testing.T) {
+		b := []byte("")
+		v, err := ParseYaml(b)
+		require.NoError(t, err)
+		require.NotNil(t, v)
+	})
+	t.Run("strict parsing should fail for non-existing field", func(t *testing.T) {
+		val := `
+maximum_allowed_collections_count: 5
+`
+		b := []byte(val)
+		v, err := ParseYaml(b)
+		require.NoError(t, err)
+		require.NotNil(t, v)
+
+		val = `
+maximum_allowed_collections_count: 5
+non_exist_filed: 78
+`
+		b = []byte(val)
+		_, err = ParseYaml(b)
+		require.Error(t, err)
+	})
 }
 
 type mockManager struct {
diff --git a/usecases/objects/validation/vector_validation_test.go b/usecases/objects/validation/vector_validation_test.go
index 80f07affed..281d522d6b 100644
--- a/usecases/objects/validation/vector_validation_test.go
+++ b/usecases/objects/validation/vector_validation_test.go
@@ -63,6 +63,15 @@ func TestVectors(t *testing.T) {
 			},
 			expErr: true,
 		},
+		"non existent named vectors": {
+			class: &models.Class{
+				VectorConfig: map[string]models.VectorConfig{"first": {}, "second": {}},
+			},
+			obj: &models.Object{
+				Vectors: models.Vectors{"third": []float32{1, 2, 3}},
+			},
+			expErr: true,
+		},
 		"mixed vectors": {
 			class: &models.Class{
 				Vectorizer:      "legacy",