# Content Service 데이터베이스 설계서 ## 데이터설계 요약 ### 설계 개요 - **서비스**: Content Service (이미지 생성 및 콘텐츠 관리) - **아키텍처 패턴**: Clean Architecture - **데이터베이스**: PostgreSQL (주 저장소), Redis (캐시 및 Job 상태 관리) - **설계 원칙**: 데이터독립성원칙 준수, Entity 클래스와 1:1 매핑 ### 주요 엔티티 1. **Content**: 이벤트별 콘텐츠 집합 정보 2. **GeneratedImage**: AI 생성 이미지 메타데이터 및 CDN URL 3. **Job**: 비동기 이미지 생성 작업 상태 추적 ### 캐시 설계 (Redis) 1. **RedisJobData**: Job 상태 추적 (TTL: 1시간) 2. **RedisImageData**: 이미지 캐싱 (TTL: 7일) 3. **RedisAIEventData**: AI 추천 데이터 캐싱 (TTL: 1시간) ### 주요 특징 - **이미지 메타데이터 최적화**: CDN URL만 저장, 실제 이미지는 Azure Blob Storage - **비동기 처리**: Kafka 기반 Job 처리, Redis로 상태 관리 - **캐싱 전략**: eventId 기반 캐시 키, TTL 설정으로 자동 만료 - **외부 연동**: Stable Diffusion/DALL-E API, Azure Blob Storage CDN --- ## 1. PostgreSQL 데이터베이스 설계 ### 1.1 테이블: content (콘텐츠 집합) **목적**: 이벤트별 생성된 콘텐츠 집합 정보 관리 **Entity 매핑**: `com.kt.event.content.biz.domain.Content` | 컬럼명 | 데이터 타입 | NULL | 기본값 | 설명 | |--------|------------|------|--------|------| | id | BIGSERIAL | NOT NULL | AUTO | 콘텐츠 ID (PK) | | event_id | VARCHAR(100) | NOT NULL | - | 이벤트 초안 ID | | event_title | VARCHAR(200) | NOT NULL | - | 이벤트 제목 | | event_description | TEXT | NULL | - | 이벤트 설명 | | created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시각 | | updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시각 | **제약 조건**: - PRIMARY KEY: id - UNIQUE INDEX: event_id (이벤트당 하나의 콘텐츠 집합) - INDEX: created_at (시간 기반 조회) **비즈니스 규칙**: - 하나의 이벤트는 하나의 콘텐츠 집합만 보유 - 이미지는 별도 테이블에서 1:N 관계로 관리 --- ### 1.2 테이블: generated_image (생성된 이미지) **목적**: AI 생성 이미지 메타데이터 및 CDN URL 관리 **Entity 매핑**: `com.kt.event.content.biz.domain.GeneratedImage` | 컬럼명 | 데이터 타입 | NULL | 기본값 | 설명 | |--------|------------|------|--------|------| | id | BIGSERIAL | NOT NULL | AUTO | 이미지 ID (PK) | | event_id | VARCHAR(100) | NOT NULL | - | 이벤트 초안 ID | | style | VARCHAR(20) | NOT NULL | - | 이미지 스타일 (FANCY, SIMPLE, TRENDY) | | platform | VARCHAR(30) | NOT NULL | - | 플랫폼 (INSTAGRAM, FACEBOOK, KAKAO, BLOG) | | cdn_url | VARCHAR(500) | NOT NULL | - | CDN 이미지 URL (Azure Blob) | | prompt | TEXT | NOT NULL | - | 생성에 사용된 프롬프트 | | selected | BOOLEAN | NOT NULL | false | 사용자 선택 여부 | | width | INT | NOT NULL | - | 이미지 너비 (픽셀) | | height | INT | NOT NULL | - | 이미지 높이 (픽셀) | | file_size | BIGINT | NULL | - | 파일 크기 (bytes) | | content_type | VARCHAR(50) | NOT NULL | 'image/png' | MIME 타입 | | created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시각 | | updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시각 | **제약 조건**: - PRIMARY KEY: id - INDEX: (event_id, style, platform) - 필터링 조회 최적화 - INDEX: event_id - 이벤트별 이미지 조회 - INDEX: created_at - 시간 기반 조회 - CHECK: style IN ('FANCY', 'SIMPLE', 'TRENDY') - CHECK: platform IN ('INSTAGRAM', 'FACEBOOK', 'KAKAO', 'BLOG') - CHECK: width > 0 AND height > 0 **비즈니스 규칙**: - 이미지 실체는 Azure Blob Storage에 저장, DB는 메타데이터만 보유 - 동일한 (event_id, style, platform) 조합으로 여러 이미지 생성 가능 (재생성) - selected = true인 이미지가 최종 선택 이미지 **플랫폼별 기본 해상도**: - INSTAGRAM: 1080x1080 - FACEBOOK: 1200x628 - KAKAO: 800x800 - BLOG: 800x600 --- ### 1.3 테이블: job (비동기 작업 추적) **목적**: 이미지 생성 비동기 작업 상태 추적 **Entity 매핑**: `com.kt.event.content.biz.domain.Job` | 컬럼명 | 데이터 타입 | NULL | 기본값 | 설명 | |--------|------------|------|--------|------| | id | VARCHAR(100) | NOT NULL | - | Job ID (PK) | | event_id | VARCHAR(100) | NOT NULL | - | 이벤트 초안 ID | | job_type | VARCHAR(50) | NOT NULL | - | 작업 타입 (IMAGE_GENERATION, IMAGE_REGENERATION) | | status | VARCHAR(20) | NOT NULL | 'PENDING' | 작업 상태 (PENDING, PROCESSING, COMPLETED, FAILED) | | progress | INT | NOT NULL | 0 | 진행률 (0-100) | | result_message | TEXT | NULL | - | 완료 메시지 | | error_message | TEXT | NULL | - | 에러 메시지 | | created_at | TIMESTAMP | NOT NULL | NOW() | 생성 시각 | | updated_at | TIMESTAMP | NOT NULL | NOW() | 수정 시각 | | completed_at | TIMESTAMP | NULL | - | 완료 시각 | **제약 조건**: - PRIMARY KEY: id - INDEX: event_id - 이벤트별 작업 조회 - INDEX: (status, created_at) - 상태별 작업 조회 - CHECK: status IN ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED') - CHECK: job_type IN ('IMAGE_GENERATION', 'IMAGE_REGENERATION') - CHECK: progress >= 0 AND progress <= 100 **비즈니스 규칙**: - Job ID는 "job-img-{uuid}" 형식 (외부에서 생성) - 상태 전이: PENDING → PROCESSING → COMPLETED/FAILED - COMPLETED/FAILED 상태에서 completed_at 자동 설정 - Redis에도 동일한 Job 정보 저장 (TTL 1시간, 폴링 조회 최적화) --- ## 2. Redis 캐시 설계 ### 2.1 RedisJobData (Job 상태 캐싱) **목적**: 비동기 작업 상태 폴링 조회 성능 최적화 **DTO 매핑**: `com.kt.event.content.biz.dto.RedisJobData` **Redis 키 패턴**: `job:{jobId}` **TTL**: 1시간 (3600초) **데이터 구조** (Hash): ``` job:job-img-abc123 = { "id": "job-img-abc123", "eventId": "evt-draft-12345", "jobType": "IMAGE_GENERATION", "status": "PROCESSING", "progress": 50, "resultMessage": null, "errorMessage": null, "createdAt": "2025-10-29T10:00:00Z", "updatedAt": "2025-10-29T10:00:05Z" } ``` **사용 시나리오**: 1. 이미지 생성 요청 시 Job 생성 → Redis 저장 2. 클라이언트 폴링 조회 → Redis에서 빠르게 조회 3. Job 완료 후 1시간 뒤 자동 삭제 4. PostgreSQL의 job 테이블과 동기화 (영구 이력) --- ### 2.2 RedisImageData (이미지 캐싱) **목적**: 동일 이벤트 재요청 시 즉시 반환 **DTO 매핑**: `com.kt.event.content.biz.dto.RedisImageData` **Redis 키 패턴**: `image:{eventId}:{style}:{platform}` **TTL**: 7일 (604800초) **데이터 구조** (Hash): ``` image:evt-draft-12345:SIMPLE:INSTAGRAM = { "eventId": "evt-draft-12345", "style": "SIMPLE", "platform": "INSTAGRAM", "imageUrl": "https://cdn.kt-event.com/images/evt-draft-12345-simple.png", "prompt": "Clean and simple event poster with coffee theme", "createdAt": "2025-10-29T10:00:10Z" } ``` **사용 시나리오**: 1. 이미지 생성 완료 → Redis 저장 2. 동일 이벤트 재요청 → Redis 캐시 확인 → 즉시 반환 3. 7일 후 자동 삭제 (오래된 캐시 정리) --- ### 2.3 RedisAIEventData (AI 추천 데이터 캐싱) **목적**: AI Service 이벤트 데이터 캐싱 **DTO 매핑**: `com.kt.event.content.biz.dto.RedisAIEventData` **Redis 키 패턴**: `ai:event:{eventId}` **TTL**: 1시간 (3600초) **데이터 구조** (Hash): ``` ai:event:evt-draft-12345 = { "eventId": "evt-draft-12345", "recommendedStyles": ["SIMPLE", "TRENDY"], "recommendedKeywords": ["coffee", "spring", "discount"], "cachedAt": "2025-10-29T10:00:00Z" } ``` **사용 시나리오**: 1. AI Service에서 이벤트 분석 완료 → Redis 저장 2. Content Service에서 이미지 생성 시 AI 추천 데이터 참조 3. 1시간 후 자동 삭제 --- ## 3. 인덱스 전략 ### 3.1 성능 최적화 인덱스 **generated_image 테이블**: ```sql -- 이벤트별 이미지 조회 (가장 빈번) CREATE INDEX idx_generated_image_event_id ON generated_image(event_id); -- 필터링 조회 (스타일, 플랫폼) CREATE INDEX idx_generated_image_filter ON generated_image(event_id, style, platform); -- 선택된 이미지 조회 CREATE INDEX idx_generated_image_selected ON generated_image(event_id, selected) WHERE selected = true; -- 시간 기반 조회 (최근 생성 이미지) CREATE INDEX idx_generated_image_created ON generated_image(created_at DESC); ``` **job 테이블**: ```sql -- 이벤트별 작업 조회 CREATE INDEX idx_job_event_id ON job(event_id); -- 상태별 작업 조회 (모니터링) CREATE INDEX idx_job_status ON job(status, created_at DESC); ``` **content 테이블**: ```sql -- 이벤트 ID 기반 조회 (UNIQUE) CREATE UNIQUE INDEX idx_content_event_id ON content(event_id); ``` --- ## 4. 데이터 정합성 규칙 ### 4.1 데이터 일관성 보장 **PostgreSQL ↔ Redis 동기화**: - **Write-Through**: Job 생성 시 PostgreSQL + Redis 동시 저장 - **Cache-Aside**: 이미지 조회 시 Redis 먼저 확인 → 없으면 PostgreSQL - **TTL 기반 자동 만료**: Redis 데이터는 TTL로 자동 정리 ### 4.2 트랜잭션 범위 **이미지 생성 트랜잭션**: ``` BEGIN TRANSACTION 1. Job 상태 업데이트 (PROCESSING) 2. 외부 API 호출 (Stable Diffusion) 3. CDN 업로드 (Azure Blob) 4. generated_image INSERT 5. Job 상태 업데이트 (COMPLETED) 6. Redis 캐시 저장 COMMIT ``` **실패 시 롤백**: - 외부 API 실패 → Job 상태 FAILED, error_message 저장 - CDN 업로드 실패 → 재시도 (3회), 최종 실패 시 FAILED - Circuit Breaker OPEN → Fallback 템플릿 이미지 사용 --- ## 5. 백업 및 보존 정책 ### 5.1 백업 전략 **PostgreSQL**: - **Full Backup**: 매일 오전 2시 (Cron Job) - **Incremental Backup**: 6시간마다 - **보관 기간**: 30일 **Redis**: - **RDB Snapshot**: 1시간마다 - **AOF (Append-Only File)**: 실시간 로깅 - **보관 기간**: 7일 ### 5.2 데이터 보존 정책 **generated_image**: - **보존 기간**: 90일 - **정리 방식**: created_at 기준 90일 초과 데이터 자동 삭제 (Batch Job) **job**: - **보존 기간**: 30일 - **정리 방식**: created_at 기준 30일 초과 데이터 자동 삭제 **content**: - **보존 기간**: 영구 (이미지 삭제 시에만 CASCADE 삭제) --- ## 6. 확장성 고려사항 ### 6.1 수평 확장 **Read Replica**: - PostgreSQL Read Replica 구성 (조회 성능 향상) - 쓰기: Master, 읽기: Replica **Sharding 전략** (미래 대비): - Shard Key: event_id (이벤트 ID 기반 분산) - 예상 임계점: 1억 건 이미지 이상 ### 6.2 캐시 전략 **Redis Cluster**: - 3 Master + 3 Replica 구성 - 데이터 파티셔닝: event_id 기반 Hash Slot **Cache Warming**: - 자주 조회되는 이미지는 Redis에 영구 보관 (별도 TTL 없음) --- ## 7. 모니터링 지표 ### 7.1 성능 지표 **PostgreSQL**: - QPS (Queries Per Second): 이미지 조회 빈도 - Slow Query: 100ms 이상 쿼리 모니터링 - Connection Pool: 사용률 70% 이하 유지 **Redis**: - Cache Hit Ratio: 90% 이상 목표 - Memory Usage: 80% 이하 유지 - Eviction Rate: 최소화 ### 7.2 비즈니스 지표 **이미지 생성**: - 성공률: 95% 이상 - 평균 생성 시간: 10초 이내 - Circuit Breaker OPEN 빈도: 월 5회 이하 **캐시 효율**: - 재사용률: 동일 이벤트 재요청 비율 - TTL 만료율: 7일 이내 재조회 비율 --- ## 8. 데이터독립성 검증 ### 8.1 서비스 경계 **Content Service 소유 데이터**: - content, generated_image, job 테이블 완전 소유 - 다른 서비스는 API를 통해서만 접근 **외부 의존성 최소화**: - Event Service 데이터: event_id만 참조 (FK 없음) - User Service 데이터: 참조하지 않음 - AI Service 데이터: Redis 캐시로만 참조 ### 8.2 크로스 서비스 조인 금지 **허용되지 않는 패턴**: ```sql -- ❌ 금지: Event Service DB와 조인 SELECT * FROM event_service.event e JOIN content_service.generated_image i ON e.id = i.event_id; ``` **올바른 패턴**: ```java // ✅ 허용: API 호출 또는 캐시 참조 String eventTitle = eventServiceClient.getEvent(eventId).getTitle(); ``` --- ## 9. 보안 고려사항 ### 9.1 접근 제어 **PostgreSQL**: - 계정: content_service_user (최소 권한) - 권한: content, generated_image, job 테이블만 SELECT, INSERT, UPDATE, DELETE - 스키마 변경: DBA 계정만 가능 **Redis**: - 계정: content_service_redis (별도 패스워드) - ACL: 특정 키 패턴만 접근 (`job:*`, `image:*`, `ai:event:*`) ### 9.2 데이터 암호화 **전송 암호화**: - PostgreSQL: SSL/TLS 연결 강제 - Redis: TLS 연결 강제 **저장 암호화**: - PostgreSQL: AES-256 암호화 (pgcrypto 확장) - CDN URL은 공개 데이터 (암호화 불필요) --- ## 10. 클래스 설계와의 매핑 검증 ### 10.1 Entity 클래스 매핑 | Entity 클래스 | PostgreSQL 테이블 | 필드 매핑 일치 | |--------------|-------------------|---------------| | Content | content | ✅ 완전 일치 | | GeneratedImage | generated_image | ✅ 완전 일치 (width, height 추가) | | Job | job | ✅ 완전 일치 | ### 10.2 DTO 매핑 | DTO 클래스 | Redis 키 패턴 | 필드 매핑 일치 | |-----------|---------------|---------------| | RedisJobData | job:{jobId} | ✅ 완전 일치 | | RedisImageData | image:{eventId}:{style}:{platform} | ✅ 완전 일치 | | RedisAIEventData | ai:event:{eventId} | ✅ 완전 일치 | ### 10.3 Enum 매핑 | Enum 클래스 | 데이터베이스 | 값 일치 | |------------|-------------|---------| | ImageStyle | VARCHAR CHECK | ✅ FANCY, SIMPLE, TRENDY | | Platform | VARCHAR CHECK | ✅ INSTAGRAM, FACEBOOK, KAKAO, BLOG | | JobStatus | VARCHAR CHECK | ✅ PENDING, PROCESSING, COMPLETED, FAILED | --- ## 11. 마이그레이션 전략 ### 11.1 초기 배포 **데이터베이스 생성**: ```sql CREATE DATABASE content_service_db; CREATE SCHEMA content; ``` **테이블 생성 순서**: 1. content (부모 테이블) 2. generated_image (자식 테이블) 3. job (독립 테이블) ### 11.2 버전 관리 **도구**: Flyway (Spring Boot 통합) **마이그레이션 파일 위치**: `src/main/resources/db/migration/` **명명 규칙**: `V{version}__{description}.sql` **예시**: - V1__create_content_tables.sql - V2__add_image_size_columns.sql - V3__create_job_status_index.sql --- ## 12. 테스트 데이터 ### 12.1 샘플 데이터 **Content**: ```sql INSERT INTO content (event_id, event_title, event_description) VALUES ('evt-draft-12345', '봄맞이 커피 할인 이벤트', '신메뉴 아메리카노 1+1 이벤트'); ``` **GeneratedImage**: ```sql INSERT INTO generated_image (event_id, style, platform, cdn_url, prompt, width, height) VALUES ('evt-draft-12345', 'SIMPLE', 'INSTAGRAM', 'https://cdn.kt-event.com/images/evt-draft-12345-simple.png', 'Clean and simple coffee event poster', 1080, 1080), ('evt-draft-12345', 'FANCY', 'INSTAGRAM', 'https://cdn.kt-event.com/images/evt-draft-12345-fancy.png', 'Vibrant and colorful coffee event poster', 1080, 1080); ``` **Job**: ```sql INSERT INTO job (id, event_id, job_type, status, progress) VALUES ('job-img-abc123', 'evt-draft-12345', 'IMAGE_GENERATION', 'COMPLETED', 100); ``` --- ## 13. 참조 문서 - **클래스 설계서**: design/backend/class/content-service.puml - **API 명세서**: design/backend/api/content-service-api.yaml - **통합 검증**: design/backend/class/integration-verification.md - **데이터설계 가이드**: claude/data-design.md --- **작성자**: Backend Developer (아키텍트) **작성일**: 2025-10-29 **버전**: v1.0