벡터 스토리지에 Redis 데이터 구조 최적화

Completed

팁 (조언)

자세한 내용은 텍스트 및 이미지 탭을 참조하세요.

Redis 해시 및 JSON 데이터 구조 중에서 선택하면 메모리 사용량, 성능 및 벡터 데이터 작업 방식에 영향을 줍니다. 절충을 이해하면 애플리케이션의 요구 사항에 맞게 최적화할 수 있습니다.

유사성 검색을 위해 벡터 포함을 사용하여 제품 카탈로그를 빌드하고 있다고 상상해 보십시오. 각 제품에는 이름, 가격, 범주 및 1536차원 포함이 있습니다. 데이터가 단순하고 평평한 경우 Redis Hash는 필요한 필드만 최소한의 오버헤드로 압축하여 저장합니다. 그러나 제품에 중첩된 특성(예: 여러 이미지, 변형 SKU 또는 계층적 범주)이 있는 경우 JSON을 사용하면 벡터 검색을 지원하면서 복잡한 구조를 자연스럽게 저장할 수 있습니다. 올바른 구조를 선택하면 시스템이 성능이 향상되고 유지 관리가 가능합니다.

벡터에 Redis 해시 사용

Redis Hash는 프로그래밍 언어의 사전 또는 맵과 유사하게 정보를 단일 키 내에 필드 값 쌍으로 저장하는 데이터 구조입니다. 벡터 스토리지에 해시를 사용하는 경우, 임베딩은 이진 바이트로 저장된 하나의 필드가 되며 제품 이름, 가격, 범주와 같은 다른 필드와 함께 하나의 Redis 키 아래 저장됩니다. 이 플랫 구조는 해시가 최소한의 형식 오버헤드로 정의한 정확한 필드만 저장하기 때문에 성능 및 메모리 효율성이 뛰어납니다. 간단한 메타데이터를 사용하는 벡터 검색 애플리케이션의 경우 해시는 가장 빠른 쿼리와 가장 작은 메모리 공간을 제공합니다. 중첩된 개체가 없는 플랫 데이터 모델이 있거나, 최대 쿼리 성능이 필요하거나, 메모리 효율성의 우선 순위를 지정하는 경우 해시를 사용합니다.

해시에 벡터 저장

벡터를 다른 메타데이터와 함께 해시 필드에 바이트로 저장합니다.

# Code fragment - focus on Hash storage
import numpy as np

embedding = np.array([0.1, 0.2, 0.3, ...], dtype=np.float32)

redis_client.hset(
    "product:12345",
    mapping={
        "name": "Wireless Mouse",
        "price": "29.99",
        "category": "electronics",
        "embedding": embedding.tobytes()  # Store vector as bytes
    }
)

벡터는 필드에 이진 Blob embedding 으로 저장됩니다. 이 방법은 메모리 오버헤드를 최소화합니다. 필요한 항목만 저장합니다.

해시 기반 벡터에 대한 인덱스 만들기

벡터 필드를 사용하여 해시 키를 검색하도록 인덱스 정의:

# Code fragment - focus on Hash index
from redis.commands.search.field import TextField, NumericField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType

schema = (
    TextField("name"),
    NumericField("price"),
    TextField("category"),
    VectorField("embedding", "HNSW", {
        "TYPE": "FLOAT32",
        "DIM": 1536,
        "DISTANCE_METRIC": "COSINE"
    })
)

redis_client.ft("idx:products").create_index(
    fields=schema,
    definition=IndexDefinition(
        prefix=["product:"],
        index_type=IndexType.HASH
    )
)

벡터에 Redis JSON 사용

Redis JSON은 데이터를 JSON 문서로 저장하므로 중첩된 개체 및 배열을 사용하여 계층적으로 정보를 구성할 수 있습니다. 해시의 플랫 필드-값 쌍과 달리 JSON을 사용하면 복잡한 데이터를 자연스럽게 구조화할 수 있습니다. 예를 들어 제품에는 이미지 배열, 중첩된 사양 또는 변형 세부 정보가 모두 하나의 문서에 있을 수 있습니다. 벡터 스토리지에 JSON을 사용하는 경우 포함은 애플리케이션에 필요한 만큼 복잡할 수 있는 다른 필드와 함께 숫자 배열 [0.1, 0.2, 0.3, ...] 로 저장됩니다. 이러한 유연성은 해시에 비해 메모리 오버헤드 및 쿼리 시간이 약간 더 높습니다.

JSON에 벡터 저장

JSON에 벡터를 저장할 때 NumPy 배열을 JSON 호환 숫자 배열을 만드는 Python 목록 tolist()으로 변환합니다. 이 배열 형식을 사용하면 Redis에서 중첩된 개체 및 복잡한 메타데이터 구조를 포함할 수 있을 만큼 유연하게 문서를 유지하면서 벡터를 구문 분석하고 인덱싱할 수 있습니다.

