포스트

Amazon RDS Proxy - Aurora MySQL 가용성테스트

Amazon RDS Proxy - Aurora MySQL 가용성테스트

2022년 11월 수행한 테스트를 기반으로 정리한 내용입니다. Amazon Aurora MySQL V2 환경(ap-northeast-2, db.t3.medium × 4 리더 인스턴스)에서 측정한 케이스입니다.

개요

Amazon RDS 프록시를 사용하면 애플리케이션이 데이터베이스 연결을 Pooling 하고 공유하도록 허용하여 확장 기능을 향상할 수 있습니다. RDS 프록시는 애플리케이션 연결을 유지하면서 예비 DB 인스턴스에 자동으로 연결하여 데이터베이스 장애에 대한 애플리케이션의 복원력을 높입니다. 예기치 않은 데이터베이스 트래픽 급증을 처리하는 데도 유용합니다.

급증을 처리하지 않으면 연결 초과 구독 또는 빠른 속도의 새 연결 생성으로 인한 문제가 발생할 수 있습니다. RDS 프록시는 데이터베이스 연결 풀을 설정하고 이 풀에서 연결을 재사용하므로, 매번 새 연결을 여는 데서 오는 메모리 및 CPU 오버헤드를 방지합니다.

Amazon RDS Proxy 동작 방법 (출처: AWS)


구성도 및 설명

구성도

기존 L4 의 특성상 Network 레벨에서만 트래픽을 분산할 뿐, DB 의 Resource 를 제어하지는 못합니다. 반면 Proxy 는 DB 의 Health Check 뿐만 아니라 Connection Caching 이 가능하기 때문에 Failover 에 대해 좀 더 능동적인 대처가 가능합니다.

L4 Switch → RDS Proxy 전환 구성도 (Before After)

장점

RDS Proxy 는 직접 Connection 을 관리하기 때문에 RDS 에 Connection 으로 발생하는 CPU 와 메모리의 오버헤드를 줄여주고, 더 많은 Connection 을 허용합니다. 가장 큰 장점은 Connection Pool 을 제공한다는 점입니다.

Connection Pool 을 애플리케이션에서 직접 관리하지 않아도 RDS Proxy 가 알아서 관리하기 때문에 불필요한 리소스 낭비를 줄일 수 있고, 트래픽이 급증할 때 연결 수를 제어하여 DB 를 보호할 수 있습니다.

제한사항

  • MySQL 의 Proxy 는 3306 포트, PostgreSQL 의 Proxy 는 5432 포트에서만 지원 가능합니다.
  • 각 AWS 계정당 최대 20개의 Proxy 로 제한됩니다 (추가 요청은 AWS Support 에 문의).
  • Serverless V2 는 사용 가능하지만, Serverless V1 은 사용 불가합니다.
  • 데이터베이스와 동일한 VPC 내에서만 사용 가능합니다.
  • 각 Proxy 에 최대 20개의 엔드포인트를 생성·조회·수정·삭제할 수 있습니다.

일반 엔드포인트 vs RDS Proxy — 연결 구조 깊이 보기

DNS 기반 엔드포인트의 한계

클러스터 Reader Endpoint 나 개별 인스턴스 엔드포인트는 모두 DNS 를 통해 동작합니다. 앱이 DNS 를 조회하여 IP 를 받으면 그 IP 로 직접 TCP 커넥션을 맺고, 이후 DNS 는 더 이상 관여하지 않습니다.

문제는 커넥션 풀에 있습니다. 앱 프로세스는 시작 시점에 DNS 를 한 번 조회하고, 그 결과로 풀 사이즈만큼의 커넥션을 모두 같은 IP 에 맺습니다. TTL 이 만료되어도 이미 맺어진 TCP 커넥션은 IP 를 재확인하지 않습니다. 앱 서버가 10대이고 커넥션 풀이 25개라면 250개의 커넥션 중 특정 replica 에 한 번 쏠린 커넥션은, 해당 커넥션이 끊어지기 전까지 계속 같은 인스턴스를 향합니다. DNS 레벨의 분산 비율은 “커넥션 풀 단위” 쏠림을 막지 못합니다.

RDS Proxy 의 연결 구조

RDS Proxy 는 단순한 이름 해석 서비스가 아니라 트래픽 경로 위에 실제로 위치한 중개자입니다. 앱은 Proxy 의 단일 엔드포인트에 연결하고, Proxy 가 내부적으로 Aurora Reader 들과의 커넥션을 별도로 관리합니다.

1
2
3
4
5
6
7
[DNS 기반]
앱 → DNS 조회 → reader-02 IP → 직접 TCP 커넥션
                                ↑ 이후 DNS 관여 없음. 쿼리가 계속 같은 IP 로.

