Handling Azure SQL Serverless Auto-Pause in Connection Pools
This guide is part of Azure SQL Connection Management. The serverless compute tier of Azure SQL Database pauses a database that has had no sessions and no user CPU for the configured auto-pause delay, and bills only for storage while it is paused. Connection pools interfere with that in both directions. A pool that keeps idle connections open — or a readiness probe that runs SELECT 1 every ten seconds — counts as a session, so the database never pauses and the cost saving never materialises. And when the database has paused, the first login that wakes it is rejected while it resumes, so the first request after a quiet period fails:
Microsoft.Data.SqlClient.SqlException (0x80131904): Database 'orders' on server 'orders-sql' is not currently
available. Please retry the connection later. If the problem persists, contact customer support, and provide
them the session tracing ID of '7c1e0c9a-…'.
Error Number:40613,State:1,Class:20
The objective is to let the database pause when the application is genuinely idle, and to make the resume invisible to users by retrying the connection within a bounded time.
Rapid incident diagnosis
Is the database pausing at all? The activity log records pause and resume operations. If a database on the serverless tier shows no pauses over a weekend with no traffic, something is holding sessions:
az monitor activity-log list --resource-id "$(az sql db show -g rg-orders -s orders-sql -n orders --query id -o tsv)" \
--start-time 2026-09-12T00:00:00Z --query "[?contains(operationName.value, 'pause') || contains(operationName.value, 'resume')].{op:operationName.value, at:eventTimestamp}" -o table
Who holds sessions while the application is idle? Query the database during a quiet period:
SELECT s.program_name, s.host_name, s.login_name, COUNT(*) AS sessions,
MAX(DATEDIFF(SECOND, s.last_request_end_time, SYSDATETIME())) AS max_idle_seconds
FROM sys.dm_exec_sessions AS s
WHERE s.is_user_process = 1
GROUP BY s.program_name, s.host_name, s.login_name
ORDER BY sessions DESC;
Typical findings are pools with a non-zero minimum (Min Pool Size, HikariCP minimumIdle), long idle timeouts, monitoring agents, and application health endpoints that query the database.
Are errors caused by resume? Error 40613 clustered at the first requests after idle periods, with successes a few seconds to a minute later, is the resume signature. Correlate application errors with resume events from the activity log. If 40613 appears during steady traffic, it is a reconfiguration or failover instead — still transient, but not caused by auto-pause, and covered in Configuring Retry Policies for Azure SQL Transient Faults.
Is the retry budget too short? If the application does retry but still fails, compare its total retry window with the observed resume time. A policy of three attempts one second apart gives up after about three seconds; resume usually takes longer.
Mathematical sizing or parameter formula
When can the database pause? The last pool connection closes after the pool’s idle timeout; the database then needs a full auto-pause delay with no sessions:
earliest_pause = last_request + pool_idle_timeout + autopause_delay
Any periodic database access with an interval shorter than pool_idle_timeout + autopause_delay prevents pausing entirely. With a 5-minute idle timeout and a 60-minute delay, a probe or scheduled job that queries the database more often than every 65 minutes keeps the database awake.
What does resume cost the first request? The first login fails, and the client must retry until the database is online. The retry window has to exceed the resume time:
retry_window = Σ_{i=1..n} min( base × 2^(i−1), max_delay ) ≥ p99_resume_time
With a base delay of 2 seconds, a maximum delay of 15 seconds and 6 attempts: 2 + 4 + 8 + 15 + 15 + 15 = 59 seconds of waiting, enough for a typical resume measured in seconds to tens of seconds, with margin. Compare this window with the request deadline: an interactive request with a 30-second gateway timeout cannot wait 59 seconds, so either accept a failed first request after long idle periods or disable auto-pause for user-facing databases.
Is pausing worth it? Compute billing on serverless accrues per second while the database is online, subject to a minimum vCore and memory charge. A rough monthly saving is:
saving ≈ paused_hours × min_vcores × price_per_vcore_hour
A development database idle 16 hours a day saves roughly two-thirds of its compute cost; a production database idle only a few minutes at a time saves nothing and pays for resume latency.
Exact remediation & configuration
Let the pool empty when idle. For SqlClient, set a zero minimum and let the pool’s own pruning close idle connections; SqlClient removes connections that have been idle for roughly four to eight minutes when the pool is above its minimum:
Server=tcp:orders-sql.database.windows.net,1433;Database=orders;Authentication=Active Directory Default;
Min Pool Size=0;Max Pool Size=40;Connect Timeout=30;ConnectRetryCount=3;ConnectRetryInterval=10;Encrypt=True
For HikariCP against the Microsoft JDBC driver:
spring:
datasource:
url: jdbc:sqlserver://orders-sql.database.windows.net:1433;database=orders;encrypt=true;connectRetryCount=3;connectRetryInterval=10;loginTimeout=30
hikari:
minimum-idle: 0 # allow the pool to empty
maximum-pool-size: 20
idle-timeout: 300000 # close idle connections after 5 minutes
connection-timeout: 45000 # longer than a typical resume
initialization-fail-timeout: -1 # start even if the database is paused
initialization-fail-timeout: -1 lets the application start while the database is paused instead of failing its first connection attempt at boot.
Retry the connection, with backoff, for 40613. In .NET, configure SqlClient’s retry logic so Open retries transient errors, including 40613, with exponential backoff:
var retry = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(new SqlRetryLogicOption
{
NumberOfTries = 6,
DeltaTime = TimeSpan.FromSeconds(2),
MaxTimeInterval = TimeSpan.FromSeconds(15),
TransientErrors = new[] { 40613, 40197, 40501, 49918, 49919, 49920, 4221 }
});
await using var conn = new SqlConnection(connectionString) { RetryLogicProvider = retry };
await conn.OpenAsync(cancellationToken);
With Entity Framework Core, the execution strategy covers both connection and command failures:
options.UseSqlServer(connectionString, sql => sql.EnableRetryOnFailure(
maxRetryCount: 6, maxRetryDelay: TimeSpan.FromSeconds(15), errorNumbersToAdd: new[] { 40613 }));
Keep health checks off the database. A liveness or readiness probe should check that the process can serve, not that the database is awake. Expose database health on a separate endpoint used by monitoring at a long interval, or not at all for serverless databases:
readinessProbe:
httpGet: { path: /health/ready, port: 8080 } # process-only checks
periodSeconds: 10
Decide per database. For user-facing production databases where the first request after idle must not wait, set auto-pause off (--auto-pause-delay -1) and keep the serverless tier only for its compute autoscaling. For development, test and batch databases, keep auto-pause on and accept the resume delay:
az sql db update -g rg-orders -s orders-sql -n orders-dev --auto-pause-delay 60
az sql db update -g rg-orders -s orders-sql -n orders --auto-pause-delay -1
Validation & verification
Pausing happens. Stop traffic to a test environment and confirm the database pauses after pool_idle_timeout + autopause_delay. The database state is visible directly:
az sql db show -g rg-orders -s orders-sql -n orders-dev --query "{status:status, pausedDelay:autoPauseDelay}" -o table
status should become Paused. If it does not, rerun the sys.dm_exec_sessions query from the diagnosis to find the remaining session.
Resume is absorbed. With the database paused, send a single request that needs the database and record its latency and outcome. It must succeed, with latency roughly equal to the resume time. Application logs should show the retried 40613 attempts at debug level, not as request failures.
No retry storm. During resume, many requests may arrive at once. Confirm total login attempts stay bounded — each pool opens at most maximum-pool-size connections and each retries at most six times:
SELECT COUNT(*) AS failed_logins_last_hour
FROM sys.event_log
WHERE event_type = 'connection_failed' AND start_time > DATEADD(HOUR, -1, SYSUTCDATETIME());
Run it in the master database of the logical server; the count should be small and proportional to instances, not to requests.
Probes are silent. With the application running but idle, sys.dm_exec_sessions should show no sessions from the application’s program_name once the pool idle timeout has passed.
Designing around resume latency
When a user-facing database must stay on the serverless tier with auto-pause enabled, the application can be designed so that resume is rarely visible.
Warm before users arrive. If traffic follows a working-day pattern, a scheduled job that issues one query a few minutes before the day starts resumes the database while nobody is waiting. The job must then stop, so the database can pause again in the evening:
# Azure Container Apps job, weekdays 07:50 UTC
schedule: "50 7 * * 1-5"
command: ["sqlcmd", "-S", "orders-sql.database.windows.net", "-d", "orders-dev", "-G", "-Q", "SELECT 1"]
Return a friendly response while resuming. For internal tools, catching a 40613 that outlasts the retry window and returning a page that says the database is starting, with automatic refresh, is better than a generic error. The request can trigger resume and the user retries seconds later.
Separate always-on paths from pausable ones. A service whose authentication data lives in the same database as its reporting data cannot let either pause. Moving small, frequently read data to a separate always-on database or a cache lets the large, occasionally used database pause without affecting login.
| Pattern | Resume visibility | Cost effect |
|---|---|---|
| Auto-pause off | None | Minimum compute billed continuously |
| Scheduled warm-up before business hours | Rare, outside the window | Pauses overnight and at weekends |
| Retry window covering resume | Slow first request | Pauses whenever idle |
| Friendly “starting” response | Visible but understandable | Pauses whenever idle |
| Split hot and cold data | Hot path never waits | Hot database always on, cold one pauses |
Measure resume time for your database rather than relying on typical figures. Pause it deliberately, send a single login, and record the time until a query succeeds; repeat several times at different sizes of data and different times of day. The p99 of those measurements is the number the retry window and any user-facing timeout must cover.
Frequently Asked Questions
Does a paused database lose pooled connections?
Will ConnectRetryCount alone handle resume?
ConnectRetryCount × ConnectRetryInterval — is modest by default and fixed rather than exponential. Configurable retry logic or an execution strategy gives better control over the total window and which errors are retried.Does elastic pool or Hyperscale behave the same?
Can a pool minimum keep the database warm cheaply?
Does a failed login during resume count against any limit?
Related
- Azure SQL Connection Management — the parent guide to Azure SQL session and worker limits.
- Configuring Retry Policies for Azure SQL Transient Faults — the retry policy design this page applies to resume.
- Handling Azure SQL Connection Throttling — when session and worker limits, not pausing, reject logins.
- Tuning Max Pool Size in SqlClient Connection Strings — the pool parameters used above.