작업 중: Meeting AI 통합 개발 진행 상황 저장

This commit is contained in:
Minseo-Jo
2025-10-29 09:15:23 +09:00
parent 143721d106
commit 621d4c16df
53 changed files with 17042 additions and 17928 deletions
+322
View File
@@ -0,0 +1,322 @@
# Meeting Service 데이터베이스 스키마 분석 문서
## 생성된 문서 목록
본 분석은 Meeting Service의 데이터베이스 스키마를 전방위적으로 분석한 결과입니다.
### 1. SCHEMA-REPORT-SUMMARY.md (메인 보고서)
**파일**: `/Users/jominseo/HGZero/claude/SCHEMA-REPORT-SUMMARY.md`
**내용**:
- Executive Summary (핵심 발견사항)
- 데이터베이스 구조 개요
- 테이블별 상세 분석 (1.1~1.8)
- 회의록 작성 플로우
- 사용자별 회의록 구조
- 마이그레이션 변경사항 (V2, V3, V4)
- 성능 최적화 포인트
- 핵심 질문 답변
- 개발 시 주의사항
**빠르게 읽기**: Executive Summary부터 시작하세요.
---
### 2. database-schema-analysis.md (상세 분석)
**파일**: `/Users/jominseo/HGZero/claude/database-schema-analysis.md`
**내용**:
- 마이그레이션 파일 현황 (V1~V4)
- 각 테이블의 상세 구조
- minutes vs agenda_sections 비교 분석
- 회의록 작성 플로우에서의 테이블 사용
- 사용자별 회의록 저장 구조
- SQL 쿼리 패턴
- 데이터 정규화 현황
- 인덱스 최적화 방안
- 데이터 저장 크기 예상
**상세 분석 필요시**: 이 문서를 참고하세요.
---
### 3. data-flow-diagram.md (흐름도)
**파일**: `/Users/jominseo/HGZero/claude/data-flow-diagram.md`
**내용**:
- 전체 시스템 플로우 (7 Phase)
- 상태 전이 다이어그램
- 사용자별 회의록 데이터 구조
- 인덱스 활용 쿼리 예시
- 데이터 저장 크기 예상
**시각적 이해 필요시**: 이 문서를 참고하세요.
---
### 4. database-diagram.puml (ER 다이어그램)
**파일**: `/Users/jominseo/HGZero/claude/database-diagram.puml`
**포맷**: PlantUML (UML 형식)
**내용**:
- 모든 테이블과 관계
- V2, V3, V4 마이그레이션 표시
- 주요 필드 강조
**다이어그램 생성**:
```bash
# PlantUML로 PNG 생성
plantuml database-diagram.puml -o database-diagram.png
# 또는 온라인 에디터
https://www.plantuml.com/plantuml/uml/
```
---
## 핵심 발견사항 한눈에 보기
### 1. Minutes 테이블 구조
```
잘못된 이해: minutes.content ← 회의록 내용
올바른 구조: minutes_sections.content ← 회의록 내용
minutes ← 메타데이터만 (title, status, version)
```
### 2. 사용자별 회의록 (V3)
```
minutes.user_id = NULL → AI 통합 회의록
minutes.user_id = 'user@.com' → 개인 회의록
인덱스: idx_minutes_meeting_user(meeting_id, user_id)
```
### 3. AI 분석 결과 저장 (V3, V4)
```
agenda_sections → 안건별 구조화된 요약
└─ todos (JSON) → 추출된 Todo [V4]
ai_summaries → 전체 AI 처리 결과 캐시
todos 테이블 → 상세 관리 필요시만
```
### 4. 정규화 (V2)
```
이전: meetings.participants = "user1,user2,user3"
현재: meeting_participants (테이블, 복합PK)
```
---
## 빠른 참조표
### 회의록 작성 플로우
| 단계 | API | 데이터베이스 변화 |
|------|-----|-----------------|
| 1 | CreateMeeting | meetings INSERT |
| 2 | StartMeeting | meetings.status = IN_PROGRESS |
| 3 | CreateMinutes | minutes INSERT (통합 + 개인) |
| 4 | UpdateMinutes | minutes_sections.content UPDATE |
| 5 | EndMeeting | meetings.status = COMPLETED, ended_at [V3] |
| 6 | FinalizeMinutes | minutes.status = FINALIZED, sections locked |
| 7 | AI 분석 | agenda_sections, ai_summaries, todos INSERT |
### 테이블별 핵심 필드
```
meetings : meeting_id, status, ended_at [V3]
minutes : id, meeting_id, user_id [V3], status
minutes_sections : id, minutes_id, content ★
agenda_sections : id, minutes_id, agenda_number, todos [V4]
ai_summaries : id, meeting_id, result (JSON)
todos : todo_id, extracted_by [V3], extraction_confidence [V3]
```
### 인덱스
```
PRIMARY:
idx_minutes_meeting_user (meeting_id, user_id) [V3]
idx_sections_meeting (meeting_id) [V3]
idx_sections_agenda (meeting_id, agenda_number) [V3]
SECONDARY:
idx_todos_extracted (extracted_by) [V3]
idx_todos_meeting (meeting_id) [V3]
idx_summaries_type (meeting_id, summary_type) [V3]
```
---
## 마이그레이션 타임라인
```
V1 (초기)
├─ meetings, minutes, minutes_sections
├─ todos, meeting_analysis
└─ JPA Hibernate로 자동 생성
V2 (2025-10-27)
├─ meeting_participants 테이블 생성
├─ meetings.participants (CSV) 마이그레이션
└─ 정규화 완료
V3 (2025-10-28) ★ 주요 변경
├─ minutes.user_id 추가 (사용자별 회의록)
├─ agenda_sections 테이블 신규 (AI 요약)
├─ ai_summaries 테이블 신규 (AI 결과 캐시)
└─ todos 테이블 확장 (extracted_by, extraction_confidence)
V4 (2025-10-28)
└─ agenda_sections.todos JSON 필드 추가
```
---
## 자주 묻는 질문
### Q: minutes 테이블에 content 필드가 있나요?
**A**: 없습니다. 실제 회의록 내용은 `minutes_sections.content`에 저장됩니다.
`minutes` 테이블은 메타데이터만 보유합니다 (title, status, version 등).
### Q: 사용자별 회의록은 어떻게 구분되나요?
**A**: `minutes.user_id` 컬럼으로 구분됩니다.
- NULL: AI 통합 회의록
- NOT NULL: 개인별 회의록 (각 참석자마다 생성)
### Q: AI 분석은 모든 회의록을 처리하나요?
**A**: 아니요. 통합 회의록(`user_id=NULL`)만 분석합니다.
개인별 회의록(`user_id NOT NULL`)은 개인 기록용이며 AI 분석 대상이 아닙니다.
### Q: agenda_sections와 minutes_sections의 차이는?
**A**:
- `minutes_sections`: 사용자가 작성한 순차적 회의록 섹션
- `agenda_sections`: AI가 분석한 안건별 구조화된 요약
### Q: Todo는 어디에 저장되나요?
**A**: 두 곳에 저장 가능합니다.
1. `agenda_sections.todos` (JSON): 안건별 요약의 일부
2. `todos` 테이블: 상세 관리 필요시만
---
## 성능 최적화 팁
### 복합 인덱스 활용
```sql
-- 가장 중요한 쿼리 (V3)
SELECT * FROM minutes
WHERE meeting_id = ? AND user_id = ?;
: idx_minutes_meeting_user (meeting_id, user_id)
```
### 추천 추가 인덱스
```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);
```
### 쿼리 패턴
```sql
-- 통합 회의록 조회 (가장 흔함)
SELECT m.*, ms.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
WHERE m.meeting_id = ? AND m.user_id IS NULL
-- 개인 회의록 조회
SELECT m.*, ms.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
WHERE m.meeting_id = ? AND m.user_id = ?
-- AI 분석 결과 조회
SELECT * FROM agenda_sections
WHERE meeting_id = ? ORDER BY agenda_number
```
---
## 문서 읽기 순서 추천
### 1단계: 빠른 이해 (5분)
`SCHEMA-REPORT-SUMMARY.md`의 Executive Summary만 읽기
### 2단계: 구조 이해 (15분)
`database-diagram.puml` (다이어그램 확인)
`data-flow-diagram.md`의 Phase 1~7 읽기
### 3단계: 상세 이해 (30분)
`SCHEMA-REPORT-SUMMARY.md` 전체 읽기
`database-schema-analysis.md`의 핵심 섹션 읽기
### 4단계: 개발 참고 (필요시)
`database-schema-analysis.md`의 쿼리 예시
`data-flow-diagram.md`의 인덱스 활용 섹션
---
## 개발 체크리스트
회의록 작성 기능 개발시:
### 데이터 저장
- [ ] 회의록 내용은 `minutes_sections.content`에 저장
- [ ] `minutes` 테이블에는 메타데이터만 저장 (title, status)
- [ ] 회의 종료시 `minutes.user_id` 값 확인 (NULL vs 사용자ID)
### AI 분석
- [ ] 통합 회의록(`user_id=NULL`)만 AI 분석 대상으로 처리
- [ ] `agenda_sections`은 통합 회의록에만 생성
- [ ] `ai_summaries`에 전체 결과 캐싱
### 쿼리 성능
- [ ] 복합 인덱스 활용: `idx_minutes_meeting_user`
- [ ] 조회시 `WHERE meeting_id AND user_id` 조건 사용
- [ ] 기존 인덱스 모두 생성 확인
### 데이터 무결성
- [ ] 회의 종료시 `ended_at` 기록 (V3)
- [ ] 최종화시 `minutes_sections` locked 처리
- [ ] AI 추출 Todo의 `extraction_confidence` 값 확인
---
## 관련 파일 위치
**마이그레이션**:
```
/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/
├─ V2__create_meeting_participants_table.sql
├─ V3__add_meeting_end_support.sql
└─ V4__add_todos_to_agenda_sections.sql
```
**엔티티**:
```
/Users/jominseo/HGZero/meeting/src/main/java/.../entity/
├─ MeetingEntity.java
├─ MinutesEntity.java
├─ MinutesSectionEntity.java
├─ AgendaSectionEntity.java [V3]
├─ TodoEntity.java
└─ MeetingParticipantEntity.java [V2]
```
**서비스**:
```
/Users/jominseo/HGZero/meeting/src/main/java/.../service/
├─ MinutesService.java
├─ MinutesSectionService.java
└─ MinutesAnalysisEventConsumer.java (비동기 AI 분석)
```
---
## 지원
이 문서에 대한 추가 질문이나 불명확한 부분이 있으면:
1. `SCHEMA-REPORT-SUMMARY.md`의 "핵심 질문 답변" 섹션 확인
2. `database-schema-analysis.md`에서 상세 내용 검색
3. `data-flow-diagram.md`에서 흐름도 재확인
---
**문서 작성일**: 2025-10-28
**분석 대상**: Meeting Service (feat/meeting-ai 브랜치)
**마이그레이션 버전**: V1~V4
**상태**: 완료 및 검증됨
+607
View File
@@ -0,0 +1,607 @@
# 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
+560
View File
@@ -0,0 +1,560 @@
# Meeting Service 데이터 플로우 다이어그램
## 1. 전체 시스템 플로우
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 회의 생명주기 데이터 플로우 │
└──────────────────────────────────────────────────────────────────────────────┘
Phase 1: 회의 준비 단계
════════════════════════════════════════════════════════════════════════════════
사용자가 회의 생성
┌─────────────────────────────────────────────────────────────┐
│ 1-1. CreateMeeting API │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO meetings ( │
│ meeting_id, title, purpose, scheduled_at, │
│ organizer_id, status, created_at │
│ ) │
│ VALUES (...) │
│ │
│ + INSERT INTO meeting_participants [V2] │
│ (meeting_id, user_id, invitation_status) │
│ FOR EACH participant │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: SCHEDULED status
✓ meeting_participants: PENDING status
Phase 2: 회의 진행 중
════════════════════════════════════════════════════════════════════════════════
회의 시작 (start_meeting API)
┌─────────────────────────────────────────────────────────────┐
│ 2-1. StartMeeting UseCase │
│ ────────────────────────────────────────────────────────────│
│ UPDATE meetings SET │
│ status = 'IN_PROGRESS', │
│ started_at = NOW() │
│ WHERE meeting_id = ? │
└─────────────────────────────────────────────────────────────┘
회의 중 회의록 작성
┌─────────────────────────────────────────────────────────────┐
│ 2-2. CreateMinutes API (회의 시작 후) │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO minutes ( │
│ id, meeting_id, user_id, title, status, │
│ created_by, version, created_at │
│ ) VALUES ( │
│ 'consolidated-minutes-1', 'meeting-001', │
│ NULL, [V3] ← AI 통합 회의록 표시 │
│ '2025년 1월 10일 회의', 'DRAFT', ... │
│ ) │
│ │
│ + 각 참석자별 회의록도 동시 생성: │
│ INSERT INTO minutes ( │
│ id, meeting_id, user_id, ... │
│ ) VALUES ( │
│ 'user-minutes-user1', 'meeting-001', │
│ 'user1@example.com', [V3] ← 참석자 구분 │
│ ... │
│ ) │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: IN_PROGRESS
✓ minutes (multiple records):
- 1개의 통합 회의록 (user_id=NULL)
- N개의 참석자별 회의록 (user_id=참석자ID)
✓ minutes_sections: 초기 섹션 생성
Phase 3: 회의록 작성 중
════════════════════════════════════════════════════════════════════════════════
사용자가 회의록 섹션 작성
┌─────────────────────────────────────────────────────────────┐
│ 3-1. UpdateMinutes API (여러 번) │
│ ────────────────────────────────────────────────────────────│
│ │
│ 각 섹션별로: │
│ INSERT INTO minutes_sections ( │
│ id, minutes_id, type, title, content, order │
│ ) VALUES ( │
│ 'section-1', 'consolidated-minutes-1', │
│ 'DISCUSSION', '신제품 기획 방향', │
│ '신제품의 주요 타겟은 20-30대 직장인으로 설정...', │
│ 1 │
│ ) │
│ │
│ UPDATE minutes_sections SET │
│ content = '...', │
│ updated_at = NOW() │
│ WHERE id = 'section-1' │
│ │
│ ★ 중요: content 컬럼에 실제 회의록 내용 저장! │
│ minutes 테이블에는 content가 없음 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ minutes: status='DRAFT'
✓ minutes_sections: 사용자가 작성한 내용 축적
✓ 각 참석자가 자신의 회의록을 독립적으로 작성
Phase 4: 회의 종료
════════════════════════════════════════════════════════════════════════════════
회의 종료 (end_meeting API)
┌─────────────────────────────────────────────────────────────┐
│ 4-1. EndMeeting UseCase [V3 추가] │
│ ────────────────────────────────────────────────────────────│
│ UPDATE meetings SET │
│ status = 'COMPLETED', │
│ ended_at = NOW() [V3] ← 종료 시간 기록 │
│ WHERE meeting_id = ? │
│ │
│ ★ 중요: 회의 종료와 동시에 회의록 준비 시작 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: status='COMPLETED', ended_at=현재시간
✓ minutes: 계속 DRAFT (사용자 추가 편집 가능)
Phase 5: 회의록 최종화
════════════════════════════════════════════════════════════════════════════════
사용자가 회의록 최종화 요청
┌─────────────────────────────────────────────────────────────┐
│ 5-1. FinalizeMinutes API │
│ ────────────────────────────────────────────────────────────│
│ UPDATE minutes SET │
│ status = 'FINALIZED', │
│ finalized_by = ?, │
│ finalized_at = NOW(), │
│ version = version + 1 │
│ WHERE id = 'consolidated-minutes-1' │
│ │
│ UPDATE minutes_sections SET │
│ locked = TRUE, │
│ locked_by = ?, │
│ verified = TRUE │
│ WHERE minutes_id = 'consolidated-minutes-1' │
│ │
│ ★ 중요: minutes_id를 통해 관련된 모든 섹션 잠금 │
└─────────────────────────────────────────────────────────────┘
Event 발생: MinutesAnalysisRequestEvent (Async)
DB State:
✓ minutes: status='FINALIZED'
✓ minutes_sections: locked=TRUE, verified=TRUE
✓ 모든 섹션이 수정 불가능
Phase 6: AI 분석 처리 (비동기 - MinutesAnalysisEventConsumer)
════════════════════════════════════════════════════════════════════════════════
이벤트 수신: MinutesAnalysisRequestEvent
┌─────────────────────────────────────────────────────────────┐
│ 6-1. 통합 회의록 조회 (user_id=NULL) │
│ ────────────────────────────────────────────────────────────│
│ SELECT m.*, GROUP_CONCAT(ms.content) AS full_content │
│ FROM minutes m │
│ LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id │
│ WHERE m.meeting_id = ? AND m.user_id IS NULL │
│ ORDER BY ms.order │
│ │
│ ★ 참석자별 회의록은 AI 분석 대상이 아님 │
│ user_id IS NOT NULL인 것들은 개인 기록용 │
└─────────────────────────────────────────────────────────────┘
AI Service 호출 (Claude API)
AI가 회의록 분석
- 안건별로 분리
- 요약 생성
- 결정사항 추출
- 보류사항 추출
- Todo 추출
┌─────────────────────────────────────────────────────────────┐
│ 6-2. agenda_sections 생성 [V3] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO agenda_sections ( │
│ id, minutes_id, meeting_id, agenda_number, │
│ agenda_title, ai_summary_short, discussions, │
│ decisions, pending_items, opinions, todos [V4] │
│ ) VALUES ( │
│ 'uuid-1', 'consolidated-minutes-1', 'meeting-001', │
│ 1, '신제품 기획 방향성', │
│ '타겟 고객을 20-30대로 설정...', │
│ '신제품의 주요 타겟 고객층을 20-30대...', │
│ ["타겟 고객: 20-30대 직장인", "UI 개선 최우선"], │
│ [], │
│ [{"speaker": "김민준", "opinion": "..."}], │
│ [ │
│ {"title": "시장 조사", "assignee": "김민준", │
│ "dueDate": "2025-02-15", "priority": "HIGH"} │
│ ] [V4] │
│ ) │
│ │
│ FOR EACH agenda detected by AI │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6-3. ai_summaries 저장 [V3] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO ai_summaries ( │
│ id, meeting_id, summary_type, │
│ source_minutes_ids, result, processing_time_ms, │
│ model_version, keywords, statistics, created_at │
│ ) VALUES ( │
│ 'summary-uuid-1', 'meeting-001', 'CONSOLIDATED', │
│ ["consolidated-minutes-1"], │
│ {AI 응답 전체 JSON}, │
│ 2500, │
│ 'claude-3.5-sonnet', │
│ ["신제품", "타겟층", "UI개선"], │
│ {"participants": 5, "agendas": 3, "todos": 8}, │
│ NOW() │
│ ) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6-4. todos 저장 [V3 확장] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO todos ( │
│ todo_id, meeting_id, minutes_id, title, │
│ assignee_id, due_date, status, priority, │
│ extracted_by, section_reference, │
│ extraction_confidence, created_at │
│ ) VALUES ( │
│ 'todo-uuid-1', 'meeting-001', │
│ 'consolidated-minutes-1', '시장 조사 보고서 작성', │
│ 'user1@example.com', '2025-02-15', 'PENDING', 'HIGH', │
│ 'AI', '안건 1: 신제품 기획', [V3] │
│ 0.95, [V3] 신뢰도 │
│ NOW() │
│ ) │
│ │
│ ★ 주의: agenda_sections.todos (JSON)에도 동시 저장 │
│ 개별 관리 필요시만 todos 테이블에 저장 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ agenda_sections: AI 요약 결과 저장됨 (안건별)
✓ ai_summaries: AI 처리 결과 캐시
✓ todos: AI 추출 Todo (extracted_by='AI')
Phase 7: 회의록 및 분석 결과 조회
════════════════════════════════════════════════════════════════════════════════
Case 1: 통합 회의록 조회
─────────────────────────────────────────────────────────────
SELECT m.*, ms.*, ag.*, ai.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
LEFT JOIN agenda_sections ag ON m.id = ag.minutes_id
LEFT JOIN ai_summaries ai ON m.meeting_id = ai.meeting_id
WHERE m.meeting_id = 'meeting-001'
AND m.user_id IS NULL [V3]
ORDER BY ms.order
Case 2: 특정 사용자의 개인 회의록 조회
─────────────────────────────────────────────────────────────
SELECT m.*, ms.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
WHERE m.meeting_id = 'meeting-001'
AND m.user_id = 'user1@example.com' [V3]
ORDER BY ms.order
→ 개인이 작성한 회의록만 조회
→ AI 분석 결과(agenda_sections) 미포함
Case 3: AI 분석 결과만 조회
─────────────────────────────────────────────────────────────
SELECT ag.* FROM agenda_sections ag
WHERE ag.meeting_id = 'meeting-001'
ORDER BY ag.agenda_number
→ 안건별 AI 요약
→ todos JSON 필드 포함 (V4)
Case 4: 추출된 Todo 조회
─────────────────────────────────────────────────────────────
SELECT * FROM todos
WHERE meeting_id = 'meeting-001'
AND extracted_by = 'AI' [V3]
ORDER BY priority DESC, due_date ASC
또는 agenda_sections의 JSON todos 필드 사용
└──────────────────────────────────────────────────────────────────────────────┘
```
---
## 2. 상태 전이 다이어그램 (State Transition)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ meetings 테이블 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
[생성]
├─────────────────────────┐
▼ │
SCHEDULED │ (시간 경과)
(scheduled_at 설정) │
│ │
│ start_meeting API │
▼ │
IN_PROGRESS │
(started_at 설정) │
│ │
│ end_meeting API [V3] │
▼ │
COMPLETED │
(ended_at 설정) [V3 추가] ├─────────────────────────┐
│ │ │
└─────────────────────────┘ │
│ 회의록 최종화 │
│ (finalize_minutes API) │
▼ │
minutes: FINALIZED │
(status='FINALIZED') │
│ │
│ (비동기 이벤트) │
▼ │
AI 분석 완료 │
agenda_sections 생성 │
ai_summaries 생성 │
todos 추출 │
│ │
└─────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ minutes 테이블 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
CREATE DRAFT
(minutes 생성) ───────────► (사용자 작성 중)
update_minutes API
(섹션 추가/수정)
finalize_minutes API
FINALIZED
(AI 분석 대기 중)
(비동기 처리 완료)
분석 완료 (상태 유지)
agenda_sections 생성됨
ai_summaries 생성됨
┌─────────────────────────────────────────────────────────────────────────────┐
│ minutes_sections 잠금 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
편집 가능
(locked=FALSE)
│ finalize_minutes
잠금됨
(locked=TRUE, locked_by=user_id)
└─────► 수정 불가
verified=TRUE
┌─────────────────────────────────────────────────────────────────────────────┐
│ todos 완료 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
PENDING
(생성됨)
│ todo 완료 API
COMPLETED
(completed_at 설정)
```
---
## 3. 사용자별 회의록 데이터 구조
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 1개 회의 (meetings: meeting-001)
│ ├─ 참석자: user1, user2, user3
└──────────────────────────────────────────────────────────────────────────────┘
회의 종료 → minutes 테이블에 여러 레코드 생성
┌─────────────────────────────────────────────────────────────────┐
│ minutes 테이블 (3개 레코드 생성) │
├─────────────────────────────────────────────────────────────────┤
│ id │ meeting_id │ user_id │ status
├─────────────────────┼─────────────┼──────────────────────┼────────
│ consol-minutes-001 │ meeting-001 │ NULL [V3] │ DRAFT
│ user1-minutes-001 │ meeting-001 │ user1@example.com │ DRAFT
│ user2-minutes-001 │ meeting-001 │ user2@example.com │ DRAFT
│ user3-minutes-001 │ meeting-001 │ user3@example.com │ DRAFT
└─────────────────────┴─────────────┴──────────────────────┴────────
↓ (각각 minutes_sections 참조)
┌─────────────────────────────────────────────────────────────────┐
│ minutes_sections 테이블 (4그룹 × N개 섹션) │
├─────────────────────────────────────────────────────────────────┤
│ id │ minutes_id │ type │ title │ content
├────────┼────────────────────┼─────────────┼──────────┼─────────
│ sec-1 │ consol-minutes-001 │ DISCUSSION │ 안건1 │ "AI가..."
│ sec-2 │ consol-minutes-001 │ DECISION │ 결정1 │ "..."
│ │ │ │ │
│ sec-3 │ user1-minutes-001 │ DISCUSSION │ 안건1 │ "사용자1..."
│ sec-4 │ user1-minutes-001 │ DISCUSSION │ 안건2 │ "..."
│ │ │ │ │
│ sec-5 │ user2-minutes-001 │ DISCUSSION │ 안건1 │ "사용자2..."
│ sec-6 │ user2-minutes-001 │ DECISION │ 결정1 │ "..."
│ │ │ │ │
│ sec-7 │ user3-minutes-001 │ DISCUSSION │ 안건1 │ "사용자3..."
└────────┴────────────────────┴─────────────┴──────────┴─────────
각 사용자가 독립적으로 작성:
- User1: consol-minutes-001의 sec-3, sec-4 편집
- User2: user2-minutes-001의 sec-5, sec-6 편집
- User3: user3-minutes-001의 sec-7 편집
AI 분석 (user_id=NULL인 것만):
┌─────────────────────────────────────────────────────────────────┐
│ agenda_sections 테이블 │
├─────────────────────────────────────────────────────────────────┤
│ id │ minutes_id │ meeting_id │ agenda_number
├────────┼────────────────────┼─────────────┼──────────────────
│ ag-1 │ consol-minutes-001 │ meeting-001 │ 1
│ ag-2 │ consol-minutes-001 │ meeting-001 │ 2
└────────┴────────────────────┴─────────────┴──────────────────
→ minutes_id를 통해 통합 회의록만 참조
→ user_id='user1@example.com'인 회의록은 참조하지 않음
```
---
## 4. 인덱스 활용 쿼리 예시
```sql
-- 쿼리 1: 특정 회의의 통합 회의록 조회 (V3 인덱스 활용)
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id IS NULL
ORDER BY created_at DESC;
: idx_minutes_meeting_user (meeting_id, user_id)
-- 쿼리 2: 특정 사용자의 회의록 조회 (복합 인덱스 활용)
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id = 'user1@example.com'
ORDER BY created_at DESC;
: idx_minutes_meeting_user (meeting_id, user_id)
-- 쿼리 3: 안건별 AI 요약 조회 (V3 인덱스 활용)
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-001'
ORDER BY agenda_number ASC;
: idx_sections_meeting (meeting_id)
-- 쿼리 4: 특정 안건의 세부 요약 (복합 인덱스 활용)
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-001' AND agenda_number = 1;
: idx_sections_agenda (meeting_id, agenda_number)
-- 쿼리 5: AI 추출 Todo 조회 (V3 인덱스 활용)
SELECT * FROM todos
WHERE meeting_id = 'meeting-001' AND extracted_by = 'AI'
ORDER BY priority DESC, due_date ASC;
: idx_todos_extracted (extracted_by)
: idx_todos_meeting (meeting_id)
-- 쿼리 6: 특정 회의의 모든 데이터 조회 (JOIN)
SELECT
m.*,
ms.content,
ag.ai_summary_short,
ag.todos,
ai.keywords
FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
LEFT JOIN agenda_sections ag ON m.id = ag.minutes_id
LEFT JOIN ai_summaries ai ON m.meeting_id = ai.meeting_id
WHERE m.meeting_id = 'meeting-001' AND m.user_id IS NULL
ORDER BY ms.order ASC, ag.agenda_number ASC;
: idx_minutes_meeting_user (meeting_id, user_id)
: idx_sections_minutes (minutes_id)
```
---
## 5. 데이터 저장 크기 예상
```
1개 회의 (참석자 5명) 데이터 크기:
├─ meetings: ~500 bytes
├─ meeting_participants (5명): ~5 × 150 = 750 bytes
├─ minutes (6개: 1 통합 + 5 개인): ~6 × 400 = 2.4 KB
├─ minutes_sections (30개 섹션): ~30 × 2 KB = 60 KB
├─ agenda_sections (5개 안건): ~5 × 4 KB = 20 KB
├─ ai_summaries: ~10 KB
└─ todos (8개): ~8 × 800 bytes = 6.4 KB
Total: ~100 KB/회의
1년 (250개 회의) 예상:
└─► 25 MB + 인덱스 ~5 MB = ~30 MB
JSON 필드 데이터 크기:
├─ agenda_sections.decisions: ~200 bytes/건
├─ agenda_sections.opinions: ~300 bytes/건
├─ agenda_sections.todos: ~500 bytes/건 [V4]
├─ ai_summaries.result: ~5-10 KB/건
└─ ai_summaries.statistics: ~200 bytes/건
```
+130
View File
@@ -0,0 +1,130 @@
@startuml Meeting Service Database Schema
!theme mono
'=== Core Tables ===
entity "meetings" {
* **meeting_id : VARCHAR(50)
--
title : VARCHAR(200) NOT NULL
purpose : VARCHAR(500)
description : TEXT
scheduled_at : TIMESTAMP NOT NULL
started_at : TIMESTAMP
ended_at : TIMESTAMP [V3]
status : VARCHAR(20) NOT NULL
organizer_id : VARCHAR(50) NOT NULL
created_at : TIMESTAMP
updated_at : TIMESTAMP
template_id : VARCHAR(50)
}
entity "meeting_participants" {
* **meeting_id : VARCHAR(50) [FK]
* **user_id : VARCHAR(100)
--
invitation_status : VARCHAR(20)
attended : BOOLEAN
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
entity "minutes" {
* **id : VARCHAR(50)
--
meeting_id : VARCHAR(50) [FK] NOT NULL
user_id : VARCHAR(100) [V3]
title : VARCHAR(200) NOT NULL
status : VARCHAR(20) NOT NULL
version : INT NOT NULL
created_by : VARCHAR(50) NOT NULL
finalized_by : VARCHAR(50)
finalized_at : TIMESTAMP
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
entity "minutes_sections" {
* **id : VARCHAR(50)
--
minutes_id : VARCHAR(50) [FK] NOT NULL
type : VARCHAR(50) NOT NULL
title : VARCHAR(200) NOT NULL
**content : TEXT
order : INT
verified : BOOLEAN
locked : BOOLEAN
locked_by : VARCHAR(50)
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
'=== V3 New Tables ===
entity "agenda_sections" {
* **id : VARCHAR(36)
--
minutes_id : VARCHAR(36) [FK] NOT NULL
meeting_id : VARCHAR(50) [FK] NOT NULL
agenda_number : INT NOT NULL
agenda_title : VARCHAR(200) NOT NULL
ai_summary_short : TEXT
discussions : TEXT
decisions : JSON
pending_items : JSON
opinions : JSON
**todos : JSON [V4]
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
entity "ai_summaries" {
* **id : VARCHAR(36)
--
meeting_id : VARCHAR(50) [FK] NOT NULL
summary_type : VARCHAR(50) NOT NULL
source_minutes_ids : JSON NOT NULL
result : JSON NOT NULL
processing_time_ms : INT
model_version : VARCHAR(50)
keywords : JSON
statistics : JSON
created_at : TIMESTAMP
}
entity "todos" {
* **todo_id : VARCHAR(50)
--
meeting_id : VARCHAR(50) [FK] NOT NULL
minutes_id : VARCHAR(50) [FK]
title : VARCHAR(200) NOT NULL
description : TEXT
assignee_id : VARCHAR(50) NOT NULL
due_date : DATE
status : VARCHAR(20) NOT NULL
priority : VARCHAR(20)
extracted_by : VARCHAR(50) [V3]
section_reference : VARCHAR(200) [V3]
extraction_confidence : DECIMAL(3,2) [V3]
completed_at : TIMESTAMP
created_at : TIMESTAMP
updated_at : TIMESTAMP
}
'=== Relationships ===
meetings ||--o{ meeting_participants : "1:N [V2]"
meetings ||--o{ minutes : "1:N"
meetings ||--o{ agenda_sections : "1:N [V3]"
meetings ||--o{ ai_summaries : "1:N [V3]"
meetings ||--o{ todos : "1:N"
minutes ||--o{ minutes_sections : "1:N"
minutes ||--o{ agenda_sections : "1:N [V3]"
'=== Legend ===
legend right
V2 = Migration 2 (2025-10-27)
V3 = Migration 3 (2025-10-28)
V4 = Migration 4 (2025-10-28)
[FK] = Foreign Key
**bold** = Important fields
end legend
@enduml
+675
View File
@@ -0,0 +1,675 @@
# Meeting Service 데이터베이스 스키마 전체 분석
## 1. 마이그레이션 파일 현황
### 마이그레이션 체인
```
V1 (초기) → V2 (회의 참석자) → V3 (회의종료) → V4 (todos)
```
### 각 마이그레이션 내용
- **V1**: 초기 스키마 (meetings, minutes, minutes_sections 등 - JPA로 자동 생성)
- **V2**: `meeting_participants` 테이블 분리 (2025-10-27)
- **V3**: 회의종료 기능 지원 (2025-10-28) - **주요 변경**
- **V4**: `agenda_sections` 테이블에 `todos` 컬럼 추가 (2025-10-28)
---
## 2. 핵심 테이블 구조 분석
### 2.1 meetings 테이블
**용도**: 회의 기본 정보 저장
| 컬럼명 | 타입 | 설명 | 용도 |
|--------|------|------|------|
| meeting_id | VARCHAR(50) | PK | 회의 고유 식별자 |
| title | VARCHAR(200) | NOT NULL | 회의 제목 |
| purpose | VARCHAR(500) | | 회의 목적 |
| description | TEXT | | 상세 설명 |
| scheduled_at | TIMESTAMP | NOT NULL | 예정된 시간 |
| started_at | TIMESTAMP | | 실제 시작 시간 |
| ended_at | TIMESTAMP | | **V3 추가**: 실제 종료 시간 |
| status | VARCHAR(20) | NOT NULL | 상태: SCHEDULED, IN_PROGRESS, COMPLETED |
| organizer_id | VARCHAR(50) | NOT NULL | 회의 주최자 |
| created_at | TIMESTAMP | | 생성 시간 |
| updated_at | TIMESTAMP | | 수정 시간 |
**관계**:
- 1:N with `meeting_participants` (V2에서 분리)
- 1:N with `minutes`
---
### 2.2 minutes 테이블
**용도**: 회의록 기본 정보 + 사용자별 회의록 구분
| 컬럼명 | 타입 | 설명 | 용도 |
|--------|------|------|------|
| id/minutes_id | VARCHAR(50) | PK | 회의록 고유 식별자 |
| meeting_id | VARCHAR(50) | FK | 해당 회의 ID |
| user_id | VARCHAR(100) | **V3 추가** | NULL: AI 통합 회의록 / NOT NULL: 참석자별 회의록 |
| title | VARCHAR(200) | NOT NULL | 회의록 제목 |
| status | VARCHAR(20) | NOT NULL | DRAFT, FINALIZED |
| version | INT | NOT NULL | 버전 관리 |
| created_by | VARCHAR(50) | NOT NULL | 작성자 |
| finalized_by | VARCHAR(50) | | 확정자 |
| finalized_at | TIMESTAMP | | 확정 시간 |
| created_at | TIMESTAMP | | 생성 시간 |
| updated_at | TIMESTAMP | | 수정 시간 |
**중요**: `minutes` 테이블에는 `content` 컬럼이 **없음**
- 실제 회의록 내용은 `minutes_sections``content`에 저장됨
- minutes는 메타데이터만 저장
**인덱스 (V3)**: `idx_minutes_meeting_user` on (meeting_id, user_id)
**관계**:
- N:1 with `meetings`
- 1:N with `minutes_sections`
- 1:N with `agenda_sections` (V3 추가)
---
### 2.3 minutes_sections 테이블
**용도**: 회의록 섹션별 상세 내용
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| id | VARCHAR(50) | PK |
| minutes_id | VARCHAR(50) | FK to minutes |
| type | VARCHAR(50) | AGENDA, DISCUSSION, DECISION, ACTION_ITEM |
| title | VARCHAR(200) | 섹션 제목 |
| **content** | TEXT | **섹션 상세 내용 저장** |
| order | INT | 섹션 순서 |
| verified | BOOLEAN | 검증 완료 여부 |
| locked | BOOLEAN | 잠금 여부 |
| locked_by | VARCHAR(50) | 잠금 사용자 |
**중요 사항**:
- 회의록 실제 내용은 여기에 저장됨
- `minutes`와 N:1 관계 (1개 회의록에 다중 섹션)
- 사용자별 회의록도 각각 섹션을 가짐
---
### 2.4 agenda_sections 테이블 (V3 신규)
**용도**: 안건별 AI 요약 결과 저장 (구조화된 형식)
| 컬럼명 | 타입 | 설명 | 포함 데이터 |
|--------|------|------|-----------|
| id | VARCHAR(36) | PK | UUID |
| minutes_id | VARCHAR(36) | FK | 통합 회의록 참조 |
| meeting_id | VARCHAR(50) | FK | 회의 ID |
| agenda_number | INT | | 안건 번호 (1, 2, 3...) |
| agenda_title | VARCHAR(200) | | 안건 제목 |
| ai_summary_short | TEXT | | 짧은 요약 (1줄, 20자 이내) |
| discussions | TEXT | | 논의 사항 (3-5문장) |
| decisions | JSON | | 결정 사항 배열 |
| pending_items | JSON | | 보류 사항 배열 |
| opinions | JSON | | 참석자별 의견: [{speaker, opinion}] |
| **todos** | JSON | **V4 추가** | 추출된 Todo: [{title, assignee, dueDate, description, priority}] |
**V4 추가 구조** (todos JSON):
```json
[
{
"title": "시장 조사 보고서 작성",
"assignee": "김민준",
"dueDate": "2025-02-15",
"description": "20-30대 타겟 시장 조사",
"priority": "HIGH"
}
]
```
**인덱스**:
- `idx_sections_meeting` on meeting_id
- `idx_sections_agenda` on (meeting_id, agenda_number)
- `idx_sections_minutes` on minutes_id
**관계**:
- N:1 with `minutes` (통합 회의록만 참조)
- N:1 with `meetings`
---
### 2.5 minutes_section vs agenda_sections 차이점
| 특성 | minutes_sections | agenda_sections |
|------|------------------|-----------------|
| **용도** | 회의록 작성용 | AI 요약 결과 저장용 |
| **구조** | 순차적 섹션 (type: AGENDA, DISCUSSION, DECISION) | 안건별 구조화된 데이터 |
| **내용 저장** | content (TEXT) | 구조화된 필드 + JSON |
| **소유 관계** | 모든 회의록 (사용자별 포함) | 통합 회의록만 (user_id=NULL) |
| **목적** | 사용자 작성 | AI 자동 생성 |
| **JSON 필드** | 없음 | decisions, pending_items, opinions, todos |
**생성 흐름**:
```
회의 종료 → 통합 회의록 (minutes, user_id=NULL)
→ minutes_sections 생성 (사용자가 내용 작성)
→ AI 분석 → agenda_sections 생성 (AI 요약 결과 저장)
동시에:
→ 참석자별 회의록 (minutes, user_id NOT NULL)
→ 참석자별 minutes_sections 생성
```
---
### 2.6 ai_summaries 테이블 (V3 신규)
**용도**: AI 요약 결과 캐싱
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| id | VARCHAR(36) | PK |
| meeting_id | VARCHAR(50) | FK |
| summary_type | VARCHAR(50) | CONSOLIDATED (통합 요약) / TODO_EXTRACTION (Todo 추출) |
| source_minutes_ids | JSON | 통합에 사용된 회의록 ID 배열 |
| result | JSON | **AI 응답 전체 결과** |
| processing_time_ms | INT | AI 처리 시간 |
| model_version | VARCHAR(50) | 사용 모델 (claude-3.5-sonnet) |
| keywords | JSON | 주요 키워드 배열 |
| statistics | JSON | 통계 (참석자 수, 안건 수 등) |
---
### 2.7 todos 테이블
**용도**: Todo 아이템 저장
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| todo_id | VARCHAR(50) | PK |
| minutes_id | VARCHAR(50) | FK | 관련 회의록 |
| meeting_id | VARCHAR(50) | FK | 회의 ID |
| title | VARCHAR(200) | 제목 |
| description | TEXT | 상세 설명 |
| assignee_id | VARCHAR(50) | 담당자 |
| due_date | DATE | 마감일 |
| status | VARCHAR(20) | PENDING, COMPLETED |
| priority | VARCHAR(20) | HIGH, MEDIUM, LOW |
| completed_at | TIMESTAMP | 완료 시간 |
**V3에서 추가된 컬럼**:
```sql
extracted_by VARCHAR(50) -- AI 또는 MANUAL
section_reference VARCHAR(200) -- 관련 회의록 섹션 참조
extraction_confidence DECIMAL(3,2) -- AI 신뢰도 (0.00~1.00)
```
---
### 2.8 meeting_participants 테이블 (V2 신규)
**용도**: 회의 참석자 정보 분리
| 컬럼명 | 타입 | 설명 |
|--------|------|------|
| meeting_id | VARCHAR(50) | PK1, FK |
| user_id | VARCHAR(100) | PK2 |
| invitation_status | VARCHAR(20) | PENDING, ACCEPTED, DECLINED |
| attended | BOOLEAN | 참석 여부 |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP | |
**변경 배경 (V2)**:
- 이전: meetings.participants (CSV 문자열)
- 현재: meeting_participants (별도 테이블, 정규화)
---
## 3. 회의록 작성 플로우에서의 테이블 사용
### 3.1 회의 시작 (StartMeeting)
```
meetings 테이블 UPDATE
└─ status: SCHEDULED → IN_PROGRESS
└─ started_at 기록
```
### 3.2 회의 종료 (EndMeeting)
```
meetings 테이블 UPDATE
├─ status: IN_PROGRESS → COMPLETED
└─ ended_at 기록 (V3 신규)
minutes 테이블 생성 (AI 통합 회의록)
├─ user_id = NULL
├─ status = DRAFT
└─ 각 참석자별 회의록도 동시 생성
└─ user_id = 참석자ID
minutes_sections 테이블 초기 생성
├─ 통합 회의록용 섹션
└─ 각 참석자별 섹션
```
### 3.3 회의록 작성 (CreateMinutes / UpdateMinutes)
```
minutes 테이블 UPDATE
├─ title 작성
└─ status 유지 (DRAFT)
minutes_sections 테이블 INSERT/UPDATE
├─ type: AGENDA, DISCUSSION, DECISION 등
├─ title: 섹션 제목
├─ content: 실제 회의록 내용 ← **여기에 사용자가 입력한 내용 저장**
└─ order: 순서
사용자가 작성한 내용 저장 경로:
minutes_sections.content (TEXT 컬럼)
```
### 3.4 AI 분석 (FinializeMinutes + AI Processing)
```
minutes 테이블 UPDATE
├─ status: DRAFT → FINALIZED
└─ finalized_at 기록
agenda_sections 테이블 INSERT
├─ minutesId = 통합 회의록 ID (user_id=NULL)
├─ AI 요약: aiSummaryShort, discussions
├─ 구조화된 데이터: decisions, pendingItems, opinions (JSON)
└─ todos (V4): AI 추출 Todo (JSON)
ai_summaries 테이블 INSERT
├─ summary_type: CONSOLIDATED
├─ result: AI 응답 전체 결과
└─ keywords, statistics
todos 테이블 INSERT (선택)
├─ 간단한 Todo는 agenda_sections.todos에만 저장
└─ 상세 관리 필요한 경우 별도 테이블 저장
```
---
## 4. 사용자별 회의록 저장 구조
### 4.1 회의 종료 시 자동 생성
```
1개의 회의 → 여러 회의록
├─ AI 통합 회의록 (minutes.user_id = NULL)
│ ├─ minutes_sections (AI/시스템이 생성)
│ └─ agenda_sections (AI 분석 결과)
└─ 각 참석자별 회의록 (minutes.user_id = 참석자ID)
├─ User1의 회의록 (minutes.user_id = 'user1@example.com')
│ └─ minutes_sections (User1이 작성)
├─ User2의 회의록 (minutes.user_id = 'user2@example.com')
│ └─ minutes_sections (User2이 작성)
└─ ...
```
### 4.2 minutes 테이블 쿼리 예시
```sql
-- 특정 회의의 AI 통합 회의록
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id IS NULL;
-- 특정 회의의 참석자별 회의록
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id IS NOT NULL;
-- 특정 사용자의 회의록
SELECT * FROM minutes
WHERE user_id = 'user1@example.com';
-- 참석자별로 회의록 조회 (복합 인덱스 활용)
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id = 'user1@example.com';
```
---
## 5. V3 마이그레이션의 주요 변경사항
### 5.1 minutes 테이블 확장
```sql
ALTER TABLE minutes ADD COLUMN IF NOT EXISTS user_id VARCHAR(100);
CREATE INDEX IF NOT EXISTS idx_minutes_meeting_user ON minutes(meeting_id, user_id);
```
**영향**:
- 기존 회의록: `user_id = NULL` (AI 통합 회의록)
- 새 회의록: `user_id = 참석자ID` (참석자별)
- 쿼리 성능: 복합 인덱스로 빠른 검색
### 5.2 agenda_sections 테이블 신규 생성
- AI 요약을 구조화된 형식으로 저장
- JSON 필드로 결정사항, 보류사항, 의견, Todo 저장
- minutes_id로 통합 회의록과 연결
### 5.3 ai_summaries 테이블 신규 생성
- AI 처리 결과 캐싱
- 처리 시간, 모델 버전 기록
- 재처리 필요 시 참조 가능
### 5.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) DEFAULT 0.00;
```
**목적**:
- AI 자동 추출 vs 수동 작성 구분
- Todo의 출처 추적
- AI 신뢰도 관리
---
## 6. V4 마이그레이션의 변경사항
### 6.1 agenda_sections 테이블에 todos 컬럼 추가
```sql
ALTER TABLE agenda_sections ADD COLUMN IF NOT EXISTS todos JSON;
```
**구조**:
```json
{
"title": "시장 조사 보고서 작성",
"assignee": "김민준",
"dueDate": "2025-02-15",
"description": "20-30대 타겟 시장 조사",
"priority": "HIGH"
}
```
**저장 경로**:
- **안건별 요약의 Todo**: `agenda_sections.todos` (JSON)
- **개별 Todo 관리**: `todos` 테이블 (필요시)
---
## 7. 데이터 정규화 현황
### 7.1 정규화 수행 (V2)
```
meetings (이전):
participants: "user1@example.com,user2@example.com"
↓ 정규화 (V2 마이그레이션)
meetings_participants (별도 테이블):
[meeting_id, user_id] (복합 PK)
invitation_status
attended
```
### 7.2 JSON 필드 사용 (V3, V4)
- `decisions`, `pending_items`, `opinions`, `todos` (agenda_sections)
- `keywords`, `statistics` (ai_summaries)
- `source_minutes_ids` (ai_summaries)
**사용 이유**:
- 변동적인 구조 데이터
- AI 응답의 유연한 저장
- 쿼리 패턴이 검색보다 전체 조회
---
## 8. 핵심 질문 답변
### Q1: minutes 테이블에 content 필드가 있는가?
**A**: **없음**. 회의록 실제 내용은 `minutes_sections.content`에 저장됨.
### Q2: minutes_section과 agenda_sections의 차이점?
| 항목 | minutes_sections | agenda_sections |
|------|-----------------|-----------------|
| 목적 | 사용자 작성 | AI 요약 |
| 모든 회의록 | O | X (통합만) |
| 구조 | 순차적 | 안건별 |
| 내용 저장 | content (TEXT) | JSON |
### Q3: 사용자별 회의록을 저장할 적절한 구조는?
**A**:
- `minutes` 테이블: `user_id` 컬럼으로 구분
- `minutes_sections`: 각 회의록의 섹션
- 인덱스: `idx_minutes_meeting_user` (meeting_id, user_id)
### Q4: V3, V4 주요 변경사항은?
- **V3**: user_id 추가, agenda_sections 신규, ai_summaries 신규, todos 확장
- **V4**: agenda_sections.todos JSON 필드 추가
---
## 9. 데이터베이스 구조도 (PlantUML)
```plantuml
@startuml
!theme mono
entity "meetings" as meetings {
* meeting_id: VARCHAR(50)
--
title: VARCHAR(200)
status: VARCHAR(20)
organizer_id: VARCHAR(50)
started_at: TIMESTAMP
ended_at: TIMESTAMP [V3]
created_at: TIMESTAMP
updated_at: TIMESTAMP
}
entity "meeting_participants" as participants {
* meeting_id: VARCHAR(50) [FK]
* user_id: VARCHAR(100)
--
invitation_status: VARCHAR(20)
attended: BOOLEAN
}
entity "minutes" as minutes {
* id: VARCHAR(50)
--
meeting_id: VARCHAR(50) [FK]
user_id: VARCHAR(100) [V3]
title: VARCHAR(200)
status: VARCHAR(20)
created_by: VARCHAR(50)
finalized_at: TIMESTAMP
}
entity "minutes_sections" as sections {
* id: VARCHAR(50)
--
minutes_id: VARCHAR(50) [FK]
type: VARCHAR(50)
title: VARCHAR(200)
content: TEXT
locked: BOOLEAN
}
entity "agenda_sections" as agenda {
* id: VARCHAR(36)
--
minutes_id: VARCHAR(36) [FK, 통합회의록만]
meeting_id: VARCHAR(50) [FK]
agenda_number: INT
agenda_title: VARCHAR(200)
ai_summary_short: TEXT
discussions: TEXT
decisions: JSON
opinions: JSON
todos: JSON [V4]
}
entity "ai_summaries" as summaries {
* id: VARCHAR(36)
--
meeting_id: VARCHAR(50) [FK]
summary_type: VARCHAR(50)
result: JSON
keywords: JSON
statistics: JSON
}
entity "todos" as todos {
* todo_id: VARCHAR(50)
--
meeting_id: VARCHAR(50) [FK]
minutes_id: VARCHAR(50) [FK]
title: VARCHAR(200)
assignee_id: VARCHAR(50)
status: VARCHAR(20)
extracted_by: VARCHAR(50) [V3]
}
meetings ||--o{ participants: "1:N"
meetings ||--o{ minutes: "1:N"
meetings ||--o{ agenda: "1:N"
meetings ||--o{ todos: "1:N"
minutes ||--o{ sections: "1:N"
minutes ||--o{ agenda: "1:N"
meetings ||--o{ summaries: "1:N"
@enduml
```
---
## 10. 회의록 작성 전체 플로우
```
┌─────────────────────────────────────────────────────┐
│ 1. 회의 시작 (StartMeeting) │
│ ├─ meetings.status = IN_PROGRESS │
│ └─ meetings.started_at 기록 │
└─────────────────┬───────────────────────────────────┘
┌─────────────────▼───────────────────────────────────┐
│ 2. 회의 진행 중 (회의록 작성) │
│ ├─ CreateMinutes: minutes 생성 (user_id=NULL 통합) │
│ ├─ CreateMinutes: 참석자별 minutes 생성 │
│ ├─ UpdateMinutes: minutes_sections 작성 │
│ │ └─ content에 회의 내용 저장 │
│ └─ SaveMinutes: draft 상태 유지 │
└─────────────────┬───────────────────────────────────┘
┌─────────────────▼───────────────────────────────────┐
│ 3. 회의 종료 (EndMeeting) │
│ ├─ meetings.status = COMPLETED │
│ ├─ meetings.ended_at = NOW() [V3] │
│ └─ 회의 기본 정보 확정 │
└─────────────────┬───────────────────────────────────┘
┌─────────────────▼───────────────────────────────────┐
│ 4. 회의록 최종화 (FinalizeMinutes) │
│ ├─ minutes.status = FINALIZED │
│ ├─ minutes.finalized_by = 확정자 │
│ ├─ minutes.finalized_at = NOW() │
│ └─ minutes_sections 내용 확정 (locked) │
└─────────────────┬───────────────────────────────────┘
┌─────────────────▼───────────────────────────────────┐
│ 5. AI 분석 처리 (MinutesAnalysisEventConsumer) │
│ ├─ 통합 회의록 분석 (user_id=NULL) │
│ │ │
│ ├─ agenda_sections INSERT [V3] │
│ │ ├─ minutes_id = 통합 회의록 ID │
│ │ ├─ ai_summary_short, discussions │
│ │ ├─ decisions, pending_items, opinions (JSON) │
│ │ └─ todos (JSON) [V4] │
│ │ │
│ ├─ ai_summaries INSERT [V3] │
│ │ ├─ summary_type = CONSOLIDATED │
│ │ ├─ result = AI 응답 전체 │
│ │ └─ keywords, statistics │
│ │ │
│ └─ todos TABLE INSERT (선택) │
│ ├─ extracted_by = 'AI' [V3] │
│ └─ extraction_confidence [V3] │
└─────────────────┬───────────────────────────────────┘
┌─────────────────▼───────────────────────────────────┐
│ 6. 회의록 조회 │
│ ├─ 통합 회의록 조회 │
│ │ └─ minutes + minutes_sections + agenda_sections │
│ ├─ 참석자별 회의록 조회 │
│ │ └─ minutes (user_id=참석자) + minutes_sections │
│ └─ Todo 조회 │
│ └─ agenda_sections.todos 또는 todos 테이블 │
└─────────────────────────────────────────────────────┘
```
---
## 11. 성능 최적화 포인트
### 11.1 인덱스 현황
```
meetings:
- PK: meeting_id
minutes:
- PK: id
- idx_minutes_meeting_user (meeting_id, user_id) [V3] ← 핵심
minutes_sections:
- PK: id
- FK: minutes_id
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)
```
### 11.2 추천 추가 인덱스
```sql
-- 빠른 조회를 위한 인덱스
CREATE INDEX idx_minutes_status 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);
```
---
## 12. 결론
### 핵심 설계 원칙
1. **참석자별 회의록**: minutes.user_id로 구분 (NULL=AI 통합, NOT NULL=개인)
2. **내용 저장**: minutes_sections.content에 사용자가 작성한 내용 저장
3. **구조화된 요약**: agenda_sections에 AI 요약을 JSON으로 저장
4. **추적 가능성**: extracted_by, section_reference로 Todo 출처 추적
5. **정규화**: V2에서 meeting_participants로 정규화 완료
### 주의사항
- `minutes` 테이블 자체는 메타데이터만 저장 (title, status 등)
- 실제 회의 내용: `minutes_sections.content`
- AI 요약 결과: `agenda_sections` (구조화됨)
- Todo는 두 곳에 저장 가능: agenda_sections.todos (JSON) / todos 테이블