-
Notifications
You must be signed in to change notification settings - Fork 55
Global memory metrics by type #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,43 +12,31 @@ | |
| #include <cassert> | ||
| #include <cstdint> | ||
|
|
||
| #include "absl/container/flat_hash_map.h" | ||
| #include "absl/strings/string_view.h" | ||
| #include "src/utils/string_interning.h" | ||
| #include "src/indexes/metric_types.h" | ||
| #include "vmsdk/src/info.h" | ||
|
|
||
| namespace valkey_search::indexes { | ||
|
|
||
| // Packed metadata structure for InternedString (32 bits total) | ||
| struct MetaData { | ||
| uint32_t metric_type : 7; // 7 bits for metric type | ||
| uint32_t use_count : 16; // 16 bits for use count | ||
| uint32_t ever_used : 1; // 1 bit to track if ever been used | ||
| uint32_t reserved : 8; // 8 bits reserved for future use | ||
| }; | ||
|
|
||
| struct MetricData { | ||
| std::atomic<uint64_t> count{0}; | ||
| }; | ||
|
|
||
| const absl::flat_hash_map<MetricType, absl::string_view> kMetricTypeToStr = { | ||
| {MetricType::kVectorsMemory, "vectors_memory"}, | ||
| {MetricType::kVectorsMemoryMarkedDeleted, "vectors_memory_marked_deleted"}, | ||
| {MetricType::kHnswNodes, "hnsw_nodes"}, | ||
| {MetricType::kHnswNodesMarkedDeleted, "hnsw_nodes_marked_deleted"}, | ||
| {MetricType::kHnswEdges, "hnsw_edges"}, | ||
| {MetricType::kHnswEdgesMarkedDeleted, "hnsw_edges_marked_deleted"}, | ||
| {MetricType::kFlatNodes, "flat_nodes"}, | ||
| {MetricType::kTags, "tags"}, | ||
| {MetricType::kTagsMemory, "tags_memory"}, | ||
| {MetricType::kNumericRecords, "numeric_records"}, | ||
| {MetricType::kInternedStrings, "interned_strings"}, | ||
| {MetricType::kInternedStringsMarkedDeleted, "interned_strings_marked_deleted"}, | ||
| {MetricType::kInternedStringsMemory, "interned_strings_memory"}, | ||
| {MetricType::kKeysMemory, "keys_memory"} | ||
| constexpr absl::string_view kMetricTypeStrings[] = { | ||
| #define METRIC_ENTRY(name, str) str, | ||
| METRIC_TYPES_TABLE | ||
| #undef METRIC_ENTRY | ||
| }; | ||
|
|
||
| inline absl::string_view GetMetricTypeString(MetricType metric_type) { | ||
| const auto index = static_cast<size_t>(metric_type); | ||
| if (index >= static_cast<size_t>(MetricType::kMetricTypeCount)) { | ||
| return ""; // Invalid metric type | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feels like this should be an assert.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, feels like the body of this and the table should be in .cc file, isn't "kMetricStringTypes" really supposed to be hidden behind "GetMetricTypeString" ?? |
||
| return kMetricTypeStrings[index]; | ||
| } | ||
|
|
||
| class GlobalIndexStats { | ||
| public: | ||
| static GlobalIndexStats& Instance() { | ||
|
|
@@ -57,125 +45,66 @@ class GlobalIndexStats { | |
| } | ||
|
|
||
| void Incr(MetricType metric_type, uint64_t value = 1) { | ||
| GetOrCreateMetric(metric_type).count.fetch_add(value, std::memory_order_relaxed); | ||
| GetMetric(metric_type).count.fetch_add(value, std::memory_order_relaxed); | ||
| } | ||
|
|
||
| void Decr(MetricType metric_type, uint64_t value = 1) { | ||
| GetOrCreateMetric(metric_type).count.fetch_sub(value, std::memory_order_relaxed); | ||
| GetMetric(metric_type).count.fetch_sub(value, std::memory_order_relaxed); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be some insulation against going negative (large positive). This is a common failure in these kinds of tracking operations. When you detect a negative counter, you should clamp it to zero and put out a log message -- be sure to use the LOG_EVERY_N .... macro to rate-limit the log messages, because you could get a lot of them here. Optionally, we might want to have a configurable to make this be a hard crash in order to help debug any account errors. |
||
| } | ||
|
|
||
| uint64_t GetCount(MetricType metric_type) const { | ||
| auto it = metrics_.find(metric_type); | ||
| return it != metrics_.end() ? it->second->count.load(std::memory_order_relaxed) : 0; | ||
| } | ||
|
|
||
| absl::flat_hash_map<MetricType, uint64_t> GetAllMetrics() const { | ||
| absl::flat_hash_map<MetricType, uint64_t> result; | ||
| for (const auto& [type, metric] : metrics_) { | ||
| result[type] = metric->count.load(std::memory_order_relaxed); | ||
| auto& store = ::valkey_search::StringInternStore::Instance(); | ||
| uint64_t total = 0; | ||
| if (metric_type == MetricType::kInternedStrings) { | ||
| for (size_t j = 0; j < static_cast<size_t>(::valkey_search::StringType::kStringTypeCount); ++j) { | ||
|
Comment on lines
+57
to
+59
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switch statement please. |
||
| total += store.GetCounters(static_cast<::valkey_search::StringType>(j)).object_count; | ||
| } | ||
| return total; | ||
| } else if (metric_type == MetricType::kInternedStringsMemory) { | ||
| for (size_t j = 0; j < static_cast<size_t>(::valkey_search::StringType::kStringTypeCount); ++j) { | ||
| total += store.GetCounters(static_cast<::valkey_search::StringType>(j)).memory_bytes; | ||
| } | ||
| return total; | ||
| } else if (metric_type == MetricType::kVectorsMemory) { | ||
| return store.GetCounters(::valkey_search::StringType::VECTOR).memory_bytes; | ||
| } else if (metric_type == MetricType::kVectorsMemoryMarkedDeleted) { | ||
| return store.GetMarkedDeletedCounters().memory_bytes; | ||
| } else if (metric_type == MetricType::kVectorsMarkedDeleted) { | ||
| return store.GetMarkedDeletedCounters().object_count; | ||
| } else if (metric_type == MetricType::kTagsMemory) { | ||
| return store.GetCounters(::valkey_search::StringType::TAG).memory_bytes; | ||
| } else if (metric_type == MetricType::kKeysMemory) { | ||
| return store.GetCounters(::valkey_search::StringType::KEY).memory_bytes; | ||
| } else { | ||
| const size_t index = static_cast<size_t>(metric_type); | ||
| if (index >= static_cast<size_t>(MetricType::kMetricTypeCount)) { | ||
| return 0; // Invalid metric type | ||
| } | ||
|
Comment on lines
+80
to
+82
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feels like an assert would be more appropriate here on the "default" label on the switch. |
||
| return metrics_[index].count.load(std::memory_order_relaxed); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| private: | ||
| GlobalIndexStats() = default; | ||
|
|
||
| MetricData& GetOrCreateMetric(MetricType metric_type) { | ||
| auto [it, inserted] = metrics_.try_emplace(metric_type, nullptr); | ||
| if (inserted) { | ||
| it->second = std::make_unique<MetricData>(); | ||
| } | ||
| return *it->second; | ||
| MetricData& GetMetric(MetricType metric_type) { | ||
| const size_t index = static_cast<size_t>(metric_type); | ||
| assert(index < static_cast<size_t>(MetricType::kMetricTypeCount)); | ||
| return metrics_[index]; | ||
| } | ||
|
|
||
| absl::flat_hash_map<MetricType, std::unique_ptr<MetricData>> metrics_; | ||
| MetricData metrics_[static_cast<size_t>(MetricType::kMetricTypeCount)]; | ||
| }; | ||
|
|
||
| inline void OnInternedStringAlloc(::valkey_search::InternedString* interned_str, MetricType metric_type) { | ||
| if (!interned_str) return; | ||
| size_t bytes = interned_str->Str().length(); | ||
| GlobalIndexStats::Instance().Incr(MetricType::kInternedStringsMemory, bytes); | ||
| GlobalIndexStats::Instance().Incr(MetricType::kInternedStrings, 1); | ||
|
|
||
| if (metric_type == MetricType::kNone) return; | ||
|
|
||
| MetaData metadata{}; | ||
| metadata.metric_type = static_cast<uint8_t>(metric_type); | ||
| metadata.use_count = 0; // Start with 0 | ||
| metadata.ever_used = 0; // Not used yet | ||
| metadata.reserved = 0; | ||
| interned_str->SetMetadataFlags(*reinterpret_cast<uint32_t*>(&metadata)); | ||
|
|
||
| GlobalIndexStats::Instance().Incr(metric_type, bytes); | ||
| } | ||
|
|
||
| inline void OnInternedStringDealloc(::valkey_search::InternedString* interned_str) { | ||
| size_t bytes = interned_str->Str().length(); | ||
| GlobalIndexStats::Instance().Decr(MetricType::kInternedStringsMemory, bytes); | ||
| GlobalIndexStats::Instance().Decr(MetricType::kInternedStrings, 1); | ||
| uint32_t metadata_flags = interned_str->GetMetadataFlags(); | ||
| if (metadata_flags == 0) return; | ||
|
|
||
| MetaData* metadata = reinterpret_cast<MetaData*>(&metadata_flags); | ||
| assert(metadata->use_count == 0); // Assert use_count is 0 when deallocating | ||
| MetricType type = static_cast<MetricType>(metadata->metric_type); | ||
| if (type != MetricType::kNone) { | ||
| GlobalIndexStats::Instance().Decr(type, bytes); | ||
| } | ||
| } | ||
|
|
||
| inline bool OnInternedStringMarkUnused(const std::shared_ptr<::valkey_search::InternedString>& interned_str) { | ||
| if (!interned_str) return false; | ||
|
|
||
| uint32_t* flags_ptr = interned_str->GetMetadataFlagsPtr(); | ||
| MetaData* metadata = reinterpret_cast<MetaData*>(flags_ptr); | ||
| assert(metadata->use_count > 0); | ||
| metadata->use_count--; | ||
|
|
||
| if (metadata->use_count == 0 && metadata->ever_used) { | ||
| // Only mark as deleted if it was actually used and now back to 0 | ||
| MetricType type = static_cast<MetricType>(metadata->metric_type); | ||
| // Only intern strings that may be marked as deleted are kVectorsMemory | ||
| assert(type == MetricType::kVectorsMemory); | ||
| GlobalIndexStats::Instance().Incr(MetricType::kVectorsMemoryMarkedDeleted, interned_str->Str().length()); | ||
| GlobalIndexStats::Instance().Incr(MetricType::kInternedStringsMarkedDeleted, 1); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| inline bool OnInternedStringIncrUsed(const std::shared_ptr<::valkey_search::InternedString>& interned_str) { | ||
| if (!interned_str) return false; | ||
|
|
||
| uint32_t* flags_ptr = interned_str->GetMetadataFlagsPtr(); | ||
| if (*flags_ptr == 0) return false; | ||
|
|
||
| MetaData* metadata = reinterpret_cast<MetaData*>(flags_ptr); | ||
|
|
||
| if (metadata->use_count == 0 && metadata->ever_used) { | ||
| // This was marked as deleted, now used again | ||
| MetricType type = static_cast<MetricType>(metadata->metric_type); | ||
| assert(type == MetricType::kVectorsMemory); | ||
| GlobalIndexStats::Instance().Decr(MetricType::kVectorsMemoryMarkedDeleted, interned_str->Str().length()); | ||
| GlobalIndexStats::Instance().Decr(MetricType::kInternedStringsMarkedDeleted, 1); | ||
| } | ||
|
|
||
| // Mark as ever used on first increment | ||
| if (!metadata->ever_used) { | ||
| metadata->ever_used = 1; | ||
| } | ||
|
|
||
| metadata->use_count++; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| template<typename InfoFieldInteger> | ||
| inline void CreateGlobalMetricsInfoFields() { | ||
| static auto global_metrics_fields = []() { | ||
| std::vector<std::unique_ptr<InfoFieldInteger>> fields; | ||
|
|
||
| for (const auto& [metric_type, metric_name] : kMetricTypeToStr) { | ||
|
|
||
| for (size_t i = 0; i < static_cast<size_t>(MetricType::kMetricTypeCount); ++i) { | ||
| const auto metric_type = static_cast<MetricType>(i); | ||
| const absl::string_view metric_name = GetMetricTypeString(metric_type); | ||
| if (metric_name.empty()) continue; | ||
| auto count_field = std::make_unique<InfoFieldInteger>( | ||
| "global_metrics", std::string(metric_name), | ||
| vmsdk::info_field::IntegerBuilder() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,29 +10,30 @@ | |
|
|
||
| namespace valkey_search::indexes { | ||
|
|
||
| #define METRIC_TYPES_TABLE \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as describe above make METRIC_ENTRY be a parameter to this macro |
||
| METRIC_ENTRY(kNone, "") \ | ||
| METRIC_ENTRY(kVectorsMemory, "vectors_memory") \ | ||
| METRIC_ENTRY(kVectorsMemoryMarkedDeleted, "vectors_memory_marked_deleted") \ | ||
| METRIC_ENTRY(kVectorsMarkedDeleted, "vectors_marked_deleted") \ | ||
| METRIC_ENTRY(kHnswNodes, "hnsw_nodes") \ | ||
| METRIC_ENTRY(kHnswNodesMarkedDeleted, "hnsw_nodes_marked_deleted") \ | ||
| METRIC_ENTRY(kHnswEdges, "hnsw_edges") \ | ||
| METRIC_ENTRY(kHnswEdgesMarkedDeleted, "hnsw_edges_marked_deleted") \ | ||
| METRIC_ENTRY(kFlatNodes, "flat_nodes") \ | ||
| METRIC_ENTRY(kTags, "tags") \ | ||
| METRIC_ENTRY(kTagsMemory, "tags_memory") \ | ||
| METRIC_ENTRY(kNumericRecords, "numeric_records") \ | ||
| METRIC_ENTRY(kInternedStrings, "interned_strings") \ | ||
| METRIC_ENTRY(kInternedStringsMemory, "interned_strings_memory") \ | ||
| METRIC_ENTRY(kKeysMemory, "keys_memory") | ||
|
|
||
| // Generate the enum from the table above | ||
| enum class MetricType { | ||
| kNone, // Indicates no metric type set | ||
| kVectorsMemory, | ||
| kVectorsMemoryMarkedDeleted, | ||
|
|
||
| kHnswNodes, | ||
| kHnswNodesMarkedDeleted, | ||
| kHnswEdges, | ||
| kHnswEdgesMarkedDeleted, | ||
|
|
||
| kFlatNodes, | ||
|
|
||
| kTags, | ||
| kTagsMemory, | ||
|
|
||
| kNumericRecords, | ||
|
|
||
| kInternedStrings, | ||
| kInternedStringsMarkedDeleted, | ||
| kInternedStringsMemory, | ||
|
|
||
| kKeysMemory, | ||
| #define METRIC_ENTRY(name, str) name, | ||
| METRIC_TYPES_TABLE | ||
| #undef METRIC_ENTRY | ||
|
|
||
| // Sentinel value for array bounds checking | ||
| kMetricTypeCount | ||
| }; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of macro with-in a macro is a very powerful thing. But I believe it would be clearer if you passed in the "METRIC_ENTRY" as a parameter to METRIC_TYPES_TABLE rather than having an externally known name that's not obvious from the usage. By passing in the "macro to invoke" you eliminate odd-ball global dependencies.