데이터베이스 & 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를 선택한 이유
- 통합 데이터 관리
- 별도의 벡터 데이터베이스 불필요
- 트랜잭션 일관성 보장
- 운영 복잡도 감소
- -- 하나의 데이터베이스에서 모든 데이터 관리 CREATE TABLE prompts ( id UUID PRIMARY KEY, embedding_vector vector(1536), -- 다른 비즈니스 데이터들 );
- 성능적 이점
- 효율적인 벡터 검색
- PostgreSQL의 강력한 인덱싱 활용"
- CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);
Vector 데이터의 저장과 검색 구현
- 데이터 저장
- 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(); }
- 유사도 검색
- 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 사용 시 성능 최적화
- 인덱스 최적화
- IVFFlat 인덱스 사용
- 적절한 lists 파라미터 설정
- CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);
- 배치 처리
- @Transactional public void savePromptBatch(List<Prompt> prompts) { // 벡터 데이터 일괄 처리 for (List<Prompt> batch : Lists.partition(prompts, 1000)) { processBatch(batch); } }
Vector 데이터의 백업과 복구 전략
- 백업 구현
- 일반 PostgreSQL 데이터와 동일한 백업 프로세스
- vector 확장 상태 포함
- -- PostgreSQL의 기본 백업 메커니즘 활용 CREATE EXTENSION IF NOT EXISTS vector; -- vector 확장이 포함된 백업 수행
- 데이터 정합성
- @Scheduled(cron = "0 0 2 * * *") // 매일 새벽 2시 public void validateVectorData() { // 벡터 데이터 검증 및 복구 }
PGVector와 OpenAI API 통합
- 벡터 크기 호환성
- text-embedding-3-small 모델 사용
- 1536 차원 벡터 저장
- // OpenAI 임베딩 모델의 출력 차원과 일치 @Column(name = "embedding_vector", columnDefinition = "vector(1536)") private double[] embeddingVector;
- 비동기 처리
- CompletableFuture<double[]> embeddingFuture = openAiApi.embeddingsAsync(prompt.getOriginalPrompt());
PGVector 관련 문제 해결 경험
- 메모리 관리
- SET maintenance_work_mem = '1GB'; SET max_parallel_maintenance_workers = 4;
- 성능 모니터링
- public void monitorVectorOperations() { // 벡터 연산 성능 모니터링 // 인덱스 사용 현황 추적 }