Tuning Aurora Serverless v2 Connection Limits

This guide is part of AWS Aurora Connection Scaling. It targets one specific failure mode: connection rejections on Aurora Serverless v2 caused by the engine’s max_connections ceiling moving with capacity. Two opposite symptoms come from the same root cause. Under sustained load you watch the connection count flatline while ACU keeps climbing — the pool, not the engine, is the bottleneck. At low traffic, when capacity has scaled back to the minimum, the application starts logging:

FATAL: remaining connection slots are reserved for non-replication superuser connections

The pool that was perfectly safe at 16 ACU is now overrunning a 2-ACU ceiling. On Serverless v2 the connection ceiling is not a constant — it is recomputed from the memory available at the current ACU value. If you size pools against peak capacity, they reject connections at the floor; if you size against the floor, you leave throughput on the table at peak. This guide gives the arithmetic to size against the floor correctly, plus the remediation knobs.

Rapid incident diagnosis

Start by separating the two failure shapes, because they need opposite fixes.

Rejections at low traffic (most common). Pull the CloudWatch ServerlessDatabaseCapacity metric at the time of the errors. If ACU is at or near its configured minimum, the engine ceiling has shrunk to its floor value and the static pool size now exceeds it. The fix is either a higher min ACU or a smaller pool — covered below.

Connections flatline while ACU still rises. If DatabaseConnections plateaus at a round number (the sum of your pool sizes) while ACUUtilization is below 100% and capacity is still increasing, the engine has room but the pool will not open more connections. The pool maximumPoolSize is the limiter, not Aurora. Raise the pool ceiling — but only after confirming the engine ceiling at min ACU still covers it.

Confirm the live picture from the engine itself:

SELECT current_setting('max_connections')::int AS ceiling,
       count(*)                                 AS in_use
FROM pg_stat_activity;

Run this while capacity is at its minimum (off-peak) to see the worst-case ceiling. That number is the constraint every pool must respect. The broader signal set for an over-pressured pool is in Detecting Connection Pool Saturation; here the tell is in_use approaching ceiling specifically when ServerlessDatabaseCapacity is low.

The ACU to memory to max_connections relationship

Serverless v2 capacity is measured in ACUs. One ACU is approximately 2 GiB of memory. The engine computes its connection ceiling from the memory available at the current capacity using the same formula as provisioned Aurora PostgreSQL:

LEAST({DBInstanceClassMemory/9531392}, 5000)

DBInstanceClassMemory tracks the memory provided by the current ACU value, so as ACU scales the ceiling scales with it. The chain is: ACU → memory → max_connections. The divisor 9531392 is roughly 9.1 MiB per connection. Approximating DBInstanceClassMemory as ACU x 2 GiB (minus engine reservation) gives a usable estimate of the ceiling at any capacity.

Worked numeric example

Take a cluster configured with min 2 ACU, max 16 ACU.

At the floor (2 ACU):

  • Memory ≈ 2 ACU x 2 GiB = 4 GiB ≈ 4,294,967,296 bytes, less engine reservation.
  • Ceiling ≈ 4,294,967,296 / 9,531,392 ≈ 450 connections (in practice ~410 after the engine’s own reserved slots).

At the ceiling (16 ACU):

  • Memory ≈ 16 ACU x 2 GiB = 32 GiB ≈ 34,359,738,368 bytes.
  • Ceiling ≈ 34,359,738,368 / 9,531,392 ≈ 3,604 connections.

So this single cluster swings between roughly 410 and 3,600 connection slots depending on capacity. The number that matters for safety is the floor: ~410. Now apply the deployment math. With a 0.7 safety factor for reserved and superuser slots and failover headroom:

safe_total_connections = 410 x 0.7 ≈ 287

If you run 8 application instances, each with a combined write + read pool, the per-instance budget is:

per_instance_pool = 287 / 8 ≈ 35 connections

Split that, for example, as a write pool of 12 and a read pool of 23 per instance. This stays under the ceiling even when capacity has scaled all the way back to 2 ACU off-peak — which is exactly when the rejections were happening.

Exact remediation & configuration

There are three independent levers. Use them together.

1. Raise the minimum ACU floor. The cheapest fix for “rejected at low traffic” is to stop the floor from getting so low. Raising min ACU from 2 to 4 roughly doubles the floor ceiling (to ~820 connections) so a pool sized for moderate concurrency stops overrunning it. This trades a little idle cost for connection headroom.

