백엔드 데이터 인프라 74편 — RedisJSON (JSON.SET · JSONPath · PG JSONB 비교)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 74편. RedisJSON 모듈 — JSON 1급 데이터 타입. JSON.SET·JSON.GET·JSONPath 명령어와 부분 업데이트·중첩 객체 조작, PostgreSQL JSONB 와의 비교, RediSearch 통합까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 74편 — RedisJSON (JSON.SET · JSONPath · PG JSONB 비교)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 74편이에요. 73편 까지 Spring 통합을 풀었다면, 이번 74편부터는 Part 4-7 — 모듈 (4편) 으로 들어가요. 첫 글은 RedisJSON — JSON 을 1급 데이터 타입으로 다루는 모듈이고 Redis 8+ 부터는 기본 포함이에요.

RedisJSON이 어렵게 느껴지는 이유

JSON 다루기는 Redis 일반 String + 직렬화로도 됐던 터라 RedisJSON 의 의의가 처음에 잘 안 잡혀요.

50편에서 본 Hash 와 직렬화 String 으로 다 풀리는 듯한데 굳이 모듈을 따로 두는 이유는, 부분 업데이트·중첩 구조·JSONPath(JSON 경로 쿼리 문법) 라는 세 가지 결정적 차이 때문이에요. 그리고 JSONPath 자체가 새 쿼리 언어인데 $.path.to.field 같은 syntax 가 XPath 와 비슷하면서도 처음 보면 헷갈리고요. 마지막은 패키징 차이 — Redis 6/7 에서는 RedisJSON 모듈을 따로 설치해야 하고, 8+ 는 기본 포함이라 환경에 따라 설치 방법이 갈려요.

이 글에서 RedisJSON 의 세 가지 강점·핵심 명령어 7개·JSONPath 기초·PG JSONB(Postgres 바이너리 JSON 타입) 비교·실무 함정까지 풀어요.

RedisJSON 의 세 가지 결정적 강점

(1) 부분 업데이트

# Hash 또는 String JSON 패턴
> SET user:42 '{"name":"Alice","age":30,"address":{"city":"Seoul"}}'
# 도시만 바꾸려면 → 전체 read · 파싱 · 수정 · 직렬화 · write
> SET user:42 '{...modified entire object...}'

# RedisJSON
> JSON.SET user:42 $.address.city '"Busan"'
OK

중첩된 필드 한 개만 갱신해서 네트워크·serialization 비용이 거의 0 이에요.

(2) 중첩 구조 지원

Hash 는 flat field-value 만 담는데, JSON 은 임의 중첩이 가능해서 복잡한 도메인 모델을 그대로 넣을 수 있어요.

(3) JSONPath 쿼리

# 특정 path 의 값만 조회
> JSON.GET user:42 $.address.city
"[\"Busan\"]"

# 배열의 특정 인덱스
> JSON.GET user:42 $.tags[0]

부분 조회라서 전체 객체를 가져오지 않고 필요한 부분만 받을 수 있어요.

핵심 명령어 7종

JSON.SET / JSON.GET

> JSON.SET bike $ '{"name":"Hyperion","price":1200,"colors":["red","blue"]}'
OK

> JSON.GET bike $
"[{\"name\":\"Hyperion\",\"price\":1200,\"colors\":[\"red\",\"blue\"]}]"

> JSON.GET bike $.name
"[\"Hyperion\"]"

$ 는 루트 path 이고, $.field · $.array[0] 같은 식으로 내려가요.

JSON.TYPE

> JSON.TYPE bike $.price
"integer"
> JSON.TYPE bike $.colors
"array"

JSON.NUMINCRBY — atomic 산술

> JSON.SET stats $ '{"views":0,"likes":0}'
OK
> JSON.NUMINCRBY stats $.views 1
"[1]"
> JSON.NUMINCRBY stats $.views 1
"[2]"
> JSON.NUMINCRBY stats $.likes 5
"[5]"

