postgresql - 为什么每秒对我的Asp.Net Core API的并发请求数增加时,响应时间会增加

标签 postgresql entity-framework asp.net-core

我在负载下测试一个端点。对于每秒1个请求,平均响应时间约为200毫秒。端点执行一些数据库查找(全部读取),这些查找速度非常快,而且始终是异步的。
但是,当每秒执行几百个请求(req/sec)时,平均响应时间会超过一秒。
我查看了最佳实践指南:
https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-2.2
一些建议,如“避免阻塞调用”和“最小化大型对象分配”似乎不适用,因为我已经在使用异步,而且我对单个请求的响应大小小于50KB。
不过,有两种方法似乎很有用,例如:
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#high-performance
https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-2.2#pool-http-connections-with-httpclientfactory
问题:
为什么平均响应时间会随着请求/秒的增加而增加?
上面我标记为“可能有用”的建议是否有帮助?我这么问是因为虽然我想尝试所有的方法,但不幸的是我的时间有限,所以我想先尝试一些最有可能帮助我的方法。
还有其他的选择值得考虑吗?
我已经看过了这两个现有的线程,但没有回答我的问题:
Correlation between requests per second and response time?
ASP.NET Web API - Handle more requests per second

最佳答案

在没有访问代码的情况下,很难回答您的特定问题,但要考虑的主要问题是EF生成的数据库查询的大小和复杂性。使用async/await将提高web服务器启动请求的响应性,但负载下的请求处理时间将在很大程度上取决于数据库成为争用点时运行的查询。您将希望确保所有查询都尽可能简单。例如,以下三种说法有很大的不同:

var someData = context.SomeTable.Include(x => x.SomeOtherTable)
    .ToList()
    .Where(x => x.SomeCriteriaMethod())
    .ToList();

var someData = context.SomeTable.Include(x => x.SomeOtherTable)
    .Where(x => x.SomeField == someField && x.SomeOtherTable.SomeOtherField == someOtherField)
    .ToList();

var someData = context.SomeTable
    .Where(x => x.SomeField == someField && x.SomeOtherTable.SomeOtherField == someOtherField)
    .Select(x => new SomeViewModel 
    {
       SomeTableId = x.SomeTableId,
       SomeField = x.SomeField,
       SomeOtherField = x.SomeOtherTable.SomeOtherField
    }).ToList();

像上面第一个这样的例子效率非常低,因为它们在筛选行之前会从数据库中加载相关表中的所有数据。即使您的web服务器可能只传回几行,但它已经从数据库中请求了所有内容。当开发人员面对这样的场景时,这类场景会蔓延到应用程序中:他们希望过滤一个EF无法转换为SQL的值(比如一个函数),因此他们通过发出ToList调用来解决这个问题,或者它可以作为分离不良的副产品引入,比如返回IEnumerable的存储库模式。
第二个例子稍微好一点,它们避免使用read all ToList()调用,但是调用仍然为不需要的数据加载整行。这会将数据库和web服务器上的资源绑定起来。
第三个示例演示如何优化查询,以只返回消费者所需的数据的绝对最小值。这可以更好地利用数据库服务器上的索引和执行计划。
在负载下还可能面临其他性能缺陷,如延迟负载。数据库将执行有限数量的并发请求,因此如果发现某些查询正在启动额外的延迟加载请求,则在没有加载的情况下,将立即执行这些请求。不过,在加载情况下,它们与其他查询和延迟加载请求一起排队,这可能会限制数据拉取。
最后,您应该对数据库运行一个SQL分析器来捕获正在执行的SQL查询的种类和数量。在测试环境中执行时,请密切注意读取计数和CPU开销,而不是总的执行时间。一般来说,较高的读取和CPU成本查询在负载下更容易受到执行时间耗尽的影响。它们需要更多的资源来运行,“触摸”更多的行意味着更多的等待行/表锁。
另一个需要注意的是在非常大的数据系统中的“重”查询,这些查询需要接触很多行,例如报表,在某些情况下,还需要高度自定义的搜索查询。如果需要,则应考虑规划数据库设计,以包括运行报表或大型搜索表达式所依据的只读副本,以避免主数据库中的行锁定情况,这种情况会降低典型读写查询的响应能力。
编辑:识别延迟加载查询。
这些查询将显示在探查器中,您可以在其中对顶级表进行查询,但随后会看到对相关表的许多其他查询。
例如,假设您有一个名为Order的表,其中一个相关的表名为Product,另一个名为Customer,另一个名为Address的表名为delivery Address。要读取日期范围内的所有订单,您将看到一个查询,例如:
SELECT [OrderId], [Quantity], [OrderDate] [ProductId], [CustomerId], [DeliveryAddressId] FROM [dbo].[Orders] WHERE [OrderDate] >= '2019-01-01' AND [OrderDate] < '2020-01-01'

你只想把订单装回去。
当序列化程序在字段上迭代时,它会找到引用的产品、客户和地址,并且通过尝试读取这些字段,将触发延迟加载,从而导致:
SELECT [CustomerId], [Name] FROM [dbo].[Customers] WHERE [CustomerId] = 22
SELECT [ProductId], [Name], [Price] FROM [dbo].[Products] WHERE [ProductId] = 1023
SELECT [AddressId], [StreetNumber], [City], [State], [PostCode] FROM [dbo].[Addresses] WHERE [AddressId] = 1211

如果原始查询返回100个订单,您可能会看到上述查询集的100倍,对于每个订单,一个延迟加载命中一个订单行的集合将尝试按客户ID查找相关客户,按产品ID查找相关产品,按交货地址ID查找相关地址。这可能而且确实会造成成本高昂。在测试环境中运行时,它可能不可见,但这会导致许多潜在的查询。
如果对相关实体使用.Include()进行紧急加载,EF将编写JOIN语句以在一次命中中获取所有相关行,这比获取每个单独的相关实体要快得多。不过,这可能会导致提取大量不需要的数据。避免这种额外成本的最好方法是通过Select利用投影来检索您需要的列。

关于postgresql - 为什么每秒对我的Asp.Net Core API的并发请求数增加时,响应时间会增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57742882/

相关文章:

database - 将列添加为外键会导致外键约束中引用的 ERROR 列不存在

c# - 我如何通过 Npgsql 调用 PROCEDURE 中的 OUT 参数

entity-framework - 导航属性上的 Entity Framework IN 子句

c# - 在 .net 核心中设置全局时区

c# - 带有 <example> 标记的数组类型不适用于 Swagger (swashbuckle.aspnetcore)

c# - 是否可以将 IHttpClientFactory 注入(inject)强类型客户端?

postgresql - pg 9.4 vs 10 按随机排序的差异()

c# - 我可以阻止 Entity Framework AddOrUpdate 覆盖某些字段吗?

c# - 实现聊天功能时出现违反多重约束的错误

sql - 如何将时间戳从一个范围扩展到另一个范围?