비명 지르는 DB 커넥션 풀을 위한 해법, RDS Proxy

RDX Proxy의 특징과 사용하는 이유, 그리고 부하 테스트에서 비명 지르는 내 RDS에 적용하는 방법까지

Jun Noh

어제 artillery를 셋팅하고 초당 2명씩 들어와서 2분 정도 체류하는 시나리오로 부하 테스트를 진행했었는데… 결과가 좀 이상했다.

테스트가 5분 정도 지나가니까 가상 유저 대부분의 반응 속도가 너무 느렸고, 심지어 1분 넘게 기다리다가 timeout이 난 경우도 50%가 넘었다.

 "summaries": {
        "http.response_time": {
          "min": 15440,
          "max": 59610,
          "count": 201,
          "mean": 38446.3,
          "p50": 36691.5,
          "median": 36691.5,
          "p75": 40550.5,
          "p90": 52591.6,
          "p95": 54738,
          "p99": 55843.8,
          "p999": 55843.8
        },
        ...
        

이 정도면 거의… 서버가 죽었다고 봐도 무방하다.

아무리 알파 테스트용으로 셋팅한 단일 노드의 medium 서버여도, 이건 말이 안된다. 고작 동접자 10명의 2분 체류도 감당을 못한다고..?

도저히 내 상식으로는 이해가 안되어서 좀 뒤져봤다. 어디가 문제지

RDS의 max Connection pool

가장 먼저 rds의 max connection pool을 봤다. 아무리 생각해도 DB 커넥션을 물고 있는 게 많아지니 요청이 연달아 200개 정도가 넘으면 그 다음부터는 지연시간이 생기는 느낌이어서..

SHOW max_connections;

>> 198

역시, 커넥션 풀이 198개 밖에 없었다.

이걸 좀 늘리고 싶었지만, RDS 셋팅 자체를 4GB 정도의 메모리로 해서… 늘렸다간 RDS가 죽어버릴 거 같았다.

그럼 어떻게 해야할까?: RDS Proxy

이런 경우에는 도대체 어떻게 해야할까? In memory DB를 앞단에 셋팅해서 connection을 관리할까? << 이건 시간이 너무 오래걸리고 코드도 뜯을게 많다.

그럼??

이럴 때 사용할 수 있는 게 RDS Proxy이다.

RDS Proxy란 무엇인가?

데이터베이스와 애플리케이션 서버 사이에서 커넥션(연결)을 관리해주는 대리인이다.

왜 써야 하는가? (The Problem)

보통 PostgreSQL이나 MySQL은 연결 하나당 메모리를 점유한다. 사양이 낮은 DB(예: 4GB RAM)는 한 번에 맺을 수 있는 연결(max_connections)이 내 경우처럼 189개 정도로 제한적이다.

하지만 입소문을 타서 2,800명이 동시에 접속하면 어떻게 될까?

  • Connection Hell: 189명 빼고 나머지는 문 앞에서 줄 서다 타임아웃(503, 504 에러)이 난다.
  • Server Crash: DB가 몰려드는 연결 요청을 처리하다가 메모리 부족으로 뻗어버린다.

RDS Proxy의 장점 (The Solution)

  1. Connection Pooling: 서버가 맺은 수천 개의 연결을 Proxy가 쥐고 있다가, 실제 쿼리가 날아올 때만 DB의 여유 있는 통로로 번개같이 전달한다. (커넥션 돌려막기)

  2. Failover 보호: DB가 점검이나 장애로 죽어서 교체될 때, Proxy가 연결을 붙잡고 있어준다. 앱에서는 “잠시 느려졌나?” 정도로 느끼고 서버가 터지지 않는다.

  3. 보안 강화: 코드에 DB 비번을 직접 적지 않고 AWS Secrets Manager를 통해 안전하게 관리한다.

RDS Proxy 구축 Step-by-Step

Step 1: 금고 만들기 (AWS Secrets Manager)

Proxy가 DB에 대신 로그인할 때 쓸 아이디/비번을 저장해야 한다.

  1. Secrets Manager 콘솔 -> ‘새 비밀 저장’
  2. 비밀 유형: ‘RDS 데이터베이스 자격 증명’ 선택
  3. 자격 증명: * 사용자 이름: username (※ 실제 DB 유저명과 토씨 하나 안 틀리고 똑같아야 함!)
    • 암호: password
  4. 대상 데이터베이스: 연결할 RDS 인스턴스 선택
  5. 비밀 이름: babple/prod/db-creds (나중에 IAM에서 쓸 이름)
  6. ARN 복사: 생성 후 상세 페이지에서 arn:aws:secretsmanager:... 주소를 따로 메모해둔다.

Step 2: 열쇠 만들기 (IAM Policy & Role)

Proxy가 금고(Secret)를 열 수 있는 권한을 주는 과정이다.

  1. IAM 정책(Policy) 생성: ‘JSON’ 탭에 아래 내용 입력

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["secretsmanager:GetSecretValue", "kms:Decrypt"],
          "Resource": ["아까_복사한_Secret_ARN"]
        }
      ]
    }
  2. IAM 역할(Role) 생성:

    • 신뢰할 수 있는 엔터티 유형: ‘AWS 서비스’ -> ‘RDS’
    • 정책 연결: 방금 만든 정책 선택
    • 신뢰 관계(Trust Relationship) 확인: rds.amazonaws.com이 포함되어 있는지 꼭 확인!

Step 3: 중개인 세우기 (RDS Proxy 생성)

  1. RDS 콘솔 -> 프록시 -> 프록시 생성
  2. 구성: * Engine family: PostgreSQL
    • Target group: 대상 RDS 인스턴스 선택
  3. 인증 (Authentication):
    • Secrets Manager 비밀: Step 1에서 만든 비밀 선택
    • IAM 역할: Step 2에서 만든 역할 선택
    • ※ 중요: IAM 인증(IAM Authentication)은 **‘비활성화(Disabled)‘**로 둔다. (앱에서 일반 패스워드를 쓸 것이기 때문)
  4. 연결성 (Connectivity): * RDS와 같은 VPC, 같은 서브넷(최소 2개) 선택

Step 4: 통행증 발급 (보안 그룹 설정)

이걸 안 하면 “연결 중…”만 뜨다가 망한다. 계단식으로 열어줘야 한다.

  1. Proxy 보안 그룹: 인바운드 5432 허용 (Source: EC2 서버 보안 그룹 ID)
  2. RDS 보안 그룹: 인바운드 5432 허용 (Source: Proxy 보안 그룹 ID)

백엔드 코드 적용 (Integration)

이제 엔드포인트만 바꿔주면 끝이다.

# .env 파일 수정
DB_HOST=for-xxxx-prod-proxy.proxy-xxxx.ap-northeast-2.rds.amazonaws.com # 프록시 엔드포인트
DB_PORT=5432
DB_USERNAME=username
DB_PASSWORD=password

변경 후 테스트 보고서

"http.response_time": {
          "min": 17,
          "max": 59675,
          "count": 344,
          "mean": 14184.2,
          "p50": 14621.8,
          "median": 14621.8,
          "p75": 21813.5,
          "p90": 25598.5,
          "p95": 26115.6,
          "p99": 27181.5,
          "p999": 56972
        },

이제 아까보다는 2배 정도 빠른 응답 속도를 가지게 되었고, min은 진짜 말도 안되게 차이가 나긴하는데… 여전히 무거운 쿼리가 문제인지, 인스턴스 자체가 문제인지 병목 현상이 있긴 하다.

하지만 1분 이상 걸리면서 timeout이 뜨는 경우는 아예 줄어들었다.

마침

다른 글 보기