Skip to content

Commit 1680445

Browse files
xiangyan99jongio
andauthored
Introducing cache group for the scenario that one service needs to ca… (#168)
* Introducing cache group for the scenario that one service needs to cache multiple clients. * updates * Update src/Services/Azure/ResourceGroup/ResourceGroupService.cs Co-authored-by: Jon Gallant <2163001+jongio@users.noreply.github.com> * update cspell * update --------- Co-authored-by: Jon Gallant <2163001+jongio@users.noreply.github.com>
1 parent 26923b3 commit 1680445

File tree

10 files changed

+344
-33
lines changed

10 files changed

+344
-33
lines changed

.vscode/cspell.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,22 @@
130130
],
131131
"words": [
132132
"1espt",
133+
"ADMINPROVIDER",
133134
"alcoop",
134135
"appconfig",
135136
"azmcp",
136137
"azuremcp",
137138
"azsdk",
138139
"azuremcp",
139140
"azuresdk",
141+
"cslschema",
140142
"bdylan",
141143
"bestpractices",
142144
"codesign",
143145
"CODEOWNERS",
144146
"cvzf",
147+
"kcsb",
148+
"Kusto",
145149
"esrp",
146150
"ESRPRELPACMANTEST",
147151
"keyvault",
@@ -161,11 +165,16 @@
161165
"npmjs",
162166
"onboarded",
163167
"pscore",
168+
"resourcegroup",
169+
"resourcegroups",
164170
"servicebus",
165171
"testsettings",
172+
"vectorizable",
173+
"Vectorizer",
166174
"vsts",
167175
"WINDOWSOS",
168176
"WINDOWSPOOL",
169-
"WINDOWSVMIMAGE"
177+
"WINDOWSVMIMAGE",
178+
"Xunit"
170179
]
171180
}

