1
- use std:: sync:: RwLock ;
1
+ use std:: sync:: atomic :: { AtomicBool , AtomicUsize , Ordering } ;
2
2
use std:: time:: Instant ;
3
3
4
4
use pg_sys:: { FunctionCall0Coll , InvalidOid } ;
@@ -17,6 +17,7 @@ use crate::access_method::DISKANN_DISTANCE_TYPE_PROC;
17
17
use crate :: util:: page:: PageType ;
18
18
use crate :: util:: tape:: Tape ;
19
19
use crate :: util:: * ;
20
+ use crate :: util:: ports:: IndexBuildHeapScanParallel ;
20
21
21
22
use self :: ports:: PROGRESS_CREATE_IDX_SUBPHASE ;
22
23
@@ -68,6 +69,7 @@ struct BuildStateParallel<'a> {
68
69
shared_state : & ' a ParallelShared ,
69
70
local_stats : InsertStats ,
70
71
local_ntuples : usize ,
72
+ is_initializing_worker : bool ,
71
73
}
72
74
73
75
impl < ' a > BuildState < ' a > {
@@ -90,6 +92,7 @@ impl<'a> BuildStateParallel<'a> {
90
92
graph : Graph < ' a > ,
91
93
page_type : PageType ,
92
94
shared_state : & ' a ParallelShared ,
95
+ is_initializing_worker : bool ,
93
96
) -> Self {
94
97
let tape = unsafe { Tape :: new ( index_relation, page_type) } ;
95
98
@@ -100,21 +103,24 @@ impl<'a> BuildStateParallel<'a> {
100
103
shared_state,
101
104
local_stats : InsertStats :: default ( ) ,
102
105
local_ntuples : 0 ,
106
+ is_initializing_worker,
103
107
}
104
108
}
105
109
106
110
fn increment_ntuples ( & mut self ) {
107
111
self . local_ntuples += 1 ;
108
- }
109
-
110
- fn update_shared_stats ( & self ) {
111
- let mut stats_guard = self . shared_state . build_state . stats . write ( ) . unwrap ( ) ;
112
- stats_guard. merge ( & self . local_stats ) ;
112
+ // Only update shared counter for the initializing worker until threshold is reached
113
+ if self . is_initializing_worker && self . local_ntuples <= parallel:: INITIAL_START_NODES_COUNT {
114
+ self . shared_state . build_state . ntuples . fetch_add ( 1 , Ordering :: Relaxed ) ;
115
+ }
113
116
}
114
117
115
118
fn into_build_state ( self ) -> BuildState < ' a > {
116
- // Update shared stats and ntuples one final time
117
- self . update_shared_stats ( ) ;
119
+ // Signal that the initializing worker is done if this is the initializing worker
120
+ if self . is_initializing_worker {
121
+ self . shared_state . build_state . initializing_worker_done . store ( true , Ordering :: Relaxed ) ;
122
+ }
123
+
118
124
self . update_shared_ntuples ( ) ;
119
125
120
126
let ntuples = self . local_ntuples ;
@@ -129,8 +135,16 @@ impl<'a> BuildStateParallel<'a> {
129
135
}
130
136
131
137
fn update_shared_ntuples ( & self ) {
132
- let mut ntuples_guard = self . shared_state . build_state . ntuples . write ( ) . unwrap ( ) ;
133
- * ntuples_guard += self . local_ntuples ;
138
+ if self . is_initializing_worker {
139
+ // For initializing worker, only add tuples beyond the initial threshold to avoid double counting
140
+ let remaining = self . local_ntuples . saturating_sub ( parallel:: INITIAL_START_NODES_COUNT ) ;
141
+ if remaining > 0 {
142
+ self . shared_state . build_state . ntuples . fetch_add ( remaining, Ordering :: Relaxed ) ;
143
+ }
144
+ } else {
145
+ // For non-initializing workers, add all local tuples
146
+ self . shared_state . build_state . ntuples . fetch_add ( self . local_ntuples , Ordering :: Relaxed ) ;
147
+ }
134
148
}
135
149
}
136
150
@@ -155,9 +169,9 @@ struct ParallelSharedParams {
155
169
#[ derive( Debug ) ]
156
170
#[ cfg_attr( not( feature = "build_parallel" ) , allow( dead_code) ) ]
157
171
struct ParallelBuildState {
158
- ntuples : RwLock < usize > ,
159
- started : RwLock < Option < Instant > > ,
160
- stats : RwLock < InsertStats > ,
172
+ ntuples : AtomicUsize ,
173
+ start_nodes_initialized : AtomicBool ,
174
+ initializing_worker_done : AtomicBool ,
161
175
}
162
176
163
177
/// Status data for parallel index builds, shared among all parallel workers.
@@ -173,6 +187,8 @@ struct ParallelShared {
173
187
#[ cfg_attr( not( feature = "build_parallel" ) , allow( dead_code) ) ]
174
188
struct ParallelBuildInfo {
175
189
parallel_shared : * mut ParallelShared ,
190
+ is_initializing_worker : bool ,
191
+ tablescandesc : * mut pg_sys:: ParallelTableScanDescData ,
176
192
}
177
193
178
194
fn get_meta_page (
@@ -298,9 +314,9 @@ pub extern "C" fn ambuild(
298
314
is_concurrent,
299
315
} ,
300
316
build_state : ParallelBuildState {
301
- ntuples : RwLock :: new ( 0 ) ,
302
- started : RwLock :: new ( None ) ,
303
- stats : RwLock :: new ( InsertStats :: default ( ) ) ,
317
+ ntuples : AtomicUsize :: new ( 0 ) ,
318
+ start_nodes_initialized : AtomicBool :: new ( false ) ,
319
+ initializing_worker_done : AtomicBool :: new ( false ) ,
304
320
} ,
305
321
} ) ;
306
322
let tablescandesc =
@@ -340,7 +356,7 @@ pub extern "C" fn ambuild(
340
356
let parallel_shared: * mut ParallelShared =
341
357
pg_sys:: shm_toc_lookup ( ( * pcxt) . toc , parallel:: SHM_TOC_SHARED_KEY , false )
342
358
. cast :: < ParallelShared > ( ) ;
343
- let ntuples = * ( * parallel_shared) . build_state . ntuples . read ( ) . unwrap ( ) ;
359
+ let ntuples = ( * parallel_shared) . build_state . ntuples . load ( Ordering :: Relaxed ) ;
344
360
parallel:: cleanup_pcxt ( pcxt, snapshot) ;
345
361
ntuples
346
362
}
@@ -558,7 +574,7 @@ pub extern "C-unwind" fn _vectorscale_build_main(
558
574
pg_sys:: shm_toc_lookup ( shm_toc, parallel:: SHM_TOC_SHARED_KEY , false )
559
575
. cast :: < ParallelShared > ( )
560
576
} ;
561
- let _tablescandesc = unsafe {
577
+ let tablescandesc = unsafe {
562
578
pg_sys:: shm_toc_lookup ( shm_toc, parallel:: SHM_TOC_TABLESCANDESC_KEY , false )
563
579
. cast :: < pg_sys:: ParallelTableScanDescData > ( )
564
580
} ;
@@ -568,6 +584,27 @@ pub extern "C-unwind" fn _vectorscale_build_main(
568
584
( * parallel_shared) . params
569
585
} ;
570
586
587
+ // Check if this worker should handle the first 1024 nodes for start node initialization
588
+ let should_initialize = unsafe {
589
+ ( * parallel_shared) . build_state . start_nodes_initialized . compare_exchange (
590
+ false ,
591
+ true ,
592
+ Ordering :: SeqCst ,
593
+ Ordering :: SeqCst
594
+ ) . is_ok ( )
595
+ } ;
596
+
597
+ if !should_initialize {
598
+ loop {
599
+ let ntuples = unsafe { ( * parallel_shared) . build_state . ntuples . load ( Ordering :: Relaxed ) } ;
600
+ let init_done = unsafe { ( * parallel_shared) . build_state . initializing_worker_done . load ( Ordering :: Relaxed ) } ;
601
+ if ntuples >= parallel:: INITIAL_START_NODES_COUNT || init_done {
602
+ break ;
603
+ }
604
+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) ;
605
+ }
606
+ }
607
+
571
608
let ( heap_lockmode, index_lockmode) = if params. is_concurrent {
572
609
(
573
610
pg_sys:: ShareLock as pg_sys:: LOCKMODE ,
@@ -593,7 +630,11 @@ pub extern "C-unwind" fn _vectorscale_build_main(
593
630
& index_relation,
594
631
meta_page,
595
632
WriteStats :: default ( ) ,
596
- Some ( ParallelBuildInfo { parallel_shared } ) ,
633
+ Some ( ParallelBuildInfo {
634
+ parallel_shared,
635
+ is_initializing_worker : should_initialize,
636
+ tablescandesc,
637
+ } ) ,
597
638
) ;
598
639
}
599
640
@@ -614,12 +655,8 @@ fn do_heap_scan(
614
655
if let Some ( parallel_info) = parallel_build_info {
615
656
let shared_state = unsafe { & * parallel_info. parallel_shared } ;
616
657
617
- {
618
- let mut started_guard = shared_state. build_state . started . write ( ) . unwrap ( ) ;
619
- if started_guard. is_none ( ) {
620
- * started_guard = Some ( Instant :: now ( ) ) ;
621
- }
622
- }
658
+ // In parallel mode, timing is handled locally by each worker
659
+ // No shared timing state needed across processes
623
660
624
661
let graph = Graph :: new (
625
662
GraphNeighborStore :: Builder ( BuilderNeighborCache :: new (
@@ -638,16 +675,17 @@ fn do_heap_scan(
638
675
) ;
639
676
let page_type = PlainStorage :: page_type ( ) ;
640
677
let mut bs =
641
- BuildStateParallel :: new ( index_relation, graph, page_type, shared_state) ;
678
+ BuildStateParallel :: new ( index_relation, graph, page_type, shared_state, parallel_info . is_initializing_worker ) ;
642
679
let mut state = StorageBuildStateParallel :: Plain ( & mut plain, & mut bs) ;
643
680
644
681
unsafe {
645
- pg_sys :: IndexBuildHeapScan (
682
+ IndexBuildHeapScanParallel (
646
683
heap_relation. as_ptr ( ) ,
647
684
index_relation. as_ptr ( ) ,
648
685
index_info,
649
686
Some ( build_callback_parallel) ,
650
687
& mut state,
688
+ parallel_info. tablescandesc ,
651
689
) ;
652
690
}
653
691
@@ -667,16 +705,17 @@ fn do_heap_scan(
667
705
668
706
let page_type = SbqSpeedupStorage :: page_type ( ) ;
669
707
let mut bs =
670
- BuildStateParallel :: new ( index_relation, graph, page_type, shared_state) ;
708
+ BuildStateParallel :: new ( index_relation, graph, page_type, shared_state, parallel_info . is_initializing_worker ) ;
671
709
let mut state = StorageBuildStateParallel :: SbqSpeedup ( & mut bq, & mut bs) ;
672
710
673
711
unsafe {
674
- pg_sys :: IndexBuildHeapScan (
712
+ IndexBuildHeapScanParallel (
675
713
heap_relation. as_ptr ( ) ,
676
714
index_relation. as_ptr ( ) ,
677
715
index_info,
678
716
Some ( build_callback_parallel) ,
679
717
& mut state,
718
+ parallel_info. tablescandesc ,
680
719
) ;
681
720
}
682
721
0 commit comments