포스트

JSON 타입 정리

MySQL의 JSON 타입에 대하여 정리합니다.

JSON 타입 정리

1. JSON 타입의 등장

  • 기존 서비스의 규모가 커지면서 요구사항이 바뀌게 되거나 비즈니스 로직의 변경이 잦아졌다. -> ALTER를 통한 테이블 구조 변경 작업 -> 테이블 구조 변경시 수분~수십분 정도의 테이블 락이 발생하게 되었다.
  • 결국, 자주 바뀌는 부분은 JSON으로 값을 넣도록 변경되었다.

2. MySQL 지원 현황

  • 5.7 부터 JSON 타입 공식 지원한다.
  • 내부적으로 바이너리 타입으로 사용한다.
  • 여러 JSON 함수를 지원한다. (JSON_EXTRACT(), JSON_SET())

3. JSON 타입의 장점

1) 스키마 유연성

  • ALTER를 통하여 테이블 구조를 수정하지 않고 여러 데이터를 하나의 필드에 추가할 수 있다.
  • 정규화를 진행할수록 테이블이 계속 늘어나지만, JSON은 하나의 컬럼에 작업 없이 여러 데이터를 저장할 수 있다.

2) 반정규화 조회 성능

  • 여러 테이블을 조인하지 않고, 하나의 JSON 필드를 읽으면 여러 값을 동시에 조회할 수 있다.
  • 여러 값이 함께 쓰이는 경우 한 번의 조회로 여러 값을 동시에 가져올 수 있다.
  • 쓰기보다 조회 요청이 많을수록 효과적이다.

3) 외부 시스템 연동 데이터 저장

  • 외부 시스템과 관련된 외부 시스템 작업 결과, 요청 결과 같은 정보는 외부시스템의 스키마를 직접 조절할 수 없다.
  • 시간이 지나 외부 시스템의 스키마나 형태가 달라질 경우, 필요한 필드만 추출해서 사용할 수 있다.

4) 이벤트 및 로그성 데이터

  • 이벤트 및 로그성 데이터는 종류마다 형태가 다르다. 구조가 정적이지 않은 데이터는 테이블로 설계하기 비효율적이다.

5) MVP 검증

  • 신규 기능을 빠르게 검증하고 싶을 때 JSON으로 데이터를 저장한 후, 기능이 안정화되면 정규 컬럼으로 분리하여 저장한다.

4. JSON 타입의 단점

1) 데이터 무결성 보장 불가

  • 테이블 컬럼은 기본키, 유니크, 외래키 제약조건을 통하여 데이터의 무결성을 보장할 수 있지만, JSON은 보장하지 않는다.
  • 애플리케이션코드에서 보장할 수 있지만, 누락되거나 유지보수 비용이 증가한다.

2) 조회 성능이 낮다

  • JSON은 내부키를 조건으로 검색하기 때문에 느리다.
  • 테이블 컬럼은 인덱스의 B-트리를 사용하기 때문에 조회성능을 올릴 수 있지만, JSON 컬럼은 풀스캔 한다.

3) JOIN과 집계의 어려움

  • 조인이나 집계 함수를 사용하기 위하여 항상 JSON 함수를 사용하여 JSON 내부의 데이터를 추출하는 작업이 필요하다.
  • 조인 쿼리나 집계 함수를 사용하는 쿼리가 길어지거나 복잡해진다. -> 쿼리를 최적화하지 못하고, 속도는 느려진다.

4) 스키마 파악 불가

  • 테이블 컬럼은 DDL을 통하여 볼 수 있지만, JSON 컬럼은 직접 파싱하여 분석해야 한다.
  • 시기가 지나면 JSON 내부 데이터의 스키마가 조금씩 달라지는 경우가 있다.
1
2
3
4
5
6
7
8
9
10
11
// before
{
  "address": "서울시 마포구"
}
// after
{
  "address": {
    "city": "서울",
    "district": "마포구"
  }
}

5) 저장 공간의 비효율성

  • 일반 컬럼은 중복이 발생할 경우 정규화를 하여 중복을 최소화할 수 있지만, JSON은 중복을 막을 수 있는 방법이 없다.
  • JSON의 데이터 하나만 바뀌어도 JSON 필드 모두를 백업하여 binlog에 저장해야 한다.

6) 부분 업데이트 비용

  • 일반 컬럼은 해당 행만 생성, 수정, 삭제가 되지만, JSON은 내부의 데이터가 하나라도 추가, 삭제, 변경되면 내부적으로 JSON을 새로 생성한다.
  • 부분 업데이트를 하기 위해서 JSON 전용 함수를 사용해야 한다.

5. JSON 타입 사용 가이드라인

1) JSON 타입 고려

  • 주된 요청이 읽기 위주이고, 조회 시 JSON 필드의 내용 전체가 동시에 함께 사용되고, 자주 변경되지 않는다면 JSON 타입 사용을 고려해본다. (상품 속성, 외부 API 요청과 응답, 설정 및 환경 정보, 이벤트 페이로드)

2) 일반 컬럼 고려

  • 일부 필드만 조회하거나, 자주 변경되거나, 조인 및 집계함수를 자주 사용한다면 일반 컬럼 사용을 고려해본다. -> 일부 스키마만 정규컬럼을 사용하고 나머지는 JSON 타입을 사용할수도 있다.
  • 데이터의 무결성이 반드시 필요한 중요한 데이터라면 일반 컬럼을 사용한다. 무결성 보장을 위하여 해당 필드가 비즈니스의 핵심이라면 일반 컬럼을 사용하고, 부가 정보이면 JSON 사용을 고려해본다.

3) JSON 스키마

  • JSON의 스키마를 팀 차원에서 별도의 문서로 관리한다.
    • 키 네이밍 컨벤션
    • 버전 필드 -> 버전 필드가 있다면 버전이 달라도 분기처리하여 처리 가능하다.
    • 최대 깊이 및 크기 제한
1
2
3
4
5
6
7
8
{
  "_schema_version": 2,
  "delivery_type": "express",
  "packaging": {
    "eco_friendly": true,
    "material": "paper"
  }
}

4) JSON 크기 제한

1
2
-- MySQL CHECK 제약
ALTER TABLE products ADD CONSTRAINT chk_attr_size CHECK (JSON_LENGTH(attributes) < 100 AND LENGTH(attributes) < 10240);
  • JSON의 크기제한 권장량은 아래와 같다.
    • 일반적인 속성 데이터: 10KB 이하
    • 이벤트 payload: 64KB 이하
    • 원본 보관용: 별도 테이블 분리, 1MB 상한

5) JSON 인덱스 추가

  • JSON 필드에 인덱스를 사용하고 싶다면 가상 컬럼을 생성한 후 인덱스를 추가한다.
1
2
3
ALTER TABLE orders 
ADD delivery_type VARCHAR(20) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(options, '$.delivery_type'))) VIRTUAL,
ADD INDEX idx_delivery_type (delivery_type);

6) JSON -> 일반 컬럼 마이그레이션 검토 기준

  • 아래 조건이 만족하면 JSON에서 일반 컬럼으로 변환을 검토해본다.
    • JSON 키로 검색하는 쿼리가 많아지거나
    • JSON 키의 NOT NULL 조건이 필요하거나
    • JSON 키로 조인, 집계를 자주하거나
    • JSON 키의 업데이트가 빈번히 이루어지거나
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.