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 }