검색을 위한 데이터 모델 설계

검색 엔진이라고도 하는 정보 검색 시스템은 검색 증강 생성(RAG), 시각적 검색, 제품 추천과 같은 다양한 AI 애플리케이션에 필수적입니다. 이러한 시스템의 핵심은 정보를 구성, 색인화 및 검색하기 위해 세심하게 설계된 데이터 모델입니다.

Milvus를 사용하면 수집 스키마를 통해 검색 데이터 모델을 지정하여 비정형 데이터, 조밀하거나 희박한 벡터 표현, 구조화된 메타데이터를 구성할 수 있습니다. 이 실습 가이드는 텍스트, 이미지 또는 기타 데이터 유형으로 작업하는 경우, 핵심 스키마 개념을 이해하고 실제로 검색 데이터 모델을 설계하는 데 적용하는 데 도움이 될 것입니다.

Data Model Anatomy 데이터 모델 해부학

데이터 모델

검색 시스템의 데이터 모델 설계에는 비즈니스 요구 사항을 분석하고 스키마로 표현된 데이터 모델로 정보를 추상화하는 작업이 포함됩니다. 잘 정의된 스키마는 데이터 모델을 비즈니스 목표에 맞추고, 데이터 일관성과 서비스 품질을 보장하는 데 중요합니다. 또한 적절한 데이터 유형과 인덱스를 선택하는 것은 비즈니스 목표를 경제적으로 달성하는 데 중요합니다.

비즈니스 요구 분석

비즈니스 요구 사항을 효과적으로 해결하기 위해서는 사용자가 수행할 쿼리 유형을 분석하고 가장 적합한 검색 방법을 결정하는 것부터 시작해야 합니다.

  • 사용자 쿼리: 사용자가 수행할 것으로 예상되는 쿼리 유형을 파악합니다. 이를 통해 스키마가 실제 사용 사례를 지원하고 검색 성능을 최적화할 수 있습니다. 여기에는 다음이 포함될 수 있습니다:

    • 자연어 쿼리와 일치하는 문서 검색하기

    • 참조 이미지와 유사한 이미지 또는 텍스트 설명과 일치하는 이미지 찾기

    • 이름, 카테고리 또는 브랜드와 같은 속성으로 제품 검색하기

    • 구조화된 메타데이터(예: 게시 날짜, 태그, 평점)를 기준으로 항목 필터링하기

    • 하이브리드 쿼리에서 여러 기준 결합(예: 시각적 검색에서 이미지와 캡션의 의미적 유사성 고려)

  • 검색 방법: 사용자가 수행할 쿼리 유형에 맞는 적절한 검색 기술을 선택하세요. 각 검색 방법은 목적에 따라 다르며, 종종 더 강력한 결과를 위해 결합할 수 있습니다:

    • 시맨틱 검색: 고밀도 벡터 유사성을 사용하여 비슷한 의미를 가진 항목을 찾는 방법으로 텍스트나 이미지 같은 비정형 데이터에 이상적입니다.

    • 전체 텍스트 검색: 키워드 매칭으로 시맨틱 검색을 보완합니다. 전체 텍스트 검색은 어휘 분석을 활용하여 긴 단어를 조각난 토큰으로 나누지 않고 검색 중에 특수 용어를 파악할 수 있습니다.

    • 메타데이터 필터링: 벡터 검색에 더해 날짜 범위, 카테고리 또는 태그와 같은 제약 조건을 적용합니다.

비즈니스 요구 사항을 검색 데이터 모델로 번역

다음 단계는 정보의 핵심 구성 요소와 그 검색 방법을 파악하여 비즈니스 요구 사항을 구체적인 데이터 모델로 변환하는 것입니다:

  • 원시 콘텐츠(텍스트, 이미지, 오디오), 관련 메타데이터(제목, 태그, 작성자), 상황별 속성(타임스탬프, 사용자 행동 등) 등 저장해야 하는 데이터를 정의합니다.

  • 각 요소에 적합한 데이터 유형과 형식을 결정합니다. 예를 들어

    • 텍스트 설명 → 문자열

    • 이미지 또는 문서 임베딩 → 고밀도 또는 희소 벡터

    • 카테고리, 태그 또는 플래그 → 문자열, 배열 및 부울

    • 가격 또는 평점과 같은 숫자 속성 → 정수 또는 실수

    • 작성자 세부정보와 같은 구조화된 정보 -> json

이러한 요소에 대한 명확한 정의는 데이터 일관성, 정확한 검색 결과, 다운스트림 애플리케이션 로직과의 통합 용이성을 보장합니다.

