백엔드 데이터 인프라 74편. RedisJSON 모듈 — JSON 1급 데이터 타입. JSON.SET·JSON.GET·JSONPath 명령어와 부분 업데이트·중첩 객체 조작, PostgreSQL JSONB 와의 비교, RediSearch 통합까지 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 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 에서 자세한 사양을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 69편 — Redis Sentinel (자동 Failover · Quorum)
- 70편 — Redis ACL (사용자·권한 관리)
- 71편 — Redis TLS (전송 암호화 · mTLS)
- 72편 — Redis 메모리 최적화 (Encoding · Hash 압축 · Fragmentation)
- 73편 — Spring Data Redis (RedisTemplate · @Cacheable)
다음 글: