hgzero/docs/DB-Schema-회의종료.md
Minseo-Jo 92e4863fc7 feat: 회의종료 기능을 위한 DB 스키마 추가
## 변경 내용
- minutes 테이블에 user_id 컬럼 추가 (참석자별 회의록 지원)
  * user_id IS NULL: AI 통합 회의록
  * user_id IS NOT NULL: 참석자별 회의록

- agenda_sections 테이블 생성 (안건별 AI 요약 저장)
  * agenda_number, agenda_title
  * ai_summary_short, discussions, decisions (JSON)
  * pending_items (JSON), opinions (JSON)

- ai_summaries 테이블 생성 (AI 결과 캐싱)
  * summary_type: CONSOLIDATED, TODO_EXTRACTION
  * keywords, statistics (JSON)
  * processing_time_ms (성능 모니터링)

- todos 테이블 확장 (AI 추출 정보)
  * extracted_by: AI, MANUAL
  * section_reference: 관련 안건 참조
  * extraction_confidence: 0.00~1.00

## 문서
- DB-Schema-회의종료.md: 상세 스키마 문서
- ERD-회의종료.puml: ERD 다이어그램
- 회의종료-개발계획.md: 전체 개발 계획

## 설계 개선
- is_consolidated 컬럼 제거 (user_id로 구분 가능)
- 중복 정보 제거로 데이터 일관성 향상
2025-10-28 11:21:32 +09:00

14 KiB

회의종료 기능 DB 스키마 설계

📋 개요

회의 종료 시 참석자별 회의록을 통합하고 AI 요약 및 Todo 자동 추출을 지원하기 위한 데이터베이스 스키마

마이그레이션 버전: V3__add_meeting_end_support.sql


🗄️ 테이블 구조

1. minutes (확장)

설명: 참석자별 회의록 및 AI 통합 회의록 저장

새로 추가된 컬럼

컬럼명 타입 제약조건 설명
user_id VARCHAR(100) NULL 작성자 사용자 ID (참석자별 회의록인 경우)
is_consolidated BOOLEAN DEFAULT FALSE AI 통합 회의록 여부
consolidated_by VARCHAR(255) DEFAULT 'AI' 통합 처리자
section_type VARCHAR(50) DEFAULT 'PARTICIPANT' 회의록 섹션 타입

인덱스

  • idx_minutes_meeting_user: (meeting_id, user_id)
  • idx_minutes_consolidated: (is_consolidated)
  • idx_minutes_section_type: (section_type)

사용 패턴

-- 참석자별 회의록 조회
SELECT * FROM minutes
WHERE meeting_id = 'meeting-123'
  AND is_consolidated = false
  AND section_type = 'PARTICIPANT';

-- AI 통합 회의록 조회
SELECT * FROM minutes
WHERE meeting_id = 'meeting-123'
  AND is_consolidated = true
  AND section_type = 'CONSOLIDATED';

2. agenda_sections (신규)

설명: 안건별 AI 요약 결과 저장

컬럼 구조

컬럼명 타입 제약조건 설명
id VARCHAR(36) PRIMARY KEY 섹션 고유 ID
minutes_id VARCHAR(36) NOT NULL, FK 회의록 ID (통합 회의록)
meeting_id VARCHAR(50) NOT NULL, FK 회의 ID
agenda_number INT NOT NULL 안건 번호
agenda_title VARCHAR(200) NOT NULL 안건 제목
ai_summary_short TEXT NULL AI 생성 짧은 요약 (1줄)
discussions TEXT NULL 논의 사항 (3-5문장)
decisions JSON NULL 결정 사항 배열
pending_items JSON NULL 보류 사항 배열
opinions JSON NULL 참석자별 의견
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 생성 시간
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 수정 시간

외래키

  • fk_agenda_sections_minutes: minutes(id) ON DELETE CASCADE
  • fk_agenda_sections_meeting: meetings(meeting_id) ON DELETE CASCADE

인덱스

  • idx_sections_meeting: (meeting_id)
  • idx_sections_agenda: (meeting_id, agenda_number)
  • idx_sections_minutes: (minutes_id)

JSON 데이터 구조

decisions (결정 사항):

[
  "타겟 고객: 20-30대 직장인",
  "UI/UX 개선을 최우선 과제로 설정",
  "예산: 5억원"
]

