🔥 엔진 선택: MySQL·PostgreSQL·Aurora

2256자
25분

16:9 가로 표지 일러스트레이션. 흰 배경. 모던 플랫 미니멀 에디토리얼 디자인. 화면 왼쪽에 muted AWS-orange 둥근 사각형 안에 MySQL, PostgreSQL, Aurora 라벨이 붙은 작은 RDBMS 데이터베이스 실린더 세 개가 세로로 쌓여 있고, 화면 오른쪽에서 가는 sky-blue 화살표가 그중 하나를 가리키며 결정 지점을 표시한다. 실린더 아래에 muted gray sans-serif 로 'engine selection' 캡션. 좌상단 다크 슬레이트 캡션 'RDS pick your engine, but compatibility cuts deeper'. 절제된 sans-serif. 프로페셔널 IT 표지 스타일.

처음 RDS 콘솔의 Create database 화면을 열었을 때 가장 당황했던 대목은 Engine options였다. 라디오 버튼이 여러 개 있다. Aurora (MySQL Compatible), Aurora (PostgreSQL Compatible), MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, IBM Db2. 이름이 비슷해 보이는 줄이 두 쌍 있어서 더 혼란스러웠다. Aurora MySQL과 그냥 MySQL은 무엇이 다른가. Aurora PostgreSQL과 그냥 PostgreSQL은 또 무엇이 다른가. 거기서 한참을 멈춰 있었다.

앞에서 RDS란 무엇인가: 관리형 DB의 의미에서 RDS 전체 구조와 지원 엔진 목록을 짚었으니 여기서는 그 위에서 한 단계만 더 들어간다. 한 줄 답을 먼저 내려놓자면 호환 엔진과 순정 엔진은 사실상 다른 플랫폼이다. 차이는 단일 인스턴스 시간 단가가 아니라 스토리지 아키텍처와 청구 모델에서 나뉜다. 그리고 실무에서 거의 모든 결정은 그중 셋 사이에서 일어난다. MySQL, PostgreSQL, 그리고 Aurora 두 변종.

호환이라는 단어의 결

Aurora가 자기를 MySQL-compatible, PostgreSQL-compatible이라고 부를 때 그게 정확히 무엇을 의미하는지부터 짚자. 호환은 두 수준에서 드러난다.

첫째는 wire-protocol 호환이다. 클라이언트가 MySQL 5.7로 접속할 때 던지는 네트워크 프로토콜을 Aurora-MySQL이 그대로 받는다. 그래서 애플리케이션 코드는 손댈 게 없다. JDBC URL의 host만 Aurora cluster endpoint로 바꾸면 그만이다. 마찬가지로 PostgreSQL 14로 접속하던 Spring Boot 앱도 Aurora-PostgreSQL로 옮길 때 driver 한 줄 안 고친다.

둘째는 SQL dialect 호환 범위다. SELECT, JOIN, WHERE, window function, JSON 타입, 대부분의 표준 SQL 구문이 똑같이 동작한다. PostgreSQL의 LATERAL JOIN도 되고 MySQL의 ON DUPLICATE KEY UPDATE도 된다.

16:9 가로 에디토리얼 일러스트레이션. 흰 배경. compatible 와 native 두 다이어그램이 나란히. 양쪽 모두 동일한 클라이언트 컴퓨터 아이콘이 가는 sky-blue 'wire-protocol' 선으로 데이터베이스 실린더에 연결된다. compatible 쪽 실린더 아래에는 점선이 3개 영역에 흩어진 6개 작은 storage 박스로 이어지고, native 쪽 실린더는 단일 디스크 박스 위에 놓인다. 상단 다크 슬레이트 캡션 'compatible at the wire — different underneath'.

여기까지 보면 그럼 그냥 같은 엔진이네라고 생각하기 쉽다. 나도 처음엔 그렇게 생각했다. 그런데 실제 차이는 실행 방식에서 바로 드러난다. 이 차이가 결정적이다.

호환이 끊기는 지점은 셋이다. 첫째, 스토리지·복제 아키텍처가 다르다. 순정 MySQL은 InnoDB가 자기 buffer pool과 redo log를 들고 EBS 볼륨 한 개 위에서 동작하지만, Aurora-MySQL은 buffer pool은 같아도 redo log를 자기가 아니라 분산 스토리지 계층으로 보낸다. 여기서부터 두 엔진은 운영 방식이 분명하게 다르다.

