✍️ 개발 기록

[👀 Owing] PostgreSQL 도입기 (feat. MySQL과의 차이)

ming412 2024. 11. 5. 17:53

이번 프로젝트를 진행하며 처음으로 PostgreSQL을 사용했습니다. 

 

PostgreSQL이 무엇이고 MySQL과 어떻게 다른지 공부한 내용과,

이번 프로젝트에서 왜 PostgreSQL을 선택하게 되었는지 기록해두려 합니다.

 

❤️‍🔥 PostgreSQL이란?

PostgreSQL은 1997년부터 사용된 오픈소스 ORDBMS(객체-관계형 데이터베이스 시스템)이다.

오라클 개발자들이 대거나와 PostgreSQL 개발에 합류하여 Oracle과 유사하다.

 

[객체-관계형 데이터베이스 시스템, Object-Relational Database Management System]
객체 지향 데이터베이스 모델을 가진 관계형 데이터베이스 관리 시스템이다. (데이터가 객체로 저장된 것처럼 작동한다.)
기본적으로는 관계형 데이터베이스(RDBMS)이지만 객체 데이터베이스와 연관되는 기능(ex. 테이블 상속 및 함수 오버로딩) 포함한다.

 

 

❤️‍🔥 PostgreSQL vs MySQL

1. PostgreSQL의 사용 추세는 꾸준히 상승세를 유지한다.

아래 그래프를 보면, PostgreSQL의 인기는 꾸준히 상승세를 유지하고 있음을 알 수 있다. MySQL은 여전히 상위권을 유지하며 많은 사용자를 확보하고 있지만, 최근 몇 년간 약간의 하락세를 보이고 있다. 

 

국내에서는 여전히 MySQL과 Oracle이 오픈 소스 관계형 데이터베이스의 대표 주자로 사용되고 있지만, 세계적으로는 PostgreSQL이 꽤 많이 치고올라오는 상태이다. 이에 따라, 데이터베이스에서 복자한 데이터 구조를 다루거나, 대규모 트랜잭션 관리가 필요한 경우 국내에서도 PostgreSQL을 도입하는 기업들이 늘어날 수 있을 것으로 예상한다.

 

 

데이터베이스 사용 추세

 

https://db-engines.com/en/ranking_trend/relational+dbms

 

historical trend of relational DBMS popularity

Ranking > Relational DBMS > Trend DB-Engines Ranking - Trend of Relational DBMS Popularity The DB-Engines Ranking ranks database management systems according to their popularity. This is a partial trend diagram of the complete ranking showing only relation

db-engines.com

 

2. PostgreSQL은 다양한 데이터 타입을 지원한다.

PostgreSQL은 `JSON`, `JSONB`, `ARRAY`, `HSTORE` 등 다양한 데이터 타입을 지원하여 비정형 데이터를 다루기 매우 유연하다. 프로젝트에 따라 복잡한 데이터 구조를 그대로 저장해야할 때, PostgreSQL은 데이터 저장 방식을 유연하게 선택할 수 있다는 장점이 있다. 

 

JSON과 JSONB 타입

- `JSON`: 원본 JSON 문자열을 그대로 저장하며, 구조나 속성 순서를 보존한다. 데이터를 불러올 때 입력된 텍스트를 파싱해야 하기 때문에 시간이 오래 걸린다.

- `JSONB`: JSON 데이터를 바이너리 형태로 저장한다. 데이터를 불러올 때 파싱이 필요 없기 때문에 시간이 적게 걸린다. 중복 데이터, 불필요한 공백 등을 제거하여 데이터 크기를 줄이고, JSON 구조의 키-값 쌍을 효과적으로 인덱싱하여 성능을 높인다.

>> SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
{"bar": "baz", "balance": 7.77, "active":false}
>> SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
{"bar": "baz", "active": false, "balance": 7.77}

 

일반적으로 대부분의 애플리케이션은 기존 객체 키에 대한 순서 보장과 같은 요구사항이 없는 한 JSON 데이터를 JSONB 타입으로 저장한다고 한다.

 

ARRAY 타입

단일 열에 여러 값을 저장할 수 있는 기능을 제공한다. 예를 들어, 한 사용자의 여러 연락처를 하나의 필드에 배열 형태로 저장할 수 있다.

