Mô tả lỗi
Ứng dụng của bạn cố gắng mở kết nối database và nhận được thông báo này:
FATAL: too many connections for role "myapp"
Hoặc biến thể này:
FATAL: remaining connection slots are reserved for non-replication superuser connections
Cùng một nguyên nhân gốc. RDS đã đạt đến giới hạn kết nối và từ chối tất cả các kết nối mới.
Nguyên nhân
PostgreSQL trên RDS đặt giới hạn cứng cho số kết nối đồng thời dựa trên bộ nhớ của instance. Công thức: LEAST({DBInstanceClassMemory/9531392}, 5000). Với db.t3.micro, đó là khoảng 60–80 kết nối tổng cộng. Không nhiều.
Chạm đến giới hạn đó và mọi kết nối mới đều thất bại — dù hầu hết các kết nối hiện tại chỉ đang ngồi idle.
Các nguyên nhân phổ biến:
- Không có connection pooler — mỗi thread hoặc process giữ kết nối riêng của nó
- Lambda cold starts — mỗi lần thực thi mới mở một kết nối mới thay vì tái sử dụng
- Connection leaks — kết nối mở nhưng không bao giờ đóng (thường ẩn trong các đường xử lý lỗi)
- Nhiều môi trường dùng chung một RDS instance (dev + staging + prod)
- ORM pool cấu hình sai trên quá nhiều replicas — 10 replicas × 20 kết nối mỗi cái = 200 kết nối, dễ xảy ra
Bước 1: Kiểm tra tình trạng kết nối hiện tại
Trước khi sửa bất cứ điều gì, hãy xem chuyện gì đang thực sự xảy ra. Kết nối với tư cách superuser hoặc rds_superuser và chạy:
SELECT count(*), usename, state
FROM pg_stat_activity
GROUP BY usename, state
ORDER BY count DESC;
So sánh với giới hạn của bạn:
SELECT current_setting('max_connections') AS max,
count(*) AS current
FROM pg_stat_activity;
Nếu bạn gần đạt ngưỡng với hàng chục kết nối idle, hãy kill chúng để có thêm không gian:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND usename = 'myapp'
AND query_start < now() - interval '10 minutes';
Cách sửa 1: Thêm pgBouncer (Khuyến nghị)
pgBouncer nằm giữa ứng dụng của bạn và RDS. Ứng dụng mở hàng trăm "kết nối" tới pgBouncer. pgBouncer gom chúng vào một pool nhỏ các kết nối RDS thật — ví dụ, 1000 kết nối ứng dụng được ánh xạ vào chỉ 20 kết nối RDS.
Deploy nó trên một EC2 instance hoặc như một sidecar container. Cấu hình tối thiểu (/etc/pgbouncer/pgbouncer.ini):
[databases]
mydb = host=mydb.xxxx.us-east-1.rds.amazonaws.com port=5432 dbname=mydb
[pgbouncer]
listen_port = 5432
listen_addr = 0.0.0.0
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
reserve_pool_size = 5
server_idle_timeout = 600
File userlist.txt:
"myapp" "md5hashofpassword"
Trỏ ứng dụng của bạn đến địa chỉ pgBouncer thay vì RDS trực tiếp. Xong — 1000 kết nối ứng dụng, 20 kết nối thật.
Đang chạy Lambda? Dùng RDS Proxy thay thế. Đây là pooler được quản lý bởi AWS, được xây dựng cho serverless nơi bạn không thể giữ tiến trình pgBouncer hoạt động:
aws rds create-db-proxy \
--db-proxy-name myapp-proxy \
--engine-family POSTGRESQL \
--auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:aws:secretsmanager:...","IAMAuth":"DISABLED"}]' \
--role-arn arn:aws:iam::123456789:role/rds-proxy-role \
--vpc-subnet-ids subnet-xxx subnet-yyy \
--vpc-security-group-ids sg-xxx
Cách sửa 2: Tăng max_connections trong Parameter Group
Giải pháp tạm thời, không phải giải pháp thật sự. Tăng giới hạn thông qua một custom parameter group:
aws rds modify-db-parameter-group \
--db-parameter-group-name myapp-params \
--parameters "ParameterName=max_connections,ParameterValue=200,ApplyMethod=pending-reboot"
Áp dụng group và khởi động lại:
aws rds modify-db-instance \
--db-instance-identifier mydb \
--db-parameter-group-name myapp-params \
--apply-immediately
aws rds reboot-db-instance --db-instance-identifier mydb
Đừng vượt quá 2× giá trị mặc định mà không thêm pooler. PostgreSQL cấp phát shared memory cho mỗi kết nối — đẩy quá cao và bạn sẽ đổi lỗi kết nối thành crash OOM.
Cách sửa 3: Tinh chỉnh ORM Connection Pool
Các giá trị mặc định của SQLAlchemy thường quá rộng rãi cho RDS. Hãy thu hẹp lại:
from sqlalchemy import create_engine
engine = create_engine(
DATABASE_URL,
pool_size=5, # số kết nối duy trì mỗi process
max_overflow=10, # kết nối burst tạm thời
pool_timeout=30, # giây chờ trước khi báo lỗi
pool_pre_ping=True, # loại bỏ kết nối cũ trước khi dùng
pool_recycle=1800, # tái sử dụng kết nối sau 30 phút
)
Con số quan trọng: (pool_size + max_overflow) × replicas = tổng số kết nối RDS của bạn. Với 10 replicas và các cài đặt này, đó là 150 kết nối. Chọn instance phù hợp với con số này.
Node.js với pg:
const { Pool } = require('pg');
const pool = new Pool({
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
Cách sửa 4: Xử lý Lambda Connection Leaks
Mô hình thực thi của Lambda là cái bẫy. Mỗi cold start tạo một kết nối mới. Mở kết nối bên trong handler và bạn có nguy cơ tạo kết nối mới mỗi lần gọi — không cái nào được đóng sạch.
Chuyển kết nối ra ngoài handler để các lần gọi warm có thể tái sử dụng:
import psycopg2
import os
# Ngoài handler — tồn tại giữa các lần gọi warm
conn = None
def get_connection():
global conn
if conn is None or conn.closed:
conn = psycopg2.connect(
host=os.environ['DB_HOST'],
database=os.environ['DB_NAME'],
user=os.environ['DB_USER'],
password=os.environ['DB_PASSWORD'],
)
return conn
def handler(event, context):
db = get_connection()
# dùng db...
Điều này giúp ích, nhưng vẫn chỉ là giải pháp tạm thời. Ở mức đồng thời cao, hàng trăm Lambda instance mỗi cái giữ kết nối riêng của nó. RDS Proxy (Cách sửa 1) xử lý vấn đề này gọn hơn — không cần thay đổi handler.
Kiểm tra
Sau khi áp dụng cách sửa, theo dõi số lượng kết nối ổn định lại:
-- Chạy mỗi vài giây để theo dõi trực tiếp
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
Khi pgBouncer đang chạy, kiểm tra bảng thống kê của nó:
psql -h localhost -p 5432 -U pgbouncer pgbouncer -c 'SHOW POOLS;'
So sánh cl_active (kết nối client) với sv_active (kết nối server). Thấy 200 kết nối client được dồn vào 18 kết nối server có nghĩa là pgBouncer đang hoạt động đúng như thiết kế.
Phòng ngừa
- Luôn pool kết nối — pgBouncer cho các dịch vụ chạy dài, RDS Proxy cho Lambda/serverless
- Bật
pool_pre_ping=True(SQLAlchemy) hoặc tương đương để loại bỏ kết nối cũ trước khi chúng gây lỗi - Đặt
idle_in_transaction_session_timeoutđể tự động kill các transaction bị treo:SET idle_in_transaction_session_timeout = '5min'; - Thêm CloudWatch alarm trên
DatabaseConnections— cảnh báo ở 80% củamax_connections, không phải 100% - Đừng chạy workload production với nhiều service trên
db.t3.micro— giới hạn 60–80 kết nối của nó sẽ gây rắc rối cho bạn

