Skip to content

Commit cd2c10c

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 4bfdd7f commit cd2c10c

5 files changed

Lines changed: 186 additions & 53 deletions

File tree

internal/core/src/common/Schema.cpp

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

475-
// Fallback to appropriate collection-level config based on field type
475+
return CollectionWarmupPolicy(is_vector, is_index);
476+
}
477+
478+
std::pair<bool, std::string>
479+
Schema::CollectionWarmupPolicy(bool is_vector, bool is_index) const {
476480
if (is_vector) {
477481
if (is_index) {
478482
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
@@ -627,6 +627,9 @@ class Schema {
627627
std::pair<bool, std::string>
628628
WarmupPolicy(const FieldId& field, bool is_vector, bool is_index) const;
629629

630+
std::pair<bool, std::string>
631+
CollectionWarmupPolicy(bool is_vector, bool is_index) const;
632+
630633
// True if the field carries FieldSchema::is_function_output.
631634
bool
632635
is_function_output(const FieldId& field_id) const {

internal/core/src/segcore/ChunkedSegmentSealedImpl.cpp

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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

889888
namespace {
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+
891911
struct 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+
43874484
bool
43884485
ChunkedSegmentSealedImpl::generate_interim_index(const FieldId field_id,
43894486
int64_t num_rows,
@@ -5026,7 +5123,8 @@ ChunkedSegmentSealedImpl::ApplyLoadDiff(milvus::OpContext* op_ctx,
50265123
}
50275124

50285125
void
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(
50985193
void
50995194
ChunkedSegmentSealedImpl::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;

internal/core/src/segcore/ChunkedSegmentSealedImpl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1225,8 +1225,13 @@ class ChunkedSegmentSealedImpl : public SegmentSealed {
12251225
int64_t num_rows,
12261226
milvus::OpContext* op_ctx);
12271227

1228+
std::string
1229+
resolve_field_data_warmup_policy(
1230+
FieldId field_id, const std::string& explicit_warmup_policy = "");
1231+
12281232
void
1229-
fill_empty_field(const FieldMeta& field_meta);
1233+
fill_empty_field(const FieldMeta& field_meta,
1234+
const std::string& warmup_policy);
12301235

12311236
void
12321237
EnsureArrayOffsetsForStructField(const FieldMeta& field_meta,

0 commit comments

Comments
 (0)