Redis Search 인덱싱
Redis Search 인덱싱 (Indexing JSON documents)
Redis 오픈소스는 해시뿐 아니라 JSON 문서도 인덱싱할 수 있습니다. FT.CREATE로 인덱스를 만들고 JSONPath 스키마를 정의하면, 저장된(및 앞으로 저장될) JSON 문서를 텍스트·수치·벡터 등으로 검색할 수 있습니다.
출처: https://redis.io/docs/latest/develop/ai/search-and-query/indexing/
JSON 스키마로 인덱스 만들기
FT.CREATE 명령으로 인덱스를 만들 때 ON JSON 키워드를 넣으면 데이터베이스에 저장된 기존·신규 JSON 문서를 모두 인덱싱합니다. SCHEMA를 정의하려면 JSONPath 표현을 제공합니다. 각 JSONPath 표현의 결과가 인덱싱되고, attribute(이전엔 field라 불림)라는 논리적 이름에 연결됩니다. 이 attribute들을 쿼리에서 쓸 수 있습니다.
참고:
FT.CREATE에서attribute는 선택입니다.
JSON 인덱스를 만드는 구문:
FT.CREATE {index_name} ON JSON SCHEMA {json_path} AS {attribute} {type}
예를 들어 재고 아이템을 나타내는 각 JSON 문서의 name, description, price, 이미지 벡터 임베딩을 인덱싱하는 명령:
127.0.0.1:6379> FT.CREATE itemIdx ON JSON PREFIX 1 item: SCHEMA $.name AS name TEXT $.description as description TEXT $.price AS price NUMERIC $.embedding AS embedding VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32
JSON 문서 추가
인덱스 생성 후 Redis는 데이터베이스에 저장된 기존·수정·새 JSON 문서를 자동으로 인덱싱합니다. 기존 문서에 대한 인덱싱은 백그라운드로 비동기 실행돼 문서가 검색되기까지 시간이 걸릴 수 있습니다. 수정·새로 만든 문서는 동기로 인덱싱돼 추가·수정 명령이 끝날 때 이미 검색 가능합니다.
JSON 문서를 만들거나 수정하는 데는 JSON.SET, JSON.ARRAPPEND 같은 JSON 쓰기 명령을 쓸 수 있습니다.
예시로 두 재고 아이템을 담는 JSON 문서:
{
"name": "Noise-cancelling Bluetooth headphones",
"description": "Wireless Bluetooth headphones with noise-cancelling technology",
"connection": { "wireless": true, "type": "Bluetooth" },
"price": 99.98,
"stock": 25,
"colors": ["black", "silver"],
"embedding": [0.87, -0.15, 0.55, 0.03]
}
{
"name": "Wireless earbuds",
"description": "Wireless Bluetooth in-ear headphones",
"connection": { "wireless": true, "type": "Bluetooth" },
"price": 64.99,
"stock": 17,
"colors": ["black", "white"],
"embedding": [-0.7, -0.51, 0.88, 0.14]
}
JSON.SET으로 저장:
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","connection":{"wireless":true,"type":"Bluetooth"},"price":99.98,"stock":25,"colors":["black","silver"],"embedding":[0.87,-0.15,0.55,0.03]}'
"OK"
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":64.99,"stock":17,"colors":["black","white"],"embedding":[-0.7,-0.51,0.88,0.14]}'
"OK"
이 경우 인덱싱은 동기이므로 JSON.SET이 반환되는 즉시 문서가 인덱스에서 조회 가능하고, 이후 인덱스 내용에 맞는 쿼리가 문서를 반환합니다.
인덱스 검색
JSON 문서를 검색하려면 FT.SEARCH 명령을 씁니다. SCHEMA에 정의된 어떤 attribute든 검색할 수 있습니다. name에 "earbuds"가 있는 아이템을 검색:
127.0.0.1:6379> FT.SEARCH itemIdx '@name:(earbuds)'
1) "1"
2) "item:2"
3) 1) "$"
2) "{\"name\":\"Wireless earbuds\",...}"
description에 "bluetooth"와 "headphones"가 모두 포함된 아이템 검색:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)'
블루투스 헤드폰 중 가격이 70 미만인 것:
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones) @price:[0 70]'
그리고 임베딩이 [1.0, 1.0, 1.0, 1.0]인 이미지와 가장 유사한 블루투스 헤드폰 검색(KNN):
127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)=>[KNN 2 @embedding $blob]' PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
참고:
FT.SEARCH쿼리는attribute수식어를 요구합니다. 쿼리 파서가 JSONPath를 완전히 지원하지 않으므로 쿼리에서 JSONPath 표현은 쓰지 마세요.
TAG 필드 동작: 해시 vs JSON
TAG 필드는 해시 문서냐 JSON 문서냐에 따라 다르게 동작하며, 이 차이는 흔한 혼란의 원인입니다.
해시 문서
# HASH: 쉼표가 기본 구분자
HSET product:1 category "Electronics,Gaming,PC"
FT.CREATE products ON HASH PREFIX 1 product: SCHEMA category TAG
# 결과: "Electronics", "Gaming", "PC" 3개의 별도 태그 생성
FT.SEARCH products '@category:{Gaming}' # ✅ 문서 발견
JSON 문서
# JSON: 기본 구분자 없음 - 전체 문자열이 태그 하나가 됨
JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG
# 결과: "Electronics,Gaming,PC" 태그 1개 생성
FT.SEARCH products '@category:{Gaming}' # ❌ 문서 못 찾음
FT.SEARCH products '@category:{Electronics,Gaming,PC}' # ✅ 문서 발견
JSON 문서를 해시처럼 동작하게
JSON에서 해시처럼 동작하게 하려면 명시적으로 SEPARATOR ","를 추가합니다.
JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG SEPARATOR ","
# 결과: "Electronics", "Gaming", "PC" 3개의 별도 태그 생성
FT.SEARCH products '@category:{Gaming}' # ✅ 이제 문서 발견
JSON 권장 방식
쉼표 구분 문자열 대신 JSON 배열을 쓰세요.
JSON.SET product:1 $ '{"category": ["Electronics", "Gaming", "PC"]}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category[*] AS category TAG
# 결과: "Electronics", "Gaming", "PC" 3개의 별도 태그 생성
FT.SEARCH products '@category:{Gaming}' # ✅ 문서 발견
JSON 배열을 TAG로 인덱싱
JSON 문서에서 여러 값을 가진 TAG 필드를 만들려면 두 가지 방법이 있습니다.
방법 1: JSON 배열(권장)
여러 태그 값을 인덱싱하는 선호 방법입니다. 배열의 각 요소가 별도 태그 값이 됩니다. 배열 요소를 인덱싱하려면 JSONPath 와일드카드 연산자 [*]를 씁니다.
# 배열 인덱싱으로 인덱스 생성
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category[*] AS category TAG
이 방식은 각 요소가 쿼리 가능한 별도 태그가 되므로, 해시의 기본 쉼표 구분자 동작과 일관됩니다.
방법 2: SEPARATOR 옵션
SEPARATOR 옵션을 TAG 필드에 지정해 구분자를 명시할 수 있습니다. (위 "JSON 문서를 해시처럼 동작하게" 참조) 단, 배열 방식이 더 명확하고 권장됩니다.
누락·빈 프로퍼티 검색 (v2.10+)
버전 2.10부터 FT.CREATE의 INDEXMISSING 옵션과 FT.SEARCH의 ismissing 쿼리 함수로 문서에 존재하지 않는(누락된) 프로퍼티를 검색할 수 있습니다. INDEXEMPTY 옵션으로 값이 없는(빈) 프로퍼티도 검색할 수 있습니다. 두 쿼리 유형 모두 DIALECT 2가 필요합니다.
JSON.SET key:1 $ '{"propA": "foo"}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXMISSING
> FT.SEARCH idx 'ismissing(@propB)' DIALECT 2
JSON.SET key:1 $ '{"propA": "foo", "propB":""}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXEMPTY
> FT.SEARCH idx '@propB:{""}' DIALECT 2
인덱스 제한 사항
스키마 매핑
인덱스 생성 시 JSON 요소를 SCHEMA 필드로 다음과 같이 매핑해야 합니다.
- 문자열 →
TEXT,TAG, 또는GEO. - 숫자 →
NUMERIC. - 불리언 →
TAG. - JSON 배열
- 문자열 배열 →
TAG또는TEXT. - 숫자 배열 →
NUMERIC또는VECTOR. - 지리 좌표 배열 →
GEO. - 이러한 배열의
null값은 무시됩니다.
- 문자열 배열 →
- JSON 객체는 인덱싱할 수 없습니다. 개별 요소를 별도 attribute로 인덱싱하세요.
null값은 무시됩니다.
여러 값을 내는 JSONPath
JSONPath가 배열 또는 여러 값으로 이어질 때:
HIGHLIGHT·SUMMARIZE미지원.SORTBY는 첫 번째 값만으로 정렬.- 스키마 attribute의
RETURN은 값을 JSON 문자열로 반환. RETURN에 스키마 attribute 대신 JSONPath를 지정하면 모든 값을 JSON 문자열로 반환.
더 알아보기 (Learn more)
- 검색 쿼리 문법: Search query syntax
- 쿼리 하기: Querying data
- JSON 경로: JSON path
- 관련 명령: FT.CREATE, FT.SEARCH, JSON.SET