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

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package db
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"io"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  
    22  	"github.com/go-openapi/strfmt"
    23  	"github.com/weaviate/weaviate/entities/additional"
    24  	"github.com/weaviate/weaviate/entities/multi"
    25  	"github.com/weaviate/weaviate/entities/schema"
    26  	"github.com/weaviate/weaviate/entities/storobj"
    27  	"github.com/weaviate/weaviate/usecases/objects"
    28  	"github.com/weaviate/weaviate/usecases/replica"
    29  )
    30  
    31  type Replicator interface {
    32  	ReplicateObject(ctx context.Context, shardName, requestID string,
    33  		object *storobj.Object) replica.SimpleResponse
    34  	ReplicateObjects(ctx context.Context, shardName, requestID string,
    35  		objects []*storobj.Object) replica.SimpleResponse
    36  	ReplicateUpdate(ctx context.Context, shard, requestID string,
    37  		doc *objects.MergeDocument) replica.SimpleResponse
    38  	ReplicateDeletion(ctx context.Context, shardName, requestID string,
    39  		uuid strfmt.UUID) replica.SimpleResponse
    40  	ReplicateDeletions(ctx context.Context, shardName, requestID string,
    41  		uuids []strfmt.UUID, dryRun bool) replica.SimpleResponse
    42  	ReplicateReferences(ctx context.Context, shard, requestID string,
    43  		refs []objects.BatchReference) replica.SimpleResponse
    44  	CommitReplication(shard,
    45  		requestID string) interface{}
    46  	AbortReplication(shardName,
    47  		requestID string) interface{}
    48  }
    49  
    50  func (db *DB) ReplicateObject(ctx context.Context, class,
    51  	shard, requestID string, object *storobj.Object,
    52  ) replica.SimpleResponse {
    53  	index, pr := db.replicatedIndex(class)
    54  	if pr != nil {
    55  		return *pr
    56  	}
    57  
    58  	return index.ReplicateObject(ctx, shard, requestID, object)
    59  }
    60  
    61  func (db *DB) ReplicateObjects(ctx context.Context, class,
    62  	shard, requestID string, objects []*storobj.Object,
    63  ) replica.SimpleResponse {
    64  	index, pr := db.replicatedIndex(class)
    65  	if pr != nil {
    66  		return *pr
    67  	}
    68  
    69  	return index.ReplicateObjects(ctx, shard, requestID, objects)
    70  }
    71  
    72  func (db *DB) ReplicateUpdate(ctx context.Context, class,
    73  	shard, requestID string, mergeDoc *objects.MergeDocument,
    74  ) replica.SimpleResponse {
    75  	index, pr := db.replicatedIndex(class)
    76  	if pr != nil {
    77  		return *pr
    78  	}
    79  
    80  	return index.ReplicateUpdate(ctx, shard, requestID, mergeDoc)
    81  }
    82  
    83  func (db *DB) ReplicateDeletion(ctx context.Context, class,
    84  	shard, requestID string, uuid strfmt.UUID,
    85  ) replica.SimpleResponse {
    86  	index, pr := db.replicatedIndex(class)
    87  	if pr != nil {
    88  		return *pr
    89  	}
    90  
    91  	return index.ReplicateDeletion(ctx, shard, requestID, uuid)
    92  }
    93  
    94  func (db *DB) ReplicateDeletions(ctx context.Context, class,
    95  	shard, requestID string, uuids []strfmt.UUID, dryRun bool,
    96  ) replica.SimpleResponse {
    97  	index, pr := db.replicatedIndex(class)
    98  	if pr != nil {
    99  		return *pr
   100  	}
   101  
   102  	return index.ReplicateDeletions(ctx, shard, requestID, uuids, dryRun)
   103  }
   104  
   105  func (db *DB) ReplicateReferences(ctx context.Context, class,
   106  	shard, requestID string, refs []objects.BatchReference,
   107  ) replica.SimpleResponse {
   108  	index, pr := db.replicatedIndex(class)
   109  	if pr != nil {
   110  		return *pr
   111  	}
   112  
   113  	return index.ReplicateReferences(ctx, shard, requestID, refs)
   114  }
   115  
   116  func (db *DB) CommitReplication(class,
   117  	shard, requestID string,
   118  ) interface{} {
   119  	index, pr := db.replicatedIndex(class)
   120  	if pr != nil {
   121  		return nil
   122  	}
   123  
   124  	return index.CommitReplication(shard, requestID)
   125  }
   126  
   127  func (db *DB) AbortReplication(class,
   128  	shard, requestID string,
   129  ) interface{} {
   130  	index, pr := db.replicatedIndex(class)
   131  	if pr != nil {
   132  		return *pr
   133  	}
   134  
   135  	return index.AbortReplication(shard, requestID)
   136  }
   137  
   138  func (db *DB) replicatedIndex(name string) (idx *Index, resp *replica.SimpleResponse) {
   139  	if !db.StartupComplete() {
   140  		return nil, &replica.SimpleResponse{Errors: []replica.Error{
   141  			*replica.NewError(replica.StatusNotReady, name),
   142  		}}
   143  	}
   144  
   145  	if idx = db.GetIndex(schema.ClassName(name)); idx == nil {
   146  		return nil, &replica.SimpleResponse{Errors: []replica.Error{
   147  			*replica.NewError(replica.StatusClassNotFound, name),
   148  		}}
   149  	}
   150  	return
   151  }
   152  
   153  func (i *Index) writableShard(name string) (ShardLike, *replica.SimpleResponse) {
   154  	localShard := i.localShard(name)
   155  	if localShard == nil {
   156  		return nil, &replica.SimpleResponse{Errors: []replica.Error{
   157  			{Code: replica.StatusShardNotFound, Msg: name},
   158  		}}
   159  	}
   160  	if localShard.isReadOnly() {
   161  		return nil, &replica.SimpleResponse{Errors: []replica.Error{{
   162  			Code: replica.StatusReadOnly, Msg: name,
   163  		}}}
   164  	}
   165  	return localShard, nil
   166  }
   167  
   168  func (i *Index) ReplicateObject(ctx context.Context, shard, requestID string, object *storobj.Object) replica.SimpleResponse {
   169  	localShard, pr := i.writableShard(shard)
   170  	if pr != nil {
   171  		return *pr
   172  	}
   173  	return localShard.preparePutObject(ctx, requestID, object)
   174  }
   175  
   176  func (i *Index) ReplicateUpdate(ctx context.Context, shard, requestID string, doc *objects.MergeDocument) replica.SimpleResponse {
   177  	localShard, pr := i.writableShard(shard)
   178  	if pr != nil {
   179  		return *pr
   180  	}
   181  	return localShard.prepareMergeObject(ctx, requestID, doc)
   182  }
   183  
   184  func (i *Index) ReplicateDeletion(ctx context.Context, shard, requestID string, uuid strfmt.UUID) replica.SimpleResponse {
   185  	localShard, pr := i.writableShard(shard)
   186  	if pr != nil {
   187  		return *pr
   188  	}
   189  	return localShard.prepareDeleteObject(ctx, requestID, uuid)
   190  }
   191  
   192  func (i *Index) ReplicateObjects(ctx context.Context, shard, requestID string, objects []*storobj.Object) replica.SimpleResponse {
   193  	localShard, pr := i.writableShard(shard)
   194  	if pr != nil {
   195  		return *pr
   196  	}
   197  	return localShard.preparePutObjects(ctx, requestID, objects)
   198  }
   199  
   200  func (i *Index) ReplicateDeletions(ctx context.Context, shard, requestID string, uuids []strfmt.UUID, dryRun bool) replica.SimpleResponse {
   201  	localShard, pr := i.writableShard(shard)
   202  	if pr != nil {
   203  		return *pr
   204  	}
   205  	return localShard.prepareDeleteObjects(ctx, requestID, uuids, dryRun)
   206  }
   207  
   208  func (i *Index) ReplicateReferences(ctx context.Context, shard, requestID string, refs []objects.BatchReference) replica.SimpleResponse {
   209  	localShard, pr := i.writableShard(shard)
   210  	if pr != nil {
   211  		return *pr
   212  	}
   213  	return localShard.prepareAddReferences(ctx, requestID, refs)
   214  }
   215  
   216  func (i *Index) CommitReplication(shard, requestID string) interface{} {
   217  	localShard := i.localShard(shard)
   218  	if localShard == nil {
   219  		return nil
   220  	}
   221  	return localShard.commitReplication(context.Background(), requestID, &i.backupMutex)
   222  }
   223  
   224  func (i *Index) AbortReplication(shard, requestID string) interface{} {
   225  	localShard := i.localShard(shard)
   226  	if localShard == nil {
   227  		return replica.SimpleResponse{Errors: []replica.Error{
   228  			{Code: replica.StatusShardNotFound, Msg: shard},
   229  		}}
   230  	}
   231  	return localShard.abortReplication(context.Background(), requestID)
   232  }
   233  
   234  func (i *Index) IncomingFilePutter(ctx context.Context, shardName,
   235  	filePath string,
   236  ) (io.WriteCloser, error) {
   237  	localShard := i.localShard(shardName)
   238  	if localShard == nil {
   239  		return nil, fmt.Errorf("shard %q does not exist locally", shardName)
   240  	}
   241  
   242  	return localShard.filePutter(ctx, filePath)
   243  }
   244  
   245  func (i *Index) IncomingCreateShard(ctx context.Context, className string, shardName string) error {
   246  	sch := i.getSchema.GetSchemaSkipAuth()
   247  	class := sch.GetClass(schema.ClassName(className))
   248  	if err := i.addNewShard(ctx, class, shardName); err != nil {
   249  		return fmt.Errorf("incoming create shard: %w", err)
   250  	}
   251  	return nil
   252  }
   253  
   254  func (i *Index) IncomingReinitShard(ctx context.Context,
   255  	shardName string,
   256  ) error {
   257  	shard := i.localShard(shardName)
   258  	if shard == nil {
   259  		return fmt.Errorf("shard %q does not exist locally", shardName)
   260  	}
   261  
   262  	return shard.reinit(ctx)
   263  }
   264  
   265  func (s *Shard) filePutter(ctx context.Context,
   266  	filePath string,
   267  ) (io.WriteCloser, error) {
   268  	// TODO: validate file prefix to rule out that we're accidentally writing
   269  	// into another shard
   270  	finalPath := filepath.Join(s.Index().Config.RootPath, filePath)
   271  	dir := path.Dir(finalPath)
   272  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   273  		return nil, fmt.Errorf("create parent folder for %s: %w", filePath, err)
   274  	}
   275  
   276  	f, err := os.Create(finalPath)
   277  	if err != nil {
   278  		return nil, fmt.Errorf("open file %q for writing: %w", filePath, err)
   279  	}
   280  
   281  	return f, nil
   282  }
   283  
   284  func (s *Shard) reinit(ctx context.Context) error {
   285  	if err := s.Shutdown(ctx); err != nil {
   286  		return fmt.Errorf("shutdown shard: %w", err)
   287  	}
   288  
   289  	if err := s.initNonVector(ctx, nil); err != nil {
   290  		return fmt.Errorf("reinit non-vector: %w", err)
   291  	}
   292  
   293  	if s.hasTargetVectors() {
   294  		if err := s.initTargetVectors(ctx); err != nil {
   295  			return fmt.Errorf("reinit vector: %w", err)
   296  		}
   297  	} else {
   298  		if err := s.initLegacyVector(ctx); err != nil {
   299  			return fmt.Errorf("reinit vector: %w", err)
   300  		}
   301  	}
   302  
   303  	s.initCycleCallbacks()
   304  	s.initDimensionTracking()
   305  
   306  	return nil
   307  }
   308  
   309  func (db *DB) OverwriteObjects(ctx context.Context,
   310  	class, shardName string, vobjects []*objects.VObject,
   311  ) ([]replica.RepairResponse, error) {
   312  	index := db.GetIndex(schema.ClassName(class))
   313  	return index.overwriteObjects(ctx, shardName, vobjects)
   314  }
   315  
   316  // overwrite objects if their state didn't change in the meantime
   317  // It returns nil if all object have been successfully overwritten
   318  // and otherwise a list of failed operations.
   319  func (i *Index) overwriteObjects(ctx context.Context,
   320  	shard string, updates []*objects.VObject,
   321  ) ([]replica.RepairResponse, error) {
   322  	result := make([]replica.RepairResponse, 0, len(updates)/2)
   323  	s := i.localShard(shard)
   324  	if s == nil {
   325  		return nil, fmt.Errorf("shard %q not found locally", shard)
   326  	}
   327  	for i, u := range updates {
   328  		// Just in case but this should not happen
   329  		data := u.LatestObject
   330  		if data == nil || data.ID == "" {
   331  			msg := fmt.Sprintf("received nil object or empty uuid at position %d", i)
   332  			result = append(result, replica.RepairResponse{Err: msg})
   333  			continue
   334  		}
   335  		// valid update
   336  		found, err := s.ObjectByID(ctx, data.ID, nil, additional.Properties{})
   337  		var curUpdateTime int64 // 0 means object doesn't exist on this node
   338  		if found != nil {
   339  			curUpdateTime = found.LastUpdateTimeUnix()
   340  		}
   341  		r := replica.RepairResponse{ID: data.ID.String(), UpdateTime: curUpdateTime}
   342  		switch {
   343  		case err != nil:
   344  			r.Err = "not found: " + err.Error()
   345  		case curUpdateTime == u.StaleUpdateTime:
   346  			// the stored object is not the most recent version. in
   347  			// this case, we overwrite it with the more recent one.
   348  			err := s.PutObject(ctx, storobj.FromObject(data, u.Vector, u.Vectors))
   349  			if err != nil {
   350  				r.Err = fmt.Sprintf("overwrite stale object: %v", err)
   351  			}
   352  		case curUpdateTime != data.LastUpdateTimeUnix:
   353  			// object changed and its state differs from recent known state
   354  			r.Err = "conflict"
   355  		}
   356  
   357  		if r.Err != "" { // include only unsuccessful responses
   358  			result = append(result, r)
   359  		}
   360  	}
   361  	if len(result) == 0 {
   362  		return nil, nil
   363  	}
   364  	return result, nil
   365  }
   366  
   367  func (i *Index) IncomingOverwriteObjects(ctx context.Context,
   368  	shardName string, vobjects []*objects.VObject,
   369  ) ([]replica.RepairResponse, error) {
   370  	return i.overwriteObjects(ctx, shardName, vobjects)
   371  }
   372  
   373  func (db *DB) DigestObjects(ctx context.Context,
   374  	class, shardName string, ids []strfmt.UUID,
   375  ) (result []replica.RepairResponse, err error) {
   376  	index := db.GetIndex(schema.ClassName(class))
   377  	return index.digestObjects(ctx, shardName, ids)
   378  }
   379  
   380  func (i *Index) digestObjects(ctx context.Context,
   381  	shardName string, ids []strfmt.UUID,
   382  ) (result []replica.RepairResponse, err error) {
   383  	result = make([]replica.RepairResponse, len(ids))
   384  	s := i.localShard(shardName)
   385  	if s == nil {
   386  		return nil, fmt.Errorf("shard %q not found locally", shardName)
   387  	}
   388  
   389  	multiIDs := make([]multi.Identifier, len(ids))
   390  	for j := range multiIDs {
   391  		multiIDs[j] = multi.Identifier{ID: ids[j].String()}
   392  	}
   393  
   394  	objs, err := s.MultiObjectByID(ctx, multiIDs)
   395  	if err != nil {
   396  		return nil, fmt.Errorf("shard objects digest: %w", err)
   397  	}
   398  
   399  	for j := range objs {
   400  		if objs[j] == nil {
   401  			deleted, err := s.WasDeleted(ctx, ids[j])
   402  			if err != nil {
   403  				return nil, err
   404  			}
   405  			result[j] = replica.RepairResponse{
   406  				ID:      ids[j].String(),
   407  				Deleted: deleted,
   408  				// TODO: use version when supported
   409  				Version: 0,
   410  			}
   411  		} else {
   412  			result[j] = replica.RepairResponse{
   413  				ID:         objs[j].ID().String(),
   414  				UpdateTime: objs[j].LastUpdateTimeUnix(),
   415  				// TODO: use version when supported
   416  				Version: 0,
   417  			}
   418  		}
   419  	}
   420  
   421  	return
   422  }
   423  
   424  func (i *Index) IncomingDigestObjects(ctx context.Context,
   425  	shardName string, ids []strfmt.UUID,
   426  ) (result []replica.RepairResponse, err error) {
   427  	return i.digestObjects(ctx, shardName, ids)
   428  }
   429  
   430  func (db *DB) FetchObject(ctx context.Context,
   431  	class, shardName string, id strfmt.UUID,
   432  ) (objects.Replica, error) {
   433  	index := db.GetIndex(schema.ClassName(class))
   434  	return index.readRepairGetObject(ctx, shardName, id)
   435  }
   436  
   437  func (i *Index) readRepairGetObject(ctx context.Context,
   438  	shardName string, id strfmt.UUID,
   439  ) (objects.Replica, error) {
   440  	shard := i.localShard(shardName)
   441  	if shard == nil {
   442  		return objects.Replica{}, fmt.Errorf("shard %q does not exist locally", shardName)
   443  	}
   444  
   445  	obj, err := shard.ObjectByID(ctx, id, nil, additional.Properties{})
   446  	if err != nil {
   447  		return objects.Replica{}, fmt.Errorf("shard %q read repair get object: %w", shard.ID(), err)
   448  	}
   449  
   450  	if obj == nil {
   451  		deleted, err := shard.WasDeleted(ctx, id)
   452  		if err != nil {
   453  			return objects.Replica{}, err
   454  		}
   455  		return objects.Replica{
   456  			ID:      id,
   457  			Deleted: deleted,
   458  		}, nil
   459  	}
   460  
   461  	return objects.Replica{
   462  		Object: obj,
   463  		ID:     obj.ID(),
   464  	}, nil
   465  }
   466  
   467  func (db *DB) FetchObjects(ctx context.Context,
   468  	class, shardName string, ids []strfmt.UUID,
   469  ) ([]objects.Replica, error) {
   470  	index := db.GetIndex(schema.ClassName(class))
   471  	return index.fetchObjects(ctx, shardName, ids)
   472  }
   473  
   474  func (i *Index) fetchObjects(ctx context.Context,
   475  	shardName string, ids []strfmt.UUID,
   476  ) ([]objects.Replica, error) {
   477  	shard := i.localShard(shardName)
   478  	if shard == nil {
   479  		return nil, fmt.Errorf("shard %q does not exist locally", shardName)
   480  	}
   481  
   482  	objs, err := shard.MultiObjectByID(ctx, wrapIDsInMulti(ids))
   483  	if err != nil {
   484  		return nil, fmt.Errorf("shard %q replication multi get objects: %w", shard.ID(), err)
   485  	}
   486  
   487  	resp := make([]objects.Replica, len(ids))
   488  
   489  	for j, obj := range objs {
   490  		if obj == nil {
   491  			deleted, err := shard.WasDeleted(ctx, ids[j])
   492  			if err != nil {
   493  				return nil, err
   494  			}
   495  			resp[j] = objects.Replica{
   496  				ID:      ids[j],
   497  				Deleted: deleted,
   498  			}
   499  		} else {
   500  			resp[j] = objects.Replica{
   501  				Object: obj,
   502  				ID:     obj.ID(),
   503  			}
   504  		}
   505  	}
   506  
   507  	return resp, nil
   508  }