@@ -24,6 +24,7 @@ const DATA_EVOLUTION_ENABLED_OPTION: &str = "data-evolution.enabled";
2424const GLOBAL_INDEX_ENABLED_OPTION : & str = "global-index.enabled" ;
2525const GLOBAL_INDEX_SEARCH_MODE_OPTION : & str = "global-index.search-mode" ;
2626const GLOBAL_INDEX_ROW_COUNT_PER_SHARD_OPTION : & str = "global-index.row-count-per-shard" ;
27+ const GLOBAL_INDEX_THREAD_NUM_OPTION : & str = "global-index.thread-num" ;
2728const GLOBAL_INDEX_COLUMN_UPDATE_ACTION_OPTION : & str = "global-index.column-update-action" ;
2829const SORTED_INDEX_RECORDS_PER_RANGE_OPTION : & str = "sorted-index.records-per-range" ;
2930const BTREE_INDEX_FALLBACK_SCAN_MAX_SIZE_OPTION : & str = "btree-index.fallback-scan-max-size" ;
@@ -114,6 +115,7 @@ const DEFAULT_READ_BATCH_SIZE: usize = 1024;
114115const DYNAMIC_BUCKET_TARGET_ROW_NUM_OPTION : & str = "dynamic-bucket.target-row-num" ;
115116const DEFAULT_DYNAMIC_BUCKET_TARGET_ROW_NUM : i64 = 200_000 ;
116117const DEFAULT_GLOBAL_INDEX_ROW_COUNT_PER_SHARD : i64 = 100_000 ;
118+ const DEFAULT_GLOBAL_INDEX_THREAD_NUM : i64 = 32 ;
117119const DEFAULT_GLOBAL_INDEX_FALLBACK_SCAN_MAX_SIZE : i64 = 256 * 1024 * 1024 ;
118120const BLOB_AS_DESCRIPTOR_OPTION : & str = "blob-as-descriptor" ;
119121pub ( crate ) const BLOB_FIELD_OPTION : & str = "blob-field" ;
@@ -574,6 +576,28 @@ impl<'a> CoreOptions<'a> {
574576 Ok ( value)
575577 }
576578
579+ /// Maximum number of concurrent tasks for global-index I/O, mirroring Java
580+ /// `CoreOptions.GLOBAL_INDEX_THREAD_NUM` (key `global-index.thread-num`,
581+ /// default 32). Used as the fan-out limit for the primary-key vector search
582+ /// (per-bucket and per-exact-file). A value of `1` reproduces strict
583+ /// sequential execution. A non-positive value is a misconfiguration and fails
584+ /// loud rather than being silently clamped.
585+ pub fn global_index_thread_num ( & self ) -> crate :: Result < usize > {
586+ let value = self
587+ . parse_i64_option ( GLOBAL_INDEX_THREAD_NUM_OPTION ) ?
588+ . unwrap_or ( DEFAULT_GLOBAL_INDEX_THREAD_NUM ) ;
589+ if value <= 0 {
590+ return Err ( crate :: Error :: DataInvalid {
591+ message : format ! (
592+ "Option '{}' must be greater than 0, got: {}" ,
593+ GLOBAL_INDEX_THREAD_NUM_OPTION , value
594+ ) ,
595+ source : None ,
596+ } ) ;
597+ }
598+ Ok ( value as usize )
599+ }
600+
577601 pub fn sorted_index_records_per_range ( & self ) -> crate :: Result < i64 > {
578602 let value = self
579603 . parse_i64_option ( SORTED_INDEX_RECORDS_PER_RANGE_OPTION ) ?
@@ -1235,6 +1259,7 @@ mod tests {
12351259 core_options. global_index_row_count_per_shard( ) . unwrap( ) ,
12361260 100_000
12371261 ) ;
1262+ assert_eq ! ( core_options. global_index_thread_num( ) . unwrap( ) , 32 ) ;
12381263 assert_eq ! (
12391264 core_options. sorted_index_records_per_range( ) . unwrap( ) ,
12401265 100_000
@@ -1365,6 +1390,39 @@ mod tests {
13651390 }
13661391 }
13671392
1393+ #[ test]
1394+ fn test_global_index_thread_num_default_and_custom ( ) {
1395+ // Default mirrors Java (32) when unset.
1396+ let empty = HashMap :: new ( ) ;
1397+ let core = CoreOptions :: new ( & empty) ;
1398+ assert_eq ! ( core. global_index_thread_num( ) . unwrap( ) , 32 ) ;
1399+
1400+ // Explicit value is read back verbatim.
1401+ let options =
1402+ HashMap :: from ( [ ( GLOBAL_INDEX_THREAD_NUM_OPTION . to_string ( ) , "8" . to_string ( ) ) ] ) ;
1403+ let core = CoreOptions :: new ( & options) ;
1404+ assert_eq ! ( core. global_index_thread_num( ) . unwrap( ) , 8 ) ;
1405+ }
1406+
1407+ #[ test]
1408+ fn test_global_index_thread_num_rejects_invalid_values ( ) {
1409+ // Non-positive values are a misconfiguration and must fail loud (never
1410+ // clamped to 1); an unparsable value is likewise rejected.
1411+ for value in [ "0" , "-1" , "abc" ] {
1412+ let options = HashMap :: from ( [ (
1413+ GLOBAL_INDEX_THREAD_NUM_OPTION . to_string ( ) ,
1414+ value. to_string ( ) ,
1415+ ) ] ) ;
1416+ let core = CoreOptions :: new ( & options) ;
1417+
1418+ let err = core
1419+ . global_index_thread_num ( )
1420+ . expect_err ( "invalid thread-num should fail" ) ;
1421+ assert ! ( matches!( err, crate :: Error :: DataInvalid { message, .. }
1422+ if message. contains( GLOBAL_INDEX_THREAD_NUM_OPTION ) ) ) ;
1423+ }
1424+ }
1425+
13681426 #[ test]
13691427 fn test_sorted_index_records_per_range_rejects_invalid_values ( ) {
13701428 for value in [ "0" , "-1" , "abc" ] {
0 commit comments