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.CREATEINDEXMISSING 옵션과 FT.SEARCHismissing 쿼리 함수로 문서에 존재하지 않는(누락된) 프로퍼티를 검색할 수 있습니다. 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)