PostgreSQL 테이블 구조
작성일: 2025-08-20
최종 수정일: 2025-12-04
데이터베이스: main_db
해석 기준
1. 조직 관련 테이블
company
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| name |
VARCHAR(255) |
NO |
|
| url |
VARCHAR(255) |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
team
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| company_id |
UUID |
NO |
FK → company |
| name |
VARCHAR(32) |
NO |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
user
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| team_id |
UUID |
NO |
FK → team |
| email |
VARCHAR(255) |
NO |
UNIQUE |
| name |
VARCHAR(255) |
YES |
|
| picture |
VARCHAR(500) |
YES |
|
| oauth_providers |
JSONB |
YES |
OAuth 정보 (google/slack/naverworks) |
| is_active |
BOOLEAN |
YES |
|
| last_login_at |
TIMESTAMPTZ |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
| username |
VARCHAR(64) |
YES |
|
| is_admin |
BOOLEAN |
NO |
|
| metadata |
JSONB |
YES |
사용자 확장 정보 (nickname, position, preferences 등) |
metadata 예시:
{
"nickname": "joann",
"position": "대표",
"title": "대표님",
"short_name": "고은",
"department": "경영",
"preferences": {
"communication": "direct",
"work_style": "flexible"
}
}
oauth_providers 예시:
{
"google": "oauth_id_123",
"slack": "U01234567",
"naverworks": "nw_user_id"
}
workspace_member
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| user_id |
UUID |
NO |
FK → user |
| role |
user_role |
NO |
OWNER/MEMBER/GUEST |
| is_active |
BOOLEAN |
YES |
|
| joined_at |
TIMESTAMP |
YES |
|
| updated_at |
TIMESTAMP |
YES |
|
2. 제품 및 로빙 테이블
product
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| name |
VARCHAR(32) |
NO |
|
| app_id |
VARCHAR(16) |
YES |
|
| scope |
JSON |
YES |
|
| description |
VARCHAR(256) |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
robeing
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| product_id |
UUID |
NO |
FK → product |
| team_id |
UUID |
NO |
FK → team |
| name |
VARCHAR(32) |
YES |
|
| level |
INTEGER |
YES |
|
| experience |
INTEGER |
YES |
|
| memory |
INTEGER |
YES |
|
| compute |
INTEGER |
YES |
|
| react |
INTEGER |
YES |
|
| empathy |
INTEGER |
YES |
|
| leadership |
INTEGER |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| robeing_container_id |
VARCHAR(64) |
YES |
|
| robeing_container_url |
VARCHAR(128) |
YES |
|
3. Slack 통합 테이블
slack_workspace
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| team_id |
UUID |
NO |
FK → team |
| slack_team_id |
VARCHAR(32) |
YES |
|
| bot_token |
VARCHAR(255) |
YES |
|
| is_enterprise_install |
BOOLEAN |
YES |
|
| is_active |
BOOLEAN |
YES |
|
| installed_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
slack_channel
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| slack_workspace_id |
UUID |
NO |
FK → slack_workspace |
| channel_id |
VARCHAR(32) |
NO |
Slack 채널 ID (예: C09C98KK2TT) |
| channel_name |
VARCHAR(255) |
NO |
채널명 (예: company-x-전체) |
| is_private |
BOOLEAN |
NO |
비공개 채널 여부 |
| is_archived |
BOOLEAN |
NO |
아카이브 여부 |
| is_member |
BOOLEAN |
NO |
robeing 봇이 멤버인지 |
| robeing_can_read |
BOOLEAN |
NO |
읽기 가능 여부 |
| robeing_can_create |
BOOLEAN |
NO |
전송 가능 여부 |
| robeing_can_update |
BOOLEAN |
NO |
수정 가능 여부 |
| robeing_can_delete |
BOOLEAN |
NO |
삭제 가능 여부 (현재 미구현) |
| status |
VARCHAR(32) |
YES |
active, inactive 등 |
| metadata |
JSONB |
YES |
추가 메타데이터 (용도, 설명 등) |
| created_at |
TIMESTAMPTZ |
NO |
|
| updated_at |
TIMESTAMPTZ |
NO |
|
제약조건: UNIQUE(slack_workspace_id, channel_id) - 워크스페이스 내 채널 ID 중복 방지
slack_canvas
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| slack_channel_id |
UUID |
NO |
FK → slack_channel |
| canvas_id |
VARCHAR(32) |
NO |
Slack Canvas/파일 ID (예: F0AL6L34CV9) |
| title |
VARCHAR(255) |
YES |
Canvas 제목 (예: 온보딩 회의) |
| metadata |
JSONB |
YES |
추가 메타데이터 |
| created_at |
TIMESTAMPTZ |
NO |
|
| updated_at |
TIMESTAMPTZ |
NO |
|
제약조건: UNIQUE(canvas_id) - Slack에서 canvas_id는 전역 고유
4. 사용자 설정 및 토큰 테이블
user_preference
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
INTEGER |
NO |
PK |
| user_id |
UUID |
NO |
FK → user |
| slack_user_id |
VARCHAR(32) |
YES |
|
| news_keywords |
VARCHAR(128)[] |
YES |
|
| email_filter |
VARCHAR(128)[] |
YES |
|
| briefing_enabled |
BOOLEAN |
YES |
|
| briefing_time |
TIME |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
gmail_token
| 컬럼명 |
타입 |
NULL |
설명 |
| id |
UUID |
NO |
PK |
| user_id |
UUID |
NO |
FK → user |
| token_data |
JSONB |
YES |
|
| oauth_config |
JSONB |
YES |
|
| scopes |
JSONB |
YES |
|
| metadata |
JSONB |
YES |
|
| expiry |
TIMESTAMP |
YES |
|
| is_equipped |
BOOLEAN |
YES |
|
| equipped_to |
VARCHAR(50) |
YES |
|
| token_type |
VARCHAR |
YES |
|
| expires_at |
DOUBLE PRECISION |
YES |
|
| created_at |
TIMESTAMPTZ |
YES |
|
| updated_at |
TIMESTAMPTZ |
YES |
|
5. 로그 및 데이터 테이블
conversation_log
- 용도: 대화 기록
- Primary Key: id (INTEGER)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
INTEGER |
NO |
|
PK |
| user_id |
UUID |
NO |
|
FK → user |
| channel_id |
VARCHAR(16) |
YES |
|
|
| message |
VARCHAR |
YES |
|
|
| response |
VARCHAR |
YES |
|
|
| intent |
VARCHAR(256) |
YES |
|
|
| confidence |
DOUBLE PRECISION |
YES |
|
|
| thread_ts |
VARCHAR(128) |
YES |
|
|
| timestamp |
TIMESTAMPTZ |
YES |
|
|
| channel_type |
VARCHAR(32) |
YES |
|
|
intent_* (동적 의도 레지스트리)
- 용도: 제로샷 의도 분류 설정을 DB에서 관리
- 관련 테이블
intents: 의도 메타데이터 (id, name, description, active, created_at, updated_at)
intent_prototypes: 의도별 임베딩(pgvector) 버전/출처 기록
intent_thresholds: semantic/LLM 경로별 임계값 관리
intent_path_stats: fastpath/semantic/llm/clarify 경로 별 베타분포 파라미터(alpha, beta) 저장
SemanticIntentClassifier는 위 테이블에서 활성화된 의도를 불러오고, 프로토타입/임계값이 없을 경우 YAML 레지스트리로 폴백합니다. 이 구조 덕분에 신규 의도를 추가할 때 DB 레코드만 업데이트하면 서비스가 재시작 없이 최신 의도 설명을 사용할 수 있습니다.
news
- 용도: 뉴스 데이터
- Primary Key: id (UUID)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
UUID |
NO |
|
PK |
| user_id |
UUID |
YES |
|
FK → user |
| data |
JSONB |
YES |
|
|
| created_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
| updated_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
rb_news
- 용도: 뉴스 발행 관리
- Primary Key: id (UUID)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
UUID |
NO |
gen_random_uuid() |
PK |
| title |
TEXT |
NO |
|
|
| url |
TEXT |
NO |
|
UNIQUE |
| summary |
TEXT |
YES |
|
|
| slack_message_ts |
VARCHAR(100) |
YES |
|
|
| slack_channel_id |
VARCHAR(100) |
YES |
|
|
| is_published |
BOOLEAN |
YES |
false |
|
| published_at |
TIMESTAMPTZ |
YES |
|
|
| created_at |
TIMESTAMPTZ |
YES |
now() |
|
인덱스:
rb_news_pkey: PRIMARY KEY (id)
robeing_diary
- 용도: 로빙 일기(성장 일지) 저장
- Primary Key: id (INTEGER)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
INTEGER |
NO |
|
PK, SERIAL |
| date |
DATE |
NO |
|
일기 날짜 |
| robeing_id |
VARCHAR(50) |
NO |
|
로빙 식별자 |
| summary |
TEXT |
YES |
|
일기 요약 |
| dominant_emotion |
VARCHAR(50) |
YES |
|
지배적 감정 |
| stats |
JSONB |
YES |
'{}' |
통계 데이터 |
| full_content |
TEXT |
NO |
|
전체 일기 내용 (마크다운) |
| created_at |
TIMESTAMPTZ |
YES |
NOW() |
생성 시간 |
인덱스:
idx_robeing_diary_date: (date DESC)
idx_robeing_diary_robeing: (robeing_id)
- UNIQUE: (date, robeing_id)
activity_log
- 용도: 로빙 활동 로그 (스킬 실행, 스케줄러 작업 등)
- Primary Key: id (INTEGER)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
INTEGER |
NO |
|
PK, SERIAL |
| robeing_id |
VARCHAR(50) |
NO |
|
로빙 식별자 |
| activity_type |
VARCHAR(50) |
NO |
|
'skill', 'scheduler', 'internal' |
| skill_name |
VARCHAR(100) |
YES |
|
스킬명 (activity_type='skill'일 때) |
| status |
VARCHAR(20) |
NO |
|
'success', 'error', 'partial' |
| result_summary |
TEXT |
YES |
|
결과 요약 |
| error_message |
TEXT |
YES |
|
에러 메시지 (status='error'일 때) |
| meta |
JSONB |
YES |
'{}' |
메타데이터 |
| created_at |
TIMESTAMPTZ |
YES |
NOW() |
생성 시간 |
인덱스:
idx_activity_log_robeing_date: (robeing_id, created_at DESC)
idx_activity_log_type: (activity_type)
rb_news_url_key: UNIQUE (url)
idx_rb_news_url: btree (url)
idx_rb_news_slack_message_ts: btree (slack_message_ts)
idx_rb_news_created_at: btree (created_at DESC)
team_document
- 용도: 팀 문서 RAG 시스템
- Primary Key: id (UUID)
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
UUID |
NO |
gen_random_uuid() |
PK |
| team_id |
UUID |
NO |
|
FK → team |
| filename |
VARCHAR(255) |
NO |
|
|
| file_hash |
VARCHAR(64) |
NO |
|
SHA256 |
| file_size |
BIGINT |
YES |
|
bytes |
| mime_type |
VARCHAR(100) |
YES |
|
|
| storage_path |
TEXT |
NO |
|
/mnt/hdd/uploads/{team_id}/ |
| text_content |
TEXT |
YES |
|
|
| chunk_count |
INTEGER |
YES |
0 |
ChromaDB 청크 수 |
| processing_status |
VARCHAR(20) |
YES |
'pending' |
pending/completed/failed |
| metadata |
JSONB |
YES |
'{}' |
uploaded_by, tags, summary 등 |
| created_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
| updated_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
인덱스:
idx_team_doc_hash: UNIQUE (team_id, file_hash) - 팀별 파일 중복 방지
naverworks_token
- 용도: NAVER WORKS OAuth 토큰 및 Passport 정보
- Primary Key: id (UUID)
- Unique: user_id
- Foreign Key: user_id → user(id) ON DELETE CASCADE
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| id |
UUID |
NO |
gen_random_uuid() |
PK |
| user_id |
UUID |
NO |
|
FK → user |
| access_token |
TEXT |
YES |
|
개별 저장용 |
| refresh_token |
TEXT |
YES |
|
개별 저장용 |
| token_type |
VARCHAR(50) |
YES |
'Bearer' |
|
| expires_at |
TIMESTAMP |
YES |
|
|
| scopes |
JSONB |
YES |
|
|
| domain_id |
VARCHAR(255) |
YES |
|
|
| service_account |
VARCHAR(255) |
YES |
|
|
| metadata |
JSONB |
YES |
|
|
| created_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
| updated_at |
TIMESTAMPTZ |
YES |
CURRENT_TIMESTAMP |
|
| username |
VARCHAR(255) |
YES |
|
|
| account_id |
VARCHAR(255) |
YES |
|
|
| token_data |
JSONB |
YES |
|
통합 저장용 |
| oauth_config |
JSONB |
YES |
|
|
| is_equipped |
BOOLEAN |
YES |
false |
Passport 장착 여부 |
인덱스:
- idx_naverworks_token_user_id (user_id)
- idx_naverworks_token_expires_at (expires_at)
트리거:
- update_naverworks_token_updated_at: updated_at 자동 갱신
6. 기타 정보
Custom Types
- user_role: ENUM (OWNER, MEMBER, GUEST)
Functions
- update_column_updated_at: 모든 테이블의 updated_at 자동 갱신 트리거
외래키 관계
| 테이블 |
컬럼 |
참조 테이블 |
참조 컬럼 |
| team |
company_id |
company |
id |
| user |
team_id |
team |
id |
| robeing |
product_id |
product |
id |
| robeing |
team_id |
team |
id |
| slack_workspace |
team_id |
team |
id |
| workspace_member |
user_id |
user |
id |
| conversation_log |
user_id |
user |
id |
| gmail_token |
user_id |
user |
id |
| news |
user_id |
user |
id |
| user_preference |
user_id |
user |
id |
| team_document |
team_id |
team |
id |
7. 감정 분석 테이블 (robeing_metrics DB)
emotion_readings
- 용도: 사용자 및 로빙 감정 분석 시계열 데이터
- 데이터베이스: robeing_metrics (TimescaleDB)
- 특징: 시계열 하이퍼테이블, Top-p 기반 복합 감정 저장
| 컬럼명 |
타입 |
NULL |
기본값 |
설명 |
| created_at |
TIMESTAMPTZ |
NO |
|
파티션 키 |
| user_id |
UUID |
YES |
|
user 테이블 참조 |
| company_id |
UUID |
YES |
|
|
| robeing_id |
VARCHAR(50) |
YES |
|
|
| emotion_type |
VARCHAR(20) |
YES |
|
user/robeing |
| probs |
JSONB |
NO |
|
7개 감정 확률 분포 |
| entropy |
DOUBLE PRECISION |
YES |
|
|
| model_version |
VARCHAR(50) |
YES |
|
onnx-7emotions-v1 등 |
| meta |
JSONB |
YES |
|
source, message_length 등 |
| text_hash |
VARCHAR(64) |
YES |
|
SHA256 |
| top_emotions |
JSONB |
YES |
|
[{"label", "probability"}] |
| cumulative_p |
DOUBLE PRECISION |
YES |
|
Top-p 70% |
인덱스:
emotion_readings_created_at_idx: btree (created_at DESC)
idx_emotion_user_time: btree (user_id, created_at DESC)
idx_emotion_company_time: btree (company_id, created_at DESC)
idx_emotion_robeing_time: btree (robeing_id, created_at DESC)
idx_emotion_type: btree (emotion_type, created_at DESC)
제약 조건:
emotion_readings_emotion_type_check: emotion_type IN ('user', 'robeing')
트리거:
ts_insert_blocker: TimescaleDB 하이퍼테이블 삽입 제어
예시 데이터:
{
"top_emotions": [
{"label": "fear", "probability": 0.499},
{"label": "neutral", "probability": 0.335}
],
"cumulative_p": 0.833,
"probs": {
"fear": 0.499, "neutral": 0.335, "anger": 0.056,
"sadness": 0.048, "disgust": 0.032, "surprise": 0.018, "happiness": 0.012
},
"entropy": 1.234
}
주의사항
데이터베이스 정보
- main_db: 주요 서비스 데이터 (PostgreSQL)
- 소유자: robeings
- 테이블: user, team, robeing, conversation_log 등
- robeing_metrics: 시계열 메트릭 데이터 (TimescaleDB)
- 테이블: emotion_readings
- 특징: 시간 기반 파티셔닝, 자동 압축, retention 정책
- 타임스탬프: TIMESTAMPTZ 사용 (WITH TIME ZONE)
- 자동 갱신: update_column_updated_at 트리거로 updated_at 자동 관리
- UUID 기반: 주요 테이블 모두 UUID 사용
문서 끝