question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Keeping a DbContext open overtime eventually slows down SaveChanges()

See original GitHub issue

As part of trying to re-engineer my application to combat GC collections, I’ve come across what I consider to be an EFCore performance degradation.

My scenario is a pretty much insert-only application, where I completely disable change tracking and set AutoDetectChangesEnabled = false.

Here’s the code I’m using the in-memory database to prove my point…

When I run the benchmark program I’ve written (dotnet run -c release fast) while constantly creating and disposing the DbContext objects I get a very consistent time for SaveChanges() which is around 4-6ms per 1000 entities saves.

When I run the benchmark in the “slow” mode (dotnet run -c release slow) where a single DbContext is preallocated and used throughout the test, the time for SaveChanges() gradually increases over a few hundred cycles and reaches tens of millieseconds within a few hundred cycles… The expectation is that the reuse of the same DbContext instance would be better for performance, but in fact it causes a pretty severe degradation. It would seem as though there’s some sort of resource leakage even after SaveChanges() is called.

Steps to reproduce

Here’s the small test I’ve come up with:

class Program
{
    static void Main(string[] args)
    {
        if (args[0] == "fast")
            Fast(args);
        if (args[0] == "slow")
            Slow(args);
    }

    static void Slow(string[] args)
    {
        using (var ctx = new BlogContext())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            ctx.ChangeTracker.QueryTrackingBehavior    = QueryTrackingBehavior.NoTracking;
            ctx.ChangeTracker.AutoDetectChangesEnabled = false;

            var sw = Stopwatch.StartNew();
            var cycle = 1;
            while (true)
            {
                PumpIntoDB(ctx, sw, cycle++);
            }
        }
    }

    static void Fast(string[] args)
    {
        using (var ctx = new BlogContext())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
        }

        var sw = Stopwatch.StartNew();
        var cycle = 1;
        while (true)
        {
            using (var ctx = new BlogContext())
            {
                ctx.ChangeTracker.AutoDetectChangesEnabled = false;
                ctx.ChangeTracker.QueryTrackingBehavior    = QueryTrackingBehavior.NoTracking;

                PumpIntoDB(ctx, sw, cycle++);
            }
        }
    }

    static void PumpIntoDB(BlogContext ctx, Stopwatch sw, int cycle)
    {
        foreach (var i in Enumerable.Range(0, 1000))
            ctx.Blogs.Add(new Blog() {Author = $"dans{i}"});

        sw.Restart();
        ctx.SaveChanges();
        var saveChangesAdd = sw.ElapsedMilliseconds;
        Console.WriteLine($"Cycle {cycle:D4}\tSave:{saveChangesAdd}ms");
    }
}

public class Blog
{
    public int Id { get; set; }
    public string Author { get; set; }
}

public class BlogContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //optionsBuilder.UseNpgsql("Host=localhost;Username=test;Password=test;Database=leakymcleakage");
        optionsBuilder.UseInMemoryDatabase("leakymcleakage");
    }

    public DbSet<Blog> Blogs { get; set; }
}

Further technical details

EFCore version: <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.1.0-preview2-final" />

Operating system: ubuntu 18.04 IDE: console

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:20 (17 by maintainers)

github_iconTop GitHub Comments

5reactions
anpetecommented, May 2, 2018

I am in no way recommending using IDbContextPoolable.ResetState for this :trollface:

1reaction
rojicommented, May 2, 2018

@divega thanks for the explanation. I think I understand better.

My expectation was that if one turns off change tracking on the context (ctx.ChangeTracker.AutoDetectChangesEnabled = false; ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;) there would be no need for the DbContext to remember anything about entities which were inserted. However, I understand that the above two properties allow one to turn off certain aspects of change tracking, but without turning the feature off as a whole.

I know that at least some users (such as @damageboy) really aren’t interested in the change tracking part of EF, and prefer to manage that part themselves, but it would seem as though that isn’t 100% possible at this point. As you already provide knobs for disabling some parts of change tracking, it may be worth thinking about allowing users to go all the way and disable it altogether.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Entity framework performance issue, saveChanges is very ...
It is fast when you process hundreds of records. Then when we move to 100k records, entity.saveChanges is very very slow. around 1-3...
Read more >
EF slowing down in loop, unless new DbContext() is created
After hundreds of iteration, this solution is slowing down significantly ... SaveChanges() somehow clean up, but it is not happening.
Read more >
Managing DbContext the right way with Entity Framework 6
Once a business transaction has completed and has called the DbContext. SaveChanges() method to persist all the changes it made, it's entirely ...
Read more >
Gotcha: Entity Framework gets slow in long Iteration Loops
What happens is that processing starts fast, but then slowly starts slowing down, getting slower and slower as the loop count goes up....
Read more >
Change Detection and Notifications - EF Core
This detection of changes happens when SaveChanges is called to ensure all changed values are detected before sending updates to the database.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found