RDS가 기본값인 경우
일반적인 웹 서비스, 백오피스, 결제·주문·사용자 데이터처럼 관계형 모델과 운영 자동화가 모두 필요한 워크로드다.
백업, 패치, failover, 모니터링을 표준화하고 DB 운영 리스크를 줄이는 것이 우선일 때분류: Layer 3 - AWS 인프라 & 보안
RDS(Relational Database Service)는 AWS가 MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, Db2 같은 관계형 데이터베이스 엔진의 프로비저닝, 백업, 패치, 장애 감지, failover 일부를 관리해 주는 관리형 관계형 데이터베이스 서비스다.
관계형 데이터베이스는 테이블, 행, 열, SQL, 트랜잭션을 중심으로 데이터를 다룬다. RDS는 이 데이터 모델을 새로 만드는 서비스가 아니라, 기존 DB 엔진을 AWS 인프라 위에서 더 쉽게 운영하게 해 주는 관리 계층이다. 그래서 RDS를 이해할 때의 핵심 질문은 “SQL을 어떻게 쓰는가”가 아니라 DB 운영 책임 중 어디까지 AWS가 가져가고, 어디부터 애플리케이션 팀이 여전히 책임지는가다.
처음 읽을 때 붙잡을 문장은 이것이다.
RDS = 관계형 DB 엔진 + AWS가 맡는 운영 자동화 + 팀이 여전히 해야 하는 데이터/쿼리/연결 설계RDS가 없다고 DB를 못 쓰는 것은 아니다. EC2에 PostgreSQL이나 MySQL을 직접 설치하고, EBS 볼륨을 붙이고, cron으로 백업을 돌리고, 장애 시 standby를 승격시키면 된다. 작은 실험 환경에서는 이 방식이 더 싸고 자유로워 보일 수 있다.
문제는 서비스가 운영 단계로 들어갈 때 생긴다. DB는 애플리케이션 서버보다 상태(state)가 강하다. 서버 한 대가 죽으면 새로 띄우면 되지만, DB의 데이터 파일, 트랜잭션 로그, 백업, 복구 가능 시점, replication 상태가 깨지면 “다시 띄우기”만으로는 복구되지 않는다.
| EC2 자가 관리에서 팀이 직접 해야 하는 일 | RDS가 기본으로 줄여 주는 운영 부담 |
|---|---|
| OS와 DB 엔진 설치, 마이너 패치, 보안 패치 | 유지보수 윈도우와 자동 마이너 업그레이드 옵션 |
| 백업 스크립트 작성, 백업 성공 여부 확인 | 자동 백업, 트랜잭션 로그 보관, PITR(Point-in-Time Recovery) |
| 장애 감지, standby 승격, 접속 주소 전환 | Multi-AZ failover와 RDS endpoint(DNS 이름) 전환 |
| 디스크 용량, 연결 수, CPU, I/O 모니터링 | CloudWatch 지표, Database Insights 계열 관측 |
| standby 복제 구성, 복구 리허설, runbook 유지 | 관리형 복제와 콘솔/API 기반 복구 흐름 |
이 표가 말하는 것은 “RDS를 쓰면 DB 운영을 몰라도 된다”가 아니다. RDS는 설치와 인프라 운영의 많은 부분을 자동화하지만, 잘못된 인덱스, 무제한 connection pool, Public Subnet에 둔 DB, 오래된 Read Replica를 읽는 버그, 잘못된 migration은 대신 해결해 주지 않는다.
일반적인 웹 서비스, 백오피스, 결제·주문·사용자 데이터처럼 관계형 모델과 운영 자동화가 모두 필요한 워크로드다.
백업, 패치, failover, 모니터링을 표준화하고 DB 운영 리스크를 줄이는 것이 우선일 때OS 파일시스템 접근, 커널 수준 튜닝, AWS 미지원 엔진/버전, RDS가 허용하지 않는 특수 권한이나 확장이 꼭 필요한 워크로드다.
관리형 제약을 받아들이면 제품 요구사항 자체를 만족할 수 없을 때RDS의 lineage는 “DB를 더 쉽게 만드는 콘솔”이 아니라 상태ful 시스템의 반복 운영 작업을 서비스 경계 안으로 끌어들인 것이다. 이후 절의 Multi-AZ, backup, parameter group, RDS Proxy는 모두 이 책임 경계를 구체화한다.
RDS 문서는 약어가 많다. 처음에는 모든 세부 옵션을 외우지 말고 아래 용어가 어떤 문제를 가리키는지부터 잡는다.
| 용어 | 첫 정의 | 처음 잡아야 할 경계 |
|---|---|---|
| RDS | Relational Database Service. AWS 관리형 관계형 DB 서비스다. | SQL 데이터 모델 자체가 아니라 DB 운영 자동화 계층이다. |
| AZ | Availability Zone. 한 리전 안에서 전원·네트워크·건물 장애를 분리한 장애 도메인이다. | AZ가 다르면 장애 격리성은 높아지지만, 네트워크 지연과 비용이 0이 되는 것은 아니다. |
| DB instance | RDS에서 실행되는 DB 서버 단위다. CPU, 메모리, 스토리지, 엔진 버전을 가진다. | EC2처럼 보이지만 OS 접속과 파일시스템 직접 제어는 제한된다. |
| Endpoint | 애플리케이션이 접속하는 DNS 이름이다. 예: mydb.xxxx.ap-northeast-2.rds.amazonaws.com. | failover 때 코드의 주소는 그대로지만 DNS가 새 primary를 가리키도록 바뀐다. |
| Multi-AZ | 여러 AZ에 DB를 두고 자동 failover를 제공하는 고가용성(High Availability, HA) 배포 계열이다. | DB instance와 DB cluster는 복제 방식, 인스턴스 수, 읽기 가능 여부가 다르다. |
| Multi-AZ DB instance | primary 1개와 다른 AZ의 동기식 standby 1개를 두는 배포다. | standby는 failover 전 읽기 트래픽을 받지 않는다. |
| Multi-AZ DB cluster | writer 1개와 서로 다른 AZ의 readable reader 2개를 두는 준동기식 배포다. | MySQL/PostgreSQL용이며 reader endpoint와 replica lag를 함께 운영한다. |
| Read Replica | primary의 변경을 비동기로 따라가는 읽기 복제본이다. | 읽기 부하 분산용이며, stale read(오래된 값 읽기)를 허용해야 한다. |
| ReplicaLag | Read Replica나 Multi-AZ DB cluster reader가 writer보다 뒤처진 정도를 나타내는 CloudWatch 지표다. | 0이 아니면 최신 데이터가 아닐 수 있다. 값이 없거나 음수면 엔진별 metric과 복제 상태를 확인한다. |
| PITR | Point-in-Time Recovery. 자동 백업과 로그를 이용해 특정 시점의 새 DB로 복원하는 기능이다. | 기존 DB를 시간여행시키는 것이 아니라 보통 새 인스턴스로 복원해 전환한다. |
| Snapshot | 특정 시점의 수동 백업 이미지다. | 자동 보관 기간과 별개로 보관되며, 삭제 전까지 비용과 관리 책임이 남는다. |
| Parameter Group | DB 설정값 묶음이다. max_connections, logging, timeout 같은 엔진 파라미터를 RDS에 적용한다. | 동적 파라미터와 재시작이 필요한 정적 파라미터를 구분해야 한다. |
| Connection Pool | 애플리케이션이 DB 연결을 미리 열어 재사용하는 구조다. | pool을 키우면 빠른 것이 아니라 DB의 동시 연결 한도를 소비한다. |
| RDS Proxy | 애플리케이션과 RDS 사이에서 DB 연결을 관리하고 failover 영향을 줄이는 관리형 프록시다. | 모든 쿼리를 빠르게 만드는 캐시가 아니다. 연결 수와 failover 회복을 다루는 계층이다. |
| Pinning | RDS Proxy가 세션 상태 때문에 한 클라이언트 연결을 특정 DB 연결에 고정하는 현상이다. | pinning이 많으면 multiplexing 효과가 줄어든다. |
| Database Insights | CloudWatch에서 DB load, wait event, Top SQL 같은 성능 신호를 보는 관측 기능이다. | 제품명보다 “DB가 CPU, I/O, lock 중 어디서 기다리는가”를 읽는 사고가 중요하다. |
RDS를 처음 쓰면 “관리형”이라는 단어 때문에 모든 장애가 AWS 책임처럼 느껴질 수 있다. 실제로는 책임이 나뉜다.
| 영역 | RDS가 해 주는 것 | 애플리케이션 팀이 여전히 해야 하는 것 |
|---|---|---|
| 설치/패치 | 엔진 설치, 관리형 패치, 유지보수 윈도우 | 패치 시점 선택, major version upgrade 테스트, 호환성 검증 |
| 백업/복구 | 자동 백업, 트랜잭션 로그 보관, snapshot 생성과 복원 API | 보관 기간 설정, 복구 리허설, 어느 시점으로 되돌릴지 판단 |
| 고가용성 | 배포 모드별 standby/reader, 장애 감지, writer 승격, endpoint 전환 | 재연결 로직, DNS TTL, idempotent retry, failover 중 사용자 영향 설계 |
| 보안 | 저장 시 암호화 옵션, IAM 통합, VPC 배치, SG 적용 | Private Subnet 배치, SG-as-source 규칙, 비밀번호/secret 관리, 권한 최소화 |
| 성능 | 인스턴스 클래스, 스토리지 옵션, 지표 제공 | 쿼리 설계, 인덱스, connection pool 크기, slow query 분석 |
| 비용 | 사용량 기반 과금과 비용 지표 제공 | Multi-AZ, storage autoscaling, snapshot, idle 개발 DB 비용 관리 |
짧은 반례를 보자. RDS 자동 백업이 켜져 있어도 애플리케이션이 잘못된 migration으로 users.email을 전부 NULL로 덮어쓰면, RDS는 그것을 정상 쓰기로 기록한다. 복구하려면 PITR로 사고 직전의 새 DB를 만들고, 어떤 데이터를 되살릴지 비교·전환해야 한다. 관리형 백업은 복구 수단이지, 잘못된 쓰기를 자동으로 판별하는 보호막이 아니다.
또 다른 반례는 연결 수다. RDS가 max_connections를 제공해도 ECS Task가 늘어날 때 pool 크기를 같이 계산하지 않으면 too many connections가 난다. RDS가 인스턴스를 운영해 주는 것과 애플리케이션이 연결을 절제하는 것은 다른 책임이다.
RDS는 마이너 버전 패치를 유지보수 윈도우에 적용할 수 있다. 이것은 “패치 파일을 다운로드하고 설치하는 작업”을 AWS가 해 준다는 뜻이다. 하지만 다음 질문은 여전히 팀의 일이다.
현재: RDS for MySQL 8.0.x목표: 새 마이너 버전 적용
AWS가 해 주는 것:- 지원되는 새 마이너 버전 제공- 유지보수 윈도우에 패치 실행- Multi-AZ 구성이라면 DB instance standby 또는 DB cluster reader의 작업과 failover 흐름 관리
팀이 해야 하는 것:- 새 버전에서 ORM, migration, SQL mode, collation, extension 호환성 확인- staging snapshot restore 후 smoke test- 배포 시간대와 rollback 판단 기준 준비- failover 중 재시도와 사용자 영향 확인이 예시는 RDS의 “관리형”을 과소평가하지도, 과대평가하지도 않게 만든다. AWS는 반복 운영 작업을 자동화하지만, 데이터 모델과 애플리케이션 호환성은 서비스 팀의 지식 없이는 판단할 수 없다.
Multi-AZ는 RDS 가용성의 핵심 구성이다. 그러나 이름 하나 아래에 Multi-AZ DB instance deployment와 Multi-AZ DB cluster deployment라는 서로 다른 방식이 있다. 둘 다 AZ나 writer 장애 시 자동 failover를 제공하지만, “Multi-AZ는 동기 standby 하나”라고만 외우면 cluster의 준동기 복제와 readable reader를 놓친다.
| 배포 모드 | 토폴로지 | commit 복제 경계 | 장애 전 읽기 | 핵심 목적 |
|---|---|---|---|---|
| Multi-AZ DB instance | primary 1 + 다른 AZ의 standby 1 | standby로 동기 복제 | standby 읽기 불가 | 단일 DB instance의 고가용성 |
| Multi-AZ DB cluster | 3개 AZ의 writer 1 + reader 2 | reader 중 최소 1개의 확인을 요구하는 준동기 복제 | reader endpoint로 두 replica 읽기 가능 | 고가용성 + 읽기 용량 + 더 낮은 write latency |
flowchart TD
App["Application"] --> Endpoint["RDS Endpoint DNS"]
Endpoint --> Primary["Primary DB in AZ-A"]
Primary --> Sync["동기 복제"]
Sync --> Standby["Standby DB in AZ-B"]
Primary --> Detect{"Primary 장애 감지"}
Detect --> Promote["Standby를 새 Primary로 승격"]
Promote --> DNS["Endpoint DNS가 새 Primary IP를 가리킴"]
DNS --> Reconnect["Application은 같은 endpoint로 재연결"] 이 배포에서 중요한 단어는 동기 복제다. primary의 변경은 다른 AZ의 standby로 동기 복제된다. standby는 장애 전 일반 읽기 endpoint로 노출되지 않으며, failover 때 승격되는 고가용성 자원이다. 이 덕분에 장애 시 데이터 유실 가능성을 낮추지만, Single-AZ보다 write/commit 지연과 비용이 늘 수 있다.
Multi-AZ DB cluster는 MySQL과 PostgreSQL에서 writer 1개와 reader 2개를 서로 다른 3개 AZ에 둔다. writer가 변경을 두 reader에 보내고, 최소 한 reader의 확인을 받아야 commit할 수 있는 semisynchronous replication(준동기 복제)을 사용한다. 이 확인은 두 reader 모두에서 변경이 완전히 실행·commit됐다는 뜻은 아니다.
두 reader는 자동 failover 대상이면서 read traffic도 처리한다. 애플리케이션은 writer endpoint로 쓰고 reader endpoint로 읽을 수 있다. 따라서 일반 Multi-AZ DB instance의 숨은 standby와 달리 읽기 용량을 제공하지만, engine 기반 복제라 ReplicaLag가 생길 수 있다. failover 후보를 고를 때도 최신 change record와 lag가 중요하므로, “준동기 commit이면 모든 reader가 항상 최신”이라고 가정하면 안 된다.
AWS 공식 문서 기준으로 일반적인 Multi-AZ DB instance failover는 보통 60~120초가 걸린다. 큰 트랜잭션이 진행 중이었거나 recovery가 길면 더 오래 걸릴 수 있다. Multi-AZ DB cluster, 즉 writer 1개와 readable reader 2개를 두는 구성은 보통 35초 미만 failover를 목표로 한다. cluster는 reader lag를 해소한 뒤 새 writer를 승격해야 하므로 실제 시간은 lag와 engine 상태에 따라 달라질 수 있다.
이 숫자는 “RDS가 primary를 바꾸는 시간”에 가깝다. 사용자가 체감하는 시간은 여기에 애플리케이션의 DNS 캐시, connection pool의 stale socket, 드라이버 재시도 정책이 더해진다.
체감 장애 시간 = RDS 장애 감지와 standby/reader 승격 + DNS 전파와 런타임 DNS cache 만료 + connection pool이 옛 연결을 버리고 새 연결을 만드는 시간 + 애플리케이션 retry/backoff 시간Multi-AZ에서 endpoint 이름은 그대로지만 그 이름이 가리키는 IP는 바뀔 수 있다. 이때 런타임이 DNS 결과를 너무 오래 캐시하면 RDS failover는 끝났는데 애플리케이션만 옛 primary IP로 계속 접속한다.
Java의 JVM DNS cache TTL(Time To Live)은 특히 확인해야 한다. AWS Java SDK 문서는 failover 대응을 위해 JVM DNS TTL을 60초 미만 또는 60초 수준으로 두는 구성을 안내한다. networkaddress.cache.ttl이 무한 캐시처럼 동작하면 failover 뒤에도 옛 IP를 계속 쓸 수 있다.
Node.js 자체의 dns.lookup은 일반적으로 JVM처럼 영구 DNS 캐시를 두지 않지만, DB driver나 ORM의 connection pool은 옛 TCP 연결을 붙잡을 수 있다. 그래서 Multi-AZ를 켰다면 endpoint 이름만 믿지 말고 pool idle timeout, connection lifetime, retry 정책을 같이 본다.
| 실패 신호 | 의미 | 먼저 볼 개념 |
|---|---|---|
RDS 이벤트에는 failover 완료, 앱은 계속 ECONNREFUSED | DNS cache나 pool이 옛 primary IP/소켓을 붙잡고 있다 | DNS TTL, connection pool 재연결 |
| failover 중 일부 요청만 오래 실패 | 프로세스별 cache와 pool 상태가 서로 다르다 | 런타임 DNS cache, pool instance 분산 |
| 재시작하면 즉시 회복 | 코드보다 프로세스 안의 stale DNS/socket 가능성이 높다 | TTL, idle timeout, RDS Proxy |
Multi-AZ는 공짜 안전장치가 아니다. DB instance deployment는 standby가 추가되어 DB instance-hour 비용이 대체로 한 대 더 붙고, DB cluster deployment는 writer와 reader 두 개, 총 3개 DB instance를 과금한다. 전체 청구액이 단순히 2배 또는 3배라는 뜻은 아니다. 스토리지, I/O, 백업, 데이터 전송, RDS Proxy 같은 항목은 구성별로 따로 계산해야 한다.
개발/스테이징 DB에서 Multi-AZ를 끄는 팀이 많은 이유가 여기에 있다. 프로덕션에서는 “한 달에 추가로 내는 비용”과 “DB 장애 1시간이 만드는 매출 손실, SLA 위반, 엔지니어 야간 대응 비용”을 비교한다.
작은 숫자 감각을 붙이면 결정이 더 선명하다. 월 30일 기준 99.9% 가용성은 한 달에 약 43분 다운타임을 허용하고, 99.95%는 약 22분, 99.99%는 약 4분 수준만 허용한다. DB 수동 복구가 30분 이상 걸릴 가능성이 있고 그 시간의 손실이 Multi-AZ 추가 비용보다 크다면, Multi-AZ는 “비싼 옵션”이 아니라 복구 시간을 사는 선택이 된다.
퀴즈
힌트: standby의 목적과 endpoint 노출 여부를 떠올린다.
일반적인 Multi-AZ DB instance의 동기식 standby는 failover용이라 읽을 수 없다. 반면 Multi-AZ DB cluster는 준동기식 reader 두 개를 읽기와 failover에 함께 사용한다. 별도 비동기 Read Replica도 읽기 확장 선택지다.
Read Replica는 primary의 변경을 비동기로 따라가는 별도 DB다. 쓰기는 primary로 보내고, stale read를 허용할 수 있는 조회를 replica로 보낸다. “비동기”라는 말은 primary commit이 먼저 끝나고, 변경이 replica에 나중에 도착할 수 있다는 뜻이다.
쓰기 요청:Application -> Primary -> commit 성공 -> 나중에 Replica로 변경 전파
읽기 요청:최신성이 중요한 조회 -> Primary오래된 값을 잠깐 봐도 되는 조회 -> Read Replica아래 비교의 Multi-AZ는 DB instance deployment를 뜻한다. Multi-AZ DB cluster의 reader는 고가용성과 읽기 용량을 함께 제공하므로 이 표의 “standby 읽기 불가”를 그대로 적용하면 안 된다.
| 항목 | Multi-AZ DB instance | Read Replica |
|---|---|---|
| 주목적 | 장애 대비, 자동 failover | 읽기 부하 분산, 리포트/검색성 조회 분리 |
| 복제 방식 | 동기식 standby 복제 | 비동기 복제 |
| endpoint | 일반 단일 standby 구성은 같은 writer endpoint 사용 | replica별 endpoint가 따로 있다 |
| 읽기 사용 | standby는 보통 직접 읽지 않는다 | 애플리케이션이 읽기 쿼리를 replica endpoint로 보낸다 |
| 실패 모드 | failover 중 연결 끊김과 재연결 | stale read, ReplicaLag 증가 |
상품 재고가 10개에서 9개로 줄었다고 가정하자.
T0: Primary에 UPDATE inventory SET stock = 9 WHERE sku = 'A' commitT1: 사용자가 주문 완료 화면으로 이동T2: 화면 조회가 Read Replica로 라우팅T3: ReplicaLag = 5초라 replica에는 아직 stock = 10이 상황은 DB 장애가 아니다. Read Replica가 약속한 모델 안에서 오래된 값을 읽은 것이다. 재고, 잔액, 결제 상태, 권한 변경처럼 최신성이 중요한 조회는 primary로 보내야 한다. 반대로 대시보드 집계, 검색 자동완성, 오래된 리포트처럼 몇 초 지연이 괜찮은 조회는 replica 후보가 된다.
ReplicaLag는 이 판단을 관측 가능한 숫자로 바꿔 준다.
| ReplicaLag 값 | 읽는 방법 | 판단 |
|---|---|---|
0 | replica가 primary와 거의 동기화된 상태 | 일반 읽기 분산에 적합 |
5 | replica가 primary보다 약 5초 뒤처진 상태 | 최신성 민감 조회는 primary로 보낸다 |
-1 또는 없음 | 복제 상태가 비정상·미측정·미지원일 수 있음 | replica 상태와 엔진별 metric 정의 확인 |
TypeORM 같은 ORM의 replication 옵션은 설정에 따라 SELECT를 replica로 라우팅할 수 있다. 이 기능을 쓰더라도 “모든 SELECT는 replica”가 기본값이 되면 안 된다. 최신성이 필요한 읽기는 명시적으로 primary를 쓰는 escape hatch가 있어야 한다.
// 최신성 민감 조회는 replica 자동 라우팅을 우회해 primary를 명시한다.const runner = dataSource.createQueryRunner("master");const order = await runner.manager.findOne(Order, { where: { id: orderId } });await runner.release();처음에는 아래처럼 데이터의 “최신성 민감도”로 나눈다.
| 조회 종류 | Replica 후보인가 | 이유 |
|---|---|---|
| 주문 직후 주문 상세 | 보통 아니오 | 사용자가 방금 만든 상태를 즉시 확인해야 한다. |
| 재고 차감 직후 재고 수량 | 아니오 | stale read가 oversell이나 잘못된 의사결정으로 이어질 수 있다. |
| 관리자 대시보드의 5분 단위 집계 | 예 | 몇 초 지연보다 primary 부하 분리가 더 중요할 수 있다. |
| 검색 자동완성, 추천 후보 | 예 | eventual consistency를 사용자 경험이 흡수할 수 있다. |
| 권한 변경 직후 접근 가능 여부 | 보통 아니오 | 이전 권한을 읽으면 보안 문제가 된다. |
이 기준은 코드 구조에도 영향을 준다. repository 계층에서 “읽기니까 replica”로 자동화하기보다, 도메인별로 최신성 요구를 표시할 수 있어야 한다. replica는 성능 도구지만, 잘못 쓰면 에러 없는 정합성 버그가 된다.
RDS 백업을 이해하려면 세 단어를 구분해야 한다.
| 개념 | 의미 | 주의할 점 |
|---|---|---|
| 자동 백업 | RDS가 정해진 backup window에 DB를 백업하고 로그를 보관하는 기능 | 보관 기간은 DB instance에서 0~35일 범위로 설정한다. 0은 자동 백업 비활성화 |
| PITR | 자동 백업과 트랜잭션 로그로 특정 시점의 DB를 새로 복원하는 기능 | 최근 몇 분은 엔진과 상태에 따라 복원 가능 시점에 아직 포함되지 않을 수 있다 |
| 수동 Snapshot | 사용자가 특정 시점에 만든 백업 이미지 | 자동 보관 기간과 별개로 남고, 삭제 전까지 비용과 관리 책임이 있다 |
PITR(Point-in-Time Recovery)은 “사고 전 10분으로 기존 DB를 되감기”가 아니다. 보통은 사고 전 시점의 새 DB instance를 만들고, 애플리케이션이 어느 DB를 바라볼지 전환하거나 필요한 데이터만 비교해 되살린다.
작은 사고 시나리오를 따라가 보자.
10:00 정상 상태10:20 잘못된 배포가 users 테이블 일부를 덮어씀10:30 장애 인지10:35 10:19 시점으로 PITR 복원 시작11:10 복원된 새 DB에서 정상 데이터 확인11:30 필요한 데이터만 추출하거나 애플리케이션 endpoint 전환여기서 중요한 결정은 “복원 버튼을 누를 수 있는가”가 아니라 “어느 시점이 안전한가”, “복원된 DB를 어떻게 검증할 것인가”, “현재 DB에 들어온 정상 쓰기와 어떻게 합칠 것인가”다. 자동 백업은 도구이고, 복구 전략은 팀의 책임이다.
반례도 있다. 자동 백업 보관 기간을 1일로 두면 비용은 줄지만, 3일 전에 들어간 데이터 오염을 PITR로 되돌릴 수 없다. 반대로 모든 스냅샷을 영구 보관하면 복구 선택지는 늘지만 비용과 개인정보 보관 리스크가 커진다.
복구를 공부할 때는 RPO와 RTO를 같이 둔다.
| 용어 | 뜻 | RDS에서 묻는 질문 |
|---|---|---|
| RPO(Recovery Point Objective) | 얼마 전 데이터까지 잃어도 되는가 | latest restorable time이 업무 요구보다 충분히 가까운가 |
| RTO(Recovery Time Objective) | 복구 완료까지 얼마나 걸려도 되는가 | PITR DB 생성, 검증, endpoint 전환까지 몇 분/몇 시간이 걸리는가 |
예를 들어 “최대 5분 데이터 손실, 30분 안 복구”가 요구사항이라면 자동 백업 보관만 켜 둔 것으로는 부족하다. 복원된 DB에서 핵심 테이블 수, migration version, 애플리케이션 smoke test를 확인하는 절차가 30분 안에 끝나는지 리허설해야 한다. 반대로 내부 리포트 DB라면 RPO 1시간, RTO 4시간도 충분할 수 있다. 백업 설정은 업무 손실 기준과 연결될 때 의미가 생긴다.
Parameter Group은 RDS DB instance에 적용되는 DB 설정값 묶음이다. PostgreSQL의 shared_buffers, MySQL의 max_connections, slow query log 설정, timeout 값처럼 엔진별 파라미터가 여기에 들어간다.
파라미터에는 적용 방식 차이가 있다.
| 종류 | 의미 | 실패 신호 |
|---|---|---|
| Dynamic parameter | DB 재시작 없이 적용될 수 있는 값 | 바꾼 직후 효과가 보이지만, 세션 단위 설정과 충돌할 수 있다 |
| Static parameter | DB 재시작 또는 failover가 필요할 수 있는 값 | 콘솔에 pending-reboot가 보이고 값이 바로 반영되지 않는다 |
| Default parameter group | AWS가 제공하는 기본 그룹 | 직접 수정할 수 없거나 변경 추적이 어렵다. 운영 DB는 custom group을 붙이는 편이 낫다 |
Parameter Group을 잘못 읽으면 “값을 바꿨는데 왜 안 먹지?”가 된다. 이때 먼저 볼 것은 앱 코드가 아니라 해당 파라미터가 dynamic인지 static인지, 현재 DB에 붙은 group이 내가 수정한 group인지, 적용 상태가 pending-reboot인지다.
처음에는 모든 파라미터를 외우지 말고 max_connections 하나로 감각을 만든다. max_connections는 DB가 동시에 받아들일 수 있는 client connection 수다. 이 값은 엔진과 인스턴스 메모리에 따라 기본값이 계산된다. MySQL 계열 RDS의 기본식은 DBInstanceClassMemory / 12582880 형태로 설명되며, 대략 메모리 MB를 12로 나눈 감각에 가깝다. 1GiB급 작은 인스턴스라면 기본 동시 연결 수가 80~90대에 머물 수 있다는 뜻이다.
애플리케이션 쪽 계산은 더 직접적이다.
최대 DB 연결 수요 = 실행 중인 애플리케이션 프로세스 수 × 프로세스당 pool max
예:ECS Task 10개 × pool max 10 = 최대 100개 연결작은 RDS의 max_connections가 약 85라면 이미 초과 가능Connection Pool(연결 풀)은 요청마다 DB 연결을 새로 만들지 않기 위해 미리 열린 연결을 재사용하는 구조다. DB 연결은 TCP handshake, TLS, 인증, 세션 초기화 비용이 있으므로 재사용이 필요하다. 하지만 pool은 성능 가속 버튼이 아니다. 프로세스가 많아질수록 pool max의 곱만큼 DB 연결을 잡아먹는다.
서비스가 아래처럼 배포되어 있다고 가정하자.
ECS Service desired count: 12 tasks각 task의 Node.js 프로세스: 1개TypeORM pool max: 10RDS max_connections: 150운영용 여유분: 30 connections계산은 이렇게 한다.
앱 최대 연결 수요 = 12 × 10 = 120DB 운영 여유분 포함 = 120 + 30 = 150이 구성은 평소에는 맞아 보이지만 rolling deployment 중에는 기존 task와 신규 task가 잠깐 동시에 떠 있을 수 있다. 그 순간 task가 18개까지 늘면 18 × 10 = 180이 되어 한도를 넘는다. 그래서 pool max는 정상 상태뿐 아니라 배포 중 동시성, 배치 작업, migration, DBA 접속 여유를 함께 보고 정한다.
| 잘못된 직관 | 왜 틀렸나 |
|---|---|
| pool max를 키우면 항상 빨라진다 | DB CPU, lock, I/O가 병목이면 연결만 늘어 대기열과 context switching이 늘 수 있다 |
too many connections는 RDS가 작아서만 난다 | 애플리케이션 task 수와 pool 설정이 한도를 초과한 설계 문제일 수 있다 |
| Parameter Group에서 max만 키우면 끝이다 | 작은 인스턴스에서 연결 수만 늘리면 메모리 압박과 성능 저하가 생길 수 있다 |
RDS Proxy는 애플리케이션과 RDS 사이에 놓이는 관리형 데이터베이스 프록시다. 핵심 기능은 두 가지다.
RDS Proxy 없음:ECS Task 100개 × pool max 5 = RDS에 최대 500개 연결
RDS Proxy 사용:ECS Task 100개 -> Proxy client connection 500개 가능Proxy -> RDS backend connection 100개 이하로 재사용 가능이 숫자는 개념 예시다. 실제 감소율은 쿼리 시간, 트랜잭션 길이, 세션 상태 사용, proxy 설정에 따라 달라진다. 짧고 독립적인 쿼리가 많을수록 multiplexing 효과가 좋고, 긴 트랜잭션이나 세션 상태가 많을수록 줄어든다.
AWS 블로그의 테스트에서는 Aurora MySQL failover에서 직접 연결 평균 약 24초가 RDS Proxy 경유 평균 약 3.1초로 줄었고, RDS Multi-AZ MySQL 테스트에서는 직접 연결 평균 약 36.8초가 proxy 경유 약 25.1초로 줄었다. 이 수치는 특정 드라이버와 테스트 조건의 결과다. 기억할 결론은 “RDS Proxy가 항상 3초로 만들어 준다”가 아니라 DNS 전파, 새 연결 수립, stale pool 처리 일부를 proxy 계층으로 옮겨 failover 체감 시간을 줄일 수 있다는 점이다.
Pinning은 RDS Proxy가 특정 client connection을 특정 backend DB connection에 고정하는 현상이다. 세션 상태가 다른 요청과 섞이면 안 되는 경우 proxy는 연결을 공유하지 못한다.
Pinning이 생기기 쉬운 예시는 다음과 같다.
SET @user_id = 1CREATE TEMPORARY TABLEquery1; query2;CloudWatch의 DatabaseConnectionsCurrentlySessionPinned metric은 현재 pinning된 DB connection 수를 보는 데 쓰인다. 이 값이 높으면 “RDS Proxy를 붙였는데 연결 수가 별로 줄지 않는다”는 현상이 생긴다.
퀴즈
힌트: Proxy가 연결을 공유하지 못하게 만드는 세션 상태를 떠올린다.
pinning을 의심한다. 세션 변수, 임시 테이블, 긴 트랜잭션, 멀티 statement 때문에 client connection이 특정 DB connection에 고정되면 multiplexing 효과가 줄어든다.
RDS Proxy는 좋은 도구지만 “RDS 앞에는 무조건 Proxy”가 아니다.
| 상황 | 판단 |
|---|---|
| Lambda나 짧게 뜨는 worker가 동시에 수백~수천 연결을 만든다 | RDS Proxy 후보가 강하다. connection storm을 흡수할 수 있다. |
| ECS Task가 많고 배포 때 connection spike가 반복된다 | pool sizing과 함께 RDS Proxy를 검토한다. |
| Multi-AZ failover 때 DNS/pool 회복 시간이 사용자 영향으로 커진다 | RDS Proxy가 회복 시간을 줄이는 데 도움이 될 수 있다. |
| 소수의 장기 실행 서버가 안정적인 pool을 유지한다 | 먼저 pool max, idle timeout, 쿼리 시간을 조정해도 충분할 수 있다. |
| 세션 변수, 임시 테이블, 긴 트랜잭션이 많다 | pinning 때문에 효과가 작을 수 있으므로 metric으로 검증한다. |
선택 기준은 “Proxy가 있으면 좋아 보인다”가 아니라 DB connection을 줄일 실제 반복 패턴이 있는가, failover 회복 시간을 줄여야 하는 요구가 있는가, pinning 때문에 효과가 사라지지 않는가다.
RDS는 보통 Private 또는 DB Subnet에 둔다. Publicly accessible 옵션을 켜고 Public Subnet에 두면 인터넷에서 DB endpoint로 직접 접근 가능한 경로가 생긴다. DB는 사용자 요청의 첫 진입점이 아니라 애플리케이션 서버 뒤의 상태 저장소이므로, 기본 설계는 다음과 같다.
Internet -> ALB in Public Subnet -> ECS Task / EC2 in Private App Subnet -> RDS in DB SubnetVPC/Subnet/SG 문서에서 배운 SG-as-source 패턴이 여기서 그대로 쓰인다.
RDS Security Group inbound: Type: PostgreSQL Port: 5432 Source: sg-ecs-task
ECS Task Security Group outbound: Destination: sg-rds Port: 5432이 패턴은 10.0.11.42/32 같은 특정 IP보다 안전하다. ECS Task나 EC2가 재배포되어 private IP가 바뀌어도 같은 SG를 달고 있으면 접근 권한이 유지된다. 반대로 같은 VPC 안에 있더라도 source SG가 허용되지 않은 리소스는 DB에 붙을 수 없다.
연결 실패를 볼 때는 “RDS가 죽었나”보다 먼저 네트워크 경로를 나눈다.
| 증상 | 의미 | 먼저 볼 곳 |
|---|---|---|
Connection timed out | 패킷이 DB까지 도달하지 못했거나 SG/NACL/route에서 막힘 | RDS SG inbound, ECS SG outbound, DB Subnet route |
ECONNREFUSED | endpoint는 해석됐지만 대상 포트 연결이 거부되거나 전환 중 | failover 이벤트, pool stale socket, DB 상태 |
| 로컬에서는 되는데 ECS에서만 실패 | 로컬 IP 허용과 ECS Task SG 허용이 다르다 | RDS SG source, 배포 환경의 DATABASE_URL |
| DNS는 해석되는데 연결만 안 됨 | endpoint 문자열과 network path는 별도 문제 | Flow Logs, Reachability Analyzer, SG |
RDS 비밀번호나 접속 문자열은 애플리케이션 환경변수에 평문으로 오래 남기지 않는 편이 좋다. 실무에서는 Secrets Manager나 SSM Parameter Store를 쓰고, 이를 읽는 권한은 ECS Task Role 같은 IAM Role에 좁게 부여한다. 이 부분은 IAM과 Secrets Management 주제와 연결되지만, RDS 문서에서는 DB endpoint는 private path로, secret은 workload role로라는 원칙만 잡으면 충분하다.
RDS 성능 문제를 처음 보면 CPU 그래프만 보게 된다. 하지만 DB는 CPU, I/O, lock, connection wait, buffer/cache miss, 쿼리 계획 같은 여러 이유로 느려진다. Database Insights나 Performance Insights 계열 도구를 보는 이유는 “느리다”를 **무엇을 기다리는가(wait event)**로 나누기 위해서다.
도구 이름과 콘솔 위치는 AWS가 바꿀 수 있다. 현재 문서에서 중요한 것은 제품명 변화가 아니라 아래 관측 모델이다.
| 관측 항목 | 읽는 법 | 다음 질문 |
|---|---|---|
| DB Load | DB가 동시에 처리하거나 기다리는 작업량 | CPU bound인가, I/O wait인가, lock wait인가 |
| Top SQL | 부하를 많이 만드는 SQL | 특정 쿼리가 전체 부하를 지배하는가 |
| Wait Event | DB 세션이 기다리는 원인 | 디스크, lock, client, CPU 중 어디인가 |
| DatabaseConnections | 현재 열린 DB 연결 수 | pool max와 task 수 계산이 맞는가 |
| FreeStorageSpace | 남은 스토리지 | auto scaling이나 용량 계획이 필요한가 |
| ReadIOPS/WriteIOPS | 스토리지 읽기/쓰기 요청량 | 쿼리/인덱스/배치 작업이 I/O를 밀어 올리나 |
슬로우 쿼리의 첫 해석도 이 모델을 따른다.
API 응답이 5초 이상 걸림 -> Top SQL에서 특정 SELECT가 부하 상위 -> EXPLAIN으로 Seq Scan 또는 잘못된 join 순서 확인 -> 필요한 인덱스, 쿼리 조건, N+1 쿼리, pagination을 검토Seq Scan이 보인다고 항상 잘못은 아니다. 작은 테이블 전체를 읽는 것이 인덱스보다 빠를 수 있다. 문제는 “Seq Scan이라는 단어”가 아니라 테이블 크기, 조건 선택도, 호출 빈도, 응답 지연이 함께 커지는 상황이다.
RDS 비용은 DB instance class만으로 끝나지 않는다. 자주 놓치는 축은 다음과 같다.
| 비용 축 | 학습용 판단 기준 |
|---|---|
| Instance class | CPU와 메모리 여유가 지속적으로 낮으면 downsizing 후보지만, peak와 배포 시간을 함께 본다. |
| Multi-AZ DB instance | 동기식 standby 1개의 비용이 추가된다. 프로덕션 가용성 비용으로 정당화되는지 본다. |
| Multi-AZ DB cluster | writer 1개와 readable reader 2개를 과금한다. HA와 읽기 용량, 더 낮은 write latency가 모두 필요한지 본다. |
| Read Replica | 읽기 부하를 줄이지만 replica instance 비용과 replica lag 운영 비용이 생긴다. |
| Storage | 할당 스토리지, IOPS, storage autoscaling 최대치를 본다. |
| Backup retention | 보관 기간이 길수록 복구 선택지는 늘지만 백업 보관 비용과 데이터 보존 리스크가 커진다. |
| Manual snapshot | 자동 보관 기간과 별개로 남는다. 오래된 snapshot은 비용과 개인정보 보관 리스크를 만든다. |
| RDS Proxy | 연결 안정성 비용이다. 모든 서비스에 기본으로 붙이기보다 연결 폭증/failover 요구를 보고 결정한다. |
| Dev/Staging idle DB | 사용하지 않는 시간에도 과금된다. 중지 가능한 DB는 중지하되, 장기 중지는 자동 재시작 제약을 확인한다. |
Storage Auto Scaling은 특히 반례가 중요하다. 스토리지가 부족할 때 자동 확장은 장애를 막는 데 도움이 되지만, 한 번 늘어난 allocated storage는 일반적으로 같은 인스턴스에서 바로 줄일 수 없다. 용량을 줄이려면 더 작은 스토리지로 새 DB를 복원하거나 dump/restore 같은 별도 절차가 필요하다. 그래서 최대 스토리지 값을 너무 크게 열어 두면 “장애 예방”이 “지속 비용 증가”로 바뀔 수 있다.
비용 최적화는 한 번의 설정이 아니라 관측 주기다. 최소 1~2주 정도의 CPU, connection, I/O, storage 증가 추세를 보고, peak 시간과 batch 시간까지 포함해 판단한다. 개발 DB 자동 중지, 예약형 구매, gp3 같은 스토리지 옵션, snapshot 정리는 모두 비용 절감 후보지만, 프로덕션 복구성과 성능을 해치지 않는 범위에서만 적용한다.
정확한 금액은 리전과 시점의 AWS Pricing을 봐야 하지만, 비용 구조는 손으로 그릴 수 있어야 한다.
단일 AZ 운영 DB: DB instance 1대 storage 200GB automated backup 7일
Multi-AZ 전환: DB instance 2대에 가까운 instance-hour 동기식 비가독 standby 유지 failover 안정성 증가
Multi-AZ DB cluster 선택: writer 1대 + readable reader 2대 준동기 commit과 reader endpoint failover 안정성 + 읽기 분산, ReplicaLag 관측 필요
Read Replica 추가: replica instance 1대 추가 읽기 부하 분산 가능 ReplicaLag 관측과 라우팅 복잡도 증가
RDS Proxy 추가: proxy 과금 추가 connection storm과 failover 영향 완화 가능이 예시는 “싼 구성”을 고르는 표가 아니다. 운영 DB 비용은 안정성 구매이기도 하다. 프로덕션에서 Multi-AZ를 끄면 청구액은 줄지만 장애 시 수동 복구와 데이터 손실 위험이 커진다. 반대로 개발 DB에 Multi-AZ, 큰 instance, 긴 snapshot 보관을 그대로 두면 학습·실험 환경이 프로덕션 비용 구조를 따라가 버린다.
이 문서는 RDS Basics이므로 NoSQL이나 Aurora가 본문을 지배하면 안 된다. 다만 선택 기준은 필요하다.
| 선택지 | 먼저 묻는 질문 | 어울리는 상황 |
|---|---|---|
| RDS PostgreSQL/MySQL | 관계, JOIN, 트랜잭션, SQL 제약이 핵심인가? | 사용자, 주문, 결제, 재고, 백오피스처럼 정확한 쓰기와 관계 모델이 중요한 데이터 |
| Aurora | RDS 호환 API를 쓰면서 더 높은 가용성·확장성·스토리지 구조가 필요한가? | DB가 이미 병목이고 Aurora의 비용과 제약을 받아들일 수 있는 프로덕션 |
| DynamoDB/MongoDB | 접근 패턴이 key-value/document 중심이고 수평 확장과 낮은 지연이 더 중요한가? | 단일 key 조회, 이벤트 상태, 세션성 데이터, 초당 매우 큰 단순 읽기/쓰기 |
| EC2 자가 관리 | 관리형 제약이 제품 요구사항을 막는가? | 특수 엔진, OS 수준 제어, RDS 미지원 버전이나 확장이 필수인 경우 |
가장 흔한 오해는 “트래픽이 많으면 NoSQL”이다. 트래픽이 많아도 결제 정합성, 재고 차감, 복잡한 조회, 운영자가 직접 SQL로 확인해야 하는 데이터라면 RDS나 Aurora가 더 단순할 수 있다. 반대로 관계형 모델이 거의 없고 단일 key 접근 패턴이 명확하다면 RDS의 JOIN 능력은 장점보다 비용이 될 수 있다.
긴 runbook을 외우는 대신 증상에서 개념으로 돌아가는 표를 만든다.
| 증상 | 의미 | 먼저 의심할 개념 |
|---|---|---|
too many connections | 전체 애플리케이션 pool 수요가 DB 한도를 넘었다 | Task 수 × pool max, max_connections, RDS Proxy |
| failover 후 일부 프로세스만 계속 실패 | DNS cache나 pool stale socket이 남았다 | endpoint, DNS TTL, idle timeout, RDS Proxy |
| 방금 쓴 데이터가 Read Replica에서 안 보임 | 비동기 복제 지연으로 stale read가 발생했다 | Read Replica, ReplicaLag, primary 라우팅 |
| Multi-AZ DB cluster reader가 writer보다 늦다 | 준동기 확인은 두 reader 모두의 apply 완료를 뜻하지 않는다 | cluster ReplicaLag, reader/writer endpoint, 최신성 요구 |
| DB 연결 timeout | 네트워크 경로 또는 SG 규칙이 막혔다 | Private Subnet, RDS SG, SG-as-source, Route Table |
| CPU는 낮은데 API가 느림 | lock wait, I/O wait, connection wait 가능성 | Database Insights, Top SQL, wait event |
| RDS 비용이 갑자기 증가 | storage autoscaling, snapshot 누적, replica/Multi-AZ 비용 가능성 | storage, backup retention, snapshot inventory |
| Parameter Group 값을 바꿨는데 효과가 없음 | 재시작 필요 파라미터이거나 다른 group이 붙어 있다 | dynamic/static parameter, pending-reboot |
| RDS Proxy를 붙였는데 연결 수가 별로 줄지 않음 | pinning 때문에 multiplexing이 작동하지 않는다 | session state, DatabaseConnectionsCurrentlySessionPinned |
| PITR 복원은 됐지만 어떤 DB로 전환해야 할지 모름 | 백업 기능은 있었지만 복구 절차와 검증 기준이 없었다 | RPO/RTO, 복구 리허설, 데이터 비교 |
이 표는 운영 절차를 대체하지 않는다. 대신 첫 10분 동안 “어떤 개념이 깨졌는지”를 좁히게 해 준다. 실제 명령은 팀의 IaC, 콘솔 권한, 모니터링 도구에 맞춰 달라진다.
DATABASE_URL 또는 secret 값이 실제 RDS endpoint를 가리키는지 확인한다. localhost, 오래된 dev endpoint, 잘못된 port는 네트워크 장애처럼 보일 수 있다.첫 회독의 목표는 값을 바꾸는 것이 아니라 “이 DB가 어느 VPC에 있고, 누가 접속할 수 있고, 복구 가능 시점은 어디인가”를 말할 수 있게 되는 것이다.
프로세스 수 × pool max를 계산한다.DatabaseConnections와 Parameter Group의 max_connections를 비교한다.ReplicaLag를 본다.networkaddress.cache.ttl을 특히 본다.0.0.0.0/0이 아니라 애플리케이션 SG가 source로 들어 있는지 확인한다.DatabaseConnections가 배포 시간에 어떻게 변하는지 본다.ReplicaLag와 replica endpoint 사용 위치를 확인한다.| 개념 A | 개념 B | 차이점 |
|---|---|---|
| RDS | EC2 자가 관리 DB | RDS는 백업·패치·failover 운영을 관리형으로 제공하고, EC2는 직접 운영한다 |
| Multi-AZ DB instance | Multi-AZ DB cluster | instance는 동기식 비가독 standby 1개, cluster는 준동기식 readable reader 2개다 |
| Multi-AZ DB instance | Read Replica | 전자는 장애 대비, 후자는 비동기 읽기 분산이다 |
| Endpoint | IP address | endpoint는 DNS 이름이고, failover 때 가리키는 IP가 바뀔 수 있다 |
| 자동 백업 | 수동 Snapshot | 자동 백업은 보관 기간을 따르고, snapshot은 삭제 전까지 남는다 |
| PITR | Snapshot restore | PITR은 특정 시점 복원, snapshot은 snapshot 생성 시점 복원이다 |
| Parameter Group | 애플리케이션 설정 | Parameter Group은 DB 엔진 설정이고, pool max는 앱 설정이다 |
| Connection Pool | RDS Proxy | pool은 앱 프로세스 안의 연결 재사용, RDS Proxy는 DB 앞의 관리형 프록시다 |
| Database Insights | Query profiler | Database Insights는 DB load와 wait event 관측, profiler는 코드/쿼리 레벨 분석이다 |
추가 학습 키워드: Aurora, RDS Proxy, PITR, Read Replica, ReplicaLag, Parameter Group, Database Insights, Slow Query Log, Connection Pooling, DB Migration Service(DMS), Secrets Manager
max_connections 공식과 한도Task 수 × pool max 계산은 반드시 손으로 해 본다.