데이터베이스 기초 (9) - MySQL vs PostgreSQL and Next Steps
PostgreSQL과 MySQL의 공통점과 차이를 기본 isolation level, 아키텍처, PRIMARY KEY와 데이터 배치, upsert 문법, replication 기준으로 표로 정리하고 선택 기준을 다룹니다. RDB 밖의 캐시, document DB, warehouse의 위치를 정리하고 시리즈 자가 테스트 체크리스트로 마무리합니다.
데이터베이스 기초 시리즈의 9편입니다. 전체 목차는 0편에 있습니다.
이 시리즈는 PostgreSQL 18 Docker container를 기준으로 진행했고, 1편부터 8편까지 매 편 끝에 MySQL 8.4와 다른 점을 적었습니다. 마지막 편에서는 그 차이들을 한 자리에 모아 정리하고, 두 DB 중 무엇을 고를지, RDB 밖의 도구들은 언제 고려할지, 그리고 시리즈를 마친 시점에 무엇을 할 수 있어야 하는지를 다룹니다.
같은 것
이 시리즈에서 다룬 내용의 대부분은 두 DB에서 거의 그대로 통합니다. SELECT와 JOIN, GROUP BY 같은 SQL 문법(1편부터 4편)과 PRIMARY KEY, FOREIGN KEY 같은 제약(2편)은 표준 SQL의 영역이라 문장 단위로 호환됩니다.
B-tree index와 실행 계획을 읽는 방법(5편), transaction과 lock(6편), parameterized query와 connection pool과 migration(7편), 백업과 권한 관리(8편)도 명령어와 도구 이름만 다를 뿐 개념 수준에서는 차이가 없습니다.
각 편 끝의 MySQL 차이 섹션이 그 증거입니다. 여덟 편 모두 차이를 몇 문장으로 정리할 수 있었고, 문법과 기본값 몇 개를 바꾸면 같은 실습을 MySQL에서도 반복할 수 있었습니다. 한쪽 DB로 기본기를 익혀 두면 다른 쪽으로 옮기는 비용은 크지 않습니다.
다른 것
그래도 실무에서 마주치는 차이는 있습니다. 시리즈에서 나온 차이에 이 편에서 처음 정리하는 upsert 문법과 타입 차이를 더해 표로 모으면 다음과 같습니다.
| 항목 | PostgreSQL | MySQL 8.4 (InnoDB) |
|---|---|---|
| 기본 isolation level | READ COMMITTED | REPEATABLE READ |
| 접속 처리 | 접속마다 process 생성 | 접속마다 thread 생성 |
| PRIMARY KEY와 데이터 배치 | heap 저장, PK는 별도 index | PK 순서의 clustered index |
| upsert 문법 | INSERT … ON CONFLICT | INSERT … ON DUPLICATE KEY UPDATE |
| 타입 | TIMESTAMPTZ, JSONB 있음 | 둘 다 없음, JSON 타입은 별도 |
| replication | WAL 스트리밍 | binlog 전달과 적용 |
기본 isolation level
6편에서 본 대로 PostgreSQL의 기본값은 READ COMMITTED이고 MySQL InnoDB의 기본값은 REPEATABLE READ입니다. 같은 동시 실행 시나리오라도 기본 설정에서 보이는 현상이 달라질 수 있으므로, DB를 옮길 때 가장 먼저 확인할 항목입니다.
접속 처리
PostgreSQL은 client가 접속할 때마다 server process를 하나 만들고, MySQL은 thread를 하나 만듭니다. process 생성이 thread 생성보다 비용이 커서 PostgreSQL에서는 7편에서 다룬 connection pool의 효과가 특히 큽니다. MySQL에서도 pool은 표준 구성입니다.
PRIMARY KEY와 데이터 배치
PostgreSQL은 row를 heap에 쌓아 두고 PRIMARY KEY도 다른 index와 같은 별도 B-tree로 유지합니다. MySQL InnoDB는 table 자체를 PK 순서로 정렬한 clustered index로 저장하고, secondary index는 row 위치 대신 PK 값을 담습니다. 그래서 MySQL에서는 PK 선택이 저장 구조와 secondary index 크기에 직접 영향을 줍니다. 5편에서 다룬 내용입니다.
upsert 문법
있으면 갱신하고 없으면 넣는 upsert는 두 DB의 문법이 다릅니다. PostgreSQL은 ON CONFLICT를 씁니다.
1
2
INSERT INTO tags (name) VALUES ('공지')
ON CONFLICT (name) DO NOTHING;
tags에 ‘공지’가 이미 있으므로 이 문장은 아무것도 넣지 않고 정상 종료합니다. DO NOTHING 자리에 DO UPDATE SET name = EXCLUDED.name처럼 넣으려던 값(EXCLUDED)을 참조하는 갱신을 쓰면 기존 row를 갱신합니다. MySQL은 ON DUPLICATE KEY UPDATE를 씁니다. 아래는 MySQL 8.4 문법이므로 psql에서는 실행되지 않습니다.
1
2
INSERT INTO tags (name) VALUES ('공지') AS new
ON DUPLICATE KEY UPDATE name = new.name;
AS new는 넣으려던 row에 붙인 별칭이고, new.name으로 그 값을 참조합니다.
타입
MySQL에는 TIMESTAMPTZ가 없습니다. TIMESTAMP 타입이 값을 UTC로 저장했다가 세션 time zone으로 변환해 주지만 동작 방식이 다릅니다. PostgreSQL에는 JSON 문서를 이진 형태로 저장하고 index를 걸 수 있는 JSONB 타입이 있습니다. MySQL에는 JSONB가 없고 JSON 타입이 따로 있으며, JSON 값에 index를 걸려면 JSON에서 뽑은 값을 저장하는 별도 column(generated column)을 만들어 거기에 index를 겁니다.
replication
8편에서 본 대로 PostgreSQL은 WAL을 replica에 스트리밍하는 방식이 기본이고, MySQL은 변경 내역을 binlog에 기록하고 replica가 이를 받아 적용하는 방식이 기본입니다. replication 지연을 감안하는 원칙은 같지만 구성 방법과 모니터링 대상이 다릅니다.
무엇을 고를 것인가
선택 기준의 첫 번째는 기능 비교가 아니라 환경입니다. 팀이 이미 한쪽을 운영하고 있다면 그쪽을 씁니다. 축적된 운영 경험, 모니터링 구성, 장애 대응 절차가 기술적 차이보다 큰 자산입니다. 회사 인프라나 사용하는 클라우드의 managed service가 한쪽만 지원한다면 같은 이유로 그쪽이 우선입니다.
새로 시작하는 프로젝트라면 어느 쪽을 골라도 이 시리즈에서 다룬 내용만으로 충분히 시작할 수 있습니다. 선택이 갈리는 것은 기능 요구가 명확할 때입니다. JSONB로 구조가 유동적인 데이터를 다뤄야 하거나 PostGIS 같은 extension으로 기능을 더해야 하면 PostgreSQL을 고르고, 기존 MySQL 기반 도구와 운영 경험을 활용해야 한다면 MySQL을 고릅니다. 두 DB의 설치와 구조는 PostgreSQL 편과 MySQL 편에 정리되어 있습니다.
RDB 밖의 도구
기본값은 RDB입니다. 다른 계열의 저장소는 RDB로 서비스를 운영하다가 병목이 실측될 때 더하는 것이지, 처음부터 골라 두는 것이 아닙니다. 어떤 상황에서 무엇을 고려하는지만 정리합니다.
Redis는 자주 읽히는 값을 메모리에 두는 캐시입니다. 같은 조회가 매우 잦아서 DB 부하의 대부분이 반복 읽기일 때, 조회 결과를 Redis에 두고 만료 시간을 걸어 DB로 가는 요청을 줄입니다.
document DB는 row 대신 JSON 형태의 문서를 저장합니다. 데이터 구조가 자주 바뀌어 migration으로 따라가기 어려운 영역에서 고려하지만, 그전에 PostgreSQL의 JSONB로 해결되는지 먼저 확인할 가치가 있습니다.
row 단위가 아니라 column 단위로 저장해서 일부 column만 읽는 대량 집계가 빠른 column 지향 저장(data warehouse, parquet)은 수천만 row 이상을 집계하는 분석용입니다. 운영 DB에 무거운 분석 쿼리를 직접 실행하면 서비스 응답이 느려지므로, 데이터를 주기적으로 복사해 분석 전용 저장소에서 처리합니다.
어느 경우든 도입의 근거는 도구 이름이 아니라 실측된 병목입니다. 5편의 EXPLAIN ANALYZE와 8편의 모니터링으로 병목을 확인한 뒤에 판단하면 됩니다.
자가 테스트
0편에서 제시한 도달점 세 가지에 시리즈 중반의 항목을 더해 체크리스트로 정리합니다. 각 항목에 예라고 답할 수 있는지 확인합니다.
- 게시판에서 글마다 댓글 수를 붙인 목록을 쿼리 한 번으로 만들 수 있습니다. (3편)
- 느린 쿼리를 EXPLAIN ANALYZE로 진단하고 index로 개선할 수 있습니다. (5편)
- deadlock이 왜 생기는지 설명하고 예방 방법을 말할 수 있습니다. (6편)
- 반복되는 데이터를 보고 어떤 값을 별도 table로 분리할지 판단할 수 있습니다. (2편)
- GROUP BY 쿼리에서 SELECT에 쓸 수 있는 column의 규칙을 설명할 수 있습니다. (4편)
- ORM 코드에서 N+1 문제를 찾아내고 고칠 수 있습니다. (7편)
- 백업 파일에서 새 database로 복원하는 리허설을 해 봤습니다. (8편)
전부 예라고 답할 수 있으면 이 시리즈가 목표로 한 기본기를 갖춘 것입니다. 막히는 항목이 있으면 해당 편으로 돌아가 게시판 예제로 실습을 반복하면 됩니다.