Repository: weaviate/weaviate
Pull request #3058 (2023-05-23 21:10:18)

Support remaining single tenant object operations
### What's being changed:

Multi-tenancy support for the following endpoints:
- PATCH
- HEAD
- POST reference
- DELETE reference
- PUT reference

### 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.



Patch:
diff --git a/adapters/handlers/rest/doc.go b/adapters/handlers/rest/doc.go
index 7b755cc67d..eb700d94a7 100644
--- a/adapters/handlers/rest/doc.go
+++ b/adapters/handlers/rest/doc.go
@@ -18,7 +18,7 @@
 //	  https
 //	Host: localhost
 //	BasePath: /v1
-//	Version: 1.19.5
+//	Version: 1.20.0-prealpha
 //	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 184b99a047..464f769988 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.19.5"
+    "version": "1.20.0-prealpha"
   },
   "basePath": "/v1",
   "paths": {
@@ -1348,6 +1348,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1412,6 +1415,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1494,6 +1500,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1577,6 +1586,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1660,6 +1672,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1794,6 +1809,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1853,6 +1871,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -1958,6 +1979,9 @@ func init() {
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -4446,7 +4470,7 @@ func init() {
       "url": "https://github.com/weaviate",
       "email": "hello@weaviate.io"
     },
-    "version": "1.19.5"
+    "version": "1.20.0-prealpha"
   },
   "basePath": "/v1",
   "paths": {
@@ -5812,6 +5836,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -5879,6 +5909,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -5964,6 +6000,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -6050,6 +6092,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -6136,6 +6184,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -6276,6 +6330,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -6338,6 +6398,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
@@ -6446,6 +6512,12 @@ func init() {
             "description": "Determines how many replicas must acknowledge a request before it is considered successful",
             "name": "consistency_level",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "Specifies the tenant in a request targeting a multi-tenant class",
+            "name": "tenant_key",
+            "in": "query"
           }
         ],
         "responses": {
diff --git a/adapters/handlers/rest/handlers_objects.go b/adapters/handlers/rest/handlers_objects.go
index 4dca73d91a..62dbf9db9b 100644
--- a/adapters/handlers/rest/handlers_objects.go
+++ b/adapters/handlers/rest/handlers_objects.go
@@ -46,24 +46,28 @@ type ModulesProvider interface {
 type objectsManager interface {
 	AddObject(context.Context, *models.Principal, *models.Object,
 		*additional.ReplicationProperties, string) (*models.Object, error)
-	ValidateObject(context.Context, *models.Principal, *models.Object, *additional.ReplicationProperties) error
-	GetObject(_ context.Context, _ *models.Principal, class string, _ strfmt.UUID,
-		_ additional.Properties, _ *additional.ReplicationProperties, _ string) (*models.Object, error)
-	DeleteObject(_ context.Context, _ *models.Principal, class string,
-		_ strfmt.UUID, _ *additional.ReplicationProperties, _ string) error
-	UpdateObject(_ context.Context, _ *models.Principal, class string, _ strfmt.UUID,
-		_ *models.Object, _ *additional.ReplicationProperties, _ string) (*models.Object, error)
-	HeadObject(ctx context.Context, principal *models.Principal, class string,
-		id strfmt.UUID, repl *additional.ReplicationProperties) (bool, *uco.Error)
+	ValidateObject(context.Context, *models.Principal,
+		*models.Object, *additional.ReplicationProperties) error
+	GetObject(context.Context, *models.Principal, string, strfmt.UUID,
+		additional.Properties, *additional.ReplicationProperties, string) (*models.Object, error)
+	DeleteObject(context.Context, *models.Principal, string,
+		strfmt.UUID, *additional.ReplicationProperties, string) error
+	UpdateObject(context.Context, *models.Principal, string, strfmt.UUID,
+		*models.Object, *additional.ReplicationProperties, string) (*models.Object, error)
+	HeadObject(ctx context.Context, principal *models.Principal, class string, id strfmt.UUID,
+		repl *additional.ReplicationProperties, tenantKey string) (bool, *uco.Error)
 	GetObjects(context.Context, *models.Principal, *int64, *int64,
 		*string, *string, *string, additional.Properties) ([]*models.Object, error)
-	Query(ctx context.Context, principal *models.Principal, params *uco.QueryParams) ([]*models.Object, *uco.Error)
-	MergeObject(context.Context, *models.Principal, *models.Object, *additional.ReplicationProperties) *uco.Error
-	AddObjectReference(context.Context, *models.Principal, *uco.AddReferenceInput, *additional.ReplicationProperties) *uco.Error
+	Query(ctx context.Context, principal *models.Principal,
+		params *uco.QueryParams) ([]*models.Object, *uco.Error)
+	MergeObject(context.Context, *models.Principal, *models.Object,
+		*additional.ReplicationProperties, string) *uco.Error
+	AddObjectReference(context.Context, *models.Principal, *uco.AddReferenceInput,
+		*additional.ReplicationProperties, string) *uco.Error
 	UpdateObjectReferences(context.Context, *models.Principal,
-		*uco.PutReferenceInput, *additional.ReplicationProperties) *uco.Error
-	DeleteObjectReference(context.Context, *models.Principal,
-		*uco.DeleteReferenceInput, *additional.ReplicationProperties) *uco.Error
+		*uco.PutReferenceInput, *additional.ReplicationProperties, string) *uco.Error
+	DeleteObjectReference(context.Context, *models.Principal, *uco.DeleteReferenceInput,
+		*additional.ReplicationProperties, string) *uco.Error
 	GetObjectsClass(ctx context.Context, principal *models.Principal, id strfmt.UUID) (*models.Class, error)
 }
 
@@ -347,8 +351,10 @@ func (h *objectHandlers) headObject(params objects.ObjectsClassHeadParams,
 			WithPayload(errPayloadFromSingleErr(err))
 	}
 
+	tenantKey := getTenantKey(params.TenantKey)
+
 	exists, objErr := h.manager.HeadObject(params.HTTPRequest.Context(),
-		principal, params.ClassName, params.ID, repl)
+		principal, params.ClassName, params.ID, repl, tenantKey)
 	if objErr != nil {
 		switch {
 		case objErr.Forbidden():
@@ -377,7 +383,9 @@ func (h *objectHandlers) patchObject(params objects.ObjectsClassPatchParams, pri
 			WithPayload(errPayloadFromSingleErr(err))
 	}
 
-	objErr := h.manager.MergeObject(params.HTTPRequest.Context(), principal, updates, repl)
+	tenantKey := getTenantKey(params.TenantKey)
+
+	objErr := h.manager.MergeObject(params.HTTPRequest.Context(), principal, updates, repl, tenantKey)
 	if objErr != nil {
 		switch {
 		case objErr.NotFound():
@@ -414,8 +422,9 @@ func (h *objectHandlers) addObjectReference(
 			WithPayload(errPayloadFromSingleErr(err))
 	}
 
-	objErr := h.manager.AddObjectReference(
-		params.HTTPRequest.Context(), principal, &input, repl)
+	tenantKey := getTenantKey(params.TenantKey)
+
+	objErr := h.manager.AddObjectReference(params.HTTPRequest.Context(), principal, &input, repl, tenantKey)
 	if objErr != nil {
 		switch {
 		case objErr.Forbidden():
@@ -451,7 +460,9 @@ func (h *objectHandlers) putObjectReferences(params objects.ObjectsClassReferenc
 			WithPayload(errPayloadFromSingleErr(err))
 	}
 
-	objErr := h.manager.UpdateObjectReferences(params.HTTPRequest.Context(), principal, &input, repl)
+	tenantKey := getTenantKey(params.TenantKey)
+
+	objErr := h.manager.UpdateObjectReferences(params.HTTPRequest.Context(), principal, &input, repl, tenantKey)
 	if objErr != nil {
 		switch {
 		case objErr.Forbidden():
@@ -487,7 +498,9 @@ func (h *objectHandlers) deleteObjectReference(params objects.ObjectsClassRefere
 			WithPayload(errPayloadFromSingleErr(err))
 	}
 
-	objErr := h.manager.DeleteObjectReference(params.HTTPRequest.Context(), principal, &input, repl)
+	tenantKey := getTenantKey(params.TenantKey)
+
+	objErr := h.manager.DeleteObjectReference(params.HTTPRequest.Context(), principal, &input, repl, tenantKey)
 	if objErr != nil {
 		switch objErr.Code {
 		case uco.StatusForbidden:
diff --git a/adapters/handlers/rest/operations/objects/objects_class_head_parameters.go b/adapters/handlers/rest/operations/objects/objects_class_head_parameters.go
index 5ec564bf6e..8a83e30505 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_head_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_head_parameters.go
@@ -57,6 +57,10 @@ type ObjectsClassHeadParams struct {
 	  In: path
 	*/
 	ID strfmt.UUID
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -84,6 +88,11 @@ func (o *ObjectsClassHeadParams) BindRequest(r *http.Request, route *middleware.
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -154,3 +163,21 @@ func (o *ObjectsClassHeadParams) validateID(formats strfmt.Registry) error {
 	}
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsClassHeadParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_class_head_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_class_head_urlbuilder.go
index b916ef4933..41865e4d97 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_head_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_head_urlbuilder.go
@@ -31,6 +31,7 @@ type ObjectsClassHeadURL struct {
 	ID        strfmt.UUID
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -88,6 +89,14 @@ func (o *ObjectsClassHeadURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_class_patch_parameters.go b/adapters/handlers/rest/operations/objects/objects_class_patch_parameters.go
index 3a83f497a1..1556f1575c 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_patch_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_patch_parameters.go
@@ -63,6 +63,10 @@ type ObjectsClassPatchParams struct {
 	  In: path
 	*/
 	ID strfmt.UUID
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -112,6 +116,11 @@ func (o *ObjectsClassPatchParams) BindRequest(r *http.Request, route *middleware
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -182,3 +191,21 @@ func (o *ObjectsClassPatchParams) validateID(formats strfmt.Registry) error {
 	}
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsClassPatchParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_class_patch_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_class_patch_urlbuilder.go
index 138fad08bb..ae28273971 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_patch_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_patch_urlbuilder.go
@@ -31,6 +31,7 @@ type ObjectsClassPatchURL struct {
 	ID        strfmt.UUID
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -88,6 +89,14 @@ func (o *ObjectsClassPatchURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_create_parameters.go b/adapters/handlers/rest/operations/objects/objects_class_references_create_parameters.go
index 8b5d7256ae..28fd5fb7ea 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_create_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_create_parameters.go
@@ -70,6 +70,10 @@ type ObjectsClassReferencesCreateParams struct {
 	  In: path
 	*/
 	PropertyName string
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -130,6 +134,11 @@ func (o *ObjectsClassReferencesCreateParams) BindRequest(r *http.Request, route
 	if err := o.bindPropertyName(rPropertyName, rhkPropertyName, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -214,3 +223,21 @@ func (o *ObjectsClassReferencesCreateParams) bindPropertyName(rawData []string,
 
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsClassReferencesCreateParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_create_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_class_references_create_urlbuilder.go
index 193c4a17cf..4218d4be1a 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_create_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_create_urlbuilder.go
@@ -32,6 +32,7 @@ type ObjectsClassReferencesCreateURL struct {
 	PropertyName string
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -96,6 +97,14 @@ func (o *ObjectsClassReferencesCreateURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_delete_parameters.go b/adapters/handlers/rest/operations/objects/objects_class_references_delete_parameters.go
index f27f85e7f9..4712d8a7ab 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_delete_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_delete_parameters.go
@@ -70,6 +70,10 @@ type ObjectsClassReferencesDeleteParams struct {
 	  In: path
 	*/
 	PropertyName string
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -130,6 +134,11 @@ func (o *ObjectsClassReferencesDeleteParams) BindRequest(r *http.Request, route
 	if err := o.bindPropertyName(rPropertyName, rhkPropertyName, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -214,3 +223,21 @@ func (o *ObjectsClassReferencesDeleteParams) bindPropertyName(rawData []string,
 
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsClassReferencesDeleteParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_delete_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_class_references_delete_urlbuilder.go
index 28fa44c124..a876fdbdc6 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_delete_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_delete_urlbuilder.go
@@ -32,6 +32,7 @@ type ObjectsClassReferencesDeleteURL struct {
 	PropertyName string
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -96,6 +97,14 @@ func (o *ObjectsClassReferencesDeleteURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_put_parameters.go b/adapters/handlers/rest/operations/objects/objects_class_references_put_parameters.go
index e8cebb2cdf..2c2ca240d8 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_put_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_put_parameters.go
@@ -70,6 +70,10 @@ type ObjectsClassReferencesPutParams struct {
 	  In: path
 	*/
 	PropertyName string
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -130,6 +134,11 @@ func (o *ObjectsClassReferencesPutParams) BindRequest(r *http.Request, route *mi
 	if err := o.bindPropertyName(rPropertyName, rhkPropertyName, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -214,3 +223,21 @@ func (o *ObjectsClassReferencesPutParams) bindPropertyName(rawData []string, has
 
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsClassReferencesPutParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_class_references_put_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_class_references_put_urlbuilder.go
index 491774349d..834442b156 100644
--- a/adapters/handlers/rest/operations/objects/objects_class_references_put_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_class_references_put_urlbuilder.go
@@ -32,6 +32,7 @@ type ObjectsClassReferencesPutURL struct {
 	PropertyName string
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -96,6 +97,14 @@ func (o *ObjectsClassReferencesPutURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_delete_parameters.go b/adapters/handlers/rest/operations/objects/objects_delete_parameters.go
index 484aefddd5..8612c9fde2 100644
--- a/adapters/handlers/rest/operations/objects/objects_delete_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_delete_parameters.go
@@ -52,6 +52,10 @@ type ObjectsDeleteParams struct {
 	  In: path
 	*/
 	ID strfmt.UUID
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -74,6 +78,11 @@ func (o *ObjectsDeleteParams) BindRequest(r *http.Request, route *middleware.Mat
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -130,3 +139,21 @@ func (o *ObjectsDeleteParams) validateID(formats strfmt.Registry) error {
 	}
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsDeleteParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_delete_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_delete_urlbuilder.go
index 0cebd3c4b2..558ad95be3 100644
--- a/adapters/handlers/rest/operations/objects/objects_delete_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_delete_urlbuilder.go
@@ -30,6 +30,7 @@ type ObjectsDeleteURL struct {
 	ID strfmt.UUID
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -80,6 +81,14 @@ func (o *ObjectsDeleteURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_patch_parameters.go b/adapters/handlers/rest/operations/objects/objects_patch_parameters.go
index ea1c15da44..3ca3101875 100644
--- a/adapters/handlers/rest/operations/objects/objects_patch_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_patch_parameters.go
@@ -58,6 +58,10 @@ type ObjectsPatchParams struct {
 	  In: path
 	*/
 	ID strfmt.UUID
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -102,6 +106,11 @@ func (o *ObjectsPatchParams) BindRequest(r *http.Request, route *middleware.Matc
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -158,3 +167,21 @@ func (o *ObjectsPatchParams) validateID(formats strfmt.Registry) error {
 	}
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsPatchParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_patch_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_patch_urlbuilder.go
index 8fb9e1b0dc..411532381d 100644
--- a/adapters/handlers/rest/operations/objects/objects_patch_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_patch_urlbuilder.go
@@ -30,6 +30,7 @@ type ObjectsPatchURL struct {
 	ID strfmt.UUID
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -80,6 +81,14 @@ func (o *ObjectsPatchURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/handlers/rest/operations/objects/objects_update_parameters.go b/adapters/handlers/rest/operations/objects/objects_update_parameters.go
index ba2d179516..0060f930ad 100644
--- a/adapters/handlers/rest/operations/objects/objects_update_parameters.go
+++ b/adapters/handlers/rest/operations/objects/objects_update_parameters.go
@@ -60,6 +60,10 @@ type ObjectsUpdateParams struct {
 	  In: path
 	*/
 	ID strfmt.UUID
+	/*Specifies the tenant in a request targeting a multi-tenant class
+	  In: query
+	*/
+	TenantKey *string
 }
 
 // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -110,6 +114,11 @@ func (o *ObjectsUpdateParams) BindRequest(r *http.Request, route *middleware.Mat
 	if err := o.bindID(rID, rhkID, route.Formats); err != nil {
 		res = append(res, err)
 	}
+
+	qTenantKey, qhkTenantKey, _ := qs.GetOK("tenant_key")
+	if err := o.bindTenantKey(qTenantKey, qhkTenantKey, route.Formats); err != nil {
+		res = append(res, err)
+	}
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
@@ -166,3 +175,21 @@ func (o *ObjectsUpdateParams) validateID(formats strfmt.Registry) error {
 	}
 	return nil
 }
+
+// bindTenantKey binds and validates parameter TenantKey from query.
+func (o *ObjectsUpdateParams) bindTenantKey(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
+		return nil
+	}
+	o.TenantKey = &raw
+
+	return nil
+}
diff --git a/adapters/handlers/rest/operations/objects/objects_update_urlbuilder.go b/adapters/handlers/rest/operations/objects/objects_update_urlbuilder.go
index ea2ab05aab..21a669d731 100644
--- a/adapters/handlers/rest/operations/objects/objects_update_urlbuilder.go
+++ b/adapters/handlers/rest/operations/objects/objects_update_urlbuilder.go
@@ -30,6 +30,7 @@ type ObjectsUpdateURL struct {
 	ID strfmt.UUID
 
 	ConsistencyLevel *string
+	TenantKey        *string
 
 	_basePath string
 	// avoid unkeyed usage
@@ -80,6 +81,14 @@ func (o *ObjectsUpdateURL) Build() (*url.URL, error) {
 		qs.Set("consistency_level", consistencyLevelQ)
 	}
 
+	var tenantKeyQ string
+	if o.TenantKey != nil {
+		tenantKeyQ = *o.TenantKey
+	}
+	if tenantKeyQ != "" {
+		qs.Set("tenant_key", tenantKeyQ)
+	}
+
 	_result.RawQuery = qs.Encode()
 
 	return &_result, nil
diff --git a/adapters/repos/db/crud.go b/adapters/repos/db/crud.go
index b1303aaaff..5338ba68ba 100644
--- a/adapters/repos/db/crud.go
+++ b/adapters/repos/db/crud.go
@@ -225,9 +225,9 @@ func (db *DB) anyExists(ctx context.Context, id strfmt.UUID,
 	return false, nil
 }
 
-func (db *DB) AddReference(ctx context.Context,
-	className string, source strfmt.UUID, propName string,
-	ref *models.SingleRef, repl *additional.ReplicationProperties,
+func (db *DB) AddReference(ctx context.Context, className string,
+	source strfmt.UUID, propName string, ref *models.SingleRef,
+	repl *additional.ReplicationProperties, tenantKey string,
 ) error {
 	target, err := crossref.ParseSingleRef(ref)
 	if err != nil {
@@ -245,18 +245,18 @@ func (db *DB) AddReference(ctx context.Context,
 				To: target,
 			},
 		},
-	}, repl)
+	}, repl, tenantKey)
 }
 
 func (db *DB) Merge(ctx context.Context, merge objects.MergeDocument,
-	repl *additional.ReplicationProperties,
+	repl *additional.ReplicationProperties, tenantKey string,
 ) error {
 	idx := db.GetIndex(schema.ClassName(merge.Class))
 	if idx == nil {
 		return fmt.Errorf("merge from non-existing index for %s", merge.Class)
 	}
 
-	err := idx.mergeObject(ctx, merge, repl)
+	err := idx.mergeObject(ctx, merge, repl, tenantKey)
 	if err != nil {
 		return errors.Wrapf(err, "merge into index %s", idx.ID())
 	}
diff --git a/adapters/repos/db/index.go b/adapters/repos/db/index.go
index 4dbc252faf..0204b7ab8d 100644
--- a/adapters/repos/db/index.go
+++ b/adapters/repos/db/index.go
@@ -14,6 +14,7 @@ package db
 import (
 	"context"
 	"fmt"
+	golangSort "sort"
 	"strings"
 	"sync"
 	"time"
@@ -1014,6 +1015,43 @@ func (i *Index) objectSearchByShard(ctx context.Context, limit int, filters *fil
 		return nil, nil, err
 	}
 
+	if len(resultObjects) == len(resultScores) {
+
+		// Force a stable sort order by UUID
+
+		type resultSortable struct {
+			object *storobj.Object
+			score  float32
+		}
+		objs := resultObjects
+		scores := resultScores
+		var results []resultSortable = make([]resultSortable, len(objs))
+		for i := range objs {
+			results[i] = resultSortable{
+				object: objs[i],
+				score:  scores[i],
+			}
+		}
+
+		golangSort.Slice(results, func(i, j int) bool {
+			if results[i].score == results[j].score {
+				return results[i].object.Object.ID > results[j].object.Object.ID
+			}
+
+			return results[i].score > results[j].score
+		})
+
+		var finalObjs []*storobj.Object = make([]*storobj.Object, len(results))
+		var finalScores []float32 = make([]float32, len(results))
+		for i, result := range results {
+
+			finalObjs[i] = result.object
+			finalScores[i] = result.score
+		}
+
+		return finalObjs, finalScores, nil
+	}
+
 	return resultObjects, resultScores, nil
 }
 
@@ -1176,6 +1214,11 @@ func (i *Index) deleteObject(ctx context.Context, id strfmt.UUID,
 ) error {
 	i.backupStateLock.RLock()
 	defer i.backupStateLock.RUnlock()
+
+	if err := i.validateMultiTenancy(tenantKey); err != nil {
+		return err
+	}
+
 	shardName, err := i.determineObjectShard(id, tenantKey)
 	if err != nil {
 		return err
@@ -1228,11 +1271,16 @@ func (i *Index) isLocalShard(shard string) bool {
 }
 
 func (i *Index) mergeObject(ctx context.Context, merge objects.MergeDocument,
-	replProps *additional.ReplicationProperties,
+	replProps *additional.ReplicationProperties, tenantKey string,
 ) error {
 	i.backupStateLock.RLock()
 	defer i.backupStateLock.RUnlock()
-	shardName, err := i.shardFromUUID(merge.ID)
+
+	if err := i.validateMultiTenancy(tenantKey); err != nil {
+		return err
+	}
+
+	shardName, err := i.determineObjectShard(merge.ID, tenantKey)
 	if err != nil {
 		return err
 	}
diff --git a/adapters/repos/db/inverted/bm25_searcher.go b/adapters/repos/db/inverted/bm25_searcher.go
index 586c33cabb..50eb9d96a5 100644
--- a/adapters/repos/db/inverted/bm25_searcher.go
+++ b/adapters/repos/db/inverted/bm25_searcher.go
@@ -75,9 +75,7 @@ func NewBM25Searcher(config schema.BM25Config, store *lsmkv.Store,
 	}
 }
 
-func (b *BM25Searcher) BM25F(ctx context.Context, filterDocIds helpers.AllowList, className schema.ClassName, limit int,
-	keywordRanking searchparams.KeywordRanking,
-) ([]*storobj.Object, []float32, error) {
+func (b *BM25Searcher) BM25F(ctx context.Context, filterDocIds helpers.AllowList, className schema.ClassName, limit int, keywordRanking searchparams.KeywordRanking) ([]*storobj.Object, []float32, error) {
 	// WEAVIATE-471 - If a property is not searchable, return an error
 	for _, property := range keywordRanking.Properties {
 		if !PropertyHasSearchableIndex(b.schema.Objects, string(className), property) {
diff --git a/adapters/repos/db/shard_read.go b/adapters/repos/db/shard_read.go
index dccace06ec..3a5ec03eef 100644
--- a/adapters/repos/db/shard_read.go
+++ b/adapters/repos/db/shard_read.go
@@ -160,10 +160,7 @@ func (s *Shard) vectorByIndexID(ctx context.Context, indexID uint64) ([]float32,
 	return storobj.VectorFromBinary(bytes)
 }
 
-func (s *Shard) objectSearch(ctx context.Context, limit int,
-	filters *filters.LocalFilter, keywordRanking *searchparams.KeywordRanking,
-	sort []filters.Sort, cursor *filters.Cursor, additional additional.Properties,
-) ([]*storobj.Object, []float32, error) {
+func (s *Shard) objectSearch(ctx context.Context, limit int, filters *filters.LocalFilter, keywordRanking *searchparams.KeywordRanking, sort []filters.Sort, cursor *filters.Cursor, additional additional.Properties) ([]*storobj.Object, []float32, error) {
 	if keywordRanking != nil {
 		if v := s.versioner.Version(); v < 2 {
 			return nil, nil, errors.Errorf(
@@ -171,13 +168,6 @@ func (s *Shard) objectSearch(ctx context.Context, limit int,
 					"Weaviate which does not yet support BM25 search")
 		}
 
-		bm25Config := s.index.getInvertedIndexConfig().BM25
-
-		bm25searcher := inverted.NewBM25Searcher(bm25Config, s.store,
-			s.index.getSchema.GetSchemaSkipAuth(),
-			s.propertyIndices, s.index.classSearcher, s.deletedDocIDs, s.propLengths,
-			s.index.logger, s.versioner.Version())
-
 		var bm25objs []*storobj.Object
 		var bm25count []float32
 		var err error
@@ -198,8 +188,9 @@ func (s *Shard) objectSearch(ctx context.Context, limit int,
 		}
 
 		className := s.index.Config.ClassName
-		bm25objs, bm25count, err = bm25searcher.BM25F(ctx,
-			filterDocIds, className, limit, *keywordRanking)
+		bm25Config := s.index.getInvertedIndexConfig().BM25
+		bm25searcher := inverted.NewBM25Searcher(bm25Config, s.store, s.index.getSchema.GetSchemaSkipAuth(), s.propertyIndices, s.index.classSearcher, s.deletedDocIDs, s.propLengths, s.index.logger, s.versioner.Version())
+		bm25objs, bm25count, err = bm25searcher.BM25F(ctx, filterDocIds, className, limit, *keywordRanking)
 		if err != nil {
 			return nil, nil, err
 		}
diff --git a/adapters/repos/db/vector/hnsw/condensor.go b/adapters/repos/db/vector/hnsw/condensor.go
index 7663bdb36d..085e3a8376 100644
--- a/adapters/repos/db/vector/hnsw/condensor.go
+++ b/adapters/repos/db/vector/hnsw/condensor.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	ssdhelpers "github.com/weaviate/weaviate/adapters/repos/db/vector/ssdhelpers"
 	"github.com/weaviate/weaviate/entities/errorcompounder"
 )
 
@@ -52,6 +53,12 @@ func (c *MemoryCondensor) Do(fileName string) error {
 
 	c.newLog = NewWriterSize(c.newLogFile, 1*1024*1024)
 
+	if res.Compressed {
+		if err := c.AddPQ(res.PQData); err != nil {
+			return fmt.Errorf("write pq data: %w", err)
+		}
+	}
+
 	for _, node := range res.Nodes {
 		if node == nil {
 			// nil nodes occur when we've grown, but not inserted anything yet
@@ -228,6 +235,27 @@ func (c *MemoryCondensor) AddTombstone(nodeid uint64) error {
 	return ec.ToError()
 }
 
+func (c *MemoryCondensor) AddPQ(data ssdhelpers.PQData) error {
+	toWrite := make([]byte, 10)
+	toWrite[0] = byte(AddPQ)
+	binary.LittleEndian.PutUint16(toWrite[1:3], data.Dimensions)
+	toWrite[3] = byte(data.EncoderType)
+	binary.LittleEndian.PutUint16(toWrite[4:6], data.Ks)
+	binary.LittleEndian.PutUint16(toWrite[6:8], data.M)
+	toWrite[8] = data.EncoderDistribution
+	if data.UseBitsEncoding {
+		toWrite[9] = 1
+	} else {
+		toWrite[9] = 0
+	}
+
+	for _, encoder := range data.Encoders {
+		toWrite = append(toWrite, encoder.ExposeDataForRestore()...)
+	}
+	_, err := c.newLog.Write(toWrite)
+	return err
+}
+
 func NewMemoryCondensor(logger logrus.FieldLogger) *MemoryCondensor {
 	return &MemoryCondensor{logger: logger}
 }
diff --git a/adapters/repos/db/vector/hnsw/deserializer.go b/adapters/repos/db/vector/hnsw/deserializer.go
index 6b6ad3d6d7..311e62d052 100644
--- a/adapters/repos/db/vector/hnsw/deserializer.go
+++ b/adapters/repos/db/vector/hnsw/deserializer.go
@@ -353,9 +353,13 @@ func (d *Deserializer) ReadClearLinks(r io.Reader, res *DeserializationResult,
 		return err
 	}
 
-	if int(id) >= len(res.Nodes) {
-		// node is out of bounds, so it can't exist, nothing to do here
-		return nil
+	newNodes, changed, err := growIndexToAccomodateNode(res.Nodes, id, d.logger)
+	if err != nil {
+		return err
+	}
+
+	if changed {
+		res.Nodes = newNodes
 	}
 
 	if res.Nodes[id] == nil {
@@ -380,9 +384,13 @@ func (d *Deserializer) ReadClearLinksAtLevel(r io.Reader, res *DeserializationRe
 		return err
 	}
 
-	if int(id) >= len(res.Nodes) {
-		// node is out of bounds, so it can't exist, nothing to do here
-		return nil
+	newNodes, changed, err := growIndexToAccomodateNode(res.Nodes, id, d.logger)
+	if err != nil {
+		return err
+	}
+
+	if changed {
+		res.Nodes = newNodes
 	}
 
 	if keepReplaceInfo {
@@ -439,9 +447,13 @@ func (d *Deserializer) ReadDeleteNode(r io.Reader, res *DeserializationResult) e
 		return err
 	}
 
-	if int(id) >= len(res.Nodes) {
-		// node is out of bounds, so it can't exist, nothing to do here
-		return nil
+	newNodes, changed, err := growIndexToAccomodateNode(res.Nodes, id, d.logger)
+	if err != nil {
+		return err
+	}
+
+	if changed {
+		res.Nodes = newNodes
 	}
 
 	res.Nodes[id] = nil
diff --git a/adapters/repos/db/vector/hnsw/search.go b/adapters/repos/db/vector/hnsw/search.go
index 4ff16d3198..d6baa758f1 100644
--- a/adapters/repos/db/vector/hnsw/search.go
+++ b/adapters/repos/db/vector/hnsw/search.go
@@ -171,6 +171,7 @@ func (h *hnsw) searchLayerByVector(queryVector []float32,
 	var byteDistancer *ssdhelpers.PQDistancer
 	if h.compressed.Load() {
 		byteDistancer = h.pq.NewDistancer(queryVector)
+		defer h.pq.ReturnDistancer(byteDistancer)
 	} else {
 		floatDistancer = h.distancerProvider.New(queryVector)
 	}
diff --git a/adapters/repos/db/vector/hnsw/startup.go b/adapters/repos/db/vector/hnsw/startup.go
index fd57aa8581..9228b44e3a 100644
--- a/adapters/repos/db/vector/hnsw/startup.go
+++ b/adapters/repos/db/vector/hnsw/startup.go
@@ -188,7 +188,16 @@ func (h *hnsw) prefillCache() {
 			for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
 				id := binary.LittleEndian.Uint64(k)
 				h.compressedVectorsCache.grow(id)
-				h.compressedVectorsCache.preload(id, v)
+
+				// Make sure to copy the vector. The cursor only guarantees that
+				// the underlying memory won't change until we hit .Next(). Since
+				// we want to keep this around in the cache "forever", we need to
+				// alloc some new memory and copy the vector.
+				//
+				// https://github.com/weaviate/weaviate/issues/3049
+				vc := make([]byte, len(v))
+				copy(vc, v)
+				h.compressedVectorsCache.preload(id, vc)
 			}
 			cursor.Close()
 		} else {
diff --git a/adapters/repos/db/vector/ssdhelpers/kmeans.go b/adapters/repos/db/vector/ssdhelpers/kmeans.go
index 54e568e606..dab236009f 100644
--- a/adapters/repos/db/vector/ssdhelpers/kmeans.go
+++ b/adapters/repos/db/vector/ssdhelpers/kmeans.go
@@ -14,6 +14,7 @@ package ssdhelpers
 import (
 	"encoding/binary"
 	"errors"
+	"fmt"
 	"math"
 	"math/rand"
 
@@ -34,6 +35,29 @@ type KMeans struct {
 	data KMeansPartitionData // Non persistent data used only during the fitting process
 }
 
+// String prints some minimal information about the encoder. This can be
+// used for viability checks to see if the encoder was initialized
+// correctly – for example after a restart.
+func (k *KMeans) String() string {
+	maxElem := 5
+	var firstCenters []float32
+	i := 0
+	for _, center := range k.centers {
+		for _, centerVal := range center {
+			if i == maxElem {
+				break
+			}
+
+			firstCenters = append(firstCenters, centerVal)
+			i++
+		}
+		if i == maxElem {
+			break
+		}
+	}
+	return fmt.Sprintf("KMeans Encoder: K=%d, dim=%d, segment=%d first_center_truncated=%v", k.K, k.dimensions, k.segment, firstCenters)
+}
+
 type KMeansPartitionData struct {
 	changes int        // How many vectors has jumped to a new cluster
 	points  []uint64   // Cluster assigned to each point
diff --git a/adapters/repos/db/vector/ssdhelpers/product_quantization.go b/adapters/repos/db/vector/ssdhelpers/product_quantization.go
index 182071cb25..f10c914608 100644
--- a/adapters/repos/db/vector/ssdhelpers/product_quantization.go
+++ b/adapters/repos/db/vector/ssdhelpers/product_quantization.go
@@ -15,7 +15,7 @@ import (
 	"encoding/binary"
 	"errors"
 	"math"
-	"sync/atomic"
+	"sync"
 
 	"github.com/weaviate/weaviate/adapters/repos/db/vector/hnsw/distancer"
 )
@@ -28,28 +28,51 @@ const (
 )
 
 type DistanceLookUpTable struct {
-	calculated [][]atomic.Bool
-	distances  [][]float32
+	calculated []bool
+	distances  []float32
 	center     [][]float32
+	segments   int
+	centroids  int
 }
 
 func NewDistanceLookUpTable(segments int, centroids int, center []float32) *DistanceLookUpTable {
-	distances := make([][]float32, segments)
-	calculated := make([][]atomic.Bool, segments)
-	for m := 0; m < segments; m++ {
-		distances[m] = make([]float32, centroids)
-		calculated[m] = make([]atomic.Bool, centroids)
-	}
+	distances := make([]float32, segments*centroids)
+	calculated := make([]bool, segments*centroids)
 	parsedCenter := make([][]float32, segments)
 	ds := len(center) / segments
 	for c := 0; c < segments; c++ {
 		parsedCenter[c] = center[c*ds : (c+1)*ds]
 	}
 
-	return &DistanceLookUpTable{
+	dlt := &DistanceLookUpTable{
 		distances:  distances,
 		calculated: calculated,
 		center:     parsedCenter,
+		segments:   segments,
+		centroids:  centroids,
+	}
+	return dlt
+}
+
+func (lut *DistanceLookUpTable) Reset(segments int, centroids int, center []float32) {
+	elems := segments * centroids
+	lut.segments = segments
+	lut.centroids = centroids
+	if len(lut.distances) != elems ||
+		len(lut.calculated) != elems ||
+		len(lut.center) != segments {
+		lut.distances = make([]float32, segments*centroids)
+		lut.calculated = make([]bool, segments*centroids)
+		lut.center = make([][]float32, segments)
+	} else {
+		for i := range lut.calculated {
+			lut.calculated[i] = false
+		}
+	}
+
+	ds := len(center) / segments
+	for c := 0; c < segments; c++ {
+		lut.center[c] = center[c*ds : (c+1)*ds]
 	}
 }
 
@@ -61,19 +84,68 @@ func (lut *DistanceLookUpTable) LookUp(
 
 	for i := range pq.kms {
 		c := pq.ExtractCode(encoded, i)
-		if lut.calculated[i][c].Load() {
-			sum += lut.distances[i][c]
+		if lut.distCalculated(i, c) {
+			sum += lut.codeDist(i, c)
 		} else {
 			centroid := pq.kms[i].Centroid(c)
 			dist := pq.distance.Step(lut.center[i], centroid)
-			lut.distances[i][c] = dist
-			lut.calculated[i][c].Store(true)
+			lut.setCodeDist(i, c, dist)
+			lut.setDistCalculated(i, c)
 			sum += dist
 		}
 	}
 	return pq.distance.Wrap(sum)
 }
 
+// meant for better readability, rely on the fact that the compiler will inline this
+func (lut *DistanceLookUpTable) posForSegmentAndCode(segment int, code uint64) int {
+	return segment*lut.centroids + int(code)
+}
+
+// meant for better readability, rely on the fact that the compiler will inline this
+func (lut *DistanceLookUpTable) distCalculated(segment int, code uint64) bool {
+	return lut.calculated[lut.posForSegmentAndCode(segment, code)]
+}
+
+// meant for better readability, rely on the fact that the compiler will inline this
+func (lut *DistanceLookUpTable) setDistCalculated(segment int, code uint64) {
+	lut.calculated[lut.posForSegmentAndCode(segment, code)] = true
+}
+
+// meant for better readability, rely on the fact that the compiler will inline this
+func (lut *DistanceLookUpTable) codeDist(segment int, code uint64) float32 {
+	return lut.distances[lut.posForSegmentAndCode(segment, code)]
+}
+
+// meant for better readability, rely on the fact that the compiler will inline this
+func (lut *DistanceLookUpTable) setCodeDist(segment int, code uint64, dist float32) {
+	lut.distances[lut.posForSegmentAndCode(segment, code)] = dist
+}
+
+type DLUTPool struct {
+	pool sync.Pool
+}
+
+func NewDLUTPool() *DLUTPool {
+	return &DLUTPool{
+		pool: sync.Pool{
+			New: func() any {
+				return &DistanceLookUpTable{}
+			},
+		},
+	}
+}
+
+func (p *DLUTPool) Get(segments, centroids int, centers []float32) *DistanceLookUpTable {
+	dlt := p.pool.Get().(*DistanceLookUpTable)
+	dlt.Reset(segments, centroids, centers)
+	return dlt
+}
+
+func (p *DLUTPool) Return(dlt *DistanceLookUpTable) {
+	p.pool.Put(dlt)
+}
+
 type ProductQuantizer struct {
 	ks                  int // centroids
 	bits                int // bits amount
@@ -92,6 +164,7 @@ type ProductQuantizer struct {
 	useBitsEncoding     bool
 	ExtractCode         func(encoded []byte, index int) uint64
 	PutCode             func(code uint64, encoded []byte, index int)
+	dlutPool            *DLUTPool
 }
 
 type PQData struct {
@@ -131,6 +204,7 @@ func NewProductQuantizer(segments int, centroids int, useBitsEncoding bool, dist
 		encoderType:         encoderType,
 		encoderDistribution: encoderDistribution,
 		useBitsEncoding:     useBitsEncoding,
+		dlutPool:            NewDLUTPool(),
 	}
 	pq.sharpCodes = pq.bits%8 == 0
 	if pq.bits > 32 {
@@ -305,6 +379,10 @@ func (pq *ProductQuantizer) NewDistancer(a []float32) *PQDistancer {
 	}
 }
 
+func (pq *ProductQuantizer) ReturnDistancer(d *PQDistancer) {
+	pq.dlutPool.Return(d.lut)
+}
+
 func (d *PQDistancer) Distance(x []byte) (float32, bool, error) {
 	return d.pq.Distance(x, d.lut), true, nil
 }
@@ -362,7 +440,7 @@ func (pq *ProductQuantizer) Decode(code []byte) []float32 {
 }
 
 func (pq *ProductQuantizer) CenterAt(vec []float32) *DistanceLookUpTable {
-	return NewDistanceLookUpTable(int(pq.m), int(pq.ks), vec)
+	return pq.dlutPool.Get(int(pq.m), int(pq.ks), vec)
 }
 
 func (pq *ProductQuantizer) Distance(encoded []byte, lut *DistanceLookUpTable) float32 {
diff --git a/client/objects/objects_class_head_parameters.go b/client/objects/objects_class_head_parameters.go
index 666abfae8b..b4054ad4bc 100644
--- a/client/objects/objects_class_head_parameters.go
+++ b/client/objects/objects_class_head_parameters.go
@@ -92,6 +92,12 @@ type ObjectsClassHeadParams struct {
 	*/
 	ID strfmt.UUID
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -178,6 +184,17 @@ func (o *ObjectsClassHeadParams) SetID(id strfmt.UUID) {
 	o.ID = id
 }
 
+// WithTenantKey adds the tenantKey to the objects class head params
+func (o *ObjectsClassHeadParams) WithTenantKey(tenantKey *string) *ObjectsClassHeadParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects class head params
+func (o *ObjectsClassHeadParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsClassHeadParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -213,6 +230,23 @@ func (o *ObjectsClassHeadParams) WriteToRequest(r runtime.ClientRequest, reg str
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_class_patch_parameters.go b/client/objects/objects_class_patch_parameters.go
index 3cd1f732bb..f09f28de98 100644
--- a/client/objects/objects_class_patch_parameters.go
+++ b/client/objects/objects_class_patch_parameters.go
@@ -100,6 +100,12 @@ type ObjectsClassPatchParams struct {
 	*/
 	ID strfmt.UUID
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -197,6 +203,17 @@ func (o *ObjectsClassPatchParams) SetID(id strfmt.UUID) {
 	o.ID = id
 }
 
+// WithTenantKey adds the tenantKey to the objects class patch params
+func (o *ObjectsClassPatchParams) WithTenantKey(tenantKey *string) *ObjectsClassPatchParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects class patch params
+func (o *ObjectsClassPatchParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsClassPatchParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -237,6 +254,23 @@ func (o *ObjectsClassPatchParams) WriteToRequest(r runtime.ClientRequest, reg st
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_class_references_create_parameters.go b/client/objects/objects_class_references_create_parameters.go
index 42c0b67df6..ab31ac7dcd 100644
--- a/client/objects/objects_class_references_create_parameters.go
+++ b/client/objects/objects_class_references_create_parameters.go
@@ -103,6 +103,12 @@ type ObjectsClassReferencesCreateParams struct {
 	*/
 	PropertyName string
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -211,6 +217,17 @@ func (o *ObjectsClassReferencesCreateParams) SetPropertyName(propertyName string
 	o.PropertyName = propertyName
 }
 
+// WithTenantKey adds the tenantKey to the objects class references create params
+func (o *ObjectsClassReferencesCreateParams) WithTenantKey(tenantKey *string) *ObjectsClassReferencesCreateParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects class references create params
+func (o *ObjectsClassReferencesCreateParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsClassReferencesCreateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -256,6 +273,23 @@ func (o *ObjectsClassReferencesCreateParams) WriteToRequest(r runtime.ClientRequ
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_class_references_delete_parameters.go b/client/objects/objects_class_references_delete_parameters.go
index 016ce98422..6be7863d15 100644
--- a/client/objects/objects_class_references_delete_parameters.go
+++ b/client/objects/objects_class_references_delete_parameters.go
@@ -103,6 +103,12 @@ type ObjectsClassReferencesDeleteParams struct {
 	*/
 	PropertyName string
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -211,6 +217,17 @@ func (o *ObjectsClassReferencesDeleteParams) SetPropertyName(propertyName string
 	o.PropertyName = propertyName
 }
 
+// WithTenantKey adds the tenantKey to the objects class references delete params
+func (o *ObjectsClassReferencesDeleteParams) WithTenantKey(tenantKey *string) *ObjectsClassReferencesDeleteParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects class references delete params
+func (o *ObjectsClassReferencesDeleteParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsClassReferencesDeleteParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -256,6 +273,23 @@ func (o *ObjectsClassReferencesDeleteParams) WriteToRequest(r runtime.ClientRequ
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_class_references_put_parameters.go b/client/objects/objects_class_references_put_parameters.go
index 5cdf7cd951..ab01c73e5e 100644
--- a/client/objects/objects_class_references_put_parameters.go
+++ b/client/objects/objects_class_references_put_parameters.go
@@ -103,6 +103,12 @@ type ObjectsClassReferencesPutParams struct {
 	*/
 	PropertyName string
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -211,6 +217,17 @@ func (o *ObjectsClassReferencesPutParams) SetPropertyName(propertyName string) {
 	o.PropertyName = propertyName
 }
 
+// WithTenantKey adds the tenantKey to the objects class references put params
+func (o *ObjectsClassReferencesPutParams) WithTenantKey(tenantKey *string) *ObjectsClassReferencesPutParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects class references put params
+func (o *ObjectsClassReferencesPutParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsClassReferencesPutParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -256,6 +273,23 @@ func (o *ObjectsClassReferencesPutParams) WriteToRequest(r runtime.ClientRequest
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_delete_parameters.go b/client/objects/objects_delete_parameters.go
index 9fe7268185..e2ffc25f8c 100644
--- a/client/objects/objects_delete_parameters.go
+++ b/client/objects/objects_delete_parameters.go
@@ -86,6 +86,12 @@ type ObjectsDeleteParams struct {
 	*/
 	ID strfmt.UUID
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -161,6 +167,17 @@ func (o *ObjectsDeleteParams) SetID(id strfmt.UUID) {
 	o.ID = id
 }
 
+// WithTenantKey adds the tenantKey to the objects delete params
+func (o *ObjectsDeleteParams) WithTenantKey(tenantKey *string) *ObjectsDeleteParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects delete params
+func (o *ObjectsDeleteParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsDeleteParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -191,6 +208,23 @@ func (o *ObjectsDeleteParams) WriteToRequest(r runtime.ClientRequest, reg strfmt
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_patch_parameters.go b/client/objects/objects_patch_parameters.go
index 15ae4bbfca..e4c0fcac51 100644
--- a/client/objects/objects_patch_parameters.go
+++ b/client/objects/objects_patch_parameters.go
@@ -94,6 +94,12 @@ type ObjectsPatchParams struct {
 	*/
 	ID strfmt.UUID
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -180,6 +186,17 @@ func (o *ObjectsPatchParams) SetID(id strfmt.UUID) {
 	o.ID = id
 }
 
+// WithTenantKey adds the tenantKey to the objects patch params
+func (o *ObjectsPatchParams) WithTenantKey(tenantKey *string) *ObjectsPatchParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects patch params
+func (o *ObjectsPatchParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsPatchParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -215,6 +232,23 @@ func (o *ObjectsPatchParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/client/objects/objects_update_parameters.go b/client/objects/objects_update_parameters.go
index cba965c4b8..694719765f 100644
--- a/client/objects/objects_update_parameters.go
+++ b/client/objects/objects_update_parameters.go
@@ -91,6 +91,12 @@ type ObjectsUpdateParams struct {
 	*/
 	ID strfmt.UUID
 
+	/* TenantKey.
+
+	   Specifies the tenant in a request targeting a multi-tenant class
+	*/
+	TenantKey *string
+
 	timeout    time.Duration
 	Context    context.Context
 	HTTPClient *http.Client
@@ -177,6 +183,17 @@ func (o *ObjectsUpdateParams) SetID(id strfmt.UUID) {
 	o.ID = id
 }
 
+// WithTenantKey adds the tenantKey to the objects update params
+func (o *ObjectsUpdateParams) WithTenantKey(tenantKey *string) *ObjectsUpdateParams {
+	o.SetTenantKey(tenantKey)
+	return o
+}
+
+// SetTenantKey adds the tenantKey to the objects update params
+func (o *ObjectsUpdateParams) SetTenantKey(tenantKey *string) {
+	o.TenantKey = tenantKey
+}
+
 // WriteToRequest writes these params to a swagger request
 func (o *ObjectsUpdateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
 
@@ -212,6 +229,23 @@ func (o *ObjectsUpdateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt
 		return err
 	}
 
+	if o.TenantKey != nil {
+
+		// query param tenant_key
+		var qrTenantKey string
+
+		if o.TenantKey != nil {
+			qrTenantKey = *o.TenantKey
+		}
+		qTenantKey := qrTenantKey
+		if qTenantKey != "" {
+
+			if err := r.SetQueryParam("tenant_key", qTenantKey); err != nil {
+				return err
+			}
+		}
+	}
+
 	if len(res) > 0 {
 		return errors.CompositeValidationError(res...)
 	}
diff --git a/modules/text2vec-huggingface/clients/url.go b/modules/text2vec-huggingface/clients/url.go
deleted file mode 100644
index 98fe61df06..0000000000
--- a/modules/text2vec-huggingface/clients/url.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//                           _       _
-// __      _____  __ ___   ___  __ _| |_ ___
-// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
-//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
-//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
-//
-//  Copyright © 2016 - 2023 Weaviate B.V. All rights reserved.
-//
-//  CONTACT: hello@weaviate.io
-//
-
-package clients
-
-import "fmt"
-
-type huggingFaceUrlBuilder struct {
-	origin   string
-	pathMask string
-}
-
-func newHuggingFaceUrlBuilder() *huggingFaceUrlBuilder {
-	return &huggingFaceUrlBuilder{
-		origin:   "https://api-inference.huggingface.co",
-		pathMask: "/pipeline/feature-extraction/%s",
-	}
-}
-
-func (o *huggingFaceUrlBuilder) url(model string) string {
-	return fmt.Sprintf("%s%s", o.origin, o.getPath(model))
-}
-
-func (o *huggingFaceUrlBuilder) getPath(model string) string {
-	return fmt.Sprintf(o.pathMask, model)
-}
diff --git a/modules/text2vec-huggingface/clients/vectorizer.go b/modules/text2vec-huggingface/clients/vectorizer.go
index 42047d8882..94d71e9298 100644
--- a/modules/text2vec-huggingface/clients/vectorizer.go
+++ b/modules/text2vec-huggingface/clients/vectorizer.go
@@ -24,6 +24,11 @@ import (
 	"github.com/weaviate/weaviate/modules/text2vec-huggingface/ent"
 )
 
+const (
+	DefaultOrigin = "https://api-inference.huggingface.co"
+	DefaultPath   = "pipeline/feature-extraction"
+)
+
 type embeddingsRequest struct {
 	Inputs  []string `json:"inputs"`
 	Options *options `json:"options,omitempty"`
@@ -48,7 +53,6 @@ type huggingFaceApiError struct {
 type vectorizer struct {
 	apiKey                string
 	httpClient            *http.Client
-	urlBuilder            *huggingFaceUrlBuilder
 	bertEmbeddingsDecoder *bertEmbeddingsDecoder
 	logger                logrus.FieldLogger
 }
@@ -57,7 +61,6 @@ func New(apiKey string, logger logrus.FieldLogger) *vectorizer {
 	return &vectorizer{
 		apiKey:                apiKey,
 		httpClient:            &http.Client{},
-		urlBuilder:            newHuggingFaceUrlBuilder(),
 		bertEmbeddingsDecoder: newBertEmbeddingsDecoder(),
 		logger:                logger,
 	}
@@ -185,17 +188,6 @@ func (v *vectorizer) getApiKey(ctx context.Context) string {
 	return ""
 }
 
-func (v *vectorizer) getURL(config ent.VectorizationConfig) string {
-	if config.EndpointURL != "" {
-		return config.EndpointURL
-	}
-	return v.url(config.Model)
-}
-
-func (v *vectorizer) url(model string) string {
-	return v.urlBuilder.url(model)
-}
-
 func (v *vectorizer) getOptions(config ent.VectorizationConfig) options {
 	return options{
 		WaitForModel: config.WaitForModel,
@@ -203,3 +195,11 @@ func (v *vectorizer) getOptions(config ent.VectorizationConfig) options {
 		UseCache:     config.UseCache,
 	}
 }
+
+func (v *vectorizer) getURL(config ent.VectorizationConfig) string {
+	if config.EndpointURL != "" {
+		return config.EndpointURL
+	}
+
+	return fmt.Sprintf("%s/%s/%s", DefaultOrigin, DefaultPath, config.Model)
+}
diff --git a/openapi-specs/schema.json b/openapi-specs/schema.json
index 7e9ff8e7a9..8a0a57025d 100644
--- a/openapi-specs/schema.json
+++ b/openapi-specs/schema.json
@@ -1659,7 +1659,7 @@
     },
     "description": "Cloud-native, modular vector database",
     "title": "Weaviate",
-    "version": "1.19.5"
+    "version": "1.20.0-prealpha"
   },
   "parameters": {
     "CommonAfterParameterQuery": {
@@ -1990,6 +1990,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2108,6 +2111,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2175,6 +2181,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2511,6 +2520,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2576,6 +2588,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2846,6 +2861,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -2929,6 +2947,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
@@ -3012,6 +3033,9 @@
           },
           {
             "$ref": "#/parameters/CommonConsistencyLevelParameterQuery"
+          },
+          {
+            "$ref": "#/parameters/CommonTenantKeyParameterQuery"
           }
         ],
         "responses": {
diff --git a/tools/dev/run_dev_server.sh b/tools/dev/run_dev_server.sh
index 22dc5502fb..a12b601a2e 100755
--- a/tools/dev/run_dev_server.sh
+++ b/tools/dev/run_dev_server.sh
@@ -45,6 +45,7 @@ case $CONFIG in
       ENABLE_MODULES="text2vec-contextionary,backup-filesystem" \
       CLUSTER_GOSSIP_BIND_PORT="7100" \
       CLUSTER_DATA_BIND_PORT="7101" \
+      LOG_LEVEL="trace" \
       go_run ./cmd/weaviate-server \
         --scheme http \
         --host "127.0.0.1" \
diff --git a/usecases/objects/add.go b/usecases/objects/add.go
index 8de19a82eb..be528ad996 100644
--- a/usecases/objects/add.go
+++ b/usecases/objects/add.go
@@ -97,7 +97,7 @@ func (m *Manager) addObjectToConnectorAndSchema(ctx context.Context, principal *
 		return nil, NewErrInvalidUserInput("invalid object: %v", err)
 	}
 
-	err = m.validateObjectAndNormalizeNames(ctx, principal, object, repl)
+	err = m.validateObjectAndNormalizeNames(ctx, principal, object, repl, tenantKey)
 	if err != nil {
 		return nil, NewErrInvalidUserInput("invalid object: %v", err)
 	}
@@ -125,9 +125,8 @@ func (m *Manager) addObjectToConnectorAndSchema(ctx context.Context, principal *
 	return object, nil
 }
 
-func (m *Manager) validateObjectAndNormalizeNames(ctx context.Context,
-	principal *models.Principal, object *models.Object,
-	repl *additional.ReplicationProperties,
+func (m *Manager) validateObjectAndNormalizeNames(ctx context.Context, principal *models.Principal,
+	object *models.Object, repl *additional.ReplicationProperties, tenantKey string,
 ) error {
 	// Validate schema given in body with the weaviate schema
 	if _, err := uuid.Parse(object.ID.String()); err != nil {
@@ -139,5 +138,5 @@ func (m *Manager) validateObjectAndNormalizeNames(ctx context.Context,
 		return err
 	}
 
-	return validation.New(m.vectorRepo.Exists, m.config, repl).Object(ctx, object, class)
+	return validation.New(m.vectorRepo.Exists, m.config, repl, tenantKey).Object(ctx, object, class)
 }
diff --git a/usecases/objects/batch_add.go b/usecases/objects/batch_add.go
index ccfc576c5e..34ec2f1044 100644
--- a/usecases/objects/batch_add.go
+++ b/usecases/objects/batch_add.go
@@ -155,7 +155,7 @@ func (b *BatchManager) validateObject(ctx context.Context, principal *models.Pri
 		ec.Add(fmt.Errorf("class '%s' not present in schema", object.Class))
 	} else {
 		// not possible without the class being present
-		err = validation.New(b.vectorRepo.Exists, b.config, repl).Object(ctx, object, class)
+		err = validation.New(b.vectorRepo.Exists, b.config, repl, "").Object(ctx, object, class)
 		ec.Add(err)
 
 		err = b.modulesProvider.UpdateVector(ctx, object, class, nil, b.findObject, b.logger)
diff --git a/usecases/objects/head.go b/usecases/objects/head.go
index 3e4bacf660..678edf8b07 100644
--- a/usecases/objects/head.go
+++ b/usecases/objects/head.go
@@ -20,10 +20,9 @@ import (
 	"github.com/weaviate/weaviate/entities/models"
 )
 
-// HeadObject check object's existence in the conncected DB
-func (m *Manager) HeadObject(ctx context.Context,
-	principal *models.Principal, class string, id strfmt.UUID,
-	repl *additional.ReplicationProperties,
+// HeadObject check object's existence in the connected DB
+func (m *Manager) HeadObject(ctx context.Context, principal *models.Principal, class string,
+	id strfmt.UUID, repl *additional.ReplicationProperties, tenantKey string,
 ) (bool, *Error) {
 	path := fmt.Sprintf("objects/%s", id)
 	if class != "" {
@@ -42,7 +41,7 @@ func (m *Manager) HeadObject(ctx context.Context,
 	m.metrics.HeadObjectInc()
 	defer m.metrics.HeadObjectDec()
 
-	ok, err := m.vectorRepo.Exists(ctx, class, id, repl, "")
+	ok, err := m.vectorRepo.Exists(ctx, class, id, repl, tenantKey)
 	if err != nil {
 		return false, &Error{"repo.exists", StatusInternalServerError, err}
 	}
diff --git a/usecases/objects/manager.go b/usecases/objects/manager.go
index be8fd0fb44..d06c87ee50 100644
--- a/usecases/objects/manager.go
+++ b/usecases/objects/manager.go
@@ -104,9 +104,9 @@ type VectorRepo interface {
 		additional additional.Properties) (*search.Result, error)
 	ObjectSearch(ctx context.Context, offset, limit int, filters *filters.LocalFilter,
 		sort []filters.Sort, additional additional.Properties) (search.Results, error)
-	AddReference(ctx context.Context, className string, source strfmt.UUID,
-		propName string, ref *models.SingleRef, repl *additional.ReplicationProperties) error
-	Merge(ctx context.Context, merge MergeDocument, repl *additional.ReplicationProperties) error
+	AddReference(ctx context.Context, className string, source strfmt.UUID, propName string,
+		ref *models.SingleRef, repl *additional.ReplicationProperties, tenantKey string) error
+	Merge(ctx context.Context, merge MergeDocument, repl *additional.ReplicationProperties, tenantKey string) error
 	Query(context.Context, *QueryInput) (search.Results, *Error)
 }
 
diff --git a/usecases/objects/merge.go b/usecases/objects/merge.go
index aeacde6af6..e1c6acceef 100644
--- a/usecases/objects/merge.go
+++ b/usecases/objects/merge.go
@@ -37,7 +37,7 @@ type MergeDocument struct {
 }
 
 func (m *Manager) MergeObject(ctx context.Context, principal *models.Principal,
-	updates *models.Object, repl *additional.ReplicationProperties,
+	updates *models.Object, repl *additional.ReplicationProperties, tenantKey string,
 ) *Error {
 	if err := m.validateInputs(updates); err != nil {
 		return &Error{"bad request", StatusBadRequest, err}
@@ -60,26 +60,27 @@ func (m *Manager) MergeObject(ctx context.Context, principal *models.Principal,
 		}
 	}
 
-	if err := m.validateObjectAndNormalizeNames(ctx, principal, updates, repl); err != nil {
+	if err := m.validateObjectAndNormalizeNames(ctx, principal, updates, repl, tenantKey); err != nil {
 		return &Error{"bad request", StatusBadRequest, err}
 	}
 
 	if updates.Properties == nil {
 		updates.Properties = map[string]interface{}{}
 	}
-	obj, err := m.vectorRepo.Object(ctx, cls, id, nil, additional.Properties{}, repl, "")
+	obj, err := m.vectorRepo.Object(ctx, cls, id, nil, additional.Properties{}, repl, tenantKey)
 	if err != nil {
 		return &Error{"repo.object", StatusInternalServerError, err}
 	}
 	if obj == nil {
 		return &Error{"not found", StatusNotFound, err}
 	}
-	return m.patchObject(ctx, principal, obj, updates, repl, propertiesToDelete)
+	return m.patchObject(ctx, principal, obj, updates, repl, propertiesToDelete, tenantKey)
 }
 
 // patchObject patches an existing object obj with updates
 func (m *Manager) patchObject(ctx context.Context, principal *models.Principal,
-	obj *search.Result, updates *models.Object, repl *additional.ReplicationProperties, propertiesToDelete []string,
+	obj *search.Result, updates *models.Object, repl *additional.ReplicationProperties,
+	propertiesToDelete []string, tenantKey string,
 ) *Error {
 	cls, id := updates.Class, updates.ID
 	primitive, refs := m.splitPrimitiveAndRefs(updates.Properties.(map[string]interface{}), cls, id)
@@ -102,7 +103,7 @@ func (m *Manager) patchObject(ctx context.Context, principal *models.Principal,
 		mergeDoc.AdditionalProperties = objWithVec.Additional
 	}
 
-	if err := m.vectorRepo.Merge(ctx, mergeDoc, repl); err != nil {
+	if err := m.vectorRepo.Merge(ctx, mergeDoc, repl, tenantKey); err != nil {
 		return &Error{"repo.merge", StatusInternalServerError, err}
 	}
 
diff --git a/usecases/objects/references_add.go b/usecases/objects/references_add.go
index 409761df80..f9b5a84b9b 100644
--- a/usecases/objects/references_add.go
+++ b/usecases/objects/references_add.go
@@ -25,11 +25,8 @@ import (
 // AddObjectReference to an existing object. If the class contains a network
 // ref, it has a side-effect on the schema: The schema will be updated to
 // include this particular network ref class.
-func (m *Manager) AddObjectReference(
-	ctx context.Context,
-	principal *models.Principal,
-	input *AddReferenceInput,
-	repl *additional.ReplicationProperties,
+func (m *Manager) AddObjectReference(ctx context.Context, principal *models.Principal,
+	input *AddReferenceInput, repl *additional.ReplicationProperties, tenantKey string,
 ) *Error {
 	m.metrics.AddReferenceInc()
 	defer m.metrics.AddReferenceDec()
@@ -58,12 +55,12 @@ func (m *Manager) AddObjectReference(
 	}
 	defer unlock()
 
-	validator := validation.New(m.vectorRepo.Exists, m.config, repl)
+	validator := validation.New(m.vectorRepo.Exists, m.config, repl, tenantKey)
 	if err := input.validate(ctx, principal, validator, m.schemaManager); err != nil {
 		return &Error{"validate inputs", StatusBadRequest, err}
 	}
 	if !deprecatedEndpoint {
-		ok, err := m.vectorRepo.Exists(ctx, input.Class, input.ID, repl, "")
+		ok, err := m.vectorRepo.Exists(ctx, input.Class, input.ID, repl, tenantKey)
 		if err != nil {
 			return &Error{"source object", StatusInternalServerError, err}
 		}
@@ -72,7 +69,8 @@ func (m *Manager) AddObjectReference(
 		}
 	}
 
-	if err := m.vectorRepo.AddReference(ctx, input.Class, input.ID, input.Property, &input.Ref, repl); err != nil {
+	if err := m.vectorRepo.AddReference(ctx, input.Class, input.ID,
+		input.Property, &input.Ref, repl, tenantKey); err != nil {
 		return &Error{"add reference to repo", StatusInternalServerError, err}
 	}
 
diff --git a/usecases/objects/references_delete.go b/usecases/objects/references_delete.go
index 9455d0d8c7..666791dd83 100644
--- a/usecases/objects/references_delete.go
+++ b/usecases/objects/references_delete.go
@@ -33,18 +33,15 @@ type DeleteReferenceInput struct {
 	Reference models.SingleRef
 }
 
-func (m *Manager) DeleteObjectReference(
-	ctx context.Context,
-	principal *models.Principal,
-	input *DeleteReferenceInput,
-	repl *additional.ReplicationProperties,
+func (m *Manager) DeleteObjectReference(ctx context.Context, principal *models.Principal,
+	input *DeleteReferenceInput, repl *additional.ReplicationProperties, tenantKey string,
 ) *Error {
 	m.metrics.DeleteReferenceInc()
 	defer m.metrics.DeleteReferenceDec()
 
 	deprecatedEndpoint := input.Class == ""
 	res, err := m.getObjectFromRepo(ctx, input.Class, input.ID,
-		additional.Properties{}, nil, "")
+		additional.Properties{}, nil, tenantKey)
 	if err != nil {
 		errnf := ErrNotFound{}
 		if errors.As(err, &errnf) {
@@ -82,7 +79,7 @@ func (m *Manager) DeleteObjectReference(
 	}
 	obj.LastUpdateTimeUnix = m.timeSource.Now()
 
-	err = m.vectorRepo.PutObject(ctx, obj, res.Vector, repl, "")
+	err = m.vectorRepo.PutObject(ctx, obj, res.Vector, repl, tenantKey)
 	if err != nil {
 		return &Error{"repo.putobject", StatusInternalServerError, err}
 	}
diff --git a/usecases/objects/references_update.go b/usecases/objects/references_update.go
index 4474835a9f..731c71524d 100644
--- a/usecases/objects/references_update.go
+++ b/usecases/objects/references_update.go
@@ -38,11 +38,8 @@ type PutReferenceInput struct {
 // UpdateObjectReferences of a specific data object. If the class contains a network
 // ref, it has a side-effect on the schema: The schema will be updated to
 // include this particular network ref class.
-func (m *Manager) UpdateObjectReferences(
-	ctx context.Context,
-	principal *models.Principal,
-	input *PutReferenceInput,
-	repl *additional.ReplicationProperties,
+func (m *Manager) UpdateObjectReferences(ctx context.Context, principal *models.Principal,
+	input *PutReferenceInput, repl *additional.ReplicationProperties, tenantKey string,
 ) *Error {
 	m.metrics.UpdateReferenceInc()
 	defer m.metrics.UpdateReferenceDec()
@@ -72,7 +69,7 @@ func (m *Manager) UpdateObjectReferences(
 	}
 	defer unlock()
 
-	validator := validation.New(m.vectorRepo.Exists, m.config, repl)
+	validator := validation.New(m.vectorRepo.Exists, m.config, repl, tenantKey)
 	if err := input.validate(ctx, principal, validator, m.schemaManager); err != nil {
 		return &Error{"bad inputs", StatusBadRequest, err}
 	}
@@ -83,7 +80,7 @@ func (m *Manager) UpdateObjectReferences(
 		obj.Properties.(map[string]interface{})[input.Property] = input.Refs
 	}
 	obj.LastUpdateTimeUnix = m.timeSource.Now()
-	err = m.vectorRepo.PutObject(ctx, obj, res.Vector, repl, "")
+	err = m.vectorRepo.PutObject(ctx, obj, res.Vector, repl, tenantKey)
 	if err != nil {
 		return &Error{"repo.putobject", StatusInternalServerError, err}
 	}
diff --git a/usecases/objects/update.go b/usecases/objects/update.go
index 5acdcf6c9c..5bf05f3cd3 100644
--- a/usecases/objects/update.go
+++ b/usecases/objects/update.go
@@ -68,7 +68,7 @@ func (m *Manager) updateObjectToConnectorAndSchema(ctx context.Context,
 		WithField("id", id).
 		Debug("received update kind request")
 
-	err = m.validateObjectAndNormalizeNames(ctx, principal, updates, repl)
+	err = m.validateObjectAndNormalizeNames(ctx, principal, updates, repl, tenantKey)
 	if err != nil {
 		return nil, NewErrInvalidUserInput("invalid object: %v", err)
 	}
diff --git a/usecases/objects/validate.go b/usecases/objects/validate.go
index 2b9e2f0add..e51ed08e01 100644
--- a/usecases/objects/validate.go
+++ b/usecases/objects/validate.go
@@ -34,7 +34,7 @@ func (m *Manager) ValidateObject(ctx context.Context, principal *models.Principa
 	}
 	defer unlock()
 
-	err = m.validateObjectAndNormalizeNames(ctx, principal, obj, repl)
+	err = m.validateObjectAndNormalizeNames(ctx, principal, obj, repl, "")
 	if err != nil {
 		return NewErrInvalidUserInput("invalid object: %v", err)
 	}
diff --git a/usecases/objects/validation/model_validation.go b/usecases/objects/validation/model_validation.go
index a78054aa23..29fcf56662 100644
--- a/usecases/objects/validation/model_validation.go
+++ b/usecases/objects/validation/model_validation.go
@@ -65,15 +65,17 @@ type Validator struct {
 	exists           exists
 	config           *config.WeaviateConfig
 	replicationProps *additional.ReplicationProperties
+	tenantKey        string
 }
 
 func New(exists exists, config *config.WeaviateConfig,
-	repl *additional.ReplicationProperties,
+	repl *additional.ReplicationProperties, tenantKey string,
 ) *Validator {
 	return &Validator{
 		exists:           exists,
 		config:           config,
 		replicationProps: repl,
+		tenantKey:        tenantKey,
 	}
 }
 
@@ -109,7 +111,7 @@ func (v *Validator) ValidateSingleRef(ctx context.Context, cref *models.SingleRe
 	}
 
 	// locally check for object existence
-	ok, err := v.exists(ctx, ref.Class, ref.TargetID, v.replicationProps, "")
+	ok, err := v.exists(ctx, ref.Class, ref.TargetID, v.replicationProps, v.tenantKey)
 	if err != nil {
 		return err
 	}
diff --git a/usecases/traverser/hybrid/rank_fusion.go b/usecases/traverser/hybrid/rank_fusion.go
index a641486dc3..48213172d3 100644
--- a/usecases/traverser/hybrid/rank_fusion.go
+++ b/usecases/traverser/hybrid/rank_fusion.go
@@ -94,8 +94,7 @@ func FusionReciprocal(weights []float64, results [][]*Result) []*Result {
 	}
 
 	sort.Slice(concat, func(i, j int) bool {
-		a_b := float64(concat[j].Score - concat[i].Score)
-		if a_b*a_b < 1e-14 {
+		if concat[j].Score == concat[i].Score {
 			return concat[i].SecondarySortValue > concat[j].SecondarySortValue
 		}
 		return float64(concat[i].Score) > float64(concat[j].Score)


Test patch:
diff --git a/adapters/handlers/rest/handlers_objects_test.go b/adapters/handlers/rest/handlers_objects_test.go
index 413b216d23..dfed81738b 100644
--- a/adapters/handlers/rest/handlers_objects_test.go
+++ b/adapters/handlers/rest/handlers_objects_test.go
@@ -1048,7 +1048,7 @@ type fakeManager struct {
 }
 
 func (f *fakeManager) HeadObject(context.Context, *models.Principal,
-	string, strfmt.UUID, *additional.ReplicationProperties,
+	string, strfmt.UUID, *additional.ReplicationProperties, string,
 ) (bool, *uco.Error) {
 	return f.headObjectReturn, f.headObjectErr
 }
@@ -1098,7 +1098,7 @@ func (f *fakeManager) UpdateObject(_ context.Context, _ *models.Principal, _ str
 }
 
 func (f *fakeManager) MergeObject(_ context.Context, _ *models.Principal,
-	_ *models.Object, _ *additional.ReplicationProperties,
+	_ *models.Object, _ *additional.ReplicationProperties, _ string,
 ) *uco.Error {
 	return f.patchObjectReturn
 }
@@ -1110,19 +1110,19 @@ func (f *fakeManager) DeleteObject(_ context.Context, _ *models.Principal,
 }
 
 func (f *fakeManager) AddObjectReference(context.Context, *models.Principal,
-	*uco.AddReferenceInput, *additional.ReplicationProperties,
+	*uco.AddReferenceInput, *additional.ReplicationProperties, string,
 ) *uco.Error {
 	return f.addRefErr
 }
 
 func (f *fakeManager) UpdateObjectReferences(context.Context, *models.Principal,
-	*uco.PutReferenceInput, *additional.ReplicationProperties,
+	*uco.PutReferenceInput, *additional.ReplicationProperties, string,
 ) *uco.Error {
 	return f.putRefErr
 }
 
 func (f *fakeManager) DeleteObjectReference(context.Context, *models.Principal,
-	*uco.DeleteReferenceInput, *additional.ReplicationProperties,
+	*uco.DeleteReferenceInput, *additional.ReplicationProperties, string,
 ) *uco.Error {
 	return f.deleteRefErr
 }
diff --git a/adapters/repos/db/bm25f_test.go b/adapters/repos/db/bm25f_test.go
index 358118e411..0fa225001d 100644
--- a/adapters/repos/db/bm25f_test.go
+++ b/adapters/repos/db/bm25f_test.go
@@ -242,6 +242,10 @@ func TestBM25FJourney(t *testing.T) {
 		// Check results in correct order
 		require.Equal(t, uint64(4), res[0].DocID())
 		require.Equal(t, uint64(5), res[1].DocID())
+		require.Equal(t, uint64(6), res[2].DocID())
+		require.Equal(t, uint64(3), res[3].DocID())
+		require.Equal(t, uint64(0), res[4].DocID())
+		require.Equal(t, uint64(2), res[5].DocID())
 
 		// Without additionalExplanations no explainScore entry should be present
 		require.Contains(t, res[0].Object.Additional, "score")
@@ -298,6 +302,11 @@ func TestBM25FJourney(t *testing.T) {
 		// Check results in correct order
 		require.Equal(t, uint64(4), res[0].DocID())
 		require.Equal(t, uint64(5), res[1].DocID())
+		require.Equal(t, uint64(6), res[2].DocID())
+		require.Equal(t, uint64(2), res[3].DocID())
+		require.Equal(t, uint64(3), res[4].DocID())
+		require.Equal(t, uint64(0), res[5].DocID())
+		require.Equal(t, uint64(1), res[6].DocID())
 	})
 
 	t.Run("bm25f journey boosted", func(t *testing.T) {
@@ -316,6 +325,9 @@ func TestBM25FJourney(t *testing.T) {
 		require.Equal(t, uint64(5), res[1].DocID())
 		require.Equal(t, uint64(6), res[2].DocID())
 		require.Equal(t, uint64(0), res[3].DocID())
+		require.Equal(t, uint64(1), res[4].DocID())
+		require.Equal(t, uint64(2), res[5].DocID())
+		require.Equal(t, uint64(3), res[6].DocID())
 	})
 
 	t.Run("Check search with two terms", func(t *testing.T) {
@@ -712,8 +724,20 @@ func TestBM25FCompare(t *testing.T) {
 		}
 
 		// Not all the scores are unique and the search is not stable, so pick ones that don't move
-		require.Equal(t, withBM25Fobjs[2].DocID(), objs[2].DocID())
-		require.Equal(t, withBM25Fobjs[5].DocID(), objs[5].DocID())
+		require.Equal(t, uint64(4), objs[0].DocID())
+		require.Equal(t, uint64(5), objs[1].DocID())
+		require.Equal(t, uint64(6), objs[2].DocID())
+		require.Equal(t, uint64(1), objs[3].DocID())
+		require.Equal(t, uint64(2), objs[4].DocID())
+		require.Equal(t, uint64(0), objs[5].DocID())
+
+		require.Equal(t, uint64(4), withBM25Fobjs[0].DocID())
+		require.Equal(t, uint64(5), withBM25Fobjs[1].DocID())
+		require.Equal(t, uint64(6), withBM25Fobjs[2].DocID())
+		require.Equal(t, uint64(1), withBM25Fobjs[3].DocID())
+		require.Equal(t, uint64(2), withBM25Fobjs[4].DocID())
+		require.Equal(t, uint64(0), withBM25Fobjs[5].DocID())
+
 	}
 }
 
diff --git a/adapters/repos/db/clusterintegrationtest/cluster_integration_test.go b/adapters/repos/db/clusterintegrationtest/cluster_integration_test.go
index edfc731a6b..f7a98824b7 100644
--- a/adapters/repos/db/clusterintegrationtest/cluster_integration_test.go
+++ b/adapters/repos/db/clusterintegrationtest/cluster_integration_test.go
@@ -376,7 +376,7 @@ func testDistributed(t *testing.T, dirName string, batch bool) {
 			PrimitiveSchema: map[string]interface{}{
 				"other_property": "a-value-inserted-through-merge",
 			},
-		}, nil)
+		}, nil, "")
 
 		require.Nil(t, err)
 	})
diff --git a/adapters/repos/db/crud_integration_test.go b/adapters/repos/db/crud_integration_test.go
index 7faadb9e3b..74e2a95a13 100644
--- a/adapters/repos/db/crud_integration_test.go
+++ b/adapters/repos/db/crud_integration_test.go
@@ -2025,7 +2025,7 @@ func Test_PutPatchRestart(t *testing.T) {
 				},
 				Vector:     testVec,
 				UpdateTime: time.Now().UnixNano() / int64(time.Millisecond),
-			}, nil)
+			}, nil, "")
 			require.Nil(t, err)
 
 			require.Nil(t, repo.Shutdown(ctx))
@@ -2532,7 +2532,7 @@ func TestIndexDifferentVectorLength(t *testing.T) {
 			PrimitiveSchema: map[string]interface{}{},
 			Vector:          []float32{1, 2, 3, 4},
 			UpdateTime:      time.Now().UnixNano() / int64(time.Millisecond),
-		}, nil)
+		}, nil, "")
 		require.NotNil(t, err)
 		found, err := repo.Object(context.Background(), class.Class,
 			obj1ID, nil, additional.Properties{}, nil, "")
@@ -2547,7 +2547,7 @@ func TestIndexDifferentVectorLength(t *testing.T) {
 			PrimitiveSchema: map[string]interface{}{},
 			Vector:          []float32{1, 2, 3},
 			UpdateTime:      time.Now().UnixNano() / int64(time.Millisecond),
-		}, nil)
+		}, nil, "")
 		require.Nil(t, err)
 		found, err := repo.Object(context.Background(), class.Class, objNilID, nil,
 			additional.Properties{}, nil, "")
diff --git a/adapters/repos/db/crud_references_integration_test.go b/adapters/repos/db/crud_references_integration_test.go
index 69d60cf29c..dc5932ff40 100644
--- a/adapters/repos/db/crud_references_integration_test.go
+++ b/adapters/repos/db/crud_references_integration_test.go
@@ -628,7 +628,7 @@ func Test_AddingReferenceOneByOne(t *testing.T) {
 
 		err := repo.AddReference(context.Background(), "AddingReferencesTestSource", sourceID, "toTarget", &models.SingleRef{
 			Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", targetID)),
-		}, nil)
+		}, nil, "")
 		assert.Nil(t, err)
 
 		// Check dimensions after adding reference
@@ -666,7 +666,7 @@ func Test_AddingReferenceOneByOne(t *testing.T) {
 	t.Run("reference a second target", func(t *testing.T) {
 		err := repo.AddReference(context.Background(), "AddingReferencesTestSource", sourceID, "toTarget", &models.SingleRef{
 			Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", target2ID)),
-		}, nil)
+		}, nil, "")
 		assert.Nil(t, err)
 	})
 
diff --git a/adapters/repos/db/merge_integration_test.go b/adapters/repos/db/merge_integration_test.go
index b9d5c20b5d..cb41293b16 100644
--- a/adapters/repos/db/merge_integration_test.go
+++ b/adapters/repos/db/merge_integration_test.go
@@ -218,7 +218,7 @@ func Test_MergingObjects(t *testing.T) {
 			UpdateTime: time.Now().UnixNano() / int64(time.Millisecond),
 		}
 
-		err := repo.Merge(context.Background(), md, nil)
+		err := repo.Merge(context.Background(), md, nil, "")
 		assert.Nil(t, err)
 	})
 
@@ -262,7 +262,7 @@ func Test_MergingObjects(t *testing.T) {
 			},
 		}
 
-		err := repo.Merge(context.Background(), md, nil)
+		err := repo.Merge(context.Background(), md, nil, "")
 		assert.Equal(t, fmt.Errorf(
 			"merge from non-existing index for WrongClass"), err)
 	})
@@ -289,7 +289,7 @@ func Test_MergingObjects(t *testing.T) {
 			},
 			References: refs,
 		}
-		err = repo.Merge(context.Background(), md, nil)
+		err = repo.Merge(context.Background(), md, nil, "")
 		assert.Nil(t, err)
 	})
 
@@ -339,7 +339,7 @@ func Test_MergingObjects(t *testing.T) {
 			ID:         sourceID,
 			References: refs,
 		}
-		err = repo.Merge(context.Background(), md, nil)
+		err = repo.Merge(context.Background(), md, nil, "")
 		assert.Nil(t, err)
 	})
 
@@ -370,7 +370,7 @@ func Test_MergingObjects(t *testing.T) {
 			Class:           "MergeTestNoVector",
 			ID:              noVecID,
 			PrimitiveSchema: map[string]interface{}{"foo": "baz"},
-		}, nil)
+		}, nil, "")
 		require.Nil(t, err)
 
 		orig, err := repo.ObjectByID(context.Background(), noVecID, nil, additional.Properties{})
@@ -558,7 +558,7 @@ func Test_Merge_UntouchedPropsCorrectlyIndexed(t *testing.T) {
 			},
 			References: nil,
 		}
-		err = repo.Merge(context.Background(), md, nil)
+		err = repo.Merge(context.Background(), md, nil, "")
 		assert.Nil(t, err)
 	})
 
diff --git a/adapters/repos/db/ranked_fusion_test.go b/adapters/repos/db/ranked_fusion_test.go
index 280b0bf911..d4a98fdeaa 100644
--- a/adapters/repos/db/ranked_fusion_test.go
+++ b/adapters/repos/db/ranked_fusion_test.go
@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"math/rand"
 	"os"
+	"strconv"
 	"testing"
 	"time"
 
@@ -386,41 +387,66 @@ func TestRFJourney(t *testing.T) {
 		require.Equal(t, float32(0.016129032), results[1].Score)
 	})
 
-	/*
-		Doing searches like this is not working correctly, possibly due to my configuration of the repo?
+	// Check basic search with one property
+	results_set_1, err := repo.VectorClassSearch(context.TODO(), dto.GetParams{
+		ClassName:    "MyClass",
+		SearchVector: peanutsVector(),
+		Pagination: &filters.Pagination{
+			Limit: 100,
+		},
+	})
 
-		// Check basic search with one property
-		results_set_1, err := repo.VectorClassSearch(context.TODO(), dto.GetParams{
-			ClassName:    "MyClass",
-			SearchVector: peanutsVector(),
-			Pagination: &filters.Pagination{
-				Limit: 100,
-			},
+	require.Nil(t, err)
+	results_set_2, err := repo.VectorClassSearch(context.TODO(), dto.GetParams{
+		ClassName:    "MyClass",
+		SearchVector: journeyVector(),
+		Pagination: &filters.Pagination{
+			Limit: 100,
+		},
+	})
+	require.Nil(t, err)
+
+	// convert search.Result to hybrid.Result
+	var results_set_1_hybrid []*hybrid.Result
+	for _, r := range results_set_1 {
+		// parse the last 12 digits of the id to get the uint64
+		id, err := strconv.Atoi(string(r.ID)[len(r.ID)-12:])
+		if err != nil {
+			fmt.Println(err)
+		}
+
+		results_set_1_hybrid = append(results_set_1_hybrid, &hybrid.Result{
+			DocID:  uint64(id),
+			Result: &r,
 		})
+	}
 
-		require.Nil(t, err)
-		results_set_2, err := repo.VectorClassSearch(context.TODO(), dto.GetParams{
-			ClassName:    "MyClass",
-			SearchVector: journeyVector(),
-			Pagination: &filters.Pagination{
-				Limit: 100,
-			},
+	var results_set_2_hybrid []*hybrid.Result
+	for _, r := range results_set_2 {
+		// parse the last 12 digits of the id to get the uint64
+		id, err := strconv.Atoi(string(r.ID)[len(r.ID)-12:])
+		if err != nil {
+			fmt.Println(err)
+		}
+
+		results_set_2_hybrid = append(results_set_2_hybrid, &hybrid.Result{
+			DocID:  uint64(id),
+			Result: &r,
 		})
-		require.Nil(t, err)
+	}
 
-		res := traverser.FusionReciprocal([]float64{0.2, 0.8}, [][]search.Result{results_set_1, results_set_2})
-		fmt.Println("--- Start results for Fusion Reciprocal ---")
-		for _, r := range res {
+	res := hybrid.FusionReciprocal([]float64{0.2, 0.8}, [][]*hybrid.Result{results_set_1_hybrid, results_set_2_hybrid})
+	fmt.Println("--- Start results for Fusion Reciprocal (", len(res), ")---")
+	for _, r := range res {
 
-			schema := r.Schema.(map[string]interface{})
-			title := schema["title"].(string)
-			description := schema["description"].(string)
-			fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
-		}
+		schema := r.Schema.(map[string]interface{})
+		title := schema["title"].(string)
+		description := schema["description"].(string)
+		fmt.Printf("Result id: %v, score: %v, title: %v, description: %v, additional %+v\n", r.ID, r.Score, title, description, r.AdditionalProperties)
+	}
 
-		require.Equal(t, "00000000-0000-0000-0000-000000000000", string(res[0].ID))
+	require.Equal(t, "00000000-0000-0000-0000-000000000000", string(res[0].ID))
 
-	*/
 	t.Run("Hybrid", func(t *testing.T) {
 		params := dto.GetParams{
 			ClassName: "MyClass",
@@ -669,12 +695,96 @@ func TestRFJourneyWithFilters(t *testing.T) {
 	})
 }
 
+func TestStability(t *testing.T) {
+	rand.Seed(time.Now().UnixNano())
+	dirName := t.TempDir()
+
+	logger := logrus.New()
+	schemaGetter := &fakeSchemaGetter{shardState: singleShardState()}
+	repo, err := New(logger, Config{
+		RootPath:                  dirName,
+		QueryMaximumResults:       10000,
+		MaxImportGoroutinesFactor: 1,
+		QueryLimit:                20,
+	}, &fakeRemoteClient{}, &fakeNodeResolver{}, &fakeRemoteNodeClient{}, nil, nil)
+	require.Nil(t, err)
+	repo.SetSchemaGetter(schemaGetter)
+	require.Nil(t, repo.WaitForStartup(context.TODO()))
+	defer repo.Shutdown(context.Background())
+
+	SetupFusionClass(t, repo, schemaGetter, logger, 1.2, 0.75)
+	idx := repo.GetIndex("MyClass")
+	require.NotNil(t, idx)
+
+	doc1 := &hybrid.Result{
+		Result: &search.Result{
+			ID: strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfa"),
+			Schema: map[string]interface{}{
+				"title": "peanuts",
+			},
+			Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
+			Score:  0.1,
+		},
+	}
+
+	doc2 := &hybrid.Result{
+		Result: &search.Result{
+			ID: strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfb"),
+			Schema: map[string]interface{}{
+				"title": "peanuts",
+			},
+			Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
+			Score:  0.1,
+		},
+	}
+
+	doc3 := &hybrid.Result{
+		Result: &search.Result{
+			ID: strfmt.UUID("e6f7e8b1-ac53-48eb-b6e4-cbe67396bcfc"),
+			Schema: map[string]interface{}{
+				"title": "peanuts",
+			},
+			Vector: []float32{0.1, 0.2, 0.3, 0.4, 0.5},
+			Score:  0.1,
+		},
+	}
+
+	resultSet1 := []*hybrid.Result{doc1, doc2, doc3}
+	resultSet2 := []*hybrid.Result{doc2, doc1, doc3}
+
+	t.Run("Fusion Reciprocal", func(t *testing.T) {
+		results := hybrid.FusionReciprocal([]float64{0.4, 0.6},
+			[][]*hybrid.Result{resultSet1, resultSet2})
+		fmt.Println("--- Start results for Fusion Reciprocal ---")
+		for _, result := range results {
+			schema := result.Schema.(map[string]interface{})
+			fmt.Println(schema["title"], result.ID, result.Score)
+		}
+		require.Equal(t, 3, len(results))
+		require.Equal(t, resultSet2[0].ID, results[0].ID)
+		require.Equal(t, resultSet2[1].ID, results[1].ID)
+		require.Equal(t, resultSet2[2].ID, results[2].ID)
+	})
+}
+
 func elephantVector() []float32 {
 	return []float32{
 		-0.106136, -0.021716, 0.632442, 0.195315, -0.038854, -0.260533, -0.728847, -0.313725, -0.161967, 0.179243, -0.124185, 0.158839, 0.09563, -0.071267, 0.073928, -0.096735, 0.27266, -0.204127, -0.387028, -0.361406, -0.278027, 0.298766, 0.265405, 0.037477, -0.079904, -0.778953, -0.525643, -0.052346, -0.2174, 0.095746, 0.610937, 0.315672, -0.125526, 0.013475, -0.075578, -0.053183, -0.381475, 0.620278, -0.093857, 0.802608, -0.105773, -0.007902, 0.663528, 0.407708, 0.753832, 0.420718, 0.139289, -0.126864, 0.36345, -0.039222, 0.089002, 0.092151, 0.138025, 0.18881, 0.51416, -0.391045, -0.169528, -0.044023, 0.437196, -0.23917, 0.081247, -0.440846, -0.484764, 0.090495, 0.001852, -0.03441, 0.18548, -0.440182, 0.286827, -0.081451, 0.030155, -0.072746, -0.366531, 0.354118, 0.418432, -0.305682, 0.515893, -0.424999, -0.495273, 0.731375, 0.358407, -0.415989, 0.441337, -0.022167, 0.318837, -0.473018, 0.342046, -0.499794, -0.303161, -0.379234, -0.279082, -0.325648, 0.200613, -0.457396, 0.116745, 0.225836, -0.322175, -0.151425, 0.322014, 0.077097, 0.049998, -0.01005, 0.489028, -0.273297, 0.218896, -0.507729, 0.488891, -0.207774, -0.499136, 0.992803, 0.379556, -0.572352, -0.295821, -0.071392, -0.625823, -0.425159, 0.024593, 0.307965, 0.311686, 0.287844, 0.435028, 0.454474, -0.208158, -0.111947, -0.380334, -0.392014, 1.747561, 0.360315, 0.472088, 0.273835, 0.635424, 0.390057, -0.021349, -0.746944, 0.265353, 0.60709, -0.171053, 0.408823, -0.059646, 0.058306, -0.062817, -0.41064, -0.342016, -0.048077, 0.862758, -0.217101, -0.048961, -0.314094, 0.228395, -0.339353, 0.558551, 0.370054, -0.319855, 0.543137, 0.71334, 0.166296, 0.040412, -0.160482, 0.432088, -0.491292, 0.072819, -0.409627, 0.300197, 0.169077, 0.44379, 0.117131, 0.142459, -0.482226, -0.100245, 0.058273, -0.590567, -0.061971, -0.415718, -0.018105, -0.693528, -0.047609, -0.041873, 0.606186, 0.19767, -0.091001, -0.315381, -1.234111, 0.228805, -0.636861, 0.208757, -0.270024, -0.259684, -0.351592, -0.978549, 0.683986, -0.331669, -0.078729, 0.385676, 0.390955, -0.901898, -0.071451, -0.103991, 0.206379, 0.469656, 0.071528, -0.152589, 0.282268, 0.539651, -0.856463, -0.344053, -0.40572, 0.771483, -0.065611, -0.408832, 0.303948, -0.565157, 0.153293, -0.699892, 1.112725, 0.259508, 0.135771, 0.484552, 0.151274, -0.743235, 0.069811, 0.137583, 0.212661, 0.376839, 0.136164, 0.145626, -0.466645, -0.474334, -0.365033, 0.251158, -0.313904, 0.210487, -1.016155, 0.262768, 0.432895, -0.291339, -0.221825, 0.513278, 0.659038, -0.401398, -0.164522, 0.395279, -0.449811, 0.076142, 0.389243, 0.076184, 0.05539, -0.597094, -0.149824, 0.206724, -0.477001, -0.315719, 0.166689, -0.357187, 0.34429, 0.256624, -0.236781, -0.713059, -0.440255, 0.27353, -0.032257, 0.06925, 0.359134, -0.088975, 0.112507, -0.071103, 0.880417, 0.528587, 0.155656, -0.720531, 0.3068, 0.754715, 0.009366, 0.067487, -0.11898, -0.471064, -0.396507, 0.298669, 0.038283, 0.057218, -0.075818, -0.01513, -0.319236, 0.692123, -0.122985, 0.875938, 0.378184, 0.427029, 0.315545, -0.549573, 0.389602, -0.017071, 0.160122, 0.368208, 0.060474, -0.199651, 0.087829, 0.447339, 0.012265, -0.095388, -0.07034,
 	}
 }
 
+// "journey"
+func journeyVector() []float32 {
+	return []float32{
+		-0.523002, 0.14169, 0.016461, -0.069062, 0.487908, -0.024193, -0.282436, 0.004778, -0.378135, 0.396011, 0.094045, -0.06584, 0.061162, -0.600018, -0.110189, 0.244562, 0.433501, 0.303775, -0.451004, -0.453709, 0.350324, 0.2047, -0.091615, -0.282805, -0.232953, -0.215143, 0.333113, -0.126952, -0.639225, 0.101498, 0.232343, 0.58831, 0.971, 0.494446, -0.483305, -0.873438, -0.483694, 0.406465, 0.342816, 1.253387, -0.24718, -0.046063, -0.660406, 0.103386, -0.06063, 0.3422, 0.322542, 0.026074, -0.623612, 0.489793, -0.632363, 0.448922, -0.370049, 0.212377, -0.315855, 0.364525, 0.056798, 0.805679, 0.145633, 0.850648, 0.432728, -1.431841, -0.226569, -0.315194, 0.560742, 0.261859, -0.001653, -0.068738, -0.662729, -0.049259, -0.380322, -0.374194, 0.363328, 0.341796, -0.077566, 0.503337, 0.353664, -0.045754, -0.499081, 0.198603, 0.038837, -0.460198, 0.00735, -0.270993, 0.950923, -0.085815, -0.52167, -0.10439, 0.31398, -0.560229, 0.411738, -0.129033, -0.009998, 0.443882, -0.045643, -0.078445, -0.259311, -0.08337, 0.232652, -0.015912, -0.229458, -0.474973, 1.265934, -0.204483, -0.293586, -0.619023, 0.158895, -0.730671, -0.163626, 0.411716, -0.000132, 0.069014, -0.682714, 0.303234, 0.299097, -0.484469, 0.608172, -0.163785, -0.419754, -0.160745, 0.278904, 0.550542, -0.008052, 0.160397, -0.211354, -0.19755, 1.182627, 0.705073, -0.461941, -0.235292, 0.534275, -0.096419, -0.405812, -0.157745, -0.335469, 0.200545, 0.406497, -0.05341, -0.009234, -0.029925, -0.394101, -0.060133, 0.182601, 0.615583, 0.212157, 0.363921, 0.41868, -0.652791, 0.657173, -0.131662, 0.269305, 0.381748, -0.827964, -0.452596, 0.201918, 0.0673, -0.020293, 0.486942, -0.72454, -0.435051, -0.615452, -0.218852, 0.090703, -0.471036, 0.032373, 0.569953, 0.098359, -0.570767, -0.21015, -0.53019, -0.227117, 0.327978, 0.087079, -0.115037, 0.09193, -0.922884, -0.165566, -0.353596, 0.535904, -0.328579, 0.029465, -1.508702, -0.320394, -0.596324, 0.290277, -0.272515, 0.104348, 0.062855, -0.236447, 0.388958, -0.186552, -0.156253, 0.355678, 0.53834, -0.321627, 0.486004, 0.301326, 0.786779, 0.430292, -0.012458, -0.164964, -0.072951, 0.746564, 0.19136, 0.003213, 0.53479, 0.511118, -0.559153, -0.088731, -0.436206, 0.421004, 0.193043, -0.656222, 0.133223, 0.00107, 0.037087, 0.263503, 0.378593, 0.158718, -0.401664, -0.10563, -0.111221, 0.018598, -0.036396, 0.189584, -0.347721, -0.544111, -0.018158, 0.134147, -0.362431, -0.702383, -0.375221, 0.365745, 0.118082, -0.19102, -0.150732, 0.638995, 0.070662, -0.054605, 0.221755, 0.23726, -0.274418, 0.294639, 0.221177, -0.012947, 0.08444, -0.486605, -0.225034, 0.774728, 0.167609, 0.766647, 0.381622, 0.241907, -0.196452, 0.245138, -0.203225, -0.701671, 0.236662, -0.627221, 0.143006, 0.055671, 0.564561, -0.114897, -0.542244, 0.464601, 0.201577, -0.177196, -0.795015, -0.580793, -0.134996, -0.579672, -0.399042, 0.008118, -0.458077, -0.43296, 0.074138, 0.328092, 0.02934, 0.406294, 0.330677, -0.138583, -0.676608, -0.099983, -0.137182, 0.713108, 0.248643, 0.153462, 0.56039, -0.109877, 0.260655, -0.529779, -0.13416, 0.067448, -0.139468, -0.179535, 0.372629, 0.287185, 0.100582, 0.093573, -0.208796,
+	}
+}
+
+// "peanuts"
+func peanutsVector() []float32 {
+	return []float32{0.563772, -0.779601, -0.18491, 0.509093, 0.080691, -0.621506, -0.127855, -0.165435, 0.57496, 0.006945, 0.452967, -0.285534, -0.129205, 0.193883, 0.092732, 0.083284, 0.714696, 0.107078, -0.398886, -0.117344, -0.387671, 0.026748, -0.562581, -0.007178, -0.354846, -0.431299, -0.788228, 0.175199, 0.914486, 0.441425, 0.089804, 0.284472, 0.106916, -0.133174, 0.399299, 0.002177, 0.551474, 0.389343, -0.016404, 0.770212, -0.219833, 0.303322, 0.127598, -0.378037, -0.172971, 0.394854, -0.424415, -0.71173, 0.080323, -0.406372, 0.398395, -0.594257, -0.418287, 0.055755, -0.352343, -0.393373, -0.732443, 0.333113, 0.420378, -0.50231, 0.261863, -0.061356, -0.180985, 0.311916, -0.180207, -0.154169, 0.371969, 0.454717, 0.320499, -0.182448, 0.087347, 0.585272, 0.136098, 0.288909, -0.229571, -0.140278, 0.229644, -0.557327, -0.110147, 0.034364, -0.021627, -0.598707, 0.221168, -0.059591, -0.203555, -0.434876, 0.209634, -0.460895, -0.345391, -0.18248, -0.24853, 0.730295, -0.295402, -0.562237, 0.255922, 0.076661, -0.713794, -0.354747, -1.109888, -0.066694, -0.195747, -0.282781, 0.459869, -0.309599, -0.002211, -0.274471, -0.003621, 0.008228, 0.011961, -0.258772, -0.210687, -0.664148, -0.257968, 0.231335, 0.530392, -0.205764, -0.621055, -0.440582, 0.080335, 0.017367, 0.880771, 0.656272, -0.713248, -0.208629, 0.095346, 0.336802, 0.888765, 0.251927, 0.066473, 0.182678, -0.220494, 0.288927, -0.602036, 0.057106, -0.594172, 0.848978, 0.751973, 0.090758, -0.732184, 0.683475, -0.075085, 0.381326, -0.076531, -0.253831, 0.10311, -0.02988, -0.043583, 0.005746, -0.460183, -0.189048, 0.25792, 0.477565, 0.391953, 0.08469, -0.10022, 0.454383, 0.170811, 0.196819, -0.760276, 0.045886, -0.743934, 0.190072, -0.216326, -0.624262, -0.22944, 0.066233, 1.024283, 0.044009, -0.373543, -0.243663, 0.204444, 0.402183, 0.043356, 0.31716, 0.302178, 0.369374, 0.36901, 0.02886, -0.26132, -0.234714, -0.791308, -0.433528, -0.098797, -0.447567, -0.124892, -0.119958, 0.31019, -0.096092, -0.259021, -0.078099, -0.178679, 0.14879, 0.106432, -0.450003, -0.294972, 0.044257, 0.402832, 0.263266, -0.309787, -0.17766, -0.399104, 0.577422, 0.30102, 0.05326, -0.271873, 0.204839, -0.019002, -0.743543, 0.739314, -0.115868, -0.504568, -0.115713, 0.042769, -0.123561, -0.057097, 0.407096, 0.770627, 0.372981, -0.321945, 0.349865, 0.437571, -0.77394, -0.090017, -0.011273, -0.468664, -0.735247, -0.745655, 0.018983, -0.248165, 0.215342, -0.136942, -0.458205, 0.4572, -0.032293, 0.654409, -0.024184, -0.392144, 0.634579, 0.222185, 0.471951, -0.063678, -0.473611, 0.796793, -0.295494, -0.157621, -0.103365, -0.564606, -0.092231, -0.517754, -0.369358, 0.137479, -0.214837, 0.11057, -0.095227, 0.726768, -0.079352, -0.065927, -0.846602, -0.317556, -0.344271, 0.201353, -0.367633, -0.004477, 0.157801, -0.249114, -0.549599, -0.147123, 0.308084, -0.175564, 0.306867, -0.071157, -0.588356, 0.450987, -0.184879, -0.096782, -0.006346, -0.017689, 0.005998, 0.200963, 0.225338, 0.189993, -1.105824, 0.520005, 0.129679, 0.198194, -0.254813, -0.127583, 0.326054, 0.009956, -0.016008, -0.483044, 0.801135, -0.517766, 0.067179, -0.372756, -0.511781, 0.058562, -0.082906, -0.28168, -0.285859}
+}
+
 type fakeMetrics struct {
 	mock.Mock
 }
diff --git a/adapters/repos/db/vector/hnsw/condensor_integration_test.go b/adapters/repos/db/vector/hnsw/condensor_integration_test.go
index a92e145b3c..d0b06e739a 100644
--- a/adapters/repos/db/vector/hnsw/condensor_integration_test.go
+++ b/adapters/repos/db/vector/hnsw/condensor_integration_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/sirupsen/logrus/hooks/test"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	ssdhelpers "github.com/weaviate/weaviate/adapters/repos/db/vector/ssdhelpers"
 	"github.com/weaviate/weaviate/entities/cyclemanager"
 )
 
@@ -487,6 +488,91 @@ func TestCondensorWithoutEntrypoint(t *testing.T) {
 	})
 }
 
+func TestCondensorWithPQInformation(t *testing.T) {
+	rand.Seed(time.Now().UnixNano())
+	rootPath := t.TempDir()
+	ctx := context.Background()
+
+	logger, _ := test.NewNullLogger()
+	uncondensed, err := NewCommitLogger(rootPath, "uncondensed", logger,
+		cyclemanager.NewNoop())
+	require.Nil(t, err)
+	defer uncondensed.Shutdown(ctx)
+
+	encoders := []ssdhelpers.PQEncoder{
+		ssdhelpers.NewKMeansWithCenters(
+			4,
+			2,
+			0,
+			[][]float32{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
+		),
+		ssdhelpers.NewKMeansWithCenters(
+			4,
+			2,
+			1,
+			[][]float32{{8, 7}, {6, 5}, {4, 3}, {2, 1}},
+		),
+		ssdhelpers.NewKMeansWithCenters(
+			4,
+			2,
+			2,
+			[][]float32{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
+		),
+	}
+
+	t.Run("add pq info", func(t *testing.T) {
+		uncondensed.AddPQ(ssdhelpers.PQData{
+			Ks:                  4,
+			M:                   3,
+			Dimensions:          6,
+			EncoderType:         ssdhelpers.UseKMeansEncoder,
+			EncoderDistribution: uint8(0),
+			Encoders:            encoders,
+			UseBitsEncoding:     false,
+		})
+
+		require.Nil(t, uncondensed.Flush())
+	})
+
+	t.Run("condense the original and verify the PQ info is present", func(t *testing.T) {
+		input, ok, err := getCurrentCommitLogFileName(commitLogDirectory(rootPath, "uncondensed"))
+		require.Nil(t, err)
+		require.True(t, ok)
+
+		err = NewMemoryCondensor(logger).Do(commitLogFileName(rootPath, "uncondensed", input))
+		require.Nil(t, err)
+
+		actual, ok, err := getCurrentCommitLogFileName(
+			commitLogDirectory(rootPath, "uncondensed"))
+		require.Nil(t, err)
+		require.True(t, ok)
+
+		assert.True(t, strings.HasSuffix(actual, ".condensed"),
+			"commit log is now saved as condensed")
+
+		initialState := DeserializationResult{}
+		fd, err := os.Open(commitLogFileName(rootPath, "uncondensed", actual))
+		require.Nil(t, err)
+
+		bufr := bufio.NewReader(fd)
+		res, _, err := NewDeserializer(logger).Do(bufr, &initialState, false)
+		require.Nil(t, err)
+
+		assert.True(t, res.Compressed)
+		expected := ssdhelpers.PQData{
+			Ks:                  4,
+			M:                   3,
+			Dimensions:          6,
+			EncoderType:         ssdhelpers.UseKMeansEncoder,
+			EncoderDistribution: uint8(0),
+			Encoders:            encoders,
+			UseBitsEncoding:     false,
+		}
+
+		assert.Equal(t, expected, res.PQData)
+	})
+}
+
 func assertIndicesFromCommitLogsMatch(t *testing.T, fileNameControl string,
 	fileNames []string,
 ) {
diff --git a/adapters/repos/db/vector/hnsw/index_too_many_links_bug_integration_test.go b/adapters/repos/db/vector/hnsw/index_too_many_links_bug_integration_test.go
index bac082e56a..30ceb79927 100644
--- a/adapters/repos/db/vector/hnsw/index_too_many_links_bug_integration_test.go
+++ b/adapters/repos/db/vector/hnsw/index_too_many_links_bug_integration_test.go
@@ -146,6 +146,57 @@ func Test_NoRace_ManySmallCommitlogs(t *testing.T) {
 		}
 	})
 
+	t.Run("delete 10 percent of data", func(t *testing.T) {
+		type tuple struct {
+			vec []float32
+			id  uint64
+		}
+
+		jobs := make(chan tuple, n)
+
+		wg := sync.WaitGroup{}
+		worker := func(jobs chan tuple) {
+			for job := range jobs {
+				index.Delete(job.id)
+			}
+
+			wg.Done()
+		}
+
+		for i := 0; i < runtime.GOMAXPROCS(0); i++ {
+			wg.Add(1)
+			go worker(jobs)
+		}
+
+		for i, vec := range data[:n/10] {
+			jobs <- tuple{id: uint64(i), vec: vec}
+		}
+
+		close(jobs)
+
+		wg.Wait()
+	})
+
+	index.Flush()
+
+	t.Run("verify there are no nodes with too many links - post deletion", func(t *testing.T) {
+		for i, node := range index.nodes {
+			if node == nil {
+				continue
+			}
+
+			for level, conns := range node.connections {
+				m := index.maximumConnections
+				if level == 0 {
+					m = index.maximumConnectionsLayerZero
+				}
+
+				assert.LessOrEqualf(t, len(conns), m, "node %d at level %d with %d conns",
+					i, level, len(conns))
+			}
+		}
+	})
+
 	t.Run("destroy the old index", func(t *testing.T) {
 		// kill the commit loger and index
 		require.Nil(t, original.Shutdown(context.Background()))
diff --git a/modules/text2vec-huggingface/clients/url_test.go b/modules/text2vec-huggingface/clients/url_test.go
deleted file mode 100644
index 55bb02c6fa..0000000000
--- a/modules/text2vec-huggingface/clients/url_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-//                           _       _
-// __      _____  __ ___   ___  __ _| |_ ___
-// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
-//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
-//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
-//
-//  Copyright © 2016 - 2023 Weaviate B.V. All rights reserved.
-//
-//  CONTACT: hello@weaviate.io
-//
-
-package clients
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_huggingFaceUrlBuilder_url(t *testing.T) {
-	tests := []struct {
-		name  string
-		model string
-		want  string
-	}{
-		{
-			name:  "Facebook DPR model",
-			model: "sentence-transformers/facebook-dpr-ctx_encoder-multiset-base",
-			want:  "https://api-inference.huggingface.co/pipeline/feature-extraction/sentence-transformers/facebook-dpr-ctx_encoder-multiset-base",
-		},
-		{
-			name:  "BERT base model (uncased)",
-			model: "bert-base-uncased",
-			want:  "https://api-inference.huggingface.co/pipeline/feature-extraction/bert-base-uncased",
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			assert.Equal(t, tt.want, newHuggingFaceUrlBuilder().url(tt.model))
-		})
-	}
-}
diff --git a/modules/text2vec-huggingface/clients/vectorizer_test.go b/modules/text2vec-huggingface/clients/vectorizer_test.go
index 50d8a9313c..26398c3211 100644
--- a/modules/text2vec-huggingface/clients/vectorizer_test.go
+++ b/modules/text2vec-huggingface/clients/vectorizer_test.go
@@ -35,11 +35,7 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "apiKey",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		expected := &ent.VectorizationResult{
 			Text:       "This is my text",
@@ -52,6 +48,7 @@ func TestClient(t *testing.T) {
 				WaitForModel: false,
 				UseGPU:       false,
 				UseCache:     true,
+				EndpointURL:  server.URL,
 			})
 
 		assert.Nil(t, err)
@@ -64,16 +61,14 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "apiKey",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		ctx, cancel := context.WithDeadline(context.Background(), time.Now())
 		defer cancel()
 
-		_, err := c.Vectorize(ctx, "This is my text", ent.VectorizationConfig{})
+		_, err := c.Vectorize(ctx, "This is my text", ent.VectorizationConfig{
+			EndpointURL: server.URL,
+		})
 
 		require.NotNil(t, err)
 		assert.Contains(t, err.Error(), "context deadline exceeded")
@@ -88,14 +83,12 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "apiKey",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		_, err := c.Vectorize(context.Background(), "This is my text",
-			ent.VectorizationConfig{})
+			ent.VectorizationConfig{
+				EndpointURL: server.URL,
+			})
 
 		require.NotNil(t, err)
 		assert.Equal(t, err.Error(), "connection to HuggingFace failed with status: 500 error: nope, not gonna happen estimated time: 20")
@@ -107,11 +100,7 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		ctxWithValue := context.WithValue(context.Background(),
 			"X-Huggingface-Api-Key", []string{"some-key"})
@@ -127,6 +116,7 @@ func TestClient(t *testing.T) {
 				WaitForModel: true,
 				UseGPU:       false,
 				UseCache:     true,
+				EndpointURL:  server.URL,
 			})
 
 		require.Nil(t, err)
@@ -142,18 +132,15 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		ctxWithValue := context.WithValue(context.Background(),
 			"X-Huggingface-Api-Key", []string{""})
 
 		_, err := c.Vectorize(ctxWithValue, "This is my text",
 			ent.VectorizationConfig{
-				Model: "sentence-transformers/gtr-t5-xxl",
+				Model:       "sentence-transformers/gtr-t5-xxl",
+				EndpointURL: server.URL,
 			})
 
 		require.NotNil(t, err)
@@ -169,14 +156,12 @@ func TestClient(t *testing.T) {
 		c := &vectorizer{
 			apiKey:     "apiKey",
 			httpClient: &http.Client{},
-			urlBuilder: &huggingFaceUrlBuilder{
-				origin:   server.URL,
-				pathMask: "/pipeline/feature-extraction/%s",
-			},
-			logger: nullLogger(),
+			logger:     nullLogger(),
 		}
 		_, err := c.Vectorize(context.Background(), "This is my text",
-			ent.VectorizationConfig{})
+			ent.VectorizationConfig{
+				EndpointURL: server.URL,
+			})
 
 		require.NotNil(t, err)
 		assert.Equal(t, err.Error(), "connection to HuggingFace failed with status: 500 error: with warnings "+
@@ -259,3 +244,40 @@ func nullLogger() logrus.FieldLogger {
 	l, _ := test.NewNullLogger()
 	return l
 }
+
+func Test_getURL(t *testing.T) {
+	v := &vectorizer{}
+
+	tests := []struct {
+		name   string
+		config ent.VectorizationConfig
+		want   string
+	}{
+		{
+			name: "Facebook DPR model",
+			config: ent.VectorizationConfig{
+				Model: "sentence-transformers/facebook-dpr-ctx_encoder-multiset-base",
+			},
+			want: "https://api-inference.huggingface.co/pipeline/feature-extraction/sentence-transformers/facebook-dpr-ctx_encoder-multiset-base",
+		},
+		{
+			name: "BERT base model (uncased)",
+			config: ent.VectorizationConfig{
+				Model: "bert-base-uncased",
+			},
+			want: "https://api-inference.huggingface.co/pipeline/feature-extraction/bert-base-uncased",
+		},
+		{
+			name: "BERT base model (uncased)",
+			config: ent.VectorizationConfig{
+				EndpointURL: "https://self-hosted-instance.com/bert-base-uncased",
+			},
+			want: "https://self-hosted-instance.com/bert-base-uncased",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.want, v.getURL(tt.config))
+		})
+	}
+}
diff --git a/test/acceptance/multi_tenancy/head_tenant_objects_test.go b/test/acceptance/multi_tenancy/head_tenant_objects_test.go
new file mode 100644
index 0000000000..ddacac094a
--- /dev/null
+++ b/test/acceptance/multi_tenancy/head_tenant_objects_test.go
@@ -0,0 +1,94 @@
+//                           _       _
+// __      _____  __ ___   ___  __ _| |_ ___
+// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
+//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
+//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
+//
+//  Copyright © 2016 - 2023 Weaviate B.V. All rights reserved.
+//
+//  CONTACT: hello@weaviate.io
+//
+
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/weaviate/weaviate/entities/models"
+	"github.com/weaviate/weaviate/test/helper"
+)
+
+func TestHeadTenantObjects(t *testing.T) {
+	tenantKey := "tenantName"
+	testClass := models.Class{
+		Class: "MultiTenantClass",
+		MultiTenancyConfig: &models.MultiTenancyConfig{
+			Enabled:   true,
+			TenantKey: tenantKey,
+		},
+		Properties: []*models.Property{
+			{
+				Name:     tenantKey,
+				DataType: []string{"string"},
+			},
+		},
+	}
+	tenantNames := []string{
+		"Tenant1", "Tenant2", "Tenant3",
+	}
+	tenantObjects := []*models.Object{
+		{
+			ID:    "0927a1e0-398e-4e76-91fb-04a7a8f0405c",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey: tenantNames[0],
+			},
+		},
+		{
+			ID:    "831ae1d0-f441-44b1-bb2a-46548048e26f",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey: tenantNames[1],
+			},
+		},
+		{
+			ID:    "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey: tenantNames[2],
+			},
+		},
+	}
+
+	defer func() {
+		helper.DeleteClass(t, testClass.Class)
+	}()
+
+	t.Run("create class with multi-tenancy enabled", func(t *testing.T) {
+		helper.CreateClass(t, &testClass)
+	})
+
+	t.Run("create tenants", func(t *testing.T) {
+		tenants := make([]*models.Tenant, len(tenantNames))
+		for i := range tenants {
+			tenants[i] = &models.Tenant{tenantNames[i]}
+		}
+		helper.CreateTenants(t, testClass.Class, tenants)
+	})
+
+	t.Run("add tenant objects", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			helper.CreateTenantObject(t, obj, tenantNames[i])
+		}
+	})
+
+	t.Run("head tenant objects", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			exists, err := helper.TenantObjectExists(t, obj.Class, obj.ID, tenantNames[i])
+			require.Nil(t, err)
+			assert.True(t, exists)
+		}
+	})
+}
diff --git a/test/acceptance/multi_tenancy/patch_tenant_objects_test.go b/test/acceptance/multi_tenancy/patch_tenant_objects_test.go
new file mode 100644
index 0000000000..c11b510d00
--- /dev/null
+++ b/test/acceptance/multi_tenancy/patch_tenant_objects_test.go
@@ -0,0 +1,130 @@
+//                           _       _
+// __      _____  __ ___   ___  __ _| |_ ___
+// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
+//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
+//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
+//
+//  Copyright © 2016 - 2023 Weaviate B.V. All rights reserved.
+//
+//  CONTACT: hello@weaviate.io
+//
+
+package test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"github.com/weaviate/weaviate/entities/models"
+	"github.com/weaviate/weaviate/test/helper"
+)
+
+func TestPatchTenantObjects(t *testing.T) {
+	tenantKey := "tenantName"
+	mutableProp := "mutableProp"
+	testClass := models.Class{
+		Class: "MultiTenantClass",
+		MultiTenancyConfig: &models.MultiTenancyConfig{
+			Enabled:   true,
+			TenantKey: tenantKey,
+		},
+		Properties: []*models.Property{
+			{
+				Name:     tenantKey,
+				DataType: []string{"string"},
+			}, {
+				Name:     mutableProp,
+				DataType: []string{"string"},
+			},
+		},
+	}
+	tenantNames := []string{
+		"Tenant1", "Tenant2", "Tenant3",
+	}
+	tenantObjects := []*models.Object{
+		{
+			ID:    "0927a1e0-398e-4e76-91fb-04a7a8f0405c",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[0],
+				mutableProp: "obj#0",
+			},
+		},
+		{
+			ID:    "831ae1d0-f441-44b1-bb2a-46548048e26f",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[1],
+				mutableProp: "obj#1",
+			},
+		},
+		{
+			ID:    "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59",
+			Class: testClass.Class,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[2],
+				mutableProp: "obj#2",
+			},
+		},
+	}
+
+	defer func() {
+		helper.DeleteClass(t, testClass.Class)
+	}()
+
+	t.Run("create class with multi-tenancy enabled", func(t *testing.T) {
+		helper.CreateClass(t, &testClass)
+	})
+
+	t.Run("create tenants", func(t *testing.T) {
+		tenants := make([]*models.Tenant, len(tenantNames))
+		for i := range tenants {
+			tenants[i] = &models.Tenant{tenantNames[i]}
+		}
+		helper.CreateTenants(t, testClass.Class, tenants)
+	})
+
+	t.Run("add tenant objects", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			helper.CreateTenantObject(t, obj, tenantNames[i])
+		}
+
+		t.Run("verify tenant object creation", func(t *testing.T) {
+			for i, obj := range tenantObjects {
+				resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantNames[i])
+				require.Nil(t, err)
+				require.Equal(t, obj.ID, resp.ID)
+				require.Equal(t, obj.Class, resp.Class)
+				require.Equal(t, obj.Properties, resp.Properties)
+			}
+		})
+	})
+
+	t.Run("patch tenant objects", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			mut := obj.Properties.(map[string]interface{})[mutableProp]
+			toUpdate := &models.Object{
+				Class: testClass.Class,
+				ID:    obj.ID,
+				Properties: map[string]interface{}{
+					tenantKey:   tenantNames[i],
+					mutableProp: fmt.Sprintf("%s--patched", mut),
+				},
+			}
+			helper.PatchTenantObject(t, toUpdate, tenantNames[i])
+		}
+
+		t.Run("assert tenant object updates", func(t *testing.T) {
+			for i, obj := range tenantObjects {
+				resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantNames[i])
+				require.Nil(t, err)
+				require.Equal(t, obj.ID, resp.ID)
+				require.Equal(t, obj.Class, resp.Class)
+				expectedProps := obj.Properties.(map[string]interface{})
+				expectedProps[mutableProp] = fmt.Sprintf("%s--patched", expectedProps[mutableProp])
+				require.Equal(t, expectedProps, resp.Properties)
+			}
+		})
+	})
+}
diff --git a/test/acceptance/multi_tenancy/tenant_objects_reference_test.go b/test/acceptance/multi_tenancy/tenant_objects_reference_test.go
new file mode 100644
index 0000000000..6aaeade3a3
--- /dev/null
+++ b/test/acceptance/multi_tenancy/tenant_objects_reference_test.go
@@ -0,0 +1,175 @@
+//                           _       _
+// __      _____  __ ___   ___  __ _| |_ ___
+// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
+//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
+//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
+//
+//  Copyright © 2016 - 2023 Weaviate B.V. All rights reserved.
+//
+//  CONTACT: hello@weaviate.io
+//
+
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/weaviate/weaviate/entities/models"
+	"github.com/weaviate/weaviate/test/helper"
+)
+
+func TestTenantObjectsReference(t *testing.T) {
+	className := "MultiTenantClass"
+	tenantKey := "tenantName"
+	mutableProp := "mutableProp"
+	refProp := "refProp"
+	testClass := models.Class{
+		Class: "MultiTenantClass",
+		MultiTenancyConfig: &models.MultiTenancyConfig{
+			Enabled:   true,
+			TenantKey: tenantKey,
+		},
+		Properties: []*models.Property{
+			{
+				Name:     tenantKey,
+				DataType: []string{"string"},
+			},
+			{
+				Name:     mutableProp,
+				DataType: []string{"string"},
+			},
+			{
+				Name:     refProp,
+				DataType: []string{className},
+			},
+		},
+	}
+	tenantNames := []string{
+		"Tenant1", "Tenant2", "Tenant3",
+	}
+	tenantObjects := []*models.Object{
+		{
+			ID:    "0927a1e0-398e-4e76-91fb-04a7a8f0405c",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[0],
+				mutableProp: "obj#0",
+			},
+		},
+		{
+			ID:    "831ae1d0-f441-44b1-bb2a-46548048e26f",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[1],
+				mutableProp: "obj#1",
+			},
+		},
+		{
+			ID:    "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[2],
+				mutableProp: "obj#2",
+			},
+		},
+	}
+	tenantRefs := []*models.Object{
+		{
+			ID:    "169b62a7-ef1c-481d-8fb0-27f11716bde7",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[0],
+				mutableProp: "ref#0",
+			},
+		},
+		{
+			ID:    "4d78424d-f7bd-479b-bd8a-52510e2db0fd",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[1],
+				mutableProp: "ref#1",
+			},
+		},
+		{
+			ID:    "c1db0a06-d5f9-4f77-aa3c-08a44f16e358",
+			Class: className,
+			Properties: map[string]interface{}{
+				tenantKey:   tenantNames[2],
+				mutableProp: "ref#2",
+			},
+		},
+	}
+
+	defer func() {
+		helper.DeleteClass(t, className)
+	}()
+
+	t.Run("create class with multi-tenancy enabled", func(t *testing.T) {
+		helper.CreateClass(t, &testClass)
+	})
+
+	t.Run("create tenants", func(t *testing.T) {
+		tenants := make([]*models.Tenant, len(tenantNames))
+		for i := range tenants {
+			tenants[i] = &models.Tenant{tenantNames[i]}
+		}
+		helper.CreateTenants(t, className, tenants)
+	})
+
+	t.Run("add tenant objects", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			helper.CreateTenantObject(t, obj, tenantNames[i])
+			helper.CreateTenantObject(t, tenantRefs[i], tenantNames[i])
+		}
+
+		t.Run("verify tenant object creation", func(t *testing.T) {
+			for i, obj := range tenantObjects {
+				resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantNames[i])
+				require.Nil(t, err)
+				require.Equal(t, obj.ID, resp.ID)
+				require.Equal(t, obj.Class, resp.Class)
+				require.Equal(t, obj.Properties, resp.Properties)
+			}
+		})
+	})
+
+	t.Run("add tenant object references", func(t *testing.T) {
+		for i, obj := range tenantObjects {
+			ref := &models.SingleRef{Beacon: helper.NewBeacon(className, tenantRefs[i].ID)}
+			helper.AddTenantReference(t, obj, ref, refProp, tenantNames[i])
+		}
+
+		t.Run("assert tenant object references", func(t *testing.T) {
+			for i, obj := range tenantObjects {
+				resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantNames[i])
+				require.Nil(t, err)
+				require.Equal(t, obj.ID, resp.ID)
+				require.Equal(t, obj.Class, resp.Class)
+				refs := resp.Properties.(map[string]interface{})[refProp].([]interface{})
+				require.Len(t, refs, 1)
+				expectedBeacon := helper.NewBeacon(className, tenantRefs[i].ID).String()
+				assert.Equal(t, expectedBeacon, refs[0].(map[string]interface{})["beacon"])
+			}
+		})
+	})
+
+	t.Run("delete tenant object references", func(Z *testing.T) {
+		for i, obj := range tenantObjects {
+			ref := &models.SingleRef{Beacon: helper.NewBeacon(className, tenantRefs[i].ID)}
+			helper.DeleteTenantReference(t, obj, ref, refProp, tenantNames[i])
+		}
+
+		t.Run("assert tenant object references", func(t *testing.T) {
+			for i, obj := range tenantObjects {
+				resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantNames[i])
+				require.Nil(t, err)
+				require.Equal(t, obj.ID, resp.ID)
+				require.Equal(t, obj.Class, resp.Class)
+				refs := resp.Properties.(map[string]interface{})[refProp].([]interface{})
+				require.Len(t, refs, 0)
+			}
+		})
+	})
+}
diff --git a/test/benchmark_bm25/cmd/import.go b/test/benchmark_bm25/cmd/import.go
index a800cb0850..0398a16e30 100644
--- a/test/benchmark_bm25/cmd/import.go
+++ b/test/benchmark_bm25/cmd/import.go
@@ -40,7 +40,7 @@ var importCmd = &cobra.Command{
 
 		ok, err := client.Misc().LiveChecker().Do(context.Background())
 		if err != nil {
-			return fmt.Errorf("weaviate is not ready: %w", err)
+			return fmt.Errorf("weaviate is not ready at %v: %w", Origin, err)
 		}
 
 		if !ok {
diff --git a/test/helper/objects.go b/test/helper/objects.go
index 1e2e64d2a9..b6cf7fd893 100644
--- a/test/helper/objects.go
+++ b/test/helper/objects.go
@@ -15,11 +15,13 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/go-openapi/strfmt"
 	"github.com/stretchr/testify/assert"
 	"github.com/weaviate/weaviate/client/batch"
 	"github.com/weaviate/weaviate/client/objects"
 	"github.com/weaviate/weaviate/client/schema"
 	"github.com/weaviate/weaviate/entities/models"
+	"github.com/weaviate/weaviate/entities/schema/crossref"
 	"github.com/weaviate/weaviate/usecases/replica"
 )
 
@@ -111,6 +113,13 @@ func PatchObject(t *testing.T, object *models.Object) {
 	AssertRequestOk(t, resp, err, nil)
 }
 
+func PatchTenantObject(t *testing.T, object *models.Object, tenantKey string) {
+	params := objects.NewObjectsClassPatchParams().WithClassName(object.Class).
+		WithID(object.ID).WithBody(object).WithTenantKey(&tenantKey)
+	resp, err := Client(t).Objects.ObjectsClassPatch(params, nil)
+	AssertRequestOk(t, resp, err, nil)
+}
+
 func DeleteClass(t *testing.T, class string) {
 	delParams := schema.NewSchemaObjectsDeleteParams().WithClassName(class)
 	delRes, err := Client(t).Schema.SchemaObjectsDelete(delParams, nil)
@@ -143,6 +152,16 @@ func AddReference(t *testing.T, object *models.Object, ref *models.SingleRef, pr
 	AssertRequestOk(t, resp, err, nil)
 }
 
+func AddTenantReference(t *testing.T, object *models.Object, ref *models.SingleRef, prop string,
+	tenantKey string,
+) {
+	params := objects.NewObjectsClassReferencesCreateParams().
+		WithClassName(object.Class).WithID(object.ID).WithBody(ref).
+		WithPropertyName(prop).WithTenantKey(&tenantKey)
+	resp, err := Client(t).Objects.ObjectsClassReferencesCreate(params, nil)
+	AssertRequestOk(t, resp, err, nil)
+}
+
 func DeleteReference(t *testing.T, object *models.Object, ref *models.SingleRef, prop string) {
 	params := objects.NewObjectsClassReferencesDeleteParams().
 		WithClassName(object.Class).WithID(object.ID).WithBody(ref).WithPropertyName(prop)
@@ -150,8 +169,20 @@ func DeleteReference(t *testing.T, object *models.Object, ref *models.SingleRef,
 	AssertRequestOk(t, resp, err, nil)
 }
 
+func DeleteTenantReference(t *testing.T, object *models.Object, ref *models.SingleRef, prop string, tenantKey string) {
+	params := objects.NewObjectsClassReferencesDeleteParams().
+		WithClassName(object.Class).WithID(object.ID).WithBody(ref).
+		WithPropertyName(prop).WithTenantKey(&tenantKey)
+	resp, err := Client(t).Objects.ObjectsClassReferencesDelete(params, nil)
+	AssertRequestOk(t, resp, err, nil)
+}
+
 func CreateTenants(t *testing.T, class string, tenants []*models.Tenant) {
 	params := schema.NewTenantsCreateParams().WithClassName(class).WithBody(tenants)
 	resp, err := Client(t).Schema.TenantsCreate(params, nil)
 	AssertRequestOk(t, resp, err, nil)
 }
+
+func NewBeacon(className string, id strfmt.UUID) strfmt.URI {
+	return crossref.New("localhost", className, id).SingleRef().Beacon
+}
diff --git a/test/helper/objects_assertions.go b/test/helper/objects_assertions.go
index 4446c29789..4fe8c55672 100644
--- a/test/helper/objects_assertions.go
+++ b/test/helper/objects_assertions.go
@@ -149,6 +149,16 @@ func ObjectExistsCL(t *testing.T, class string, id strfmt.UUID, cl replica.Consi
 	return resp.IsCode(http.StatusNoContent), nil
 }
 
+func TenantObjectExists(t *testing.T, class string, id strfmt.UUID, tenantKey string) (bool, error) {
+	req := objects.NewObjectsClassHeadParams().
+		WithClassName(class).WithID(id).WithTenantKey(&tenantKey)
+	resp, err := Client(t).Objects.ObjectsClassHead(req, nil)
+	if err != nil {
+		return false, err
+	}
+	return resp.IsCode(http.StatusNoContent), nil
+}
+
 func GetObjectFromNode(t *testing.T, class string, uuid strfmt.UUID, nodename string) (*models.Object, error) {
 	req := objects.NewObjectsClassGetParams().WithID(uuid)
 	if class != "" {
diff --git a/usecases/objects/authorization_test.go b/usecases/objects/authorization_test.go
index 8ec4e46f74..480bf1815d 100644
--- a/usecases/objects/authorization_test.go
+++ b/usecases/objects/authorization_test.go
@@ -86,7 +86,7 @@ func Test_Kinds_Authorization(t *testing.T) {
 			methodName: "MergeObject",
 			additionalArgs: []interface{}{
 				&models.Object{Class: "class", ID: "foo"},
-				(*additional.ReplicationProperties)(nil),
+				(*additional.ReplicationProperties)(nil), "",
 			},
 			expectedVerb:     "update",
 			expectedResource: "objects/class/foo",
diff --git a/usecases/objects/fakes_for_test.go b/usecases/objects/fakes_for_test.go
index d73a0f8014..2255f3fd75 100644
--- a/usecases/objects/fakes_for_test.go
+++ b/usecases/objects/fakes_for_test.go
@@ -206,9 +206,7 @@ func (f *fakeVectorRepo) BatchDeleteObjects(ctx context.Context, params BatchDel
 	return args.Get(0).(BatchDeleteResult), args.Error(1)
 }
 
-func (f *fakeVectorRepo) Merge(ctx context.Context, merge MergeDocument,
-	repl *additional.ReplicationProperties,
-) error {
+func (f *fakeVectorRepo) Merge(ctx context.Context, merge MergeDocument, repl *additional.ReplicationProperties, tenantKey string) error {
 	args := f.Called(merge)
 	return args.Error(0)
 }
@@ -220,9 +218,8 @@ func (f *fakeVectorRepo) DeleteObject(ctx context.Context, className string,
 	return args.Error(0)
 }
 
-func (f *fakeVectorRepo) AddReference(ctx context.Context, class string,
-	source strfmt.UUID, prop string, ref *models.SingleRef,
-	repl *additional.ReplicationProperties,
+func (f *fakeVectorRepo) AddReference(ctx context.Context, class string, source strfmt.UUID,
+	prop string, ref *models.SingleRef, repl *additional.ReplicationProperties, tenantKey string,
 ) error {
 	args := f.Called(class, source, prop, ref)
 	return args.Error(0)
diff --git a/usecases/objects/head_test.go b/usecases/objects/head_test.go
index 6b568a3684..f78f0d14e9 100644
--- a/usecases/objects/head_test.go
+++ b/usecases/objects/head_test.go
@@ -78,7 +78,7 @@ func Test_HeadObject(t *testing.T) {
 		if tc.authErr == nil && tc.lockErr == nil {
 			m.repo.On("Exists", tc.class, id).Return(tc.mockedOk, tc.mockedErr).Once()
 		}
-		ok, err := m.Manager.HeadObject(context.Background(), nil, tc.class, id, nil)
+		ok, err := m.Manager.HeadObject(context.Background(), nil, tc.class, id, nil, "")
 		code := 0
 		if err != nil {
 			code = err.Code
diff --git a/usecases/objects/merge_test.go b/usecases/objects/merge_test.go
index dd0def8465..ba6f4e4499 100644
--- a/usecases/objects/merge_test.go
+++ b/usecases/objects/merge_test.go
@@ -465,7 +465,7 @@ func Test_MergeObject(t *testing.T) {
 			// called during validation of cross-refs only.
 			m.repo.On("Exists", mock.Anything, mock.Anything).Maybe().Return(true, tc.errExists)
 
-			err := m.MergeObject(context.Background(), nil, tc.updated, nil)
+			err := m.MergeObject(context.Background(), nil, tc.updated, nil, "")
 			code := 0
 			if err != nil {
 				code = err.Code
diff --git a/usecases/objects/references_test.go b/usecases/objects/references_test.go
index f05a783696..d63951e6b6 100644
--- a/usecases/objects/references_test.go
+++ b/usecases/objects/references_test.go
@@ -54,7 +54,7 @@ func Test_ReferencesAddDeprecated(t *testing.T) {
 		m.repo.On("AddReference", cls, mock.Anything, expectedRefProperty, expectedRef).Return(nil)
 		m.modulesProvider.On("UsingRef2Vec", mock.Anything).Return(false)
 
-		err := m.AddObjectReference(context.Background(), nil, &req, nil)
+		err := m.AddObjectReference(context.Background(), nil, &req, nil, "")
 		require.Nil(t, err)
 		m.repo.AssertExpectations(t)
 	})
@@ -68,7 +68,7 @@ func Test_ReferencesAddDeprecated(t *testing.T) {
 		}
 		m := newFakeGetManager(zooAnimalSchemaForTest())
 		m.repo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
-		err := m.AddObjectReference(context.Background(), nil, &req, nil)
+		err := m.AddObjectReference(context.Background(), nil, &req, nil, "")
 		require.NotNil(t, err)
 		if !err.BadRequest() {
 			t.Errorf("error expected: not found error got: %v", err)
@@ -84,7 +84,7 @@ func Test_ReferencesAddDeprecated(t *testing.T) {
 		}
 		m := newFakeGetManager(zooAnimalSchemaForTest())
 		m.repo.On("ObjectByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("any"))
-		err := m.AddObjectReference(context.Background(), nil, &req, nil)
+		err := m.AddObjectReference(context.Background(), nil, &req, nil, "")
 		require.NotNil(t, err)
 		if err.Code != StatusInternalServerError {
 			t.Errorf("error expected: internal error, got: %v", err)
@@ -219,7 +219,7 @@ func Test_ReferenceAdd(t *testing.T) {
 				m.repo.On("AddReference", cls, id, prop, &expectedRef).Return(tc.ErrAddRef).Once()
 			}
 
-			err := m.AddObjectReference(context.Background(), nil, &tc.Req, nil)
+			err := m.AddObjectReference(context.Background(), nil, &tc.Req, nil, "")
 			if tc.WantCode != 0 {
 				code := 0
 				if err != nil {
@@ -367,7 +367,7 @@ func Test_ReferenceUpdate(t *testing.T) {
 				m.repo.On("PutObject", mock.Anything, mock.Anything).Return(tc.ErrPutRefs).Once()
 			}
 
-			err := m.UpdateObjectReferences(context.Background(), nil, &tc.Req, nil)
+			err := m.UpdateObjectReferences(context.Background(), nil, &tc.Req, nil, "")
 			if tc.WantCode != 0 {
 				code := 0
 				if err != nil {
@@ -553,7 +553,7 @@ func Test_ReferenceDelete(t *testing.T) {
 				m.repo.On("PutObject", mock.Anything, mock.Anything).Return(tc.ErrPutRefs).Once()
 			}
 
-			err := m.DeleteObjectReference(context.Background(), nil, &tc.Req, nil)
+			err := m.DeleteObjectReference(context.Background(), nil, &tc.Req, nil, "")
 			if tc.WantCode != 0 {
 				code := 0
 				if err != nil {
@@ -619,7 +619,7 @@ func Test_ReferenceAdd_Ref2Vec(t *testing.T) {
 		Return(ref1.Vector, nil)
 	m.repo.On("PutObject", mock.Anything, ref1.Vector).Return(nil)
 
-	err := m.Manager.AddObjectReference(ctx, nil, &req, nil)
+	err := m.Manager.AddObjectReference(ctx, nil, &req, nil, "")
 	assert.Nil(t, err)
 }
 
@@ -658,7 +658,7 @@ func Test_ReferenceDelete_Ref2Vec(t *testing.T) {
 	m.repo.On("PutObject", parent.Object(), []float32(nil)).Return(nil)
 	m.modulesProvider.On("UsingRef2Vec", mock.Anything).Return(true)
 
-	err := m.Manager.DeleteObjectReference(ctx, nil, &req, nil)
+	err := m.Manager.DeleteObjectReference(ctx, nil, &req, nil, "")
 	assert.Nil(t, err)
 }
 
diff --git a/usecases/objects/validation/phone_numbers_test.go b/usecases/objects/validation/phone_numbers_test.go
index dc612a9cca..01f484df57 100644
--- a/usecases/objects/validation/phone_numbers_test.go
+++ b/usecases/objects/validation/phone_numbers_test.go
@@ -159,7 +159,7 @@ func TestPropertyOfTypePhoneNumberValidation(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			config := &config.WeaviateConfig{}
-			validator := New(fakeExists, config, nil)
+			validator := New(fakeExists, config, nil, "")
 
 			obj := &models.Object{
 				Class: "Person",