pending_items (보류 사항):

[
  "세부 일정 확정은 다음 회의에서 논의",
  "추가 예산 검토 필요"
]

opinions (참석자별 의견):

[
  {
    "speaker": "김민준",
    "opinion": "타겟 고객층을 명확히 설정하여 마케팅 전략 수립 필요"
  },
  {
    "speaker": "박서연",
    "opinion": "UI/UX 개선에 AI 기술 적용 검토"
  }
]

사용 패턴

-- 안건별 섹션 조회
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-123'
ORDER BY agenda_number;

-- 특정 안건 조회
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-123'
  AND agenda_number = 1;

-- JSON 필드 쿼리 (PostgreSQL)
SELECT
  agenda_title,
  decisions,
  jsonb_array_length(decisions::jsonb) as decision_count
FROM agenda_sections
WHERE meeting_id = 'meeting-123';

3. ai_summaries (신규)

설명: AI 요약 결과 캐싱 및 성능 최적화

컬럼 구조

컬럼명 타입 제약조건 설명
id VARCHAR(36) PRIMARY KEY 요약 결과 고유 ID
meeting_id VARCHAR(50) NOT NULL, FK 회의 ID
summary_type VARCHAR(50) NOT NULL 요약 타입
source_minutes_ids JSON NOT NULL 통합에 사용된 회의록 ID 배열
result JSON NOT NULL AI 응답 전체 결과
processing_time_ms INT NULL AI 처리 시간 (밀리초)
model_version VARCHAR(50) DEFAULT 'claude-3.5-sonnet' AI 모델 버전
keywords JSON NULL 주요 키워드 배열
statistics JSON NULL 통계 정보
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 생성 시간

summary_type 값

  • CONSOLIDATED: 통합 회의록 요약
  • TODO_EXTRACTION: Todo 자동 추출

외래키

  • fk_ai_summaries_meeting: meetings(meeting_id) ON DELETE CASCADE

인덱스

  • idx_summaries_meeting: (meeting_id)
  • idx_summaries_type: (meeting_id, summary_type)
  • idx_summaries_created: (created_at)

JSON 데이터 구조

source_minutes_ids:

[
  "minutes-uuid-1",
  "minutes-uuid-2",
  "minutes-uuid-3"
]

result (CONSOLIDATED 타입):

{
  "agendaSections": [
    {
      "agendaNumber": 1,
      "aiSummaryShort": "타겟 고객을 20-30대로 설정...",
      "discussions": "신제품의 주요 타겟 고객층...",
      "decisions": ["타겟 고객: 20-30대", "UI/UX 개선"],
      "pendingItems": [],
      "opinions": [
        {"speaker": "김민준", "opinion": "..."}
      ]
    }
  ]
}

result (TODO_EXTRACTION 타입):

{
  "todos": [
    {
      "content": "시장 조사 보고서 작성",
      "assignee": "김민준",
      "dueDate": "2025-11-01",
      "priority": "HIGH",
      "sectionReference": "안건 1",
      "confidence": 0.92
    }
  ]
}

keywords:

[
  "신제품기획",
  "예산편성",
  "일정조율",
  "시장조사",
  "UI/UX"
]

statistics:

{
  "participantsCount": 4,
  "durationMinutes": 90,
  "agendasCount": 3,
  "todosCount": 5
}

사용 패턴

-- 캐시된 통합 요약 조회
SELECT * FROM ai_summaries
WHERE meeting_id = 'meeting-123'
  AND summary_type = 'CONSOLIDATED'
ORDER BY created_at DESC
LIMIT 1;

-- 성능 통계 조회
SELECT
  summary_type,
  AVG(processing_time_ms) as avg_time,
  MAX(processing_time_ms) as max_time
FROM ai_summaries
GROUP BY summary_type;

-- JSON 필드 쿼리
SELECT
  meeting_id,
  keywords,
  statistics->>'participantsCount' as participants,
  statistics->>'todosCount' as todos
FROM ai_summaries
WHERE summary_type = 'CONSOLIDATED';

4. todos (확장)

설명: AI 자동 추출 정보 추가

새로 추가된 컬럼

컬럼명 타입 제약조건 설명
extracted_by VARCHAR(50) DEFAULT 'AI' Todo 추출 방법
section_reference VARCHAR(200) NULL 관련 회의록 섹션 참조
extraction_confidence DECIMAL(3,2) DEFAULT 0.00 AI 추출 신뢰도 점수