스키마 설계

Milvus에서 데이터 모델은 컬렉션 스키마를 통해 표현됩니다. 컬렉션 스키마 내에서 올바른 필드를 설계하는 것은 효과적인 검색을 가능하게 하는 핵심입니다. 각 필드는 컬렉션에 저장된 특정 유형의 데이터를 정의하며 검색 프로세스에서 고유한 역할을 합니다. 상위 수준에서 Milvus는 벡터 필드스칼라 필드라는 두 가지 주요 유형의 필드를 지원합니다.

이제 데이터 모델을 벡터 및 보조 스칼라 필드를 포함한 필드 스키마에 매핑할 수 있습니다. 각 필드가 데이터 모델의 속성과 연관되어 있는지 확인하고, 특히 벡터 유형(밀도 또는 스페이스) 및 차원에 주의하세요.

벡터 필드

벡터 필드는 텍스트, 이미지, 오디오와 같은 비정형 데이터 유형에 대한 임베딩을 저장합니다. 이러한 임베딩은 데이터 유형과 사용되는 검색 방법에 따라 고밀도, 스파스 또는 바이너리일 수 있습니다. 일반적으로 고밀도 벡터는 시맨틱 검색에 사용되는 반면, 스파스 벡터는 전체 텍스트 또는 어휘 매칭에 더 적합합니다. 이진 벡터는 저장 공간과 계산 리소스가 제한되어 있을 때 유용합니다. 컬렉션에는 여러 개의 벡터 필드가 포함되어 멀티모달 또는 하이브리드 검색 전략을 사용할 수 있습니다. 이 주제에 대한 자세한 가이드는 멀티-벡터 하이브리드 검색을 참조하세요.

밀도 벡터FLOAT_VECTOR, 스파스 벡터는 SPARSE_FLOAT_VECTOR, 바이너리 벡터는 BINARY_VECTOR 에서 벡터 데이터 유형을 지원합니다.

스칼라 필드

스칼라 필드는 숫자, 문자열 또는 날짜와 같은 원시적이고 구조화된 값(일반적으로 메타데이터라고 함)을 저장합니다. 이러한 값은 벡터 검색 결과와 함께 반환될 수 있으며 필터링 및 정렬에 필수적입니다. 특정 카테고리나 정의된 시간 범위로 문서를 제한하는 등 특정 속성을 기반으로 검색 결과의 범위를 좁힐 수 있습니다.

Milvus는 벡터가 아닌 데이터를 저장하고 필터링하기 위해 BOOL, INT8/16/32/64, FLOAT, DOUBLE, VARCHAR, JSON, ARRAY 과 같은 스칼라 유형을 지원합니다. 이러한 유형은 검색 작업의 정밀도와 사용자 정의 기능을 향상시킵니다.

스키마 설계에서 고급 기능 활용

스키마를 설계할 때 단순히 지원되는 데이터 유형을 사용하여 데이터를 필드에 매핑하는 것만으로는 충분하지 않습니다. 필드 간의 관계와 구성에 사용할 수 있는 전략을 철저히 이해하는 것이 필수적입니다. 설계 단계에서 주요 기능을 염두에 두면 스키마가 즉각적인 데이터 처리 요구 사항을 충족할 뿐만 아니라 향후 필요에 따라 확장 및 조정할 수 있습니다. 이러한 기능을 신중하게 통합하면 Milvus의 기능을 극대화하고 보다 광범위한 데이터 전략과 목표를 지원하는 강력한 데이터 아키텍처를 구축할 수 있습니다. 다음은 컬렉션 스키마를 만드는 주요 기능에 대한 개요입니다:

기본 키

기본 키 필드는 컬렉션 내의 각 엔티티를 고유하게 식별하므로 스키마의 기본 구성 요소입니다. 기본 키를 정의하는 것은 필수입니다. 정수 또는 문자열 유형의 스칼라 필드여야 하며 is_primary=True 로 표시되어야 합니다. 선택적으로 컬렉션에 데이터가 더 많이 수집될수록 정수가 자동으로 할당되는 기본 키에 auto_id 를 활성화할 수 있습니다.

자세한 내용은 기본 필드 및 자동 ID를 참조하세요.

파티셔닝

검색 속도를 높이려면 선택적으로 파티셔닝을 켤 수 있습니다. 파티셔닝을 위해 특정 스칼라 필드를 지정하고 검색 중에 이 필드를 기반으로 필터링 기준을 지정하면 검색 범위를 관련 파티션으로만 효과적으로 제한할 수 있습니다. 이 방법은 검색 도메인을 줄임으로써 검색 작업의 효율성을 크게 향상시킵니다.

