使用 PostgreSQL / Npgsql 客户端最大化并发请求处理

Maximising concurrent request handling with PostgreSQL / Npgsql client

我有一个读取和写入的数据库和客户端应用程序,我需要处理大量并发读取,但要确保写入优先,同时还要遵守我的数据库的连接限制。

长版:
我有一个允许 100 个连接的单个实例 pgSQL 数据库。 我的 .net 微服务使用 Npgsql 连接到数据库。它必须执行可能需要 20-2000 毫秒的读取查询和可能需要大约 500-2000 毫秒的写入查询。现在有 2 个应用程序实例,使用相同的用户凭据连接。我信任 Npgsql 来管理我的连接池,并且正在准备我的读取查询,因为基本上只有 2 或 3 个具有不同参数值的变体。

随着用户请求的增加,我开始遇到数据库连接限制的问题。来自数据库的“连接过多”等错误。

为了解决这个问题,我在我的 repo 中引入了一个简单的门系统 class:

private static readonly SemaphoreSlim _writeGate = new(20, 20);
private static readonly SemaphoreSlim _readGate = new(25, 25);

public async Task<IEnumerable<SomeDataItem>> ReadData(string query, CancellationToken ct)
{
   await _readGate.WaitAsync(ct);
   // try to get data, finally release the gate
   _readGate.Release();
}

public async Task WriteData(IEnumerable<SomeDataItem>, CancellationToken ct)
{
   await _writeGate.WaitAsync(ct);
   // try to write data, finally release the gate
   _writeGate.Release();
}

我选择为读取和写入设置单独的门,因为我想确信读取不会被并发写入完全阻塞。 限制是硬编码的,连接到 1 个数据库服务器实例的 2 个应用程序实例中的每一个的总限制为 45。 写入数据的尝试不会失败比读取数据的尝试更重要。我在这里使用 Polly 重试模式进一步确保安全。

这有一段时间没问题,但随着并发读取请求的增加,我看到响应时间开始下降,因为读取请求的积压开始累积。

因此,对于这个问题,假设我的 sql 查询和数据库模式已优化到最大,我可以做些什么来提高吞吐量?

我知道有时我的 _readGate 已满,但 _writeGate 中有可用容量。但是我不敢降低硬编码限制,因为在其他时候我需要支持并发写入。所以我需要某种 QoS 解决方案,它可以在可能的情况下允许更多并发读取,但在需要时会优先写入。

队列管理对我来说相当复杂,但也为很多人所熟知,那么有没有一个好的 nuget 包可以帮助我解决这个问题? (我什至不知道要做什么google)
是否对我的代码进行了简单的更改以改进我上面的内容?
使用不同的 conn 字符串/用户进行读取和写入会有帮助吗?
我还能用 npgsql / 连接字符串做些什么来改进吗?

我认为 postgresql 建议将连接数限制为 100,这里有一个 SO 线程: 在 perf 停止改进并最终下降之前,您可以 运行 并行查询的数量始终存在限制。 但是我可以在我的 azure 遥测中看到我的数据库服务器没有接近完全使用 cpu、ram 或磁盘 IO(cpu 不超过 70%,而且通常更少,内存相同,和 IOPS 低于其容量的 30%)所以我相信在某个地方还有更多的东西可以挤出:)

也许还有其他地方需要研究,但为了这个问题,我只想关注如何更好地管理连接。

首先,如果您在 PostgreSQL 端收到“太多连接”,这意味着 Npgsql 打开的物理连接总数超过了 PG 中的 max_connection 设置。您需要确保所有应用程序实例中 Npgsql 的 Max Pool Size 的总和不超过该值,因此如果您的 max_connection 是 100 并且您有两个 Npgsql 实例,每个实例都需要 运行 与 Max Pool Size=50.

其次,通过使用不同的连接字符串,您确实可以为读取和写入设置不同的连接池(一个好的技巧是将 Application Name 设置为不同的值)。但是,您可能希望设置一个或多个只读副本(primary/secondary 设置);这将允许将所有读取工作负载定向到只读副本,同时保留主副本仅用于写操作。这是一个很好的负载均衡技术,Npgsql 6.0 引入了对它的大力支持(https://www.npgsql.org/doc/failover-and-load-balancing.html)。

除此之外,您绝对可以尝试在 PG 端增加 max_connection - 相应地在客户端增加 Max Pool Size - load-test 这对资源利用率有何影响.