hgzero/claude/SCHEMA-REPORT-SUMMARY.md

608 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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