둘째, DDL 거동이 다르다. 같은 ALTER TABLE ADD COLUMN 문이라도 순정 MySQL은 테이블을 잠그거나 카피 후 재생성하는 데 비해 Aurora-MySQL은 분산 스토리지의 fast DDL 경로를 탄다. 한 시간 락이 걸릴 일이 몇 초로 줄기도 한다.

셋째, Aurora는 MySQL plug-in과 extension을 모두 그대로 받지 않는다. NDB Cluster 관련 storage engine은 Aurora-MySQL이 안 받는다. PostgreSQL 쪽도 마찬가지로 일부 third-party extension은 Aurora-PostgreSQL의 허용 목록 바깥이다. 그래서 기존 확장을 전제로 한 구성은 바로 옮기지 못한다.

그래서 Aurora의 compatible클라이언트 입장에서는 같은 엔진, 운영 입장에서는 다른 플랫폼이라고 읽으면 거의 맞다.

Aurora 스토리지 구조 더 깊게 짚기

앞 편에서 Aurora가 3 AZ × 2 복제 = 6 복제로 데이터를 쓴다는 단문 사실까지는 짚었다. 이제 6중 복제가 어떻게 움직이는지부터 짚는다.

Aurora의 storage volume은 한 덩어리가 아니다. Aurora는 데이터를 10 GiB 단위 segment(공식 용어로는 protection group)로 잘게 나눈다. 100 GiB 데이터베이스라면 segment가 10개고, 1 TiB라면 100개다. Aurora는 각 segment를 독립적으로 6중 복제로 배치한다. 클러스터 하나가 100 GiB라면 storage 노드들에 분산된 segment 수는 10 × 6 = 60개다.

Aurora 분산 스토리지 다이어그램 — 한 클러스터 storage volume이 10 GiB segment로 쪼개져 3개 AZ 각각 2개 storage 노드, 합 6 노드에 6 복제로 흩어진다. 한 segment의 6 복제가 색으로 강조돼 모든 노드에 한 개씩 들어 있다.

그림 1. 같은 색의 사각형 6개 = 한 segment의 6 복제. AZ 한 곳이 통째로 사라져도 나머지 두 AZ에 4 복제가 살아 있어 Aurora가 4-of-6 quorum을 그대로 채운다.

이 segment 단위 분할이 결정적인 이유가 두 가지 있다. 하나는 장애 격리다. 한 storage 노드가 죽었다고 해서 클러스터 전체가 영향받지 않는다. 그 노드가 들고 있던 segment 중 일부만 일시적으로 한 복제가 모자란 상태가 되고, 나머지 segment는 멀쩡하다. 다른 하나는 병렬 복구다. 죽은 노드의 segment들을 여러 다른 노드에서 동시에 채운다. 한 segment가 10 GiB로 작기 때문에 복구 시간이 짧다.

writer 인스턴스가 commit을 받을 때 일어나는 일을 한 번 따라가 보자. 순정 MySQL의 InnoDB는 트랜잭션을 commit할 때 redo log와 더티 페이지(buffer pool에서 변경된 페이지)를 모두 디스크에 쓴다. 페이지 자체가 16 KiB 단위니 commit 한 번에 디스크 I/O가 꽤 무겁다.

Aurora-MySQL의 writer는 다르다. redo log만 흘려보낸다. 더티 페이지는 디스크에 안 쓴다. 그 redo log가 storage 노드 6개 중 4개에서 durable이라는 응답을 받으면 트랜잭션 commit이 성공한다. 이게 4-of-6 write quorum이다. 페이지를 redo log로부터 재생성하는 일은 storage 노드들이 알아서 한다. writer는 그걸 신경 안 쓴다.

4-of-6 write quorum 다이어그램 — writer가 6개 storage 노드로 redo log를 흘리면 그중 4개에서 durable ack가 돌아온 시점에 commit이 성공한다. 나머지 2개는 뒤늦게 따라잡아도 된다.

그림 2. 빨간 화살표 4개가 ack로 돌아온 시점이 commit 성공점. 나머지 2개 노드는 background로 따라잡아 결국 6중 복제를 모두 채운다.

읽기는 3-of-6 read quorum이다. read replica가 같은 storage volume을 그대로 마운트하기 때문에 writer가 쓰던 데이터를 그대로 본다. 별도 binlog 기반 복제 같은 게 끼지 않는다. 그래서 Aurora의 read replica는 lag이 매우 짧다. 보통 수십 밀리초 안쪽에서 끝난다.

