github.com/weaviate/weaviate@v1.24.6/usecases/replica/replicator.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 replica
    13  
    14  import (
    15  	"context"
    16  	"fmt"
    17  	"sync/atomic"
    18  	"time"
    19  
    20  	"github.com/go-openapi/strfmt"
    21  	"github.com/sirupsen/logrus"
    22  	"github.com/weaviate/weaviate/entities/storobj"
    23  	"github.com/weaviate/weaviate/usecases/objects"
    24  )
    25  
    26  // opID operation encode as and int
    27  type opID int
    28  
    29  const (
    30  	opPutObject opID = iota + 1
    31  	opMergeObject
    32  	opDeleteObject
    33  
    34  	opPutObjects = iota + 97
    35  	opAddReferences
    36  	opDeleteObjects
    37  )
    38  
    39  type (
    40  	shardingState interface {
    41  		NodeName() string
    42  		ResolveParentNodes(class, shardName string) (map[string]string, error)
    43  	}
    44  
    45  	nodeResolver interface {
    46  		NodeHostname(nodeName string) (string, bool)
    47  	}
    48  
    49  	// _Result represents a valid value or an error ( _ prevent make it public).
    50  	_Result[T any] struct {
    51  		Value T
    52  		Err   error
    53  	}
    54  )
    55  
    56  type Replicator struct {
    57  	class          string
    58  	stateGetter    shardingState
    59  	client         Client
    60  	resolver       *resolver
    61  	log            logrus.FieldLogger
    62  	requestCounter atomic.Uint64
    63  	stream         replicatorStream
    64  	*Finder
    65  }
    66  
    67  func NewReplicator(className string,
    68  	stateGetter shardingState,
    69  	nodeResolver nodeResolver,
    70  	client Client,
    71  	l logrus.FieldLogger,
    72  ) *Replicator {
    73  	resolver := &resolver{
    74  		Schema:       stateGetter,
    75  		nodeResolver: nodeResolver,
    76  		Class:        className,
    77  		NodeName:     stateGetter.NodeName(),
    78  	}
    79  	return &Replicator{
    80  		class:       className,
    81  		stateGetter: stateGetter,
    82  		client:      client,
    83  		resolver:    resolver,
    84  		log:         l,
    85  		Finder:      NewFinder(className, resolver, client, l),
    86  	}
    87  }
    88  
    89  func (r *Replicator) PutObject(ctx context.Context,
    90  	shard string,
    91  	obj *storobj.Object,
    92  	l ConsistencyLevel,
    93  ) error {
    94  	coord := newCoordinator[SimpleResponse](r, shard, r.requestID(opPutObject), r.log)
    95  	isReady := func(ctx context.Context, host, requestID string) error {
    96  		resp, err := r.client.PutObject(ctx, host, r.class, shard, requestID, obj)
    97  		if err == nil {
    98  			err = resp.FirstError()
    99  		}
   100  		if err != nil {
   101  			return fmt.Errorf("%q: %w", host, err)
   102  		}
   103  		return nil
   104  	}
   105  	replyCh, level, err := coord.Push(ctx, l, isReady, r.simpleCommit(shard))
   106  	if err != nil {
   107  		r.log.WithField("op", "push.one").WithField("class", r.class).
   108  			WithField("shard", shard).Error(err)
   109  		return fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   110  
   111  	}
   112  	err = r.stream.readErrors(1, level, replyCh)[0]
   113  	if err != nil {
   114  		r.log.WithField("op", "put").WithField("class", r.class).
   115  			WithField("shard", shard).WithField("uuid", obj.ID()).Error(err)
   116  	}
   117  	return err
   118  }
   119  
   120  func (r *Replicator) MergeObject(ctx context.Context,
   121  	shard string,
   122  	doc *objects.MergeDocument,
   123  	l ConsistencyLevel,
   124  ) error {
   125  	coord := newCoordinator[SimpleResponse](r, shard, r.requestID(opMergeObject), r.log)
   126  	op := func(ctx context.Context, host, requestID string) error {
   127  		resp, err := r.client.MergeObject(ctx, host, r.class, shard, requestID, doc)
   128  		if err == nil {
   129  			err = resp.FirstError()
   130  		}
   131  		if err != nil {
   132  			return fmt.Errorf("%q: %w", host, err)
   133  		}
   134  		return nil
   135  	}
   136  	replyCh, level, err := coord.Push(ctx, l, op, r.simpleCommit(shard))
   137  	if err != nil {
   138  		r.log.WithField("op", "push.merge").WithField("class", r.class).
   139  			WithField("shard", shard).Error(err)
   140  		return fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   141  	}
   142  	err = r.stream.readErrors(1, level, replyCh)[0]
   143  	if err != nil {
   144  		r.log.WithField("op", "put").WithField("class", r.class).
   145  			WithField("shard", shard).WithField("uuid", doc.ID).Error(err)
   146  	}
   147  	return err
   148  }
   149  
   150  func (r *Replicator) DeleteObject(ctx context.Context,
   151  	shard string,
   152  	id strfmt.UUID,
   153  	l ConsistencyLevel,
   154  ) error {
   155  	coord := newCoordinator[SimpleResponse](r, shard, r.requestID(opDeleteObject), r.log)
   156  	op := func(ctx context.Context, host, requestID string) error {
   157  		resp, err := r.client.DeleteObject(ctx, host, r.class, shard, requestID, id)
   158  		if err == nil {
   159  			err = resp.FirstError()
   160  		}
   161  		if err != nil {
   162  			return fmt.Errorf("%q: %w", host, err)
   163  		}
   164  		return nil
   165  	}
   166  	replyCh, level, err := coord.Push(ctx, l, op, r.simpleCommit(shard))
   167  	if err != nil {
   168  		r.log.WithField("op", "push.delete").WithField("class", r.class).
   169  			WithField("shard", shard).Error(err)
   170  		return fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   171  	}
   172  	err = r.stream.readErrors(1, level, replyCh)[0]
   173  	if err != nil {
   174  		r.log.WithField("op", "put").WithField("class", r.class).
   175  			WithField("shard", shard).WithField("uuid", id).Error(err)
   176  	}
   177  	return err
   178  }
   179  
   180  func (r *Replicator) PutObjects(ctx context.Context,
   181  	shard string,
   182  	objs []*storobj.Object,
   183  	l ConsistencyLevel,
   184  ) []error {
   185  	coord := newCoordinator[SimpleResponse](r, shard, r.requestID(opPutObjects), r.log)
   186  	op := func(ctx context.Context, host, requestID string) error {
   187  		resp, err := r.client.PutObjects(ctx, host, r.class, shard, requestID, objs)
   188  		if err == nil {
   189  			err = resp.FirstError()
   190  		}
   191  		if err != nil {
   192  			return fmt.Errorf("%q: %w", host, err)
   193  		}
   194  		return nil
   195  	}
   196  
   197  	replyCh, level, err := coord.Push(ctx, l, op, r.simpleCommit(shard))
   198  	if err != nil {
   199  		r.log.WithField("op", "push.many").WithField("class", r.class).
   200  			WithField("shard", shard).Error(err)
   201  		err = fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   202  		errs := make([]error, len(objs))
   203  		for i := 0; i < len(objs); i++ {
   204  			errs[i] = err
   205  		}
   206  		return errs
   207  	}
   208  	errs := r.stream.readErrors(len(objs), level, replyCh)
   209  	if err := firstError(errs); err != nil {
   210  		r.log.WithField("op", "put.many").WithField("class", r.class).
   211  			WithField("shard", shard).Error(errs)
   212  	}
   213  	return errs
   214  }
   215  
   216  func (r *Replicator) DeleteObjects(ctx context.Context,
   217  	shard string,
   218  	uuids []strfmt.UUID,
   219  	dryRun bool,
   220  	l ConsistencyLevel,
   221  ) []objects.BatchSimpleObject {
   222  	coord := newCoordinator[DeleteBatchResponse](r, shard, r.requestID(opDeleteObjects), r.log)
   223  	op := func(ctx context.Context, host, requestID string) error {
   224  		resp, err := r.client.DeleteObjects(
   225  			ctx, host, r.class, shard, requestID, uuids, dryRun)
   226  		if err == nil {
   227  			err = resp.FirstError()
   228  		}
   229  		if err != nil {
   230  			return fmt.Errorf("%q: %w", host, err)
   231  		}
   232  		return nil
   233  	}
   234  	commit := func(ctx context.Context, host, requestID string) (DeleteBatchResponse, error) {
   235  		resp := DeleteBatchResponse{}
   236  		err := r.client.Commit(ctx, host, r.class, shard, requestID, &resp)
   237  		if err == nil {
   238  			err = resp.FirstError()
   239  		}
   240  		if err != nil {
   241  			err = fmt.Errorf("%q: %w", host, err)
   242  		}
   243  		return resp, err
   244  	}
   245  
   246  	replyCh, level, err := coord.Push(ctx, l, op, commit)
   247  	if err != nil {
   248  		r.log.WithField("op", "push.deletes").WithField("class", r.class).
   249  			WithField("shard", shard).Error(err)
   250  		err = fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   251  		errs := make([]objects.BatchSimpleObject, len(uuids))
   252  		for i := 0; i < len(uuids); i++ {
   253  			errs[i].Err = err
   254  		}
   255  		return errs
   256  	}
   257  	rs := r.stream.readDeletions(len(uuids), level, replyCh)
   258  	if err := firstBatchError(rs); err != nil {
   259  		r.log.WithField("op", "put.many").WithField("class", r.class).
   260  			WithField("shard", shard).Error(rs)
   261  	}
   262  	return rs
   263  }
   264  
   265  func (r *Replicator) AddReferences(ctx context.Context,
   266  	shard string,
   267  	refs []objects.BatchReference,
   268  	l ConsistencyLevel,
   269  ) []error {
   270  	coord := newCoordinator[SimpleResponse](r, shard, r.requestID(opAddReferences), r.log)
   271  	op := func(ctx context.Context, host, requestID string) error {
   272  		resp, err := r.client.AddReferences(ctx, host, r.class, shard, requestID, refs)
   273  		if err == nil {
   274  			err = resp.FirstError()
   275  		}
   276  		if err != nil {
   277  			return fmt.Errorf("%q: %w", host, err)
   278  		}
   279  		return nil
   280  	}
   281  	replyCh, level, err := coord.Push(ctx, l, op, r.simpleCommit(shard))
   282  	if err != nil {
   283  		r.log.WithField("op", "push.refs").WithField("class", r.class).
   284  			WithField("shard", shard).Error(err)
   285  		err = fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   286  		errs := make([]error, len(refs))
   287  		for i := 0; i < len(refs); i++ {
   288  			errs[i] = err
   289  		}
   290  		return errs
   291  	}
   292  	errs := r.stream.readErrors(len(refs), level, replyCh)
   293  	if err := firstError(errs); err != nil {
   294  		r.log.WithField("op", "put.refs").WithField("class", r.class).
   295  			WithField("shard", shard).Error(errs)
   296  	}
   297  	return errs
   298  }
   299  
   300  // simpleCommit generate commit function for the coordinator
   301  func (r *Replicator) simpleCommit(shard string) commitOp[SimpleResponse] {
   302  	return func(ctx context.Context, host, requestID string) (SimpleResponse, error) {
   303  		resp := SimpleResponse{}
   304  		err := r.client.Commit(ctx, host, r.class, shard, requestID, &resp)
   305  		if err == nil {
   306  			err = resp.FirstError()
   307  		}
   308  		if err != nil {
   309  			err = fmt.Errorf("%s: %w", host, err)
   310  		}
   311  		return resp, err
   312  	}
   313  }
   314  
   315  // requestID returns ID as [CoordinatorName-OpCode-TimeStamp-Counter].
   316  // The coordinator uses it to uniquely identify a transaction.
   317  // ID makes the request observable in the cluster by specifying its origin
   318  // and the kind of replication request.
   319  func (r *Replicator) requestID(op opID) string {
   320  	return fmt.Sprintf("%s-%.2x-%x-%x",
   321  		r.stateGetter.NodeName(),
   322  		op,
   323  		time.Now().UnixMilli(),
   324  		r.requestCounter.Add(1))
   325  }