Apache Cassandra 벡터 스토어

Apache Cassandra 벡터 스토어 (Apache Cassandra Vector Store)

이 섹션에서는 CassandraVectorStore를 설정해서 문서 임베딩을 저장하고 유사도 검색을 수행하는 방법을 안내해요. Apache Cassandra는 선형 확장성과 검증된 내결함성, 낮은 지연 시간으로 유명한 오픈소스 분산 데이터베이스이고, 그 Vector Similarity Search (VSS)는 JVector 라이브러리 기반이에요.

출처: 문서

본문

Apache Cassandra 벡터 스토어 (Apache Cassandra Vector Store)

이 섹션은 CassandraVectorStore를 설정해 문서 임베딩을 저장하고 유사도 검색을 수행하는 방법을 안내해요.

Apache Cassandra란? (What is Apache Cassandra?)

Apache Cassandra®는 선형 확장성, 검증된 내결함성, 낮은 지연 시간으로 유명한 진정한 오픈소스 분산 데이터베이스로, 미션 크리티컬한 트랜잭션 데이터를 위한 완벽한 플랫폼이에요. 그것의 Vector Similarity Search (VSS)는 최고 수준의 성능과 관련성을 보장하는 JVector 라이브러리 기반이에요.

Apache Cassandra의 벡터 검색은 다음과 같이 간단히 수행돼요:

SELECT content FROM table ORDER BY content_vector ANN OF query_embedding;

이에 대한 더 많은 문서는 여기에서 읽을 수 있어요.

이 Spring AI Vector Store는 완전히 새로운 RAG 애플리케이션과 기존 데이터·테이블 위에 장착(retrofit)할 수 있는 경우 모두를 위해 설계됐어요. 이 스토어는 기존 데이터베이스에서 시맨틱 검색, 지리 근접 검색 같은 비-RAG 사용 사례에도 사용할 수 있어요.

스토어는 구성에 따라 필요에 맞게 스키마를 자동으로 생성하거나 향상시켜요. 스키마 수정을 원하지 않으면 initializeSchema로 스토어를 구성하세요. spring-boot-autoconfigure를 사용할 때 initializeSchema는 Spring Boot 표준에 따라 기본값이 false이며, application.properties 파일에서 …initialize-schema=true로 설정해 스키마 생성/수정을 선택해야 해요.

JVector란? (What is JVector?)

JVector는 순수 Java 임베디드 벡터 검색 엔진이에요. 다른 HNSW Vector Similarity Search 구현과 구별되는 점은 다음과 같아요:

  • 알고리즘적으로 빠름 (Algorithmic-fast). JVector는 DiskANN과 관련 연구에서 영감을 받은 최첨단 그래프 알고리즘을 사용해 높은 재현율과 낮은 지연 시간을 제공해요.
  • 구현상 빠름 (Implementation-fast). JVector는 Panama SIMD API를 사용해 인덱스 빌드와 쿼리를 가속화해요.
  • 메모리 효율적 (Memory efficient). JVector는 product quantization으로 벡터를 압축해서 검색 중 메모리에 유지할 수 있게 해 줘요.
  • 디스크 인지 (Disk-aware). JVector의 디스크 레이아웃은 쿼리 시 최소한의 필수 iops를 하도록 설계됐어요.
  • 동시성 (Concurrent). 인덱스 빌드는 최소 32개 스레드까지 선형으로 확장돼요. 스레드를 두 배로 하면 빌드 시간이 절반이 돼요.
  • 점진적 (Incremental). 빌드하면서 인덱스를 쿼리할 수 있어요. 벡터를 추가하는 것과 검색 결과에서 찾을 수 있는 것 사이에 지연이 없어요.
  • 임베딩하기 쉬움 (Easy to embed). 프로덕션에서 사용하는 사람들에 의해 쉬운 임베딩을 위해 설계된 API.

사전 준비 (Prerequisites)

  1. 문서 임베딩을 계산할 EmbeddingModel 인스턴스. 보통 Spring Bean으로 구성해요. 여러 옵션이 가능해요:

    • Transformers Embedding - 로컬 환경에서 임베딩을 계산해요. 기본값은 ONNX와 all-MiniLM-L6-v2 Sentence Transformers예요. 그냥 작동해요.
    • OpenAI의 Embeddings를 원한다면 - OpenAI 임베딩 엔드포인트를 사용해요. OpenAI Signup에서 계정을 만들고 API Keys에서 api-key 토큰을 생성해야 해요.
    • 더 많은 선택지가 있으니 Embeddings API 문서를 참고하세요.
  2. 5.0-beta1 버전부터의 Apache Cassandra 인스턴스.

