From 714adeb59a4ffb115046638c9f101e953055a75a Mon Sep 17 00:00:00 2001 From: happybell80 Date: Tue, 17 Mar 2026 00:13:54 +0900 Subject: [PATCH] Strengthen PostgreSQL consolidation docs --- ...postgresql로_통일하는_아이디어.md | 5 ++ ..._nonrdb_계층_postgresql_통일_계획.md | 83 +++++++++++++++++++ ...합_vs_전용db_구조성능_리서치.md | 12 +++ 3 files changed, 100 insertions(+) diff --git a/journey/ideas/260316_모든db를_postgresql로_통일하는_아이디어.md b/journey/ideas/260316_모든db를_postgresql로_통일하는_아이디어.md index e374631..c089fba 100644 --- a/journey/ideas/260316_모든db를_postgresql로_통일하는_아이디어.md +++ b/journey/ideas/260316_모든db를_postgresql로_통일하는_아이디어.md @@ -20,6 +20,7 @@ tags: [infra, database, postgres, ideas, consolidation] - 지금 인프라와 서비스 계층에는 `PostgreSQL`, `ChromaDB`, `Neo4j`, 경우에 따라 `Redis/검색 전용 계층`처럼 역할이 나뉘는 구조가 남아 있다. - 이 구조는 각 역할에 최적화된 장점이 있지만, 운영자 입장에서는 백업, 장애 추적, 권한, 런타임 SSOT, 복구 절차, 데이터 정합성 관리가 분산된다. - 특히 팀 규모가 작고 서비스 경계가 자주 바뀌는 단계에서는 `DB를 여러 개 운영하는 비용`이 기능 이점보다 더 크게 느껴질 수 있다. +- 이번 아이디어의 직접 대상은 `ChromaDB`, `Neo4j`, 검색 전용 저장소 같은 non-RDB 계층이며, `Redis 전체 캐시 전략`은 지금 문서의 범위에 넣지 않는다. ## 현재상태 -> 기대상태 @@ -55,6 +56,7 @@ tags: [infra, database, postgres, ideas, consolidation] - 서비스 간 데이터 동기화와 이중 저장 부담이 줄어든다. - `workspace-config`와 런타임 SSOT 해석도 더 직접적이 된다. - 운영자가 장애 시 봐야 하는 저장소 종류와 복구 절차가 줄어든다. +- `rb8001`, `skill-rag-file` 같은 실제 경로에서 `PostgreSQL만 보면 된다`는 운영 해석이 가능해진다. ## 검증이 필요한 이유 @@ -73,6 +75,9 @@ tags: [infra, database, postgres, ideas, consolidation] - 이 아이디어는 검토 대상이 아니라 현재 방향으로 고정할 수 있다. - 현 단계 목표는 `전용 DB와 non-RDB 저장소를 유지할 이유를 찾는 것`이 아니라 `PostgreSQL 통일 경로를 설계하는 것`이다. - 실행 순서와 제거 대상 우선순위는 [non-RDB 계층 PostgreSQL 통일 계획](../plans/260317_nonrdb_계층_postgresql_통일_계획.md)으로 넘긴다. +- 경계도 같이 고정한다. + - 지금 없애는 대상: `ChromaDB`, `Neo4j`, 검색 전용 저장소 + - 지금 보류하는 대상: `Redis 전체 캐시 전략` - 현재 시점의 해석은 아래 문장으로 고정한다. `인프라의 벡터, 그래프, 검색 계층은 지금부터 PostgreSQL로 통일하는 방향으로 정리하고, 전용 DB는 현 단계에서 제거 대상으로 본다.` diff --git a/journey/plans/260317_nonrdb_계층_postgresql_통일_계획.md b/journey/plans/260317_nonrdb_계층_postgresql_통일_계획.md index 48956a6..20b0901 100644 --- a/journey/plans/260317_nonrdb_계층_postgresql_통일_계획.md +++ b/journey/plans/260317_nonrdb_계층_postgresql_통일_계획.md @@ -50,6 +50,9 @@ tags: [infra, database, postgres, pgvector, search, graph, plans] - 벡터 해법은 `pgvector`를 기본으로 하고, 인덱스는 `HNSW`를 우선 후보로 검토한다. - 전환 우선순위는 `ChromaDB -> 검색 계층 -> Neo4j` 순서로 둔다. - 이유: 현재 벡터 계층은 이미 임베딩 SSOT와 직접 연결돼 있고, 검색은 PostgreSQL 기능만으로 빠르게 흡수 가능하며, 그래프는 모델 재설계 논의가 가장 많이 필요하다. +- 이번 계획에서 제외하는 경계도 고정한다. + - `Redis 전체 캐시 전략`은 별도 주제로 남긴다. + - 이번 계획의 닫힘은 `ChromaDB`, `Neo4j`, 검색 전용 저장소 제거 기준으로 판단한다. ## 목표 상태 @@ -59,18 +62,75 @@ tags: [infra, database, postgres, pgvector, search, graph, plans] - 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로 확정한다 @@ -122,6 +182,22 @@ tags: [infra, database, postgres, pgvector, search, graph, plans] - 4차: Neo4j 관계 추론 경로 - 각 단계는 `기존 읽기 경로 병행 -> PostgreSQL 결과 비교 -> 읽기 경로 전환 -> 기존 저장소 쓰기 중지 -> 기존 저장소 제거` 순으로 진행한다. +### 4-1. 대표 질의셋을 먼저 고정한다 + +- 벡터 질의셋: + - 최근 기억 회상 + - 사용자별 유사 대화 top-k + - 메타데이터 필터 포함 회상 +- 검색 질의셋: + - 정확 키워드 + - 두 단어 구문 검색 + - 오타 1~2글자 포함 검색 + - 태그/metadata 필터 포함 검색 +- 그래프 질의셋: + - 사건 -> 감정 -> 결과 연결 조회 + - 최근 1년 bounded-depth 관계 회상 + - 성공 사건 우선 랭킹 + ### 5. 운영 기준을 PostgreSQL 중심으로 다시 묶는다 - 백업 기준은 PostgreSQL dump/physical backup을 우선 SSOT로 삼는다. @@ -148,6 +224,7 @@ tags: [infra, database, postgres, pgvector, search, graph, plans] - 전용 DB 없이도 대표 사용자 시나리오가 재현된다. - 관련 문서의 아키텍처 설명이 `PostgreSQL + ChromaDB + Neo4j`에서 `PostgreSQL 중심 단일 구조`로 교체된다. - cut-over 후 worklog가 남고, 전용 DB 제거 또는 비활성화 근거가 기록된다. +- 24 런타임과 배포 문서에서 `ChromaDB`, `Neo4j`, 검색 전용 저장소가 필수 서비스 목록에서 제거된다. ## 리스크와 대응 @@ -160,6 +237,12 @@ tags: [infra, database, postgres, pgvector, search, graph, plans] - 테이블 비대화 가능성: - tenant key, partial index, partitioning 후보를 초기에 같이 검토한다. +## 롤백 기준 + +- PostgreSQL 경로가 대표 질의셋에서 기존 결과 품질을 현저히 깨면 읽기 경로를 즉시 기존 저장소로 되돌린다. +- PostgreSQL 전환 후 p95 응답 시간이 합의 기준을 넘기면 쓰기 중지는 유지하되 읽기 cut-over는 보류한다. +- 롤백은 `읽기 경로 restore -> 기존 저장소 쓰기 재개 -> PostgreSQL 비교 로그 보존` 순서로 수행한다. + ## 다음 단계로 넘길 결정 항목 - `memory_vectors`, `document_vectors`, `graph_nodes`, `graph_edges`, `search_documents` 같은 실제 테이블 명세 diff --git a/journey/research/260316_postgres_통합_vs_전용db_구조성능_리서치.md b/journey/research/260316_postgres_통합_vs_전용db_구조성능_리서치.md index 16e75e1..bc7ec16 100644 --- a/journey/research/260316_postgres_통합_vs_전용db_구조성능_리서치.md +++ b/journey/research/260316_postgres_통합_vs_전용db_구조성능_리서치.md @@ -100,18 +100,29 @@ tags: [infra, database, postgres, redis, pgvector, elasticsearch, neo4j, researc - 로빙 계열 벡터 검색은 메타데이터 필터, 사용자 단위 분리, top-k 검색, 기억 회상, 파일/RAG 결합처럼 관계형 데이터와 함께 움직이는 비중이 크다. - 이 유형은 별도 벡터 DB보다 `PostgreSQL + pgvector + 관계형 필터`가 오히려 자연스럽다. - 실제 남는 과제는 `HNSW/IVFFlat 선택`, `ef_search/iterative_scan`, `부분 인덱스/파티셔닝`, `halfvec/압축 여부` 같은 튜닝이지, 기능 부재가 아니다. +- 대표 질의 형태도 이미 PostgreSQL 친화적이다. + - `tenant_id = ? AND category = 'memory' ORDER BY embedding <=> :query LIMIT 10` + - `tenant_id = ? AND created_at >= now() - interval '30 days' ORDER BY embedding <=> :query LIMIT 5` ### 4. 검색은 `FTS + pg_trgm` 조합으로 먼저 닫을 수 있다 - 사용자가 기대하는 검색은 보통 `정확 키워드`, `구문`, `웹검색식 입력`, `오타/부분일치`, `메타데이터 필터`, `랭킹`의 조합이다. - PostgreSQL은 `phraseto_tsquery`, `websearch_to_tsquery`, `setweight`, `jsonb_to_tsvector`, `pg_trgm` similarity를 모두 공식 기능으로 제공한다. - 따라서 현재 단계의 검색 통일은 `Elasticsearch 대체 불가 여부`를 묻기보다 `PostgreSQL 안에서 검색 스키마와 인덱스를 어떻게 짤지`를 묻는 단계로 보는 것이 맞다. +- 대표 질의 형태는 아래처럼 바로 그릴 수 있다. + - `WHERE search_tsv @@ websearch_to_tsquery('simple', :q)` + - `ORDER BY ts_rank_cd(search_tsv, websearch_to_tsquery('simple', :q)) DESC, similarity(title, :q) DESC` + - metadata 필터는 `metadata->>'source' = 'companyx'` 같은 SQL 조건으로 결합 가능하다. ### 5. 그래프는 `무제한 자유 탐색`이 아니라 `경계 있는 관계 탐색`으로 재정의하면 PostgreSQL로 수렴시킬 수 있다 - PostgreSQL의 recursive CTE로 그래프성 질의를 구현할 수는 있다. - `SEARCH`와 `CYCLE` 구문, path array, `ltree` 같은 도구를 쓰면 bounded-depth traversal, 계층형 경로, 관계 추적은 공식 SQL 범위 안에서 구현할 수 있다. - 현재 로빙 문서에 적힌 Neo4j 용도도 초거대 공개 그래프 탐색이 아니라 `사건-감정-결과 관계 가중치`와 같은 제한된 관계망이므로, 현 단계에서는 PostgreSQL 모델 재설계로 충분히 흡수 가능한 범주에 가깝다. +- 대표 질의도 `3단계 이하 관계 회상`에 가깝다. + - `특정 사건과 감정/결과로 연결된 최근 사건 찾기` + - `사용자별 최근 1년 유사 사건 경로 찾기` + - `성공 결과가 붙은 사건을 우선 랭킹하기` ### 6. 이 아이디어를 닫는 데 남은 미확정은 `가능한가`가 아니라 `어떻게 옮길까`에 가깝다 @@ -125,6 +136,7 @@ tags: [infra, database, postgres, redis, pgvector, elasticsearch, neo4j, researc - `ChromaDB collection -> PostgreSQL table` 매핑 단위를 `tenant per table`, `global table + tenant key`, `partition per tenant` 중 무엇으로 잡을지 결정이 아직 없다. - Neo4j의 `Event-Emotion-Result` 그래프를 `adjacency table`, `materialized path`, `ltree`, `jsonb edge payload` 중 어떤 방식으로 표현할지 결정이 아직 없다. - 검색 쪽도 `문서 원문`, `요약문`, `태그`, `metadata jsonb`에 어떤 가중치를 줄지와 `FTS + pg_trgm + vector hybrid rank` 공식을 아직 정하지 않았다. +- 내부 대표 질의셋 10~20개를 무엇으로 고정할지 아직 없다. - 즉 이 주제의 다음 단계는 추가 일반론 리서치가 아니라 [non-RDB 계층 PostgreSQL 통일 계획](../plans/260317_nonrdb_계층_postgresql_통일_계획.md) 기준의 `스키마/인덱스/랭킹 설계 실행`이다. ## 한 줄 결론