이는 데이터를 개별적으로 분리하지 않고도 하나의 열에서 관리할 수 있게 해주므로, 관련된 데이터를 그룹화하는데 효과적이다.

create table member (
	name varchar(10),
	phone_number varchar(10)[]
);

insert into member (name, phone_number) 
values ('민수', array['010-1234-5678', '010-1111-2222']);
name|phone_number |
----|-------------|
민수 |{'010-1234-5678', '010-1111-2222'}|

 

HSTORE 타입

HSTORE는 키-값 쌍으로 구성된 데이터를 저장할 수 있는 데이터 타입이다.

# 작업 이전에 hstore extension을 활성화시켜야 한다.
create extension hstore;

create table books (
	id serial primary key,
    title varchar(255).
    attr hstore
);

insert into books (title, attr)
values ( 
	'The Great Gatsby',
    '"author" => "Sam",
    "year" -> "1925"'
);
id  |      title       |    attr     |
----|------------------|-------------|
1   | The Great Gatsby | "author" => "Sam", "year" => "1925"

 

3. PostgreSQL은 MVCC이고, MySQL은 아니다.

가장 큰 차이는 MVCC(다중 버전 동시성 제어)에 있다고 본다. PostgreSQL에서는 멀티버전에 대한 정보를 하나의 Page(Table)에서 관리하고 있다. 모든 테이블은 System Columns를 가지고 있고, 그중 mvcc를 구현하게 해주는 것이 `xmin`, `xmax` 컬럼이다.

- `xmin`: tuple을 insert 하거나 update 하는 시점의 Transaction ID를 갖는 메타데이터
- `xmax`: tuple을 delete 하거나 update 하는 시점의 Transaction ID를 갖는 메타데이터

 

신규 insert, update 시 xmin에 현재 Transaction ID를 넣고, xmax에는 null 값을 넣는다. delete, update 시 이전 tuple의 xmax에는 작업을 수행한 Transaction ID 값을 넣는다.

 

이를 통해 트랜잭션이 시작된 시점의 Transaction ID와 같거나 작은 Transaction ID를 가지는 데이터를 읽는다. (xmin과 xmax의 범위를 통해 해당 트랜잭션이 조회할 수 있는 데이터인지를 판단한다.)

 

xmin  | xmax  |  value
-------+-------+-----
  100 |  120 | A
  102 |  120 | B
  110 |  134 | C
  115 |    0 | D
  115 |  120 | E
[Transaction ID 별 조회 가능한 데이터]
Transaction 101에서는 A
Transaction 109에서는 A, B
Transaction 112에서는 A, B, C
Transaction 117에서는 A, B, C, D, E

 

하나의 Page(Table)에 이전 tuple들이 남겨지기 때문에 row가 삭제되어도 용량은 그대로 차지하는 경우가 있다. 그럼 실제 보여지는 row는 적어보이지만 

 

쿼리 성능 또한 지속적으로 떨어지게 된다. 따라서 PostgreSQL에서는 Vaccum 작업을 진행해줘야 한다.

Vaccum 작업은 개발자가 직접 진행할 수도 있지만(ex. `vaccume {table_name};`) 특정 조건이 맞으면 자동으로 진행된다. (AutoVaccume)

- Dead Tuple의 개수가 임계치에 도달했을 때
- Table이나 Tuple의 age가 임계치에 도달했을 때

 

age란 AutoVaccume의 조건 중 하나로, tuple이 처음 생길 때 1로 부여되고 그 후 Transaction이 발생할 때마다 1씩 증가하게 된다.

 

4. MySQL은 CRUD에서 성능 우위를 가지고, PostgreSQL는 무결성 보장에 강점을 가진다.

MySQL은 처음부터 웹 애플리케이션과 같은 고속 데이터 처리가 필요한 환경을 염두에 두고 설계되었다. 즉, 데이터 무결성보다는 성능에 초점을 맞추고 있다. MySQL은 빠른 읽기/쓰기 작업에 최적화된 구조로 설계되었으며, 기본 엔진인 `InnoDB` 외에도 고성능의 비ACID 엔진인 `MyISAM`을 지원한다. `MyISAM` 엔진은 트랜잭션을 지원하지 않기 때문에 성능에 집중하는 작업에서 속도 이점이 크다.

 

