Skip to content

Commit 67bd380

Browse files
committed
fix: warm no-index vector fields as indexes
Segments without a built or interim vector index serve search from raw vector data, but field-data warmup previously followed vector-field policy. With warmup.vectorField=disable and warmup.vectorIndex=sync, those raw vectors could remain lazy even though they were the search-critical representation. Resolve field-data warmup inside segcore using the current segment index state from SegmentLoadInfo and binlog_index_bitset_. Go load paths continue to pass field warmup unchanged; segcore ignores that field-data override only when vector raw data has no usable index and must behave like the vector index/search path. Once a built or interim index exists, raw vector data falls back to normal vector-field semantics. Signed-off-by: Shawn Wang <shawn.wang@zilliz.com>
1 parent 88b4757 commit 67bd380

5 files changed

Lines changed: 134 additions & 33 deletions

File tree

internal/core/src/common/Schema.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,11 @@ Schema::WarmupPolicy(const FieldId& field_id,
244244
return {true, it->second};
245245
}
246246

247-
// Fallback to appropriate collection-level config based on field type
247+
return CollectionWarmupPolicy(is_vector, is_index);
248+
}
249+
250+
std::pair<bool, std::string>
251+
Schema::CollectionWarmupPolicy(bool is_vector, bool is_index) const {
248252
if (is_vector) {
249253
if (is_index) {
250254
return {warmup_vector_index_.has_value(),

internal/core/src/common/Schema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ class Schema {
449449
std::pair<bool, std::string>
450450
WarmupPolicy(const FieldId& field, bool is_vector, bool is_index) const;
451451

452+
std::pair<bool, std::string>
453+
CollectionWarmupPolicy(bool is_vector, bool is_index) const;
454+
452455
private:
453456
int64_t debug_id = START_USER_FIELDID;
454457
std::vector<FieldId> field_ids_;

internal/core/src/segcore/ChunkedSegmentSealedImpl.cpp

Lines changed: 112 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,31 @@ ChunkedSegmentSealedImpl::LoadFieldData(const LoadFieldDataInfo& load_info,
298298
}
299299
}
300300

301+
namespace {
302+
303+
// A column group or field-binlog group may contain multiple fields but has only
304+
// one translator warmup policy. Accumulate per-field policies into the most
305+
// aggressive group policy: sync > async > disable. Empty means no field has
306+
// provided an explicit warmup setting yet, so downstream logic may still fall
307+
// back to global config.
308+
void
309+
AccumulateWarmupPolicyForGroup(const std::string& policy,
310+
std::string& aggregated_warmup_policy) {
311+
if (policy.empty()) {
312+
return;
313+
}
314+
315+
if (policy == "sync") {
316+
aggregated_warmup_policy = "sync";
317+
} else if (policy == "async" && aggregated_warmup_policy != "sync") {
318+
aggregated_warmup_policy = "async";
319+
} else if (policy == "disable" && aggregated_warmup_policy.empty()) {
320+
aggregated_warmup_policy = "disable";
321+
}
322+
}
323+
324+
} // namespace
325+
301326
void
302327
ChunkedSegmentSealedImpl::load_column_group_data_internal(
303328
const LoadFieldDataInfo& load_info, milvus::OpContext* op_ctx) {
@@ -357,6 +382,16 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
357382
mmap_dir_path);
358383

359384
auto field_metas = schema_->get_field_metas(milvus_field_ids);
385+
std::string warmup_policy = info.warmup_policy;
386+
if (warmup_policy.empty()) {
387+
std::string aggregated_warmup_policy;
388+
for (auto field_id : milvus_field_ids) {
389+
AccumulateWarmupPolicyForGroup(
390+
resolve_field_data_warmup_policy(field_id),
391+
aggregated_warmup_policy);
392+
}
393+
warmup_policy = aggregated_warmup_policy;
394+
}
360395

361396
auto translator =
362397
std::make_unique<storagev2translator::GroupChunkTranslator>(
@@ -369,7 +404,7 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
369404
mmap_config.GetMmapPopulate(),
370405
milvus_field_ids.size(),
371406
load_info.load_priority,
372-
info.warmup_policy);
407+
warmup_policy);
373408

374409
auto chunked_column_group =
375410
std::make_shared<ChunkedColumnGroup>(std::move(translator));
@@ -493,6 +528,8 @@ ChunkedSegmentSealedImpl::load_field_data_internal(
493528
storage::SortByPath(file_infos);
494529

495530
auto field_meta = schema_->operator[](field_id);
531+
auto warmup_policy =
532+
resolve_field_data_warmup_policy(field_id, info.warmup_policy);
496533
std::unique_ptr<Translator<milvus::Chunk>> translator =
497534
std::make_unique<storagev1translator::ChunkTranslator>(
498535
this->get_segment_id(),
@@ -502,7 +539,7 @@ ChunkedSegmentSealedImpl::load_field_data_internal(
502539
info.enable_mmap,
503540
mmap_config.GetMmapPopulate(),
504541
load_info.load_priority,
505-
info.warmup_policy);
542+
warmup_policy);
506543

507544
auto data_type = field_meta.get_data_type();
508545
auto slot = cachinglayer::Manager::GetInstance().CreateCacheSlot(
@@ -877,6 +914,69 @@ ChunkedSegmentSealedImpl::mask_with_delete(BitsetTypeView& bitset,
877914
deleted_record_.Query(bitset, ins_barrier, timestamp);
878915
}
879916

917+
std::string
918+
ChunkedSegmentSealedImpl::resolve_field_data_warmup_policy(
919+
FieldId field_id, const std::string& explicit_warmup_policy) {
920+
if (SystemProperty::Instance().IsSystem(field_id)) {
921+
return explicit_warmup_policy;
922+
}
923+
924+
// "Has index" here means an index is usable now:
925+
// - SegmentLoadInfo covers DataCoord-built index files loaded with segment
926+
// metadata.
927+
// - binlog_index_bitset_ covers interim indexes already generated from raw
928+
// binlog data.
929+
// Do not predict whether an interim index may be generated later. Before an
930+
// index exists, raw vector data is the search-critical representation and
931+
// follows vector-index warmup.
932+
bool has_index = false;
933+
{
934+
std::shared_lock lck(mutex_);
935+
has_index = segment_load_info_.HasIndexInfo(field_id);
936+
if (!has_index) {
937+
has_index = get_bit(binlog_index_bitset_, field_id);
938+
}
939+
}
940+
941+
const auto& field_meta = schema_->operator[](field_id);
942+
auto is_vector = IsVectorDataType(field_meta.get_data_type());
943+
944+
// explicit_warmup_policy is the field-data override carried by the current
945+
// load request. It is normally authoritative. The only exception is vector
946+
// raw data without a usable index: QueryCoord may have propagated
947+
// warmup.vectorField=disable into the field TypeParams, but in this state
948+
// the raw vector column is effectively the index/search path and must use
949+
// vector-index warmup instead.
950+
if (!explicit_warmup_policy.empty() && (!is_vector || has_index)) {
951+
return explicit_warmup_policy;
952+
}
953+
954+
if (is_vector && !has_index) {
955+
auto [has_index_warmup, index_warmup_policy] =
956+
schema_->CollectionWarmupPolicy(/*is_vector=*/true,
957+
/*is_index=*/true);
958+
if (has_index_warmup) {
959+
return index_warmup_policy;
960+
}
961+
962+
switch (milvus::cachinglayer::Manager::GetInstance()
963+
.getVectorIndexCacheWarmupPolicy()) {
964+
case CacheWarmupPolicy::CacheWarmupPolicy_Sync:
965+
return "sync";
966+
case CacheWarmupPolicy::CacheWarmupPolicy_Async:
967+
return "async";
968+
case CacheWarmupPolicy::CacheWarmupPolicy_Disable:
969+
return "disable";
970+
default:
971+
return "";
972+
}
973+
}
974+
975+
auto [has_field_warmup, field_warmup_policy] =
976+
schema_->WarmupPolicy(field_id, is_vector, /*is_index=*/false);
977+
return has_field_warmup ? field_warmup_policy : "";
978+
}
979+
880980
void
881981
ChunkedSegmentSealedImpl::vector_search(SearchInfo& search_info,
882982
const void* query_data,
@@ -3133,14 +3233,11 @@ ChunkedSegmentSealedImpl::LoadColumnGroup(
31333233
bool is_vector = false;
31343234
bool has_mmap_setting = false;
31353235
bool mmap_enabled = false;
3136-
bool has_warmup_setting = false;
3137-
bool warmup_sync = false;
3236+
std::string aggregated_warmup_policy;
31383237
for (auto& [field_id, field_meta] : field_metas) {
31393238
if (IsVectorDataType(field_meta.get_data_type())) {
31403239
is_vector = true;
31413240
}
3142-
std::shared_lock lck(mutex_);
3143-
auto iter = index_has_raw_data_.find(field_id);
31443241

31453242
// if field has mmap setting, use it
31463243
// - mmap setting at collection level, then all field are the same
@@ -3150,23 +3247,14 @@ ChunkedSegmentSealedImpl::LoadColumnGroup(
31503247
has_mmap_setting = has_mmap_setting || field_has_setting;
31513248
mmap_enabled = mmap_enabled || field_mmap_enabled;
31523249

3153-
// if field has warmup setting, use it
3154-
// - warmup setting at collection level, uses appropriate key based on field type
3155-
// - warmup setting at field level, use the most aggressive policy (sync > disable)
3156-
// Note: this is for field data loading, not index (is_index = false)
3157-
bool field_is_vector = IsVectorDataType(field_meta.get_data_type());
3158-
auto [field_has_warmup, field_warmup_policy] = schema_->WarmupPolicy(
3159-
field_id, field_is_vector, /*is_index=*/false);
3160-
if (field_has_warmup) {
3161-
has_warmup_setting = true;
3162-
warmup_sync = warmup_sync || (field_warmup_policy == "sync");
3163-
}
3250+
AccumulateWarmupPolicyForGroup(
3251+
resolve_field_data_warmup_policy(field_id),
3252+
aggregated_warmup_policy);
31643253
}
31653254

31663255
// Determine warmup policy: use per-field settings if any,
31673256
// otherwise pass empty string to fall back to global config
3168-
std::string warmup_policy =
3169-
has_warmup_setting ? (warmup_sync ? "sync" : "disable") : "";
3257+
std::string warmup_policy = aggregated_warmup_policy;
31703258

31713259
auto& mmap_config = storage::MmapManager::GetInstance().GetMmapConfig();
31723260
bool global_use_mmap = is_vector ? mmap_config.GetVectorFieldEnableMmap()
@@ -3347,8 +3435,7 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
33473435
bool is_vector = false;
33483436
bool has_nullable_vector = false;
33493437

3350-
bool has_warmup_setting = false;
3351-
bool warmup_sync = false;
3438+
std::string aggregated_warmup_policy;
33523439
for (const auto& child_field_id : field_ids) {
33533440
auto& field_meta = schema_->operator[](child_field_id);
33543441
if (IsVectorDataType(field_meta.get_data_type())) {
@@ -3372,15 +3459,9 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
33723459
index_has_raw_data = false;
33733460
}
33743461

3375-
auto [field_has_warmup, field_warmup_policy] =
3376-
schema_->WarmupPolicy(
3377-
child_field_id,
3378-
IsVectorDataType(field_meta.get_data_type()),
3379-
/*is_index=*/false);
3380-
if (field_has_warmup) {
3381-
has_warmup_setting = true;
3382-
warmup_sync = warmup_sync || (field_warmup_policy == "sync");
3383-
}
3462+
AccumulateWarmupPolicyForGroup(
3463+
resolve_field_data_warmup_policy(child_field_id),
3464+
aggregated_warmup_policy);
33843465
}
33853466

33863467
auto group_id = field_binlog.fieldid();
@@ -3423,8 +3504,7 @@ ChunkedSegmentSealedImpl::LoadBatchFieldData(
34233504

34243505
// Determine group warmup policy: use per-field settings if any,
34253506
// otherwise fall back to global warmup policy
3426-
field_binlog_info.warmup_policy =
3427-
has_warmup_setting ? (warmup_sync ? "sync" : "disable") : "";
3507+
field_binlog_info.warmup_policy = aggregated_warmup_policy;
34283508

34293509
// Store in map
34303510
load_field_data_info.field_infos[group_id] = field_binlog_info;

internal/core/src/segcore/ChunkedSegmentSealedImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,10 @@ class ChunkedSegmentSealedImpl : public SegmentSealed {
973973
int64_t num_rows,
974974
milvus::OpContext* op_ctx);
975975

976+
std::string
977+
resolve_field_data_warmup_policy(
978+
FieldId field_id, const std::string& explicit_warmup_policy = "");
979+
976980
void
977981
fill_empty_field(const FieldMeta& field_meta);
978982

internal/querynodev2/segments/utils_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ func TestGetFieldWarmupPolicy(t *testing.T) {
228228
assert.Equal(t, common.WarmupDisable, policy)
229229
})
230230

231+
t.Run("vector field TypeParams warmup is returned", func(t *testing.T) {
232+
policy := getFieldWarmupPolicy(&schemapb.FieldSchema{
233+
DataType: schemapb.DataType_FloatVector,
234+
TypeParams: []*commonpb.KeyValuePair{
235+
{Key: common.WarmupKey, Value: common.WarmupDisable},
236+
},
237+
})
238+
assert.Equal(t, common.WarmupDisable, policy)
239+
})
240+
231241
t.Run("fallback to global config for scalar field", func(t *testing.T) {
232242
paramtable.Get().Save(paramtable.Get().QueryNodeCfg.TieredWarmupScalarField.Key, common.WarmupSync)
233243
defer paramtable.Get().Reset(paramtable.Get().QueryNodeCfg.TieredWarmupScalarField.Key)

0 commit comments

Comments
 (0)