github.com/weaviate/weaviate@v1.24.6/usecases/replica/repairer.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  	"sort"
    19  
    20  	"github.com/sirupsen/logrus"
    21  	enterrors "github.com/weaviate/weaviate/entities/errors"
    22  
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/weaviate/weaviate/entities/additional"
    25  	"github.com/weaviate/weaviate/entities/search"
    26  	"github.com/weaviate/weaviate/entities/storobj"
    27  	"github.com/weaviate/weaviate/usecases/objects"
    28  )
    29  
    30  var (
    31  	// errConflictFindDeleted object exists on one replica but is deleted on the other.
    32  	//
    33  	// It depends on the order of operations
    34  	// Created -> Deleted    => It is safe in this case to propagate deletion to all replicas
    35  	// Created -> Deleted -> Created => propagating deletion will result in data lost
    36  	errConflictExistOrDeleted = errors.New("conflict: object has been deleted on another replica")
    37  
    38  	// errConflictObjectChanged object changed since last time and cannot be repaired
    39  	errConflictObjectChanged = errors.New("source object changed during repair")
    40  )
    41  
    42  // repairer tries to detect inconsistencies and repair objects when reading them from replicas
    43  type repairer struct {
    44  	class  string
    45  	client finderClient // needed to commit and abort operation
    46  	logger logrus.FieldLogger
    47  }
    48  
    49  // repairOne repairs a single object (used by Finder::GetOne)
    50  func (r *repairer) repairOne(ctx context.Context,
    51  	shard string,
    52  	id strfmt.UUID,
    53  	votes []objTuple, st rState,
    54  	contentIdx int,
    55  ) (_ *storobj.Object, err error) {
    56  	var (
    57  		lastUTime int64
    58  		winnerIdx int
    59  		cl        = r.client
    60  	)
    61  	for i, x := range votes {
    62  		if x.o.Deleted {
    63  			return nil, errConflictExistOrDeleted
    64  		}
    65  		if x.UTime > lastUTime {
    66  			lastUTime = x.UTime
    67  			winnerIdx = i
    68  		}
    69  	}
    70  	// fetch most recent object
    71  	updates := votes[contentIdx].o
    72  	winner := votes[winnerIdx]
    73  	if updates.UpdateTime() != lastUTime {
    74  		updates, err = cl.FullRead(ctx, winner.sender, r.class, shard, id,
    75  			search.SelectProperties{}, additional.Properties{})
    76  		if err != nil {
    77  			return nil, fmt.Errorf("get most recent object from %s: %w", winner.sender, err)
    78  		}
    79  		if updates.UpdateTime() != lastUTime {
    80  			return nil, fmt.Errorf("fetch new state from %s: %w, %v", winner.sender, errConflictObjectChanged, err)
    81  		}
    82  	}
    83  
    84  	gr := enterrors.NewErrorGroupWrapper(r.logger)
    85  	for _, vote := range votes { // repair
    86  		if vote.UTime == lastUTime {
    87  			continue
    88  		}
    89  		vote := vote
    90  		gr.Go(func() error {
    91  			ups := []*objects.VObject{{
    92  				LatestObject:    &updates.Object.Object,
    93  				Vector:          updates.Object.Vector,
    94  				StaleUpdateTime: vote.UTime,
    95  			}}
    96  			resp, err := cl.Overwrite(ctx, vote.sender, r.class, shard, ups)
    97  			if err != nil {
    98  				return fmt.Errorf("node %q could not repair object: %w", vote.sender, err)
    99  			}
   100  			if len(resp) > 0 && resp[0].Err != "" {
   101  				return fmt.Errorf("overwrite %w %s: %s", errConflictObjectChanged, vote.sender, resp[0].Err)
   102  			}
   103  			return nil
   104  		})
   105  	}
   106  
   107  	return updates.Object, gr.Wait()
   108  }
   109  
   110  // iTuple tuple of indices used to identify a unique object
   111  type iTuple struct {
   112  	S       int   // sender's index
   113  	O       int   // object's index
   114  	T       int64 // last update time
   115  	Deleted bool
   116  }
   117  
   118  // repairExist repairs a single object when checking for existence
   119  func (r *repairer) repairExist(ctx context.Context,
   120  	shard string,
   121  	id strfmt.UUID,
   122  	votes []boolTuple,
   123  	st rState,
   124  ) (_ bool, err error) {
   125  	var (
   126  		lastUTime int64
   127  		winnerIdx int
   128  		cl        = r.client
   129  	)
   130  	for i, x := range votes {
   131  		if x.o.Deleted {
   132  			return false, errConflictExistOrDeleted
   133  		}
   134  		if x.UTime > lastUTime {
   135  			lastUTime = x.UTime
   136  			winnerIdx = i
   137  		}
   138  	}
   139  	// fetch most recent object
   140  	winner := votes[winnerIdx]
   141  	resp, err := cl.FullRead(ctx, winner.sender, r.class, shard, id, search.SelectProperties{}, additional.Properties{})
   142  	if err != nil {
   143  		return false, fmt.Errorf("get most recent object from %s: %w", winner.sender, err)
   144  	}
   145  	if resp.UpdateTime() != lastUTime {
   146  		return false, fmt.Errorf("fetch new state from %s: %w, %v", winner.sender, errConflictObjectChanged, err)
   147  	}
   148  	gr, ctx := enterrors.NewErrorGroupWithContextWrapper(r.logger, ctx)
   149  	for _, vote := range votes { // repair
   150  		if vote.UTime == lastUTime {
   151  			continue
   152  		}
   153  		vote := vote
   154  		gr.Go(func() error {
   155  			ups := []*objects.VObject{{
   156  				LatestObject:    &resp.Object.Object,
   157  				Vector:          resp.Object.Vector,
   158  				StaleUpdateTime: vote.UTime,
   159  			}}
   160  			resp, err := cl.Overwrite(ctx, vote.sender, r.class, shard, ups)
   161  			if err != nil {
   162  				return fmt.Errorf("node %q could not repair object: %w", vote.sender, err)
   163  			}
   164  			if len(resp) > 0 && resp[0].Err != "" {
   165  				return fmt.Errorf("overwrite %w %s: %s", errConflictObjectChanged, vote.sender, resp[0].Err)
   166  			}
   167  			return nil
   168  		})
   169  	}
   170  	return !resp.Deleted, gr.Wait()
   171  }
   172  
   173  // repairAll repairs objects when reading them ((use in combination with Finder::GetAll)
   174  func (r *repairer) repairBatchPart(ctx context.Context,
   175  	shard string,
   176  	ids []strfmt.UUID,
   177  	votes []vote,
   178  	st rState,
   179  	contentIdx int,
   180  ) ([]*storobj.Object, error) {
   181  	var (
   182  		result     = make([]*storobj.Object, len(ids)) // final result
   183  		lastTimes  = make([]iTuple, len(ids))          // most recent times
   184  		ms         = make([]iTuple, 0, len(ids))       // mismatches
   185  		nDeletions = 0
   186  		cl         = r.client
   187  		nVotes     = len(votes)
   188  		// The input objects cannot be used for repair because
   189  		// their attributes might have been filtered out
   190  		reFetchSet = make(map[int]struct{})
   191  	)
   192  
   193  	// find most recent objects
   194  	for i, x := range votes[contentIdx].FullData {
   195  		lastTimes[i] = iTuple{S: contentIdx, O: i, T: x.UpdateTime(), Deleted: x.Deleted}
   196  		votes[contentIdx].Count[i] = nVotes // reuse Count[] to check consistency
   197  	}
   198  
   199  	for i, vote := range votes {
   200  		if i != contentIdx {
   201  			for j, x := range vote.DigestData {
   202  				deleted := lastTimes[j].Deleted || x.Deleted
   203  				if curTime := lastTimes[j].T; x.UpdateTime > curTime {
   204  					lastTimes[j] = iTuple{S: i, O: j, T: x.UpdateTime}
   205  					delete(reFetchSet, j) // input object is not up to date
   206  				} else if x.UpdateTime < curTime {
   207  					reFetchSet[j] = struct{}{} // we need to fetch this object again
   208  				}
   209  				lastTimes[j].Deleted = deleted
   210  				votes[i].Count[j] = nVotes
   211  			}
   212  		}
   213  	}
   214  
   215  	// find missing content (diff)
   216  	for i, p := range votes[contentIdx].FullData {
   217  		if lastTimes[i].Deleted { // conflict
   218  			nDeletions++
   219  			result[i] = nil
   220  			votes[contentIdx].Count[i] = 0
   221  		} else if _, ok := reFetchSet[i]; ok || (contentIdx != lastTimes[i].S) {
   222  			ms = append(ms, lastTimes[i])
   223  		} else {
   224  			result[i] = p.Object
   225  		}
   226  	}
   227  	if len(ms) > 0 { // fetch most recent objects
   228  		// partition by hostname
   229  		sort.SliceStable(ms, func(i, j int) bool { return ms[i].S < ms[j].S })
   230  		partitions := make([]int, 0, len(votes))
   231  		pre := ms[0].S
   232  		for i, y := range ms {
   233  			if y.S != pre {
   234  				partitions = append(partitions, i)
   235  				pre = y.S
   236  			}
   237  		}
   238  		partitions = append(partitions, len(ms))
   239  
   240  		// concurrent fetches
   241  		gr, ctx := enterrors.NewErrorGroupWithContextWrapper(r.logger, ctx)
   242  		start := 0
   243  		for _, end := range partitions { // fetch diffs
   244  			rid := ms[start].S
   245  			receiver := votes[rid].Sender
   246  			query := make([]strfmt.UUID, end-start)
   247  			for j := 0; start < end; start++ {
   248  				query[j] = ids[ms[start].O]
   249  				j++
   250  			}
   251  			start := start
   252  			gr.Go(func() error {
   253  				resp, err := cl.FullReads(ctx, receiver, r.class, shard, query)
   254  				for i, n := 0, len(query); i < n; i++ {
   255  					idx := ms[start-n+i].O
   256  					if err != nil || lastTimes[idx].T != resp[i].UpdateTime() {
   257  						votes[rid].Count[idx]--
   258  					} else {
   259  						result[idx] = resp[i].Object
   260  					}
   261  				}
   262  				return nil
   263  			})
   264  
   265  		}
   266  		if err := gr.Wait(); err != nil {
   267  			return nil, err
   268  		}
   269  	}
   270  
   271  	// concurrent repairs
   272  	gr, ctx := enterrors.NewErrorGroupWithContextWrapper(r.logger, ctx)
   273  	for rid, vote := range votes {
   274  		query := make([]*objects.VObject, 0, len(ids)/2)
   275  		m := make(map[string]int, len(ids)/2) //
   276  		for j, x := range lastTimes {
   277  			cTime := vote.UpdateTimeAt(j)
   278  			if x.T != cTime && !x.Deleted && result[j] != nil && vote.Count[j] == nVotes {
   279  				obj := objects.VObject{
   280  					LatestObject:    &result[j].Object,
   281  					Vector:          result[j].Vector,
   282  					StaleUpdateTime: cTime,
   283  				}
   284  				query = append(query, &obj)
   285  				m[string(result[j].ID())] = j
   286  			}
   287  		}
   288  		if len(query) == 0 {
   289  			continue
   290  		}
   291  		receiver := vote.Sender
   292  		rid := rid
   293  		gr.Go(func() error {
   294  			rs, err := cl.Overwrite(ctx, receiver, r.class, shard, query)
   295  			if err != nil {
   296  				for _, idx := range m {
   297  					votes[rid].Count[idx]--
   298  				}
   299  				return nil
   300  			}
   301  			for _, r := range rs {
   302  				if r.Err != "" {
   303  					if idx, ok := m[r.ID]; ok {
   304  						votes[rid].Count[idx]--
   305  					}
   306  				}
   307  			}
   308  			return nil
   309  		})
   310  	}
   311  
   312  	return result, gr.Wait()
   313  }