| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- RESTClient
- GitHub Actions
- 도메인 주도 개발 시작하기
- Spring Batch
- K3S
- 최범균
- cicd
- 모이삼
- 불변객체
- ddd
- 이펙티브자바
- 백엔드
- Domain Driven Design
- JPQL
- java
- 중간 지점 추천
- 한국대학생it경영학회
- JPA
- 큐시즘
- Spring
- 쿠버네티스
- 중간 장소 추천
- kusitms
- 자바 ORM 표준 JPA 프로그래밍
- springboot
- 모임 장소 추천
- Container Registry
- redis
- 객체지향 쿼리 언어
- 약속 장소 추천
- Today
- Total
코딩은 마라톤
[PostgreSQL] FATAL: remaining connection slots are reserved for roles with privileges of the "rds_reserved" role 해결하기 본문
[PostgreSQL] FATAL: remaining connection slots are reserved for roles with privileges of the "rds_reserved" role 해결하기
anxi 2025. 10. 21. 20:40🤦🏻♂️ 문제 : 외부에서 DB 접속이 되지 않음

최근 모이삼 프로젝트 git이 꼬여 제거하고 다시 clone 하여 로컬 환경 설정을 진행했다.
IDE에서 DB를 연결하는 도중, 위와 같은 에러가 발생했다.

RDS를 사용하고 있어 확인해 보니 "현재 활동"의 연결 수가 72개였다. 현재 활동이란 Connection 연결 수를 의미한다.

로그를 확인해 보니 위 에러가 반복적으로 발생했음을 알 수 있었다.
DB가 허용하는 최대 연결 수(Max Connections)가 완전히 꽉 찼으며, 일반 사용자를 위한 연결 슬롯은 0개입니다. 이제 남은 연결 자리는 AWS가 DB 관리를 위해 확보해 둔 비상용 슬롯뿐입니다.
즉, AWS가 아닌 우리(사용자)는 DB 연결을 할 수 없음을 의미한다.
왜 이런 문제가 발생했을까?
🙌 Client와 RDS를 중개하는 EC2 서버가 존재

메인 서버는 Naver Cloud Server를 사용하고 있다. 그리고 DB는 AWS의 RDS를 사용한다.
메인 서버에서 DB의 연결은 AWS의 EC2로 중개하여 구축했다.
위와 같이 구축한 이유는 다음과 같다.
- AWS 프리티어의 이점을 살리기 위함.
- Naver Cloud의 DB 인스턴스 비용이 비쌈.
- Naver Cloud와 AWS 멀티 클라우드 환경에서의 연결이 가능하지만 비용 발생.
구축 이유를 요약하자면, 비용을 들이지 않고 구축하기를 바랐다.
그래서 에러 원인을 찾기 위해 중개 서버인 EC2를 확인했고, 그 결과..

수많은 socat 프로세스가 존재했고, 이는 RDS에서 보았던 현재 활동의 수와 일치함을 확인했다.
😢 기존 EC2, RDS 연결 방식 : socat을 이용하여 포트포워딩
메인 서버에서 RDS를 접속하기 위해서 EC2가 포트포워딩을 해줘야 했다. EC2를 포트포워딩 하기 위해 나는 socat을 사용했다.
socat은 소켓(Socket) 생성 및 소켓 간 데이터 전송 등의 데이터 송신 등의 기능을 제공하는 프로그램이다.
# EC2
$ nohup socat TCP-LISTEN:{EC2_PORT},fork TCP:{RDS_ADDRESS}:{RDS_PORT} &
EC2의 특정 포트로 들어온 연결을 RDS의 특정 포트로 연결하여, 메인 서버에서 EC2 IP를 사용해 RDS에 접근하도록 했다.
앞의 nohup은 터미널 세션이 끝나도 동작하도록, 뒤에 ampersand(&)는 백그라운드에서 실행 가능하기 위해 설정했다.
하지만 fork 옵션에서 문제가 발생한다.
fork 옵션은 자식 프로세스를 생성하기 때문에 다수의 connection을 만들기 위해 필수적인 옵션이다. 그러나 프로세스가 계속 누적되고, 누적된 프로세스를 청소하질 않아 위의 문제가 발생했다.
그래서 임시방편으로 누적된 프로세스를 제거하기 위해 killall socat을 사용하여 socat의 모든 프로세스를 제거했다.
😊 해결 방법 : PgBouncer를 이용
1시간마다 프로세스를 클린업 하도록 자동화 스크립트를 작성하면 socat을 사용하더라도 괜찮을 수 있다. 다만 단순히 프로세스를 클린업 하는 건 좋지 않다고 생각했다.
예를 들면, 정각에 클린업 이후 다수의 db connection이 발생했을 경우에 초기 연결 비용이 발생할 수 있다. 또한 현재 사용하고 있는 프로세스가 db 트랜잭션 연결 중인지, idle 상태인지 구분하기 어렵다고 보았다.
그러던 중 PostgreSQL 프로세스 연결을 도와주는 도구인 PgBouncer를 알게 되었다.

