Description
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
-
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. -
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.