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 }