skip to Main Content

My web app in Azure has key/values in application settings Environment Variables.

Question – How does Azure store these values? More specifically, when I fetch each value, is it fetched from a small memory cache or is there some internal file that gets looked through to fetch the value?

My Concern – If I start receiving lots of traffic on my site and there is a high frequency of key/value (Sendgrid, Stripe, Cloudinary) fetching from the Environment vaiables, is this going to cause a slow down in the app if the values are stored in a file vs memory?

Below are two ways I fetch these values, the first method fetches once on startup, saves the values in a cached object ‘SendGridOptions’ and the other fetches each time I need a value. The downside with the first method is if a value changes I have to restart the web app, with the second method Azure gives me a message saying it might have to restart, but my concern is if there’s a high frequency of fetching the app will slow down because it has to fetch from some internal configuration file and not memory?

  1. Startup.cs -> ConfigurationServices(IServiceCollection services)
// fetch all values needed once at startup
public void ConfigureServices(IServiceCollection services) {
  services.Configure <SendGridOptions> (_config.GetSection("SendGrid"));
}
  1. Fetch value when needed in each Controller with IConfiguration.
private readonly IConfiguration _config;

public CalendarController(IConfiguration config) {
  _config = config;
}

ControllerFunction() {
  var secretKey = = _config["SendGrid:SecretKey"];
}

2

Answers


  1. I don’t have insider knowledge of the Azure backend, but my understanding is that config values are loaded into memory at application startup, and they are then fetched from memory as needed. This is why the app needs to re-initialize when you change the values on disk.

    If you want a method to alter your config values without restarting the app, you can point the configuration variables at another source of information, such as a database or Azure Key Vault (maybe especially appropriate for your SendGrid keys). This will introduce some minor latency, of course, but you could build a read-through memory cache for this pretty easily. A read-through cache could then be on a timer to refresh (value expiration) or through administrative signaling it can be instructed to refresh its value (by reading from Azure Key Vault again to fetch updated value).

    An alternative service to think about would be Azure App Configuration, which also lets you use pointers to other values and has additional benefits for managing change as a whole (feature flags, etc). It can be especially valuable when you’re using multiple hosts for your application, letting you use one system of record for configuration values rather than setting them by hand in each service config.
    https://learn.microsoft.com/en-us/azure/azure-app-configuration/overview

    Login or Signup to reply.
  2. 👨‍💻 Let’s consider such a setup:

    
    // Program.cs
    
    builder.Services.Configure<SendGridOptions>(builder.Configuration.GetSection("SendGrid"));
    
    
    // Controller
    
    private readonly IOptionsMonitor<SendGridOptions> _sendGridOptionsMonitor;
    
    public WeatherForecastController(IOptionsMonitor<SendGridOptions> sendGridOptionsMonitor)
    {
        _sendGridOptionsMonitor = sendGridOptionsMonitor;
    }
    
    // Action
    
    // Console.Log is here only for a straight-forward example
    Console.Log(_sendGridOptionsMonitor.CurrentValue.SecretKey);
    
    

    ⚙ Configuration in ASP.NET Core works in such a way:

    1. Providers are implicitly configured in ConfigurationBuilder.

      This is implicit; not in our custom code

      By default there are few providers configured for environment variables and JSON files.

      Providers can be of types:

      • Files (e.g., JSON)
      • Environment variables
      • User secrets
      • Command-line parameters
      • Azure Key Vault
      • Azure App Configuration
      • Custom providers
    2. Configuration is built:

      1️⃣ This is implicit; not in our custom code. Every provider loads values to memory

      Configuration values are sequentially loaded from every provider (order matters) in a key-value form.

      Every provider has its own specific implementation and reload mechanism.

    3. Binding and DI are configured for IOptions<> / IOptionsSnapshot<> / IOptionsMonitor<>

      builder.Services.Configure<SendGridOptions>(
          builder.Configuration.GetSection("SendGrid"));
      
    4. When request is processed:

      • DI instantiates (or gets from cache) all dependencies for a Controller, including options that can be of types IOptions<> / IOptionsSnapshot<> / IOptionsMonitor<>
      // It happens before calling the following constructor
      public WeatherForecastController(IOptionsMonitor<SendGridOptions> sendGridOptionsMonitor)
      { }
      
      • When options instance is constructed (see below for different types of options), it’s read from Configuration. In that moment key-value pairs become an object.

      2️⃣ Happens implicitly

      • Controller is instantiated and options value is injected via constructor
      public WeatherForecastController(
          IOptionsMonitor<SendGridOptions> sendGridOptionsMonitor)
      {
          _sendGridOptionsMonitor = sendGridOptionsMonitor;
      }
      
      • Custom code in controller action reads from options.CurrentValue
      Console.Log(_sendGridOptionsMonitor.CurrentValue.SecretKey);
      

      Different types of options provide different functionality:

      • IOptions<> value is read from Configuration only the first time.
      • IOptionsSnapshot<> value is bound once per request.
      • IOptionsMonitor<> value is cached and reloaded with some delay every time when provider notifies about changes.
    5. If configuration is changed in the source (JSON file, App Configuration, etc.), respective provider updates key-value pairs in its own bucket inside Configuration. It happens with a configurable delay.

      1️⃣ That repeats the same step (when Configuration is built) but only for the specific provider.

      This happens only if provider supports reloading and reload is enabled when provider is registered in ConfigurationBulider on the start of the application.


    🕔 Latency points

    So, we have only two points in time when something effectively happens with configuration values:

    • 1️⃣ Provider loads or reloads key-value pair to memory (doesn’t impact requests):

      • Happens on the start of the application.
      • Happens when provider supports reload, reload is enabled, and change happened in the configuration source (e.g., appsettings.json).
    • 2️⃣ IOptions<> / IOptionsSnapshot<> / IOptionsMonitor<> objects are instantiated by DI:

      • IOptions<> – only first time; cached forever (no impact).
      • IOptionsSnapshot<> – on every request; cached per request (minor impact).
      • IOptionsMonitor<> – only if Configuration was reloaded; otherwise, cached (less impact).

    📝 Notes

    • Configuration is updated per provider only for reloadable providers. That happens in the background.
    • Use relevant type of options (IOptions<> / IOptionsSnapshot<> / IOptionsMonitor<>) for your use case.
    • If you use IOptionsMonitor<>, options.CurrentValue is rebound from Configuration only in case if any provider was reloaded.

    Conclusion

    • Even if you use IOptionsMonitor<> but configuration is not being reloaded by any provider, there should be no latency impact for requests.
    • Usage of IOptionsMonitor<> allows refreshing configuration values without restarting the application.
    • Unnoticeable impact on performance can happen only while reloading Configuration; if the configuration is big and changes all the time (highly unlikely).
    • More noticeable impact can happen only if Configuration is big and constantly reloads (highly unlikely) plus IOptionsMonitor<> is in use.

    ! Sidenote

    🤦‍♀️ Injecting IConfiguration via constructor is a bad taste.

    IConfiguration is not meant to be injected but IOptions<> / IOptionsSnapshot<> / IOptionsMonitor<> are.

    🔗 Links:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search