Perf of different tracking behaviors
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Refactor
- Clarity
- Needs clarification
- Activity status
- Stale
- Tech stack
- csharp
- Domain
- databases, performance
Research direction
Start with the benchmark code included in the issue, especially QueryTrackingBehavior, the Tracking, NoTracking, and NoTrackingWithIdentityResolution methods, and the Include variants. Run the benchmark to reproduce the allocation and CPU results, then investigate the listed identity-resolution and shadow-property concerns; done means a measured improvement supported by updated benchmark results.
Written by the indexing model from the issue text.
Description
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run();
}
}
[MemoryDiagnoser]
public class QueryTrackingBehavior
{
private BloggingContext _context;
[Params(1000)]
public int NumBlogs { get; set; }
[Params(100)]
public int NumPostsPerBlog { get; set; }
[GlobalSetup]
public void Setup()
{
Console.WriteLine("Setting up database...");
using var context = new BloggingContext();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
context.SeedData(NumBlogs, NumPostsPerBlog);
Console.WriteLine("Setup complete.");
}
[IterationSetup]
public void CreateContext()
{
_context = new BloggingContext();
_context.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTrackingWithIdentityResolution;
}
[IterationCleanup]
public void DisposeContext()
{
_context.Dispose();
}
[Benchmark]
public void Tracking()
{
foreach (var item in _context.Posts.AsTracking()/*.Include(p => p.Blog)*/)
{
}
}
[Benchmark]
public void NoTracking()
{
foreach (var item in _context.Posts.AsNoTracking()/*.Include(p => p.Blog)*/)
{
}
}
[Benchmark]
public void NoTrackingWithIdentityResolution()
{
foreach (var item in _context.Posts/*.Include(p => p.Blog)*/)
{
}
}
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=SLDW;Database=test;Trusted_Connection=True;;Connect Timeout=60;ConnectRetryCount=0");
// @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True");
}
public void SeedData(int numBlogs, int numPostsPerBlog)
{
using var context = new BloggingContext();
context.AddRange(
Enumerable.Range(0, numBlogs).Select(_ => new Blog
{
Posts = Enumerable.Range(0, numPostsPerBlog).Select(_ => new Post()).ToList()
}));
context.SaveChanges();
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public int Rating { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
}
Results without Include
| Method | NumBlogs | NumPostsPerBlog | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| Tracking | 1000 | 100 | 320.86 ms | 6.290 ms | 6.178 ms | 16000.0000 | 7000.0000 | - | 110.76 MB |
| NoTracking | 1000 | 100 | 51.55 ms | 0.997 ms | 1.493 ms | 5000.0000 | - | - | 25.95 MB |
| NoTrackingWithIdentityResolution | 1000 | 100 | 305.46 ms | 3.989 ms | 3.114 ms | 16000.0000 | 7000.0000 | - | 110.73 MB |
Results with Include
| Method | NumBlogs | NumPostsPerBlog | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| Tracking | 1000 | 100 | 583.1 ms | 11.64 ms | 21.29 ms | 24000.0000 | 10000.0000 | 1000.0000 | 154.54 MB |
| NoTracking | 1000 | 100 | 149.0 ms | 2.00 ms | 1.87 ms | 20000.0000 | - | - | 93.86 MB |
| NoTrackingWithIdentityResolution | 1000 | 100 | 703.3 ms | 11.23 ms | 9.95 ms | 33000.0000 | 10000.0000 | 1000.0000 | 210.26 MB |
The AsTracking operator has no effect in the result how it is configured.
- Allocations/CPU is higher in identity resolution compared to tracking when doing include.
- Tracking would have higher allocation without duplicate but in Include scenario there should be a lot of duplicate blogs so allocations should be higher than tracking and other.
Few ideas
- Identity resolution sets IsLoaded flag for navigation differently than tracking query. That could be reason for very bad perf. (may be for allocation too)
- There are GC everywhere but what is getting garbage collected? At least for tracking kind of scenario, we are not getting any reference out of scope.
Improvements for NoTrackingWithIdentityResolution
- Stop passing shadow property values from query side. They cannot be accessed anyway.
- Have a different implementation of statemanager for identity resolution which optimizes in it. We don't need
- any snapshoting
- Everything could be just InternalClrEntityEntry
- No state data needs to be set
Came out discussion with @roji
- Dominant language
- C#
- Stars
- 14.8k
- Forks
- 3.4k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 134
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/efcore
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
-
customer-reported
Difficulty 5/5 Over a week Newbie friendliness 38/100
-
area-cosmos area-vector-search
Difficulty 5/5 Over a week Newbie friendliness 25/100
-
area-cosmos
Difficulty 5/5 Over a week Newbie friendliness 25/100
-
area-tools needs-design
Difficulty 4/5 3-5 days Newbie friendliness 25/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100