기술 인터뷰를 참여하면 가끔 듣는 질문이 있다.
왜 그 프로젝트에서 PostgreSQL을 사용하셨나요?
처음에는 전혀 대답하지 못했다. 왜냐하면 입사했을 때부터 회사의 모든 프로젝트에서 PostgreSQL을 사용하고 있었고, 별다른 고민이나 의문을 갖지 않고 그대로 사용했기 때문이다.
그리고 몇 년뒤, 두번째로 질문을 받았을 때는 어설픈 지식으로 오답을 말했다.
JSON 데이터타입을 쓰려구요.
면접관: MySQL도 JSON 타입 지원하는데요?
반성했다. 이전에 똑같이 받았던 질문을 왜 복기하지 않았을까? 아니 그 이전에 왜 아무런 기술적 고민 없이 DB를 사용하고 있었을까? 이 문제에 대해 좀 더 깊이있게 고민해봐야겠다 싶었다.
하지만 이 질문은 오랫동안 ToDo리스트에서 자고있었다.
첫째로 중간중간에 짤막하게 구글링을 해봤지만 명쾌한 답을 얻지 못했기 때문이며,
둘째로 이 고민이 필요한 상황을 실무에서 참여해보지 못했기 때문이다.
바쁘다는 핑계로 미루다 제법 시간이 지나 이제서야 이 문제를 꺼내볼 용기가 생겼다.
왜 MySQL vs PostgreSQL인가?
일단 둘은 대표적인 오픈소스 DB이다. 처음부터 엔터프라이즈급의 서비스를 설계하는 게 아니라면, 처음에는 무료 솔루션을 고민하게 될 것이다. 오라클 등의 요금은 결코 만만하지 않다.
그리고 엔터프라이즈급에서도 MySQL이나 PostgreSQL도 충분히 선택지가 될 수 있다. 실제로 많은 기업(Facebook, Netflix, Google 등)에서도 사용 중인 것으로 알려졌다. 그만큼 오랜 기간동안 많은 기업에서 사용되어 검증 받았고, 성숙한 커뮤니티가 형성되어 있다. 특별한 경우가 아니라면 아마 대부분의 서비스에서 이 두 DB 중 하나를 고른다면 오답은 아닐 것이다.
좋다. 그럼 MySQL vs PostgreSQL 뭐가 좋아?
모두가 예상하듯 서비스 환경에 따라 다르다는 답변을 할 수 밖에 없는 것 같다.
그렇다면 이제 할 일은 선택에 있어 어떤걸 고려해야 하는가? 라는 점일 것이다. 이제부터 이 부분에 집중해서 고민을 해볼 예정이다.
고려해볼만한 포인트는 뭐가 있을까?
- 커뮤니티와 성숙도
- 라이센싱
- 기술적 차이
1, 2번은 현재 글에서 다룰 범위가 아니기에 여기서는 3번 기술적 차이만 집중할 것이다. 또 다양한 차이가 있지만, 개인적으로 좀 와닿을 만한 부분만 추려보았다. 자세한 차이는 아래를 참고해보면 좋다.
- https://www.ibm.com/blog/postgresql-vs-mysql-whats-the-difference/
- https://www.javatpoint.com/postgresql-vs-mysql
- https://www.digitalocean.com/community/tutorials/sqlite-vs-mysql-vs-postgresql-a-comparison-of-relational-database-management-systems
💡 MySQL은 다양한 엔진을 지원한다. 여기서는 가장 대중적인 InnoDB를 기준으로 설명한다.
기술적 차이
일반적으로 PostgreSQL은 ANSI SQL 표준을 완전히 지원한다. 성능을 약간 희생하고 안정성과 다양한 기능을 지원하는 특징이 있으며, MySQL은 ANSI SQL 표준을 완전히 지원하지는 않지만 다양한 최적화를 통해 성능을 개선하고 있다는 특징이 있다.
1. 데이터베이스 타입
MySQL과는 다르게 PostgreSQL은 ORDBMS(Object-Relational Database Management System) 타입이다.
그렇다면 실질적으로 어떤게 다를까? 한다면 PostgreSQL은 테이블 상속과 메서드 오버로딩 그리고 커스텀 데이터 타입 기능을 지원한다.
객체 개념을 갖기 때문에 객체지향의 특징인 상속이나 오버로딩을 지원하는 것이다. 이러한 특성을 통해 PostGIS라는 훌륭한 플러그인도 개발되었다고 알고있다.
하지만 백엔드 개발자로서 적극적으로 사용할 만한 기능인가? 라고 보면 그렇지는 않은 것 같다.
최근 추세는 RDBMS에서의 프로시저 등을 활용하기 보다는 앱레벨에서 다양한 처리를 많이 하는 것 같다. DB의 함수 호출 등의 개발은 유연하지 못하고 테스트, 디버깅이나 협업도 쉽지 않기 때문이다. 따라서 이 부분은 이 정도로 넘어간다.
2. DB가 지원하는 데이터타입의 차이
MySQL은 자주 사용되는 데이터타입만을 지원하여 복잡도를 낮추고 성능을 개선하고 있다. 그 때문인지 PostgreSQL이 비교적 더 다양한 데이터타입을 지원한다.
대표적으로 XML, Geometric Types, N차원배열 등이 PostgreSQL이 지원하는 데이터타입이다. 특히 Geometric과 관련된 서비스라면 PostgreSQL 선택을 하는 데 있어 제법 납득할만한 포인트가 될 수 있을 것 같다.
다양한 Geometric 연산과 함수를 지원하는데, 좌표간의 위치 비교나 거리, 센터 좌표 계산 등을 DB레벨에서 지원하기 때문에, 앱의 복잡도를 줄이고, 검증된 Geographic 연산을 사용하여 안정성과 성능 개선을 노려볼 수 있을 것이다.
3. 성능
MySQL
기본적으로 MySQL은 읽기 성능에 최적화되어 있다고 알려져있다. 그렇다면 MySQL은 어떤 방식으로 읽기작업을 최적화했을까? 이에 대해 알아보자.
- 클러스터링 인덱스
MySQL은 인덱스 키의 순서에 따라 레코드를 물리적으로 저장하는 방식이다. 이런 방식 때문에 특히 키값을 기준으로 한 범위 탐색이 굉장히 빠르다. 단점은 이런 물리적 저장방식으로 인해 새로운 레코드의 삽입이나 수정이 느려질 수 밖에 없다.
PostgreSQL은 기본적으로 인덱스 클러스터링 방식을 사용하지는 않지만 `Cluster` 라는 명령어로 레코드를 재정렬할 수 있다. 그러나 매번 수동으로 실행해야하므로 새롭게 삽입되는 레코드는 자동으로 정렬되지 않는다.
- 버퍼 풀
느린 디스크 I/O를 최소화하기 위해서 필요한 데이터와 인덱스 페이지를 메모리에 캐시하는 방법이다.
💡여기서 페이지란 데이터와 인덱스 정보를 저장하고 관리하기 위한 기본 단위를 의미한다.
- 데이터 페이지: 실제 테이블의 레코드들을 저장하는 페이지.
- 인덱스 페이지: B-tree 인덱스 구조를 구성하는 데 사용되는 페이지.
이때 버퍼풀의 캐시는 LRU-Cache 전략으로 관리되며, 일반적인 LRU 캐시와는 다른 라이프사이클 방식이 사용되는데, 자세한 내용은 아래 블로그를 참조하면 좋을 것 같다.
https://cl8d.tistory.com/m/102
- 어댑티브 해시 인덱싱
자주 접근되는 인덱스 페이지의 검색 성능을 개선하기 위한 방법이다.
조회 쿼리가 요청될 경우 InnoDB는 디스크 혹은 버퍼풀에서 인덱싱페이지를 가져오게 된다. 이 페이지를 조회할 때 B-Tree방식으로 조회하게 되는데 O(logn)의 시간복잡도를 갖게 된다. 충분히 효율적인 알고리즘이지만 어댑티브 해시 인덱싱을 통해 더 개선할 수 있다.
내부적으로 여러 조건을 통해 자주 조회된다고 판단되는 데이터의 해시값을 생성한다. 해시값을 통해 데이터에 직접 접근하기 때문에 O(1)의 상수 시간복잡도를 갖게 되어, 매우 효율적으로 데이터를 접근할 수 있다.
그러나 내부적인 옵티마이저에 의해 해시키가 생성되기 때문에 제어가 어렵고, 오래 사용되지 않은 테이블에도 해시키가 살아있기 때문에 메모리 낭비가 발생할 수 있다.
PostgreSQL
일반적으로 PostgreSQL은 쓰기성능이 뛰어나다고 알려져있다. 어떻게 좋은 쓰기 성능을 확보할 수 있었는지, 그 특징에 대해 알아보자.
- MVCC 방식.
MVCC 방식은 MGA(Multi-Generation Architecture) 를 채탹했다. 이 방식은 레코드를 수정해야 하는 경우, 실제로는 내부적으로 기존 레코드를 수정하는게 아니라 새로운 버전의 레코드를 삽입하게 된다. 따라서 동시 읽기나 쓰기 등의 트랜잭션이 경합하는 상황을 최소화할 수 있다. 동시성을 제어하여 데이터 일관성을 지키면서 쓰기 성능을 개선할 수 있는 방법으로 MySQL의 Undo Segment방식과는 다른 방식이다. 문제는 여러 버전이 삽입되는 문제로 오래된 데이터가 남게 되어 디스크 용량을 낭비하게 되는 문제가 발생하는데 일정주기로 Vacuum으로 저장영역을 회수한다..
- WAL(Write-Ahead Logging)
WAL의 기본 아이디어는 변경되는 데이터를 디스크에 직접 쓰기 전에 반드시 로그를 쓰는 것이다. 이렇게 하면 시스템 장애가 발생하여 실제 레코드 쓰기가 실패한 경우에도 로그를 기반으로 마지막 상태의 데이터로 복구하는 것이 가능하다. WAL은 데이터 무결성 보장을 위한 방법이지만 쓰기 성능을 개선하는 데에도 효과가 있다.
어? 로그를 쓰는데 왜 쓰기가 빨라?
트랜잭션에 의해 변경된 모든 데이터 파일이 아니라 로그만 작성하기 때문에 디스크 쓰기가 현저하게 줄어든다. 또 시퀀셜하게 저장된 로그를 데이터와 Sync하는 것이 모든 관련 데이터페이지를 Flush하는 것에 비해 비용이 훨씬 적다.
💡MySQL에도 동일한 개념이 있다. 이를 Redo Log라 한다.
추가로 보면 메모리 효율성이 있다. PostgreSQL은 커넥션마다 프로세스를 Fork한다. 각 프로세스는 약 10MB의 메모리를 할당받게 되는데, 상황에 따라 빠르게 할당메모리가 증가할 수 있는 문제가 있다. MySQL은 하나의 프로세스 안에서 커넥션마다 스레드를 생성한다.
DB 서버의 스펙이 크지 않고, 각 커넥션의 요청이 복잡하지 않은 경우 MySQL이 유리할 수 있다.
4. 동시성
Isolation Level
- 기본적으로 두 DB모두 표준 SQL Isolation Level을 지원한다.
- 두 DB 모두 MVCC를 활용해서 버전을 관리한다.
- 기본값은 다르게 설정되어 있다.
MySQL | PostgreSQL |
Repeatable Read | Read Committed |
MVCC
MVCC는 많은 동시 트랜잭션이 발생하는 상황에서 Lock을 사용하지 않고 데이터의 일관성을 보장하기 위한 방법이다.
MySQL(InnoDB) | PostgreSQL |
Undo Segement | MGA(Multi-Generation Architecture) |
두 MVCC 방식의 차이점을 간단하게 알아보자.
Undo Segment는 데이터의 이전버전을 저장하는 방식이다.
데이터를 수정 쿼리가 발생하면 적용전에 기존 데이터의 사본을 Undo 세그먼트라는 임시 영역에 저장하게 된다.
트랜잭션이 종료될 때까지의 라이프사이클만 가지며 새로운 트랜잭션이 발성하게 되면 덮어쓰여지게 된다.
MGA는 위에 설명한 바와 같이 Undo와는 다르게 여러 버전을 저장하는 방식이며 마찬가지로 트랜잭션이 종료될 때까지의 라이프사이클을 가진다.
대부분의 경우는 둘 다 충분히 유효한 방식이지만, 대규모 동시 읽기, 쓰기가 발생하는 경우에는 Undo Segment 방식은 Race Condition이 발생할 수 있기 때문에 MVA 방식이 더 효과적인데 이런 경우에는 PostgreSQL이 더 적합한 선택일 수 있다.
5. 레플리케이션
데이터베이스의 마스터-슬레이브 구조를 구축을 위한 기능을 지원하는 것은 상당히 중요한 일이다.
💡 마스터에 장애가 발생할 경우 슬레이브를 마스터로 승격하여 SPOF를 제거하고 가용성을 개선할 수 있다. 혹은 읽기전용 슬레이브를 사용하여 수평확장이 가능하여 읽기 성능을 크게 향상시킬수도 있다. 이러한 장점으로 많은 서비스가 프로덕션 환경에서 마스터-슬레이브 구조를 많이 채용하고 있으며 구축과 설정이 비교적 쉽고 간단한 장점을 지닌다.
MySQL은 데이터 복제가 비동기 방식으로 동작하기 때문에 성능면에서는 이점이 있다. 슬레이브는 마스터의 쿼리로그를 복제하여 이를 수행하고 수행 로그를 작성하는 데이터 흐름을 갖게 된다.
PostgreSQL은 상기했던 WAL을 활용하여 레플리케이션을 수행한다. 일반적으로 스트리밍을 통해 새로운 WAL로그를 수신하여 수행하게 되는데, 모든 마스터와 슬레이브가 동일한 로그를 갖게 되는 특징이 있다. 모든 DB가 동일한 로그를 갖으며 동기적으로 수행되기 때문에 데이터 동기화가 엄격해야하는 경우 이점이 있다.
💡 MySQL은 데이터 무결성을 위한 반동기 복제방식을 지원한다. 마스터에 트랜잭션이 커밋되기 전에 슬레이브 중 하나라도 로그를 수신해야 하는 방식이다.
6. 결론
- 동시 쓰기 트랜잭션에 대한 염려가 비교적 적고 읽기쿼리가 자주 발생한다면? MySQL
- 쓰기 쿼리가 자주 발생하고 복잡한 쿼리를 자주 수행해야 하는 서비스라면? PostgreSQL
- 동시성과 데이터 무결성가 중요하다면? PostgreSQL
- XML, Geographic, N차원 배열과 같은 데이터타입 지원이 필요하다면? PostgreSQL
이 글의 오류나 개선사항이 있다면 언제든 편하게 댓글을 달아주시면 감사하겠다. 🙏
참고
https://www.guru99.com/postgresql-vs-mysql-difference.html
https://kinsta.com/blog/postgresql-vs-mysql/
https://cl8d.tistory.com/m/102
https://tech.kakao.com/2016/04/07/innodb-adaptive-hash-index/