EF Core和npgsql连接池已经耗尽。

4
我们有一个已经成功运行了5年的ASP.Net Core 3.1 Rest API。它使用EF Core 5.1与SQLite数据库进行交互。
我们现在正在将数据库从SQLite迁移到AWS Aurora Postgres。
考虑到这一点,我们已经添加了Npgsql.EntityFrameworkCore.PostgresSQL nuget包,并修改了连接字符串,类似于以下内容:
"Host=[Our AWS host]; Port=5432; User ID=postgres; Password=XXXXXXXX; Database=api_test_db"

我们有一套针对API运行的集成测试。当连接到SQLite数据库时,所有测试都能成功运行。 然而,当运行针对Postgres的测试时,在运行了大约20个测试后开始出现以下错误:
"The connection pool has been exhausted, either raise MaxPoolSize (currently 100) or Timeout (currently 15 seconds)"
我尝试通过添加"Pooling=false"来更改连接字符串,结果仍然出现相同的错误。 然后我尝试移除"Pooling=false"并添加"Maximum Pool Size = 200"(以及更多)。同样,结果还是出现了相同的错误。
因为这些更改没有任何影响,我怀疑EF并没有使用我认为的连接字符串,所以我故意将连接字符串中的Database元素更改为一个不存在的数据库名称,结果立即失败。从而证明确实使用了正确的连接字符串。
关于我们使用EF Core的其他注意事项:
我们不是注入一个具体的DbContext类,而是将一个IContext接口注入到我们的服务中。
我们像这样在服务集合中注册接口:
services.AddScoped<IContext>(serviceProvider =>
{
    var connectionString = "...";
    var context = new Context(connectionString);
    return context;
});

Context类的样子如下:
public class Context : DbContext, IContext 
{ 
    ... 

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (_connectionString.ToLower().Contains("sqlite"))
        {
            optionsBuilder.UseSqlite(_connectionString, 
                options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery));
        }
        else
        {
            optionsBuilder.UseNpgsql(_connectionString, 
                options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
        }
    }
    
}

就像我说的,这个代码库多年来一直在与SQLite成功地配合使用。但是当然,与Npgsql一样,SQLite没有连接池的概念。
我已经阅读了Postgres Npgsql Connection Pooling和其他相关的SO帖子,但是无法找出问题所在。
你有什么想法我们做错了什么吗?

为什么你在DbContext内部配置连接,而不是使用AddDbContext?什么是IContext?你发布的内容并不是使用EF的标准方式,所以很难猜测发生了什么。我怀疑代码使用的是singleton而不是作用域实例,或者至少在没有正确关闭它们的情况下创建新的DbContext实例。 - undefined
SQLite 是一个嵌入式的单用户数据库。它只在应用程序打开时运行,并且不支持并发连接,至少没有一些技巧的情况下是这样。在 ASP.NET Core 中,范围是请求本身,这意味着 SQLite 必须为每个单独的请求打开和关闭。我怀疑应用程序正在做某些操作以保持 SQLite 连接对多个查询保持打开状态。但是基于服务器的数据库不会以这种方式工作,连接应该尽快关闭。这就是为什么默认范围是请求的原因。 - undefined
1
在基于服务器的数据库中,数据库驱动程序(甚至不包括EF Core)维护一个可重用连接池,以便随时供使用,这样就不需要在每个请求中重新打开它们。当连接关闭时,驱动程序实际上会重置连接并放回连接池。这样,少量的连接可以处理数十甚至数百个并发请求。连接池耗尽意味着应用程序代码在每个请求后未能正确关闭连接,从而迫使驱动程序创建新的连接。 - undefined
最大池大小也是一种提高可扩展性的功能。无论数据库如何,同时进行的操作都会导致资源争用。当同时进行的操作过多时,所有操作都会变慢,因为每个操作都与其他操作竞争。设置并发操作/连接的上限意味着减少了争用,因此可以在更长的时间内执行更多的操作。 - undefined
一如既往地,尝试在一个最简化的程序中重现问题;很有可能在这个过程中你会自己找到问题所在,如果没有的话,你可以将最简化的程序发布给其他人来查看。 - undefined
1个回答

0
我采纳了@Shay Rojansky的建议,构建了一个简化的程序,并且正如他所建议的那样,我找到了问题所在。
原来我们的API中有一些中间件,根据请求需要工作的数据库(SQLite或Postgres)的存在性进行检查。在Postgres的情况下,我们打开了一个数据库连接却没有关闭它。这意味着连接对象从未返回到连接池中,因此在使用100个连接后,连接池被耗尽,导致我们的API无法处理请求。
感谢大家提供的建议。特别感谢@Shay Rojansky在EF和NpgSql方面的出色工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接