github.com/weaviate/weaviate@v1.24.6/usecases/replica/finder.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  	"errors"
    17  	"fmt"
    18  
    19  	enterrors "github.com/weaviate/weaviate/entities/errors"
    20  
    21  	"github.com/go-openapi/strfmt"
    22  	"github.com/sirupsen/logrus"
    23  	"github.com/weaviate/weaviate/entities/additional"
    24  	"github.com/weaviate/weaviate/entities/search"
    25  	"github.com/weaviate/weaviate/entities/storobj"
    26  	"github.com/weaviate/weaviate/usecases/objects"
    27  )
    28  
    29  var (
    30  	// msgCLevel consistency level cannot be achieved
    31  	msgCLevel = "cannot achieve consistency level"
    32  
    33  	errReplicas = errors.New("cannot reach enough replicas")
    34  	errRepair   = errors.New("read repair error")
    35  	errRead     = errors.New("read error")
    36  )
    37  
    38  type (
    39  	// senderReply is a container for the data received from a replica
    40  	senderReply[T any] struct {
    41  		sender     string // hostname of the sender
    42  		Version    int64  // sender's current version of the object
    43  		Data       T      // the data sent by the sender
    44  		UpdateTime int64  // sender's current update time
    45  		DigestRead bool
    46  	}
    47  	findOneReply senderReply[objects.Replica]
    48  	existReply   struct {
    49  		Sender string
    50  		RepairResponse
    51  	}
    52  )
    53  
    54  // Finder finds replicated objects
    55  type Finder struct {
    56  	resolver     *resolver // host names of replicas
    57  	finderStream           // stream of objects
    58  }
    59  
    60  // NewFinder constructs a new finder instance
    61  func NewFinder(className string,
    62  	resolver *resolver,
    63  	client rClient,
    64  	l logrus.FieldLogger,
    65  ) *Finder {
    66  	cl := finderClient{client}
    67  	return &Finder{
    68  		resolver: resolver,
    69  		finderStream: finderStream{
    70  			repairer: repairer{
    71  				class:  className,
    72  				client: cl,
    73  				logger: l,
    74  			},
    75  			log: l,
    76  		},
    77  	}
    78  }
    79  
    80  // GetOne gets object which satisfies the giving consistency
    81  func (f *Finder) GetOne(ctx context.Context,
    82  	l ConsistencyLevel, shard string,
    83  	id strfmt.UUID,
    84  	props search.SelectProperties,
    85  	adds additional.Properties,
    86  ) (*storobj.Object, error) {
    87  	c := newReadCoordinator[findOneReply](f, shard)
    88  	op := func(ctx context.Context, host string, fullRead bool) (findOneReply, error) {
    89  		if fullRead {
    90  			r, err := f.client.FullRead(ctx, host, f.class, shard, id, props, adds)
    91  			return findOneReply{host, 0, r, r.UpdateTime(), false}, err
    92  		} else {
    93  			xs, err := f.client.DigestReads(ctx, host, f.class, shard, []strfmt.UUID{id})
    94  			var x RepairResponse
    95  			if len(xs) == 1 {
    96  				x = xs[0]
    97  			}
    98  			r := objects.Replica{ID: id, Deleted: x.Deleted}
    99  			return findOneReply{host, x.Version, r, x.UpdateTime, true}, err
   100  		}
   101  	}
   102  	replyCh, state, err := c.Pull(ctx, l, op, "")
   103  	if err != nil {
   104  		f.log.WithField("op", "pull.one").Error(err)
   105  		return nil, fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   106  	}
   107  	result := <-f.readOne(ctx, shard, id, replyCh, state)
   108  	if err = result.Err; err != nil {
   109  		err = fmt.Errorf("%s %q: %w", msgCLevel, l, err)
   110  	}
   111  	return result.Value, err
   112  }
   113  
   114  type ShardDesc struct {
   115  	Name string
   116  	Node string
   117  }
   118  
   119  // CheckConsistency for objects belonging to different physical shards.
   120  //
   121  // For each x in xs the fields BelongsToNode and BelongsToShard must be set non empty
   122  func (f *Finder) CheckConsistency(ctx context.Context,
   123  	l ConsistencyLevel, xs []*storobj.Object,
   124  ) (retErr error) {
   125  	if len(xs) == 0 {
   126  		return nil
   127  	}
   128  	for i, x := range xs { // check shard and node name are set
   129  		if x == nil {
   130  			return fmt.Errorf("contains nil at object at index %d", i)
   131  		}
   132  		if x.BelongsToNode == "" || x.BelongsToShard == "" {
   133  			return fmt.Errorf("missing node or shard at index %d", i)
   134  		}
   135  	}
   136  
   137  	if l == One { // already consistent
   138  		for i := range xs {
   139  			xs[i].IsConsistent = true
   140  		}
   141  		return nil
   142  	}
   143  	// check shard consistency concurrently
   144  	gr, ctx := enterrors.NewErrorGroupWithContextWrapper(f.logger, ctx)
   145  	for _, part := range cluster(createBatch(xs)) {
   146  		part := part
   147  		gr.Go(func() error {
   148  			_, err := f.checkShardConsistency(ctx, l, part)
   149  			if err != nil {
   150  				f.log.WithField("op", "check_shard_consistency").
   151  					WithField("shard", part.Shard).Error(err)
   152  			}
   153  			return err
   154  		}, part)
   155  	}
   156  	return gr.Wait()
   157  }
   158  
   159  // Exists checks if an object exists which satisfies the giving consistency
   160  func (f *Finder) Exists(ctx context.Context,
   161  	l ConsistencyLevel,
   162  	shard string,
   163  	id strfmt.UUID,
   164  ) (bool, error) {
   165  	c := newReadCoordinator[existReply](f, shard)
   166  	op := func(ctx context.Context, host string, _ bool) (existReply, error) {
   167  		xs, err := f.client.DigestReads(ctx, host, f.class, shard, []strfmt.UUID{id})
   168  		var x RepairResponse
   169  		if len(xs) == 1 {
   170  			x = xs[0]
   171  		}
   172  		return existReply{host, x}, err
   173  	}
   174  	replyCh, state, err := c.Pull(ctx, l, op, "")
   175  	if err != nil {
   176  		f.log.WithField("op", "pull.exist").Error(err)
   177  		return false, fmt.Errorf("%s %q: %w", msgCLevel, l, errReplicas)
   178  	}
   179  	result := <-f.readExistence(ctx, shard, id, replyCh, state)
   180  	if err = result.Err; err != nil {
   181  		err = fmt.Errorf("%s %q: %w", msgCLevel, l, err)
   182  	}
   183  	return result.Value, err
   184  }
   185  
   186  // NodeObject gets object from a specific node.
   187  // it is used mainly for debugging purposes
   188  func (f *Finder) NodeObject(ctx context.Context,
   189  	nodeName,
   190  	shard string,
   191  	id strfmt.UUID,
   192  	props search.SelectProperties, adds additional.Properties,
   193  ) (*storobj.Object, error) {
   194  	host, ok := f.resolver.NodeHostname(nodeName)
   195  	if !ok || host == "" {
   196  		return nil, fmt.Errorf("cannot resolve node name: %s", nodeName)
   197  	}
   198  	r, err := f.client.FullRead(ctx, host, f.class, shard, id, props, adds)
   199  	return r.Object, err
   200  }
   201  
   202  // checkShardConsistency checks consistency for a set of objects belonging to a shard
   203  // It returns the most recent objects or and error
   204  func (f *Finder) checkShardConsistency(ctx context.Context,
   205  	l ConsistencyLevel,
   206  	batch shardPart,
   207  ) ([]*storobj.Object, error) {
   208  	var (
   209  		c         = newReadCoordinator[batchReply](f, batch.Shard)
   210  		shard     = batch.Shard
   211  		data, ids = batch.Extract() // extract from current content
   212  	)
   213  	op := func(ctx context.Context, host string, fullRead bool) (batchReply, error) {
   214  		if fullRead { // we already have the content
   215  			return batchReply{Sender: host, IsDigest: false, FullData: data}, nil
   216  		} else {
   217  			xs, err := f.client.DigestReads(ctx, host, f.class, shard, ids)
   218  			return batchReply{Sender: host, IsDigest: true, DigestData: xs}, err
   219  		}
   220  	}
   221  
   222  	replyCh, state, err := c.Pull(ctx, l, op, batch.Node)
   223  	if err != nil {
   224  		return nil, fmt.Errorf("pull shard: %w", errReplicas)
   225  	}
   226  	result := <-f.readBatchPart(ctx, batch, ids, replyCh, state)
   227  	return result.Value, result.Err
   228  }