DIY Quick Start

관리형 서비스의 경우 Astra DB가 넉넉한 무료 티어를 제공해요.

의존성 (Dependencies)

참고: Spring AI auto-configuration과 starter 모듈의 아티팩트 이름에 큰 변화가 있었어요. 자세한 내용은 upgrade notes를 참고해 주세요.

참고: 의존성 관리를 위해 Dependency Management 섹션에 설명된 대로 Spring AI BOM을 사용하는 것을 권장해요.

프로젝트에 다음 의존성을 추가하세요:

  • Cassandra Vector Store만 필요한 경우:
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-cassandra-store</artifactId>
</dependency>
  • 또는 RAG 애플리케이션에 필요한 모든 것(기본 ONNX Embedding Model 사용)을 원한다면:
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-cassandra</artifactId>
</dependency>

구성 프로퍼티 (Configuration Properties)

Spring Boot 구성에서 다음 프로퍼티를 사용해 Apache Cassandra 벡터 스토어를 커스터마이즈할 수 있어요.

Property Default Value
spring.ai.vectorstore.cassandra.keyspace springframework
spring.ai.vectorstore.cassandra.table ai_vector_store
spring.ai.vectorstore.cassandra.initialize-schema false
spring.ai.vectorstore.cassandra.index-name
spring.ai.vectorstore.cassandra.content-column-name content
spring.ai.vectorstore.cassandra.embedding-column-name embedding
spring.ai.vectorstore.cassandra.fixed-thread-pool-executor-size 16

사용법 (Usage)

기본 사용법 (Basic Usage)

Spring Bean으로 CassandraVectorStore 인스턴스를 만드세요:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .session(session)
        .keyspace("my_keyspace")
        .table("my_vectors")
        .build();
}

벡터 스토어 인스턴스가 생기면 문서를 추가하고 검색을 수행할 수 있어요:

// Add documents
vectorStore.add(List.of(
    new Document("1", "content1", Map.of("key1", "value1")),
    new Document("2", "content2", Map.of("key2", "value2"))
));

// Search with filters
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder().query("search text")
        .topK(5)
        .similarityThreshold(0.7)
        .filterExpression("metadata.key1 == 'value1'")
        .build()
);

고급 구성 (Advanced Configuration)

더 복잡한 사용 사례는 Spring Bean에서 추가 설정을 구성할 수 있어요:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .session(session)
        .keyspace("my_keyspace")
        .table("my_vectors")
        // Configure primary keys
        .partitionKeys(List.of(
            new SchemaColumn("id", DataTypes.TEXT),
            new SchemaColumn("category", DataTypes.TEXT)
        ))
        .clusteringKeys(List.of(
            new SchemaColumn("timestamp", DataTypes.TIMESTAMP)
        ))
        // Add metadata columns with optional indexing
        .addMetadataColumns(
            new SchemaColumn("category", DataTypes.TEXT, SchemaColumnTags.INDEXED),
            new SchemaColumn("score", DataTypes.DOUBLE)
        )
        // Customize column names
        .contentColumnName("text")
        .embeddingColumnName("vector")
        // Performance tuning
        .fixedThreadPoolExecutorSize(32)
        // Schema management
        .initializeSchema(true)
        // Custom batching strategy
        .batchingStrategy(new TokenCountBatchingStrategy())
        .build();
}

연결 구성 (Connection Configuration)

Cassandra에 연결을 구성하는 두 가지 방법이 있어요:

  • 주입된 CqlSession 사용 (권장):
@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .session(session)
        .keyspace("my_keyspace")
        .table("my_vectors")
        .build();
}
  • 빌더에서 연결 정보를 직접 사용:
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .contactPoint(new InetSocketAddress("localhost", 9042))
        .localDatacenter("datacenter1")
        .keyspace("my_keyspace")
        .build();
}

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

CassandraVectorStore에서 일반적이고 휴대 가능한 메타데이터 필터를 활용할 수 있어요. 메타데이터 컬럼이 검색 가능하려면 기본 키이거나 SAI 인덱싱되어야 해요. 기본 키가 아닌 컬럼을 인덱싱하려면 SchemaColumnTags.INDEXED로 메타데이터 컬럼을 구성하세요.

예를 들어 텍스트 표현 언어를 사용할 수 있어요:

vectorStore.similaritySearch(
    SearchRequest.builder().query("The World")
        .topK(5)
        .filterExpression("country in ['UK', 'NL'] && year >= 2020").build());

또는 표현식 DSL로 프로그래밍 방식으로:

Filter.Expression f = new FilterExpressionBuilder()
    .and(
        f.in("country", "UK", "NL"),
        f.gte("year", 2020)
    ).build();

vectorStore.similaritySearch(
    SearchRequest.builder().query("The World")
        .topK(5)
        .filterExpression(f).build());

휴대 가능한 필터 표현식은 자동으로 CQL 쿼리로 변환돼요.

고급 예제: Wikipedia 데이터셋 위의 벡터 스토어 (Advanced Example: Vector Store on top of Wikipedia Dataset)

다음 예제는 기존 스키마 위에서 스토어를 사용하는 방법을 보여 줘요. 여기서는 전체 wikipedia 데이터셋을 이미 벡터화해 제공하는 github.com/datastax-labs/colbert-wikipedia-data 프로젝트의 스키마를 사용해요.

먼저 Cassandra 데이터베이스에 스키마를 만드세요:

wget https://s.apache.org/colbert-wikipedia-schema-cql -O colbert-wikipedia-schema.cql
cqlsh -f colbert-wikipedia-schema.cql

그런 다음 빌더 패턴으로 스토어를 구성하세요:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    List<SchemaColumn> partitionColumns = List.of(
        new SchemaColumn("wiki", DataTypes.TEXT),
        new SchemaColumn("language", DataTypes.TEXT),
        new SchemaColumn("title", DataTypes.TEXT)
    );

    List<SchemaColumn> clusteringColumns = List.of(
        new SchemaColumn("chunk_no", DataTypes.INT),
        new SchemaColumn("bert_embedding_no", DataTypes.INT)
    );

    List<SchemaColumn> extraColumns = List.of(
        new SchemaColumn("revision", DataTypes.INT),
        new SchemaColumn("id", DataTypes.INT)
    );

    return CassandraVectorStore.builder()
        .session(session)
        .embeddingModel(embeddingModel)
        .keyspace("wikidata")
        .table("articles")
        .partitionKeys(partitionColumns)
        .clusteringKeys(clusteringColumns)
        .contentColumnName("body")
        .embeddingColumnName("all_minilm_l6_v2_embedding")
        .indexName("all_minilm_l6_v2_ann")
        .initializeSchema(false)
        .addMetadataColumns(extraColumns)
        .primaryKeyTranslator((List<Object> primaryKeys) -> {
            if (primaryKeys.isEmpty()) {
                return "test§¶0";
            }
            return String.format("%s§¶%s", primaryKeys.get(2), primaryKeys.get(3));
        })
        .documentIdTranslator((id) -> {
            String[] parts = id.split("§¶");
            String title = parts[0];
            int chunk_no = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
            return List.of("simplewiki", "en", title, chunk_no, 0);
        })
        .build();
}

@Bean
public EmbeddingModel embeddingModel() {
    // default is ONNX all-MiniLM-L6-v2 which is what we want
    return new TransformersEmbeddingModel();
}

전체 Wikipedia 데이터셋 로드 (Loading the Complete Wikipedia Dataset)

전체 wikipedia 데이터셋을 로드하려면:

  1. s.apache.org/simplewiki-sstable-tar에서 simplewiki-sstable.tar을 다운로드하세요 (오래 걸릴 거예요. 파일은 수십 GB예요).
  2. 데이터 로드:
tar -xf simplewiki-sstable.tar -C ${CASSANDRA_DATA}/data/wikidata/articles-*/
nodetool import wikidata articles ${CASSANDRA_DATA}/data/wikidata/articles-*/

참고:

  • 이 테이블에 기존 데이터가 있으면 tar를 할 때 tarball의 파일이 기존 sstables를 덮어쓰지 않는지 확인하세요.
  • nodetool import의 대안은 Cassandra를 그냥 재시작하는 것이에요.
  • 인덱스에 실패가 있으면 자동으로 재빌드돼요.

네이티브 클라이언트 접근 (Accessing the Native Client)

Cassandra Vector Store 구현은 getNativeClient() 메서드를 통해 내부 네이티브 Cassandra 클라이언트(CqlSession)에 접근을 제공해요:

CassandraVectorStore vectorStore = context.getBean(CassandraVectorStore.class);
Optional<CqlSession> nativeClient = vectorStore.getNativeClient();

if (nativeClient.isPresent()) {
    CqlSession session = nativeClient.get();
    // Use the native client for Cassandra-specific operations
}

네이티브 클라이언트는 VectorStore 인터페이스로는 노출되지 않는 Cassandra 특화 기능과 작업에 접근할 수 있게 해 줘요.

더 알아보기 (Learn more)