[RDS Proxy]
앱 → Proxy 단일 엔드포인트 → Proxy 가 내부적으로 DB 커넥션 관리
                               ↑ 쿼리가 도달할 때마다 여유 있는 replica 로 전달

Connection Multiplexing: 앱 커넥션이 250개더라도 Proxy 는 실제 쿼리 처리에 필요한 만큼의 DB 커넥션만 유지합니다. Idle 상태의 앱 커넥션은 Proxy 내부에서 공유되므로, DB 의 max_connections 소비가 크게 줄어듭니다.

쿼리 단위 라우팅: Proxy 는 커넥션이 맺어진 시점이 아니라 쿼리가 도달할 때마다 현재 가장 여유 있는 DB 커넥션으로 전달합니다. DNS 조회 결과나 커넥션 풀 편향과 무관하게 실시간 부하 기반 분산이 이루어집니다.

Pinning — Multiplexing 이 제한되는 조건

Proxy 가 항상 Multiplexing 을 적용하지는 않습니다. 다음 상황에서는 앱 커넥션이 특정 DB 커넥션에 Pin(고정) 됩니다.

  • SET 명령으로 세션 변수를 변경할 때
  • 멀티 스테이트먼트 트랜잭션 진행 중
  • GET_LOCK() 등 세션 수준 잠금 사용 시
  • Temporary Table 사용 시

Pin 된 커넥션은 세션이 종료될 때까지 특정 인스턴스를 점유합니다. CloudWatch 의 DatabaseConnectionsCurrentlySessionPinned 메트릭을 정기적으로 확인하고, 이 값이 높다면 쿼리 패턴 검토가 필요합니다.

DBA 시각에서의 체크포인트

항목DNS 기반 엔드포인트RDS Proxy
분산 기준DNS 조회 시점 (확률적)쿼리 도달 시점 (실시간)
DB 실제 커넥션 수앱 풀 합계 그대로Proxy 가 최소화하여 절감
Failover 인지DNS TTL 경과 후Proxy 내부에서 즉시 처리
추가 모니터링해당 없음SessionPinned 메트릭 확인 필수

테스트

Connection Pool Test

None Proxy 의 경우 Aurora 클러스터의 리더 인스턴스 엔드포인트로 클라이언트가 직접 접속하며, Proxy 의 경우 Proxy 를 통해 리더 인스턴스에 연결합니다. 각 클라이언트 접속 후 @@aurora_server_id 가 모두 bjh-test-aurorainstance1 임을 확인하여 동일 인스턴스로 사전 테스트 환경을 맞춰 두었습니다.

Reader Instance 강제 삭제 — 장애 시뮬레이션

연결된 상태에서 임의로 Reader Instance 를 삭제하여 장애를 강제로 발생시켰습니다.

AWS 콘솔에서 Reader Instance 삭제

Failover 결과 비교

항목None ProxyProxy
장애 인지 시점인스턴스 삭제 완료 후 (느림)삭제 진행 중 즉시 감지
Failover 대상bjh-test-aurorainstance2bjh-test-aurorainstance2
Connection 유지 여부끊김 (ERROR 2013 Lost connection)유지 (기존 세션 그대로 이어받음)
Connection ID 변화16 → 101 (새 세션으로 전환)변화 없음
Failover 후 쿼리 응답 시간0.08초 (재접속 후)9.38초 (Failover 로직 포함)

None Proxy 의 경우 Failover 시 기존 Connection 이 끊기고 새 Session 으로 재접속됩니다. Connection ID 도 16에서 101로 변경되어 기존 Transaction 을 유지하지 못한 채 Transaction 을 새로 시작해야 합니다.

반면 Proxy 엔드포인트의 경우 기존 Connection 을 이어받아 동일한 Session 정보로 Failover 가 완료됩니다. 다만 기존 0.01초 걸리던 쿼리가 Failover 로직까지 포함되어 9.38초가 소요되었습니다.

Proxy 는 Connection 을 유지한 채 투명하게 Failover 하지만, 그 대가로 약 9초의 지연이 발생합니다. 애플리케이션의 query timeout 설정을 이보다 여유 있게 잡아 두어야 합니다.


부하에 따른 Load Balance Test — 환경 구성

공통 조건

항목
테스트 도구sysbench 1.0.20 (LuaJIT 2.1.0-beta2)
Thread 수64
테스트 시간300초
데이터10 tables × 1,000,000 rows
리더 인스턴스 수각 4벌
부하 패턴oltp_read_only.lua

부하 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# None Proxy 엔드포인트
sysbench --db-driver=mysql \
  --mysql-host=<aurora-cluster-ro-endpoint> \
  --max-requests=0 --max-time=300 --num-threads=64 \
  --mysql-user=admin --mysql-password=***** \
  --mysql-db=sysbench --table-size=100000 --tables=10 \
  /usr/share/sysbench/oltp_read_only.lua run

