hgzero/claude/database-schema-analysis.md

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_sectionscontent에 저장됨
  • 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_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
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. 결론

핵심 설계 원칙

  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 테이블