// Licensed to the LF AI & Data foundation under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #include #include #include #include #include #include #include "cachinglayer/CacheSlot.h" #include "cachinglayer/Manager.h" #include "cachinglayer/Translator.h" #include "cachinglayer/Utils.h" #include "common/Chunk.h" #include "common/GroupChunk.h" #include "common/EasyAssert.h" #include "common/OpContext.h" #include "common/Span.h" #include "mmap/ChunkedColumnInterface.h" #include "segcore/storagev2translator/GroupCTMeta.h" namespace milvus { using GroupChunkVector = std::vector>; using namespace milvus::cachinglayer; // ChunkedColumnGroup represents a collection of group chunks class ChunkedColumnGroup { public: explicit ChunkedColumnGroup( std::unique_ptr> translator) : slot_(Manager::GetInstance().CreateCacheSlot(std::move(translator))) { num_chunks_ = slot_->num_cells(); num_rows_ = GetNumRowsUntilChunk().back(); } virtual ~ChunkedColumnGroup() = default; void ManualEvictCache() const { slot_->ManualEvictAll(); } // Get the number of group chunks size_t num_chunks() const { return num_chunks_; } PinWrapper GetGroupChunk(milvus::OpContext* op_ctx, int64_t chunk_id) const { auto ca = SemiInlineGet(slot_->PinCells(op_ctx, {chunk_id})); auto chunk = ca->get_cell_of(chunk_id); return PinWrapper(ca, chunk); } std::shared_ptr> GetGroupChunks(milvus::OpContext* op_ctx, const std::vector& chunk_ids) { return SemiInlineGet(slot_->PinCells(op_ctx, chunk_ids)); } std::vector> GetAllGroupChunks(milvus::OpContext* op_ctx) { auto ca = SemiInlineGet(slot_->PinAllCells(op_ctx)); std::vector> ret; ret.reserve(num_chunks_); for (size_t i = 0; i < num_chunks_; i++) { auto chunk = ca->get_cell_of(i); ret.emplace_back(ca, chunk); } return ret; } int64_t NumRows() const { return num_rows_; } int64_t GetNumRowsUntilChunk(int64_t chunk_id) const { AssertInfo( chunk_id >= 0 && chunk_id <= num_chunks_, "[StorageV2] chunk_id out of range: " + std::to_string(chunk_id)); return GetNumRowsUntilChunk()[chunk_id]; } const std::vector& GetNumRowsUntilChunk() const { auto meta = static_cast( slot_->meta()); return meta->num_rows_until_chunk_; } std::pair GetChunkIDByOffset(int64_t offset) const { const auto& num_rows_until_chunk = GetNumRowsUntilChunk(); auto iter = std::lower_bound(num_rows_until_chunk.begin(), num_rows_until_chunk.end(), offset + 1); size_t chunk_idx = std::distance(num_rows_until_chunk.begin(), iter) - 1; size_t offset_in_chunk = offset - num_rows_until_chunk[chunk_idx]; return {chunk_idx, offset_in_chunk}; } std::pair, std::vector> GetChunkIDsByOffsets(const int64_t* offsets, int64_t count) { const auto& num_rows_until_chunk = GetNumRowsUntilChunk(); std::vector cids(count, 1); std::vector offsets_in_chunk(count); int64_t len = num_rows_until_chunk.size() - 1; while (len > 1) { const int64_t half = len / 2; len -= half; for (size_t i = 0; i < count; ++i) { const bool cmp = num_rows_until_chunk[cids[i] + half - 1] < offsets[i] + 1; cids[i] += static_cast(cmp) * half; } } for (size_t i = 0; i < count; ++i) { offsets_in_chunk[i] = offsets[i] - num_rows_until_chunk[--cids[i]]; } return std::make_pair(std::move(cids), std::move(offsets_in_chunk)); } size_t NumFieldsInGroup() const { auto meta = static_cast( slot_->meta()); return meta->num_fields_; } size_t memory_size() const { auto meta = static_cast( slot_->meta()); size_t memory_size = 0; for (auto& size : meta->chunk_memory_size_) { memory_size += size; } return memory_size; } protected: mutable std::shared_ptr> slot_; size_t num_chunks_{0}; size_t num_rows_{0}; }; class ProxyChunkColumn : public ChunkedColumnInterface { public: explicit ProxyChunkColumn(std::shared_ptr group, FieldId field_id, const FieldMeta& field_meta) : group_(group), field_id_(field_id), field_meta_(field_meta), data_type_(field_meta.get_data_type()) { } void ManualEvictCache() const override { if (group_->NumFieldsInGroup() == 1) { group_->ManualEvictCache(); } } PinWrapper DataOfChunk(milvus::OpContext* op_ctx, int chunk_id) const override { auto group_chunk = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = group_chunk.get()->GetChunk(field_id_); return PinWrapper(group_chunk, chunk->Data()); } bool IsValid(milvus::OpContext* op_ctx, size_t offset) const override { auto [chunk_id, offset_in_chunk] = group_->GetChunkIDByOffset(offset); auto group_chunk = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = group_chunk.get()->GetChunk(field_id_); return chunk->isValid(offset_in_chunk); } void BulkIsValid(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets = nullptr, int64_t count = 0) const override { if (!field_meta_.is_nullable()) { if (offsets == nullptr) { for (int64_t i = 0; i < group_->NumRows(); i++) { fn(true, i); } } else { for (int64_t i = 0; i < count; i++) { fn(true, i); } } } // nullable: if (count == 0) { return; } auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto valid = chunk->isValid(offsets_in_chunk[i]); fn(valid, i); } } bool IsNullable() const override { return field_meta_.is_nullable(); } size_t NumRows() const override { return group_->NumRows(); } int64_t num_chunks() const override { return group_->num_chunks(); } size_t DataByteSize() const override { return group_->memory_size(); } int64_t chunk_row_nums(int64_t chunk_id) const override { return group_->GetNumRowsUntilChunk(chunk_id + 1) - group_->GetNumRowsUntilChunk(chunk_id); } // TODO(tiered storage): make it async void PrefetchChunks(milvus::OpContext* op_ctx, const std::vector& chunk_ids) const override { group_->GetGroupChunks(op_ctx, chunk_ids); } PinWrapper Span(milvus::OpContext* op_ctx, int64_t chunk_id) const override { if (!IsChunkedColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] Span only supported for ChunkedColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper( chunk_wrapper, static_cast(chunk.get())->Span()); } PinWrapper, FixedVector>> StringViews(milvus::OpContext* op_ctx, int64_t chunk_id, std::optional> offset_len = std::nullopt) const override { if (!IsChunkedVariableColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] StringViews only supported for " "ChunkedVariableColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper< std::pair, FixedVector>>( chunk_wrapper, static_cast(chunk.get())->StringViews(offset_len)); } PinWrapper, FixedVector>> ArrayViews(milvus::OpContext* op_ctx, int64_t chunk_id, std::optional> offset_len = std::nullopt) const override { if (!IsChunkedArrayColumnDataType(data_type_)) { ThrowInfo( ErrorCode::Unsupported, "[StorageV2] ArrayViews only supported for ChunkedArrayColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper, FixedVector>>( chunk_wrapper, static_cast(chunk.get())->Views(offset_len)); } PinWrapper, FixedVector>> VectorArrayViews(milvus::OpContext* op_ctx, int64_t chunk_id, std::optional> offset_len = std::nullopt) const override { if (!IsChunkedVectorArrayColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] VectorArrayViews only supported for " "ChunkedVectorArrayColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper< std::pair, FixedVector>>( chunk_wrapper, static_cast(chunk.get())->Views(offset_len)); } PinWrapper VectorArrayOffsets(milvus::OpContext* op_ctx, int64_t chunk_id) const override { if (!IsChunkedVectorArrayColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "VectorArrayOffsets only supported for " "ChunkedVectorArrayColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper( chunk_wrapper, static_cast(chunk.get())->Offsets()); } PinWrapper, FixedVector>> StringViewsByOffsets(milvus::OpContext* op_ctx, int64_t chunk_id, const FixedVector& offsets) const override { if (!IsChunkedVariableColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] ViewsByOffsets only supported for " "ChunkedVariableColumn"); } auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper< std::pair, FixedVector>>( chunk_wrapper, static_cast(chunk.get())->ViewsByOffsets(offsets)); } PinWrapper, FixedVector>> ArrayViewsByOffsets(milvus::OpContext* op_ctx, int64_t chunk_id, const FixedVector& offsets) const override { auto chunk_wrapper = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = chunk_wrapper.get()->GetChunk(field_id_); return PinWrapper, FixedVector>>( chunk_wrapper, static_cast(chunk.get())->ViewsByOffsets(offsets)); } std::pair GetChunkIDByOffset(int64_t offset) const override { return group_->GetChunkIDByOffset(offset); } std::pair, std::vector> GetChunkIDsByOffsets(const int64_t* offsets, int64_t count) const override { return group_->GetChunkIDsByOffsets(offsets, count); } PinWrapper GetChunk(milvus::OpContext* op_ctx, int64_t chunk_id) const override { auto group_chunk = group_->GetGroupChunk(op_ctx, chunk_id); auto chunk = group_chunk.get()->GetChunk(field_id_); return PinWrapper(group_chunk, chunk.get()); } std::vector> GetAllChunks(milvus::OpContext* op_ctx) const override { std::vector> ret; auto group_chunks = group_->GetAllGroupChunks(op_ctx); ret.reserve(group_chunks.size()); for (auto& group_chunk : group_chunks) { auto chunk = group_chunk.get()->GetChunk(field_id_); ret.emplace_back(group_chunk, chunk.get()); } return ret; } int64_t GetNumRowsUntilChunk(int64_t chunk_id) const override { return group_->GetNumRowsUntilChunk(chunk_id); } const std::vector& GetNumRowsUntilChunk() const override { return group_->GetNumRowsUntilChunk(); } void BulkValueAt(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets, int64_t count) override { auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); fn(chunk->ValueAt(offsets_in_chunk[i]), i); } } template void BulkPrimitiveValueAtImpl(milvus::OpContext* op_ctx, void* dst, const int64_t* offsets, int64_t count) { static_assert(std::is_fundamental_v && std::is_fundamental_v); auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); auto typed_dst = static_cast(dst); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto value = chunk->ValueAt(offsets_in_chunk[i]); typed_dst[i] = *static_cast(static_cast(value)); } } void BulkPrimitiveValueAt(milvus::OpContext* op_ctx, void* dst, const int64_t* offsets, int64_t count) override { switch (data_type_) { case DataType::INT8: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::INT16: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::INT32: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::INT64: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::TIMESTAMPTZ: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::FLOAT: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::DOUBLE: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } case DataType::BOOL: { BulkPrimitiveValueAtImpl( op_ctx, dst, offsets, count); break; } default: { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] BulkScalarValueAt is not supported for " "unknown scalar " "data type: {}", data_type_); } } } void BulkVectorValueAt(milvus::OpContext* op_ctx, void* dst, const int64_t* offsets, int64_t element_sizeof, int64_t count) override { auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); auto dst_vec = reinterpret_cast(dst); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto value = chunk->ValueAt(offsets_in_chunk[i]); memcpy(dst_vec + i * element_sizeof, value, element_sizeof); } } void BulkRawStringAt(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets = nullptr, int64_t count = 0) const override { if (!IsChunkedVariableColumnDataType(data_type_) || data_type_ == DataType::JSON) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] BulkRawStringAt only supported for " "ProxyChunkColumn of " "variable length type(except Json)"); } if (offsets == nullptr) { int64_t current_offset = 0; for (cid_t cid = 0; cid < num_chunks(); ++cid) { auto group_chunk = group_->GetGroupChunk(op_ctx, cid); auto chunk = group_chunk.get()->GetChunk(field_id_); auto chunk_rows = chunk->RowNums(); for (int64_t i = 0; i < chunk_rows; ++i) { auto valid = chunk->isValid(i); auto value = static_cast(chunk.get())->operator[](i); fn(value, current_offset + i, valid); } current_offset += chunk_rows; } } else { auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto valid = chunk->isValid(offsets_in_chunk[i]); auto value = static_cast(chunk.get()) -> operator[](offsets_in_chunk[i]); fn(value, i, valid); } } } // TODO(tiered storage 2): replace with Bulk version void BulkRawJsonAt(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets, int64_t count) const override { if (data_type_ != DataType::JSON) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] RawJsonAt only supported for " "ProxyChunkColumn of Json type"); } if (count == 0) { return; } auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto valid = chunk->isValid(offsets_in_chunk[i]); auto str_view = static_cast(chunk.get()) -> operator[](offsets_in_chunk[i]); fn(Json(str_view.data(), str_view.size()), i, valid); } } void BulkRawBsonAt(milvus::OpContext* op_ctx, std::function fn, const uint32_t* row_offsets, const uint32_t* value_offsets, int64_t count) const override { if (data_type_ != DataType::STRING) { ThrowInfo(ErrorCode::Unsupported, "BulkRawBsonAt only supported for ProxyChunkColumn of " "Bson type"); } if (count == 0) { return; } AssertInfo(row_offsets != nullptr, "row_offsets is nullptr"); auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(row_offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto str_view = static_cast(chunk.get()) -> operator[](offsets_in_chunk[i]); fn(BsonView(reinterpret_cast(str_view.data()), str_view.size()), row_offsets[i], value_offsets[i]); } } void BulkArrayAt(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets, int64_t count) const override { if (!IsChunkedArrayColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] BulkArrayAt only supported for " "ChunkedArrayColumn"); } auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto view = static_cast(chunk.get()) ->View(offsets_in_chunk[i]); fn(view, i); } } void BulkVectorArrayAt(milvus::OpContext* op_ctx, std::function fn, const int64_t* offsets, int64_t count) const override { if (!IsChunkedVectorArrayColumnDataType(data_type_)) { ThrowInfo(ErrorCode::Unsupported, "[StorageV2] BulkVectorArrayAt only supported for " "ChunkedVectorArrayColumn"); } auto [cids, offsets_in_chunk] = ToChunkIdAndOffset(offsets, count); auto ca = group_->GetGroupChunks(op_ctx, cids); for (int64_t i = 0; i < count; i++) { auto* group_chunk = ca->get_cell_of(cids[i]); auto chunk = group_chunk->GetChunk(field_id_); auto array = static_cast(chunk.get()) ->View(offsets_in_chunk[i]) .output_data(); fn(std::move(array), i); } } void BuildValidRowIds(milvus::OpContext* op_ctx) override { if (!field_meta_.is_nullable()) { return; } auto total_rows = NumRows(); auto total_chunks = num_chunks(); valid_data_.resize(total_rows); valid_count_per_chunk_.resize(total_chunks); int64_t logical_offset = 0; for (int64_t i = 0; i < total_chunks; i++) { auto group_chunk = group_->GetGroupChunk(op_ctx, i); auto chunk = group_chunk.get()->GetChunk(field_id_); auto rows = chunk->RowNums(); int64_t valid_count = 0; for (int64_t j = 0; j < rows; j++) { if (chunk->isValid(j)) { valid_data_[logical_offset + j] = true; valid_count++; } else { valid_data_[logical_offset + j] = false; } } valid_count_per_chunk_[i] = valid_count; logical_offset += rows; } BuildOffsetMapping(); } private: std::shared_ptr group_; FieldId field_id_; const FieldMeta field_meta_; DataType data_type_; }; } // namespace milvus