diff --git a/common/data_bucket.cpp b/common/data_bucket.cpp index d0ad01b7e..9735c8cee 100644 --- a/common/data_bucket.cpp +++ b/common/data_bucket.cpp @@ -19,6 +19,37 @@ extern WorldDatabase database; #error "You must define either ZONE or WORLD" #endif +// Key: compound cache key (e.g., account_id|character_id|zone_id|instance_id|top_key|full_key) +// Value: resolved DataBuckets with extracted nested value +static std::unordered_map g_nested_bucket_cache; + +static std::string MakeNestedCacheKey(const DataBucketKey &k, const std::string &full_key) { + return fmt::format( + "account_id:{}|character_id:{}|npc_id:{}|bot_id:{}|zone_id:{}|instance_id:{}|top_key:{}|full_key:{}", + k.account_id, k.character_id, k.npc_id, k.bot_id, k.zone_id, k.instance_id, + Strings::Split(full_key, NESTED_KEY_DELIMITER).front(), + full_key + ); +} + +static std::string MakeNestedCacheKeyPrefix(const DataBucketKey &k, const std::string &top_key) { + return fmt::format( + "account_id:{}|character_id:{}|npc_id:{}|bot_id:{}|zone_id:{}|instance_id:{}|top_key:{}|", + k.account_id, k.character_id, k.npc_id, k.bot_id, k.zone_id, k.instance_id, top_key + ); +} + +static void InvalidateNestedCacheForKey(const DataBucketKey &k, const std::string &top_key) { + std::string prefix = MakeNestedCacheKeyPrefix(k, top_key); + for (auto it = g_nested_bucket_cache.begin(); it != g_nested_bucket_cache.end(); ) { + if (it->first.find(prefix) == 0) { + it = g_nested_bucket_cache.erase(it); + } else { + ++it; + } + } +} + void DataBucket::SetData(const std::string &bucket_key, const std::string &bucket_value, std::string expires_time) { auto k = DataBucketKey{ @@ -136,6 +167,15 @@ void DataBucket::SetData(const DataBucketKey &k_) // Serialize JSON back to string b.value = json_value.dump(); b.key_ = top_key; // Use the top-level key + + if (CanCache(k_)) { + InvalidateNestedCacheForKey(k_, top_key); + std::string nested_cache_key = MakeNestedCacheKey(k_, k_.key); + auto extracted = ExtractNestedValue(b, k_.key); + if (extracted.id > 0) { + g_nested_bucket_cache[nested_cache_key] = extracted; + } + } } if (bucket_id) { @@ -251,7 +291,19 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_, LogDataBuckets("Returning key [{}] value [{}] from cache", e.key_, e.value); if (is_nested_key && !k_.key.empty()) { - return ExtractNestedValue(e, k_.key); + std::string nested_cache_key = MakeNestedCacheKey(k_, k.key); + + auto it = g_nested_bucket_cache.find(nested_cache_key); + if (it != g_nested_bucket_cache.end()) { + LogDataBucketsDetail("Nested cache hit for key [{}]", nested_cache_key); + return it->second; + } + + auto extracted = ExtractNestedValue(e, k_.key); + if (extracted.id > 0) { + g_nested_bucket_cache[nested_cache_key] = extracted; + } + return extracted; } return e; @@ -315,23 +367,42 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_, } // Add the value to the cache if it doesn't exist + // If cacheable and not found in cache, short-circuit and assume it doesn't exist if (can_cache) { - bool has_cache = false; + bool found_in_cache = false; for (const auto &e : g_data_bucket_cache) { - if (e.id == bucket.id) { - has_cache = true; + if (CheckBucketMatch(e, k)) { + found_in_cache = true; break; } } - if (!has_cache) { - g_data_bucket_cache.emplace_back(bucket); + if (!found_in_cache) { + LogDataBuckets("Cache miss for key [{}] - skipping DB due to CanCache", k.key); + return DataBucketsRepository::NewEntity(); } } // Handle nested key extraction if (is_nested_key && !k_.key.empty()) { - return ExtractNestedValue(bucket, k_.key); + if (CanCache(k_)) { + std::string nested_cache_key = MakeNestedCacheKey(k_, k.key); + + auto it = g_nested_bucket_cache.find(nested_cache_key); + if (it != g_nested_bucket_cache.end()) { + LogDataBucketsDetail("Nested cache hit for key [{}]", nested_cache_key); + return it->second; + } + + auto extracted = ExtractNestedValue(bucket, k_.key); + if (extracted.id > 0) { + g_nested_bucket_cache[nested_cache_key] = extracted; + } + return extracted; + } else { + // Not cacheable, just extract and return + return ExtractNestedValue(bucket, k_.key); + } } return bucket;