
들어가며
이번 프로젝트에서 또 다른 도전적인 선택은 자주 쓰던 MySQL을 벗어나 PostgreSQL을 도입해봤다는 것이다! 막상 써보면 MySQL과 크게 다를 것이 없어 무슨 차이인가 싶기도 하다. 그래서 이번기회에 깊게 정리하며 어떤 상황에 어떤 DB기술을 도입하면 좋을지 주관을 확립하려고 한다! 그리고 이번 프로젝트에도 postgreSQL의 이점을 적극 활용할 수 있는 api도 적용해보았다. 가보자!
본론으로
🧐 MySQL과 PostgreSQL의 철학 차이
https://aws.amazon.com/ko/compare/the-difference-between-mysql-vs-postgresql/
PostgreSQL과 MySQL 비교 - 관계형 데이터베이스 관리 시스템(RDBMS) 간의 차이점 - AWS
MySQL은 데이터를 행과 열이 있는 테이블로 저장할 수 있는 관계형 데이터베이스 관리 시스템입니다. 많은 웹 애플리케이션, 동적 웹 사이트 및 임베디드 시스템을 지원하는 널리 사용되는 시스
aws.amazon.com
AWS의 테크글에도 다음과 같이 정리되어있다.
데이터베이스를 고를 때 많은 개발자들이 스펙 비교표만 보고 선택하지만, 실제로는 DBMS가 추구하는 철학이 프로젝트 요구사항과 맞아떨어지느냐가 더 중요하다.
- MySQL
→ 속도가 중요한 단순 CRUD 서비스
→ 대량 트래픽 + 단순한 데이터 모델
→ SNS 초기 서비스, 게시판형 웹서비스, 쇼핑몰의 주문 엔진 등에 적합 - PostgreSQL
→ 복잡한 비즈니스 로직
→ 분석/집계/정렬/통계 등 “두뇌를 쓰는 쿼리”
→ 도메인 규칙이 많은 서비스, 분석형 기능이 많은 서비스
→ 금융, 게임, 로그 분석, 권한 처리 같은 정교한 DB 기능 필요할 때 강함
🛠️ PostgreSQL의 Window Function 최적화는 압도적이다. 왜?
🧑💻 PostgreSQL 최적화 요소 1 - Parallel Execution(병렬 처리)
PostgreSQL
- 정렬(Sort)
- Window Function
- CTE 처리
- Aggregation (집계)
이런 작업을 자동으로 병렬 처리한다. CPU 코어 수가 많을수록 성능 이득이 기하급수적으로 커진다. 예를 들어
SELECT
DENSE_RANK() OVER (ORDER BY balance DESC) AS rank
FROM members;
이 명령이 실행될 때 PostgreSQL은 내부적으로:
- 데이터를 여러 worker에게 분산
- 부분 정렬 → 병합 정렬
- Window 계산을 병렬화
이런 방식으로 처리한다.
MySQL
병렬 쿼리가 존재하긴 하지만, 실제 Window Function + ORDER BY 조합에서 병렬화는 거의 일어나지 않는다.
→ 결과: 회원 수가 늘어날수록 PostgreSQL은 성능이 완만하게 증가하지만, MySQL은 기하급수적 오버헤드가 발생한다.
🧑💻 PostgreSQL 최적화 요소 2 — Incremental Sort + Index Skip Scan
정렬 기반 쿼리(랭킹, 점수 순위, 누적 통계 등)는 DB 성능 병목의 핵심이다. PostgreSQL은 이 병목에 대해 매우 강력한 최적화를 갖고 있다.
PostgreSQL은 이미 정렬된 상태의 인덱스를 활용해 전체 재정렬 없이 필요한 부분만 재정렬한다.
ORDER BY balance DESC, id ASC
이 정렬 기준에 맞춘 인덱스가 있다면:
- 전체 멤버 테이블을 정렬하는 것이 아니라
- 이미 정렬된 balance 인덱스를 따라가면서
- id만 추가 정렬하는 “부분 정렬”로 끝난다.
MySQL에서는 “조건에 걸리지 않은 인덱스 컬럼”은 풀스캔으로 가기 쉽지만, PostgreSQL은 인덱스 구조를 건너뛰며 필요한 값만 탐색한다. 이 두 요소가 결합하면 랭킹 기반 API에서 체감되는 차이가 매우 크다.
🧑💻 PostgreSQL 최적화 요소 3 — CTE Materialization (필요 시 결과 캐싱)
CTE(복잡한 쿼리를 단순화하고 가독성을 높이기 위해 일시적인 결과 집합을 정의하는 방법, Common Table Expression)도 MySQL과 PostgreSQL은 실행 방식이 다르다.
MySQL
- CTE = 사실상 inline view
- materialization 없음
- 즉, “뷰로 변환되어 매번 다시 계산됨”
PostgreSQL
- 옵티마이저가 필요하다고 판단하면 CTE 결과를 Materialization(구체화)
- 즉, “한 번 계산된 결과를 여러 번 재사용함”
랭킹 API는 다음과 같은 패턴이 많다
WITH ranked AS (
SELECT id, balance,
DENSE_RANK() OVER (...) AS rank
FROM members
)
SELECT *
FROM ranked
WHERE id = ?
PostgreSQL은 ranked를 한 번 계산해 캐싱할 수 있지만, MySQL은 매번 다시 계산한다.
→ Window Function + CTE 조합에서 PostgreSQL이 월등한 이유
🧑💻 PostgreSQL 최적화 요소 4 — Window Function 전문 엔진
PostgreSQL은 Window Function을 엔진 차원에서 깊게 최적화한다.
- DENSE_RANK(), RANK()
- ROW_NUMBER()
- LAG(), LEAD()
- percentile_cont, moving average
- FRAME RANGE/ROWS 옵션
- PARTITION BY + ORDER BY 조합
이 조합들이 모두 높은 성능을 유지하며 작동한다.
MySQL은 지원은 하지만 최적화가 부족하다:
- Window + ORDER BY + LIMIT 조합에서 병목 발생
- PARTITION BY가 전체 스캔 기반으로 구현됨
- CTE와 결합 시 재계산 폭발
"MySQL도 Window Function을 지원하지만, PostgreSQL은 아예 Window Function을 핵심으로 두고 엔진이 설계되어 있다.”
이 철학적 구조 차이가 고급 SQL 중심의 프로젝트에서는 실질적인 성능 차이로 이어진다.
🔥 그래서 나는 이번 프로젝트에서 “정확히 이런 이점을 활용해 API를 만들었다"
이제 PostgreSQL의 이론적 강점만이 아니라, 내 프로젝트에서 실질적으로 PostgreSQL이 완전히 맞아떨어졌던 부분을 정리하면 다음과 같다.
잔액 기반 Top N 랭킹 API
SELECT
nickname,
balance,
DENSE_RANK() OVER (ORDER BY balance DESC, id ASC) AS rank
FROM members
ORDER BY balance DESC, id ASC
LIMIT :limit
여기서 PostgreSQL이 실제로 발휘한 장점:
- Window Function이 병렬 정렬과 결합되어 고속 실행
- incremental sort로 ORDER BY 비용 감소
- skip scan이 balance 정렬 인덱스를 효율적으로 탐색
- LIMIT 적용도 정렬 후 빠르게 슬라이스됨
- 랭킹 계산 자체는 이미 캐싱된 Window 결과 활용 가능
-> MySQL이었다면 LIMIT 최적화가 제대로 작동하지 않고 전체 Window를 매번 계산했을 가능성이 높음.
특정 회원 단일 랭킹 조회 API
SELECT nickname, balance, rank FROM (
SELECT
id,
nickname,
balance,
DENSE_RANK() OVER (ORDER BY balance DESC, id ASC) AS rank
FROM members
) ranked
WHERE ranked.id = :memberId
PostgreSQL에서는 다음 최적화가 작동한다:
- CTE/서브쿼리 materialization
- Window 결과를 한 번 계산 후 WHERE로 빠르게 검색
- parallel execution으로 전체 정렬 비용 감소
- skip scan으로 특정 id 검색 최적화
-> 데이터가 커질수록 PostgreSQL의 구조적 이점이 더 크게 나타난다.
🙌 추가될 복잡한 랭킹·통계 API를 PostgreSQL은 자연스럽게 확장할 수 있다
랭킹 API는 시작일 뿐이다. 이후 기능이 조금만 확장되면 다음과 같은 고급 기능이 필요해진다.
일/주/월 누적 랭킹, 최근 30일 승률 기반 랭킹, 아예 통계형 API (백분위수, 중앙값 등), 누적 보상 지표
이런 기능은 모두 PostgreSQL이 Window Function을 통해 기본 지원하는 영역이다. 즉, 코드 수정 없이 SQL만 확장하면 된다.
MySQL에서 동일 기능을 구현하려면?
- 서브쿼리 중첩
- 복잡한 조인
- 사용자 정의 변수
- 혹은 애플리케이션 레벨 계산
기능이 늘어날수록 MySQL은 점점 꼼수(?)가 필요하고, PostgreSQL은 순정 SQL 기능만으로 안정적으로 확장된다. (대부분 기능 하나로 끝나는 쿼리문은 없겠지만, 활용할 수 있지 않겠는가!)
마무리하며
솔직히 말하면 나는 그동안 MySQL만 써왔다. 이번 기회에 DB관련해서 새로운 시도를 해보고 싶었고, "코끼리" 로고에 끌렸던 것도 맞다..! 하지만 PostgreSQL을 직접 공부하고, 실제 기능에 녹여보면서 느낀 점은 단순 호기심 이상의 가치였다.
PostgreSQL은 단순히 색다른 DBMS가 아니라 고급 SQL 기반의 연산을 안정적으로 처리하도록 "설계"된 엔진이었다. Window Function, CTE, Parallel Execution, Incremental Sort 같은 기능들을 파고들수록 왜 많은 기업들이 분석형, 순위형, 그래프형 서비스에 PostgreSQL을 쓰는지 자연스럽게 이해가 됐다.
이번 프로젝트에서 랭킹 API를 구현하면서 대규모 트래픽을 직접 경험한 것은 아니지만, 내가 공부한 내용 기반으로 보면 PostgreSQL의 구조적 강점은 분명하다.
데이터가 폭발적으로 증가하고 순위,집계,정렬이 반복되는 서비스일수록 PostgreSQL의 진짜 힘이 발휘될 것이라는 확신이 생겼다.
아직은 작은 사이드 프로젝트이지만, 언젠가 더 많은 사용자가 몰리는 대용량 서비스를 운영하는 개발자가 되고 싶다.
그때 오늘의 선택(PostgreSQL)이 진짜 성능과 확장성에서 빛을 발하는 순간이 오길 바란다.
무엇보다도 새로운 기술을 도입하고 직접 구조를 분석해보는 과정 자체가 너무 재미있었다. 이번 경험은 단순히 “다른 DB를 써본 것”이 아니라 DBMS의 생태계와 더욱 더 친해지게 되는 경험이었다. 앞으로도 이런 선택들을 계속 이어가고 싶다.
'DB' 카테고리의 다른 글
| [Redis] 열받았던 탓에 도입해본 나의 첫 Redis (3) | 2025.11.16 |
|---|