extracted_by 값

  • AI: AI 자동 추출
  • MANUAL: 사용자 수동 작성

인덱스

  • idx_todos_extracted: (extracted_by)
  • idx_todos_meeting: (meeting_id)

사용 패턴

-- AI 추출 Todo 조회
SELECT * FROM todos
WHERE meeting_id = 'meeting-123'
  AND extracted_by = 'AI'
ORDER BY extraction_confidence DESC;

-- 신뢰도 높은 Todo 조회
SELECT * FROM todos
WHERE meeting_id = 'meeting-123'
  AND extracted_by = 'AI'
  AND extraction_confidence >= 0.80;

-- 안건별 Todo 조회
SELECT * FROM todos
WHERE meeting_id = 'meeting-123'
  AND section_reference = '안건 1';

🔄 데이터 플로우

회의 종료 시 데이터 저장 순서

1. 참석자별 회의록 저장 (minutes)
   ├─ user_id: 각 참석자 ID
   ├─ is_consolidated: false
   └─ section_type: 'PARTICIPANT'

2. AI Service 호출 → 통합 요약 생성

3. 통합 회의록 저장 (minutes)
   ├─ is_consolidated: true
   ├─ section_type: 'CONSOLIDATED'
   └─ consolidated_by: 'AI'

4. 안건별 섹션 저장 (agenda_sections)
   ├─ meeting_id: 회의 ID
   ├─ minutes_id: 통합 회의록 ID
   └─ AI 요약 결과 (discussions, decisions, opinions 등)

5. AI 요약 결과 캐싱 (ai_summaries)
   ├─ summary_type: 'CONSOLIDATED'
   ├─ source_minutes_ids: 참석자 회의록 ID 배열
   └─ result: 전체 AI 응답 (JSON)

6. Todo 자동 추출 및 저장 (todos)
   ├─ extracted_by: 'AI'
   ├─ section_reference: 관련 안건
   └─ extraction_confidence: 신뢰도 점수

7. 통계 정보 캐싱 (ai_summaries)
   ├─ keywords: 주요 키워드 배열
   └─ statistics: 참석자 수, 안건 수, Todo 수 등

📊 ERD (Entity Relationship Diagram)

meetings
  ├─ 1:N → minutes (참석자별 회의록)
  │         ├─ user_id (참석자)
  │         └─ is_consolidated = false
  │
  ├─ 1:1 → minutes (통합 회의록)
  │         └─ is_consolidated = true
  │
  ├─ 1:N → agenda_sections (안건별 섹션)
  │         ├─ FK: minutes_id (통합 회의록)
  │         └─ FK: meeting_id
  │
  ├─ 1:N → ai_summaries (AI 요약 캐시)
  │         ├─ summary_type: CONSOLIDATED | TODO_EXTRACTION
  │         └─ source_minutes_ids (JSON)
  │
  └─ 1:N → todos (할일)
            ├─ extracted_by: AI | MANUAL
            ├─ section_reference
            └─ extraction_confidence

🔍 주요 쿼리 예시

1. 회의 종료 후 전체 데이터 조회

-- 회의 기본 정보 + 통계
SELECT
  m.meeting_id,
  m.title,
  m.status,
  s.statistics->>'participantsCount' as participants,
  s.statistics->>'durationMinutes' as duration,
  s.statistics->>'agendasCount' as agendas,
  s.statistics->>'todosCount' as todos,
  s.keywords
FROM meetings m
LEFT JOIN ai_summaries s ON m.meeting_id = s.meeting_id
  AND s.summary_type = 'CONSOLIDATED'
WHERE m.meeting_id = 'meeting-123';

-- 안건별 섹션 + Todo
SELECT
  a.agenda_number,
  a.agenda_title,
  a.ai_summary_short,
  a.discussions,
  a.decisions,
  a.pending_items,
  json_agg(
    json_build_object(
      'content', t.content,
      'assignee', t.assignee,
      'dueDate', t.due_date,
      'priority', t.priority,
      'confidence', t.extraction_confidence
    )
  ) as todos
FROM agenda_sections a
LEFT JOIN todos t ON a.meeting_id = t.meeting_id
  AND t.section_reference = CONCAT('안건 ', a.agenda_number)