또, MySQL의 전체적인 구조는 PostgreSQL보다 가벼운 편이다. 이를 통해 메모리 사용량이 적고, CPU 리소스가 상대적으로 적게 소모되어 CRUD 작업에서 빠른 응답 속도를 보인다.

 

반면, PostgreSQL은 무결성 보장에 최적화된 데이터베이스이다. PostgreSQL은 ACID 특성을 완벽히 준수하며, 높은 수준의 데이터 일관성과 트랜잭션 관리 기능을 제공한다. 이를 통해 데이터의 무결성을 강력히 보장할 수 있어, 복잡한 트랜잭션이 필요한 환경에서 특히 강점을 발휘한다.

 

5. MySQL의 기본 격리 수준은 REPETABLE_READ 이고, PostgreSQL은 READ_COMMITED 이다.

MySQL과 PostgreSQL의 기본 격리 수준 선택은 두 DB의 설계 목표(철학)의 차이에서 비롯된다.

 

MySQL은 단순한 CRUD 작업에서의 성능 최적화를 중시하면서도, 기본적으로 애플리케이션 수준에서 높은 일관성을 보장하려는 설계 철학을 가지고 있다. 그래서 `REPEATABLE_READ`로 더 높은 격리 수준을 제공하면서도, 내부 최적화를 통해 동시성 성능을 유지하려 한다.

 

PostgreSQL은 기본 격리 수준으로 `READ_COMMITED`을 선택했는데, 이는 PostgreSQL의 MVCC 시스템이 데이터의 일관성을 충분히 유지할 수 있도록 보완해주기 때문에 가능한 선택이다. 즉, PostgreSQL은 성능을 최적화하면서도 실용적인 일관성을 제공하기 위해 `READ_COMMITED`를 기본으로 선택한 것이다.

 

  설명 문제점
READ UNCOMMITED 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있도록 함 더티 리드 발생 가능
READ COMMITED 커밋된 데이터만 다른 트랜잭션이 읽을 수 있도록 함 다른 트랜잭션의 커밋 여부에 따라 조회 결과가 달라질 수 있다. (= Non-Repeatable Read, 반복 읽기 불가능)
REPEATABLE READ 트랜잭션이 처음 데이터를 읽은 이후, 해당 데이터를 다른 트랜잭션이 수정하더라도 (처음 읽은) 동일한 결과(=수정 전 결과)를 반환할 것을 보장 팬텀 리드 발생 가능
SERIALIZABLE 트랜잭션을 순차적으로 진행시켜 완벽한 일관성 보장. 트랜잭션 간의 읽기 및 쓰기가 모두 차단 동시성이 크게 제한되어 성능 저하가 발생할 수 있음

 

❤️‍🔥 PostgreSQL 선택 이유

프로젝트에서 PostgreSQL을 선택하게 된 가장 큰 이유는, JSON 데이터를 저장하고 관리할 필요가 있었기 때문이다.

물론 MySQL에도 JSON 지원 기능이 있긴 하지만 아래와 같은 한계가 있다.

1. JSON 데이터에 대해 기본적인 조회와 일부 함수만 제공하므로 복잡한 JSON 구조에 대한 검색과 조작 기능이 제한적이다.
2. JSON 데이터에 인덱스를 직접적으로 적용하기 어렵기 때문에 JSON 데이터를 효율적으로 다루는 데 한계가 있다.

 

❤️‍🔥 사용 소감

1. MySQL만 사용하다 PostgresSQL을 처음 다루게 되어 schema의 개념이 헷갈렸다.

MySQL은 논리DB를 schema라고 하는 반면, PostgreSQL에서는 database, schema, user의 개념이 모두 있기 때문이다.

이 둘의 차이는 따로 정리할 예정이다.

 

2. 다양한 데이터 타입 지원이 유용했다.

JSON, 배열, UUID 같은 다양한 데이터 타입을 기본으로 지원한다는 점에서 확장성 높은 데이터 관리가 필요한 경우 유리하다고 생각했다.