이 모델 덕분에 Aurora는 인스턴스와 스토리지를 깨끗이 떼어 놓는다. read replica를 추가해도 storage volume을 따로 더 만들지 않는다. 같은 segment들 위에 새 reader 인스턴스가 하나 더 붙는 것뿐이다. 그래서 Aurora 한 클러스터에 read replica를 최대 15개까지 붙일 수 있다. RDS for MySQL/PostgreSQL도 동일 region 안에서 한도 자체는 15개다. 다만 AWS는 이 한도를 다루는 기준을 Aurora와 다르게 둔다. 순정 쪽은 reader 한 대마다 자기 EBS 볼륨이 있고 writer로부터 binlog/streaming replication을 받아 적용해야 한다. reader를 늘릴수록 EBS·인스턴스 비용이 그대로 곱해지고 replication lag도 인스턴스마다 따로 관리해야 한다. Aurora 쪽은 같은 6 복제 segment 위에 reader 인스턴스만 추가하는 모델이라 storage 비용이 곱해지지 않는다.

Aurora vs RDS for MySQL read replica 토폴로지 비교 — Aurora는 한 storage volume 위에 writer 와 reader 인스턴스들이 모두 같은 데이터를 본다. RDS for MySQL은 writer 와 reader 가 각자 EBS 볼륨을 따로 들고 binlog 로 복제한다.

그림 3. 두 토폴로지의 결정적 차이는 storage 볼륨이 공유 자원이냐 인스턴스별 자원이냐다. 그래서 Aurora reader 수가 EBS 사본 부담 없이 15까지 가는 것.

그리고 클러스터 storage volume의 자동 확장 한도가 작년부터 한 번 더 커졌다. 2026년 5월 기준 Aurora MySQL 3.10(MySQL 8.0.42 호환) 이상과 Aurora PostgreSQL 17.5/16.9/15.13 이상은 Aurora가 자동으로 256 TiB까지 늘려 준다. 그 아래 버전은 128 TiB가 천장이다. 작은 차이 같지만 내가 이 클러스터를 5년 뒤까지 운영할 거라면 어느 메이저 버전을 깔 것인가라는 결정에 영향을 준다.

엔진별 운영 차이

여기까지 오면 호환 엔진과 순정 엔진을 사실상 다른 플랫폼으로 봐야 하는 이유가 또렷하다. 한 번 표로 정리하자.

엔진라이선스스토리지최대 read replica단가 비교쓰면 안 되는 경우
MySQL CommunityGPLv2 (오픈소스)EBS gp3·io2, 단일 AZ 묶임15 (인스턴스마다 EBS 따로)가장 쌈다중 AZ 자동 failover가 분 단위 RTO로 충분치 않을 때
PostgreSQLPostgreSQL License (BSD-like)EBS gp3·io2, 단일 AZ 묶임15 (streaming replication)가장 쌈extension 충돌 없는 워크로드면 가장 무난한 답
MariaDBGPLv2 + 일부 BSLEBS gp3·io215MySQL과 비슷MySQL 8.0의 신기능(Window function, CTE)이 필요할 때
OracleOracle 라이선스 (BYOL/LI)EBS15라이선스 비용이 인스턴스 단가를 압도라이선스 의무 없는 그린필드 (굳이 비싼 길로 안 가는 게 답)
SQL ServerMicrosoft 라이선스EBS15같은 기준으로 보면 라이선스 비용 부담이 큼.NET 의존성 없는 그린필드
Aurora-MySQLMySQL 호환 (AWS 자체)분산 스토리지, 3 AZ (한 볼륨 공유)15 (storage 공유)단일 인스턴스는 더 비싸지만 read fan-out·고가용성 워크로드에서 총비용 역전단일 AZ 토이 프로젝트, MySQL plug-in 의존 워크로드
Aurora-PostgreSQLPostgreSQL 호환 (AWS 자체)분산 스토리지, 3 AZ (한 볼륨 공유)15 (storage 공유)같은 기준같은 조건

표 한 줄을 더 풀어 두자면, 인스턴스 시간 단가만 보면 Aurora가 RDS for MySQL보다 대체로 더 비싼 편이다(정확한 차이는 region·인스턴스 클래스·Standard/I/O-Optimized 구성에 따라 다르다). 이걸 보고 Aurora가 비싸네라고 판단하면 절반만 본 거다. 같은 워크로드를 read replica를 5개 띄워 다중 AZ로 운영해야 하는 시나리오라면, 순정 MySQL 쪽은 인스턴스 5대 + 각자의 EBS 볼륨이 곱해져 들어간다. Aurora 쪽은 reader 인스턴스 5대만 추가될 뿐 storage는 한 볼륨 그대로다. 거기서 총비용이 역전한다.