49편의 String INCR 을 JSON 버전으로 옮긴 셈이라, 중첩 카운터를 자연스럽게 다뤄요.

JSON.ARRAPPEND / JSON.ARRINSERT

> JSON.ARRAPPEND bike $.colors '"green"'
"[3]"           # 배열 길이
> JSON.GET bike $.colors
"[[\"red\",\"blue\",\"green\"]]"

> JSON.ARRINSERT bike $.colors 0 '"black"'
"[4]"
> JSON.GET bike $.colors
"[[\"black\",\"red\",\"blue\",\"green\"]]"

배열 끝이나 특정 위치에 원소를 추가해요.

JSON.ARRPOP / JSON.ARRLEN

> JSON.ARRPOP bike $.colors -1     # 마지막 원소
"\"green\""
> JSON.ARRLEN bike $.colors
"[3]"

JSON.STRAPPEND

> JSON.STRAPPEND bike $.name '" V2"'
"[12]"          # 변경 후 길이
> JSON.GET bike $.name
"[\"Hyperion V2\"]"

JSON.DEL

> JSON.DEL bike $.colors[-1]
(integer) 1

특정 path 를 골라서 삭제해요.

JSON.OBJKEYS / JSON.OBJLEN

> JSON.OBJKEYS bike $
1) "name"
2) "price"
3) "colors"
> JSON.OBJLEN bike $
"[3]"

객체의 키 목록과 필드 수를 돌려줘요.

JSONPath 기초

Syntax 의미
$ 루트
$.field 객체 필드
$.field.sub 중첩 필드
$.array[0] 배열 인덱스
$.array[-1] 끝에서 1번째
$.array[0:5] 슬라이스
$.array[*] 모든 원소
$..field 재귀 탐색 (모든 level 의 field)
$.array[?(@.price > 100)] 조건부 필터

재귀 탐색 예제

> JSON.SET cart $ '{"items":[{"name":"a","price":100},{"name":"b","price":200}]}'
> JSON.GET cart $..price
"[100,200]"           # 모든 level 의 price

조건부 필터는 RedisJSON 2.0+ 부터 지원해요.

RedisJSON vs PostgreSQL JSONB — 정확한 비교

항목 RedisJSON PG JSONB
저장 위치 메모리 디스크
지연 시간 μs ms
부분 업데이트 ◯ atomic △ (jsonb_set, 트랜잭션 안에서)
인덱싱 RediSearch 와 통합 GIN 인덱스 native
SQL 조인 X
ACID △ (단일 키만)
트랜잭션 MULTI/EXEC 또는 Lua
데이터량 메모리 한계 TB 단위

선택 가이드

  • 자주 읽고·쓰는 작은 JSON 문서 (캐시·세션)RedisJSON
  • 영구 보관 + SQL 결합 필요PG JSONB
  • 둘 다 = 일반 패턴 (PG 가 진실, Redis 가 빠른 사본)

RediSearch 와 통합 — JSON 인덱스 + 검색

RedisJSON 의 진짜 강점은 RediSearch(Redis 전문 검색·인덱스 모듈, 75편) 와 결합할 때 드러나요.

> FT.CREATE bike-idx ON JSON PREFIX 1 bike: SCHEMA $.name AS name TEXT $.price AS price NUMERIC SORTABLE

> JSON.SET bike:1 $ '{"name":"Hyperion","price":1200}'
> JSON.SET bike:2 $ '{"name":"Phoenix","price":800}'

> FT.SEARCH bike-idx "@price:[1000 +inf]"

JSON 문서에 전문 검색·범위 인덱스·정렬을 얹어서, MongoDB 와 비슷한 문서 DB 경험을 낼 수 있어요.

설치 — Redis 7 이하

Docker (Redis Stack)

docker run -d -p 6379:6379 redis/redis-stack-server:latest

redis-stack-server 는 Redis + 모든 공식 모듈(RedisJSON·RediSearch·TimeSeries·Bloom) 묶음이에요.

모듈 수동 로드

# redis.conf
loadmodule /path/to/rejson.so

