- 기존 conversation_log의 1:1 대화 한계 - Event 테이블 6하원칙 설계 (who/what/when/where/why/how) - JSONB로 복잡한 관계 표현 (to_who, using, affected) - Graph DB vs Event Stream 비교 - 시간축 중심 설계 (베이지안 성장과 일치) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
229 lines
6.9 KiB
Markdown
229 lines
6.9 KiB
Markdown
# Conversation Log 재설계: Event Stream 아키텍처
|
|
|
|
## 작성일
|
|
2025-10-02
|
|
|
|
## 문제 상황
|
|
|
|
### 기존 conversation_log 테이블 한계
|
|
```sql
|
|
conversation_log
|
|
- user_id (FK)
|
|
- message
|
|
- response
|
|
```
|
|
|
|
**가정**: 항상 1:1 대화 (user → robeing)
|
|
|
|
### 실제 요구사항
|
|
1. **그룹 대화**: user1, user2, user3, robeing
|
|
2. **로빙 브리핑**: robeing → 팀 전체
|
|
3. **로빙 협업**: robeing1 ↔ robeing2
|
|
4. **멀티턴 쓰레드**: user ↔ robeing (여러 왕복)
|
|
|
|
### 근본 문제
|
|
- **참여자 표현 불가**: 1:1만 가정
|
|
- **방향성 모호**: message/response 구분 애매
|
|
- **컨텍스트 부재**: 어떤 채널/쓰레드인지 불명확
|
|
- **로빙 철학 위배**: 로빙도 대화 주체인데 user 테이블에 없음
|
|
|
|
## 철학적 배경
|
|
|
|
### 로빙 철학과 DB 구조 충돌
|
|
- **user 테이블**: 인증/식별 (OAuth, email, login)
|
|
- **robeing 테이블**: 성장 주체 (level, experience, stats)
|
|
- **문제**: 로빙 봇(Slack Bot ID)이 user 테이블에 없어서 봇 메시지 저장 불가
|
|
|
|
### "대화는 이벤트의 흐름"
|
|
- **Graph DB**: 공간적 관계망 (A-B-C 연결 구조)
|
|
- **Event Stream**: 시간적 흐름 (A→B→C 순서)
|
|
- 로빙의 베이지안 성장 = 시간에 따른 업데이트 → **시간축이 핵심**
|
|
|
|
## 해결 방안: Event 테이블 (6하원칙)
|
|
|
|
### 설계 원칙
|
|
**장소(where_id) + 시간(timestamp) = 이벤트 연결**
|
|
- where_id: Slack, 이메일, 웹, 음성통화 등 모든 채널
|
|
- timestamp: 시간 순서 보장
|
|
- 6하원칙: 모든 이벤트를 표준화
|
|
|
|
### Event 테이블 구조
|
|
```sql
|
|
event
|
|
-- 기본 6하원칙
|
|
- id (PK, UUID)
|
|
- who_id (UUID) -- 행위 주체
|
|
- who_type (VARCHAR) -- human/robeing/system
|
|
- what_type (VARCHAR) -- message/file_transfer/task_assign/analysis_request/reaction
|
|
- what_content (JSONB) -- 실제 내용/데이터
|
|
- when_at (TIMESTAMPTZ) -- 시간
|
|
- where_id (VARCHAR) -- 장소 (channel/dm/email/system)
|
|
- where_type (VARCHAR) -- slack/email/web/voice/internal
|
|
- why (VARCHAR) -- intent/purpose
|
|
- how (VARCHAR) -- method/tool
|
|
|
|
-- 확장 필드 (복잡한 관계 처리)
|
|
- to_who (JSONB) -- [{id, type}] 수신자 목록
|
|
- using (JSONB) -- [{id, type, resource}] 사용된 리소스/데이터
|
|
- affected (JSONB) -- [{id, type, change}] 영향받은 대상
|
|
- parent_event_id (UUID) -- 이전 이벤트 (인과관계)
|
|
- result (JSONB) -- {status, outcome, data} 결과
|
|
- metadata (JSONB) -- 기타 확장 데이터
|
|
|
|
-- 인덱스
|
|
- (where_id, when_at) -- 시계열 조회
|
|
- (who_id, when_at) -- 주체별 이력
|
|
- parent_event_id -- 이벤트 체인
|
|
```
|
|
|
|
### JSONB 활용 예시
|
|
|
|
#### 1. 파일 전송
|
|
```json
|
|
{
|
|
"who_id": "user-uuid",
|
|
"what_type": "file_transfer",
|
|
"what_content": {"filename": "report.pdf", "size": 1024},
|
|
"to_who": [{"id": "user2-uuid", "type": "human"}],
|
|
"where_id": "slack-dm-123"
|
|
}
|
|
```
|
|
|
|
#### 2. 로빙 협업
|
|
```json
|
|
{
|
|
"who_id": "robeing1-uuid",
|
|
"what_type": "analysis_request",
|
|
"to_who": [{"id": "robeing2-uuid", "type": "robeing"}],
|
|
"using": [{"id": "robeing3-uuid", "type": "robeing", "resource": "market_data"}],
|
|
"result": {"status": "completed", "data": "..."}
|
|
}
|
|
```
|
|
|
|
#### 3. 그룹 대화
|
|
```json
|
|
{
|
|
"who_id": "user1-uuid",
|
|
"what_type": "message",
|
|
"what_content": {"text": "팀 회의 시작합니다"},
|
|
"to_who": [
|
|
{"id": "user2-uuid", "type": "human"},
|
|
{"id": "user3-uuid", "type": "human"},
|
|
{"id": "robeing-uuid", "type": "robeing"}
|
|
],
|
|
"where_id": "slack-channel-team"
|
|
}
|
|
```
|
|
|
|
## 이벤트 연결 메커니즘
|
|
|
|
### 간단 버전
|
|
1. **같은 장소**: where_id로 묶음
|
|
2. **같은 쓰레드**: parent_event_id 체인
|
|
3. **시간 순서**: when_at으로 정렬
|
|
|
|
### 쿼리 예시
|
|
```sql
|
|
-- 특정 채널의 모든 이벤트 (시간순)
|
|
SELECT * FROM event
|
|
WHERE where_id = 'slack-channel-123'
|
|
ORDER BY when_at;
|
|
|
|
-- 특정 이벤트 체인 (쓰레드)
|
|
WITH RECURSIVE thread AS (
|
|
SELECT * FROM event WHERE id = 'root-event-id'
|
|
UNION ALL
|
|
SELECT e.* FROM event e
|
|
JOIN thread t ON e.parent_event_id = t.id
|
|
)
|
|
SELECT * FROM thread ORDER BY when_at;
|
|
```
|
|
|
|
## Graph DB와의 관계
|
|
|
|
### Graph DB가 필요한 순간
|
|
1. **복잡한 관계 쿼리 반복**: 3-hop 이상 탐색
|
|
2. **실시간 추천**: 협업 필터링, 소셜 추천
|
|
3. **영향력 분석**: 정보 전파 경로, 네트워크 중심성
|
|
4. **다중 로빙 협업**: 최적 조합 찾기
|
|
|
|
### 현재 판단
|
|
**Event Stream만으로 충분**
|
|
- JSONB로 관계 저장 (충분히 유연)
|
|
- 시계열 분석이 핵심 (TimescaleDB 최적화)
|
|
- 나중에 필요하면 Graph DB 추가 (Read Model 패턴)
|
|
|
|
### Graph vs Event Stream
|
|
- **Graph**: "누구와 연결되었나" (공간적 관계망)
|
|
- **Event**: "언제 무엇을 했나" (시간적 흐름)
|
|
- 로빙의 핵심(성장/기억/감정) = 시간축 → **Graph는 optional**
|
|
|
|
## 마이그레이션 전략
|
|
|
|
### 1단계: Event 테이블 생성 (robeing_metrics DB)
|
|
```sql
|
|
CREATE TABLE event (
|
|
-- 6하원칙 컬럼들
|
|
...
|
|
) PARTITION BY RANGE (when_at);
|
|
|
|
-- TimescaleDB 하이퍼테이블 전환
|
|
SELECT create_hypertable('event', 'when_at');
|
|
```
|
|
|
|
### 2단계: 기존 conversation_log 데이터 변환
|
|
```sql
|
|
INSERT INTO event (who_id, what_type, what_content, when_at, where_id, ...)
|
|
SELECT
|
|
user_id as who_id,
|
|
'message' as what_type,
|
|
jsonb_build_object('text', message, 'response', response) as what_content,
|
|
timestamp as when_at,
|
|
channel_id as where_id,
|
|
...
|
|
FROM conversation_log;
|
|
```
|
|
|
|
### 3단계: 애플리케이션 코드 전환
|
|
- conversation_log 조회 → event 조회
|
|
- 새 대화 저장 → event INSERT
|
|
|
|
## 기대 효과
|
|
|
|
### 1. 확장성
|
|
- 모든 종류의 이벤트 저장 가능 (대화, 파일, 작업, 알림 등)
|
|
- 새로운 이벤트 타입 추가 시 스키마 변경 불필요 (JSONB)
|
|
|
|
### 2. 분석 역량
|
|
- 시계열 분석 최적화 (TimescaleDB)
|
|
- 이벤트 체인 추적 (베이지안 업데이트)
|
|
- 사용자/로빙별 이력 조회
|
|
|
|
### 3. 철학적 일관성
|
|
- 로빙도 이벤트 주체로 동등하게 취급
|
|
- user = "관계의 상대방" 명확화
|
|
- 성장과 기억의 원천 = 이벤트 흐름
|
|
|
|
## 교훈
|
|
|
|
### 기존 설계의 문제
|
|
- **conversation_log**: 챗봇 QA 로그 수준
|
|
- **1:1 가정**: 다중 참여자 협업 불가
|
|
- **user/robeing 분리**: 로빙을 도구로 취급
|
|
|
|
### 새로운 설계의 핵심
|
|
- **Event Stream**: 모든 행위를 시간순 이벤트로
|
|
- **6하원칙**: 표준화된 구조
|
|
- **JSONB**: 복잡한 관계도 유연하게
|
|
- **시간축 중심**: 베이지안 성장과 일치
|
|
|
|
## 참고사항
|
|
|
|
### 관련 문서
|
|
- `/home/admin/DOCS/300_architecture/database/tables.md`: DB 스키마
|
|
- `/home/admin/DOCS/100_philosophy/125_베이즈_성장과_관계의_철학.md`: 로빙 철학
|
|
|
|
### DB 위치
|
|
- **main_db**: user, robeing 테이블 (기존)
|
|
- **robeing_metrics**: event 테이블 (새로 추가)
|
|
- TimescaleDB 하이퍼테이블로 시계열 최적화 |