@@ -758,10 +758,9 @@ ChunkedSegmentSealedImpl::LoadColumnGroups(const std::string& manifest_path,
758758 eager_fields.push_back (field_id);
759759 continue ;
760760 }
761- auto [has_warmup, warmup_str] = schema_->WarmupPolicy (
762- field_id, field_is_vector, /* is_index=*/ false );
761+ auto warmup_str = resolve_field_data_warmup_policy (field_id);
763762 // Resolve effective warmup using global config as fallback
764- auto resolved = getCacheWarmupPolicy (has_warmup ? warmup_str : " " ,
763+ auto resolved = getCacheWarmupPolicy (warmup_str,
765764 field_is_vector,
766765 /* is_index=*/ false ,
767766 /* in_load_list=*/ true );
@@ -888,6 +887,27 @@ ChunkedSegmentSealedImpl::SynthesizeExternalSystemFields() {
888887
889888namespace {
890889
890+ // A column group or field-binlog group may contain multiple fields but has only
891+ // one translator warmup policy. Accumulate per-field policies into the most
892+ // aggressive group policy: sync > async > disable. Empty means no field has
893+ // provided an explicit warmup setting yet, so downstream logic may still fall
894+ // back to global config.
895+ void
896+ AccumulateWarmupPolicyForGroup (const std::string& policy,
897+ std::string& aggregated_warmup_policy) {
898+ if (policy.empty ()) {
899+ return ;
900+ }
901+
902+ if (policy == " sync" ) {
903+ aggregated_warmup_policy = " sync" ;
904+ } else if (policy == " async" && aggregated_warmup_policy != " sync" ) {
905+ aggregated_warmup_policy = " async" ;
906+ } else if (policy == " disable" && aggregated_warmup_policy.empty ()) {
907+ aggregated_warmup_policy = " disable" ;
908+ }
909+ }
910+
891911struct FileMetadataLoadResult {
892912 milvus_storage::RowGroupMetadataVector row_group_meta;
893913 // per field_id → per-row-group statistics; nullptr entry means the row
@@ -1080,6 +1100,16 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
10801100 mmap_dir_path);
10811101
10821102 auto field_metas = schema_->get_field_metas (milvus_field_ids);
1103+ std::string warmup_policy = info.warmup_policy ;
1104+ if (warmup_policy.empty ()) {
1105+ std::string aggregated_warmup_policy;
1106+ for (auto field_id : milvus_field_ids) {
1107+ AccumulateWarmupPolicyForGroup (
1108+ resolve_field_data_warmup_policy (field_id),
1109+ aggregated_warmup_policy);
1110+ }
1111+ warmup_policy = aggregated_warmup_policy;
1112+ }
10831113
10841114 std::vector<FieldId> fields_for_stats;
10851115 if (ENABLE_PARQUET_STATS_SKIP_INDEX ) {
@@ -1112,7 +1142,7 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
11121142 mmap_config.GetMmapPopulate (),
11131143 milvus_field_ids.size (),
11141144 load_info.load_priority ,
1115- info. warmup_policy );
1145+ warmup_policy);
11161146 auto chunked_column_group =
11171147 std::make_shared<ChunkedColumnGroup>(std::move (translator));
11181148
@@ -1143,7 +1173,7 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
11431173 init_storage_v1_timestamp_index (std::move (ts), num_rows);
11441174 } else {
11451175 init_storage_v2_timestamp_index (
1146- column, num_rows, info. warmup_policy );
1176+ column, num_rows, warmup_policy);
11471177 }
11481178 }
11491179 }
@@ -1221,6 +1251,8 @@ ChunkedSegmentSealedImpl::load_field_data_internal(
12211251 storage::SortByPath (file_infos);
12221252
12231253 auto field_meta = schema_->operator [](field_id);
1254+ auto warmup_policy =
1255+ resolve_field_data_warmup_policy (field_id, info.warmup_policy );
12241256 std::unique_ptr<Translator<milvus::Chunk>> translator =
12251257 std::make_unique<storagev1translator::ChunkTranslator>(
12261258 this ->get_segment_id (),
@@ -1230,7 +1262,7 @@ ChunkedSegmentSealedImpl::load_field_data_internal(
12301262 info.enable_mmap ,
12311263 mmap_config.GetMmapPopulate (),
12321264 load_info.load_priority ,
1233- info. warmup_policy );
1265+ warmup_policy);
12341266
12351267 auto data_type = field_meta.get_data_type ();
12361268 auto slot = cachinglayer::Manager::GetInstance ().CreateCacheSlot (
@@ -4384,6 +4416,71 @@ ChunkedSegmentSealedImpl::mask_with_timestamps(BitsetTypeView& bitset_chunk,
43844416 bitset_chunk |= mask;
43854417}
43864418
4419+ std::string
4420+ ChunkedSegmentSealedImpl::resolve_field_data_warmup_policy (
4421+ FieldId field_id, const std::string& explicit_warmup_policy) {
4422+ // System fields do not carry user-field warmup settings and are not
4423+ // represented in user-field bitsets. They should not affect group warmup
4424+ // aggregation.
4425+ if (SystemProperty::Instance ().IsSystem (field_id)) {
4426+ return explicit_warmup_policy;
4427+ }
4428+
4429+ auto load_info = std::atomic_load (&segment_load_info_);
4430+
4431+ // "Has index" here means an index is usable now:
4432+ // - SegmentLoadInfo covers DataCoord-built index files loaded with segment
4433+ // metadata.
4434+ // - binlog_index_bitset_ covers interim indexes already generated from raw
4435+ // binlog data.
4436+ // Do not predict whether an interim index may be generated later. Before an
4437+ // index exists, raw vector data is the search-critical representation and
4438+ // follows vector-index warmup.
4439+ bool has_index = load_info != nullptr && load_info->HasIndexInfo (field_id);
4440+ if (!has_index) {
4441+ std::shared_lock lck (mutex_);
4442+ has_index = get_bit (binlog_index_bitset_, field_id);
4443+ }
4444+
4445+ const auto & field_meta = schema_->operator [](field_id);
4446+ auto is_vector = IsVectorDataType (field_meta.get_data_type ());
4447+
4448+ // explicit_warmup_policy is the field-data override carried by the current
4449+ // load request. It is normally authoritative. The only exception is vector
4450+ // raw data without a usable index: QueryCoord may have propagated
4451+ // warmup.vectorField=disable into the field TypeParams, but in this state
4452+ // the raw vector column is effectively the index/search path and must use
4453+ // vector-index warmup instead.
4454+ if (!explicit_warmup_policy.empty () && (!is_vector || has_index)) {
4455+ return explicit_warmup_policy;
4456+ }
4457+
4458+ if (is_vector && !has_index) {
4459+ auto [has_index_warmup, index_warmup_policy] =
4460+ schema_->CollectionWarmupPolicy (/* is_vector=*/ true ,
4461+ /* is_index=*/ true );
4462+ if (has_index_warmup) {
4463+ return index_warmup_policy;
4464+ }
4465+
4466+ switch (milvus::cachinglayer::Manager::GetInstance ()
4467+ .getVectorIndexCacheWarmupPolicy ()) {
4468+ case CacheWarmupPolicy::CacheWarmupPolicy_Sync:
4469+ return " sync" ;
4470+ case CacheWarmupPolicy::CacheWarmupPolicy_Async:
4471+ return " async" ;
4472+ case CacheWarmupPolicy::CacheWarmupPolicy_Disable:
4473+ return " disable" ;
4474+ default :
4475+ return " " ;
4476+ }
4477+ }
4478+
4479+ auto [has_field_warmup, field_warmup_policy] =
4480+ schema_->WarmupPolicy (field_id, is_vector, /* is_index=*/ false );
4481+ return has_field_warmup ? field_warmup_policy : " " ;
4482+ }
4483+
43874484bool
43884485ChunkedSegmentSealedImpl::generate_interim_index (const FieldId field_id,
43894486 int64_t num_rows,
@@ -5026,7 +5123,8 @@ ChunkedSegmentSealedImpl::ApplyLoadDiff(milvus::OpContext* op_ctx,
50265123}
50275124
50285125void
5029- ChunkedSegmentSealedImpl::fill_empty_field (const FieldMeta& field_meta) {
5126+ ChunkedSegmentSealedImpl::fill_empty_field (const FieldMeta& field_meta,
5127+ const std::string& warmup_policy) {
50305128 auto field_id = field_meta.get_id ();
50315129 auto data_type = field_meta.get_data_type ();
50325130 LOG_INFO (
@@ -5050,9 +5148,6 @@ ChunkedSegmentSealedImpl::fill_empty_field(const FieldMeta& field_meta) {
50505148 AssertInfo (size > 0 , " Chunked Sealed segment must have more than 0 row" );
50515149 auto field_data_info = FieldDataInfo (field_id.get (), size, mmap_dir_path);
50525150
5053- auto [field_has_warmup, field_warmup_policy] = schema_->WarmupPolicy (
5054- field_id, IsVectorDataType (data_type), /* is_index=*/ false );
5055- std::string warmup_policy = field_has_warmup ? field_warmup_policy : " " ;
50565151 std::unique_ptr<Translator<milvus::Chunk>> translator =
50575152 std::make_unique<storagev1translator::DefaultValueChunkTranslator>(
50585153 get_segment_id (),
@@ -5098,8 +5193,18 @@ ChunkedSegmentSealedImpl::EnsureArrayOffsetsForStructField(
50985193void
50995194ChunkedSegmentSealedImpl::FillDefaultValueFields (
51005195 const std::vector<FieldId>& field_ids) {
5101- std::unique_lock lck (mutex_);
5196+ // resolve_field_data_warmup_policy() may take mutex_ in shared mode to
5197+ // inspect interim-index state. Resolve policies before this method takes
5198+ // the write lock; field/bitset mutations below stay protected by mutex_.
5199+ std::vector<std::string> warmup_policies;
5200+ warmup_policies.reserve (field_ids.size ());
51025201 for (const auto & field_id : field_ids) {
5202+ warmup_policies.push_back (resolve_field_data_warmup_policy (field_id));
5203+ }
5204+
5205+ std::unique_lock lck (mutex_);
5206+ for (size_t i = 0 ; i < field_ids.size (); ++i) {
5207+ const auto & field_id = field_ids[i];
51035208 // Skip if field data already loaded
51045209 if (get_bit (field_data_ready_bitset_, field_id)) {
51055210 continue ;
@@ -5113,7 +5218,7 @@ ChunkedSegmentSealedImpl::FillDefaultValueFields(
51135218 continue ;
51145219 }
51155220 const auto & field_meta = schema_->operator [](field_id);
5116- fill_empty_field (field_meta);
5221+ fill_empty_field (field_meta, warmup_policies[i] );
51175222 EnsureArrayOffsetsForStructField (field_meta, num_rows_.value_or (0 ));
51185223 }
51195224}
@@ -5342,13 +5447,11 @@ ChunkedSegmentSealedImpl::LoadColumnGroup(
53425447 bool is_vector = false ;
53435448 bool has_mmap_setting = false ;
53445449 bool mmap_enabled = false ;
5345- bool has_warmup_setting = false ;
5346- std::string aggregated_warmup_policy = " disable" ;
5450+ std::string aggregated_warmup_policy;
53475451 for (auto & [field_id, field_meta] : field_metas) {
53485452 if (IsVectorDataType (field_meta.get_data_type ())) {
53495453 is_vector = true ;
53505454 }
5351- std::shared_lock lck (mutex_);
53525455
53535456 // if field has mmap setting, use it
53545457 // - mmap setting at collection level, then all field are the same
@@ -5358,22 +5461,9 @@ ChunkedSegmentSealedImpl::LoadColumnGroup(
53585461 has_mmap_setting = has_mmap_setting || field_has_setting;
53595462 mmap_enabled = mmap_enabled || field_mmap_enabled;
53605463
5361- // if field has warmup setting, use it
5362- // - warmup setting at collection level, uses appropriate key based on field type
5363- // - warmup setting at field level, use the most aggressive policy (sync > async > disable)
5364- // Note: this is for field data loading, not index (is_index = false)
5365- bool field_is_vector = IsVectorDataType (field_meta.get_data_type ());
5366- auto [field_has_warmup, field_warmup_policy] = schema_->WarmupPolicy (
5367- field_id, field_is_vector, /* is_index=*/ false );
5368- if (field_has_warmup) {
5369- has_warmup_setting = true ;
5370- if (field_warmup_policy == " sync" ) {
5371- aggregated_warmup_policy = " sync" ;
5372- } else if (field_warmup_policy == " async" &&
5373- aggregated_warmup_policy != " sync" ) {
5374- aggregated_warmup_policy = " async" ;
5375- }
5376- }
5464+ AccumulateWarmupPolicyForGroup (
5465+ resolve_field_data_warmup_policy (field_id),
5466+ aggregated_warmup_policy);
53775467 }
53785468
53795469 auto & mmap_config = storage::MmapManager::GetInstance ().GetMmapConfig ();
@@ -5411,8 +5501,7 @@ ChunkedSegmentSealedImpl::LoadColumnGroup(
54115501
54125502 // Determine warmup policy: use per-field settings if any,
54135503 // otherwise pass empty string to fall back to global config
5414- std::string warmup_policy =
5415- has_warmup_setting ? aggregated_warmup_policy : " " ;
5504+ std::string warmup_policy = aggregated_warmup_policy;
54165505
54175506 // Multiple lazy entries can share the same column-group index (one per
54185507 // field), so the translator cache key must be disambiguated by the
@@ -5602,8 +5691,7 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
56025691 bool mmap_enabled = false ;
56035692 bool is_vector = false ;
56045693
5605- bool has_warmup_setting = false ;
5606- std::string aggregated_warmup_policy = " disable" ;
5694+ std::string aggregated_warmup_policy;
56075695 for (const auto & child_field_id : fields_to_load) {
56085696 auto & field_meta = schema_->operator [](child_field_id);
56095697 if (IsVectorDataType (field_meta.get_data_type ())) {
@@ -5625,20 +5713,9 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
56255713 index_has_raw_data = false ;
56265714 }
56275715
5628- auto [field_has_warmup, field_warmup_policy] =
5629- schema_->WarmupPolicy (
5630- child_field_id,
5631- IsVectorDataType (field_meta.get_data_type ()),
5632- /* is_index=*/ false );
5633- if (field_has_warmup) {
5634- has_warmup_setting = true ;
5635- if (field_warmup_policy == " sync" ) {
5636- aggregated_warmup_policy = " sync" ;
5637- } else if (field_warmup_policy == " async" &&
5638- aggregated_warmup_policy != " sync" ) {
5639- aggregated_warmup_policy = " async" ;
5640- }
5641- }
5716+ AccumulateWarmupPolicyForGroup (
5717+ resolve_field_data_warmup_policy (child_field_id),
5718+ aggregated_warmup_policy);
56425719 }
56435720
56445721 auto group_id = field_binlog.fieldid ();
@@ -5687,8 +5764,7 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
56875764
56885765 // Determine group warmup policy: use per-field settings if any,
56895766 // otherwise fall back to global warmup policy
5690- field_binlog_info.warmup_policy =
5691- has_warmup_setting ? aggregated_warmup_policy : " " ;
5767+ field_binlog_info.warmup_policy = aggregated_warmup_policy;
56925768
56935769 // Store in map
56945770 load_field_data_info.field_infos [group_id] = field_binlog_info;
0 commit comments