자세한 내용은 파티션 키 사용을 참조하세요.

분석기

분석기는 텍스트 데이터를 처리하고 변환하는 데 필수적인 도구입니다. 주요 기능은 원시 텍스트를 토큰으로 변환하고 색인 및 검색을 위해 구조화하는 것입니다. 문자열을 토큰화하고, 중지 단어를 삭제하고, 개별 단어의 어간을 토큰으로 변환하는 방식으로 이를 수행합니다.

자세한 내용은 분석기 개요를 참조하세요.

기능

Milvus를 사용하면 스키마의 일부로 내장 함수를 정의하여 특정 필드를 자동으로 도출할 수 있습니다. 예를 들어, VARCHAR 필드에서 스파스 벡터를 생성하는 기본 제공 BM25 함수를 추가하여 전체 텍스트 검색을 지원할 수 있습니다. 이러한 함수 파생 필드는 전처리를 간소화하고 컬렉션이 독립적이고 쿼리 준비가 된 상태로 유지되도록 합니다.

자세한 내용은 전체 텍스트 검색을 참조하세요.

실제 예제

이 섹션에서는 위 다이어그램에 표시된 멀티미디어 문서 검색 애플리케이션의 스키마 설계 및 코드 예제를 간략하게 설명합니다. 이 스키마는 다음 필드에 데이터가 매핑된 문서가 포함된 데이터 집합을 관리하도록 설계되었습니다:

필드

데이터 소스

검색 메서드에서 사용

기본 키

파티션 키

분석기

함수 입력/출력

article_id (INT64)

활성화된 상태에서 자동 생성 auto_id

Get을 사용하여 쿼리

Y

N

N

N

title (VARCHAR)

기사 제목

텍스트 일치

N

N

Y

N

타임스탬프 (INT32)

게시 날짜

파티션 키로 필터링

N

Y

N

N

텍스트 (VARCHAR)

기사의 원시 텍스트

멀티-벡터 하이브리드 검색

N

N

Y

입력

text_dense_vector (FLOAT_VECTOR)

텍스트 임베딩 모델에 의해 생성된 고밀도 벡터

기본 벡터 검색

N

N

N

N

text_sparse_vector (SPARSE_FLOAT_VECTOR)

내장된 BM25 함수에 의해 자동 생성된 스파스 벡터

전체 텍스트 검색

N

N

N

출력

스키마에 대한 자세한 내용과 다양한 유형의 필드 추가에 대한 자세한 지침은 스키마 설명을 참조하세요.

스키마 초기화

시작하려면 먼저 빈 스키마를 만들어야 합니다. 이 단계에서는 데이터 모델을 정의하기 위한 기본 구조를 설정합니다.

from pymilvus import MilvusClient

schema = MilvusClient.create_schema()
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
        .uri("http://localhost:19530")
        .build();

MilvusClientV2 client = new MilvusClientV2(connectConfig);

// 2. Create an empty schema
CreateCollectionReq.CollectionSchema schema = client.createSchema();
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

//Skip this step using JavaScript
import "github.com/milvus-io/milvus/client/v2/entity"

schema := entity.NewSchema()
# Skip this step using cURL

필드 추가

스키마가 생성되면 다음 단계는 데이터를 구성할 필드를 지정하는 것입니다. 각 필드는 각각의 데이터 유형 및 속성과 연결됩니다.

from pymilvus import DataType

schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, auto_id=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, enable_analyzer=True, enable_match=True, max_length=200, description="article title")
schema.add_field(field_name="timestamp", datatype=DataType.INT32, description="publish date")
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000, enable_analyzer=True, description="article text content")
schema.add_field(field_name="text_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense vector")
schema.add_field(field_name="text_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse vector")
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;

