-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add timer trigger to refresh org cache #59
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
Changes from all commits
0b69c79
b0240b6
84ea819
87ec943
eaf0dcf
07910d4
fd5dce3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,19 +5,22 @@ | |
| using Dan.Plugin.Tilda.Clients; | ||
| using Dan.Plugin.Tilda.Exceptions; | ||
| using Dan.Plugin.Tilda.Interfaces; | ||
| using Dan.Plugin.Tilda.Models.AlertMessages; | ||
| using Dan.Plugin.Tilda.Services; | ||
| using Dan.Plugin.Tilda.Utils; | ||
| using Dan.Tilda.Models.Alerts; | ||
| using Microsoft.Azure.Functions.Worker; | ||
| using Microsoft.Azure.Functions.Worker.Http; | ||
| using Microsoft.Extensions.Logging; | ||
| using StackExchange.Redis; | ||
|
|
||
| namespace Dan.Plugin.Tilda.Functions; | ||
|
|
||
| public class Timers( | ||
| ITildaSourceProvider tildaSourceProvider, | ||
| IMtamCounterClient mtamCounterClient, | ||
| IAlertMessageSender alertMessageSender) | ||
| IAlertMessageSender alertMessageSender, | ||
| IConnectionMultiplexer connectionMultiplexer, | ||
| IBrregService brregService, | ||
| ILogger<Timers> logger) | ||
| { | ||
| // MTAM - Melding til annen myndighet (message to other authority/auditor) | ||
| [Function("MtamTimer")] | ||
|
|
@@ -36,7 +39,7 @@ public async Task MessageToOtherAuditorsTimer( | |
| { | ||
| messages = await mtamSource.GetAlertMessagesAsync(from); | ||
| } | ||
| catch (FailedToFetchDataException e) | ||
| catch (FailedToFetchDataException) | ||
| { | ||
| // If we fail to fetch alert messages from a source, we should continue with the rest of sources | ||
| // but not update this source's counter so we'll try to fetch from the same time again next attempt | ||
|
|
@@ -54,4 +57,96 @@ public async Task MessageToOtherAuditorsTimer( | |
| await mtamCounterClient.UpsertMtamCounter(mtamCounter); | ||
| } | ||
| } | ||
|
|
||
| [Function("CacheRefreshTimer")] | ||
| public async Task CacheRefreshTimer( | ||
| //[HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req, // easier to test locally using http trigger | ||
| [TimerTrigger("%CacheRefreshCron%")] TimerInfo timerInfo, | ||
| FunctionContext context) | ||
| { | ||
| var db = connectionMultiplexer.GetDatabase(); | ||
| var now = DateTime.UtcNow; | ||
| var mainunitKeys = new List<string>(); | ||
| var subunitKeys = new List<string>(); | ||
| const string mainKeyPrefix = "Tilda-Cache_Absolute_GET_https://data.brreg.no/enhetsregisteret/api/enheter/"; | ||
| const string subunitKeyPrefix = "Tilda-Cache_Absolute_GET_https://data.brreg.no/enhetsregisteret/api/enheter/?overordnetEnhet="; | ||
|
|
||
| var keys = GetKeysAsync("Tilda-Cache_Absolute*"); | ||
| await foreach (var key in keys) | ||
| { | ||
| var expiry = await db.KeyExpireTimeAsync(key); | ||
| if (expiry is null) | ||
| { | ||
| logger.LogInformation("Expiry for key {key} is null", key); | ||
| continue; | ||
| } | ||
| var timeLeft = expiry - now; | ||
| if (!(timeLeft <= TimeSpan.FromMinutes(11))) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| if (key.StartsWith(subunitKeyPrefix)) | ||
| { | ||
| var subKey = key.Replace(subunitKeyPrefix, ""); | ||
| subunitKeys.Add(subKey); | ||
| } | ||
| else if (key.StartsWith(mainKeyPrefix)) | ||
| { | ||
| var mainKey = key.Replace(mainKeyPrefix, ""); | ||
| mainunitKeys.Add(mainKey); | ||
| } | ||
| } | ||
|
|
||
| if (mainunitKeys.Count == 0 && subunitKeys.Count == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| logger.LogInformation("Refreshing {mainKeys} main keys and {subunitKeys} subunit keys", mainunitKeys.Count, subunitKeys.Count); | ||
|
|
||
| var tasks = new List<Task>(); | ||
| foreach (var key in mainunitKeys) | ||
| { | ||
| tasks.Add(RefreshOrganisationEntry(key, includeSubunits: false)); | ||
| } | ||
|
|
||
| foreach (var key in subunitKeys) | ||
| { | ||
| tasks.Add(RefreshOrganisationEntry(key, includeSubunits:true)); | ||
| } | ||
|
|
||
| await Task.WhenAll(tasks); | ||
|
Comment on lines
+108
to
+119
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. 🧩 Analysis chain🏁 Script executed: cat -n src/Dan.Plugin.Tilda/Functions/Timers.cs | sed -n '100,130p'Repository: data-altinn-no/plugin-tilda Length of output: 1213 🏁 Script executed: cat -n src/Dan.Plugin.Tilda/Functions/Timers.cs | sed -n '80,120p'Repository: data-altinn-no/plugin-tilda Length of output: 1602 🏁 Script executed: cat -n src/Dan.Plugin.Tilda/Functions/Timers.cs | sed -n '60,85p'Repository: data-altinn-no/plugin-tilda Length of output: 1445 🏁 Script executed: find src/Dan.Plugin.Tilda -name "*.csproj" -o -name "*.fsproj" | xargs cat | grep -A 5 "TargetFramework"Repository: data-altinn-no/plugin-tilda Length of output: 281 🏁 Script executed: rg -A 10 "async.*RefreshOrganisationEntry" src/Dan.Plugin.Tilda/Functions/Timers.csRepository: data-altinn-no/plugin-tilda Length of output: 679 🏁 Script executed: rg "MaxDegreeOfParallelism|ParallelOptions|Parallel.ForEachAsync" src/Dan.Plugin.Tilda/Repository: data-altinn-no/plugin-tilda Length of output: 53 🏁 Script executed: find src/Dan.Plugin.Tilda -name "appsettings*" -o -name "*.json" | head -5Repository: data-altinn-no/plugin-tilda Length of output: 399 🏁 Script executed: rg "IConfiguration|ConfigurationBuilder|options\." src/Dan.Plugin.Tilda/Functions/Timers.cs | head -10Repository: data-altinn-no/plugin-tilda Length of output: 53 🏁 Script executed: find src/Dan.Plugin.Tilda -name "Program.cs" -o -name "Startup.cs" | xargs cat | head -80Repository: data-altinn-no/plugin-tilda Length of output: 3067 🏁 Script executed: find src/Dan.Plugin.Tilda -name "local.settings.json" -o -name "appsettings.json"Repository: data-altinn-no/plugin-tilda Length of output: 53 🏁 Script executed: find src/Dan.Plugin.Tilda -name "Settings.cs" -o -path "*/Config/*" | head -5Repository: data-altinn-no/plugin-tilda Length of output: 197 🏁 Script executed: rg -l "class Settings" src/Dan.Plugin.Tilda/ | head -3Repository: data-altinn-no/plugin-tilda Length of output: 110 🏁 Script executed: cat -n src/Dan.Plugin.Tilda/Config/Settings.csRepository: data-altinn-no/plugin-tilda Length of output: 2138 Bound refresh concurrency to avoid BRREG thundering herd. The code at lines 108-119 launches one concurrent task per expired key with no cap. When many cache entries approach expiry together (within the 11-minute window), this can spike outbound traffic to BRREG and spike worker resource usage. Implement concurrency limiting with 🛡️ Suggested change- var tasks = new List<Task>();
- foreach (var key in mainunitKeys)
- {
- tasks.Add(RefreshOrganisationEntry(key, includeSubunits: false));
- }
-
- foreach (var key in subunitKeys)
- {
- tasks.Add(RefreshOrganisationEntry(key, includeSubunits:true));
- }
-
- await Task.WhenAll(tasks);
+ var maxParallelRefresh = 10; // consider moving to configuration
+ var refreshTargets = mainunitKeys.Select(k => (Org: k, IncludeSubunits: false))
+ .Concat(subunitKeys.Select(k => (Org: k, IncludeSubunits: true)));
+
+ await Parallel.ForEachAsync(
+ refreshTargets,
+ new ParallelOptions { MaxDegreeOfParallelism = maxParallelRefresh },
+ async (target, _) => await RefreshOrganisationEntry(target.Org, target.IncludeSubunits));🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| private async IAsyncEnumerable<string> GetKeysAsync(string pattern) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(pattern)) | ||
| { | ||
| throw new ArgumentException("Value cannot be null or whitespace.", nameof(pattern)); | ||
| } | ||
|
|
||
| foreach (var endpoint in connectionMultiplexer.GetEndPoints()) | ||
| { | ||
| var server = connectionMultiplexer.GetServer(endpoint); | ||
| await foreach (var key in server.KeysAsync(pattern: pattern)) | ||
| { | ||
| yield return key.ToString(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private async Task RefreshOrganisationEntry(string org, bool includeSubunits) | ||
| { | ||
| try | ||
| { | ||
| await brregService.GetFromBr(org, includeSubunits, skipCache: true); | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| // Logging warning would be flooding the alerts/dashboards a bit too much, it's not really an issue if | ||
| // something fails to refresh, this is just a feature to prevent regular calls being too long too often | ||
| logger.LogInformation("Failed to refresh organisation cache entry for {org}, exception message: {exMessage}", org, e.Message); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.