PostgreSQL, MySQL, Oracle 중 무엇을 선택할까¶
“가장 빠른 데이터베이스”를 찾는 대신, 우리 팀의 업무 규칙과 병목을 가장 적은 위험으로 감당할 제품을 고르는 글입니다.
새 서비스의 데이터베이스를 정할 때 흔히 “PostgreSQL과 MySQL 중 어느 쪽이 더 빠른가?”부터 묻는다. 그러나 제품 이름만 놓고는 답할 수 없다. 같은 MySQL도 기본 키의 크기, 보조 인덱스 수, 조회와 쓰기 비율, 한 트랜잭션이 잡고 있는 행의 수에 따라 병목이 전혀 달라진다. Oracle을 MySQL로 옮겼더라도 빨라졌다는 사실만으로 선택이 옳았다고 말할 수 없다. 라이선스와 운영 조직, 장애 격리, 기존 코드의 의존성까지 함께 달라졌기 때문이다.
이 글의 결론은 간단하다. 이미 안정적으로 운영하는 제품이 있다면 그것이 출발점이고, 다른 제품으로 결론을 바꾸려면 측정 가능한 이유가 있어야 한다. 새 제품의 장점보다 이전·교육·장애 대응 비용이 더 클 수 있어서다.
먼저 이름의 관계부터 바로잡기¶
PostgreSQL은 하나의 관계형 데이터베이스 관리 시스템이다. SQL 처리기, 질의 최적화기, 동시성 제어, 복구, 저장 구조를 함께 제공한다. 자료형·연산자·인덱스·확장 기능을 데이터베이스 안에 추가할 수 있는 구조가 강점이다. 복잡한 조인, JSON 문서 일부 검색, 지리 정보, 전문 검색처럼 조회 방식이 다양해질 가능성이 크다면 후보가 되기 쉽다. 다만 기능이 많다는 사실은 자동으로 좋은 실행 계획을 보장하지 않는다. 통계가 실제 데이터 분포를 잘못 표현하거나 오래된 행 버전이 쌓이면 성능과 운영 부담이 커진다.
MySQL도 완성된 데이터베이스 서버다. 연결, SQL 해석, 권한, 최적화, 복제 같은 서버 기능을 담당하고, 실제 표의 행·인덱스·트랜잭션·복구는 저장 엔진에 맡길 수 있다. InnoDB는 MySQL 안에서 이 역할을 하는 기본 저장 엔진이다. MySQL 8.4에서 별도 엔진을 지정하지 않고 표를 만들면 InnoDB가 사용된다.
따라서 MySQL과 InnoDB를 서로 경쟁하는 제품처럼 비교하면 안 된다. 보통 “MySQL을 쓴다”는 말은 MySQL 서버가 SQL을 처리하고 InnoDB가 데이터를 저장한다는 뜻이다. 이 관계와 내부 흐름은 MySQL 안에서 InnoDB가 맡는 일에서 저장 엔진의 배경부터 이어서 설명한다.
Oracle Database는 관계형 모델 위에 오랜 기간 축적된 관리·고가용성·보안·분할 기능과 상용 지원 체계를 제공한다. 실제 선택에서 중요한 것은 기능 개수가 아니다. 이미 핵심 업무가 Oracle의 저장 프로시저, 운영 도구, RAC(Real Application Clusters, 여러 인스턴스가 하나의 데이터베이스를 함께 서비스하는 구성), Data Guard 같은 복구 체계에 깊이 기대고 있는지가 중요하다. 이런 조직에서 라이선스 비용만 보고 교체하면 데이터 이동보다 업무 로직 검증과 운영 절차 재구축이 더 큰 사업 위험이 될 수 있다.
반대로 새 팀이 이런 기능과 지원 체계를 사용하지 않고, Oracle을 운영할 인력과 예산도 없다면 Oracle의 넓은 기능은 장점이 아니라 지불만 하는 선택지가 된다. Oracle은 “대기업용”, MySQL은 “작은 서비스용”이라는 구분보다 현재 조직이 어떤 보장과 운영 체계를 실제로 사용할 것인가로 판단해야 한다.
결론을 바꾸는 다섯 가지 질문¶
1. 팀이 장애를 복구해 본 제품은 무엇인가¶
제품 기능보다 강한 기본값은 팀의 운영 경험이다. 백업 파일이 있다는 것과 정해진 시간 안에 복구할 수 있다는 것은 다르다. 버전 올리기, 온라인 스키마 변경, 복제 지연, 잠금 대기, 장애 조치 절차를 이미 자동화한 제품이라면 그 자산도 선택 비용에 포함해야 한다.
관리형 서비스를 사용하면 설치 부담은 줄지만 제품 지식이 사라지지는 않는다. 느린 질의와 잘못된 인덱스, 긴 트랜잭션, 연결 풀 고갈은 여전히 애플리케이션과 팀의 책임이다. “클라우드가 운영해 준다”는 말은 장애 시 역할 경계를 계약과 실제 절차로 확인해야 의미가 있다.
2. 틀리면 안 되는 업무 규칙은 어디에 있는가¶
잔액, 재고, 원장처럼 여러 변경이 함께 성공해야 한다면 트랜잭션 경계와 격리 동작을 먼저 본다. 세 제품 모두 트랜잭션을 제공하지만 같은 격리 수준 이름이 모든 세부 동작까지 같다는 뜻은 아니다. 잠금 범위, 스냅샷이 만들어지는 시점, 충돌 시 대기와 취소 방식은 다를 수 있다.
제품을 고르기 전에 “쿠폰은 한 사람당 한 번만 쓴다”, “취소 합계는 승인 금액을 넘지 않는다” 같은 불변식을 문장으로 적는다. 그다음 고유 제약, 외래 키, 조건 검사, 트랜잭션 중 무엇으로 지킬지 정한다. 이 단계가 없으면 강한 데이터베이스를 사용하면서도 애플리케이션의 확인 후 업데이트 사이에서 규칙이 깨진다.
3. 조회가 얼마나 다양하게 변하는가¶
정해진 기본 키와 몇 개의 보조 인덱스로 대부분의 요청을 처리하고, 조직에 MySQL 운영 표준이 잘 갖춰져 있다면 InnoDB는 매우 실용적인 기본값이다. 반면 조회가 복잡하게 조합되고 JSON, 배열, 전문 검색, 지리 정보, 사용자 정의 자료형처럼 관계형 모델 주변 기능을 한 트랜잭션 안에서 함께 다뤄야 한다면 PostgreSQL의 확장성이 선택 이유가 될 수 있다.
여기서 “PostgreSQL은 복잡한 질의, MySQL은 단순 질의”라고 외우면 안 된다. 실행 계획은 스키마와 데이터 분포에 의해 결정된다. 실제 대표 질의를 두 제품에 같은 데이터 규모로 넣고, 읽은 행 수와 페이지 수, 실행 계획의 추정 오차를 비교해야 한다.
4. 기본 키와 쓰기 패턴이 저장 구조에 어떤 비용을 만드는가¶
InnoDB에서는 기본 키가 행의 물리적 기준이고 보조 인덱스가 기본 키를 참조한다. 기본 키가 넓으면 보조 인덱스가 함께 넓어진다. 값이 무작위로 퍼지는 키는 이미 찬 여러 페이지 사이에 삽입되며 페이지 분할과 캐시 분산을 늘릴 수 있다. 쓰기가 많은 서비스에서 이 차이는 제품 이름보다 크게 나타날 수 있다.
PostgreSQL의 일반 표는 힙에 행을 두고 인덱스가 행 위치를 가리킨다. 업데이트는 새 행 버전을 만들기 때문에 오래된 버전을 정리하는 VACUUM과 긴 트랜잭션 관리가 중요하다. 어느 구조가 절대적으로 우수한 것이 아니라, InnoDB는 기본 키 설계의 비용이 넓게 퍼지고 PostgreSQL은 행 버전 정리 상태가 지속적으로 영향을 준다고 이해하면 된다.
5. 이 선택을 되돌리는 비용은 얼마인가¶
데이터베이스 교체는 접속 주소를 바꾸는 작업이 아니다. SQL 문법과 함수, 정렬 규칙, 시간대, 자동 증가 값, 격리 동작, 오류 코드, 스키마 변경 방식이 애플리케이션에 스며든다. 저장 프로시저와 제품 전용 기능이 많을수록 되돌리기 어렵다.
그래서 새 서비스에는 조직 표준 제품과 이식 가능한 SQL로 작게 시작하고, 특정 기능이 실제 문제를 크게 줄일 때 의도적으로 의존하는 편이 안전하다. 반대로 이미 제품 전용 기능이 핵심 업무를 안정적으로 지키고 있다면 “중립성”만을 위해 제거하는 것도 비용이다.
초당 요청 수 하나로는 고를 수 없다¶
“몇 TPS부터 샤딩해야 하나?”라는 질문에도 하나의 숫자는 없다. TPS(Transaction Per Second, 초당 트랜잭션 수)가 같아도 한 건이 행 하나를 읽는지, 인덱스 다섯 개를 업데이트하는지, 같은 키에 몰리는지에 따라 부하는 달라진다. 데이터베이스 선택과 확장 시점에는 다음 수치를 함께 기록해야 한다.
- 조회·쓰기 요청 수와 p50·p95·p99 지연
- 한 질의가 읽은 행과 실제 반환한 행의 비율
- 트랜잭션 길이, 잠금 대기 시간, 교착 상태 발생 수
- 버퍼 캐시 적중률과 저장 장치 조회·쓰기 대역폭
- WAL 또는 redo 생성량, 복제 지연과 복제본 적용 속도
- 데이터와 인덱스 증가량, 남은 용량이 버틸 시간
- 스키마 변경 시간과 그동안 생긴 잠금·복제 영향
- 백업에서 복구해 실제 서비스가 열릴 때까지의 시간
이 수치가 현재 장비와 운영 목표 안에 있으면 유명한 대규모 사례를 따라 샤딩할 이유가 없다. 반대로 평균 지연이 낮아도 p99가 급격히 오르고 복제 지연이 계속 누적되며 디스크 소진 시점이 가까우면 구조 변경을 검토해야 한다.
flowchart TB
A["업무 규칙과 장애 허용 범위를 적는다"] --> B["현재 팀의 운영 경험과 표준을 확인한다"]
B --> C["대표 조회·쓰기와 데이터 분포를 재현한다"]
C --> D["꼬리 지연·잠금·로그량·복제 지연을 측정한다"]
D --> E{"기존 표준이 목표를 만족하는가?"}
E -- "그렇다" --> F["표준 제품으로 시작하고 운영 절차를 재사용한다"]
E -- "아니다" --> G["부족한 보장을 가장 직접 채우는 제품을 비교한다"]
G --> H["이전·교육·복구 비용까지 포함해 결정한다"]
실제 사례에서 읽어야 할 것¶
토스페이먼츠는 공유 Oracle 원장에 새 표를 더하는 대신 독립 MySQL 원장을 만들었다. 공개 글에 제시된 이유는 “MySQL이 무조건 빠르다”가 아니었다. 다른 팀 작업의 장애 영향을 분리하고, 조직의 MySQL·JPA 중심 개발 방식을 맞추며, 별도 환경에서 변경을 시도하기 쉬워진다는 판단이었다. 제품 선택이 곧 소유권과 팀의 작업 속도 선택이었던 셈이다.
LINE Manga는 캠페인 뒤 활동 사용자가 늘면서 MySQL 복제 지연을 겪었다. 이때 단순 사용자 수가 아니라 바이너리 로그 회전이 40분에 한 번에서 10분에 한 번으로 빨라진 점, 디스크 사용량이 8개월 동안 2.6TB에서 4.6TB로 늘어 현재 추세면 6개월 뒤 가득 찬다는 점을 근거로 삼았다. 이후 쓰기가 집중된 다섯 표 약 3TB를 골라 사용자 식별자 기준 여덟 조각으로 나눴다. “대규모 서비스는 샤딩한다”가 아니라 복제와 용량의 지속 가능한 한계가 관측됐기 때문에 필요한 범위만 나눴다고 읽어야 한다.
또 다른 LINE 사례에서는 8,500개가 넘는 데이터베이스 서비스를 3만 7천 개 이상의 가상 머신으로 운영했다. 이 규모에서는 개별 제품 기능만큼 생성·장애 조치·상태 복구를 표준화하는 내부 플랫폼이 중요해졌다. 반대로 데이터베이스 몇 개를 운영하는 팀이 같은 플랫폼을 먼저 만들면 자동화가 줄이는 비용보다 플랫폼 자체의 유지 비용이 더 클 수 있다.
토스페이먼츠의 다른 사례에서는 JDBC의 batchUpdate로 5천 건을 넣는 작업이 1분 넘게 걸렸다. 원인은 “데이터베이스가 느리다”는 한 문장으로 끝나지 않았다. 네트워크 패킷과 호출 경로를 추적해 대량 삽입 전에 예상 밖의 조회가 반복되는 사실을 찾았다. 제품 교체 전 애플리케이션 드라이버, 연결 왕복, 호출 횟수를 확인해야 하는 이유다.
선택을 한 문장으로 남기는 법¶
좋은 결정 기록은 “PostgreSQL이 기능이 많아서 선택했다”로 끝나지 않는다. 다음처럼 조건과 포기한 비용이 함께 있어야 나중에 재검토할 수 있다.
결제 원장의 강한 무결성을 유지하면서 팀이 이미 운영하는 복구 절차를 재사용해야 하므로 MySQL과 InnoDB를 선택한다. 대표 부하에서 p99 지연, 잠금 대기, 복제 지연이 목표 안에 드는지 확인한다. 복잡한 지리·전문 검색은 현재 요구가 아니므로 PostgreSQL 확장 기능의 이점보다 새 운영 체계를 만드는 비용이 크다고 판단한다.
조건이 바뀌면 결론도 바뀐다. 한 제품을 영구 승자로 정하는 대신, 어떤 관측값과 업무 요구가 바뀌면 다시 비교할지를 함께 기록하는 것이 관계형 데이터베이스 선택의 핵심이다.
참고 자료¶
- PostgreSQL 공식 문서: 다중 버전 동시성 제어 소개
- PostgreSQL 공식 문서: JSON 자료형과 인덱싱
- PostgreSQL 공식 문서: 확장 기능
- MySQL 8.4 공식 문서: InnoDB 소개
- MySQL 8.4 공식 문서: InnoDB 클러스터형·보조 인덱스
- Oracle 공식 문서: Oracle Database 구조와 인스턴스
- Oracle 공식 문서: Real Application Clusters
- Oracle 공식 문서: Data Guard
- 토스페이먼츠: 레거시 결제 원장을 확장 가능한 시스템으로
- 토스페이먼츠: Spring JDBC 성능 문제, 네트워크 분석으로 파악하기
- LINE Engineering: LINE Manga 데이터베이스 샤딩
- LINE Engineering: 쿠버네티스를 활용한 선언형 클라우드 DB 서비스