PGvector 벡터 스토어

PGvector 벡터 스토어 (Spring AI)

임베딩을 PostgreSQL에 바로 저장하고, 그 안에서 유사도 검색까지 하고 싶다면 PGvector가 가장 자연스러운 선택이에요. Spring AI의 PgVectorStore는 문서 임베딩을 저장하고 유사도 검색을 수행하는 설정 과정을 안내해 주는데, 이 글은 바로 그 설정을 처음부터 끝까지 다뤄요.

PGvector는 PostgreSQL용 오픈소스 확장으로, 머신러닝으로 생성한 임베딩을 저장하고 검색할 수 있게 해 줘요. 정확한 최근접 이웃(exact nearest neighbor)과 근사 최근접 이웃(approximate nearest neighbor)을 모두 식별할 수 있는 여러 기능을 제공하고, 인덱싱과 쿼리를 포함한 다른 PostgreSQL 기능들과 자연스럽게 어우러져요.

사전 준비 (Prerequisites)

먼저 vector, hstore, uuid-ossp 확장이 활성화된 PostgreSQL 인스턴스에 접근할 수 있어야 해요.

스키마 초기화 기능을 명시적으로 켜면, PgVectorStore가 시작할 때 필요한 DB 확장을 설치하고, 없으면 vector_store 테이블과 인덱스를 생성해요. 원하면 수동으로도 아래처럼 만들 수 있어요.

CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE IF NOT EXISTS vector_store (
	id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
	content text,
	metadata json,
	embedding vector(1536) // 1536 is the default embedding dimension
);

CREATE INDEX ON vector_store USING HNSW (embedding vector_cosine_ops);

참고: 다른 차원을 쓴다면 1536을 실제 임베딩 차원으로 바꿔 주세요. PGvector는 HNSW 인덱스에서 최대 2000차원을 지원해요.

그다음, 필요한 경우 PgVectorStore에 저장할 임베딩을 생성하는 EmbeddingModel용 API 키를 준비해요.

참고: PGvector 데이터베이스를 Spring Boot dev service로 Docker Compose나 Testcontainers로 돌릴 수도 있고, 부록의 로컬 Postgres/PGvector 설정을 따라 Docker 컨테이너로 직접 만들 수도 있어요.

자동 설정 (Auto-Configuration)

중요: Spring AI 자동 설정과 스타터 모듈의 아티팩트 이름에 큰 변화가 있었어요. 자세한 내용은 upgrade notes를 확인해 주세요.

먼저 PgVectorStore 부트 스타터 의존성을 프로젝트에 추가해요.

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>

또는 Gradle build.gradle 파일에 이렇게 넣어요.

dependencies {
    implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
}

벡터 스토어 구현이 필요한 스키마를 직접 초기화해 줄 수 있지만, 반드시 옵트인(opt-in)해야 해요. 적절한 생성자에서 initializeSchema 불리언을 지정하거나, application.properties 파일에 ...initialize-schema=true를 설정하면 돼요.

중요: 이것은 breaking change예요! 이전 버전의 Spring AI에서는 이 스키마 초기화가 기본으로 동작했어요.

벡터 스토어는 문서의 임베딩을 계산하기 위해 EmbeddingModel 인스턴스도 필요해요. 사용 가능한 EmbeddingModel 구현 중 하나를 고르면 되는데, 예를 들어 OpenAI EmbeddingModel을 쓰려면 다음 의존성을 추가해요.

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

또는 Gradle build.gradle 파일에 이렇게 넣어요.

dependencies {
    implementation 'org.springframework.ai:spring-ai-starter-model-openai'
}

참고: 스프링 AI BOM을 빌드 파일에 추가하는 방법은 Dependency Management, Maven Central/Snapshot 저장소 추가는 Artifact Repositories 섹션을 참고해요.

PgVectorStore에 연결하고 설정하려면 인스턴스 접근 정보를 제공해야 해요. Spring Boot의 application.yml로 간단히 설정할 수 있어요.

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: postgres
  ai:
	vectorstore:
	  pgvector:
		index-type: HNSW
		distance-type: COSINE_DISTANCE
		dimensions: 1536
		max-document-batch-size: 10000 # Optional: Maximum number of documents per batch

참고: PGvector를 Spring Boot dev service로 Docker Compose나 Testcontainers로 실행한다면, URL·username·password는 Spring Boot가 자동 설정하므로 따로 설정할 필요 없어요.

참고: 기본값과 설정 옵션은 아래 configuration parameters 목록을 확인해 주세요.

이제 애플리케이션에서 VectorStore를 오토와이어해서 사용할 수 있어요.

