단일 PostgreSQL 위에서 pgvector(의미 검색)와 Apache AGE(지식 그래프)를 동시에 운용하고, 문서 수집 시 통제된 온톨로지를 만들며, 검색 시 벡터 + 그래프를 융합(HybridRAG)하여 문서를 더 잘 찾는 지식관리 시스템의 기술 사상입니다.
설계 문서의 핵심 축을 한눈에 볼 수 있도록 구성했습니다.
세 가지 원칙이 전체 구현을 관통합니다.
| 원칙 | 내용 | 구현 반영 |
|---|---|---|
| Unified Data Plane | 벡터 DB와 그래프 DB를 쪼개지 않고, 하나의 PostgreSQL에서 관계형·벡터·그래프를 함께 다룬다. 이중 기록(dual-write)과 정합성 지연을 제거한다. | pgvector + Apache AGE 동일 인스턴스 |
| Ontology before Graph | LLM이 문서마다 임의 타입을 쏟아내는 것을 금지한다. 허용 목록(allowlist) 온톨로지로 추출하고, 모르는 타입은 검수 큐로 보낸다. | 2-tier ontology · closed-world IE |
| Hybrid Retrieval | 의미 검색만으로는 multi-hop 관계가 약하고, 그래프만으로는 fuzzy 매칭이 약하다. 둘을 진입→확장→융합한다. | BM25 + Dense + Graph + RRF |
| SQL SoT, Graph Projection | 권위 있는 원본은 SQL 테이블이다. AGE 그래프는 재구축 가능한 파생 투영(projection)이다. | entity_relations → AGE MERGE |
| LLM outside TX | 임베딩·추출·생성 등 느리고 비결정적인 LLM I/O는 DB 트랜잭션 밖에서 수행한다. 짧은 TX로 SQL+AGE만 기록한다. | ingest worker 단계 분리 |
단일 접근만으로는 기업 지식 검색이 자주 실패합니다.
| 접근 | 강점 | 약점 | Hybrid KMS의 대응 |
|---|---|---|---|
| Pure VectorRAG | 의미적으로 비슷한 문장·문서를 잘 찾음 | 명시적 관계·다단 추론 약함 | Stage A dense + Stage B graph expansion |
| Pure GraphRAG | 관계·multi-hop 질의에 강함 | 표현이 다른 동의 질의·신규 문서 커버리지 약함 | 벡터 진입점 후 그래프 확장 |
| 이종 스토어 분리 Vector DB + Neo4j 등 |
각 엔진 최적화 가능 | dual-write, consistency lag, ops 복잡도 | 단일 Postgres 트랜잭션 경계 |
| 무통제 LLM 추출 | 초기 커버리지 빠름 | ontology explosion (5문서 → 17 타입 등) | allowlist + candidate review queue |
설계 리뷰를 통과한 핵심 결정입니다. 구현 시 “왜 이렇게 했나”의 기준선입니다.
| ID | 결정 영역 | 선택 | 근거 |
|---|---|---|---|
| KD-1 | 데이터 플레인 | 단일 PostgreSQL + pgvector + AGE | 트랜잭션 일관성, ops 단순, 교차 지연 제거 |
| KD-2 | 그래프 언어 | Apache AGE openCypher | 표준 Cypher DX, PG 내장 |
| KD-3 | 온톨로지 | 2-tier (Core + Domain 검수) | ontology explosion 방지 |
| KD-4 | 정보 추출 | Closed-world first, open → review queue | 품질 vs 커버리지 균형 |
| KD-5 | 결과 융합 | Weighted RRF (k=60) | 점수 스케일 불일치에 강건 |
| KD-6 | 검색 단계 | A→B→C→D | 지연 예산 분리·점진 고도화 |
| KD-7 | 엔티티 저장 | SQL SoT / AGE projection | 재구축·ACL·감사 가능 |
| KD-8 | 벡터 인덱스 | HNSW + ACL oversample | ANN 속도 + post-filter recall 보호 |
| KD-9 | Entity resolution | exact/alias → trgm+cosine | 결정적·테스트 가능 병합 |
| KD-10 | Cypher 안전 | allowlist + agtype 파라미터 | injection 방지 |
| KD-11 | 멀티테넌시 | v1 single-tenant + document ACL | 초기 복잡도 억제 |
| KD-14 | 버전 범위 | current_version + active only | stale chunk 오염 방지 |
| KD-15 | 인증 | API Key → principal | 빠른 착수, OIDC 교체 가능 |
| KD-16 | 청킹 | 512 tokens / overlap 12% | 재현 가능한 기본값 |
| KD-17 | 한국어 lexical | simple FTS + dense 가중 우세 | 형태소 사전 없이도 hybrid 품질 |
클라이언트 → API → 수집/검색 서비스 → 단일 Postgres 데이터 플레인.
| 구성요소 | 역할 | 기술 |
|---|---|---|
| API | 업로드, 검색, Q&A, 온톨로지, 첨부 | FastAPI + uvicorn |
| Worker | 비동기 파싱·임베딩·IE·그래프 기록 | DB job 폴링 워커 |
| Postgres | 단일 데이터 플레인 | PG17 + vector + AGE |
| Storage | 원본·parsed.md·표 CSV | 로컬 볼륨 (S3 교체 가능) |
| LLM (옵션) | IE 보강, Q&A 생성 | OpenAI / Claude(Anthropic) |
원본을 첨부파일로 보관하고, 정리된 텍스트·표를 만든 뒤 검색·그래프 인덱스를 구축합니다.
| 입력 | 파서 | 산출 첨부 | 검색 반영 |
|---|---|---|---|
| pdftotext + pypdf | original, parsed.md | 청크·임베딩·엔티티 | |
| DOCX | python-docx | + 표 CSV | 본문 + 표 마크다운 |
| PPTX | python-pptx | parsed.md | 슬라이드 단위 텍스트 |
| XLSX | openpyxl | 시트별 CSV | 시트 테이블 검색 |
| HWP/HWPX | olefile / zip+lxml | parsed.md | 검색용 텍스트 추출 |
| MD/TXT/HTML/CSV | 내장 | parsed.md / CSV | 동일 파이프라인 |
한 번의 질의에 세 경로를 돌리고, 순위를 RRF로 융합합니다.
| Stage | 이름 | 하는 일 | 왜 필요한가 |
|---|---|---|---|
| A | Lexical + Dense | tsvector BM25와 청크 벡터 ANN |
키워드 정확 매칭 + 의미 유사 매칭 |
| B | Graph expansion | 엔티티 링크 후 hop-0 직접 언급 + hop-1/2 이웃 청크 | 관계 기반 multi-hop 문맥 |
| C | RRF Fusion | 가중 RRF로 순위 합산, ACL/버전 재검증 | 이종 점수 스케일 정규화 |
| D | Q&A (옵션) | 상위 청크를 근거로 LLM 생성 | 설명형 답변 + citation |
| 소스 | 가중치 | 비고 |
|---|---|---|
| Dense | 1.1 | 한국어 lexical 한계 보완 |
| Graph | 1.0 | 정렬된 graph_score 순위만 투입 |
| BM25 | 0.7 | 정확한 키워드 신호 |
| RRF k | 60 | 표준 RRF 상수 |
그래프의 품질은 스키마 통제에서 결정됩니다.
| 티어 | 성격 | 변경 방식 |
|---|---|---|
| Core | Person, Organization, Policy, Technology… | 시드·신중 변경 |
| Domain | Service, Component, Incident… | 운영 중 확장 가능 |
| Candidates | LLM이 제안한 미등록 타입 | 검수 후 approve |
ontology_candidatesRELATED_TO는 last-resort (남용 방지)Entity + class 속성관계형으로 권위를 두고, 그래프·벡터는 인덱스로 취급합니다.
| 계층 | 저장소 | 역할 | 삭제/재처리 시 |
|---|---|---|---|
| Relational SoT | SQL 테이블 | 문서, 청크, ACL, 온톨로지, 멘션, 관계 | 권위 있는 상태 변경 |
| Vector index | pgvector | 청크/엔티티 의미 검색 | 재임베딩 backfill |
| Graph projection | Apache AGE | 경로 설명, multi-hop 탐색 보조 | SQL에서 rebuild 가능 |
| Object storage | 파일 볼륨 | 원본 첨부, parsed.md, 표 CSV | 버전 디렉터리 단위 관리 |
| 위협 | 대응 |
|---|---|
| 무단 문서 열람 | API Key → principal, document ACL, 검색 경로마다 visible_doc 필터 |
| Cypher/SQL injection | 라벨 allowlist, AGE 쿼리는 dollar-quote 템플릿 + agtype 파라미터 |
| 그래프 ACL 우회 | 그래프에 ACL 미저장; hydrate 시 SQL에서 재검증 |
| LLM 타입 오염 | closed-world 검증, candidate 검수 |
chunks.version = documents.current_version 이고
chunks.status = 'active' 인 행만 대상입니다.
재처리 시 이전 청크는 retired 되어 stale 결과가 섞이지 않습니다.
| 경로 | 목표 | 조건 |
|---|---|---|
| Hybrid 검색 A+B+C | p95 < 500ms | LLM 없음, 튜닝 후 |
| Q&A (Stage D 포함) | p95 < 5s | LLM 네트워크 포함 |
| 질문 | 이 시스템의 답 |
|---|---|
| 왜 Postgres 하나인가? | 벡터·그래프·메타를 한 트랜잭션으로 묶어 운영 비용을 낮춘다. |
| 왜 온톨로지를 강제하는가? | 검색 가능한 지식 그래프는 스키마 품질 없이는 오래 가지 못한다. |
| 왜 Hybrid 검색인가? | 의미(vector)와 관계(graph)는 서로 다른 실패 모드를 보완한다. |
| 왜 SQL이 SoT인가? | ACL, 재처리, 감사, 재구축을 결정적으로 수행할 수 있다. |
| 문서를 넣으면? | 원본 첨부 + 파싱 정리본 + 청크 벡터 + 온톨로지 엔티티/관계가 쌓인다. |
| 검색하면? | BM25·Dense·Graph 순위를 RRF로 합쳐 더 잘 찾는다. |
구현 코드: /srv/sites/kms.engineerstory.kr ·
마크다운 설계서: docs/01-hybrid-kms-architecture.md ·
앱: https://kms.engineerstory.kr/ ·
매뉴얼: /manual ·
분석 요약: /summary ·
API: /api/docs