데이터베이스
Starnion은 PostgreSQL 16 + pgvector 확장을 주요 데이터 저장소로 사용합니다. 텍스트 데이터와 768차원 벡터 임베딩을 동일한 데이터베이스에서 관리하여 하이브리드 RAG(벡터 유사도 + 전문 검색)를 구현합니다.
전체 스키마 개요
PostgreSQL 16 + pgvector
│
├── 인증·신원
│ ├── users # 중앙 사용자 테이블
│ ├── platform_identities # 플랫폼별 ID 매핑 (텔레그램, 웹 등)
│ └── platform_link_codes # 계정 연결 코드 (10분 TTL)
│
├── 대화
│ ├── conversations # 대화 세션 (LangGraph thread_id 포함)
│ └── messages # 대화 메시지 (첨부파일 JSONB)
│
├── 재무
│ ├── finances # 가계부 거래 내역
│ └── (budget: profiles.preferences JSONB)
│
├── 개인 기록 (벡터 임베딩 포함)
│ ├── daily_logs # 일일 로그·일기 (vector 768)
│ ├── diary_entries # 일기 엔트리 (vector 768)
│ ├── memos # 메모 (vector 768)
│ ├── goals # 목표 관리
│ ├── goal_checkins # 목표 체크인 기록
│ └── ddays # 디데이
│
├── 미디어·문서 (벡터 임베딩 포함)
│ ├── documents # 업로드된 문서 메타데이터
│ ├── document_sections # 문서 청크 (vector 768)
│ ├── images # 이미지 갤러리
│ └── audios # 오디오 갤러리
│
├── 지식 및 검색 (벡터 임베딩 포함)
│ ├── knowledge_base # 패턴 분석 결과·지식 (vector 768)
│ └── searches # 웹 검색 기록 (vector 768)
│
├── 설정·연동
│ ├── skills # 스킬 카탈로그
│ ├── user_skills # 사용자별 스킬 활성화 상태
│ ├── providers # LLM 프로바이더 설정
│ ├── personas # AI 페르소나
│ ├── google_tokens # Google OAuth2 토큰
│ └── integration_keys # 외부 서비스 API 키
│
├── 채널·알림
│ ├── channel_settings # 텔레그램 채널 설정
│ ├── telegram_approved_contacts # 텔레그램 승인 연락처
│ ├── telegram_pairing_requests # 텔레그램 페어링 요청
│ └── notifications # 알림 내역
│
├── 사용량
│ └── usage_logs # LLM 토큰 사용량 로그
│
└── 메타
└── schema_migrations # 스키마 버전 관리
핵심 테이블 상세
users — 사용자
모든 사용자 데이터의 루트가 되는 테이블입니다. 이메일/비밀번호 인증과 플랫폼 기반 인증을 모두 지원합니다.
CREATE TABLE users (
id TEXT PRIMARY KEY, -- UUID
display_name TEXT,
email TEXT UNIQUE, -- 이메일 인증 사용자만
password_hash TEXT, -- bcrypt
role TEXT DEFAULT 'user', -- 'admin' | 'user'
preferences JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
platform_identities — 플랫폼 ID 매핑
텔레그램, 웹, 디스코드 등 다양한 플랫폼의 사용자 ID를 단일 user_id로 통합합니다.
CREATE TABLE platform_identities (
user_id TEXT REFERENCES users(id),
platform TEXT, -- 'telegram' | 'web' | 'discord' | 'credential'
platform_id TEXT, -- 플랫폼 내 고유 ID (telegram chat_id, 이메일 등)
display_name TEXT,
metadata JSONB DEFAULT '{}',
last_active_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (platform, platform_id)
);
conversations / messages — 대화
LangGraph의 체크포인트 시스템과 연동됩니다. thread_id가 LangGraph 대화 상태와 연결됩니다.
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT REFERENCES users(id),
title TEXT DEFAULT '새 대화',
platform TEXT DEFAULT 'web', -- 'web' | 'telegram'
thread_id TEXT, -- LangGraph thread ID
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID REFERENCES conversations(id),
role TEXT CHECK (role IN ('user', 'assistant')),
content TEXT,
attachments JSONB, -- 첨부파일 URL 배열
created_at TIMESTAMPTZ DEFAULT NOW()
);
finances — 가계부
CREATE TABLE finances (
id BIGSERIAL PRIMARY KEY,
user_id TEXT REFERENCES users(id),
amount INTEGER, -- 원(KRW) 단위. 수입: 양수, 지출: 음수
category TEXT, -- '식비' | '교통' | '쇼핑' | '수입' 등
description TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
daily_logs — 일일 로그 (벡터 임베딩)
대화 내용과 일기를 벡터로 저장합니다. 4-Layer RAG 메모리의 Layer 1에 해당합니다.
CREATE TABLE daily_logs (
id BIGSERIAL PRIMARY KEY,
user_id TEXT REFERENCES users(id),
content TEXT,
sentiment TEXT, -- '좋음' | '보통' | '나쁨' | '피곤' | '기쁨'
embedding vector(768), -- Gemini text-embedding-004
content_tsv tsvector, -- 전문 검색용 (자동 트리거)
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- HNSW 인덱스: 빠른 근사 최근접 이웃 검색
CREATE INDEX ON daily_logs USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 전문 검색 GIN 인덱스
CREATE INDEX ON daily_logs USING gin(content_tsv);
document_sections — 문서 청크 (벡터 임베딩)
업로드된 문서를 청크 단위로 분할하여 저장합니다. 4-Layer RAG 메모리의 Layer 3에 해당합니다.
CREATE TABLE document_sections (
id BIGSERIAL PRIMARY KEY,
document_id BIGINT REFERENCES documents(id),
content TEXT,
embedding vector(768),
content_tsv tsvector,
metadata JSONB DEFAULT '{}' -- 페이지 번호, 위치 등
);
knowledge_base — 지식 베이스 (벡터 임베딩)
소비 패턴 분석 결과, 사용자 선호, 개인화 정보를 저장합니다. 4-Layer RAG 메모리의 Layer 2에 해당합니다.
CREATE TABLE knowledge_base (
id BIGSERIAL PRIMARY KEY,
user_id TEXT REFERENCES users(id),
key TEXT, -- 지식 유형 (예: 'pattern_analysis', 'user_preference')
value TEXT, -- 지식 내용
source TEXT, -- 출처 스킬
embedding vector(768),
content_tsv tsvector,
created_at TIMESTAMPTZ DEFAULT NOW()
);
skills / user_skills — 스킬 관리
CREATE TABLE skills (
id TEXT PRIMARY KEY, -- 스킬 ID (예: 'finance', 'weather')
name TEXT,
description TEXT,
category TEXT,
emoji TEXT DEFAULT '',
tools TEXT[] DEFAULT '{}', -- 스킬이 제공하는 도구 목록
reports TEXT[] DEFAULT '{}', -- 생성하는 리포트 유형
cron_rules TEXT[] DEFAULT '{}', -- 크론 스케줄 규칙
enabled_by_default BOOLEAN DEFAULT TRUE,
permission_level INT DEFAULT 1,
sort_order INT DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE user_skills (
user_id TEXT,
skill_id TEXT REFERENCES skills(id),
enabled BOOLEAN,
updated_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, skill_id)
);
벡터 검색 (pgvector)
개요
pgvector 확장을 통해 768차원 임베딩 벡터를 저장하고 코사인 유사도 검색을 수행합니다.
- 임베딩 모델: Google
text-embedding-004(768차원) - 인덱스 방식: HNSW (Hierarchical Navigable Small World)
- 유사도 함수: 코사인 유사도 (
<=>연산자)
벡터를 사용하는 테이블
| 테이블 | 용도 | RAG Layer |
|---|---|---|
daily_logs |
대화·일기 기억 검색 | Layer 1 |
knowledge_base |
사용자 패턴·선호 검색 | Layer 2 |
document_sections |
업로드 문서 내용 검색 | Layer 3 |
diary_entries |
일기 시맨틱 검색 | - |
memos |
메모 시맨틱 검색 | - |
searches |
웹 검색 기록 검색 | - |
match_logs 함수
Agent의 메모리 검색에서 사용하는 벡터 유사도 검색 함수입니다.
SELECT * FROM match_logs(
query_embedding := $1::vector, -- 768차원 쿼리 벡터
match_threshold := 0.7, -- 최소 유사도 임계값
match_count := 5, -- 반환할 최대 결과 수
p_user_id := 'uuid...'
);
-- 반환: id, content, similarity (코사인 유사도 0~1)
하이브리드 검색
벡터 유사도 검색과 PostgreSQL 전문 검색(Full-Text Search)을 결합합니다.
사용자 쿼리: "지난주에 먹은 음식"
│
┌─────────┴──────────┐
▼ ▼
pgvector 검색 FTS 검색
(의미 유사도) (키워드 매칭)
embedding <=> tsvector @@ tsquery
query_vector to_tsquery('simple', '먹은 & 음식')
│ │
└─────────┬──────────┘
▼
결과 병합 & 재정렬
(벡터 유사도 + FTS 점수)
tsvector 자동 업데이트
INSERT/UPDATE 시 PostgreSQL 트리거가 자동으로 content_tsv를 갱신합니다.
-- 예시: daily_logs 트리거
CREATE TRIGGER trg_daily_logs_tsv
BEFORE INSERT OR UPDATE OF content ON daily_logs
FOR EACH ROW EXECUTE FUNCTION daily_logs_tsv_trigger();
-- 내부: NEW.content_tsv := to_tsvector('simple', COALESCE(NEW.content, ''))
같은 방식의 트리거가 knowledge_base, document_sections, diary_entries, memos, searches 테이블에도 적용됩니다.
스키마 버전 관리
신규 설치
docker/init.sql을 사용합니다. 전체 스키마를 한 번에 생성하는 기준선(baseline) 파일입니다.
# Docker 초기화 시 자동 실행
docker compose up -d postgres
버전 업그레이드
docker/migrations/incremental/ 디렉터리의 증분 마이그레이션 파일을 순서대로 적용합니다.
# 예시: 새 마이그레이션 적용
psql $DATABASE_URL -f docker/migrations/incremental/031_new_feature.sql
현재 적용된 버전은 schema_migrations 테이블에 기록됩니다.
SELECT version, applied_at FROM schema_migrations ORDER BY applied_at;
-- 1.0.0 | 2025-01-01 00:00:00+00
연결 방식
Gateway (Go)
database/sql + lib/pq 드라이버를 사용합니다.
DATABASE_URL=postgres://user:pass@localhost:5432/starnion?sslmode=disable
Agent (Python)
psycopg (psycopg3) + psycopg-pool 커넥션 풀을 사용합니다.
DATABASE_URL=postgresql://user:pass@localhost:5432/starnion
LangGraph 체크포인트 저장소도 동일한 PostgreSQL 인스턴스를 사용합니다 (langgraph-checkpoint-postgres).
데이터 격리
각 사용자의 데이터는 user_id 외래키로 완전히 격리됩니다. 한 사용자가 다른 사용자의 데이터에 접근할 수 없으며, 모든 쿼리에 WHERE user_id = $1 조건이 적용됩니다.
성능 고려사항
| 인덱스 | 대상 테이블 | 용도 |
|---|---|---|
| HNSW (m=16, ef=64) | daily_logs, document_sections, knowledge_base, diary_entries, memos, searches |
벡터 근사 최근접 이웃 검색 |
| GIN | 위 테이블의 content_tsv |
전문 검색 |
| B-tree | user_id, created_at 컬럼 |
필터링 및 정렬 |
| 복합 인덱스 | conversations(user_id, updated_at DESC) |
대화 목록 조회 |
HNSW 파라미터:
m = 16: 각 노드의 최대 연결 수 (높을수록 정확하지만 메모리 증가)ef_construction = 64: 인덱스 구축 시 탐색 범위 (높을수록 품질 향상, 구축 시간 증가)