티스토리 뷰
"야, 너 그러다 큰일 난다."
로컬 개발 환경이 편하다고 운영(Production) 서버인 EC2에까지 Docker Compose로 앱의 백단과 웹이랑 DB(PostgreSQL)를 같이 올린 상태에서 알파 테스트를 진행하려다가, 내 AI 시니어 개발자에게 검토를 시켰더니 딱 저 한 마디가 돌아왔다.
생각해 보니 그렇다. AWS 크레딧도 $1000나 있는데, 왜 굳이 만 원 아끼겠다고 OOM Killer(메모리 부족 시 프로세스 강제 종료)의 공포를 안고 가야 하나.
나는 개발자지 DBA가 아니다. 새벽 3시에 DB 죽어서 로그 까보고 싶지 않으면, 돈으로 해결할 수 있는 건 해결하는 게 맞다.
그래서 오늘은 그 '마음의 평화'를 사기 위해, Docker로 띄운 PostgreSQL을 AWS RDS로 이사 시키는 과정을 기록한다.
왜 굳이 돈 써서 RDS로 가야하나? 안그럼 큰일남?
ㅇㅇ 큰일남. 왜 EC2 내부 도커 DB가 위험하고, RDS가 답인지 원리를 짚고 넘어가자.
OOM Killer와 시끄러운 이웃 (Noisy Neighbor)
EC2 인스턴스 하나에 앱(Node.js/Spring 등)과 DB를 같이 띄우면 자원 전쟁이 일어난다. 트래픽이 몰려 앱이 메모리를 쳐묵쳐묵 하기 시작하면, 리눅스 커널은 "어? 메모리 없네?" 하고 가장 덩치 큰 녀석을 죽여버린다.
슬프게도 그건 99% 확률로 우리 DB다.
게다가 앱이 CPU를 100% 쓰면 DB 쿼리가 느려지고, 반대로 무거운 쿼리가 돌면 앱이 멈춘다.
서로가 서로에게 '시끄러운 이웃'이 되는 셈이다. 이걸 물리적으로 분리하는 게 RDS 도입의 핵심이다.
RDS 생성의 핵심: gp3와 스토리지 오토스케일링
RDS를 만들 때 아무거나 누르면 안 된다. 가이드에 따르면 다음 설정이 '국룰'이자 모범 사례다.
- 엔진 버전: Docker에서 쓰던 것과 맞춰야함. 버전 다르면 덤프 복원할 때 피곤해진다.
- 스토리지 타입: General Purpose SSD (gp3)를 써라. gp2보다 성능은 좋고 가격은 비슷하거나 더 싸다.
- 스토리지 오토스케일링: 이거 켜놔야 한다. 데이터 꽉 찼다고 DB 멈추는 꼴 보기 싫으면 Enable 체크하고 최대 한계(Threshold)를 설정해 두자.
- 퍼블릭 액세스: 당연히 No. DB는 방구석(VPC 내부)에 있어야지 대문 열어놓으면 털린다.
데이터 이관의 정석: pg_dump와 옵션의 미학
데이터를 옮길 때 그냥 복사 붙여넣기가 아니다. pg_dump를 써야 하는데, 여기서 중요한 건 옵션이다.
# Docker 내부의 DB를 밖으로 꺼내는 명령어
# -T: TTY 할당 금지 (Docker exec 에러 방지용)
# --no-owner --no-privileges: RDS 마이그레이션 필수 옵션
# (이거 안 하면 권한 에러가 나는데, 상당히 까다롭다.)
docker compose exec -T postgres pg_dump -U postgres -d postgres --no-owner --no-privileges > data/postgres_dump_clean.sql
여기서 --no-owner가 왜 중요하냐면, 로컬 DB의 주인(Owner) 권한과 RDS의 마스터 사용자 권한 체계가 다르기 때문이다. 이 옵션 없이 덤프를 뜨면 복원할 때 "너 권한 없는데?" 하면서 에러를 뿜어낸다. (경험담이다...)
복원 및 연결 (Restore & Reconnect)
덤프를 떴으면 RDS에 밀어 넣어야 한다. 로컬이나 EC2에서 RDS 엔드포인트를 바라보고 쏘면 된다.
# 복원 명령어
PGPASSWORD='비밀번호' psql -h <RDS-엔드포인트> -U postgres -d postgres -f data/postgres_dump_clean.sql
복원이 끝나면 백엔드 앱의 환경 변수(.env)만 바꿔주면 끝이다. DB_HOST를 localhost에서 RDS 엔드포인트로 변경하고, SSL 연결(DB_SSL=true)을 켜주는 게 좋다.(RDS는 무조건 SSL만 허용하는 거 같다.)
# 이제 localhost나 postgres 컨테이너 이름이 아니다.
DB_HOST=RDS 엔드포인트
DB_PORT=5432
DB_USERNAME=RDS Master User Name
DB_PASSWORD=RDS Master Password
DB_NAME=RDS DB NAME
# [중요] RDS는 보안상 SSL 연결을 권장한다. 이거 안 켜면 연결 거부당할 수도 있음.
DB_SSL=true
Troubleshooting
역시나 한 번에 되면 마이그레이션이 아니지. 이번 이사 과정에서 마주친 찐 에러들이다.
[Error] psql: ... OCI runtime exec failed
[상황] 덤프 파일을 열어봤더니 SQL 쿼리는 없고 이상한 에러 메시지만 들어있었다. OCI runtime exec failed: exec failed: unable to start container process...
[원인] docker compose exec 명령어를 쓸 때 -T 옵션을 안 썼다. 쉘 스크립트나 리다이렉션(>)으로 결과를 파일로 저장할 때, 터미널(TTY)을 할당하려고 하면 도커가 "나보고 어쩌라고"라며 뱉어내는 에러다.
[해결책] 명령어에 -T 옵션을 추가해서 해결. sudo docker compose exec -T postgres ...
[Error] Connection timed out
[상황] 복원하려고 psql을 날렸는데 아무 반응 없이 커서만 깜빡이다가 timeout이 뜬다.
[원인] 100% 보안 그룹(Security Group) 문제다. RDS를 생성할 때 보안 그룹을 새로 만들었는데, 방화벽 규칙이 비어있어서 내 EC2의 접근조차 막고 있었다.
[해결책] AWS 콘솔 -> RDS 보안 그룹 -> Inbound Rules 편집. 처음엔 EC2 보안 그룹 ID를 넣으려다가, VPC 내 서브넷 IP 대역을 허용하는 방식으로 해결했다. Type: PostgreSQL (5432) Source: 내 VPC의 Private Subnet CIDR (예: 10.0.1.0/24) 이렇게 하면 내 VPC 서브넷 안에 있는 EC2 인스턴스들은 별도 초대 없이도 RDS에 접근할 수 있다. (물론 Public IP인 0.0.0.0/0은 절대 넣으면 안 된다!)
결론 (Outro)
이제 docker-compose.yml에서 postgres 서비스를 과감히 지웠다. 메모리 그래프가 안정을 되찾았고, 혹시나 DB가 죽을까 봐 노심초사하던 마음도 사라졌다.
오늘의 교훈:
- 데이터베이스는 소중하다. 전문가(RDS)에게 맡기자.
- pg_dump 할 때는 --no-owner를 잊지 말자.
- 인프라 아키텍처는 초반에 잡아야 나중에 피눈물 안 흘린다.
이제 DB도 분리했으니, 다음은 스토리지를 S3를 사용하도록 셋팅하고 파일들도 마이그레이션 해야 한다.(산 넘어 산이네)
끝!
- Async
- redux-thunk
- 정보보안기사 #실기 #정리
- 맛집
- redux
- 인천 구월동 이탈리안 맛집
- await
- 인천 구월동 맛집
- 파니노구스토
- 이탈리안 레스토랑
- javascript
- react
- AsyncStorage
- react-native
- Promise
- Total
- Today
- Yesterday