Ask a question
I've implemented a job scheduler that processes many records from our PostgreSQL database every few minutes. Within one of our Grafana dashboards, I noticed that the Gen2 size slowly increases over time and finally reaches a plateau:

So I was wondering what causes Gen2 to become that large and created a memory dump. To my surprise, there are thousands of InternalEntityEntry objects which retain more than a million objects from being GCed:

To be honest, this comes to my surprise as I'd have expected that context.ChangeTracker.Clear() discards those elements after saving them - but maybe I'm misunderstanding something? 🤔
So I'd like to know whether
a) that is normal/expected behavior and if yes,
b) why is it that there are so many InternalEntityEntry objects ending up in Gen2?
Include your code
public class InteractionPoint
{
public int Key { get; set; }
public ProcessState State { get; set; } = ProcessState.Received;
}
public enum ProcessState
{
Received = 0,
Incomplete = 1,
Processed = 2,
TombstoneReceived = 3,
TombstoneProcessed = 4
}
public class DeliveryPlanDbContext : DbContext
{
public DeliveryPlanDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<InteractionPoint> InteractionPoints { get; set; }
}
public async Task Foo(IDbContextFactory<MyDbContext> factory, ILogger logger)
{
var context = factory.CreateDbContext();
var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using IDbContextTransaction transaction = await context.Database.BeginTransactionAsync(ct);
var entities = await dbContext.Set<InteractionPoint>()
.Where(entity => entity.State == ProcessState.Received)
.OrderBy(entity => entity.Key)
.Take(1000)
.AsTracking()
.ToListAsync(ct);
await HandleEntitiesAsync(entities, ct);
await context.SaveChangesAsync(ct);
context.ChangeTracker.Clear();
await transaction.CommitAsync();
});
}
public async Task HandleEntitiesAsync(List<InteractionPoint> entities, CancellationToken ct)
{
foreach (var entity in entities)
{
// do some processing
entity.State = ProcessState.Processed;
}
}
...and the configuration:
void Options(IServiceProvider provider, DbContextOptionsBuilder builder)
{
builder
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.UseNpgsql(
connectionString,
sqlOptions => sqlOptions.CommandTimeout(40));
}
services.AddDbContextPool<DeliveryPlanDbContext>(Options);
services.AddPooledDbContextFactory<DeliveryPlanDbContext>(Options);
For a full repro case, see this repo: https://github.com/mu88/Repro_EFCore_MemoryUsage
Include stack traces
There are no errors or exceptions.
Include verbose output
Using project 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj'.
Using startup project 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj'.
Writing 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\obj\MyCompany.MyApp.Service.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\MyUser\AppData\Local\Temp\tmp5chq3h.tmp /verbosity:quiet /nologo C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj
Writing 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\obj\MyCompany.MyApp.Service.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\MyUser\AppData\Local\Temp\tmpj3tm2x.tmp /verbosity:quiet /nologo C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj
Build started...
dotnet build C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj /verbosity:quiet /nologo /p:PublishAot=false
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:05.15
Build succeeded.
dotnet exec --depsfile C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\bin\Debug\net7.0\MyCompany.MyApp.Service.deps.json --additionalprobingpath C:\Users\MyUser\.nuget\packages --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" --additionalprobingpath "C:\Program Files\dotnet\sdk\NuGetFallbackFolder" --runtimeconfig C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\bin\Debug\net7.0\MyCompany.MyApp.Service.runtimeconfig.json C:\Users\MyUser\.dotnet\tools\.store\dotnet-ef\8.0.1\dotnet-ef\8.0.1\tools\net8.0\any\tools\netcoreapp2.0\any\ef.dll dbcontext list --assembly C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\bin\Debug\net7.0\MyCompany.MyApp.Service.dll --project C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj --startup-assembly C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\bin\Debug\net7.0\MyCompany.MyApp.Service.dll --startup-project C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\MyCompany.MyApp.Service.csproj --project-dir C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\ --root-namespace MyCompany.MyApp.Service --language C# --framework net7.0 --nullable --working-dir C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service --verbose
Using assembly 'MyCompany.MyApp.Service'.
Using startup assembly 'MyCompany.MyApp.Service'.
Using application base 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\bin\Debug\net7.0'.
Using working directory 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service'.
Using root namespace 'MyCompany.MyApp.Service'.
Using project directory 'C:\work\Bitbucket\MyOrg\my-app\src\MyCompany.MyApp.Service\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'MyCompany.MyApp.Service'...
Finding Microsoft.Extensions.Hosting service provider...
Using environment 'Development'.
Using application service provider from Microsoft.Extensions.Hosting.
Found DbContext 'DeliveryPlanDbContext'.
Finding DbContext classes in the project...
MyCompany.MyApp.Core.Persistence.DeliveryPlanDbContext
Include provider and version information
EF Core version: 7.0
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL
Target framework: .NET 7.0
Operating system: Alpine Linux
IDE: not relevant as it happens on production
Ask a question
I've implemented a job scheduler that processes many records from our PostgreSQL database every few minutes. Within one of our Grafana dashboards, I noticed that the Gen2 size slowly increases over time and finally reaches a plateau:

So I was wondering what causes Gen2 to become that large and created a memory dump. To my surprise, there are thousands of

InternalEntityEntryobjects which retain more than a million objects from being GCed:To be honest, this comes to my surprise as I'd have expected that
context.ChangeTracker.Clear()discards those elements after saving them - but maybe I'm misunderstanding something? 🤔So I'd like to know whether
a) that is normal/expected behavior and if yes,
b) why is it that there are so many
InternalEntityEntryobjects ending up in Gen2?Include your code
...and the configuration:
For a full repro case, see this repo: https://github.com/mu88/Repro_EFCore_MemoryUsage
Include stack traces
There are no errors or exceptions.
Include verbose output
Include provider and version information
EF Core version: 7.0
Database provider:
Npgsql.EntityFrameworkCore.PostgreSQLTarget framework: .NET 7.0
Operating system: Alpine Linux
IDE: not relevant as it happens on production