그리고 PgBouncer 관련 글을 읽던 와중, 세 번째 방식인 전용 호스트를 두는 방법이 중개 서버 EC2와 유사하다고 느껴 적용하게 되었다.
🔧 적용 과정
적용 방식은 https://postgresql.kr/blog/pgbouncer.html을 참고했습니다.
1. PgBouncer 설치하기
$ sudo apt update
$ sudo apt install pgbouncer
2. /etc/pgbouncer/pgbouncer.ini 설정하기
/etc/pgbouncer 하위에 pgbouncer.ini와 userlist.txt가 존재한다.
pgbouncer.ini는 응용 프로그램 설정 파일, userlist.txt는 사용자 인증 파일이다.
값 설정은 위 링크를 참고하여 서비스에 맞게 설정하면 된다.
모이삼은 다음과 같이 설정했다.
$ sudo vi pgbouncer.imi
[databases] # PgBouncer가 RDS 인스턴스에 접속할 때 사용할 정보
<RDS 별칭> = host=<RDS 엔드포인트> port=5432 dbname=<RDS 이름> user=<RDS 사용자 이름>
ex) mydb = host=mydb.xxxx.rds.amazonaws.com port=5432 dbname=app user=admin
[pgbouncer]
listen_addr = * # 모든 IP 허용
listen_port = 5432
;; any, trust, plain, md5, cert, hba, pam
auth_type = md5 # 암호 방식
auth_file = /etc/pgbouncer/userlist.txt # 사용자 인증 파일 위치
;; When server connection is released back to pool:
;; session - after client disconnects (default)
;; transaction - after transaction finishes
;; statement - after statement finishes
;; 트랜잭션이 끝날 때마다 풀에 반납하도록 함
pool_mode = transaction
;; Comma-separated list of parameters to ignore when given in startup packet.
;; 최신 JDBC 드라이버가 PgBouncer가 지원하지 않는 시작 파라미터를 보낼 때 연결이 거부되는 오류를 방지
ignore_startup_parameters = extra_float_digits
;; PgBouncer가 RDS로 유지하는 connection 수
default_pool_size = 40
;; 풀에 들어있는 최소 connection 수
min_pool_size = 10
;; RDS connection 슬롯 부족을 방지하기 위해 default_pool_size와 동일하게 설정
max_db_connections = 40
;; PgBouncer와 RDS 간의 연결을 1시간마다 주기적으로 재생성
;; 장기 연결로 인한 DB 서버의 메모리 누수 방지
server_lifetime = 3600
;; 사용되지 않고 풀에 남아 있는 RDS 연결을 10분 후 자동으로 정리
;; 불필요한 DB 연결 슬롯 낭비 방지
server_idle_timeout = 600
;; PgBouncer에 연결한 클라이언트가 1분 동안 아무 활동이 없으면 연결을 강제로 끊음.
client_idle_timeout = 60
3. /etc/pgbouncer/userlist.txt 설정하기
"사용자이름" "비밀번호"로 작성하면 되고, 비밀번호는 DB 암호를 사용하면 된다. (AWS RDS PostgreSQL 17 기준)
$ sudo vi userlist.txt
# "사용자 이름" "비밀번호"
"admin" "1234"
🚨 비밀번호를 scram-sha-256 방식으로 바꿔서 넣어야 한다고 하는데, pgbouncer가 동작하지 않았다.
만약 AWS RDS PostgreSQL 17 버전을 사용한다면, 비밀번호를 평문으로 넣어서 실행하면 될 것이다.
4. PgBouncer 재시작하기
$ sudo systemctl restart pgbouncer
5. AWS EC2 IP를 사용해 RDS 접속하기
# application.yml
spring:
datasource:
# EC2 인스턴스의 IP 주소 사용
url: jdbc:postgresql://<EC2 IP 주소>/<Database>
Spring Boot는 위와 같이 변경하면 되고, Data Grip을 사용할 경우에도 EC2 IP 주소를 통해 접속 가능하다.
🤭 마무리
FATAL: remaining connection slots are reserved for roles with privileges of the "rds_reserved" role
AWS RDS 연결 과정에서 마주한 에러를 PgBouncer를 사용해 해결했다.
이전에는 socat을 사용했고, 이번에는 PgBouncer를 사용하는 방식으로 변경했다.
돈을 내지 않기 위해 이런저런 방법을 사용해 봤는데, 나름대로 괜찮은 방법인 거 같다.
RDS의 Public Access를 키더라도, 비용 발생할 뿐만 아니라 보안적으로도 좋지 않고,, Naver Cloud의 DB를 사용하기엔 비용이 너무 비싸다. 메인 서버 내부의 파드로 띄울 수도 있긴 하지만, RDS에서 제공하는 기능을 사용할 수 있음 좋으니까..!
참고