Resolving from D.I. is "slow"
See original GitHub issueMoving this here as a perf issue in D.I. per @divega’s request.
Original issue https://github.com/aspnet/EntityFrameworkCore/issues/12031 filed by @suchoss
Hello, it seems to me that performance of injected EF inside scoped service is really low.
There is a high chance that I am using EF incorrectly in this case, but I have not been able to find proper description/documentation how to use EF in this case (Inject it into custom hosted service).
In EasyRabbit/startup.cs (ConfigureServices) are registered following services:
// db
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer("Server=localhost; Database=RabbitTest; MultipleActiveResultSets=true; User ID=sa; Password=Admin1234");
});
// default REST Api
services.AddMvc();
// Configuration for Rabbit connector
services.Configure<RabbitConfig>(Configuration.GetSection("RabbitConfig"));
// Rabbit Connector
services.AddSingleton<RabbitConnector>();
// Subscriber which listens if some new message arrives
services.AddSingleton<IHostedService, GenericHostedSubscriber<CalculatorInputs>>();
// Every arival message is then processed in following scope
// ApplicationDbContext dbContext is injected into this RabbitSubscribers.Adder
services.AddScoped<IScopedProcessingService<CalculatorInputs>, RabbitSubscribers.Adder>();
Now if I understand it correctly, it should automatically create db context scope for every new RabbitSubscribers.Adder scope.
Problem is that like this it can consume/process only about 50 messages per second on average.
When I comment all operations with db (AddAsync and SaveChangesAsync) from following code, then it can process about 2000 messages per second which is nice but without db useless for me 😦
namespace EasyRabbit.RabbitSubscribers
{
public class Adder : IScopedProcessingService<CalculatorInputs>
{
private ILogger<Adder> _logger;
private ApplicationDbContext _db;
public Adder(ApplicationDbContext dbContext, ILogger<Adder> logger)
{
_logger = logger;
_db = dbContext;
}
public async Task HandleMessageAsync(CalculatorInputs message)
{
Console.WriteLine($"Calculator: [{message.FirstNumber}] + [{message.SecondNumber}] = {message.FirstNumber + message.SecondNumber}");
await _db.Calculations.AddAsync(new Calculation()
{
FirstNumber = message.FirstNumber,
SecondNumber = message.SecondNumber,
Result = message.FirstNumber + message.SecondNumber
});
await _db.SaveChangesAsync();
}
}
}
When I tried to replace EF with System.Data.SqlClient (following piece of code directly used in Adder.cs), then it could process 1100 messages per second on average. But to work with DB like this is really unconvinient 😕
public static class DB
{
private static string _connectionString = "Server=localhost; Database=RabbitTest; MultipleActiveResultSets=true; User ID=sa; Password=Admin1234";
public static void AddRecord(MyDBObject myDBObject)
{
using (SqlConnection con = new SqlConnection(_connectionString))
{
using (SqlCommand cmd = new SqlCommand("insert into test (FirstNumber, SecondNumber, Result) values (@FirstNumber, @SecondNumber, @Result)", con))
{
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@FirstNumber", myDBObject.FirstNumber);
cmd.Parameters.AddWithValue("@SecondNumber", myDBObject.SecondNumber);
cmd.Parameters.AddWithValue("@Result", myDBObject.Result);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
}
}
public class MyDBObject
{
public int FirstNumber { get; set; }
public int SecondNumber { get; set; }
public int Result { get; set; }
}
Thanks.
Steps to reproduce
- Clone current repository from: https://github.com/suchoss/uServiceChasis
- Install RabbitMQ from: https://www.rabbitmq.com/#getstarted
- Install MSSQL
- Change connector to DB in EasyRabbit/startup.cs (line 31)
- Go to folder RandomNumberPairGenerator and run command dotnet run for a few seconds, then you can cancel it with ctrl+c (it creates some messages into RabbitMQ queue)
- Run EasyRabbit project and watch how many messages per second is being processed *. If you have RabbitMQ management installed you can watch performance on http://localhost:15672 (default login: guest; password: guest)
Further technical details
EF Core version: EF Core 2.0.2 Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Win 10 IDE: Visual Studio 2017 15.7.1
Issue Analytics
- State:
- Created 5 years ago
- Comments:16 (12 by maintainers)
Top GitHub Comments
Comment by @suchoss
Hello @ajcvickers,
So today, I’ve created following three projects:
Steps to reproduce:
"Server=localhost; Database=DIApproach; MultipleActiveResultSets=true; User ID=sa; Password=Admin1234"
or change connection string accordingly in Startup.cs (DependencyInjection project) and in ModelContext.cs (DirectAproach project)dotnet ef database update
dotnet run
dotnet run
Conclusion:
If you are successful with running test you should see following results - of course that on different hw those times are going to be different - (measured 10x): Time: 00:00:51.5376170, url: http://localhost:5000/api/DI Time: 00:00:46.4245608, url: http://localhost:5001/api/Direct Time: 00:00:50.6788257, url: http://localhost:5000/api/DI Time: 00:00:46.3511890, url: http://localhost:5001/api/Direct Time: 00:00:52.3130819, url: http://localhost:5000/api/DI Time: 00:00:47.1922426, url: http://localhost:5001/api/Direct Time: 00:00:52.1807489, url: http://localhost:5000/api/DI Time: 00:00:46.9598238, url: http://localhost:5001/api/Direct Time: 00:00:50.8818136, url: http://localhost:5000/api/DI Time: 00:00:45.2605405, url: http://localhost:5001/api/Direct Time: 00:00:50.7522685, url: http://localhost:5000/api/DI Time: 00:00:46.5828745, url: http://localhost:5001/api/Direct Time: 00:00:51.2286150, url: http://localhost:5000/api/DI Time: 00:00:47.3935218, url: http://localhost:5001/api/Direct Time: 00:00:51.2117850, url: http://localhost:5000/api/DI Time: 00:00:46.2748000, url: http://localhost:5001/api/Direct Time: 00:00:51.4667565, url: http://localhost:5000/api/DI Time: 00:00:45.4717610, url: http://localhost:5001/api/Direct Time: 00:00:50.6048048, url: http://localhost:5000/api/DI Time: 00:00:46.0667616, url: http://localhost:5001/api/Direct
From those numbers you can see that injected EF db context is on average about 9 % slower.
This behaviour is getting even worse when used as described in my first post, but now I also noticed that it was maybe caused by “busy” hard drive (because of RabbitMQ). I’ll try to verify that on some server machine (maybe next week), but meantime you can test scenario from this test if that reproduces even on your computers. EFCoreIssue.zip
@muratg I had no specific expectations on this.
The 9% difference mentioned at https://github.com/aspnet/Extensions/issues/691#issuecomment-410825384 just made me wonder if there was a perf issue in DI worth looking at.
@pakrym’s analysis indicates that the difference when you look at DI vs constructor in isolation is minimal. It seems ok to then conclude that it is not because of a perf issue in DI.
However, I don’t think we have been able to explain why the difference becomes more noticeable when there are other components interacting. Not to mention the more significant differences mentioned in https://github.com/aspnet/Extensions/issues/691#issuecomment-410824355:
Of course, any differences between ADO.NET and EF Core are not likely attributable to DI.