Fall in IT.

DB에 JSON 데이터를 저장하는 두 가지 방법: JSON 타입 vs TEXT 타입 본문

데이터베이스

DB에 JSON 데이터를 저장하는 두 가지 방법: JSON 타입 vs TEXT 타입

D.Y 2026. 3. 26. 13:31
반응형

문제의 출발점

애플리케이션에서 구조화된 데이터를 하나의 컬럼에 통째로 저장해야 하는 경우가 있다. 설정값, 메타데이터, 가변적인 속성 목록 등이 대표적이다. 이때 선택지는 크게 두 가지로 나뉜다.

  • 데이터베이스가 제공하는 JSON/JSONB 타입을 사용하는 방법
  • 일반 TEXT 타입에 JSON 문자열을 그대로 저장하는 방법

두 방식 모두 현업에서 널리 쓰이며, 어느 쪽이 절대적으로 우월하다고 말하기는 어렵다. 상황에 따라 적합한 선택이 달라지기 때문이다.


JSON/JSONB 타입으로 저장하는 경우

개요

PostgreSQL의 jsonb, MySQL 8.0+의 JSON 등 주요 RDBMS는 JSON 전용 컬럼 타입을 지원한다. 단순히 문자열로 저장하는 것이 아니라, DB 엔진이 JSON 구조를 인식하고 파싱한 상태로 보관한다.

장점

1. 데이터 무결성 보장

INSERT/UPDATE 시점에 DB가 JSON 유효성을 자동으로 검증한다. 잘못된 형식의 데이터가 저장되는 것 자체를 원천 차단할 수 있다. TEXT 타입에서는 애플리케이션 레이어에서만 이를 검증할 수 있으므로, 버그나 직접 SQL 실행 등으로 잘못된 데이터가 유입될 가능성이 존재한다.

2. JSON 내부 필드 쿼리

JSON 내부의 특정 키에 대해 직접 조건절을 작성할 수 있다.

-- PostgreSQL 예시: 변수 목록에서 type이 'FORMULA'인 step 조회
SELECT * FROM recipe_steps
WHERE variable_metadata::jsonb @> '[{"type": "FORMULA"}]';

TEXT 타입에서는 LIKE '%FORMULA%'와 같은 문자열 패턴 매칭에 의존해야 하며, 이는 정확도와 성능 양면에서 불리하다.

3. 인덱싱 지원

PostgreSQL의 jsonb는 GIN 인덱스를 생성할 수 있어, JSON 내부 필드에 대한 검색 성능을 크게 향상시킬 수 있다.

CREATE INDEX idx_variable_metadata ON recipe_steps
USING GIN (variable_metadata jsonb_path_ops);

4. 저장 공간 효율 (JSONB 한정)

PostgreSQL jsonb는 바이너리 형태로 저장하므로, 키 중복 제거와 압축이 적용되어 TEXT 대비 저장 공간이 절약된다. 또한 읽기 시 파싱 비용이 들지 않는다.

단점

1. DB 간 호환성 부족

JSON 타입의 문법과 기능은 DB 벤더마다 상이하다. PostgreSQL의 jsonb 연산자(->, ->>, @>)는 MySQL이나 H2에서 동작하지 않는다. 테스트 환경에서 H2를 사용하고 운영에서 PostgreSQL을 사용하는 경우, JSON 타입 컬럼은 호환성 문제를 일으킨다.

2. ORM 매핑의 복잡성

JPA/Hibernate에서 jsonb 컬럼을 매핑하려면 별도의 설정이 필요하다. @JdbcTypeCode(SqlTypes.JSON) 어노테이션을 사용하거나, hypersistence-utils 같은 서드파티 라이브러리를 추가해야 한다. 단순한 String 필드에 columnDefinition = "TEXT"를 지정하는 것에 비하면 진입 장벽이 높다.

3. 쓰기 성능 오버헤드

jsonb는 저장 시 파싱과 바이너리 변환 과정을 거치므로, 단순 TEXT 저장보다 쓰기 비용이 높다. 빈번한 갱신이 발생하는 컬럼에서는 이 차이가 누적될 수 있다.


TEXT 타입으로 저장하는 경우

개요

JSON 문자열을 있는 그대로 TEXT 컬럼에 저장하는 방식이다. DB 입장에서는 일반 문자열과 다를 바 없으며, JSON 파싱과 검증은 전적으로 애플리케이션의 책임이 된다.

장점

1. DB 독립성

