본문 바로가기
개발/서버

왜 PostgreSQL을 선택했는가: 다국어 서비스를 위한 JSONB 활용

by agong이 2025. 10. 16.

StayOne Korea 프로젝트에서는 한국어, 중국어, 프랑스어, 영어 총 4개 국어를 지원하며, 숙소 정보를 여러 언어로 저장해야 합니다. 초기에는 언어별로 컬럼을 만들고 name_ko, name_en, name_zh, name_fr처럼 저장하는 방법을 생각했습니다. 그러나 언어가 늘어날 때마다 스키마를 수정해야 하고 관리 비용도 커집니다. 이를 해결하기 위해 단일 컬럼에 JSON 형태로 여러 언어의 이름을 저장하는 구조를 고려하게 되었습니다.

 
{ 	
	"ko": "해운대 고시원",
	"en": "Haeundae Gosiwon",
    	"zh": "海云台高级考试院",
    	"fr": "Gosiwon Haeundae"
}
 
일반적인 JSON 타입은 문자열로 저장되기 때문에 데이터 검증이나 검색·필터링이 어렵습니다. 애플리케이션에서 JSON을 파싱해 비교하는 로직을 작성해야 했고, 성능 문제도 우려되었습니다. 반면 PostgreSQL의 jsonb 타입은 이진 형태로 저장되며 인덱싱과 검색을 지원합니다. 

JSONB를 활용한 중복 체크 예시

서비스에서는 고시원 이름의 중복을 방지하기 위해 name_i18n 컬럼을 jsonb 타입으로 저장하고, 특정 언어 키로 직접 검색합니다. 예를 들어 한국어 이름이 같은 고시원이 존재하는지 확인하는 쿼리는 다음과 같습니다.

 
/** * nameI18n의 한국어 이름으로 중복 체크 * JSONB 필드 검색 */ 
@Query(
    value = "SELECT CASE WHEN COUNT(*) > 0 THEN true ELSE false END " + 
    "FROM residences " + 
    "WHERE name_i18n->>'ko' = :koreanName", // jsonb에서 key로 특정 언어 이름 조회 
    nativeQuery = true
) 
boolean existsByKoreanName(@Param("koreanName") String koreanName);

name_i18n->>'ko'는 JSON 객체의 ko 키 값을 텍스트로 추출하는데, 이는 PostgreSQL jsonb 타입이 제공하는 연산자 덕분이다. 일반 json 타입이나 MySQL에서는 이런 연산자가 없어서 쿼리 안에서 JSON을 비교하기 어렵습니다.

 

PostgreSQL JSONB vs. MySQL JSON

 

PostgreSQL JSONB

PostgreSQL과 MySQL 모두 JSON 타입을 제공하지만 세부 구현과 기능에서 차이가 있습니다. PostgreSQL jsonb는 여러 인덱스 방식을 지원해 키와 값에 대한 검색 및 필터링이 가능합니다. 예를 들어 data_column->>'name'과 같은 특정 경로에 대해 인덱스를 만들 수 있습니다. 반면 MySQL은 JSON 컬럼에 직접 인덱스를 생성할 수 없습니다.

 

또한 PostgreSQL은 JSONB에서 ->, ->>, @> 등 다양한 연산자를 제공해 JSON 내부 키의 존재 여부나 포함 관계를 쉽게 검사할 수 있습니다. 즉, PostgreSQL이 JSON 처리에서 더 많은 연산자와 인덱싱 옵션을 제공합니다. 

MySQL JSON

MySQL 역시 JSON 타입을 지원하지만, 내부적으로는 여전히 텍스트 기반에 가깝습니다. JSON 구조를 인식하긴 하지만,

JSON 컬럼 자체에 직접 인덱스를 걸 수는 없습니다. 대신, 특정 JSON 키를 검색하려면 JSON_EXTRACT()나 JSON_UNQUOTE() 같은 함수를 사용해야 하고, 이를 인덱싱하려면 가상 컬럼을 별도로 정의해야 합니다.

예를 들어 다음과 같이 해야만 인덱스를 활용할 수 있습니다 

ALTER TABLE residences
ADD COLUMN name_ko VARCHAR(255)
    GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(name_i18n, '$.ko')))
    STORED;

CREATE INDEX idx_residence_name_ko ON residences (name_ko);
 
이렇게 하지 않으면 MySQL은 JSON 내부 값을 검색할 때 전체 테이블을 스캔하기 때문에 데이터가 많아질 수록 느려질 것입니다.

또한 MySQL의 JSON은 중복 키를 허용합니다. 예를 들어 {"ko": "해운대", "ko": "강남"}처럼 같은 키가 여러 번 등장해도 저장이 되며, 조회 시에는 첫 번째 값만 반환됩니다. 이는 데이터 일관성 측면에서 위험 요소가 될 수 있습니다.

⚖️ 결론

정리하자면,

  • PostgreSQL jsonb는 정규화된 구조, 풍부한 연산자, 인덱스 지원을 모두 제공하며,
    JSON 데이터를 안정적이고 빠르게 다룰 수 있습니다.
  • 반면 MySQL의 JSON은 문자열 중심의 단순 구조로,
    인덱스 활용과 데이터 일관성 면에서 제약이 있습니다.

따라서 JSON을 핵심 데이터 모델로 활용해야 하는 서비스라면,
PostgreSQL의 jsonb가 더 유연하고 강력한 선택이 됩니다.

 

더보기

SQLite의 JSON 저장 방식

SQLite도 고려해보기위해 추가적으로 찾아보니 SQLite는 JSON 타입이 따로 없습니다.
즉, JSON 데이터를 그냥 TEXT로 저장합니다.

 이를 위해 SQLite는 json1이라는 내장 확장을 제공합니다. 이를 통해 JSON 데이터에서 특정 값을 추출하거나 수정할 수 있습니다. 하지만 이 또한 인덱스가 작동되지는 않습니다.

//TEXT로 저장
CREATE TABLE rooms (
  id INTEGER PRIMARY KEY,
  name_i18n TEXT
);

INSERT INTO rooms (name_i18n)
VALUES ('{"ko": "해운대 프리미엄 고시원", "en": "Haeundae Premium Gosiwon"}');
------
//JSQON1 확장 기능 사용
SELECT json_extract(name_i18n, '$.ko')
FROM rooms
WHERE json_extract(name_i18n, '$.en') = 'Haeundae Premium Gosiwon';

장점 정리

  1. 스키마 확장성 – 여러 언어 정보를 하나의 컬럼(jsonb)에 저장하여 언어가 늘어나더라도 컬럼 수를 늘리지 않아도 된다.
  2. 검색 및 필터링 성능  – 파싱된 이진 형태로 저장되어 검색 속도가 MySQL보다 월등히 빠름. 특히 대규모 데이터셋에서도 인덱스 기반 탐색이 가능해 효율적
  3. 유효성 검증 및 정규화  – jsonb는 저장 시 JSON 문법을 확인하므로 잘못된 JSON이 저장되지 않는다. 중복 키를 제거하며 key 정렬까지 수행한다.
  4. 서비스 로직 단순화 – 애플리케이션에서 JSON을 파싱하지 않고도 DB 쿼리만으로 중복 여부나 필터링을 수행할 수 있다.

결론

PostgreSQL의 jsonb는 단순히 JSON을 "저장"하는 수준이 아니라, 검색·인덱싱·검증·성능 최적화까지 가능한 완전한 JSON 데이터 엔진입니다. 무엇보다 json타입에 대한 유효성 검증을 해주고, 인덱싱을 활용할 수 있기 때문에 선택하였습니다.

댓글