@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 PGVector
vectorStore.add(documents);

// Retrieve documents similar to a query
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());

설정 프로퍼티

Spring Boot 설정에서 PGvector 벡터 스토어를 커스터마이즈할 수 있는 프로퍼티는 다음과 같아요.

Property Description Default value
spring.ai.vectorstore.pgvector.index-type Nearest neighbor search index type. Options are NONE - exact nearest neighbor search, IVFFlat - index divides vectors into lists, and then searches a subset of those lists that are closest to the query vector. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff). HNSW - creates a multilayer graph. It has slower build times and uses more memory than IVFFlat, but has better query performance (in terms of speed-recall tradeoff). There's no training step like IVFFlat, so the index can be created without any data in the table. HNSW
spring.ai.vectorstore.pgvector.distance-type Search distance type. Defaults to COSINE_DISTANCE. But if vectors are normalized to length 1, you can use EUCLIDEAN_DISTANCE or NEGATIVE_INNER_PRODUCT for best performance. COSINE_DISTANCE
spring.ai.vectorstore.pgvector.dimensions Embeddings dimension. If not specified explicitly the PgVectorStore will retrieve the dimensions form the provided EmbeddingModel. Dimensions are set to the embedding column the on table creation. If you change the dimensions your would have to re-create the vector_store table as well. -
spring.ai.vectorstore.pgvector.remove-existing-vector-store-table Deletes the existing vector_store table on start up. false
spring.ai.vectorstore.pgvector.initialize-schema Whether to initialize the required schema false
spring.ai.vectorstore.pgvector.schema-name Vector store schema name public
spring.ai.vectorstore.pgvector.table-name Vector store table name vector_store
spring.ai.vectorstore.pgvector.schema-validation Enables schema and table name validation to ensure they are valid and existing objects. false
spring.ai.vectorstore.pgvector.max-document-batch-size Maximum number of documents to process in a single batch. 10000

참고: 커스텀 스키마나 테이블명을 구성한다면 spring.ai.vectorstore.pgvector.schema-validation=true로 스키마 검증을 켜는 걸 고려해 보세요. 이름의 정확성을 보장하고 SQL 인젝션 공격 위험을 줄여 줘요.

메타데이터 필터링 (Metadata filtering)

PgVector 스토어에서도 일반적이고 이식 가능한(portable) 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());

참고: 이 필터 표현식들은 효율적인 메타데이터 필터링을 위해 PostgreSQL JSON path 표현식으로 변환돼요.

수동 설정 (Manual Configuration)

Spring Boot 자동 설정 대신 PgVectorStore를 수동으로 구성할 수도 있어요. 그러려면 프로젝트에 PostgreSQL 연결과 JdbcTemplate 자동 설정 의존성을 추가해야 해요.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>

참고: 스프링 AI BOM을 빌드 파일에 추가하는 방법은 Dependency Management 섹션을 참고해요.

애플리케이션에서 PgVector를 구성하는 방법은 다음과 같아요.

@Bean
public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
    return PgVectorStore.builder(jdbcTemplate, embeddingModel)
        .dimensions(1536)                    // Optional: defaults to model dimensions or 1536
        .distanceType(COSINE_DISTANCE)       // Optional: defaults to COSINE_DISTANCE
        .indexType(HNSW)                     // Optional: defaults to HNSW
        .initializeSchema(true)              // Optional: defaults to false
        .schemaName("public")                // Optional: defaults to "public"
        .vectorTableName("vector_store")     // Optional: defaults to "vector_store"
        .maxDocumentBatchSize(10000)         // Optional: defaults to 10000
        .build();
}

Postgres & PGvector DB 로컬 실행

docker run -it --rm --name postgres -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres pgvector/pgvector

이 서버에 이렇게 연결할 수 있어요.

psql -U postgres -h localhost -p 5432

네이티브 클라이언트 접근

PgVectorStore 구현은 getNativeClient() 메서드를 통해 내부의 네이티브 JDBC 클라이언트(JdbcTemplate)에 접근할 수 있게 해 줘요.

PgVectorStore vectorStore = context.getBean(PgVectorStore.class);
Optional<JdbcTemplate> nativeClient = vectorStore.getNativeClient();

if (nativeClient.isPresent()) {
    JdbcTemplate jdbc = nativeClient.get();
    // Use the native client for PostgreSQL-specific operations
}

네이티브 클라이언트는 VectorStore 인터페이스로 노출되지 않는 PostgreSQL 고유의 기능과 연산에 접근할 수 있게 해 줘요.

더 알아보기 (Learn more)