TEXT는 모든 RDBMS에서 동일하게 지원된다. 운영 DB와 테스트 DB가 다른 벤더를 사용하더라도 문제가 없다. 예를 들어, 운영에서 PostgreSQL을 쓰고 테스트에서 H2 인메모리 DB를 쓰는 구조에서 TEXT 컬럼은 아무런 호환성 이슈 없이 동작한다.

2. ORM 매핑의 단순함

Java의 String 필드에 @Column(columnDefinition = "TEXT")만 선언하면 끝이다. 별도의 타입 변환기, 서드파티 라이브러리, DB별 방언 설정이 전혀 필요 없다.

@Column(name = "variable_metadata", columnDefinition = "TEXT")
private String variableMetadata;

3. 쓰기 성능

파싱이나 바이너리 변환 없이 문자열을 그대로 저장하므로 쓰기 속도가 빠르다.

4. 유연성

DB가 JSON 구조를 강제하지 않으므로, 스키마 변경 없이 JSON 구조를 자유롭게 변경할 수 있다. 과도기적 데이터 마이그레이션 시에도 유연하게 대처할 수 있다.

단점

1. 무결성 미보장

DB는 컬럼에 어떤 문자열이 들어오든 허용한다. 잘못된 JSON, 빈 문자열, HTML 등이 저장되어도 DB 레벨에서 막을 수 없다. 데이터 품질은 전적으로 애플리케이션 코드에 의존하게 된다.

2. 내부 필드 검색 불가

JSON 내부의 특정 키를 조건으로 검색하려면 LIKE 또는 정규식을 사용해야 하며, 이는 정확성도 낮고 인덱스를 활용하지 못한다.

3. 읽기 시 파싱 비용

매번 조회할 때마다 애플리케이션에서 JSON 파싱을 수행해야 한다. jsonb는 이미 파싱된 상태로 저장되므로 읽기 시 추가 비용이 없지만, TEXT는 매번 ObjectMapper.readValue() 등을 호출해야 한다.


비교 요약

기준 JSON/JSONB 타입 TEXT 타입

DB 레벨 유효성 검증 O X
내부 필드 쿼리 O X
GIN 인덱스 O X
DB 간 호환성 제한적 완전 호환
ORM 매핑 복잡도 높음 낮음
쓰기 성능 상대적으로 느림 빠름
읽기 시 파싱 비용 없음 (이미 파싱) 매번 파싱 필요
저장 공간 효율 좋음 (JSONB) 보통

어떤 경우에 무엇을 선택할 것인가

TEXT가 적합한 경우

  • 데이터를 통째로 읽고 쓰기만 하고, DB에서 JSON 내부를 쿼리할 일이 없는 경우
  • 운영 DB와 테스트 DB가 다른 벤더를 사용하며, 호환성이 중요한 경우
  • ORM 설정을 단순하게 유지하고 싶은 경우
  • JSON 스키마가 자주 변경되는 초기 개발 단계

JSON/JSONB가 적합한 경우

  • DB 레벨에서 JSON 내부 필드를 조건으로 검색해야 하는 경우
  • 데이터 무결성이 비즈니스 요구사항 수준에서 중요한 경우 (예: 규제 환경)
  • 동일 DB 벤더를 운영/테스트 모두에서 사용하는 경우
  • 저장된 JSON이 크고, 부분 읽기/업데이트가 필요한 경우

실무 적용 사례

EBR(Electronic Batch Record) 시스템에서는 레시피 단계(Step)별 변수 목록을 variable_metadata 컬럼에 JSON 배열로 저장하고 있다. 이 필드는 다음과 같은 특성을 가진다.

  • Step 단위로 통째로 읽고 쓴다
  • DB에서 JSON 내부를 쿼리하지 않는다
  • 운영은 PostgreSQL, 테스트는 H2를 사용한다

이러한 조건에서 TEXT 타입은 합리적인 선택이다. H2 호환성을 유지하면서 ORM 매핑을 단순하게 가져갈 수 있기 때문이다. 만약 향후 "특정 타입의 변수를 포함하는 Step을 검색"하는 요구사항이 발생한다면, 그때 jsonb로 전환하는 것이 YAGNI 원칙에도 부합하는 접근이다.


결론

JSON 데이터의 저장 방식은 "정답"이 아니라 "맥락에 따른 선택"의 문제이다.

DB가 JSON을 이해할 필요가 있는가, 여러 DB 벤더를 지원해야 하는가, ORM 복잡도를 얼마나 감수할 수 있는가 이 세 가지 질문에 대한 답이 선택의 기준이 된다. 현재 필요하지 않은 기능을 위해 복잡성을 도입하기보다는, 실제 요구사항이 발생했을 때 전환하는 편이 대부분의 경우 더 나은 전략이다.

반응형
Comments