feat: [Tiered] Make load list work as warmup hint (#42490)

Related to #42489
See also #41435

This PR's main target is to make partial load field list work as caching
layer warmup policy hint. If user specify load field list, the fields
not included in the list shall use `disabled` warmup policy and be able
to lazily loaded if any read op uses them.

The major changes are listed here:
- Pass load list to segcore and creating collection&schema
- Add util functions to check field shall be proactively loaded
- Adapt storage v2 column group, which may lead to hint fail if columns
share same group

---------

Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>
This commit is contained in:
congqixia 2025-06-04 10:28:32 +08:00 committed by GitHub
parent fc010e44a8
commit b76478378a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 135 additions and 42 deletions

View File

@ -91,7 +91,7 @@ Schema::ConvertToArrowSchema() const {
} }
std::unique_ptr<std::vector<FieldMeta>> std::unique_ptr<std::vector<FieldMeta>>
Schema::absent_fields(Schema& old_schema) const { Schema::AbsentFields(Schema& old_schema) const {
std::vector<FieldMeta> result; std::vector<FieldMeta> result;
for (const auto& [field_id, field_meta] : fields_) { for (const auto& [field_id, field_meta] : fields_) {
auto it = old_schema.fields_.find(field_id); auto it = old_schema.fields_.find(field_id);

View File

@ -16,10 +16,12 @@
#pragma once #pragma once
#include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -27,6 +29,7 @@
#include "FieldMeta.h" #include "FieldMeta.h"
#include "boost/stacktrace/frame.hpp" #include "boost/stacktrace/frame.hpp"
#include "boost/stacktrace/stacktrace_fwd.hpp" #include "boost/stacktrace/stacktrace_fwd.hpp"
#include "common/Types.h"
#include "pb/schema.pb.h" #include "pb/schema.pb.h"
#include "log/Log.h" #include "log/Log.h"
#include "Consts.h" #include "Consts.h"
@ -238,7 +241,7 @@ class Schema {
} }
const std::unordered_map<FieldId, FieldMeta> const std::unordered_map<FieldId, FieldMeta>
get_field_metas(std::vector<FieldId> field_ids) { get_field_metas(const std::vector<FieldId>& field_ids) {
std::unordered_map<FieldId, FieldMeta> field_metas; std::unordered_map<FieldId, FieldMeta> field_metas;
for (const auto& field_id : field_ids) { for (const auto& field_id : field_ids) {
field_metas.emplace(field_id, operator[](field_id)); field_metas.emplace(field_id, operator[](field_id));
@ -272,6 +275,28 @@ class Schema {
const ArrowSchemaPtr const ArrowSchemaPtr
ConvertToArrowSchema() const; ConvertToArrowSchema() const;
void
UpdateLoadFields(const std::vector<int64_t>& field_ids) {
load_fields_.clear();
for (auto field_id : field_ids) {
load_fields_.emplace(field_id);
}
}
bool
ShallLoadField(FieldId field_id) {
return load_fields_.empty() || load_fields_.count(field_id) > 0;
}
std::vector<int64_t>
load_fields() {
auto fields = std::vector<int64_t>();
for (auto field_id : field_ids_) {
fields.emplace_back(field_id.get());
}
return std::move(fields);
}
public: public:
static std::shared_ptr<Schema> static std::shared_ptr<Schema>
ParseFrom(const milvus::proto::schema::CollectionSchema& schema_proto); ParseFrom(const milvus::proto::schema::CollectionSchema& schema_proto);
@ -290,7 +315,7 @@ class Schema {
} }
std::unique_ptr<std::vector<FieldMeta>> std::unique_ptr<std::vector<FieldMeta>>
absent_fields(Schema& old_schema) const; AbsentFields(Schema& old_schema) const;
private: private:
int64_t debug_id = START_USER_FIELDID; int64_t debug_id = START_USER_FIELDID;
@ -306,6 +331,10 @@ class Schema {
std::optional<FieldId> primary_field_id_opt_; std::optional<FieldId> primary_field_id_opt_;
std::optional<FieldId> dynamic_field_id_opt_; std::optional<FieldId> dynamic_field_id_opt_;
// field partial load list
// work as hint now
std::unordered_set<FieldId> load_fields_;
// schema_version_, currently marked with update timestamp // schema_version_, currently marked with update timestamp
uint64_t schema_version_; uint64_t schema_version_;
}; };

View File

@ -28,7 +28,8 @@ class ChunkedColumnInterface {
// Default implementation does nothing. // Default implementation does nothing.
virtual void virtual void
ManualEvictCache() const {} ManualEvictCache() const {
}
// Get raw data pointer of a specific chunk // Get raw data pointer of a specific chunk
virtual cachinglayer::PinWrapper<const char*> virtual cachinglayer::PinWrapper<const char*>

View File

@ -31,10 +31,12 @@ struct FieldDataInfo {
FieldDataInfo(int64_t field_id, FieldDataInfo(int64_t field_id,
size_t row_count, size_t row_count,
std::string mmap_dir_path = "") std::string mmap_dir_path = "",
bool in_list = false)
: field_id(field_id), : field_id(field_id),
row_count(row_count), row_count(row_count),
mmap_dir_path(std::move(mmap_dir_path)) { mmap_dir_path(std::move(mmap_dir_path)),
in_load_list(in_list) {
arrow_reader_channel = std::make_shared<ArrowReaderChannel>(); arrow_reader_channel = std::make_shared<ArrowReaderChannel>();
} }
@ -42,5 +44,6 @@ struct FieldDataInfo {
size_t row_count; size_t row_count;
std::string mmap_dir_path; std::string mmap_dir_path;
std::shared_ptr<ArrowReaderChannel> arrow_reader_channel; std::shared_ptr<ArrowReaderChannel> arrow_reader_channel;
bool in_load_list = false;
}; };
} // namespace milvus } // namespace milvus

View File

@ -269,12 +269,22 @@ ChunkedSegmentSealedImpl::load_column_group_data_internal(
metadata->GetGroupFieldIDList().GetFieldIDList( metadata->GetGroupFieldIDList().GetFieldIDList(
column_group_id.get()); column_group_id.get());
std::vector<FieldId> milvus_field_ids; std::vector<FieldId> milvus_field_ids;
// if multiple fields share same column group
// hint for not loading certain field shall not be working for now
// warmup will be disabled only when all columns are not in load list
bool merged_in_load_list = false;
for (int i = 0; i < field_id_list.size(); ++i) { for (int i = 0; i < field_id_list.size(); ++i) {
milvus_field_ids.emplace_back(field_id_list.Get(i)); milvus_field_ids.emplace_back(field_id_list.Get(i));
merged_in_load_list =
merged_in_load_list ||
schema_->ShallLoadField(FieldId(field_id_list.Get(i)));
} }
auto column_group_info = FieldDataInfo( auto column_group_info = FieldDataInfo(column_group_id.get(),
column_group_id.get(), num_rows, load_info.mmap_dir_path); num_rows,
load_info.mmap_dir_path,
merged_in_load_list);
LOG_INFO("segment {} loads column group {} with num_rows {}", LOG_INFO("segment {} loads column group {} with num_rows {}",
this->get_segment_id(), this->get_segment_id(),
column_group_id.get(), column_group_id.get(),
@ -321,8 +331,10 @@ ChunkedSegmentSealedImpl::load_field_data_internal(
auto field_id = FieldId(id); auto field_id = FieldId(id);
auto field_data_info = auto field_data_info = FieldDataInfo(field_id.get(),
FieldDataInfo(field_id.get(), num_rows, load_info.mmap_dir_path); num_rows,
load_info.mmap_dir_path,
schema_->ShallLoadField(field_id));
LOG_INFO("segment {} loads field {} with num_rows {}, sorted by pk {}", LOG_INFO("segment {} loads field {} with num_rows {}, sorted by pk {}",
this->get_segment_id(), this->get_segment_id(),
field_id.get(), field_id.get(),
@ -1872,7 +1884,7 @@ ChunkedSegmentSealedImpl::Reopen(SchemaPtr sch) {
index_ready_bitset_.resize(sch->size()); index_ready_bitset_.resize(sch->size());
binlog_index_bitset_.resize(sch->size()); binlog_index_bitset_.resize(sch->size());
auto absent_fields = sch->absent_fields(*schema_); auto absent_fields = sch->AbsentFields(*schema_);
for (const auto& field_meta : *absent_fields) { for (const auto& field_meta : *absent_fields) {
// vector field is not supported to be "added field", thus if a vector // vector field is not supported to be "added field", thus if a vector
// field is absent, it means for some reason we want to skip loading this // field is absent, it means for some reason we want to skip loading this

View File

@ -79,8 +79,14 @@ Collection::parse_schema(const void* schema_proto_blob,
AssertInfo(suc, "parse schema proto failed"); AssertInfo(suc, "parse schema proto failed");
auto old_schema = schema_;
schema_ = Schema::ParseFrom(collection_schema); schema_ = Schema::ParseFrom(collection_schema);
schema_->set_schema_version(version); schema_->set_schema_version(version);
if (old_schema) {
schema_->UpdateLoadFields(old_schema->load_fields());
}
} }
} // namespace milvus::segcore } // namespace milvus::segcore

View File

@ -1197,7 +1197,7 @@ void
SegmentGrowingImpl::Reopen(SchemaPtr sch) { SegmentGrowingImpl::Reopen(SchemaPtr sch) {
std::unique_lock lck(mutex_); std::unique_lock lck(mutex_);
auto absent_fields = sch->absent_fields(*schema_); auto absent_fields = sch->AbsentFields(*schema_);
for (const auto& field_meta : *absent_fields) { for (const auto& field_meta : *absent_fields) {
fill_empty_field(field_meta); fill_empty_field(field_meta);

View File

@ -978,8 +978,12 @@ upper_bound(const ConcurrentVector<Timestamp>& timestamps,
// Get the globally configured cache warmup policy for the given content type. // Get the globally configured cache warmup policy for the given content type.
CacheWarmupPolicy CacheWarmupPolicy
getCacheWarmupPolicy(bool is_vector, bool is_index) { getCacheWarmupPolicy(bool is_vector, bool is_index, bool in_load_list) {
auto& manager = milvus::cachinglayer::Manager::GetInstance(); auto& manager = milvus::cachinglayer::Manager::GetInstance();
// if field not in load list(hint), disable warmup
if (!in_load_list) {
return CacheWarmupPolicy::CacheWarmupPolicy_Disable;
}
if (is_index) { if (is_index) {
return is_vector ? manager.getVectorIndexCacheWarmupPolicy() return is_vector ? manager.getVectorIndexCacheWarmupPolicy()
: manager.getScalarIndexCacheWarmupPolicy(); : manager.getScalarIndexCacheWarmupPolicy();

View File

@ -131,6 +131,6 @@ upper_bound(const ConcurrentVector<Timestamp>& timestamps,
Timestamp value); Timestamp value);
CacheWarmupPolicy CacheWarmupPolicy
getCacheWarmupPolicy(bool is_vector, bool is_index); getCacheWarmupPolicy(bool is_vector, bool is_index, bool in_load_list = true);
} // namespace milvus::segcore } // namespace milvus::segcore

View File

@ -49,6 +49,21 @@ UpdateSchema(CCollection collection,
} }
} }
CStatus
UpdateLoadFields(CCollection collection,
const int64_t* field_ids,
const int64_t length) {
try {
auto col = static_cast<milvus::segcore::Collection*>(collection);
col->get_schema()->UpdateLoadFields(
std::vector<int64_t>(field_ids, field_ids + length));
return milvus::SuccessCStatus();
} catch (std::exception& e) {
return milvus::FailureCStatus(&e);
}
}
CStatus CStatus
SetIndexMeta(CCollection collection, SetIndexMeta(CCollection collection,
const void* proto_blob, const void* proto_blob,

View File

@ -31,6 +31,11 @@ UpdateSchema(CCollection collection,
const int64_t length, const int64_t length,
const uint64_t version); const uint64_t version);
CStatus
UpdateLoadFields(CCollection collection,
const int64_t* field_ids,
const int64_t length);
CStatus CStatus
SetIndexMeta(CCollection collection, SetIndexMeta(CCollection collection,
const void* proto_blob, const void* proto_blob,

View File

@ -44,7 +44,8 @@ ChunkTranslator::ChunkTranslator(
: milvus::cachinglayer::StorageType::MEMORY, : milvus::cachinglayer::StorageType::MEMORY,
milvus::segcore::getCacheWarmupPolicy( milvus::segcore::getCacheWarmupPolicy(
IsVectorDataType(field_meta.get_data_type()), IsVectorDataType(field_meta.get_data_type()),
/* is_index */ false), /* is_index */ false,
/* in_load_list*/ field_data_info.in_load_list),
/* support_eviction */ false) { /* support_eviction */ false) {
AssertInfo(!SystemProperty::Instance().IsSystem(FieldId(field_id_)), AssertInfo(!SystemProperty::Instance().IsSystem(FieldId(field_id_)),
"ChunkTranslator not supported for system field"); "ChunkTranslator not supported for system field");
@ -68,7 +69,7 @@ ChunkTranslator::load_chunk(milvus::cachinglayer::cid_t cid) {
pool.Submit(LoadArrowReaderFromRemote, pool.Submit(LoadArrowReaderFromRemote,
std::vector<std::string>{files_and_rows_[cid].first}, std::vector<std::string>{files_and_rows_[cid].first},
channel); channel);
LOG_DEBUG("segment {} submits load field {} chunk {} task to thread pool", LOG_INFO("segment {} submits load field {} chunk {} task to thread pool",
segment_id_, segment_id_,
field_id_, field_id_,
cid); cid);

View File

@ -30,7 +30,8 @@ DefaultValueChunkTranslator::DefaultValueChunkTranslator(
: milvus::cachinglayer::StorageType::MEMORY, : milvus::cachinglayer::StorageType::MEMORY,
milvus::segcore::getCacheWarmupPolicy( milvus::segcore::getCacheWarmupPolicy(
IsVectorDataType(field_meta.get_data_type()), IsVectorDataType(field_meta.get_data_type()),
/* is_index */ false), /* is_index */ false,
/* in_load_list, set to false to reduce memory usage */ false),
/* support_eviction */ false) { /* support_eviction */ false) {
meta_.num_rows_until_chunk_.push_back(0); meta_.num_rows_until_chunk_.push_back(0);
meta_.num_rows_until_chunk_.push_back(field_data_info.row_count); meta_.num_rows_until_chunk_.push_back(field_data_info.row_count);

View File

@ -62,7 +62,12 @@ InterimSealedIndexTranslator::get_cells(
vec_data_](size_t id) { vec_data_](size_t id) {
const void* data; const void* data;
int64_t data_id = id; int64_t data_id = id;
field_raw_data_ptr->BulkValueAt([&data, &data_id](const char* value, size_t i) {data = static_cast<const void*>(value);}, &data_id, 1); field_raw_data_ptr->BulkValueAt(
[&data, &data_id](const char* value, size_t i) {
data = static_cast<const void*>(value);
},
&data_id,
1);
return data; return data;
}; };
@ -71,28 +76,29 @@ InterimSealedIndexTranslator::get_cells(
index_type_, index_type_,
metric_type_, metric_type_,
knowhere::Version::GetCurrentVersion().VersionNumber(), knowhere::Version::GetCurrentVersion().VersionNumber(),
view_data, false); view_data,
false);
} else if (vec_data_type_ == DataType::VECTOR_FLOAT16) { } else if (vec_data_type_ == DataType::VECTOR_FLOAT16) {
vec_index = vec_index = std::make_unique<index::VectorMemIndex<knowhere::fp16>>(
std::make_unique<index::VectorMemIndex<knowhere::fp16>>(
index_type_, index_type_,
metric_type_, metric_type_,
knowhere::Version::GetCurrentVersion().VersionNumber(), knowhere::Version::GetCurrentVersion().VersionNumber(),
view_data, false); view_data,
} else if (vec_data_type_ == false);
DataType::VECTOR_BFLOAT16) { } else if (vec_data_type_ == DataType::VECTOR_BFLOAT16) {
vec_index = vec_index = std::make_unique<index::VectorMemIndex<knowhere::bf16>>(
std::make_unique<index::VectorMemIndex<knowhere::bf16>>(
index_type_, index_type_,
metric_type_, metric_type_,
knowhere::Version::GetCurrentVersion().VersionNumber(), knowhere::Version::GetCurrentVersion().VersionNumber(),
view_data, false); view_data,
false);
} }
} else { } else {
vec_index = std::make_unique<index::VectorMemIndex<float>>( vec_index = std::make_unique<index::VectorMemIndex<float>>(
index_type_, index_type_,
metric_type_, metric_type_,
knowhere::Version::GetCurrentVersion().VersionNumber(), false); knowhere::Version::GetCurrentVersion().VersionNumber(),
false);
} }
auto num_chunk = vec_data_->num_chunks(); auto num_chunk = vec_data_->num_chunks();

View File

@ -287,6 +287,7 @@ func NewCollection(collectionID int64, schema *schemapb.CollectionSchema, indexM
isGpuIndex := false isGpuIndex := false
req := &segcore.CreateCCollectionRequest{ req := &segcore.CreateCCollectionRequest{
Schema: loadSchema, Schema: loadSchema,
LoadFieldList: loadFieldIDs.Collect(),
} }
if indexMeta != nil && len(indexMeta.GetIndexMetas()) > 0 && indexMeta.GetMaxIndexRowCount() > 0 { if indexMeta != nil && len(indexMeta.GetIndexMetas()) > 0 && indexMeta.GetMaxIndexRowCount() > 0 {
req.IndexMeta = indexMeta req.IndexMeta = indexMeta

View File

@ -24,6 +24,7 @@ type CreateCCollectionRequest struct {
CollectionID int64 CollectionID int64
Schema *schemapb.CollectionSchema Schema *schemapb.CollectionSchema
IndexMeta *segcorepb.CollectionIndexMeta IndexMeta *segcorepb.CollectionIndexMeta
LoadFieldList []int64
} }
// CreateCCollection creates a CCollection from a CreateCCollectionRequest. // CreateCCollection creates a CCollection from a CreateCCollectionRequest.
@ -51,6 +52,14 @@ func CreateCCollection(req *CreateCCollectionRequest) (*CCollection, error) {
return nil, err return nil, err
} }
} }
if req.LoadFieldList != nil {
status = C.UpdateLoadFields(ptr, (*C.int64_t)(unsafe.Pointer(&req.LoadFieldList[0])),
C.int64_t(len(req.LoadFieldList)))
if err := ConsumeCStatusIntoError(&status); err != nil {
C.DeleteCollection(ptr)
return nil, err
}
}
return &CCollection{ return &CCollection{
collectionID: req.CollectionID, collectionID: req.CollectionID,
ptr: ptr, ptr: ptr,