src/Services/Azure/Kusto/KustoService.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ public sealed class KustoService(
1919
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
2020
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
2121

22-
private const string KUSTO_CLUSTERS_CACHE_KEY = "kusto_clusters";
23-
private const string KUSTO_ADMINPROVIDER_CACHE_KEY = "kusto_adminprovider";
22+
private const string CACHE_GROUP = "kusto";
23+
private const string KUSTO_CLUSTERS_CACHE_KEY = "clusters";
24+
private const string KUSTO_ADMINPROVIDER_CACHE_KEY = "adminprovider";
2425
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromHours(1);
2526
private static readonly TimeSpan PROVIDER_CACHE_DURATION = TimeSpan.FromHours(2);
2627

@@ -50,7 +51,7 @@ public async Task<List<string>> ListClusters(
5051
: $"{KUSTO_CLUSTERS_CACHE_KEY}_{subscriptionId}_{tenant}";
5152

5253
// Try to get from cache first
53-
var cachedClusters = await _cacheService.GetAsync<List<string>>(cacheKey, CACHE_DURATION);
54+
var cachedClusters = await _cacheService.GetAsync<List<string>>(CACHE_GROUP, cacheKey, CACHE_DURATION);
5455
if (cachedClusters != null)
5556
{
5657
return cachedClusters;
@@ -66,7 +67,7 @@ public async Task<List<string>> ListClusters(
6667
clusters.Add(cluster.Data.Name);
6768
}
6869
}
69-
await _cacheService.SetAsync(cacheKey, clusters, CACHE_DURATION);
70+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, clusters, CACHE_DURATION);
7071

7172
return clusters;
7273
}
@@ -298,11 +299,11 @@ public async Task<List<JsonElement>> QueryItems(
298299
private async Task<ICslAdminProvider> GetOrCreateCslAdminProvider(KustoConnectionStringBuilder kcsb)
299300
{
300301
var providerCacheKey = GetProviderCacheKey(kcsb);
301-
var cslAdminProvider = await _cacheService.GetAsync<ICslAdminProvider>(providerCacheKey, PROVIDER_CACHE_DURATION);
302+
var cslAdminProvider = await _cacheService.GetAsync<ICslAdminProvider>(CACHE_GROUP, providerCacheKey, PROVIDER_CACHE_DURATION);
302303
if (cslAdminProvider == null)
303304
{
304305
cslAdminProvider = KustoClientFactory.CreateCslAdminProvider(kcsb);
305-
await _cacheService.SetAsync(providerCacheKey, cslAdminProvider, PROVIDER_CACHE_DURATION);
306+
await _cacheService.SetAsync(CACHE_GROUP, providerCacheKey, cslAdminProvider, PROVIDER_CACHE_DURATION);
306307
}
307308

308309
return cslAdminProvider;
@@ -311,11 +312,11 @@ private async Task<ICslAdminProvider> GetOrCreateCslAdminProvider(KustoConnectio
311312
private async Task<ICslQueryProvider> GetOrCreateCslQueryProvider(KustoConnectionStringBuilder kcsb)
312313
{
313314
var providerCacheKey = GetProviderCacheKey(kcsb) + "_query";
314-
var cslQueryProvider = await _cacheService.GetAsync<ICslQueryProvider>(providerCacheKey, PROVIDER_CACHE_DURATION);
315+
var cslQueryProvider = await _cacheService.GetAsync<ICslQueryProvider>(CACHE_GROUP, providerCacheKey, PROVIDER_CACHE_DURATION);
315316
if (cslQueryProvider == null)
316317
{
317318
cslQueryProvider = KustoClientFactory.CreateCslQueryProvider(kcsb);
318-
await _cacheService.SetAsync(providerCacheKey, cslQueryProvider, PROVIDER_CACHE_DURATION);
319+
await _cacheService.SetAsync(CACHE_GROUP, providerCacheKey, cslQueryProvider, PROVIDER_CACHE_DURATION);
319320
}
320321

321322
return cslQueryProvider;

src/Services/Azure/ResourceGroup/ResourceGroupService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class ResourceGroupService(ICacheService cacheService, ISubscriptionServi
1313
{
1414
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
1515
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
16+
private const string CACHE_GROUP = "resourcegroup";
1617
private const string CACHE_KEY = "resourcegroups";
1718
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromHours(1);
1819

@@ -25,7 +26,7 @@ public async Task<List<ResourceGroupInfo>> GetResourceGroups(string subscription
2526

2627
// Try to get from cache first
2728
var cacheKey = $"{CACHE_KEY}_{subscriptionId}_{tenant ?? "default"}";
28-
var cachedResults = await _cacheService.GetAsync<List<ResourceGroupInfo>>(cacheKey, CACHE_DURATION);
29+
var cachedResults = await _cacheService.GetAsync<List<ResourceGroupInfo>>(CACHE_GROUP, cacheKey, CACHE_DURATION);
2930
if (cachedResults != null)
3031
{
3132
return cachedResults;
@@ -43,7 +44,7 @@ public async Task<List<ResourceGroupInfo>> GetResourceGroups(string subscription
4344
.ToListAsync();
4445

4546
// Cache the results
46-
await _cacheService.SetAsync(cacheKey, resourceGroups, CACHE_DURATION);
47+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, resourceGroups, CACHE_DURATION);
4748

4849
return resourceGroups;
4950
}
@@ -62,7 +63,7 @@ public async Task<List<ResourceGroupInfo>> GetResourceGroups(string subscription
6263

6364
// Try to get from cache first
6465
var cacheKey = $"{CACHE_KEY}_{subscriptionId}_{tenant ?? "default"}";
65-
var cachedResults = await _cacheService.GetAsync<List<ResourceGroupInfo>>(cacheKey, CACHE_DURATION);
66+
var cachedResults = await _cacheService.GetAsync<List<ResourceGroupInfo>>(CACHE_GROUP, cacheKey, CACHE_DURATION);
6667
if (cachedResults != null)
6768
{
6869
return cachedResults.FirstOrDefault(rg => rg.Name.Equals(resourceGroupName, StringComparison.OrdinalIgnoreCase));

src/Services/Azure/Search/SearchService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public sealed class SearchService(ISubscriptionService subscriptionService, ICac
1818
{
1919
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
2020
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
21-
private const string SEARCH_SERVICES_CACHE_KEY = "search_services";
21+
private const string CACHE_GROUP = "search";
22+
private const string SEARCH_SERVICES_CACHE_KEY = "services";
2223
private static readonly TimeSpan CACHE_DURATION_SERVICES = TimeSpan.FromHours(1);
2324

2425
public async Task<List<string>> ListServices(
@@ -32,7 +33,7 @@ public async Task<List<string>> ListServices(
3233
? $"{SEARCH_SERVICES_CACHE_KEY}_{subscription}"
3334
: $"{SEARCH_SERVICES_CACHE_KEY}_{subscription}_{tenantId}";
3435

35-
var cachedServices = await _cacheService.GetAsync<List<string>>(cacheKey, CACHE_DURATION_SERVICES);
36+
var cachedServices = await _cacheService.GetAsync<List<string>>(CACHE_GROUP, cacheKey, CACHE_DURATION_SERVICES);
3637
if (cachedServices != null)
3738
{
3839
return cachedServices;
@@ -50,7 +51,7 @@ public async Task<List<string>> ListServices(
5051
}
5152
}
5253

53-
await _cacheService.SetAsync(cacheKey, services, CACHE_DURATION_SERVICES);
54+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, services, CACHE_DURATION_SERVICES);
5455
}
5556
catch (Exception ex)
5657
{

src/Services/Azure/Storage/StorageService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public class StorageService(ISubscriptionService subscriptionService, ITenantSer
1818
{
1919
private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService));
2020
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
21-
private const string STORAGE_ACCOUNTS_CACHE_KEY = "storage_accounts";
21+
private const string CACHE_GROUP = "storage";
22+
private const string STORAGE_ACCOUNTS_CACHE_KEY = "accounts";
2223
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromHours(1);
2324

2425
public async Task<List<string>> GetStorageAccounts(string subscriptionId, string? tenant = null, RetryPolicyArguments? retryPolicy = null)
@@ -31,7 +32,7 @@ public async Task<List<string>> GetStorageAccounts(string subscriptionId, string
3132
: $"{STORAGE_ACCOUNTS_CACHE_KEY}_{subscriptionId}_{tenant}";
3233

3334
// Try to get from cache first
34-
var cachedAccounts = await _cacheService.GetAsync<List<string>>(cacheKey, CACHE_DURATION);
35+
var cachedAccounts = await _cacheService.GetAsync<List<string>>(CACHE_GROUP, cacheKey, CACHE_DURATION);
3536
if (cachedAccounts != null)
3637
{
3738
return cachedAccounts;
@@ -50,7 +51,7 @@ public async Task<List<string>> GetStorageAccounts(string subscriptionId, string
5051
}
5152

5253
// Cache the results
53-
await _cacheService.SetAsync(cacheKey, accounts, CACHE_DURATION);
54+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, accounts, CACHE_DURATION);
5455
}
5556
catch (Exception ex)
5657
{

src/Services/Azure/Subscription/SubscriptionService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class SubscriptionService(ICacheService cacheService, ITenantService tena
1212
: BaseAzureService(tenantService), ISubscriptionService
1313
{
1414
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
15+
private const string CACHE_GROUP = "subscription";
1516
private const string CACHE_KEY = "subscriptions";
1617
private const string SUBSCRIPTION_CACHE_KEY = "subscription";
1718
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromHours(12);
@@ -20,7 +21,7 @@ public async Task<List<ArgumentOption>> GetSubscriptions(string? tenant = null,
2021
{
2122
// Try to get from cache first
2223
var cacheKey = string.IsNullOrEmpty(tenant) ? CACHE_KEY : $"{CACHE_KEY}_{tenant}";
23-
var cachedResults = await _cacheService.GetAsync<List<ArgumentOption>>(cacheKey, CACHE_DURATION);
24+
var cachedResults = await _cacheService.GetAsync<List<ArgumentOption>>(CACHE_GROUP, cacheKey, CACHE_DURATION);
2425
if (cachedResults != null)
2526
{
2627
return cachedResults;
@@ -41,7 +42,7 @@ public async Task<List<ArgumentOption>> GetSubscriptions(string? tenant = null,
4142
}
4243

4344
// Cache the results
44-
await _cacheService.SetAsync(cacheKey, results, CACHE_DURATION);
45+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, results, CACHE_DURATION);
4546

4647
return results;
4748
}
@@ -57,7 +58,7 @@ public async Task<SubscriptionResource> GetSubscription(string subscription, str
5758
var cacheKey = string.IsNullOrEmpty(tenant)
5859
? $"{SUBSCRIPTION_CACHE_KEY}_{subscriptionId}"
5960
: $"{SUBSCRIPTION_CACHE_KEY}_{subscriptionId}_{tenant}";
60-
var cachedSubscription = await _cacheService.GetAsync<SubscriptionResource>(cacheKey, CACHE_DURATION);
61+
var cachedSubscription = await _cacheService.GetAsync<SubscriptionResource>(CACHE_GROUP, cacheKey, CACHE_DURATION);
6162
if (cachedSubscription != null)
6263
{
6364
return cachedSubscription;
@@ -71,7 +72,7 @@ public async Task<SubscriptionResource> GetSubscription(string subscription, str
7172
}
7273

7374
// Cache the result using subscription ID
74-
await _cacheService.SetAsync(cacheKey, response.Value, CACHE_DURATION);
75+
await _cacheService.SetAsync(CACHE_GROUP, cacheKey, response.Value, CACHE_DURATION);
7576

7677
return response.Value;
7778
}

src/Services/Azure/Tenant/TenantService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ public class TenantService(ICacheService cacheService)
1111
: BaseAzureService, ITenantService
1212
{
1313
private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
14+
private const string CACHE_GROUP = "tenant";
1415
private const string CACHE_KEY = "tenants";
1516
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromHours(12);
1617

1718
public async Task<List<ArgumentOption>> GetTenants()
1819
{
1920
// Try to get from cache first
20-
var cachedResults = await _cacheService.GetAsync<List<ArgumentOption>>(CACHE_KEY, CACHE_DURATION);
21+
var cachedResults = await _cacheService.GetAsync<List<ArgumentOption>>(CACHE_GROUP, CACHE_KEY, CACHE_DURATION);
2122
if (cachedResults != null)
2223
{
2324
return cachedResults;
@@ -40,7 +41,7 @@ public async Task<List<ArgumentOption>> GetTenants()
4041
}
4142

4243
// Cache the results
43-
await _cacheService.SetAsync(CACHE_KEY, results, CACHE_DURATION);
44+
await _cacheService.SetAsync(CACHE_GROUP, CACHE_KEY, results, CACHE_DURATION);
4445
return results;
4546
}
4647

src/Services/Caching/CacheService.cs

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Collections.Concurrent;
45
using AzureMcp.Services.Interfaces;
56
using Microsoft.Extensions.Caching.Memory;
67

@@ -9,29 +10,99 @@ namespace AzureMcp.Services.Caching;
910
public class CacheService(IMemoryCache memoryCache) : ICacheService
1011
{
1112
private readonly IMemoryCache _memoryCache = memoryCache;
13+
private static readonly ConcurrentDictionary<string, HashSet<string>> _groupKeys = new();
1214

13-
public ValueTask<T?> GetAsync<T>(string key, TimeSpan? expiration = null)
15+
public ValueTask<T?> GetAsync<T>(string group, string key, TimeSpan? expiration = null)
1416
{
15-
return _memoryCache.TryGetValue(key, out T? value) ? new ValueTask<T?>(value) : default;
17+
string cacheKey = GetGroupKey(group, key);
18+
return _memoryCache.TryGetValue(cacheKey, out T? value) ? new ValueTask<T?>(value) : default;
1619
}
1720

18-
public ValueTask SetAsync<T>(string key, T data, TimeSpan? expiration = null)
21+
public ValueTask SetAsync<T>(string group, string key, T data, TimeSpan? expiration = null)
1922
{
2023
if (data == null)
2124
return default;
2225

26+
string cacheKey = GetGroupKey(group, key);
27+
2328
var options = new MemoryCacheEntryOptions
2429
{
2530
AbsoluteExpirationRelativeToNow = expiration
2631
};
2732

28-
_memoryCache.Set(key, data, options);
33+
_memoryCache.Set(cacheKey, data, options);
34+
35+
// Track the key in the group
36+
_groupKeys.AddOrUpdate(
37+
group,
38+
new HashSet<string> { key },
39+
(_, keys) =>
40+
{
41+
keys.Add(key);
42+
return keys;
43+
});
44+
2945
return default;
3046
}
3147

32-
public ValueTask DeleteAsync(string key)
48+
public ValueTask DeleteAsync(string group, string key)
3349
{
34-
_memoryCache.Remove(key);
50+
string cacheKey = GetGroupKey(group, key);
51+
_memoryCache.Remove(cacheKey);
52+
53+
// Remove from group tracking
54+
if (_groupKeys.TryGetValue(group, out var keys))
55+
{
56+
keys.Remove(key);
57+
}
58+
3559
return default;
3660
}
61+
62+
public ValueTask<IEnumerable<string>> GetGroupKeysAsync(string group)
63+
{
64+
if (_groupKeys.TryGetValue(group, out var keys))
65+
{
66+
return new ValueTask<IEnumerable<string>>(keys.AsEnumerable());
67+
}
68+
69+
return new ValueTask<IEnumerable<string>>(Array.Empty<string>());
70+
}
71+
72+
public ValueTask ClearAsync()
73+
{
74+
// Clear all items from the memory cache
75+
if (_memoryCache is MemoryCache memoryCache)
76+
{
77+
memoryCache.Compact(1.0);
78+
}
79+
80+
// Clear all group tracking
81+
_groupKeys.Clear();
82+
83+
return default;
84+
}
85+
86+
public ValueTask ClearGroupAsync(string group)
87+
{
88+
// If this group doesn't exist, nothing to do
89+
if (!_groupKeys.TryGetValue(group, out var keys))
90+
{
91+
return default;
92+
}
93+
94+
// Remove each key in the group from the cache
95+
foreach (var key in keys)
96+
{
97+
string cacheKey = GetGroupKey(group, key);
98+
_memoryCache.Remove(cacheKey);
99+
}
100+
101+
// Remove the group from tracking
102+
_groupKeys.TryRemove(group, out _);
103+
104+
return default;
105+
}
106+
107+
private static string GetGroupKey(string group, string key) => $"{group}:{key}";
37108
}

0 commit comments

Comments
 (0)