mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 20:46:23 +00:00
608 lines
15 KiB
Markdown
608 lines
15 KiB
Markdown
# Meeting Service 데이터베이스 스키마 분석 최종 보고서
|
||
|
||
**작성일**: 2025-10-28
|
||
**분석 대상**: Meeting Service (feat/meeting-ai 브랜치)
|
||
**분석 범위**: 마이그레이션 V1~V4, 엔티티 구조, 데이터 플로우
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
### 핵심 발견사항
|
||
|
||
1. **minutes 테이블에 content 필드가 없음**
|
||
- 실제 회의록 내용은 `minutes_sections.content`에 저장
|
||
- minutes 테이블은 메타데이터만 보유 (title, status, version 등)
|
||
|
||
2. **사용자별 회의록 완벽하게 지원 (V3)**
|
||
- `minutes.user_id = NULL`: AI 통합 회의록
|
||
- `minutes.user_id = 참석자ID`: 개인별 회의록
|
||
- 인덱스: `idx_minutes_meeting_user` (meeting_id, user_id)
|
||
|
||
3. **AI 분석 결과 구조화 저장 (V3, V4)**
|
||
- `agenda_sections`: 안건별 구조화된 요약
|
||
- `ai_summaries`: AI 처리 결과 캐싱
|
||
- `todos` (V4): 각 안건의 JSON으로 저장
|
||
|
||
4. **정규화 완료 (V2)**
|
||
- `meetings.participants` (CSV) → `meeting_participants` (테이블)
|
||
- 복합 PK: (meeting_id, user_id)
|
||
|
||
---
|
||
|
||
## 데이터베이스 구조 개요
|
||
|
||
### 테이블 분류
|
||
|
||
**핵심 테이블** (V1):
|
||
- `meetings`: 회의 기본 정보
|
||
- `minutes`: 회의록 메타데이터
|
||
- `minutes_sections`: 회의록 섹션 (실제 내용)
|
||
|
||
**참석자 관리** (V2):
|
||
- `meeting_participants`: 회의 참석자 정보
|
||
|
||
**AI 분석** (V3):
|
||
- `agenda_sections`: 안건별 AI 요약
|
||
- `ai_summaries`: AI 처리 결과 캐시
|
||
- `todos`: Todo 아이템 (expanded)
|
||
|
||
---
|
||
|
||
## 1. 핵심 테이블별 상세 분석
|
||
|
||
### 1.1 meetings (회의 기본 정보)
|
||
|
||
**구성**:
|
||
- PK: meeting_id (VARCHAR(50))
|
||
- 주요 필드: title, purpose, description
|
||
- 상태: SCHEDULED → IN_PROGRESS → COMPLETED
|
||
- 시간: scheduled_at, started_at, ended_at (V3)
|
||
|
||
**중요 변경**:
|
||
- V3에서 `ended_at` 추가
|
||
- 회의 정확한 종료 시간 기록
|
||
|
||
```sql
|
||
-- 조회 예시
|
||
SELECT * FROM meetings
|
||
WHERE status = 'COMPLETED'
|
||
AND ended_at >= NOW() - INTERVAL '7 days'
|
||
ORDER BY ended_at DESC;
|
||
```
|
||
|
||
---
|
||
|
||
### 1.2 minutes (회의록 메타데이터)
|
||
|
||
**구성**:
|
||
```
|
||
minutes_id (PK)
|
||
├─ meeting_id (FK)
|
||
├─ user_id (V3) ← NULL: AI 통합 회의록 / NOT NULL: 개인 회의록
|
||
├─ title
|
||
├─ status (DRAFT, FINALIZED)
|
||
├─ version
|
||
├─ created_by, finalized_by
|
||
└─ created_at, finalized_at
|
||
```
|
||
|
||
**중요**:
|
||
- **content 필드 없음** → minutes_sections에 저장
|
||
- 메타데이터만 관리 (생성자, 확정자, 버전 등)
|
||
|
||
**쿼리 패턴**:
|
||
```sql
|
||
-- AI 통합 회의록
|
||
SELECT * FROM minutes
|
||
WHERE meeting_id = ? AND user_id IS NULL;
|
||
|
||
-- 특정 사용자의 회의록
|
||
SELECT * FROM minutes
|
||
WHERE meeting_id = ? AND user_id = ?;
|
||
|
||
-- 복합 인덱스 활용: idx_minutes_meeting_user
|
||
```
|
||
|
||
---
|
||
|
||
### 1.3 minutes_sections (회의록 섹션 - 실제 내용)
|
||
|
||
**구성**:
|
||
```
|
||
section_id (PK)
|
||
├─ minutes_id (FK) ← 어느 회의록에 속하는가
|
||
├─ type (AGENDA, DISCUSSION, DECISION, ACTION_ITEM)
|
||
├─ title
|
||
├─ content ← ★ 실제 회의록 내용
|
||
├─ order
|
||
├─ verified (검증 완료)
|
||
├─ locked (수정 불가)
|
||
└─ locked_by
|
||
```
|
||
|
||
**핵심 특성**:
|
||
- **content**: 사용자가 작성한 실제 내용
|
||
- **locked**: finalize_minutes 호출시 잠금
|
||
- **verified**: 확정시 TRUE로 설정
|
||
|
||
**데이터 흐름**:
|
||
```
|
||
1. CreateMinutes → minutes_sections 초기 생성
|
||
2. UpdateMinutes → content 저장 (여러 번)
|
||
3. FinalizeMinutes → locked=TRUE, verified=TRUE
|
||
4. (locked 상태에서 수정 불가)
|
||
```
|
||
|
||
---
|
||
|
||
### 1.4 agenda_sections (AI 요약 - V3)
|
||
|
||
**구성**:
|
||
```
|
||
id (PK, UUID)
|
||
├─ minutes_id (FK) ← 통합 회의록만 (user_id=NULL)
|
||
├─ meeting_id (FK)
|
||
├─ agenda_number (1, 2, 3...)
|
||
├─ agenda_title
|
||
├─ ai_summary_short (1줄 요약)
|
||
├─ discussions (3-5문장 논의)
|
||
├─ decisions (JSON 배열)
|
||
├─ pending_items (JSON 배열)
|
||
├─ opinions (JSON 배열: {speaker, opinion})
|
||
└─ todos (JSON 배열 [V4])
|
||
```
|
||
|
||
**V4 추가 사항**:
|
||
- `todos` JSON 필드 추가
|
||
- 안건별 추출된 Todo 저장
|
||
|
||
```json
|
||
{
|
||
"title": "시장 조사 보고서 작성",
|
||
"assignee": "김민준",
|
||
"dueDate": "2025-02-15",
|
||
"description": "20-30대 타겟 시장 조사",
|
||
"priority": "HIGH"
|
||
}
|
||
```
|
||
|
||
**중요**:
|
||
- **통합 회의록만 분석** (user_id=NULL인 것)
|
||
- 참석자별 회의록(user_id NOT NULL)은 AI 분석 대상 아님
|
||
- minutes_id로 통합 회의록 참조
|
||
|
||
---
|
||
|
||
### 1.5 minutes_sections vs agenda_sections
|
||
|
||
| 항목 | minutes_sections | agenda_sections |
|
||
|------|-----------------|-----------------|
|
||
| **용도** | 사용자 작성 | AI 요약 |
|
||
| **모든 회의록** | ✓ 통합 + 개인 | ✗ 통합만 |
|
||
| **구조** | 순차적 섹션 | 안건별 구조화 |
|
||
| **내용 저장** | content (TEXT) | JSON 필드들 |
|
||
| **관계** | 1:N (minutes과) | N:1 (minutes과) |
|
||
| **목적** | 기록 | 분석/요약 |
|
||
|
||
**생성 흐름**:
|
||
```
|
||
회의 시작
|
||
↓
|
||
minutes 생성 (통합 + 개인)
|
||
↓
|
||
minutes_sections 생성 (4개 그룹)
|
||
↓
|
||
사용자 작성 중...
|
||
↓
|
||
회의 종료 → FinalizeMinutes
|
||
↓
|
||
minutes_sections locked
|
||
↓
|
||
AI 분석 (비동기)
|
||
↓
|
||
agenda_sections 생성 (통합 회의록 기반)
|
||
```
|
||
|
||
---
|
||
|
||
### 1.6 ai_summaries (AI 처리 결과 - V3)
|
||
|
||
**구성**:
|
||
```
|
||
id (PK, UUID)
|
||
├─ meeting_id (FK)
|
||
├─ summary_type (CONSOLIDATED, TODO_EXTRACTION)
|
||
├─ source_minutes_ids (JSON: 사용된 회의록 ID 배열)
|
||
├─ result (JSON: AI 응답 전체)
|
||
├─ processing_time_ms (처리 시간)
|
||
├─ model_version (claude-3.5-sonnet)
|
||
├─ keywords (JSON: 키워드 배열)
|
||
└─ statistics (JSON: {participants, agendas, todos})
|
||
```
|
||
|
||
**용도**:
|
||
- AI 처리 결과 캐싱
|
||
- 재처리 필요시 참조
|
||
- 성능 통계 기록
|
||
|
||
---
|
||
|
||
### 1.7 todos (Todo 아이템)
|
||
|
||
**기본 구조**:
|
||
```
|
||
todo_id (PK)
|
||
├─ meeting_id (FK)
|
||
├─ minutes_id (FK)
|
||
├─ title
|
||
├─ description
|
||
├─ assignee_id
|
||
├─ due_date
|
||
├─ status (PENDING, COMPLETED)
|
||
├─ priority (HIGH, MEDIUM, LOW)
|
||
└─ completed_at
|
||
```
|
||
|
||
**V3 추가 필드**:
|
||
```
|
||
├─ extracted_by (AI / MANUAL) ← AI 자동 추출 vs 수동
|
||
├─ section_reference (안건 참조)
|
||
└─ extraction_confidence (0.00~1.00) ← AI 신뢰도
|
||
```
|
||
|
||
**저장 전략**:
|
||
1. `agenda_sections.todos` (JSON): 간단한 Todo, 기본 저장 위치
|
||
2. `todos` 테이블: 상세 관리 필요시만 추가 저장
|
||
|
||
---
|
||
|
||
### 1.8 meeting_participants (참석자 관리 - V2)
|
||
|
||
**구성**:
|
||
```
|
||
PK: (meeting_id, user_id)
|
||
├─ invitation_status (PENDING, ACCEPTED, DECLINED)
|
||
├─ attended (BOOLEAN)
|
||
└─ created_at, updated_at
|
||
```
|
||
|
||
**V2 개선**:
|
||
- 이전: meetings.participants (CSV 문자열)
|
||
- 현재: 별도 테이블 (정규화)
|
||
- 복합 PK로 중복 방지
|
||
|
||
---
|
||
|
||
## 2. 회의록 작성 플로우 (전체)
|
||
|
||
### 단계별 데이터 변화
|
||
|
||
```
|
||
PHASE 1: 회의 준비
|
||
═════════════════════════════════════════════
|
||
1. CreateMeeting
|
||
→ INSERT meetings (status='SCHEDULED')
|
||
→ INSERT meeting_participants (5명)
|
||
|
||
PHASE 2: 회의 진행
|
||
═════════════════════════════════════════════
|
||
2. StartMeeting
|
||
→ UPDATE meetings SET status='IN_PROGRESS'
|
||
|
||
3. CreateMinutes (회의 중)
|
||
→ INSERT minutes (user_id=NULL) × 1 (통합)
|
||
→ INSERT minutes (user_id=user_id) × 5 (개인)
|
||
→ INSERT minutes_sections (초기 생성)
|
||
|
||
4. UpdateMinutes (여러 번)
|
||
→ UPDATE minutes_sections SET content='...'
|
||
|
||
PHASE 3: 회의 종료
|
||
═════════════════════════════════════════════
|
||
5. EndMeeting
|
||
→ UPDATE meetings SET
|
||
status='COMPLETED',
|
||
ended_at=NOW() [V3]
|
||
|
||
PHASE 4: 회의록 최종화
|
||
═════════════════════════════════════════════
|
||
6. FinalizeMinutes
|
||
→ UPDATE minutes SET
|
||
status='FINALIZED'
|
||
→ UPDATE minutes_sections SET
|
||
locked=TRUE,
|
||
verified=TRUE
|
||
|
||
PHASE 5: AI 분석 (비동기)
|
||
═════════════════════════════════════════════
|
||
7. MinutesAnalysisEventConsumer
|
||
→ Read minutes (user_id=NULL)
|
||
→ Read minutes_sections
|
||
→ Call AI Service
|
||
→ INSERT agenda_sections [V3]
|
||
→ INSERT ai_summaries [V3]
|
||
→ INSERT todos [V3 확장]
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 사용자별 회의록 구조
|
||
|
||
### 데이터 분리 방식
|
||
|
||
**1개 회의 (참석자 5명)**:
|
||
|
||
```
|
||
meetings: 1개
|
||
├─ meeting_id = 'meeting-001'
|
||
└─ status = COMPLETED
|
||
|
||
meeting_participants: 5개
|
||
├─ (meeting-001, user1@example.com)
|
||
├─ (meeting-001, user2@example.com)
|
||
├─ (meeting-001, user3@example.com)
|
||
├─ (meeting-001, user4@example.com)
|
||
└─ (meeting-001, user5@example.com)
|
||
|
||
minutes: 6개 [V3]
|
||
├─ (id=consol-1, meeting_id=meeting-001, user_id=NULL)
|
||
│ → 통합 회의록 (AI 분석 대상)
|
||
├─ (id=user1-min, meeting_id=meeting-001, user_id=user1@example.com)
|
||
│ → 사용자1 개인 회의록
|
||
├─ (id=user2-min, meeting_id=meeting-001, user_id=user2@example.com)
|
||
│ → 사용자2 개인 회의록
|
||
├─ ... (user3, user4, user5)
|
||
└─
|
||
|
||
minutes_sections: 수십 개 (6개 회의록 × N개 섹션)
|
||
├─ Group 1: consol-1의 섹션들 (AI 작성)
|
||
├─ Group 2: user1-min의 섹션들 (사용자1 작성)
|
||
├─ Group 3: user2-min의 섹션들 (사용자2 작성)
|
||
└─ ... (user3, user4, user5)
|
||
|
||
agenda_sections: 5개 [V3]
|
||
├─ (id=ag-1, minutes_id=consol-1, agenda_number=1)
|
||
├─ (id=ag-2, minutes_id=consol-1, agenda_number=2)
|
||
└─ ... (3, 4, 5)
|
||
```
|
||
|
||
**핵심**:
|
||
- 참석자별 회의록은 minutes.user_id로 구분
|
||
- 인덱스 활용: `idx_minutes_meeting_user`
|
||
- AI 분석: 통합 회의록만 (user_id=NULL)
|
||
|
||
---
|
||
|
||
## 4. 마이그레이션 변경사항 요약
|
||
|
||
### V2 (2025-10-27)
|
||
|
||
```sql
|
||
-- meeting_participants 테이블 생성
|
||
CREATE TABLE meeting_participants (
|
||
meeting_id, user_id (복합 PK),
|
||
invitation_status, attended
|
||
)
|
||
|
||
-- 데이터 마이그레이션
|
||
SELECT TRIM(participant) FROM meetings.participants (CSV)
|
||
→ INSERT INTO meeting_participants
|
||
|
||
-- meetings.participants 컬럼 삭제
|
||
ALTER TABLE meetings DROP COLUMN participants
|
||
```
|
||
|
||
**영향**: 정규화 완료, 중복 데이터 제거
|
||
|
||
---
|
||
|
||
### V3 (2025-10-28)
|
||
|
||
#### 3-1. minutes 테이블 확장
|
||
```sql
|
||
ALTER TABLE minutes ADD COLUMN user_id VARCHAR(100);
|
||
CREATE INDEX idx_minutes_meeting_user ON minutes(meeting_id, user_id);
|
||
```
|
||
|
||
**의미**: 사용자별 회의록 지원
|
||
|
||
---
|
||
|
||
#### 3-2. agenda_sections 테이블 신규
|
||
```sql
|
||
CREATE TABLE agenda_sections (
|
||
id, minutes_id, meeting_id,
|
||
agenda_number, agenda_title,
|
||
ai_summary_short, discussions,
|
||
decisions (JSON),
|
||
pending_items (JSON),
|
||
opinions (JSON)
|
||
)
|
||
```
|
||
|
||
**의미**: AI 요약을 구조화된 형식으로 저장
|
||
|
||
---
|
||
|
||
#### 3-3. ai_summaries 테이블 신규
|
||
```sql
|
||
CREATE TABLE ai_summaries (
|
||
id, meeting_id, summary_type,
|
||
source_minutes_ids (JSON),
|
||
result (JSON),
|
||
processing_time_ms,
|
||
model_version,
|
||
keywords (JSON),
|
||
statistics (JSON)
|
||
)
|
||
```
|
||
|
||
**의미**: AI 처리 결과 캐싱
|
||
|
||
---
|
||
|
||
#### 3-4. todos 테이블 확장
|
||
```sql
|
||
ALTER TABLE todos ADD COLUMN extracted_by VARCHAR(50) DEFAULT 'AI';
|
||
ALTER TABLE todos ADD COLUMN section_reference VARCHAR(200);
|
||
ALTER TABLE todos ADD COLUMN extraction_confidence DECIMAL(3,2);
|
||
```
|
||
|
||
**의미**: AI 자동 추출 추적
|
||
|
||
---
|
||
|
||
### V4 (2025-10-28)
|
||
|
||
```sql
|
||
ALTER TABLE agenda_sections ADD COLUMN todos JSON;
|
||
```
|
||
|
||
**의미**: 안건별 Todo를 JSON으로 저장
|
||
|
||
---
|
||
|
||
## 5. 성능 최적화
|
||
|
||
### 현재 인덱스
|
||
|
||
```
|
||
meetings:
|
||
├─ PK: meeting_id
|
||
|
||
minutes:
|
||
├─ PK: id
|
||
└─ idx_minutes_meeting_user (meeting_id, user_id) [V3]
|
||
|
||
minutes_sections:
|
||
├─ PK: id
|
||
└─ (minutes_id로 FK 지원)
|
||
|
||
agenda_sections: [V3]
|
||
├─ PK: id
|
||
├─ idx_sections_meeting (meeting_id)
|
||
├─ idx_sections_agenda (meeting_id, agenda_number)
|
||
└─ idx_sections_minutes (minutes_id)
|
||
|
||
ai_summaries: [V3]
|
||
├─ PK: id
|
||
├─ idx_summaries_meeting (meeting_id)
|
||
├─ idx_summaries_type (meeting_id, summary_type)
|
||
└─ idx_summaries_created (created_at)
|
||
|
||
todos:
|
||
├─ PK: todo_id
|
||
├─ idx_todos_extracted (extracted_by) [V3]
|
||
└─ idx_todos_meeting (meeting_id) [V3]
|
||
|
||
meeting_participants: [V2]
|
||
├─ PK: (meeting_id, user_id)
|
||
├─ idx_user_id (user_id)
|
||
└─ idx_invitation_status (invitation_status)
|
||
```
|
||
|
||
### 추천 추가 인덱스
|
||
|
||
```sql
|
||
-- 자주 조회하는 패턴
|
||
CREATE INDEX idx_minutes_status_created
|
||
ON minutes(status, created_at DESC);
|
||
|
||
CREATE INDEX idx_agenda_meeting_created
|
||
ON agenda_sections(meeting_id, created_at DESC);
|
||
|
||
CREATE INDEX idx_todos_meeting_assignee
|
||
ON todos(meeting_id, assignee_id);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 핵심 질문 답변
|
||
|
||
### Q1: minutes 테이블에 content 필드가 있는가?
|
||
**A**: **없음**
|
||
- minutes: 메타데이터만 (title, status, version 등)
|
||
- 실제 내용: minutes_sections.content
|
||
|
||
### Q2: minutes_section과 agenda_sections의 차이점?
|
||
**A**:
|
||
| 항목 | minutes_sections | agenda_sections |
|
||
|------|-----------------|-----------------|
|
||
| 목적 | 사용자 작성 | AI 요약 |
|
||
| 모든 회의록 | O | X (통합만) |
|
||
| 내용 저장 | content (TEXT) | JSON |
|
||
|
||
### Q3: 사용자별 회의록 저장 방식?
|
||
**A**:
|
||
- minutes.user_id로 구분
|
||
- NULL: AI 통합회의록
|
||
- NOT NULL: 개인별 회의록
|
||
- 인덱스: idx_minutes_meeting_user
|
||
|
||
### Q4: V3, V4 주요 변경?
|
||
**A**:
|
||
- V3: user_id, agenda_sections, ai_summaries, todos 확장
|
||
- V4: agenda_sections.todos JSON 추가
|
||
|
||
---
|
||
|
||
## 7. 개발 시 주의사항
|
||
|
||
### Do's ✓
|
||
- minutes_sections.content에 실제 내용 저장
|
||
- AI 분석시 user_id=NULL인 minutes만 처리
|
||
- agenda_sections.todos와 todos 테이블 동시 저장 (필요시)
|
||
- 복합 인덱스 활용 (meeting_id, user_id)
|
||
|
||
### Don'ts ✗
|
||
- minutes 테이블에 content 저장 (없음)
|
||
- 참석자별 회의록(user_id NOT NULL)을 AI 분석 (통합만)
|
||
- agenda_sections를 모든 minutes에 생성 (통합만)
|
||
- 인덱스 무시한 풀 스캔
|
||
|
||
---
|
||
|
||
## 8. 파일 위치 및 참조
|
||
|
||
**마이그레이션 파일**:
|
||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V2__*.sql`
|
||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V3__*.sql`
|
||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V4__*.sql`
|
||
|
||
**엔티티**:
|
||
- `MeetingEntity`, `MinutesEntity`, `MinutesSectionEntity`
|
||
- `AgendaSectionEntity`, `TodoEntity`, `MeetingParticipantEntity`
|
||
|
||
**서비스**:
|
||
- `MinutesService`, `MinutesSectionService`
|
||
- `MinutesAnalysisEventConsumer` (비동기)
|
||
|
||
---
|
||
|
||
## 9. 결론
|
||
|
||
### 핵심 설계 원칙
|
||
1. **메타데이터 vs 내용 분리**: minutes (메타) vs minutes_sections (내용)
|
||
2. **사용자별 격리**: user_id 컬럼으로 개인 회의록 관리
|
||
3. **AI 결과 구조화**: JSON으로 유연성과 성능 확보
|
||
4. **정규화 완료**: 참석자 정보 테이블화
|
||
|
||
### 검증 사항
|
||
- V3, V4 마이그레이션 정상 적용
|
||
- 모든 인덱스 생성됨
|
||
- 관계 설정 정상 (FK, 1:N)
|
||
|
||
### 다음 단계
|
||
- 성능 모니터링 (쿼리 실행 계획)
|
||
- 추가 인덱스 검토
|
||
- AI 분석 결과 검증
|
||
- 참석자별 회의록 사용성 테스트
|
||
|
||
---
|
||
|
||
**문서 정보**:
|
||
- 작성자: Database Architecture Analysis
|
||
- 대상 서비스: Meeting Service (AI 통합 회의록)
|
||
- 최종 버전: 2025-10-28
|