WHERE a.meeting_id = 'meeting-123'
GROUP BY a.id, a.agenda_number, a.agenda_title,
         a.ai_summary_short, a.discussions, a.decisions, a.pending_items
ORDER BY a.agenda_number;

2. 참석자별 회의록 vs AI 통합 회의록 비교

SELECT
  'PARTICIPANT' as type,
  user_id,
  content,
  created_at
FROM minutes
WHERE meeting_id = 'meeting-123'
  AND is_consolidated = false

UNION ALL

SELECT
  'CONSOLIDATED' as type,
  'AI' as user_id,
  content,
  created_at
FROM minutes
WHERE meeting_id = 'meeting-123'
  AND is_consolidated = true
ORDER BY type, created_at;

3. AI 성능 모니터링

-- AI 처리 시간 통계
SELECT
  summary_type,
  COUNT(*) as total_count,
  AVG(processing_time_ms) as avg_time_ms,
  MIN(processing_time_ms) as min_time_ms,
  MAX(processing_time_ms) as max_time_ms,
  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY processing_time_ms) as p95_time_ms
FROM ai_summaries
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY summary_type;

-- Todo 추출 정확도 (신뢰도 분포)
SELECT
  CASE
    WHEN extraction_confidence >= 0.90 THEN 'HIGH (>=0.90)'
    WHEN extraction_confidence >= 0.70 THEN 'MEDIUM (0.70-0.89)'
    ELSE 'LOW (<0.70)'
  END as confidence_level,
  COUNT(*) as count
FROM todos
WHERE extracted_by = 'AI'
  AND created_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY confidence_level
ORDER BY confidence_level DESC;

🚀 마이그레이션 실행 방법

1. 로컬 환경

# Flyway 마이그레이션 (Spring Boot)
./gradlew flywayMigrate

# 또는 직접 SQL 실행
psql -h localhost -U postgres -d hgzero -f meeting/src/main/resources/db/migration/V3__add_meeting_end_support.sql

2. Docker Compose 환경

# 컨테이너 재시작 (자동 마이그레이션)
docker-compose down
docker-compose up -d meeting-service

# 마이그레이션 로그 확인
docker-compose logs -f meeting-service | grep "Flyway"

3. 프로덕션 환경

# 1. 백업
pg_dump -h prod-db-host -U postgres -d hgzero > backup_$(date +%Y%m%d_%H%M%S).sql

# 2. 마이그레이션 실행
psql -h prod-db-host -U postgres -d hgzero -f V3__add_meeting_end_support.sql

# 3. 검증
psql -h prod-db-host -U postgres -d hgzero -c "\d+ agenda_sections"
psql -h prod-db-host -U postgres -d hgzero -c "\d+ ai_summaries"

검증 체크리스트

마이그레이션 후 검증

  • minutes 테이블에 새 컬럼 추가 확인
  • agenda_sections 테이블 생성 확인
  • ai_summaries 테이블 생성 확인
  • todos 테이블 확장 확인
  • 모든 인덱스 생성 확인
  • 외래키 제약조건 확인
  • 트리거 생성 확인 (updated_at)

성능 검증

  • 참석자별 회의록 조회 성능 (인덱스 활용)
  • 안건별 섹션 조회 성능
  • AI 캐시 조회 성능 (<100ms)
  • JSON 필드 쿼리 성능

📝 참고 사항

JSON 필드 쿼리 (PostgreSQL)

-- JSON 배열 길이
SELECT jsonb_array_length(decisions::jsonb) FROM agenda_sections;

-- JSON 필드 추출
SELECT
  keywords->0 as first_keyword,
  statistics->>'todosCount' as todo_count
FROM ai_summaries;

-- JSON 배열 펼치기
SELECT
  agenda_title,
  jsonb_array_elements_text(decisions::jsonb) as decision
FROM agenda_sections;

성능 최적화 팁

  1. 캐싱 활용: ai_summaries 테이블을 우선 조회하여 불필요한 AI 호출 방지
  2. 인덱스 활용: meeting_id + summary_type 복합 인덱스로 빠른 조회
  3. JSON 필드: PostgreSQL의 jsonb 타입 사용 권장 (인덱스 지원)
  4. 파티셔닝: 대용량 데이터의 경우 created_at 기준 파티셔닝 고려