Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

Unusually long initial load time in Blazor Server app with EF Core, even with DbContext warm-up

$
0
0

I'm working on a simple project to learn Blazor Server and EF Core, partly generated by an AI assistant. I've noticed that a specific test page, while not complex, has a very long initial load time (around 8 seconds). This kind of delay would be unacceptable in a real-world commercial project.

To diagnose this, I implemented a timing mechanism. Here's what I've found:

  • Without DbContext warm-up, the first page load takes ~8.1 seconds

  • With DbContext warm-up, my application startup log shows the DbContext takes ~7.9 seconds to warm up. However, the first page load still takes ~7.8 seconds

This doesn't seem right. It appears that warming up the DbContext has almost no effect on the user-perceived initial load time. My understanding was that EF Core's model compilation was a major part of the cold start, but my results suggest another bottleneck of similar magnitude exists.

My question

Is this long initial load time (~8 seconds) normal for a Blazor Server application's cold start, even after the DbContext has been pre-warmed? If not, what could be the bottleneck? Is it the Blazor framework itself, SignalR connection setup, or something else I'm missing?

Here is the relevant code for my project:

Program.cs (with warm-up logic):

// using statements...var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddRazorComponents()    .AddInteractiveServerComponents();var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");builder.Services.AddDbContextFactory<AppDbContext>(options => options.UseNpgsql(connectionString));builder.Services.AddSingleton<PageLoadTimerService>();var app = builder.Build();// --- Application Warm-up ---Console.WriteLine("Application is starting, beginning warm-up...");using (var scope = app.Services.CreateScope()){    var timer = new Stopwatch();    timer.Start();    var services = scope.ServiceProvider;    try    {        var dbContextFactory = services.GetRequiredService<IDbContextFactory<AppDbContext>>();        await using (var dbContext = await dbContextFactory.CreateDbContextAsync())        {            await dbContext.Database.CanConnectAsync();            timer.Stop();            Console.WriteLine($"Database context has been successfully warmed up, took {timer.ElapsedMilliseconds} ms.");        }    }    catch (Exception ex)    {        // ... error logging ...    }}// ... rest of Program.cs ...app.Run();

DbTest.razor (the slow-loading page):

    @page "/db-test"    @using System.Diagnostics    @using Microsoft.EntityFrameworkCore    @using BlazorApp.Data    @using BlazorApp.Model    @using BlazorApp.Services    @inject IDbContextFactory<AppDbContext> DbFactory    @inject PageLoadTimerService GlobalTimerService    @rendermode InteractiveServer<PageTitle>Database Relation Test</PageTitle>    @if (fullLoadTimeInMs.HasValue)    {<div class="alert alert-info"><strong>Full Load Time (from click to completion):</strong><span>@fullLoadTimeInMs.Value.ToString("F2") ms</span></div>    }    @if(hotLoadTimeInMs.HasValue)    {<div class="alert alert-secondary"><strong>OnInitializedAsync Duration (Hot Load):</strong><span>@hotLoadTimeInMs.Value.ToString("F2") ms</span></div>    }<h1>Database Relation Test</h1><div class="row"><!-- Action Panel --><div class="col-md-4"><h3>Action Panel</h3><div class="d-grid gap-2"><button class="btn btn-primary" @onclick="CreatePostWithTags">1. Create New Post with Tags</button><button class="btn btn-info" @onclick="LoadPosts">2. Load/Refresh Posts</button><button class="btn btn-success" @onclick="UpdateFirstPostTags" disabled="@(posts.Count == 0)">3. Update First Post's Tags</button><button class="btn btn-danger" @onclick="DeleteFirstPost" disabled="@(posts.Count == 0)">4. Delete First Post</button><button class="btn btn-secondary" @onclick="ClearLogs">Clear Logs</button></div><hr /><h4>Status Log:</h4><div class="log-box bg-light p-2 rounded" style="height: 200px; overflow-y: scroll;">            @foreach (var log in logs)            {<div class="@log.CssClass">@log.Message</div>            }</div></div><!-- Data Display --><div class="col-md-8"><h3>Current Post List:</h3>        @if (isLoading)        {<p><em>Loading...</em></p>        }        else if (posts.Count == 0)        {<p><em>No posts in the database. Please create one first.</em></p>        }        else        {<ul class="list-group">                @foreach (var post in posts)                {<li class="list-group-item"><h5>@post.Title <small class="text-muted">(ID: @post.Id)</small></h5><p>@post.Content</p><div>                            @foreach (var tag in post.Tags)                            {<span class="badge bg-secondary me-1">@tag.Name</span>                            }</div><small class="text-muted">Created at: @post.CreatedDate.ToLocalTime()</small></li>                }</ul>        }</div></div>    @code {        private double? fullLoadTimeInMs;        private double? hotLoadTimeInMs;        // ... other properties and methods ...        protected override async Task OnInitializedAsync()        {            var hotLoadStopwatch = Stopwatch.StartNew();            await LoadPosts();            hotLoadStopwatch.Stop();            hotLoadTimeInMs = hotLoadStopwatch.Elapsed.TotalMilliseconds;            fullLoadTimeInMs = GlobalTimerService.GetLastMeasurement();            StateHasChanged();        }        private async Task LoadPosts()        {            // ... loads posts from DbContext ...        }    }

PageLoadTimerService.cs (used for timing):

public class PageLoadTimerService{    private long _startTimeTicks = 0;    private double? _lastMeasurement = null;    public void Start()    {        _startTimeTicks = Stopwatch.GetTimestamp();        _lastMeasurement = null;    }    public double? GetLastMeasurement()    {        if (_startTimeTicks != 0)        {            var endTimeTicks = Stopwatch.GetTimestamp();            var elapsed = Stopwatch.GetElapsedTime(_startTimeTicks, endTimeTicks);            _lastMeasurement = elapsed.TotalMilliseconds;            _startTimeTicks = 0;        }        return _lastMeasurement;    }}

Any insights into why the initial load remains so slow would be greatly appreciated.


Viewing all articles
Browse latest Browse all 4839

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>