본문 바로가기
데이터베이스 & ORM

PostgreSQL & pgvector

by DoRightting 2024. 11. 16.

1. 프로젝트 내 구현 사례

1.1 PGVector 확장 및 테이블 설정

-- init.sql에서 구현된 Vector 설정
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE prompts (
    id UUID PRIMARY KEY,
    prompt_id VARCHAR(255) UNIQUE NOT NULL,
    original_prompt TEXT NOT NULL,
    improved_prompt TEXT,
    embedding_vector vector(1536),
    status VARCHAR(50) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

-- Vector 유사도 검색을 위한 인덱스
CREATE INDEX prompt_vector_idx ON prompts
    USING ivfflat (embedding_vector vector_l2_ops)
WITH (lists = 100);

1.2 Vector 데이터 처리를 위한 JPA 구현

@Entity
@Table(name = "prompts")
public class Prompt {
    @Type(JsonBType.class)
    @Column(columnDefinition = "jsonb")
    private Map<String, Object> configuration;

    @Column(name = "embedding_vector", columnDefinition = "vector(1536)")
    private double[] embeddingVector;
}

1.3 Custom Vector Repository 구현

@Repository
@RequiredArgsConstructor
public class CustomVectorRepository {
    private final JdbcTemplate jdbcTemplate;

    public List<Prompt> findSimilarPrompts(double[] vector, double threshold, int limit) {
        String sql = """
            SELECT * FROM prompts
            WHERE embedding_vector IS NOT NULL
            AND 1 - (embedding_vector <=> cast(? as vector(1536))) > ?
            ORDER BY 1 - (embedding_vector <=> cast(? as vector(1536))) DESC
            LIMIT ?
            """;

        String vectorStr = vectorToString(vector);
        return jdbcTemplate.query(sql, promptRowMapper,
                vectorStr, threshold, vectorStr, limit);
    }

    public void savePromptWithVector(Prompt prompt) {
        String vectorStr = vectorToString(prompt.getEmbeddingVector());

        String sql = String.format("""
            INSERT INTO prompts (
                id, prompt_id, embedding_vector, status, created_at, updated_at
            ) VALUES (?, ?, %s::vector(1536), ?, ?, ?)
            """,
            vectorStr == null ? "NULL" : "'" + vectorStr + "'");

        // ... insert 로직
    }
}

2. 프로젝트에서 PGVector를 선택한 이유

  1. 통합 데이터 관리
    • 별도의 벡터 데이터베이스 불필요
    • 트랜잭션 일관성 보장
    • 운영 복잡도 감소
  2. -- 하나의 데이터베이스에서 모든 데이터 관리 CREATE TABLE prompts ( id UUID PRIMARY KEY, embedding_vector vector(1536), -- 다른 비즈니스 데이터들 );
  3. 성능적 이점
    • 효율적인 벡터 검색
    • PostgreSQL의 강력한 인덱싱 활용"
  4. CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);

Vector 데이터의 저장과 검색 구현

  1. 데이터 저장
  2. public void savePromptWithVector(Prompt prompt) { String vectorStr = vectorToString(prompt.getEmbeddingVector()); // 벡터 데이터를 PostgreSQL vector 타입으로 변환하여 저장 } private String vectorToString(double[] vector) { if (vector == null) return null; StringBuilder sb = new StringBuilder("["); for (int i = 0; i < vector.length; i++) { if (i > 0) sb.append(","); sb.append(String.format("%.8f", vector[i])); } sb.append("]"); return sb.toString(); }
  3. 유사도 검색
  4. public List<Prompt> findSimilarPrompts(double[] vector, double threshold, int limit) { String sql = """ SELECT * FROM prompts WHERE 1 - (embedding_vector <=> cast(? as vector(1536))) > ? ORDER BY 1 - (embedding_vector <=> cast(? as vector(1536))) DESC """; // 코사인 유사도 기반 검색 구현 } ```"

PGVector 사용 시 성능 최적화

  1. 인덱스 최적화
    • IVFFlat 인덱스 사용
    • 적절한 lists 파라미터 설정
  2. CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);
  3. 배치 처리
    • 대량 데이터 처리 최적화
    • 트랜잭션 관리"
  4. @Transactional public void savePromptBatch(List<Prompt> prompts) { // 벡터 데이터 일괄 처리 for (List<Prompt> batch : Lists.partition(prompts, 1000)) { processBatch(batch); } }

Vector 데이터의 백업과 복구 전략

  1. 백업 구현
    • 일반 PostgreSQL 데이터와 동일한 백업 프로세스
    • vector 확장 상태 포함
  2. -- PostgreSQL의 기본 백업 메커니즘 활용 CREATE EXTENSION IF NOT EXISTS vector; -- vector 확장이 포함된 백업 수행
  3. 데이터 정합성
    • 주기적 데이터 검증
    • 필요시 재생성 로직"
  4. @Scheduled(cron = "0 0 2 * * *") // 매일 새벽 2시 public void validateVectorData() { // 벡터 데이터 검증 및 복구 }

PGVector와 OpenAI API 통합

  1. 벡터 크기 호환성
    • text-embedding-3-small 모델 사용
    • 1536 차원 벡터 저장
  2. // OpenAI 임베딩 모델의 출력 차원과 일치 @Column(name = "embedding_vector", columnDefinition = "vector(1536)") private double[] embeddingVector;
  3. 비동기 처리
    • 효율적인 API 호출
    • 벡터 생성 병렬화"
  4. CompletableFuture<double[]> embeddingFuture = openAiApi.embeddingsAsync(prompt.getOriginalPrompt());

PGVector 관련 문제 해결 경험

  1. 메모리 관리
    • 대용량 벡터 데이터 처리
    • 인덱스 생성 최적화
  2. SET maintenance_work_mem = '1GB'; SET max_parallel_maintenance_workers = 4;
  3. 성능 모니터링
    • 쿼리 실행 계획 분석
    • 성능 병목 지점 식별"
  4. public void monitorVectorOperations() { // 벡터 연산 성능 모니터링 // 인덱스 사용 현황 추적 }

'데이터베이스 & ORM' 카테고리의 다른 글

비관적 락(Pessimistic Lock)  (1) 2024.11.22
REPEATABLE READ  (0) 2024.11.15
Row-Level Lock  (1) 2024.11.12
VACUUM ANALYZE  (1) 2024.11.11
PostgreSQL GIN  (5) 2024.11.10