@@ -120,8 +120,8 @@ type searchContext struct {
120
120
tempVectorsWithKeys []vecstore.VectorWithKey
121
121
}
122
122
123
- // VectorIndex implements the C-SPANN algorithm, which adapts Microsoft’ s SPANN
124
- // and SPFresh algorithms to work well with CockroachDB’ s unique distributed
123
+ // VectorIndex implements the C-SPANN algorithm, which adapts Microsoft' s SPANN
124
+ // and SPFresh algorithms to work well with CockroachDB' s unique distributed
125
125
// architecture. This enables CockroachDB to efficiently answer approximate
126
126
// nearest neighbor (ANN) queries with high accuracy, low latency, and fresh
127
127
// results, with millions or even billions of indexed vectors. In a departure
@@ -314,60 +314,17 @@ func (vi *VectorIndex) Insert(
314
314
func (vi * VectorIndex ) Delete (
315
315
ctx context.Context , txn vecstore.Txn , vector vector.T , key vecstore.PrimaryKey ,
316
316
) error {
317
- // Potentially throttle delete operation if background work is falling behind.
318
- if err := vi . fixups . DelayInsertOrDelete ( ctx ); err != nil {
317
+ result , err := vi . SearchForDelete ( ctx , txn , vector , key )
318
+ if err != nil {
319
319
return err
320
320
}
321
-
322
- // Search for the vector in the index.
323
- searchCtx := searchContext {
324
- Txn : txn ,
325
- Original : vector ,
326
- Level : vecstore .LeafLevel ,
327
- Options : SearchOptions {
328
- SkipRerank : vi .options .DisableErrorBounds ,
329
- UpdateStats : true ,
330
- },
321
+ if result == nil {
322
+ return nil
331
323
}
332
- searchCtx .Ctx = internal .WithWorkspace (ctx , & searchCtx .Workspace )
333
-
334
- // Randomize the vector.
335
- tempRandomized := searchCtx .Workspace .AllocVector (vi .quantizer .GetDims ())
336
- defer searchCtx .Workspace .FreeVector (tempRandomized )
337
- searchCtx .Randomized = vi .randomizeVector (vector , tempRandomized )
338
-
339
- searchSet := vecstore.SearchSet {MaxResults : 1 , MatchKey : key }
340
324
341
- // Search with the base beam size. If that fails to find the vector, try again
342
- // with a larger beam size, in order to minimize the chance of dangling
343
- // vector references in the index.
344
- baseBeamSize := max (vi .options .BaseBeamSize , 1 )
345
- for {
346
- searchCtx .Options .BaseBeamSize = baseBeamSize
347
-
348
- err := vi .searchHelper (& searchCtx , & searchSet )
349
- if err != nil {
350
- return err
351
- }
352
- results := searchSet .PopUnsortedResults ()
353
- if len (results ) == 0 {
354
- // Retry search with significantly higher beam size.
355
- if baseBeamSize == vi .options .BaseBeamSize {
356
- baseBeamSize *= 8
357
- continue
358
- }
359
- return nil
360
- }
361
-
362
- // Remove the vector from its partition in the store.
363
- _ , err = vi .removeFromPartition (ctx , txn , results [0 ].ParentPartitionKey , results [0 ].ChildKey )
364
- if errors .Is (err , vecstore .ErrRestartOperation ) {
365
- // If store requested the operation be retried, then re-run the
366
- // search and delete.
367
- continue
368
- }
369
- return err
370
- }
325
+ // Remove the vector from its partition in the store.
326
+ _ , err = vi .removeFromPartition (ctx , txn , result .ParentPartitionKey , result .ChildKey )
327
+ return err
371
328
}
372
329
373
330
// Search finds vectors in the index that are closest to the given query vector
@@ -438,6 +395,59 @@ func (vi *VectorIndex) SearchForInsert(
438
395
return result , nil
439
396
}
440
397
398
+ // SearchForDelete finds the leaf partition containing the vector to be deleted.
399
+ // It returns a single search result containing the key of that partition, or
400
+ // nil if the vector cannot be found. This is useful for callers that directly
401
+ // delete KV rows rather than using this library to do it.
402
+ func (vi * VectorIndex ) SearchForDelete (
403
+ ctx context.Context , txn vecstore.Txn , vector vector.T , key vecstore.PrimaryKey ,
404
+ ) (* vecstore.SearchResult , error ) {
405
+ // Potentially throttle operation if background work is falling behind.
406
+ if err := vi .fixups .DelayInsertOrDelete (ctx ); err != nil {
407
+ return nil , err
408
+ }
409
+
410
+ searchCtx := searchContext {
411
+ Txn : txn ,
412
+ Original : vector ,
413
+ Level : vecstore .LeafLevel ,
414
+ Options : SearchOptions {
415
+ SkipRerank : vi .options .DisableErrorBounds ,
416
+ UpdateStats : true ,
417
+ },
418
+ }
419
+ searchCtx .Ctx = internal .WithWorkspace (ctx , & searchCtx .Workspace )
420
+
421
+ // Randomize the vector.
422
+ tempRandomized := searchCtx .Workspace .AllocVector (vi .quantizer .GetDims ())
423
+ defer searchCtx .Workspace .FreeVector (tempRandomized )
424
+ searchCtx .Randomized = vi .randomizeVector (vector , tempRandomized )
425
+
426
+ searchCtx .tempSearchSet = vecstore.SearchSet {MaxResults : 1 , MatchKey : key }
427
+
428
+ // Search with the base beam size. If that fails to find the vector, try again
429
+ // with a larger beam size, in order to minimize the chance of dangling
430
+ // vector references in the index.
431
+ baseBeamSize := max (vi .options .BaseBeamSize , 1 )
432
+ for i := 0 ; i < 2 ; i ++ {
433
+ searchCtx .Options .BaseBeamSize = baseBeamSize
434
+
435
+ err := vi .searchHelper (& searchCtx , & searchCtx .tempSearchSet )
436
+ if err != nil {
437
+ return nil , err
438
+ }
439
+ results := searchCtx .tempSearchSet .PopUnsortedResults ()
440
+ if len (results ) == 0 {
441
+ // Retry search with significantly higher beam size.
442
+ baseBeamSize *= 8
443
+ } else {
444
+ return & results [0 ], nil
445
+ }
446
+ }
447
+
448
+ return nil , nil
449
+ }
450
+
441
451
// SuspendFixups suspends background fixup processing until ProcessFixups is
442
452
// explicitly called. It is used for testing.
443
453
func (vi * VectorIndex ) SuspendFixups () {
0 commit comments