github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/replication.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 db 13 14 import ( 15 "context" 16 "fmt" 17 "io" 18 "os" 19 "path" 20 "path/filepath" 21 22 "github.com/go-openapi/strfmt" 23 "github.com/weaviate/weaviate/entities/additional" 24 "github.com/weaviate/weaviate/entities/multi" 25 "github.com/weaviate/weaviate/entities/schema" 26 "github.com/weaviate/weaviate/entities/storobj" 27 "github.com/weaviate/weaviate/usecases/objects" 28 "github.com/weaviate/weaviate/usecases/replica" 29 ) 30 31 type Replicator interface { 32 ReplicateObject(ctx context.Context, shardName, requestID string, 33 object *storobj.Object) replica.SimpleResponse 34 ReplicateObjects(ctx context.Context, shardName, requestID string, 35 objects []*storobj.Object) replica.SimpleResponse 36 ReplicateUpdate(ctx context.Context, shard, requestID string, 37 doc *objects.MergeDocument) replica.SimpleResponse 38 ReplicateDeletion(ctx context.Context, shardName, requestID string, 39 uuid strfmt.UUID) replica.SimpleResponse 40 ReplicateDeletions(ctx context.Context, shardName, requestID string, 41 uuids []strfmt.UUID, dryRun bool) replica.SimpleResponse 42 ReplicateReferences(ctx context.Context, shard, requestID string, 43 refs []objects.BatchReference) replica.SimpleResponse 44 CommitReplication(shard, 45 requestID string) interface{} 46 AbortReplication(shardName, 47 requestID string) interface{} 48 } 49 50 func (db *DB) ReplicateObject(ctx context.Context, class, 51 shard, requestID string, object *storobj.Object, 52 ) replica.SimpleResponse { 53 index, pr := db.replicatedIndex(class) 54 if pr != nil { 55 return *pr 56 } 57 58 return index.ReplicateObject(ctx, shard, requestID, object) 59 } 60 61 func (db *DB) ReplicateObjects(ctx context.Context, class, 62 shard, requestID string, objects []*storobj.Object, 63 ) replica.SimpleResponse { 64 index, pr := db.replicatedIndex(class) 65 if pr != nil { 66 return *pr 67 } 68 69 return index.ReplicateObjects(ctx, shard, requestID, objects) 70 } 71 72 func (db *DB) ReplicateUpdate(ctx context.Context, class, 73 shard, requestID string, mergeDoc *objects.MergeDocument, 74 ) replica.SimpleResponse { 75 index, pr := db.replicatedIndex(class) 76 if pr != nil { 77 return *pr 78 } 79 80 return index.ReplicateUpdate(ctx, shard, requestID, mergeDoc) 81 } 82 83 func (db *DB) ReplicateDeletion(ctx context.Context, class, 84 shard, requestID string, uuid strfmt.UUID, 85 ) replica.SimpleResponse { 86 index, pr := db.replicatedIndex(class) 87 if pr != nil { 88 return *pr 89 } 90 91 return index.ReplicateDeletion(ctx, shard, requestID, uuid) 92 } 93 94 func (db *DB) ReplicateDeletions(ctx context.Context, class, 95 shard, requestID string, uuids []strfmt.UUID, dryRun bool, 96 ) replica.SimpleResponse { 97 index, pr := db.replicatedIndex(class) 98 if pr != nil { 99 return *pr 100 } 101 102 return index.ReplicateDeletions(ctx, shard, requestID, uuids, dryRun) 103 } 104 105 func (db *DB) ReplicateReferences(ctx context.Context, class, 106 shard, requestID string, refs []objects.BatchReference, 107 ) replica.SimpleResponse { 108 index, pr := db.replicatedIndex(class) 109 if pr != nil { 110 return *pr 111 } 112 113 return index.ReplicateReferences(ctx, shard, requestID, refs) 114 } 115 116 func (db *DB) CommitReplication(class, 117 shard, requestID string, 118 ) interface{} { 119 index, pr := db.replicatedIndex(class) 120 if pr != nil { 121 return nil 122 } 123 124 return index.CommitReplication(shard, requestID) 125 } 126 127 func (db *DB) AbortReplication(class, 128 shard, requestID string, 129 ) interface{} { 130 index, pr := db.replicatedIndex(class) 131 if pr != nil { 132 return *pr 133 } 134 135 return index.AbortReplication(shard, requestID) 136 } 137 138 func (db *DB) replicatedIndex(name string) (idx *Index, resp *replica.SimpleResponse) { 139 if !db.StartupComplete() { 140 return nil, &replica.SimpleResponse{Errors: []replica.Error{ 141 *replica.NewError(replica.StatusNotReady, name), 142 }} 143 } 144 145 if idx = db.GetIndex(schema.ClassName(name)); idx == nil { 146 return nil, &replica.SimpleResponse{Errors: []replica.Error{ 147 *replica.NewError(replica.StatusClassNotFound, name), 148 }} 149 } 150 return 151 } 152 153 func (i *Index) writableShard(name string) (ShardLike, *replica.SimpleResponse) { 154 localShard := i.localShard(name) 155 if localShard == nil { 156 return nil, &replica.SimpleResponse{Errors: []replica.Error{ 157 {Code: replica.StatusShardNotFound, Msg: name}, 158 }} 159 } 160 if localShard.isReadOnly() { 161 return nil, &replica.SimpleResponse{Errors: []replica.Error{{ 162 Code: replica.StatusReadOnly, Msg: name, 163 }}} 164 } 165 return localShard, nil 166 } 167 168 func (i *Index) ReplicateObject(ctx context.Context, shard, requestID string, object *storobj.Object) replica.SimpleResponse { 169 localShard, pr := i.writableShard(shard) 170 if pr != nil { 171 return *pr 172 } 173 return localShard.preparePutObject(ctx, requestID, object) 174 } 175 176 func (i *Index) ReplicateUpdate(ctx context.Context, shard, requestID string, doc *objects.MergeDocument) replica.SimpleResponse { 177 localShard, pr := i.writableShard(shard) 178 if pr != nil { 179 return *pr 180 } 181 return localShard.prepareMergeObject(ctx, requestID, doc) 182 } 183 184 func (i *Index) ReplicateDeletion(ctx context.Context, shard, requestID string, uuid strfmt.UUID) replica.SimpleResponse { 185 localShard, pr := i.writableShard(shard) 186 if pr != nil { 187 return *pr 188 } 189 return localShard.prepareDeleteObject(ctx, requestID, uuid) 190 } 191 192 func (i *Index) ReplicateObjects(ctx context.Context, shard, requestID string, objects []*storobj.Object) replica.SimpleResponse { 193 localShard, pr := i.writableShard(shard) 194 if pr != nil { 195 return *pr 196 } 197 return localShard.preparePutObjects(ctx, requestID, objects) 198 } 199 200 func (i *Index) ReplicateDeletions(ctx context.Context, shard, requestID string, uuids []strfmt.UUID, dryRun bool) replica.SimpleResponse { 201 localShard, pr := i.writableShard(shard) 202 if pr != nil { 203 return *pr 204 } 205 return localShard.prepareDeleteObjects(ctx, requestID, uuids, dryRun) 206 } 207 208 func (i *Index) ReplicateReferences(ctx context.Context, shard, requestID string, refs []objects.BatchReference) replica.SimpleResponse { 209 localShard, pr := i.writableShard(shard) 210 if pr != nil { 211 return *pr 212 } 213 return localShard.prepareAddReferences(ctx, requestID, refs) 214 } 215 216 func (i *Index) CommitReplication(shard, requestID string) interface{} { 217 localShard := i.localShard(shard) 218 if localShard == nil { 219 return nil 220 } 221 return localShard.commitReplication(context.Background(), requestID, &i.backupMutex) 222 } 223 224 func (i *Index) AbortReplication(shard, requestID string) interface{} { 225 localShard := i.localShard(shard) 226 if localShard == nil { 227 return replica.SimpleResponse{Errors: []replica.Error{ 228 {Code: replica.StatusShardNotFound, Msg: shard}, 229 }} 230 } 231 return localShard.abortReplication(context.Background(), requestID) 232 } 233 234 func (i *Index) IncomingFilePutter(ctx context.Context, shardName, 235 filePath string, 236 ) (io.WriteCloser, error) { 237 localShard := i.localShard(shardName) 238 if localShard == nil { 239 return nil, fmt.Errorf("shard %q does not exist locally", shardName) 240 } 241 242 return localShard.filePutter(ctx, filePath) 243 } 244 245 func (i *Index) IncomingCreateShard(ctx context.Context, className string, shardName string) error { 246 sch := i.getSchema.GetSchemaSkipAuth() 247 class := sch.GetClass(schema.ClassName(className)) 248 if err := i.addNewShard(ctx, class, shardName); err != nil { 249 return fmt.Errorf("incoming create shard: %w", err) 250 } 251 return nil 252 } 253 254 func (i *Index) IncomingReinitShard(ctx context.Context, 255 shardName string, 256 ) error { 257 shard := i.localShard(shardName) 258 if shard == nil { 259 return fmt.Errorf("shard %q does not exist locally", shardName) 260 } 261 262 return shard.reinit(ctx) 263 } 264 265 func (s *Shard) filePutter(ctx context.Context, 266 filePath string, 267 ) (io.WriteCloser, error) { 268 // TODO: validate file prefix to rule out that we're accidentally writing 269 // into another shard 270 finalPath := filepath.Join(s.Index().Config.RootPath, filePath) 271 dir := path.Dir(finalPath) 272 if err := os.MkdirAll(dir, os.ModePerm); err != nil { 273 return nil, fmt.Errorf("create parent folder for %s: %w", filePath, err) 274 } 275 276 f, err := os.Create(finalPath) 277 if err != nil { 278 return nil, fmt.Errorf("open file %q for writing: %w", filePath, err) 279 } 280 281 return f, nil 282 } 283 284 func (s *Shard) reinit(ctx context.Context) error { 285 if err := s.Shutdown(ctx); err != nil { 286 return fmt.Errorf("shutdown shard: %w", err) 287 } 288 289 if err := s.initNonVector(ctx, nil); err != nil { 290 return fmt.Errorf("reinit non-vector: %w", err) 291 } 292 293 if s.hasTargetVectors() { 294 if err := s.initTargetVectors(ctx); err != nil { 295 return fmt.Errorf("reinit vector: %w", err) 296 } 297 } else { 298 if err := s.initLegacyVector(ctx); err != nil { 299 return fmt.Errorf("reinit vector: %w", err) 300 } 301 } 302 303 s.initCycleCallbacks() 304 s.initDimensionTracking() 305 306 return nil 307 } 308 309 func (db *DB) OverwriteObjects(ctx context.Context, 310 class, shardName string, vobjects []*objects.VObject, 311 ) ([]replica.RepairResponse, error) { 312 index := db.GetIndex(schema.ClassName(class)) 313 return index.overwriteObjects(ctx, shardName, vobjects) 314 } 315 316 // overwrite objects if their state didn't change in the meantime 317 // It returns nil if all object have been successfully overwritten 318 // and otherwise a list of failed operations. 319 func (i *Index) overwriteObjects(ctx context.Context, 320 shard string, updates []*objects.VObject, 321 ) ([]replica.RepairResponse, error) { 322 result := make([]replica.RepairResponse, 0, len(updates)/2) 323 s := i.localShard(shard) 324 if s == nil { 325 return nil, fmt.Errorf("shard %q not found locally", shard) 326 } 327 for i, u := range updates { 328 // Just in case but this should not happen 329 data := u.LatestObject 330 if data == nil || data.ID == "" { 331 msg := fmt.Sprintf("received nil object or empty uuid at position %d", i) 332 result = append(result, replica.RepairResponse{Err: msg}) 333 continue 334 } 335 // valid update 336 found, err := s.ObjectByID(ctx, data.ID, nil, additional.Properties{}) 337 var curUpdateTime int64 // 0 means object doesn't exist on this node 338 if found != nil { 339 curUpdateTime = found.LastUpdateTimeUnix() 340 } 341 r := replica.RepairResponse{ID: data.ID.String(), UpdateTime: curUpdateTime} 342 switch { 343 case err != nil: 344 r.Err = "not found: " + err.Error() 345 case curUpdateTime == u.StaleUpdateTime: 346 // the stored object is not the most recent version. in 347 // this case, we overwrite it with the more recent one. 348 err := s.PutObject(ctx, storobj.FromObject(data, u.Vector, u.Vectors)) 349 if err != nil { 350 r.Err = fmt.Sprintf("overwrite stale object: %v", err) 351 } 352 case curUpdateTime != data.LastUpdateTimeUnix: 353 // object changed and its state differs from recent known state 354 r.Err = "conflict" 355 } 356 357 if r.Err != "" { // include only unsuccessful responses 358 result = append(result, r) 359 } 360 } 361 if len(result) == 0 { 362 return nil, nil 363 } 364 return result, nil 365 } 366 367 func (i *Index) IncomingOverwriteObjects(ctx context.Context, 368 shardName string, vobjects []*objects.VObject, 369 ) ([]replica.RepairResponse, error) { 370 return i.overwriteObjects(ctx, shardName, vobjects) 371 } 372 373 func (db *DB) DigestObjects(ctx context.Context, 374 class, shardName string, ids []strfmt.UUID, 375 ) (result []replica.RepairResponse, err error) { 376 index := db.GetIndex(schema.ClassName(class)) 377 return index.digestObjects(ctx, shardName, ids) 378 } 379 380 func (i *Index) digestObjects(ctx context.Context, 381 shardName string, ids []strfmt.UUID, 382 ) (result []replica.RepairResponse, err error) { 383 result = make([]replica.RepairResponse, len(ids)) 384 s := i.localShard(shardName) 385 if s == nil { 386 return nil, fmt.Errorf("shard %q not found locally", shardName) 387 } 388 389 multiIDs := make([]multi.Identifier, len(ids)) 390 for j := range multiIDs { 391 multiIDs[j] = multi.Identifier{ID: ids[j].String()} 392 } 393 394 objs, err := s.MultiObjectByID(ctx, multiIDs) 395 if err != nil { 396 return nil, fmt.Errorf("shard objects digest: %w", err) 397 } 398 399 for j := range objs { 400 if objs[j] == nil { 401 deleted, err := s.WasDeleted(ctx, ids[j]) 402 if err != nil { 403 return nil, err 404 } 405 result[j] = replica.RepairResponse{ 406 ID: ids[j].String(), 407 Deleted: deleted, 408 // TODO: use version when supported 409 Version: 0, 410 } 411 } else { 412 result[j] = replica.RepairResponse{ 413 ID: objs[j].ID().String(), 414 UpdateTime: objs[j].LastUpdateTimeUnix(), 415 // TODO: use version when supported 416 Version: 0, 417 } 418 } 419 } 420 421 return 422 } 423 424 func (i *Index) IncomingDigestObjects(ctx context.Context, 425 shardName string, ids []strfmt.UUID, 426 ) (result []replica.RepairResponse, err error) { 427 return i.digestObjects(ctx, shardName, ids) 428 } 429 430 func (db *DB) FetchObject(ctx context.Context, 431 class, shardName string, id strfmt.UUID, 432 ) (objects.Replica, error) { 433 index := db.GetIndex(schema.ClassName(class)) 434 return index.readRepairGetObject(ctx, shardName, id) 435 } 436 437 func (i *Index) readRepairGetObject(ctx context.Context, 438 shardName string, id strfmt.UUID, 439 ) (objects.Replica, error) { 440 shard := i.localShard(shardName) 441 if shard == nil { 442 return objects.Replica{}, fmt.Errorf("shard %q does not exist locally", shardName) 443 } 444 445 obj, err := shard.ObjectByID(ctx, id, nil, additional.Properties{}) 446 if err != nil { 447 return objects.Replica{}, fmt.Errorf("shard %q read repair get object: %w", shard.ID(), err) 448 } 449 450 if obj == nil { 451 deleted, err := shard.WasDeleted(ctx, id) 452 if err != nil { 453 return objects.Replica{}, err 454 } 455 return objects.Replica{ 456 ID: id, 457 Deleted: deleted, 458 }, nil 459 } 460 461 return objects.Replica{ 462 Object: obj, 463 ID: obj.ID(), 464 }, nil 465 } 466 467 func (db *DB) FetchObjects(ctx context.Context, 468 class, shardName string, ids []strfmt.UUID, 469 ) ([]objects.Replica, error) { 470 index := db.GetIndex(schema.ClassName(class)) 471 return index.fetchObjects(ctx, shardName, ids) 472 } 473 474 func (i *Index) fetchObjects(ctx context.Context, 475 shardName string, ids []strfmt.UUID, 476 ) ([]objects.Replica, error) { 477 shard := i.localShard(shardName) 478 if shard == nil { 479 return nil, fmt.Errorf("shard %q does not exist locally", shardName) 480 } 481 482 objs, err := shard.MultiObjectByID(ctx, wrapIDsInMulti(ids)) 483 if err != nil { 484 return nil, fmt.Errorf("shard %q replication multi get objects: %w", shard.ID(), err) 485 } 486 487 resp := make([]objects.Replica, len(ids)) 488 489 for j, obj := range objs { 490 if obj == nil { 491 deleted, err := shard.WasDeleted(ctx, ids[j]) 492 if err != nil { 493 return nil, err 494 } 495 resp[j] = objects.Replica{ 496 ID: ids[j], 497 Deleted: deleted, 498 } 499 } else { 500 resp[j] = objects.Replica{ 501 Object: obj, 502 ID: obj.ID(), 503 } 504 } 505 } 506 507 return resp, nil 508 }