AWS RDS 커넥션 부족? max_connections 계산부터 HikariCP·PgBouncer 설정까지
새벽에 슬랙 알림이 울려서 확인해보니 RDS 대시보드에 "Too many connections" 에러가 빼곡하게 찍혀 있었어요. 트래픽이 갑자기 몰린 것도 아닌데 왜 커넥션이 가득 찼는지, 그때는 정말 감이 안 잡히더라고요. 😓
알고 보니 문제는 DB 자체가 아니라 애플리케이션 쪽 커넥션 풀 설정에 있었어요. 이 경험 이후로 커넥션 풀 튜닝의 중요성을 뼈저리게 느꼈는데요, 오늘은 그때 배운 것들을 정리해서 공유해볼게요!
'Too many connections' 에러, 왜 발생하나요? 🤔
이 에러는 말 그대로 DB 서버가 허용하는 최대 동시 연결 수를 초과했을 때 발생해요. RDS에서 이 한도는 max_connections 파라미터로 결정되는데, 인스턴스 클래스(메모리 크기)에 따라 기본값이 달라요.
근데 진짜 문제는, max_connections 자체보다 커넥션을 낭비하는 구조인 경우가 훨씬 많아요. 흔한 원인들을 보면요:
- 커넥션 풀 없이 매 요청마다 새 연결을 맺고 끊는 경우
- 커넥션 풀 사이즈를 서버 인스턴스 수만큼 곱해서 생각하지 않은 경우
- 슬로우 쿼리가 커넥션을 오래 점유하면서 풀이 고갈되는 경우
- 커넥션 누수(leak) — 사용 후 반환하지 않는 코드 버그
커넥션 풀(Connection Pool)은 미리 일정 수의 DB 연결을 만들어두고 재사용하는 기술이에요. 매번 연결을 새로 맺는 비용(TCP 핸드셰이크 + 인증)을 절약할 수 있어서, 사실상 모든 프로덕션 환경에서 필수예요.
RDS max_connections 기본값 확인하기 📊
먼저 내 RDS가 허용하는 최대 커넥션 수를 알아야 튜닝 기준이 잡혀요. RDS는 인스턴스 메모리에 따라 자동으로 기본값을 계산해요.
📝 max_connections 계산 공식
MySQL RDS: {DBInstanceClassMemory/12582880} (메모리(바이트) ÷ 12MB)
PostgreSQL RDS: LEAST({DBInstanceClassMemory/9531392}, 5000)
| 인스턴스 클래스 | 메모리 | MySQL 기본값 | PostgreSQL 기본값 |
|---|---|---|---|
| db.t3.micro | 1GB | ~85 | ~112 |
| db.t3.medium | 4GB | ~340 | ~450 |
| db.r6g.large | 16GB | ~1365 | ~1802 |
| db.r6g.xlarge | 32GB | ~2730 | ~3604 |
현재 사용 중인 커넥션 수는 SQL로 직접 조회할 수 있어요.
MySQL:
SHOW STATUS LIKE 'Threads_connected';
PostgreSQL:
SELECT count(*) FROM pg_stat_activity;
RDS 파라미터 그룹에서 max_connections를 무작정 올리면 안 돼요! 커넥션 하나당 메모리를 소비하기 때문에, 인스턴스 메모리가 부족해지면 OOM이나 성능 저하로 이어질 수 있어요.
커넥션 풀 사이즈, 어떻게 정하나요? 🧮
커넥션 풀 사이즈를 정하는 건 생각보다 과학적인 접근이 필요해요. 무조건 크게 잡는다고 좋은 게 아니에요. 오히려 작은 풀이 더 나은 성능을 보여주는 경우가 많아요.
📝 커넥션 풀 사이즈 계산 가이드
기본 공식 = (CPU 코어 수 × 2) + 디스크 수
예를 들어 4코어 서버에 SSD 1개라면 (4 × 2) + 1 = 약 9~10개가 시작점이에요.
그리고 반드시 기억해야 할 건, 전체 커넥션 수 = 풀 사이즈 × 앱 서버 인스턴스 수라는 점이에요.
실전 계산 예시 📝
- RDS 인스턴스: db.t3.medium (max_connections ≈ 340)
- 앱 서버: ECS 컨테이너 10개
- 모니터링/관리용 예비 커넥션: 약 40개
1) 사용 가능 커넥션: 340 - 40(예비) = 300개
2) 컨테이너당 풀 사이즈: 300 ÷ 10 = 30개
→ 각 앱 서버의 커넥션 풀을 최대 30으로 설정하면 안전해요.
오토스케일링을 사용한다면 최대 인스턴스 수 기준으로 계산해야 해요. 스케일아웃 시 순간적으로 커넥션이 폭증하면서 에러가 재발할 수 있거든요.
프레임워크별 커넥션 풀 설정 가이드 🛠️
실제로 설정을 바꾸려면 사용하는 프레임워크와 커넥션 풀 라이브러리에 따라 방법이 달라요. 대표적인 것들을 정리해볼게요.
Java (HikariCP) — Spring Boot 환경
spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=300000 spring.datasource.hikari.connection-timeout=20000 spring.datasource.hikari.max-lifetime=1200000
| 설정값 | 역할 | 권장값 |
|---|---|---|
| maximum-pool-size | 최대 커넥션 수 | 10~30 |
| minimum-idle | 유휴 시 유지할 최소 커넥션 | 5~10 |
| idle-timeout | 유휴 커넥션 제거까지 대기 시간 | 300000ms (5분) |
| max-lifetime | 커넥션 최대 수명 | 1200000ms (20분) |
Node.js (pg-pool / mysql2)
const pool = new Pool({ max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 5000, });
Python (SQLAlchemy)
engine = create_engine( DATABASE_URL, pool_size=10, max_overflow=20, pool_timeout=30, pool_recycle=1800, )
PostgreSQL이라면 PgBouncer를 고려하세요 🐘
PostgreSQL은 커넥션 하나당 프로세스를 fork하는 구조라서, 커넥션 수가 많아지면 메모리와 CPU 오버헤드가 꽤 커져요. 이때 PgBouncer라는 경량 커넥션 풀러를 중간에 두면 효과가 확실해요.
PgBouncer는 애플리케이션과 DB 사이에서 커넥션을 중계해주는데요, 수백 개의 앱 커넥션을 수십 개의 실제 DB 커넥션으로 압축해서 전달해요. AWS에서도 RDS Proxy라는 관리형 서비스를 제공하고 있어서, 인프라 관리가 부담스럽다면 이쪽을 추천해요.
| 구분 | PgBouncer (자체 운영) | RDS Proxy (관리형) |
|---|---|---|
| 운영 부담 | EC2에 직접 설치·관리 | AWS가 자동 관리 |
| 비용 | EC2 비용만 | vCPU 시간당 추가 과금 |
| 설정 유연성 | 세밀한 풀 모드 선택 가능 | 제한적 (핀닝 이슈 존재) |
| 적합한 경우 | 세밀한 제어가 필요한 팀 | 운영 간소화를 원하는 팀 |
RDS Proxy는 Prepared Statement를 많이 사용하는 앱에서 핀닝(pinning) 현상이 발생할 수 있어요. 핀닝이 되면 커넥션 다중화 효과가 사라져서 오히려 성능이 떨어질 수 있으니, 도입 전에 꼭 테스트해보세요.
커넥션 누수, 이렇게 잡으세요 🔍
설정을 아무리 잘 해도 커넥션 누수가 있으면 소용없어요. 누수 의심 징후와 확인 방법을 알아볼게요.
- 징후 파악 — CloudWatch에서 DatabaseConnections 지표가 시간이 갈수록 계단식으로 올라간다면 누수를 의심해보세요.
- 유휴 커넥션 조회 — PostgreSQL에서 SELECT * FROM pg_stat_activity WHERE state = 'idle';로 놀고 있는 커넥션을 확인해요.
- 코드 점검 — try-finally 또는 try-with-resources 패턴으로 커넥션이 반드시 반환되도록 코드를 확인해요.
HikariCP를 쓴다면 leak-detection-threshold를 설정해보세요. 커넥션이 일정 시간 이상 반환되지 않으면 경고 로그를 찍어줘서 누수 지점을 빠르게 찾을 수 있어요.
마무리: 핵심 내용 요약 📝
'Too many connections'는 단순히 max_connections를 올린다고 해결되는 문제가 아니에요. 전체 아키텍처를 고려한 체계적인 접근이 필요해요.
- 현황 파악 먼저: 현재 커넥션 수와 max_connections 기본값을 확인하세요.
- 풀 사이즈 계산: 전체 앱 인스턴스 수를 곱한 총 커넥션이 DB 한도를 넘지 않도록 설계하세요.
- 풀러 도입 검토: PostgreSQL은 PgBouncer나 RDS Proxy로 커넥션 다중화를 고려해보세요.
- 누수 방지: leak-detection 설정과 모니터링으로 커넥션 누수를 조기에 발견하세요.
커넥션 풀 튜닝은 한 번 잘 잡아두면 한동안 신경 쓸 일이 없는데, 방치하면 서비스 장애로 직결되는 영역이에요. 혹시 다른 DB 환경에서 겪은 커넥션 이슈가 있다면 댓글로 공유해주세요~ 😊