Redis 8+

기본 포함이라 별도 설치가 필요 없어요.

한계·실무 함정

1. 한 키 = 한 JSON 문서

여러 사용자를 한 JSON 으로 묶지 마세요. 큰 문서는 부분 업데이트도 비싸지기 때문에, 사용자별 키로 쪼개는 게 표준이에요.

2. JSONPath 가 표준이 아님

JSONPath-Plus·Goessner 같은 여러 구현이 미세하게 달라요. RedisJSON 은 자체 구현이라, 너무 복잡한 경로는 호환성을 확인하는 게 안전해요.

3. RedisJSON 2 = JSONPath 1 (default)

RedisJSON 1 의 JSONPath 0 와 결과 형식이 달라서 반환값이 항상 배열로 wrap 돼요. 마이그레이션할 때 이 부분을 놓치기 쉬워요.

4. 메모리 압박

JSON 이 큰 자료구조가 되면 메모리가 폭증해요. 적절히 분할하거나, RDB(관계형 DB) 를 진실로 두고 Redis 는 빠른 사본으로 쓰는 패턴이 안전해요.

5. 직렬화 오버헤드

매 JSON.GET 마다 서버에서 직렬화·전송·클라이언트 역직렬화가 한 번씩 일어나요. 전체 객체를 자주 가져오는 패턴은 부담이 커서, 필요한 path 만 골라서 가져오는 게 효율적이에요.

Spring Data Redis 통합

@Autowired
private RedisTemplate<String, String> redisTemplate;

public void setUser(String uid, String json) {
    redisTemplate.opsForValue().getOperations().execute(
        (RedisCallback<Object>) connection ->
            connection.execute("JSON.SET", uid.getBytes(), "$".getBytes(), json.getBytes())
    );
}

Spring Data Redis 자체에는 RedisJSON 1급 API 가 없어서, raw 명령으로 호출하거나 Spring Data Redis OM(객체-매핑 라이브러리) 을 써요.

시험 직전 한 번 더 — RedisJSON 함정 압축 노트

  • RedisJSON = JSON 1급 데이터 타입 모듈
  • Redis 8+ = 기본 포함, 6/7 = 별도 설치
  • 3가지 강점 = 부분 업데이트 · 중첩 구조 · JSONPath
  • 핵심 명령 7종 = JSON.SET·JSON.GET·JSON.TYPE·JSON.NUMINCRBY·JSON.ARRAPPEND·JSON.STRAPPEND·JSON.DEL
  • 보조 = JSON.ARRINSERT·JSON.ARRPOP·JSON.ARRLEN·JSON.OBJKEYS·JSON.OBJLEN
  • path syntax = $ 루트, $.field, $.array[0], $.array[*], $..field 재귀
  • JSON.NUMINCRBY = atomic 산술, 중첩 카운터
  • JSON.ARRAPPEND = 배열 끝 추가
  • RedisJSON vs PG JSONB — 메모리 vs 디스크 / μs vs ms / 부분 업데이트 atomic
  • 선택 — 자주 read/write 작은 JSON = RedisJSON / 영구 + SQL 결합 = PG
  • RediSearch 와 결합 = JSON 문서 검색·인덱스 (75편)
  • FT.CREATE ... ON JSON PREFIX ... SCHEMA $.field AS name TEXT/NUMERIC
  • 설치 = Redis Stack (redis-stack-server) 도커
  • 모듈 수동 로드 = loadmodule rejson.so
  • 함정 — 한 키 = 한 JSON 문서, 사용자별 분할
  • 함정 — JSONPath 구현 호환성 (너무 복잡한 경로)
  • 함정 — RedisJSON 2 의 JSONPath 1 default = 결과 배열로 wrap
  • 함정 — 큰 JSON = 메모리 폭증
  • 함정 — 전체 객체 자주 GET = 직렬화 부담, 필요한 path 만
  • Spring Data Redis = raw 명령 또는 Spring Data Redis OM

공식 문서: Redis JSON 에서 자세한 사양을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!