@@ -222,6 +222,7 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
222
222
. await ?;
223
223
let job = IndexBuild {
224
224
index_name : name. clone ( ) ,
225
+ index_id : index_id. internal_id ( ) ,
225
226
by_id : by_id_metadata. id ( ) . internal_id ( ) ,
226
227
developer_config : developer_config. clone ( ) ,
227
228
metadata_id : index_id,
@@ -272,17 +273,17 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
272
273
let index_path = TempDir :: new ( ) ?;
273
274
let mut tx = self . database . begin ( Identity :: system ( ) ) . await ?;
274
275
let table_id = tx. table_mapping ( ) . inject_table_number ( ) ( * job. index_name . table ( ) ) ?;
275
- let mut snapshot_ts = tx. begin_timestamp ( ) ;
276
+ let mut new_ts = tx. begin_timestamp ( ) ;
276
277
let ( previous_segments, build_type) = match job. on_disk_state {
277
278
VectorIndexState :: Backfilling ( ref backfill_state) => {
278
279
let backfill_snapshot_ts = backfill_state
279
280
. backfill_snapshot_ts
280
- . map ( |ts| snapshot_ts . prior_ts ( ts) )
281
+ . map ( |ts| new_ts . prior_ts ( ts) )
281
282
. transpose ( ) ?
282
- . unwrap_or ( snapshot_ts ) ;
283
+ . unwrap_or ( new_ts ) ;
283
284
// For backfilling indexes, the snapshot timestamp we return is the backfill
284
285
// snapshot timestamp
285
- snapshot_ts = backfill_snapshot_ts;
286
+ new_ts = backfill_snapshot_ts;
286
287
287
288
let cursor = backfill_state. cursor ;
288
289
@@ -303,13 +304,18 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
303
304
vec ! [ ] ,
304
305
MultipartBuildType :: IncrementalComplete {
305
306
cursor : None ,
306
- backfill_snapshot_ts : snapshot_ts ,
307
+ backfill_snapshot_ts : new_ts ,
307
308
} ,
308
309
) ,
309
- VectorIndexSnapshotData :: MultiSegment ( ref parts) => (
310
- parts. clone ( ) ,
311
- MultipartBuildType :: Partial ( snapshot_ts. prior_ts ( snapshot. ts ) ?) ,
312
- ) ,
310
+ VectorIndexSnapshotData :: MultiSegment ( ref parts) => {
311
+ let ts = IndexWorkerMetadataModel :: new ( & mut tx)
312
+ . get_fast_forward_ts ( snapshot. ts , job. index_id )
313
+ . await ?;
314
+ (
315
+ parts. clone ( ) ,
316
+ MultipartBuildType :: Partial ( new_ts. prior_ts ( ts) ?) ,
317
+ )
318
+ } ,
313
319
}
314
320
} ,
315
321
} ;
@@ -319,13 +325,7 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
319
325
updated_previous_segments,
320
326
backfill_result,
321
327
} = self
322
- . build_multipart_segment_in_dir (
323
- job,
324
- & index_path,
325
- snapshot_ts,
326
- build_type,
327
- previous_segments,
328
- )
328
+ . build_multipart_segment_in_dir ( job, & index_path, new_ts, build_type, previous_segments)
329
329
. await ?;
330
330
331
331
let new_segment = if let Some ( new_segment) = new_segment {
@@ -358,7 +358,7 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
358
358
let data = VectorIndexSnapshotData :: MultiSegment ( new_and_updated_parts) ;
359
359
360
360
Ok ( IndexBuildResult {
361
- snapshot_ts : * snapshot_ts ,
361
+ snapshot_ts : * new_ts ,
362
362
data,
363
363
total_vectors,
364
364
vectors_in_new_segment,
@@ -565,6 +565,7 @@ impl<RT: Runtime> VectorIndexFlusher<RT> {
565
565
let worker = Self :: new ( runtime, database, storage, writer) ;
566
566
let job = IndexBuild {
567
567
index_name,
568
+ index_id : metadata. clone ( ) . into_id_and_value ( ) . 0 . internal_id ( ) ,
568
569
by_id : by_id_metadata. id ( ) . internal_id ( ) ,
569
570
developer_config : developer_config. clone ( ) ,
570
571
metadata_id : metadata. clone ( ) . id ( ) ,
@@ -679,6 +680,11 @@ mod tests {
679
680
MULTI_SEGMENT_FULL_SCAN_THRESHOLD_KB ,
680
681
VECTOR_INDEX_SIZE_SOFT_LIMIT ,
681
682
} ,
683
+ runtime:: Runtime ,
684
+ types:: {
685
+ IndexId ,
686
+ IndexName ,
687
+ } ,
682
688
} ;
683
689
use keybroker:: Identity ;
684
690
use maplit:: {
@@ -697,12 +703,14 @@ mod tests {
697
703
TableIdAndTableNumber ,
698
704
} ;
699
705
use vector:: {
706
+ PublicVectorSearchQueryResult ,
700
707
QdrantExternalId ,
701
708
VectorSearch ,
702
709
} ;
703
710
704
711
use super :: VectorIndexFlusher ;
705
712
use crate :: {
713
+ bootstrap_model:: index_workers:: IndexWorkerMetadataModel ,
706
714
test_helpers:: new_test_database,
707
715
tests:: vector_test_utils:: {
708
716
add_document_vec,
@@ -714,6 +722,7 @@ mod tests {
714
722
vector_index_worker:: compactor:: CompactionConfig ,
715
723
Database ,
716
724
IndexModel ,
725
+ SystemMetadataModel ,
717
726
UserFacingModel ,
718
727
} ;
719
728
@@ -1489,22 +1498,109 @@ mod tests {
1489
1498
let ( metrics, _) = worker. step ( ) . await ?;
1490
1499
assert_eq ! ( metrics, btreemap! { resolved_index_name. clone( ) => 1 } ) ;
1491
1500
1492
- let ( results, _usage_stats) = fixtures
1493
- . db
1494
- . vector_search (
1495
- Identity :: system ( ) ,
1496
- VectorSearch {
1497
- index_name : index_name. clone ( ) ,
1498
- vector : vector. into_iter ( ) . map ( |value| value as f32 ) . collect ( ) ,
1499
- limit : Some ( 1 ) ,
1500
- expressions : btreeset ! [ ] ,
1501
- } ,
1502
- )
1503
- . await ?;
1504
-
1501
+ let results = vector_search ( & fixtures. db , index_name. clone ( ) , vector) . await ?;
1505
1502
assert_eq ! ( results. first( ) . unwrap( ) . id. internal_id( ) , id. internal_id( ) ) ;
1506
1503
}
1507
1504
1508
1505
Ok ( ( ) )
1509
1506
}
1507
+
1508
+ async fn set_fast_forward_time_to_now < RT : Runtime > (
1509
+ db : & Database < RT > ,
1510
+ index_id : IndexId ,
1511
+ ) -> anyhow:: Result < ( ) > {
1512
+ let mut tx = db. begin_system ( ) . await ?;
1513
+ let metadata = IndexWorkerMetadataModel :: new ( & mut tx)
1514
+ . get_or_create_vector_search ( index_id)
1515
+ . await ?;
1516
+ let ( worker_meta_doc_id, mut metadata) = metadata. into_id_and_value ( ) ;
1517
+ * metadata. index_metadata . mut_fast_forward_ts ( ) = * tx. begin_timestamp ( ) ;
1518
+ SystemMetadataModel :: new ( & mut tx)
1519
+ . replace ( worker_meta_doc_id, metadata. try_into ( ) ?)
1520
+ . await ?;
1521
+ db. commit ( tx) . await ?;
1522
+ Ok ( ( ) )
1523
+ }
1524
+
1525
+ #[ convex_macro:: test_runtime]
1526
+ async fn multi_segment_with_newer_fast_forward_time_builds_from_fast_forward_time (
1527
+ rt : TestRuntime ,
1528
+ ) -> anyhow:: Result < ( ) > {
1529
+ let fixtures = VectorFixtures :: new ( rt. clone ( ) ) . await ?;
1530
+
1531
+ let IndexData {
1532
+ index_name,
1533
+ index_id,
1534
+ resolved_index_name,
1535
+ } = fixtures. enabled_vector_index ( ) . await ?;
1536
+
1537
+ let vector = [ 8f64 , 9f64 ] ;
1538
+ fixtures
1539
+ . add_document_vec_array ( index_name. table ( ) , vector)
1540
+ . await ?;
1541
+
1542
+ set_fast_forward_time_to_now ( & fixtures. db , index_id. internal_id ( ) ) . await ?;
1543
+
1544
+ let mut worker = fixtures. new_index_flusher_with_full_scan_threshold ( 0 ) ?;
1545
+ let ( metrics, _) = worker. step ( ) . await ?;
1546
+ assert_eq ! ( metrics, btreemap! { resolved_index_name. clone( ) => 0 } ) ;
1547
+
1548
+ let results = vector_search ( & fixtures. db , index_name, vector) . await ?;
1549
+
1550
+ assert ! ( results. is_empty( ) ) ;
1551
+
1552
+ Ok ( ( ) )
1553
+ }
1554
+
1555
+ #[ convex_macro:: test_runtime]
1556
+ async fn multi_segment_with_older_fast_forward_time_builds_from_index_time (
1557
+ rt : TestRuntime ,
1558
+ ) -> anyhow:: Result < ( ) > {
1559
+ let fixtures = VectorFixtures :: new ( rt. clone ( ) ) . await ?;
1560
+
1561
+ let IndexData {
1562
+ index_name,
1563
+ index_id,
1564
+ resolved_index_name,
1565
+ } = fixtures. enabled_vector_index ( ) . await ?;
1566
+
1567
+ set_fast_forward_time_to_now ( & fixtures. db , index_id. internal_id ( ) ) . await ?;
1568
+
1569
+ let vector = [ 8f64 , 9f64 ] ;
1570
+ let vector_doc_id = fixtures
1571
+ . add_document_vec_array ( index_name. table ( ) , vector)
1572
+ . await ?;
1573
+
1574
+ let mut worker = fixtures. new_index_flusher_with_full_scan_threshold ( 0 ) ?;
1575
+ let ( metrics, _) = worker. step ( ) . await ?;
1576
+ assert_eq ! ( metrics, btreemap! { resolved_index_name. clone( ) => 1 } ) ;
1577
+
1578
+ let results = vector_search ( & fixtures. db , index_name, vector) . await ?;
1579
+
1580
+ assert_eq ! (
1581
+ results. first( ) . unwrap( ) . id. internal_id( ) ,
1582
+ vector_doc_id. internal_id( )
1583
+ ) ;
1584
+
1585
+ Ok ( ( ) )
1586
+ }
1587
+
1588
+ async fn vector_search < RT : Runtime > (
1589
+ db : & Database < RT > ,
1590
+ index_name : IndexName ,
1591
+ vector : [ f64 ; 2 ] ,
1592
+ ) -> anyhow:: Result < Vec < PublicVectorSearchQueryResult > > {
1593
+ Ok ( db
1594
+ . vector_search (
1595
+ Identity :: system ( ) ,
1596
+ VectorSearch {
1597
+ index_name,
1598
+ vector : vector. into_iter ( ) . map ( |value| value as f32 ) . collect ( ) ,
1599
+ limit : Some ( 1 ) ,
1600
+ expressions : btreeset ! [ ] ,
1601
+ } ,
1602
+ )
1603
+ . await ?
1604
+ . 0 )
1605
+ }
1510
1606
}
0 commit comments