다른 한 줄은 버전 호환 기준이다. 글을 쓰는 2026년 5월 시점에 Aurora MySQL 1(MySQL 5.6 호환)은 이미 2023-02-28에 EOL이 됐다. Aurora MySQL 2(MySQL 5.7 호환)도 2024-10-31에 standard support가 끝나 RDS Extended Support로 자동 enroll됐다. RDS for MySQL 5.7도 같은 시기 2024-02-29에 standard support가 끝났고 Extended Support는 2027-02-28까지 운영 가능하다. 지금 새 클러스터를 만든다면 MySQL 8.0을 까는 게 표준이다. PostgreSQL 쪽은 17이 2024-09-26에 community에서 GA됐고, Aurora PostgreSQL 17은 같은 해 11월 RDS Database Preview 환경에서 먼저 풀린 뒤 정식 지원으로 들어왔다.

Aurora를 고르지 않는 편이 나은 경우

관리형이라고 해서 늘 답은 아니다. Aurora보다 RDS가 더 나은 경우는 셋이다.

첫 번째는 단일 region·단일 AZ 토이 프로젝트다. Aurora의 가치는 읽기 처리와 저장소 부담을 여러 노드에 나눠 처리하는 방식에서 나온다. 그런데 사이드 프로젝트나 PoC는 분당 트래픽이 한 자릿수고 read replica를 안 띄우고 다중 AZ도 안 켠다. 그러면 Aurora가 주는 가치가 거의 살지 않은 채로 인스턴스 단가 프리미엄만 그대로 낸다. 이런 환경에서는 RDS for MySQL이나 PostgreSQL 단일 인스턴스가 답이다.

두 번째는 MySQL plug-in 또는 extension에 묶인 워크로드다. NDB Cluster 같은 cluster storage engine, 또는 PostgreSQL 쪽의 일부 third-party extension(예전에 pg_cron 같은 것도 한동안 그랬다)에 의존하는 코드가 있다면 Aurora의 허용 목록을 먼저 확인해야 한다. 의존성이 허용 밖이면 마이그레이션 자체가 막히고, Aurora로 옮기면 빨라질 거야Aurora로 옮기면 동작 안 해가 된다. 옮기기 전에 Aurora User Guide의 호환 extension 목록을 한 번 읽어 두는 게 안전하다.

세 번째는 읽기 전용 보고용 데이터베이스다. 매일 한두 번 ETL로 데이터를 부어 넣고 BI 도구가 쿼리만 던지는 데이터마트라면, OSS PostgreSQL에 read replica 한두 개 붙은 RDS 구성이 거의 항상 더 싸다. 분산 스토리지가 보호해야 할 쓰기 워크로드 자체가 적기 때문이다.

'관리형'이 감추는 비용: Aurora I/O-Optimized

Aurora를 보다 보면 또 하나 만나는 결정 지점이 있다. 콘솔의 Cluster storage configuration 옵션에 Aurora StandardAurora I/O-Optimized 두 개가 있다. 처음엔 I/O 최적화라는 게 더 빨라지는 옵션인가? 싶었는데 그게 아니었다. 청구 모델 자체가 다른 두 모드다.

Aurora Standard는 우리가 RDS란 무엇인가: 관리형 DB의 의미에서 본 5겹 청구 그대로다. 인스턴스 시간 + 스토리지 GB월 + I/O 요청 수 + backup storage + DTO. 여기서 I/O 요청은 storage 노드에 redo log가 닿거나 페이지가 읽히는 모든 횟수를 카운트한다. write-heavy 워크로드일수록 I/O 요청이 계속 늘어난다.

Aurora I/O-Optimized는 2023-05-11 GA된 두 번째 모드다. I/O 청구가 0이고 그 대신 인스턴스 시간 단가와 스토리지 GB월 단가가 올라간다. 스토리지 GB월은 공식 가격표에서 직접 비교하면 Aurora Standard $0.10에서 Aurora I/O-Optimized $0.225로 약 2.25배다. 인스턴스 단가도 함께 올라간다.

