--- tags: [infra, database, postgres, pgvector, search, graph, plans] --- # 260317 non-RDB 계층 PostgreSQL 통일 계획 **상태**: closed ## 상위 원칙 - [Infra Project Identity](../../00_Philosophy/00_IDENTITY/Infra_Project_Identity.md) - [Core Infrastructure Principles](../../00_Philosophy/01_PRINCIPLES/Core_Infrastructure_Principles.md) - [Operational Guardrails](../../00_Philosophy/02_GUARDRAILS/Operational_Guardrails.md) - 공통 작성 원칙: [0_VALUE Writing Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/writing-principles.md) ## 관련 문서 - [Infra Journey](../README.md) - [PostgreSQL 통합 vs 전용 DB 구조성능 리서치](../research/260316_postgres_통합_vs_전용db_구조성능_리서치.md) - [모든 DB를 PostgreSQL로 통일하는 아이디어](../ideas/260316_모든db를_postgresql로_통일하는_아이디어.md) - [non-RDB 계층 PostgreSQL 통일 1차 실행 완료](../worklog/260317_nonrdb_계층_postgresql_통일_1차_실행완료.md) - [PostgreSQL Neo4j TCP healthcheck](../troubleshooting/260115_postgresql_neo4j_tcp_healthcheck.md) - [백엔드: PostgreSQL, ChromaDB, Vector Memory 설계](https://github.com/happybell80/robeing/blob/main/DOCS/book/300_architecture/330_%EB%B0%B1%EC%97%94%EB%93%9C_PostgreSQL_ChromaDB_Vector_Memory.md) - [Phase 2: ChromaDB + Neo4j 하이브리드 기억 회상 시스템 구현](https://github.com/happybell80/robeing/blob/main/DOCS/journey/troubleshooting/251016_phase2_hybrid_memory_implementation.md) ## 문제 정의 - 현재 운영 구조는 관계형 데이터는 PostgreSQL, 벡터는 ChromaDB, 관계 추론은 Neo4j, 검색은 별도 전용 계층 가능성을 함께 열어 둔 하이브리드 상태다. - 이 구조는 기능 분화 측면에서는 설명 가능하지만, 운영 기준으로는 백업, 복구, 권한, 런타임 SSOT, 장애 추적, 데이터 정합성 경로를 여러 저장소에 분산시킨다. - 이번 계획의 목표는 `벡터`, `검색`, `그래프` 계층을 PostgreSQL로 통일하고, 현 단계의 전용 DB를 제거 가능한 상태까지 실제 실행 순서를 고정하는 것이다. ## 범위 ### 포함 - ChromaDB가 맡고 있는 벡터/메모리/RAG 저장 구조를 PostgreSQL `pgvector` 기반으로 재설계 - Neo4j가 맡고 있거나 맡을 예정인 관계 추론 구조를 PostgreSQL 관계 모델 + `WITH RECURSIVE` 중심으로 재설계 - 검색 전용 계층 후보를 PostgreSQL `FTS + pg_trgm + jsonb_to_tsvector` 구조로 흡수 - 운영 기준 SSOT를 PostgreSQL 중심 백업/복구/모니터링 구조로 재정렬 ### 제외 - 지금 단계에서 Redis까지 함께 없애는 캐시 전략 전체 재설계 - 대규모 분산 클러스터링, 샤딩, 멀티리전 설계 - 구현 완료 보고와 성능 확정 수치 기록 ## 결정 - 기본 저장소 전략은 `PostgreSQL only until proven otherwise`로 고정한다. - 현 단계의 non-RDB 기능은 전용 DB 존치를 기본값으로 두지 않고 PostgreSQL 흡수를 기본값으로 둔다. - 검색 해법은 `FTS + pg_trgm + metadata filter + vector hybrid rank` 조합을 기본으로 한다. - 그래프 해법은 `adjacency table + recursive query`를 기본 모델로 삼고, 필요 시 `ltree`를 보조 선택지로 쓴다. - 벡터 해법은 `pgvector`를 기본으로 하고, 인덱스는 `HNSW`를 우선 후보로 검토한다. - 전환 우선순위는 `ChromaDB -> 검색 계층 -> Neo4j` 순서로 둔다. - 이유: 현재 벡터 계층은 이미 임베딩 SSOT와 직접 연결돼 있고, 검색은 PostgreSQL 기능만으로 빠르게 흡수 가능하며, 그래프는 모델 재설계 논의가 가장 많이 필요하다. - 이번 계획에서 제외하는 경계도 고정한다. - `Redis 전체 캐시 전략`은 별도 주제로 남긴다. - 이번 계획의 닫힘은 `ChromaDB`, `Neo4j`, 검색 전용 저장소 제거 기준으로 판단한다. ## 목표 상태 ### 1. 벡터 - 문서/메모리/질의 임베딩은 PostgreSQL `pgvector(768)` 컬럼으로 저장한다. - tenant 분리는 별도 컬렉션이 아니라 `tenant_id` 또는 동등한 범위 키로 관리한다. - 메타데이터 필터, 최근성, 중요도, 사용자 범위는 SQL 조건으로 함께 평가한다. #### 벡터 테이블 초안 ```sql create table memory_vectors ( id uuid primary key, tenant_id uuid not null, source_type text not null, source_id uuid not null, content text not null, embedding vector(768) not null, metadata jsonb not null default '{}'::jsonb, importance numeric(6,3), created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); ``` ### 2. 검색 - 문서 본문, 요약, 태그, metadata jsonb는 PostgreSQL 내부에서 색인한다. - 정확 키워드/구문은 FTS, 오타/부분일치는 `pg_trgm`, 의미 유사도는 `pgvector`로 처리한다. - 최종 검색 랭킹은 SQL 안에서 결합 가능하도록 정의한다. #### 검색 테이블 초안 ```sql create table search_documents ( id uuid primary key, tenant_id uuid not null, title text, content text not null, summary text, tags text[] not null default '{}', metadata jsonb not null default '{}'::jsonb, search_tsv tsvector, embedding vector(768), created_at timestamptz not null default now() ); ``` ### 3. 그래프 - 사건, 감정, 결과, 참여자 관계는 PostgreSQL 테이블과 edge 구조로 저장한다. - 관계 점수 계산은 SQL 또는 DB 인접 계층 서비스 로직으로 계산하되 저장소는 PostgreSQL 하나로 고정한다. - bounded-depth traversal과 cycle 방지는 `WITH RECURSIVE`, `SEARCH`, `CYCLE` 구문을 우선 사용한다. #### 그래프 테이블 초안 ```sql create table graph_nodes ( node_id uuid primary key, tenant_id uuid not null, node_type text not null, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now() ); create table graph_edges ( edge_id uuid primary key, tenant_id uuid not null, src_node_id uuid not null references graph_nodes(node_id), dst_node_id uuid not null references graph_nodes(node_id), edge_type text not null, weight numeric(6,3) not null default 1.0, payload jsonb not null default '{}'::jsonb, created_at timestamptz not null default now() ); ``` ## 실행 순서 ### 1. 벡터 저장 구조를 먼저 PostgreSQL로 확정한다 - `Chroma collection`의 공통 속성을 추출한다. - `tenant_id` - `document_id` - `content` - `embedding vector(768)` - `metadata jsonb` - `created_at` - `updated_at` - 저장 전략은 `global table + tenant key`를 기본값으로 둔다. - 인덱스 초안은 아래를 기본으로 둔다. - `embedding`용 HNSW - `tenant_id`, `created_at`, 주요 metadata key용 btree 또는 표현식 인덱스 - 필요 시 tenant 또는 도메인 단위 partition ### 2. 검색 스키마와 랭킹 공식을 고정한다 - 검색 대상 테이블은 `document_search` 같은 단일 검색 뷰 또는 검색 전용 테이블로 수렴시킨다. - 색인 필드는 최소 아래를 포함한다. - `title` - `content` - `summary` - `tags` - `metadata jsonb` - 랭킹은 아래 순서로 결합한다. - `ts_rank_cd(weighted_tsvector, query)` - `pg_trgm similarity` - 필요 시 `vector distance` - 최신성 또는 중요도 가중치 - 이 결합은 SQL 함수 또는 view로 감싼다. ### 3. 그래프 구조를 PostgreSQL 관계 모델로 재작성한다 - 기본 구조는 아래 두 테이블 계열로 시작한다. - `graph_nodes(node_id, node_type, payload jsonb, tenant_id, created_at)` - `graph_edges(edge_id, src_node_id, dst_node_id, edge_type, weight, payload jsonb, tenant_id, created_at)` - 사건-감정-결과 관계는 `edge_type`으로 표준화한다. - 회상과 추론은 bounded-depth recursive query로 먼저 구현한다. - 계층형 경로나 고정 깊이 탐색이 반복되면 `ltree` 또는 materialized path를 보조 채택한다. ### 4. 이관 단위를 서비스별로 나눈다 - 1차: `rb8001` 메모리 벡터와 recall - 2차: `skill-rag-file` 문서 벡터와 검색 - 3차: 검색 전용 API 또는 검색 보조 기능 - 4차: Neo4j 관계 추론 경로 - 각 단계는 `기존 읽기 경로 병행 -> PostgreSQL 결과 비교 -> 읽기 경로 전환 -> 기존 저장소 쓰기 중지 -> 기존 저장소 제거` 순으로 진행한다. ### 4-1. 대표 질의셋을 먼저 고정한다 - 벡터 질의셋: - 최근 기억 회상 - 사용자별 유사 대화 top-k - 메타데이터 필터 포함 회상 - 검색 질의셋: - 정확 키워드 - 두 단어 구문 검색 - 오타 1~2글자 포함 검색 - 태그/metadata 필터 포함 검색 - 그래프 질의셋: - 사건 -> 감정 -> 결과 연결 조회 - 최근 1년 bounded-depth 관계 회상 - 성공 사건 우선 랭킹 ### 5. 운영 기준을 PostgreSQL 중심으로 다시 묶는다 - 백업 기준은 PostgreSQL dump/physical backup을 우선 SSOT로 삼는다. - 모니터링은 `pg_stat_statements`, 인덱스 크기, bloat, query latency, vacuum 상태를 중심으로 본다. - 장애 복구 문서도 `Chroma/Neo4j 복구`가 아니라 `PostgreSQL 스키마/인덱스/데이터 복구` 중심으로 재작성한다. ## 검증 기준 ### 기능 검증 - ChromaDB가 제공하던 top-k 벡터 검색이 PostgreSQL `pgvector`에서 동일 tenant 범위로 재현된다. - 기존 검색 대표 질의셋이 `FTS + pg_trgm` 경로에서 허용 가능한 품질로 재현된다. - Neo4j에서 표현하던 사건-감정-결과 관계 질의가 PostgreSQL recursive query로 재현된다. ### 운영 검증 - 백업/복구 절차가 PostgreSQL 기준 1세트로 단순화된다. - 런타임 env와 문서 SSOT에서 ChromaDB, Neo4j, 검색 전용 저장소를 필수 의존성으로 더 이상 요구하지 않는다. - 서비스 로그에 전용 DB 연결 실패가 핵심 경로 장애 원인으로 남지 않는다. ### 닫힘 조건 - `rb8001`, `skill-rag-file` 기준 주요 벡터·검색·관계 기능이 PostgreSQL 경로로 동작한다. - 전용 DB 없이도 대표 사용자 시나리오가 재현된다. - 관련 문서의 아키텍처 설명이 `PostgreSQL + ChromaDB + Neo4j`에서 `PostgreSQL 중심 단일 구조`로 교체된다. - cut-over 후 worklog가 남고, 전용 DB 제거 또는 비활성화 근거가 기록된다. - 24 런타임과 배포 문서에서 `ChromaDB`, `Neo4j`, 검색 전용 저장소가 필수 서비스 목록에서 제거된다. ## 리스크와 대응 - 벡터 검색 품질 저하 가능성: - 동일 질의셋으로 ChromaDB와 PostgreSQL 결과를 병렬 비교한다. - 검색 랭킹 불안정 가능성: - 가중치 조정용 대표 질의셋을 먼저 고정한다. - 그래프 질의 복잡성 증가 가능성: - 무제한 탐색을 허용하지 않고 bounded-depth 시나리오만 먼저 지원한다. - 테이블 비대화 가능성: - tenant key, partial index, partitioning 후보를 초기에 같이 검토한다. ## 롤백 기준 - PostgreSQL 경로가 대표 질의셋에서 기존 결과 품질을 현저히 깨면 읽기 경로를 즉시 기존 저장소로 되돌린다. - PostgreSQL 전환 후 p95 응답 시간이 합의 기준을 넘기면 쓰기 중지는 유지하되 읽기 cut-over는 보류한다. - 롤백은 `읽기 경로 restore -> 기존 저장소 쓰기 재개 -> PostgreSQL 비교 로그 보존` 순서로 수행한다. ## 다음 단계로 넘길 결정 항목 - `memory_vectors`, `document_vectors`, `graph_nodes`, `graph_edges`, `search_documents` 같은 실제 테이블 명세 - HNSW 파라미터와 vacuum/maintenance 기준 - hybrid rank 공식 - 서비스별 cut-over 순서와 rollback 기준 ## 실행 결과 - 1차: `rb8001` 메모리 벡터와 recall은 PostgreSQL `memory_vectors`, `memory_graph_nodes`, `memory_graph_edges`로 전환 완료. - 2차: `skill-rag-file` 문서 벡터와 검색은 PostgreSQL `team_document_chunk` + `pgvector(768)`로 전환 완료. - 3차: `rb8001`의 잔여 Neo4j 런타임 경로였던 `coldmail memory`, `startup valuation comparable prior`, `valuation premia recalculation`도 PostgreSQL 경로로 전환 완료. - 24 배포는 `rb8001` Gitea Actions와 `skill-rag-file` 실배포 검증으로 반영됐고, 컨테이너 헬스체크와 실제 저장/검색/재계산 결과로 확인됐다. - 이 계획의 닫힘 조건은 [non-RDB 계층 PostgreSQL 통일 1차 실행 완료](../worklog/260317_nonrdb_계층_postgresql_통일_1차_실행완료.md)에서 충족 증거를 남겼다. ## 한 줄 결론 - 이번 계획의 핵심은 `전용 DB를 유지할 이유를 찾는 것`이 아니라, `벡터 -> 검색 -> 그래프` 순서로 non-RDB 계층을 PostgreSQL 하나로 흡수하는 실제 전환 순서를 고정하는 것이다.