aws rds modify-db-cluster \
  --db-cluster-identifier mycluster \
  --serverless-v2-scaling-configuration MinCapacity=4,MaxCapacity=16 \
  --apply-immediately

2. Size pools against the floor, not the peak. Set maximumPoolSize from the min-ACU ceiling and the per-instance budget computed above. Apply it as a rolling restart so no single moment doubles the connection count.

# Per application instance, sized for the 2-ACU floor (~410), 8 instances
spring:
  datasource:
    write:
      hikari:
        maximum-pool-size: 12
        minimum-idle: 2            # keep idle low so the floor isn't held hostage
        connection-timeout: 4000
        max-lifetime: 1500000
        pool-name: aurora-sv2-writer
    read:
      hikari:
        maximum-pool-size: 23
        minimum-idle: 2
        connection-timeout: 4000
        read-only: true
        pool-name: aurora-sv2-reader

Keep minimum-idle small. A high minimum-idle across many instances holds connections open even at idle, defeating the cost benefit of Serverless v2 scaling down and consuming floor-level slots for nothing.

3. Put RDS Proxy in front. The most robust answer for a fan-out or serverless application tier is to multiplex through AWS RDS Proxy Connection Pooling. The proxy holds a bounded pool of backend connections and lends them per transaction, so hundreds of client connections ride on a backend count that fits comfortably under the 2-ACU floor. Configure the proxy’s MaxConnectionsPercent to leave headroom — for example 75% — so it never itself overruns the ceiling at min ACU.

For the underlying pool-acquisition timeout behavior that makes these failures surface quickly instead of hanging, see Connection Acquisition Timeout Strategies.

Validation & verification

After applying changes, verify against the floor, not the peak. Force or wait for off-peak so capacity scales to minimum, then confirm the connection count stays comfortably under the ceiling:

SELECT current_setting('max_connections')::int                                   AS ceiling,
       count(*)                                                                   AS in_use,
       round(100.0 * count(*) / current_setting('max_connections')::int, 1)       AS pct_used
FROM pg_stat_activity;

pct_used should sit well below 80% at the floor. Break the count down by application to spot a misconfigured service:

SELECT application_name, state, count(*)
FROM pg_stat_activity
GROUP BY application_name, state
ORDER BY count DESC;

On the CloudWatch side, overlay three metrics on one graph: DatabaseConnections, ServerlessDatabaseCapacity (ACU), and ACUUtilization. A healthy configuration shows DatabaseConnections tracking below the ceiling at every ACU value, with no flat ceiling on connections while ACU still has room to climb. If connections plateau while ACU rises, the pool is the limiter and you can raise maximumPoolSize — provided the new total still fits the min-ACU floor. The endpoint-level interpretation of these metrics across writer and readers is detailed in the parent guide, AWS Aurora Connection Scaling.

Frequently Asked Questions

Why does my pool work fine all day and only reject connections at night?
Aurora Serverless v2 scales capacity down during low traffic, and the max_connections ceiling shrinks with it. A pool sized for daytime capacity exceeds the reduced nighttime ceiling. Size pools against the minimum ACU floor, or raise the min ACU so the floor never drops that far.
Does raising max ACU increase my connection limit?
Only at peak. The ceiling at max ACU is high, but the binding constraint is the ceiling at min ACU, because that is the smallest value max_connections will take. Raising max ACU does nothing for the low-traffic rejections — raise min ACU instead.
Can I just set max_connections to a fixed high number in the parameter group?
You can, but it is dangerous on Serverless v2. A literal ceiling lets clients open more backends than the memory at low ACU can support, and each PostgreSQL backend consumes work_mem-scaled RAM. That starves the engine and can stall scaling. Leave the memory formula in place and constrain demand at the pool or proxy layer.
How do I know whether the pool or the engine is my bottleneck?
Overlay DatabaseConnections and ServerlessDatabaseCapacity. If connections plateau at the sum of your pool sizes while ACU is still rising and ACUUtilization is under 100%, the pool is the limiter — raise maximumPoolSize. If connections approach the engine ceiling while ACU is low, the engine floor is the limiter — raise min ACU or add RDS Proxy.
Should I use RDS Proxy with Serverless v2?
For serverless or high-fan-out application tiers, yes. RDS Proxy multiplexes many client connections onto a small backend pool that fits under the min-ACU ceiling, and it holds connections through scaling and failover events. For a small, fixed set of long-running application instances, well-sized client pools may be enough.