Azure App Service and Azure SQL Database: Connection Pool Scaling vs SNAT Port Exhaustion
0 reputation · 05 Jun 2023, 15:19 UTC
0 reputation · 05 Jun 2023, 15:19 UTC
When scaling Azure App Service applications that communicate with Azure SQL Database, high volumes of concurrent requests can introduce intermittent latency. This behavior often manifests during rapid traffic spikes rather than steady-state loads, complicating the distinction between application-level bottlenecks and infrastructure limits.
The interaction between the .NET connection pool and the App Service outbound IP infrastructure creates a potential conflict. While increasing the Max Pool Size can reduce request queuing latency, it increases the number of concurrent TCP connections maintained by the instance. This raises the risk of SNAT (Source Network Address Translation) port exhaustion on the App Service outbound IP, which can lead to connection timeouts and increased latency for new requests.
Furthermore, the Azure SQL Database gateway may exhibit increased latency when processing a high volume of simultaneous new connection establishments compared to the reuse of existing pooled connections.
There is a critical trade-off between optimizing for database resource contention and avoiding outbound port exhaustion. It is unclear how to balance the Max Pool Size against the available SNAT port quota to ensure stability during burst concurrency without triggering gateway-level latency.
1. Idle connection lifecycle: Azure App Service keeps a TCP connection open for as long as the application’s connection pool holds a reference to it. When a pooled connection is idle, the underlying TCP socket remains open until the pool is evicted or the application restarts. This means that idle connections continue to occupy an SNAT port, contributing to the pool’s total usage. The environment does not automatically close idle database sockets; it relies on the application’s pooling logic to release them.
2. Aligning pool limits with SNAT capacity: The safest approach is to set the Max Pool Size to a value that is comfortably below the number of SNAT ports available per instance. For a Standard plan (≈1024 ports) a pool size of 500–600 is typical; for Premium v2/v3 (≈2048 ports) you can raise the pool to 1200–1500. This buffer protects against transient spikes and other outbound traffic that also consumes SNAT ports.
The .NET SqlClient library keeps a physical connection alive for the lifetime of a pooled object. When the pool is idle, the socket remains open and the SNAT port stays allocated until the application explicitly disposes the connection or the pool’s Connection Lifetime expires. Therefore, if you set a high Max Pool Size and the pool never reaches its limit, you may still exhaust SNAT ports because many sockets stay open.
Max Pool Size to ≤ 50 % of that value to leave a safety margin for other outbound traffic.Connection Lifetime (e.g., 30 minutes) so that long‑held idle sockets are eventually closed.Connection Resiliency in SqlClient to automatically retry transient SNAT failures.Outbound Connections and SNAT Port Usage metrics in Azure Monitor during load tests to confirm that usage stays below the threshold.connectionString = "Server=tcp:mydb.database.windows.net,1433;Database=mydb;User ID=app;Password=***;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Max Pool Size=500;Connection Lifetime=1800;";
SNAT Port Usage metric for the affected instance.Max Pool Size or increase the plan tier.Could you confirm the exact App Service plan SKU you are using (Standard, Premium v2/v3, ASE, etc.)? Knowing the SNAT port count will allow a more precise recommendation.
Use comments to ask for clarification. Post a solution as an answer.
28,775 reputation · 05 Jun 2023, 23:01 UTC
Enabling a service endpoint (or private endpoint) for Azure SQL Database routes the traffic through the Microsoft backbone, bypassing the App Service outbound NAT. Consequently, connections to SQL no longer consume SNAT ports on the App Service instance, allowing you to increase Max Pool Size without risking port exhaustion. This does not change the database‑side connection pooling behavior; idle pooled connections still hold a socket, but that socket is now a direct backbone link that does not count toward the SNAT quota.