mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 10:16:24 +00:00
22 KiB
22 KiB
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):
[
{
"title": "시장 조사 보고서 작성",
"assignee": "김민준",
"dueDate": "2025-02-15",
"description": "20-30대 타겟 시장 조사",
"priority": "HIGH"
}
]
인덱스:
idx_sections_meetingon meeting_ididx_sections_agendaon (meeting_id, agenda_number)idx_sections_minuteson 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 |
| 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에서 추가된 컬럼:
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 테이블 쿼리 예시
-- 특정 회의의 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 테이블 확장
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 테이블 확장
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 컬럼 추가
ALTER TABLE agenda_sections ADD COLUMN IF NOT EXISTS todos 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)
@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 추천 추가 인덱스
-- 빠른 조회를 위한 인덱스
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. 결론
핵심 설계 원칙
- 참석자별 회의록: minutes.user_id로 구분 (NULL=AI 통합, NOT NULL=개인)
- 내용 저장: minutes_sections.content에 사용자가 작성한 내용 저장
- 구조화된 요약: agenda_sections에 AI 요약을 JSON으로 저장
- 추적 가능성: extracted_by, section_reference로 Todo 출처 추적
- 정규화: V2에서 meeting_participants로 정규화 완료
주의사항
minutes테이블 자체는 메타데이터만 저장 (title, status 등)- 실제 회의 내용:
minutes_sections.content - AI 요약 결과:
agenda_sections(구조화됨) - Todo는 두 곳에 저장 가능: agenda_sections.todos (JSON) / todos 테이블