Post

02 VectorDB

02 VectorDB

1. Note

  • 벡터 디비의 흐름!
    • VectorDB에는 Context형태로 저장하는건 맞고
    • metaData로 필터해서 embedding 데이터를 끄집어내서
    • 나온 chunck들을 모은뒤에
    • LLM에 한번더 보내서 그 서류들을 바탕으로 답변하는 패턴을 취함.
  • LLM은 저정할때와 가공할때만 사용되고
  • 나머지는 VectorDB에서 사용
  • Vector_Store는 공통으로 써도 무관함
    • metaData로 내가 원하는 데이터를 찾을 수 있기 때문에 모아서 해도 되기는함!
  • ☆ VectorDB ☆
    • SpartaNote02 - Vector DB < 기본 개념과 흐름 >
    • SpartaWIL04 - 02pgvector_기본 패턴 < 사용법 / 기본 패턴 파악 >
    • SpartaWIL04 - 03pgvector_실무적인 접근 < 실무 패턴 >

1. VectorDB 사용 전체 흐름

1. 원본 문서 (PDF, DB 데이터, API 응답 등) 저장

  • 텍스트 추출 (필요에 따라서 AP에서 선별해서 넣기도 함)
  • chunk 단위로 분할 (데이터를 한번에 넣으면 추후에 통으로 읽어야하므로 청크화)
  • 임베딩 생성 ( 데이터 임베딩화 )
  • DB 저장 (vector + metadata)

    1
    2
    3
    4
    5
    6
    7
    
    CREATE TABLE vector_documents (
      id UUID PRIMARY KEY, -- UUID
      content TEXT, - 원본 문서
      embedding VECTOR(1536), -- 임베딩화 된 데이터
      metadata JSONB, -- JSON 데이터
      created_at TIMESTAMP -- 생성일
    );
    

2. 검색 (Retrieval)

  • 사용자 질문 → 임베딩 생성
  • pgvector cosine similarity 검색
  • topK 결과 가져오기
  • metadata 기반 필터링

3. 응답 생성 (RAG)

  • 검색 결과 + 질문 → LLM에 전달
  • 최종 답변 생성

2. PgVector 관련 문법

1. Where 관련

1
2
3
4
5
  WHERE metadata->>'documentId' = '123'
  -- "->>" 로 JSON 값을 "문자열"로 꺼냄
  
  WHERE (metadata->>'chunkIndex')::int > 5
  -- 값 비교(타입 변경 필요)       

2. 벡터 검색 + WHERE + ORDER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  SELECT *
  FROM vector_documents
  WHERE metadata->>'category' = 'backend'
  ORDER BY embedding <-> :queryVector 
  LIMIT 5;
  
  
  ORDER BY embedding <->
  -- 유사도 정렬       
   
  :queryVector
  -- 사용자 입력값
  -- Service에서 " float[] queryVector = embeddingClient.embed("스프링 트랜잭션이 뭐야?"); " 요런값
  
  LIMIT
  -- topK

3. metadata (필수 작업)

1. JSON형태로 데이터에 관한 정보를 저장함.

1
2
3
4
5
6
7
8
9
   {
     "documentId": "123", 
     "fileName": "spring-guide.pdf",
     "page": 5,
     "chunkIndex": 2,
     "category": "backend",
     "author": "admin",
     "createdAt": "2026-04-04"
   }

2. 특정 metaData 기준 조회

1
2
3
4
5
6
7
8
   SELECT *
   FROM vector_documents
   WHERE metadata->>'category' = 'backend'
   ORDER BY embedding <-> :queryVector
   LIMIT 5;
   
   -- WHERE metadata->>'team' = 'A' => teamA의 데이터를 조회한다
   -- WHERE metadata->>'documentId' = '123' => 도큐먼트Id관리하고 있다면 서류 123번 조회  

4. Return 값

1. select * from ~

1
2
3
4
5
  id: 550e8400-e29b-41d4-a716-446655440000
  content: "스프링 트랜잭션은 데이터의 일관성을 보장하기 위한..."
  embedding: [0.123, -0.532, 0.923, ...]
  metadata: {"documentId":"123","chunkIndex":0,"category":"backend"}
  created_at: 2026-04-04 10:00:00

2. DTO 결과값

1. DTO

1
2
3
4
5
6
class VectorDocument {
    UUID id;
    String content;
    float[] embedding;
    Map<String, Object> metadata;
}

2. 리턴 방식

1
2
3
4
5
6
7
8
9
 // select * from 한 값 5개가 VectorDocument에 쌓임
 List<VectorDocument> results = ~~;
 
 // 실무적으로 어차피 비슷한내용 합쳐서 LLM에서 한번더 가공해야하니
 // context로 한번에 받게됨
 // 그냥 사용해도 문제는 없음!
 String context = results.stream()
    .map(r -> r.getContent())
    .collect(Collectors.joining("\n"));
This post is licensed under CC BY 4.0 by the author.