Milvus 벡터 스토어
Milvus 벡터 스토어 (Spring AI)
방대한 데이터셋에서도 빠른 유사 벡터 검색이 필요하다면 Milvus가 강력한 선택이에요. 이 글은 Spring AI로 Milvus VectorStore를 설정해서 문서 임베딩을 저장하고 유사도 검색을 수행하는 과정을 안내해요.
Milvus는 데이터 과학과 머신러닝 분야에서 큰 주목을 받은 오픈소스 벡터 데이터베이스예요. 벡터 인덱싱과 쿼리에 대한 탄탄한 지원이 두드러지는 특징인데, 검색 과정을 가속화하는 최첨단 알고리즘을 사용해서 방대한 데이터셋에서도 유사 벡터 검색이 매우 효율적이에요.
사전 준비 (Prerequisites)
- 실행 중인 Milvus 인스턴스. 다음 옵션이 가능해요.
- Milvus Standalone: Docker, Operator, Helm, DEB/RPM, Docker Compose.
- Milvus Cluster: Operator, Helm.
- 필요한 경우
MilvusVectorStore에 저장할 임베딩을 생성하는EmbeddingModel용 API 키를 준비해요.
의존성 (Dependencies)
중요: Spring AI 자동 설정과 스타터 모듈의 아티팩트 이름에 큰 변화가 있었어요. 자세한 내용은 upgrade notes를 확인해 주세요.
Milvus VectorStore 부트 스타터 의존성을 프로젝트에 추가해요.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>
또는 Gradle build.gradle 파일에 이렇게 넣어요.
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-vector-store-milvus'
}
참고: 스프링 AI BOM은 Dependency Management, Maven Central/Snapshot 저장소 추가는 Artifact Repositories 섹션을 참고해요.
벡터 스토어 구현이 필요한 스키마를 직접 초기화해 줄 수 있지만, 반드시 옵트인해야 해요. 적절한 생성자에서 initializeSchema 불리언을 지정하거나 application.properties에 ...initialize-schema=true를 설정하면 돼요.
중요: 이것은 breaking change예요! 이전 버전의 Spring AI에서는 이 스키마 초기화가 기본으로 동작했어요.
벡터 스토어는 문서의 임베딩을 계산하기 위해 EmbeddingModel 인스턴스도 필요해요. 사용 가능한 EmbeddingModel 구현 중 하나를 고를 수 있어요.
MilvusVectorStore에 연결하고 설정하려면 인스턴스 접근 정보를 제공해야 해요. Spring Boot의 application.yml로 간단히 설정할 수 있어요.
spring:
ai:
vectorstore:
milvus:
client:
host: "localhost"
port: 19530
username: "root"
password: "milvus"
databaseName: "default"
collectionName: "vector_store"
embeddingDimension: 1536
indexType: IVF_FLAT
metricType: COSINE
참고: 기본값과 설정 옵션은 아래 configuration parameters 목록을 확인해 주세요.
이제 애플리케이션에서 Milvus Vector Store를 오토와이어해서 사용할 수 있어요.
@Autowired VectorStore vectorStore;
// ...
List <Document> documents = List.of(
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("meta1", "meta1")),
new Document("The World is Big and Salvation Lurks Around the Corner"),
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2")));
// Add the documents to Milvus Vector Store
vectorStore.add(documents);
// Retrieve documents similar to a query
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());
수동 설정 (Manual Configuration)
Spring Boot 자동 설정 대신 MilvusVectorStore를 수동으로 구성할 수도 있어요. 프로젝트에 다음 의존성을 추가해요.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
</dependency>
참고: 스프링 AI BOM을 빌드 파일에 추가하는 방법은 Dependency Management 섹션을 참고해요.
애플리케이션에서 MilvusVectorStore를 구성하는 방법은 다음과 같아요.
@Bean
public VectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel) {
return MilvusVectorStore.builder(milvusClient, embeddingModel)
.collectionName("test_vector_store")
.databaseName("default")
.indexType(IndexType.IVF_FLAT)
.metricType(MetricType.COSINE)
.batchingStrategy(new TokenCountBatchingStrategy())
.initializeSchema(true)
.build();
}
@Bean
public MilvusServiceClient milvusClient() {
return new MilvusServiceClient(ConnectParam.newBuilder()
.withAuthorization("minioadmin", "minioadmin")
.withUri(milvusContainer.getEndpoint())
.build());
}
메타데이터 필터링 (Metadata filtering)
Milvus 스토어에서도 일반적이고 이식 가능한 metadata filters를 활용할 수 있어요.
예를 들어 텍스트 표현 언어로 필터링할 수 있고,
vectorStore.similaritySearch(
SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("author in ['john', 'jill'] && article_type == 'blog'").build());
Filter.Expression DSL로 프로그래밍 방식으로도 필터링할 수 있어요.
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression(b.and(
b.in("author","john", "jill"),
b.eq("article_type", "blog")).build()).build());
참고: 이 필터 표현식들은 동등한 Milvus 필터로 변환돼요.
참고: 검색 결과에서 메타데이터를 다시 읽을 때 숫자 값은 Gson으로 역직렬화돼요. 정수 값은
Long으로, 소수 값은Double로 반환돼요. 정수 메타데이터 필드를Double로 직접 캐스팅하는 코드는Long또는Number상위 타입을 쓰도록 업데이트해야 해요.
MilvusSearchRequest 사용
MilvusSearchRequest는 SearchRequest를 확장해서, 네이티브 표현식과 검색 파라미터 JSON 같은 Milvus 고유 검색 파라미터를 사용할 수 있게 해 줘요.
MilvusSearchRequest request = MilvusSearchRequest.milvusBuilder()
.query("sample query")
.topK(5)
.similarityThreshold(0.7)
.nativeExpression("metadata[\"age\"] > 30") // Overrides filterExpression if both are set
.filterExpression("age <= 30") // Ignored if nativeExpression is set
.searchParamsJson("{\"nprobe\":128}")
.build();
List results = vectorStore.similaritySearch(request);
이렇게 하면 Milvus 고유 검색 기능을 사용할 때 더 큰 유연성을 얻을 수 있어요.
MilvusSearchRequest에서 nativeExpression과 searchParamsJson의 중요성
이 두 파라미터는 Milvus 검색 정밀도를 높이고 최적의 쿼리 성능을 보장해요.
nativeExpression: Milvus 네이티브 필터링 표현식을 사용한 추가 필터링을 가능하게 해 줘요. (Milvus Filtering 참고)
예시:
MilvusSearchRequest request = MilvusSearchRequest.milvusBuilder()
.query("sample query")
.topK(5)
.nativeExpression("metadata['category'] == 'science'")
.build();
searchParamsJson: Milvus의 기본 인덱스인 IVF_FLAT를 사용할 때 검색 동작 튜닝에 필수적이에요. (Milvus Vector Index 참고)
기본적으로 IVF_FLAT는 정확한 결과를 위해 nprobe가 설정돼야 해요. 지정하지 않으면 nprobe가 1로 기본 설정되어 재현율이 낮아지거나 검색 결과가 0개가 될 수 있어요.
예시:
MilvusSearchRequest request = MilvusSearchRequest.milvusBuilder()
.query("sample query")
.topK(5)
.searchParamsJson("{\"nprobe\":128}")
.build();
nativeExpression은 고급 필터링을 보장하고, searchParamsJson은 낮은 기본 nprobe 값으로 인한 비효율적인 검색을 방지해요.
Milvus VectorStore 프로퍼티
Spring Boot 설정에서 Milvus 벡터 스토어를 커스터마이즈할 수 있는 프로퍼티는 다음과 같아요.
| Property | Description | Default value |
|---|---|---|
| spring.ai.vectorstore.milvus.database-name | The name of the Milvus database to use. | default |
| spring.ai.vectorstore.milvus.collection-name | Milvus collection name to store the vectors | vector_store |
| spring.ai.vectorstore.milvus.partition-name | Existing Milvus partition name used to scope insert, delete, and search operations. When unset or blank, Milvus default behavior is used. | - |
| spring.ai.vectorstore.milvus.initialize-schema | whether to initialize Milvus' backend | false |
| spring.ai.vectorstore.milvus.embedding-dimension | The dimension of the vectors to be stored in the Milvus collection. | 1536 |
| spring.ai.vectorstore.milvus.index-type | The type of the index to be created for the Milvus collection. | IVF_FLAT |
| spring.ai.vectorstore.milvus.metric-type | The metric type to be used for the Milvus collection. | COSINE |
| spring.ai.vectorstore.milvus.index-parameters | The index parameters to be used for the Milvus collection. | {"nlist":1024} |
| spring.ai.vectorstore.milvus.id-field-name | The ID field name for the collection | doc_id |
| spring.ai.vectorstore.milvus.auto-id | Boolean flag to indicate if the auto-id is used for the ID field | false |
| spring.ai.vectorstore.milvus.content-field-name | The content field name for the collection | content |
| spring.ai.vectorstore.milvus.metadata-field-name | The metadata field name for the collection | metadata |
| spring.ai.vectorstore.milvus.embedding-field-name | The embedding field name for the collection | embedding |
| spring.ai.vectorstore.milvus.client.host | The name or address of the host. | localhost |
| spring.ai.vectorstore.milvus.client.port | The connection port. | 19530 |
| spring.ai.vectorstore.milvus.client.uri | The uri of Milvus instance | - |
| spring.ai.vectorstore.milvus.client.token | Token serving as the key for identification and authentication purposes. | - |
| spring.ai.vectorstore.milvus.client.connect-timeout-ms | Connection timeout value of client channel. The timeout value must be greater than zero. | 10000 |
| spring.ai.vectorstore.milvus.client.keep-alive-time-ms | Keep-alive time value of client channel. The keep-alive value must be greater than zero. | 55000 |
| spring.ai.vectorstore.milvus.client.keep-alive-timeout-ms | The keep-alive timeout value of client channel. The timeout value must be greater than zero. | 20000 |
| spring.ai.vectorstore.milvus.client.rpc-deadline-ms | Deadline for how long you are willing to wait for a reply from the server. With a deadline setting, the client will wait when encounter fast RPC fail caused by network fluctuations. The deadline value must be larger than or equal to zero. | 0 |
| spring.ai.vectorstore.milvus.client.client-key-path | The client.key path for tls two-way authentication, only takes effect when "secure" is true | - |
| spring.ai.vectorstore.milvus.client.client-pem-path | The client.pem path for tls two-way authentication, only takes effect when "secure" is true | - |
| spring.ai.vectorstore.milvus.client.ca-pem-path | The ca.pem path for tls two-way authentication, only takes effect when "secure" is true | - |
| spring.ai.vectorstore.milvus.client.server-pem-path | server.pem path for tls one-way authentication, only takes effect when "secure" is true. | - |
| spring.ai.vectorstore.milvus.client.server-name | Sets the target name override for SSL host name checking, only takes effect when "secure" is True. Note: this value is passed to grpc.ssl_target_name_override | - |
| spring.ai.vectorstore.milvus.client.secure | Secure the authorization for this connection, set to True to enable TLS. | false |
| spring.ai.vectorstore.milvus.client.idle-timeout-ms | Idle timeout value of client channel. The timeout value must be larger than zero. | 24h |
| spring.ai.vectorstore.milvus.client.username | The username and password for this connection. | root |
| spring.ai.vectorstore.milvus.client.password | The password for this connection. | milvus |
Milvus 스토어 시작
src/test/resources/ 폴더 안에서 다음을 실행해요.
docker-compose up
환경을 정리하려면:
docker-compose down; rm -Rf ./volumes
그런 다음 http://localhost:19530의 벡터 스토어에 연결하거나, 관리용으로 http://localhost:9001(user: minioadmin, pass: minioadmin)에 연결해요.
트러블슈팅 (Troubleshooting)
Docker가 리소스 문제를 호소하면 다음을 실행해요.
docker system prune --all --force --volumes
네이티브 클라이언트 접근
Milvus 벡터 스토어 구현은 getNativeClient() 메서드를 통해 내부의 네이티브 Milvus 클라이언트(MilvusServiceClient)에 접근할 수 있게 해 줘요.
MilvusVectorStore vectorStore = context.getBean(MilvusVectorStore.class);
Optional<MilvusServiceClient> nativeClient = vectorStore.getNativeClient();
if (nativeClient.isPresent()) {
MilvusServiceClient client = nativeClient.get();
// Use the native client for Milvus-specific operations
}
네이티브 클라이언트는 VectorStore 인터페이스로 노출되지 않는 Milvus 고유 기능과 연산에 접근할 수 있게 해 줘요.