Skip to content

API proposal for Circuit state persistence #62373

Open
@javiercn

Description

@javiercn

Background and Motivation

The current Blazor Server circuit persistence mechanism has limitations that impact scalability and user experience. This proposal introduces improvements to circuit state persistence with the following key changes:

  • HybridCache Integration: We now rely on HybridCache for handling storage to distributed storage mechanisms, eliminating the need to create individual packages for different distributed backends (Redis, Blob Storage, etc.)
  • Memory-Efficient Default Implementation: A default implementation leveraging MemoryCache (similar to disconnected circuits in CircuitRegistry) that stores a limited number of persisted circuits for a longer period (2 hours by default)
  • Optimized Resource Usage: The persisted state typically consumes only dozens to hundreds of KB of memory, similar to prerendering constraints where pushing more than 100KB starts to degrade the experience

Key benefits of persisting state over keeping circuits alive:

  • Disconnected circuits might continue consuming CPU and memory resources even when not in use
  • Persisted state consumes a fixed amount of memory that developers can control
  • The persisted state represents only a subset of the total memory consumed by the application (without tracking individual components)

The default retention period of 2 hours is chosen as a trade-off between average session duration and the percentage of circuits that fail to resume due to discarded state. Applications can adjust this based on their specific usage patterns.

Proposed API

namespace Microsoft.AspNetCore.Components.Server
{
    public sealed class CircuitOptions
    {
+        public int PersistedCircuitInMemoryMaxRetained { get; set; }
+        public TimeSpan PersistedCircuitInMemoryRetentionPeriod { get; set; }
+        public HybridCache? HybridPersistenceCache { get; set; }
+        public TimeSpan? PersistedCircuitDistributedRetentionPeriod { get; set; }
    }
}

TypeScript APIs for client-side control:

interface Blazor {
+    pause(): void;
+    resume(): void;
}

Usage Examples

Configuring Circuit Persistence Options

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddServerSideBlazor(options =>
{
    // Configure in-memory retention
    options.PersistedCircuitInMemoryMaxRetained = 100; // Maximum 100 circuits in memory
    options.PersistedCircuitInMemoryRetentionPeriod = TimeSpan.FromHours(2); // Keep for 2 hours
    
    // Configure distributed cache retention (when using HybridCache)
    options.PersistedCircuitDistributedRetentionPeriod = TimeSpan.FromHours(24); // Keep in distributed cache for 24 hours
});

// Add HybridCache with Redis backend
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromHours(24),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});

var app = builder.Build();

Client-Side Circuit Control

// Pause the circuit when the page becomes hidden (tab change, minimizing)
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    Blazor.pause();
  } else {
    Blazor.resume();
  }
});

Configuring with Custom HybridCache Implementation

builder.Services.AddServerSideBlazor(options =>
{
    // Use a custom HybridCache instance
    var customCache = new ServiceCollection().AddHybridCache().BuildServiceProvider().   GetRequiredService<HybridCache>();
    options.HybridPersistenceCache = customCache;
});

Alternative Designs

Automatically persisting circuit state in a distributed cache without explicit developer configuration was considered, but it was deemed too opaque and could lead to unexpected behavior. The proposed approach allows developers to control persistence settings explicitly and define their own policies for when to pause or resume circuits.

Risks

  1. Memory Pressure: Applications with high traffic may need to carefully tune PersistedCircuitInMemoryMaxRetained to avoid memory pressure. The default of storing limited circuits mitigates this risk.

  2. State Size Limitations: Large component states (>100KB) will degrade the user experience, similar to prerendering. Developers need to be mindful of what state they persist.

Metadata

Metadata

Assignees

Labels

api-ready-for-reviewAPI is ready for formal API review - https://github.com/dotnet/apireviewsarea-blazorIncludes: Blazor, Razor Componentsarea-perfPerformance infrastructure issues

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions