Skip to content

DB 전략 — 트랜잭션 이중 지원 + 벡터 저장소 분리 ​

작성 시각: 2026-09-13 확정 결정: 트랜잭션(업무) 데이터는 MySQL·PostgreSQL 이중 지원을 유지하고, 벡터 검색(RAG)만 PostgreSQL(pgvector) 또는 외부 벡터 저장소로 분리한다.

1. 왜 이렇게 나누는가 ​

  • 엔터프라이즈 고객은 기존 DB 표준(MySQL 또는 PostgreSQL)을 바꾸지 않는다 → 업무 DB 고정은 수주 장애물이다.
  • 벡터 검색은 "기능 스토리지"라 고객 환경에 맞춰 교체할 수 있다(pgvector ↔ 외부 벡터 DB).
  • AI 완성(요약·번역·정합성 텍스트 분석)은 DB를 쓰지 않는다(텍스트만 LLM에 전달) → DB 중립.

2. 지원 매트릭스 ​

계층예시MySQLPostgreSQL근거
트랜잭션/업무프로세스·전결 규정·알림·사용자✅✅drizzle/schema.mysql.ts + schema.pg.ts + server/db-connection.ts dialect helper
AI 완성게시본 요약, 자동 번역, 정합성(텍스트 비교)✅✅aiCompletionEnabled() = PRIVATE_LLM_URL 설정
벡터 검색(RAG)유사 문서·질의응답·의미 중복 탐지⚠️✅vectorSearchEnabled() = RAG_ENABLED + postgresql + PRIVATE_EMBEDDING_URL, 또는 외부 벡터 저장소 주입

⚠️ MySQL에서 벡터 검색을 쓰려면 외부 벡터 저장소(Qdrant·Milvus·Elasticsearch kNN 등)를 setVectorStore()로 주입한다. pgvector의 <=>·::vector·ANN 인덱스에 해당하는 기능이 MySQL에는 없다.

3. capability 플래그 (server/ai/aiClient.ts) ​

함수조건대상 기능
aiCompletionEnabled()PRIVATE_LLM_URL 설정요약·번역·정합성 등 LLM 완성 계열
isPostgresDialect()DATABASE_DIALECT=postgresql벡터 검색의 전제 조건
vectorSearchEnabled()RAG_ENABLED=true ∧ postgresql ∧ PRIVATE_EMBEDDING_URLRAG·유사도 계열

AI 완성 기능은 RAG 스위치·업무 DB 종류와 무관하게 켜진다(기존 RAG_ENABLED 공유 결합을 해소).

4. 벡터 저장소 계약 (server/rag/vectorStore.ts) ​

ts
interface VectorStore {
  readonly kind: string;
  health(): { kind: string; available: boolean; reason?: string };
  search(input: { vector: readonly number[]; principal: RagPrincipal; limit: number }): Promise<RagSearchResult[]>;
}
  • 기본 구현 createPgVectorStore() — 트랜잭션 안에서 set_config('my.app_tenant_id | my.app_user_org_id | my.app_user_security_level', ..., true)(SET LOCAL)로 RLS 컨텍스트를 설정한 뒤 pgvector <=>로 유사 청크를 조회한다.
  • 비활성 구현 createDisabledVectorStore(reason) — 사유와 함께 실패하며, 게시·AI 완성 기능에는 영향을 주지 않는다.
  • 외부 저장소: setVectorStore(adapter)로 주입(후속: Qdrant/Milvus 어댑터 구현).
  • privateRag.searchPrivateRag()는 저장소를 해석해 위임만 하며, 비활성이면 임베딩 호출도 하지 않는다.

5. 지켜야 할 규율 (이중 지원 비용을 낮추는 방법) ​

  1. 업무 저장소(server/repositories)에서 PG 전용 SQL 금지: set_config, ::vector, ON CONFLICT, RETURNING, ILIKE, jsonb 연산자, pg_* 함수.
    • 예외는 벡터/RAG 계층(server/rag/**)과 dialect helper(server/db-connection.ts)에만 허용한다.
  2. dialect 차이는 helper로 흡수한다: insertReturningId · getAffectedRowCount · dialectUpsert · advisory lock(PG pg_try_advisory_xact_lock / MySQL GET_LOCK).
  3. 스키마는 drizzle/schema.mysql.ts와 drizzle/schema.pg.ts를 항상 함께 갱신하고 drizzle/schema.ts(barrel)에 등록한다.
  4. 마이그레이션은 dialect별로 각각 만든다 — 새 PostgreSQL DDL은 재실행 안전하게 멱등(IF NOT EXISTS)으로 작성한다.

6. 현재 진행 상태 (2026-09-13) ​

항목상태
PostgreSQL 마이그레이션0014 ~ 0016 적용·검증 완료(멱등 DDL, 개발 DB 반영)
MySQL 마이그레이션0027·0028 파일만 유지 — 생성·적용·검증은 후속(초기 개발은 PostgreSQL 집중)
검증 스크립트pnpm db:postgres:verify-governance — 트랜잭션 롤백으로 상태 불변 확인
호환 테스트server/db-connection.compatibility.test.ts로 dialect별 동작 검증
RAG/벡터server/rag/vectorStore.ts 계약 도입, pgvector·비활성 구현 완료

7. 남은 작업 ​

  1. 외부 벡터 저장소 어댑터(Qdrant·Milvus) 실제 구현 + 운영 환경에서 setVectorStore() 주입 경로
  2. MySQL 환경에서 RAG 비활성 사유를 화면에 노출(privateRagHealth().reason 활용) — 현재는 health 응답으로만 제공
  3. MySQL 마이그레이션 생성·적용 검증(PostgreSQL 검증 완료 후)

8. AI·벡터 계층의 서비스 경계 (플랫폼 아키텍처 연결) ​

이 저장소의 AI 구현은 조직의 기존 플랫폼 경계 문서를 따른다.

AI 계층소유자근거
계약·유틸(@hohyun94/portal-ai)portal-foundation소비 포털이 빌드하는 재사용 계약
포털별 적용(게시본 요약 트리거·번역 대상 필드)ProcessCenter업무 정책
다중 포털 탐색 실행 조합(포털 어댑터 오케스트레이션)ExploreGatewayExploreGateway/docs/SERVICE_BOUNDARIES.md, ProcessCenter/docs/EXPLORE_ADAPTER_CONTRACT.md
문서 인덱스·RAG·citationRagServiceFileStore/docs/ARCHITECTURE.md, UiPlatform/docs/DECISION_LOG.md(2026-09-13 정정)
원본 파일 보관·RAG 발행(ragPublishAllowed)FileStoreFileStore/docs/ARCHITECTURE.md
모델·키·비용·감사(LLM 완성)ModelGateway(모델 게이트웨이)UiPlatform/docs/DECISION_LOG.md(2026-09-13), MODEL_GATEWAY_SCOPE.md
  • server/rag/**·server/ai/**는 이제 위임·폴백 계층이다. LLM 완성은 MODEL_GATEWAY_URL(ModelGateway), 문서 인덱스·RAG는 RAG_SERVICE_URL(RagService)로 나가고, 각각 미설정·실패일 때만 로컬(PRIVATE_LLM_URL·pgvector VectorStore)로 폴백한다.
  • RAGCenter 연동 상태(2026-09-26): PC가 맡는 것은 ① 검색·답변 위임(server/ragCenterClient.ts → /api/v1/search·/api/v1/chat/stream, rag:read 위임 토큰) ② 수집 발행(server/ragPublicationService.ts → POST /api/v1/documents, rag:admin) ③ 삭제 전파(같은 admin API, 실패는 비차단) ④ 첨부 원본 제공(GET /api/rag/v1/attachments/:assetId — 서명 URL을 서버가 받아 스트리밍)이다. 식별자는 server/rag/ragIdentity.ts의 uuidv5 투영이 단일 기준이다. 상세·잔여 항목: docs/RAGCENTER_INTEGRATION_PLAN.md §4·§5.
  • server/rag·server/ai는 폴백 전용으로 유지한다 — P3 결정(2026-09-26): 삭제하지 않는다(RAGService 장애 시 로컬이 유일한 검색 수단이고, VectorStore 계약이 외부 벡터 DB 어댑터 주입 지점이다). 재검토는 RAGService 가용성이 목표(SLO)를 만족하고 배포 스택에 기본 포함될 때. 계약(@hohyun94/portal-ai)과 포털 적용 로직은 각각 portal-foundation·이 저장소에 유지한다.
  • 탐색 어댑터(구현 완료): server/remoteExplore.ts가 POST /api/explore/v1/search를 제공한다(서비스 토큰 EXPLORE_GATEWAY_API_KEY + x-explore-subject-id). 계약: docs/EXPLORE_ADAPTER_CONTRACT.md · docs/explore-adapter.openapi.json.
  • ModelGateway 위임 구현 완료(2026-09-13): server/ai/aiClient.ts가 MODEL_GATEWAY_URL·MODEL_GATEWAY_SERVICE_TOKEN 설정 시 게이트웨이를 우선 사용하고(귀속 헤더 x-portal-id·x-purpose·x-actor 자동 부착) 없으면 PRIVATE_LLM_URL로 폴백한다. 남은 것은 운영 환경의 주소·토큰 주입과 P3(프롬프트 표준화)다.
  • 목적(purpose) 기반 모델 라우팅(2026-09-26): 모델을 고정하지 않으면(MODEL_GATEWAY_MODEL·PRIVATE_LLM_MODEL 미설정) 요청 본문에 예약값 model: "auto"를 보내 게이트웨이가 purposeModels(목적별 모델)·기본 모델로 고른다. 소비 포탈은 목적(x-purpose)만 알면 되고, 모델·키·쿼터·비용·감사는 게이트웨이가 소유한다. 모델 고정이 필요한 배포는 기존 env를 그대로 쓰면 우선 적용된다.
  • RAG 답변 생성도 같은 경로로 통일(2026-09-26): server/rag/privateRag.ts의 answerPrivateRag()가 resolveAiProvider({ purpose: "rag.answer", actor })를 거친다. server/rag/privateAi.ts에는 임베딩(createPrivateEmbedding)만 남아, LLM 호출이 게이트웨이를 우회해 키·쿼터·감사가 갈라지지 않는다. 화면 진입은 2026-10-05 상단 검색창(연합 검색 + 중앙 RagService 답변)으로 통합돼 /rag·/explore 화면과 메뉴 항목이 사라졌다 — 코드는 아래 P3 대로 남는다.