# Code fragment - focus on JSON storage
import redis.commands.json as json_commands

document = {
    "name": "Wireless Mouse",
    "price": 29.99,
    "category": "electronics",
    "embedding": embedding.tolist(),  # Convert to list for JSON
    "specs": {
        "color": "black",
        "wireless": True,
        "battery": "AA"
    }
}

redis_client.json().set("product:12345", "$", document)

벡터는 JSON 배열 [0.1, 0.2, 0.3, ...]로 저장됩니다. 이 형식은 자연스럽게 같은 specs 중첩된 개체를 지원합니다.

JSON 기반 벡터에 대한 인덱스 만들기

벡터 필드를 사용하여 JSON 문서를 검색하도록 인덱스 정의 데이터에 중첩 구조가 있거나, 문서당 여러 벡터가 필요하거나, 애플리케이션에서 이미 JSON 형식을 사용하거나, JSON 쿼리 기능이 필요한 경우 JSON을 사용합니다.

# Code fragment - focus on JSON index
from redis.commands.search.field import TextField, NumericField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType

schema = (
    TextField("$.name", as_name="name"),
    NumericField("$.price", as_name="price"),
    TextField("$.category", as_name="category"),
    VectorField("$.embedding", "HNSW", {
        "TYPE": "FLOAT32",
        "DIM": 1536,
        "DISTANCE_METRIC": "COSINE"
    }, as_name="embedding")
)

redis_client.ft("idx:products").create_index(
    fields=schema,
    definition=IndexDefinition(
        prefix=["product:"],
        index_type=IndexType.JSON
    )
)

필드 이름에 대한 $. JSONPath 구문과 쿼리 참조에 대한 as_name 매개 변수를 주목하세요.

해시 및 JSON 비교

데이터 복잡성 및 성능 요구 사항에 따라 선택합니다.

요인 Hash JSON
메모리 사용량 하위(이진 벡터 스토리지) 상위(JSON 배열 형식)
쿼리 성능 더 빠른 약간 느립니다.
데이터 복잡성 플랫 필드만 지원되는 중첩 개체
벡터 스토리지 이진 바이트 숫자 배열
적합한 대상 단순 레코드, 최대 성능 복잡한 문서, 유연성

성능 고려 사항

벡터는 컴팩트한 이진 Blob으로 저장되기 때문에 해시 스토리지는 더 빠르고 메모리를 적게 사용합니다. JSON 스토리지는 배열 구조에 대한 오버헤드를 추가하지만 더 많은 유연성을 제공합니다.

# Code fragment - focus on storage comparison

# Hash: 1536 floats × 4 bytes = 6,144 bytes for vector
hash_vector = embedding.tobytes()  # 6,144 bytes

# JSON: Similar data size but with JSON formatting overhead
json_vector = embedding.tolist()  # 6,144 bytes + JSON structure

1,536차원 벡터가 있는 1백만 개의 제품의 경우 해시는 상당한 메모리를 절약하고 쿼리 대기 시간을 개선합니다.

해시와 JSON 간 전환

새 인덱스로 데이터를 다시 수집하여 데이터 구조를 변경할 수 있습니다.

# Code fragment - focus on migration approach

# Read from Hash
hash_data = redis_client.hgetall("product:12345")

# Convert to JSON
document = {
    "name": hash_data["name"],
    "price": float(hash_data["price"]),
    "category": hash_data["category"],
    "embedding": np.frombuffer(hash_data["embedding"], dtype=np.float32).tolist()
}

# Store as JSON
redis_client.json().set("product:12345", "$", document)

이전 인덱스 삭제 및 새 구조에 적합한 IndexType 새 인덱스 만들기

빠른 의사 결정 가이드

이 가이드를 사용하여 데이터 구조를 선택합니다.

다음과 같은 경우 해시 선택:

  • 각 항목에는 단순하고 평평한 필드가 있습니다.
  • 메모리 효율성 우선 순위 지정
  • 최대 쿼리 속도가 필요합니다.
  • 데이터 모델에 중첩된 개체가 필요하지 않습니다.

다음과 같은 경우 JSON을 선택합니다.

  • 데이터에 중첩 구조체가 있습니다.
  • 항목당 여러 벡터를 저장해야 합니다.
  • 애플리케이션에서 이미 JSON을 사용하고 있습니다.
  • 유연성은 원시 성능보다 더 중요합니다.

간단한 메타데이터를 사용하는 대부분의 벡터 검색 애플리케이션의 경우 Hash는 성능과 단순성의 최상의 균형을 제공합니다. 데이터 복잡성이 필요한 경우 JSON을 사용합니다.

추가 리소스