Skip to content

Commit 0446810

Browse files
[genhttp] Improve database performance (#10340)
* Switch to context pooling and compiled models * Fix tracking settings
1 parent 9002572 commit 0446810

15 files changed

+410
-62
lines changed

frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
3636
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
3737

38+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" PrivateAssets="all" />
39+
40+
</ItemGroup>
41+
42+
<ItemGroup>
43+
<Folder Include="Model\CompiledModel\" />
3844
</ItemGroup>
3945

4046
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// <auto-generated />
2+
using Benchmarks;
3+
using Benchmarks.Model;
4+
using Microsoft.EntityFrameworkCore.Infrastructure;
5+
6+
#pragma warning disable 219, 612, 618
7+
#nullable disable
8+
9+
[assembly: DbContextModel(typeof(DatabaseContext), typeof(DatabaseContextModel))]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// <auto-generated />
2+
using Benchmarks.Model;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
using Microsoft.EntityFrameworkCore.Metadata;
5+
6+
#pragma warning disable 219, 612, 618
7+
#nullable disable
8+
9+
namespace Benchmarks
10+
{
11+
[DbContext(typeof(DatabaseContext))]
12+
public partial class DatabaseContextModel : RuntimeModel
13+
{
14+
private static readonly bool _useOldBehavior31751 =
15+
System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751;
16+
17+
static DatabaseContextModel()
18+
{
19+
var model = new DatabaseContextModel();
20+
21+
if (_useOldBehavior31751)
22+
{
23+
model.Initialize();
24+
}
25+
else
26+
{
27+
var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024);
28+
thread.Start();
29+
thread.Join();
30+
31+
void RunInitialization()
32+
{
33+
model.Initialize();
34+
}
35+
}
36+
37+
model.Customize();
38+
_instance = (DatabaseContextModel)model.FinalizeModel();
39+
}
40+
41+
private static DatabaseContextModel _instance;
42+
public static IModel Instance => _instance;
43+
44+
partial void Initialize();
45+
46+
partial void Customize();
47+
}
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// <auto-generated />
2+
using System;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
using Microsoft.EntityFrameworkCore.Metadata;
5+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
6+
7+
#pragma warning disable 219, 612, 618
8+
#nullable disable
9+
10+
namespace Benchmarks
11+
{
12+
public partial class DatabaseContextModel
13+
{
14+
private DatabaseContextModel()
15+
: base(skipDetectChanges: false, modelId: new Guid("e6a922c5-5e25-4191-8617-6c6410b754cc"), entityTypeCount: 2)
16+
{
17+
}
18+
19+
partial void Initialize()
20+
{
21+
var fortune = FortuneEntityType.Create(this);
22+
var world = WorldEntityType.Create(this);
23+
24+
FortuneEntityType.CreateAnnotations(fortune);
25+
WorldEntityType.CreateAnnotations(world);
26+
27+
AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
28+
AddAnnotation("ProductVersion", "10.0.0");
29+
AddAnnotation("Relational:MaxIdentifierLength", 63);
30+
}
31+
}
32+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// <auto-generated />
2+
using System;
3+
using System.Reflection;
4+
using Benchmarks.Model;
5+
using Microsoft.EntityFrameworkCore.Infrastructure;
6+
using Microsoft.EntityFrameworkCore.Metadata;
7+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8+
9+
#pragma warning disable 219, 612, 618
10+
#nullable disable
11+
12+
namespace Benchmarks
13+
{
14+
[EntityFrameworkInternal]
15+
public partial class FortuneEntityType
16+
{
17+
public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
18+
{
19+
var runtimeEntityType = model.AddEntityType(
20+
"Benchmarks.Model.Fortune",
21+
typeof(Fortune),
22+
baseEntityType,
23+
propertyCount: 2,
24+
keyCount: 1);
25+
26+
var id = runtimeEntityType.AddProperty(
27+
"Id",
28+
typeof(int),
29+
propertyInfo: typeof(Fortune).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
30+
fieldInfo: typeof(Fortune).GetField("<Id>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
31+
valueGenerated: ValueGenerated.OnAdd,
32+
afterSaveBehavior: PropertySaveBehavior.Throw,
33+
sentinel: 0);
34+
id.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
35+
id.AddAnnotation("Relational:ColumnName", "id");
36+
37+
var message = runtimeEntityType.AddProperty(
38+
"Message",
39+
typeof(string),
40+
propertyInfo: typeof(Fortune).GetProperty("Message", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
41+
fieldInfo: typeof(Fortune).GetField("<Message>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
42+
nullable: true,
43+
maxLength: 2048);
44+
message.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.None);
45+
message.AddAnnotation("Relational:ColumnName", "message");
46+
47+
var key = runtimeEntityType.AddKey(
48+
new[] { id });
49+
runtimeEntityType.SetPrimaryKey(key);
50+
51+
return runtimeEntityType;
52+
}
53+
54+
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
55+
{
56+
runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
57+
runtimeEntityType.AddAnnotation("Relational:Schema", null);
58+
runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
59+
runtimeEntityType.AddAnnotation("Relational:TableName", "fortune");
60+
runtimeEntityType.AddAnnotation("Relational:ViewName", null);
61+
runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);
62+
63+
Customize(runtimeEntityType);
64+
}
65+
66+
static partial void Customize(RuntimeEntityType runtimeEntityType);
67+
}
68+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// <auto-generated />
2+
using System;
3+
using System.Reflection;
4+
using Benchmarks.Model;
5+
using Microsoft.EntityFrameworkCore.Infrastructure;
6+
using Microsoft.EntityFrameworkCore.Metadata;
7+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
8+
9+
#pragma warning disable 219, 612, 618
10+
#nullable disable
11+
12+
namespace Benchmarks
13+
{
14+
[EntityFrameworkInternal]
15+
public partial class WorldEntityType
16+
{
17+
public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
18+
{
19+
var runtimeEntityType = model.AddEntityType(
20+
"Benchmarks.Model.World",
21+
typeof(World),
22+
baseEntityType,
23+
propertyCount: 2,
24+
keyCount: 1);
25+
26+
var id = runtimeEntityType.AddProperty(
27+
"Id",
28+
typeof(int),
29+
propertyInfo: typeof(World).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
30+
fieldInfo: typeof(World).GetField("<Id>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
31+
valueGenerated: ValueGenerated.OnAdd,
32+
afterSaveBehavior: PropertySaveBehavior.Throw,
33+
sentinel: 0);
34+
id.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
35+
id.AddAnnotation("Relational:ColumnName", "id");
36+
37+
var randomNumber = runtimeEntityType.AddProperty(
38+
"RandomNumber",
39+
typeof(int),
40+
propertyInfo: typeof(World).GetProperty("RandomNumber", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
41+
fieldInfo: typeof(World).GetField("<RandomNumber>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
42+
sentinel: 0);
43+
randomNumber.AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.None);
44+
randomNumber.AddAnnotation("Relational:ColumnName", "randomnumber");
45+
46+
var key = runtimeEntityType.AddKey(
47+
new[] { id });
48+
runtimeEntityType.SetPrimaryKey(key);
49+
50+
return runtimeEntityType;
51+
}
52+
53+
public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
54+
{
55+
runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
56+
runtimeEntityType.AddAnnotation("Relational:Schema", null);
57+
runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
58+
runtimeEntityType.AddAnnotation("Relational:TableName", "world");
59+
runtimeEntityType.AddAnnotation("Relational:ViewName", null);
60+
runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);
61+
62+
Customize(runtimeEntityType);
63+
}
64+
65+
static partial void Customize(RuntimeEntityType runtimeEntityType);
66+
}
67+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Benchmarks.Model;
2+
3+
public static class Database
4+
{
5+
public static readonly DatabaseContextPool<DatabaseContext> NoTrackingPool;
6+
7+
public static readonly DatabaseContextPool<DatabaseContext> TrackingPool;
8+
9+
static Database()
10+
{
11+
NoTrackingPool = new DatabaseContextPool<DatabaseContext>(factory: DatabaseContext.CreateNoTracking, maxSize: 512);
12+
TrackingPool = new DatabaseContextPool<DatabaseContext>(factory: DatabaseContext.CreateTracking, maxSize: 512);
13+
}
14+
15+
}
Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,48 @@
11
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.DependencyInjection;
23

34
namespace Benchmarks.Model;
45

56
public sealed class DatabaseContext : DbContext
67
{
7-
private static DbContextOptions<DatabaseContext> _options;
8+
private static readonly Lazy<DbContextOptions<DatabaseContext>> TrackingOptions = new(() => CreateOptions(true), LazyThreadSafetyMode.ExecutionAndPublication);
89

9-
private static DbContextOptions<DatabaseContext> _noTrackingOptions;
10+
private static readonly Lazy<DbContextOptions<DatabaseContext>> NoTrackingOptions = new(() => CreateOptions(false), LazyThreadSafetyMode.ExecutionAndPublication);
1011

11-
#region Factory
12+
public static DatabaseContext CreateTracking() => new(TrackingOptions.Value, true);
1213

13-
public static DatabaseContext Create() => new(_options ??= GetOptions(true));
14+
public static DatabaseContext CreateNoTracking() => new(NoTrackingOptions.Value, false);
1415

15-
public static DatabaseContext CreateNoTracking() => new(_noTrackingOptions ??= GetOptions(false));
16-
17-
private static DbContextOptions<DatabaseContext> GetOptions(bool tracking)
16+
private static DbContextOptions<DatabaseContext> CreateOptions(bool tracking)
1817
{
19-
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
18+
var services = new ServiceCollection();
19+
20+
services.AddEntityFrameworkNpgsql();
21+
22+
var provider = services.BuildServiceProvider();
2023

21-
optionsBuilder.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4");
24+
var builder = new DbContextOptionsBuilder<DatabaseContext>();
25+
26+
builder.UseInternalServiceProvider(provider)
27+
.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4;Multiplexing=true")
28+
.EnableThreadSafetyChecks(false)
29+
.UseModel(DatabaseContextModel.Instance);
2230

2331
if (!tracking)
2432
{
25-
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
33+
builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
2634
}
2735

28-
return optionsBuilder.Options;
36+
return builder.Options;
2937
}
3038

31-
private DatabaseContext(DbContextOptions options) : base(options) { }
32-
33-
#endregion
34-
35-
#region Entities
39+
internal DatabaseContext(DbContextOptions<DatabaseContext> options, bool tracking = false) : base(options)
40+
{
41+
ChangeTracker.AutoDetectChangesEnabled = tracking;
42+
}
3643

3744
public DbSet<World> World { get; set; }
3845

3946
public DbSet<Fortune> Fortune { get; set; }
4047

41-
#endregion
42-
4348
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Benchmarks.Model;
2+
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Design;
5+
6+
public class DatabaseContextFactory : IDesignTimeDbContextFactory<DatabaseContext>
7+
{
8+
9+
public DatabaseContext CreateDbContext(string[] args)
10+
{
11+
var options = new DbContextOptionsBuilder<DatabaseContext>()
12+
.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable")
13+
.Options;
14+
15+
return new DatabaseContext(options);
16+
}
17+
18+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Benchmarks.Model;
2+
3+
using System.Collections.Concurrent;
4+
5+
using Microsoft.EntityFrameworkCore;
6+
7+
public sealed class DatabaseContextPool<TContext> where TContext : DbContext
8+
{
9+
private readonly ConcurrentBag<TContext> _pool = new();
10+
11+
private readonly Func<TContext> _factory;
12+
13+
private readonly int _maxSize;
14+
15+
public DatabaseContextPool(Func<TContext> factory, int maxSize)
16+
{
17+
_factory = factory;
18+
_maxSize = maxSize;
19+
}
20+
21+
public TContext Rent()
22+
{
23+
if (_pool.TryTake(out var ctx))
24+
{
25+
ctx.ChangeTracker.Clear();
26+
return ctx;
27+
}
28+
29+
return _factory();
30+
}
31+
32+
public void Return(TContext context)
33+
{
34+
if (_pool.Count >= _maxSize)
35+
{
36+
context.Dispose();
37+
return;
38+
}
39+
40+
41+
context.ChangeTracker.Clear();
42+
43+
_pool.Add(context);
44+
}
45+
46+
}

0 commit comments

Comments
 (0)