16:9 가로 에디토리얼 비용 곡선 일러스트레이션. 흰 배경. 모던 플랫 미니멀 인포그래픽. X축 'I/O spend as percent of total Aurora bill', Y축 'monthly bill'. slate-gray 선 'Aurora Standard'는 처음엔 완만하다가 25 percent 이후 가파르게 상승하고, muted AWS-orange 수평선 'Aurora I/O-Optimized'는 거의 평평하게 유지. 두 선이 25 percent 부근에서 교차하는 지점에 'break-even' 마커가 표시돼 있다. 차트 오른쪽에 'Standard 1000 USD'와 'I/O-Optimized 993 USD' 라벨.

언제 I/O-Optimized가 답인가. AWS 공식 발표는 I/O 요청 비중이 25%를 넘으면 I/O-Optimized가 더 유리하다고 설명한다. 현재 Aurora 청구 중 I/O 요청 비용이 25%를 넘는 워크로드라면 I/O-Optimized로 전환할 때 AWS는 최대 40%까지 비용을 줄여 준다. 25%라는 임계값은 우연이 아니다. write-heavy 워크로드의 청구 패턴을 보면 I/O 요청이 25%를 넘는 지점부터 I/O 청구가 본체 비용보다 빠르게 자라는 영역이고, 거기서 정액제로 전환할 때 차익이 가장 크다.

작은 worked example을 직접 계산해 보자. 한 클러스터의 한 달 Aurora Standard 청구가 인스턴스 600 + 스토리지 50 + I/O 250 + backup 100 = $1,000이라고 하자. I/O 비중은 25%다. 이걸 I/O-Optimized로 옮기면 인스턴스 600은 30% 올라 780, 스토리지 50은 2.25배가 돼 약 113이 되고, I/O 250은 0이 된다. 합이 780 + 113 + 100 = $993이다. 거의 본전이다. I/O 비중이 커질수록 두 옵션의 총비용 차이는 줄어든다. 50%면 발표 자료가 말한 최대 40% 절감 지점에 거의 닿는다.

16:9 가로 컨셉추얼 일러스트레이션. 흰 배경. 'managed' 라벨이 붙은 카드보드 박스 한 개가 얇은 받침대 위에 놓여 있고, 박스 뚜껑이 살짝 열려 안쪽에 patch, backup, failover, I/O charge, storage premium, replica count 라벨이 붙은 작은 사각형들이 쌓여 있는 게 보인다. 안쪽 라벨들은 muted AWS-orange 로 은은히 빛나고 바깥 박스는 slate gray. 상단 다크 슬레이트 캡션 'managed: what is folded inside the word'.

여기서 짚어 둘 점이 있다. Aurora는 비용을 두 기준으로 계산하고, 그 차이가 선택을 바꾼다. 관리형이 감추는 비용은 한 항목에 고정되지 않는다. 콘솔에서 Aurora를 골랐다고 끝이 아니다. 어느 storage configuration을 골랐는지가 그 다음 한 달 청구를 바꾼다. 더 헷갈리는 건 이 옵션을 한번 토글하면 30일 동안 다시 못 바꾼다는 제약이다(Standard로 되돌리는 건 언제든 된다). 그래서 첫 클러스터를 띄울 때 워크로드 구조를 미리 추정해서 한쪽을 골라야 한다.

다시 콘솔 앞에서

처음 콘솔의 라디오 버튼 앞에서 멈췄던 지점으로 돌아가면 내가 먼저 던질 질문도 바뀐다. 어느 게 답인지 모르겠다가 아니라 내 워크로드가 어느 구조에 더 맞는지부터 따져야 한다. 호환 엔진과 순정 엔진은 클라이언트 입장에선 같지만 운영 입장에선 다른 플랫폼이다. 분산 스토리지의 가치는 read fan-out과 가용성 요구가 있는 곳에서 살고, 그렇지 않은 곳에서는 인스턴스 프리미엄만 더 내고 끝난다. Aurora 안에서도 청구 모델이 두 개라 I/O 비중이 25%를 넘는가가 또 한 번의 결정을 만든다.

지금까지는 각 엔진이 어떤 구조로 동작하는지 짚었다. 다음으로는 Multi-AZ와 장애 조치가 운영에 어떤 차이를 만드는지 다룬다. 그 엔진이 죽었을 때 무엇이 일어나는가가 그 다음 질문이다.

YouTube 영상

채널 보기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
트라이(Trie)에서 단어를 삭제하는 방법 | Trie 자료구조 이야기
직교성과 벡터 투영 | 선형대수학
행렬의 기본 연산 - 행렬 덧셈, 스칼라 곱, 전치 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
투영과 예측, 그리고 선형 결합 | 선형대수학