schema.addField(AddFieldReq.builder()
        .fieldName("article_id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("title")
        .dataType(DataType.VarChar)
        .maxLength(200)
        .enableAnalyzer(true)
        .enableMatch(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("timestamp")
        .dataType(DataType.Int32)
        .build())
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(2000)
        .enableAnalyzer(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text_dense_vector")
        .dataType(DataType.FloatVector)
        .dimension(768)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text_sparse_vector")
        .dataType(DataType.SparseFloatVector)
        .build());
const fields = [
    {
        name: "article_id",
        data_type: DataType.Int64,
        is_primary_key: true,
        auto_id: true
    },
    {
        name: "title",
        data_type: DataType.VarChar,
        max_length: 200,
        enable_analyzer: true,
        enable_match: true
    },
    {
        name: "timestamp",
        data_type: DataType.Int32
    },
    {
        name: "text",
        data_type: DataType.VarChar,
        max_length: 2000,
        enable_analyzer: true
    },
    {
        name: "text_dense_vector",
        data_type: DataType.FloatVector,
        dim: 768
    },
    {
        name: "text_sparse_vector",
        data_type: DataType.SparseFloatVector
    }
]
schema.WithField(entity.NewField().
    WithName("article_id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true).
    WithDescription("article id"),
).WithField(entity.NewField().
    WithName("title").
    WithDataType(entity.FieldTypeVarChar).
    WithMaxLength(200).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithDescription("article title"),
).WithField(entity.NewField().
    WithName("timestamp").
    WithDataType(entity.FieldTypeInt32).
    WithDescription("publish date"),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithMaxLength(2000).
    WithEnableAnalyzer(true).
    WithDescription("article text content"),
).WithField(entity.NewField().
    WithName("text_dense_vector").
    WithDataType(entity.FieldTypeFloatVector).
    WithDim(768).
    WithDescription("text dense vector"),
).WithField(entity.NewField().
    WithName("text_sparse_vector").
    WithDataType(entity.FieldTypeSparseVector).
    WithDescription("text sparse vector"),
)
export fields='[
    {
        "fieldName": "article_id",
        "dataType": "Int64",
        "isPrimary": true
    },
    {
        "fieldName": "title",
        "dataType": "VarChar",
        "elementTypeParams": {
            "max_length": 200,
            "enable_analyzer": true,
            "enable_match": true
        }
    },
    {
        "fieldName": "timestamp",
        "dataType": "Int32"
    },
    {
       "fieldName": "text",
       "dataType": "VarChar",
       "elementTypeParams": {
            "max_length": 2000,
            "enable_analyzer": true
        }
    },
    {
       "fieldName": "text_dense_vector",
       "dataType": "FloatVector",
       "elementTypeParams": {
            "dim": 768
        }
    },
    {
       "fieldName": "text_sparse_vector",
       "dataType": "SparseFloatVector",
    }
]'

export schema="{
    \"autoID\": true,
    \"fields\": $fields
}"

이 예에서는 필드에 다음과 같은 속성이 지정되어 있습니다:

  • 기본 키: article_id 이 기본 키로 사용되어 들어오는 엔티티에 기본 키를 자동으로 할당할 수 있습니다.

  • 파티션 키: 파티션별로 필터링할 수 있도록 timestamp 이 파티션 키로 할당됩니다. 예를 들면 다음과 같습니다.

  • 텍스트 분석기: 텍스트 분석기가 2개의 문자열 필드 titletext 에 적용되어 각각 텍스트 일치 및 전체 텍스트 검색을 지원합니다.

(선택 사항) 기능 추가

데이터 쿼리 기능을 향상시키기 위해 함수를 스키마에 통합할 수 있습니다. 예를 들어 특정 필드와 관련된 처리를 위해 함수를 만들 수 있습니다.

from pymilvus import Function, FunctionType

bm25_function = Function(
    name="text_bm25",
    input_field_names=["text"],
    output_field_names=["text_sparse_vector"],
    function_type=FunctionType.BM25,
)

schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;

import java.util.*;

schema.addFunction(Function.builder()
        .functionType(FunctionType.BM25)
        .name("text_bm25")
        .inputFieldNames(Collections.singletonList("text"))
        .outputFieldNames(Collections.singletonList("text_sparse_vector"))
        .build());
import FunctionType from "@zilliz/milvus2-sdk-node";

const functions = [
    {
      name: 'text_bm25',
      description: 'bm25 function',
      type: FunctionType.BM25,
      input_field_names: ['text'],
      output_field_names: ['text_sparse_vector'],
      params: {},
    },
];
function := entity.NewFunction().
    WithName("text_bm25").
    WithInputFields("text").
    WithOutputFields("text_sparse_vector").
    WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
export myFunctions='[
    {
        "name": "text_bm25",
        "type": "BM25",
        "inputFieldNames": ["text"],
        "outputFieldNames": ["text_sparse_vector"],
        "params": {}
    }
]'

export schema="{
    \"autoID\": true,
    \"fields\": $fields
    \"functions\": $myFunctions
}"

이 예에서는 스키마에 기본 제공 BM25 함수를 추가하여 text 필드를 입력으로 활용하고 결과 스파스 벡터를 text_sparse_vector 필드에 저장합니다.

다음 단계