# Proxy 엔드포인트
sysbench --db-driver=mysql \
  --mysql-host=<proxy-read-endpoint> \
  --max-requests=0 --max-time=300 --num-threads=64 \
  --mysql-user=admin --mysql-password=***** \
  --mysql-db=sysbench --table-size=100000 --tables=10 \
  /usr/share/sysbench/oltp_read_only.lua run

300초간 64스레드의 SELECT 부하를 각 엔드포인트를 달리하여 진행했습니다.


SYSBENCH 결과

Proxy 엔드포인트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SQL statistics:
    queries performed:
        read:    5,599,706 / write: 0 / other: 799,958 / total: 6,399,664
    transactions:   399,979  (1,332.91 per sec.)
    queries:      6,399,664  (21,326.62 per sec.)
    ignored errors:  0 / reconnects: 0

General statistics:
    total time:              300.0765s
    total number of events:  399,979

Latency (ms):
         min:             24.48
         avg:             48.00
         max:            463.66
    95th percentile:      69.29

Threads fairness:
    events (avg/stddev):          6,249.67 / 1,445.27
    execution time (avg/stddev):   299.9999 / 0.03

None Proxy 엔드포인트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SQL statistics:
    queries performed:
        read:    5,754,098 / write: 0 / other: 822,014 / total: 6,576,112
    transactions:   411,007  (1,369.78 per sec.)
    queries:      6,576,112  (21,916.52 per sec.)
    ignored errors:  0 / reconnects: 0

General statistics:
    total time:              300.0506s
    total number of events:  411,007

Latency (ms):
         min:             11.91
         avg:             46.71
         max:            877.44
    95th percentile:      87.56

Threads fairness:
    events (avg/stddev):          6,421.98 / 2,807.11
    execution time (avg/stddev):   299.9874 / 0.03

수치 비교

지표ProxyNone Proxy
TPS1,332.911,369.78
avg Latency48.00ms46.71ms
95th pct Latency69.29ms87.56ms
max Latency463.66ms877.44ms
Thread stddev (공정성)1,445.272,807.11

TPS 와 avg Latency 는 None Proxy 가 소폭 우세합니다. 그러나 95th percentile, max Latency, Thread stddev(부하 공정성) 면에서는 Proxy 가 월등히 안정적입니다.


부하에 따른 Load Balance Test 결과 — Proxy 엔드포인트

Read Instance 별 CPU 사용률

Proxy 엔드포인트 — CPU 사용률 (4개 인스턴스 균등 분산, 45~55%)

Read Instance 별 Read Latency

Proxy 엔드포인트 — 읽기 지연 (4개 인스턴스 균등 분산)

Read Instance 별 Network 처리량 (MB/초)

Proxy 엔드포인트 — Network 처리량 (4개 인스턴스 균등)


부하에 따른 Load Balance Test 결과 — None Proxy 엔드포인트

Read Instance 별 CPU 사용률

None Proxy 엔드포인트 — CPU 사용률 (4개 중 2개에 90%, 70% 집중)

Read Instance 별 Read Latency

None Proxy 엔드포인트 — 읽기 지연 (2개 인스턴스만 처리, 최대 7.5ms)

Read Instance 별 Network 처리량 (MB/초)

None Proxy 엔드포인트 — Network 처리량 (2개 인스턴스에 집중)


결과 해석 및 결론

Proxy 엔드포인트로 부하 테스트 시, 4개의 리더 인스턴스에서 CPU 사용률, 읽기 지연, Network 처리량이 고르게 분산되었습니다. 각 인스턴스가 50% 대의 CPU 부하를 유지하며 적절하게 Load Balance 되었습니다.

반면 None Proxy 환경에서는 급작스러운 부하에 4개 인스턴스 중 2개에만 트랜잭션이 집중되었고, 해당 인스턴스들은 각각 90%, 70% 대의 CPU 부하를 기록했습니다.

판단 및 권고

  • RDS Proxy 없이 Aurora 를 운용할 경우, 특정 인스턴스에 부하가 집중될 수 있으므로 AutoScaling 정책을 반드시 수립하여 리더 인스턴스가 CPU 한계치에 도달하지 않도록 해야 합니다.
  • 대고객 서비스나 특정 이벤트로 급격한 부하가 예상될 때는 사전에 리더 인스턴스를 추가한 후 Proxy 를 세팅하여 부하 분산에 대비하는 것이 좋습니다.
  • Connection 을 맺고 끊는 빈도가 잦은 서비스일수록 RDS Proxy 세팅이 중요합니다.

Connection 맺고 끊음이 잦거나 급격한 트래픽 변동이 예상되는 서비스라면, RDS Proxy 는 DB 보호와 부하 분산 두 가지를 동시에 챙길 수 있는 실용적인 선택입니다.


참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.