펜타 서비스 데이터베이스 스키마 (단순화 버전)
개요
콘텐츠 플랫폼의 핵심 기능에 집중한 단순화된 데이터베이스 스키마입니다. 확장성을 유지하면서 불필요한 복잡도를 제거했습니다.
1. 콘텐츠 도메인
books (도서)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 고유 식별자 | PK, AUTO_INCREMENT |
| title | VARCHAR(500) | 도서 제목 | NOT NULL |
| author | VARCHAR(200) | 작가 | NULL 가능 |
| publisher | VARCHAR(100) | 출판사 | NOT NULL |
| synopsis | TEXT | 줄거리 | NULL 가능 |
| age_range | VARCHAR(20) | 연령대 | NULL 가능 |
| published_date | DATE | 공개일 | NOT NULL |
| metadata | JSON | 추가 메타데이터 | NULL 가능 |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
book_languages (도서 언어)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| language_code | VARCHAR(10) | 언어 코드 | PK |
| title | VARCHAR(500) | 번역 제목 | NOT NULL |
| cover_url | VARCHAR(500) | 표지 URL | NOT NULL |
| content_url | VARCHAR(500) | 콘텐츠 URL | NOT NULL |
episodes (에피소드)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| episode_id | BIGINT | 에피소드 고유 식별자 | PK, AUTO_INCREMENT |
| book_id | BIGINT | 도서 ID | FK → books.book_id |
| episode_number | INT | 회차 번호 | NOT NULL |
| title | VARCHAR(500) | 에피소드 제목 | NOT NULL |
categories (카테고리)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| category_id | BIGINT | 카테고리 고유 식별자 | PK, AUTO_INCREMENT |
| name | VARCHAR(100) | 카테고리명 (기본) | NOT NULL |
| type | VARCHAR(50) | 카테고리 유형 | NOT NULL |
| parent_id | BIGINT | 상위 카테고리 | FK → categories.category_id, NULL 가능 |
| translations | JSON | 다국어 번역 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NULL 가능 |
book_categories (도서-카테고리 매핑)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| category_id | BIGINT | 카테고리 ID | PK, FK → categories.category_id |
book_series (시리즈)
홈 화면 필터 기능의 시리즈(프렌차이즈) 필터 구현을 위한 테이블입니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| series_id | BIGINT | 시리즈 고유 식별자 | PK, AUTO_INCREMENT |
| series_name | VARCHAR(200) | 시리즈명 | NOT NULL |
| series_type | ENUM | 시리즈 유형 ('franchise', 'collection') | NOT NULL |
| display_order | INT | 노출 순서 | DEFAULT 0 |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
book_series_mapping (도서-시리즈 매핑)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| series_id | BIGINT | 시리즈 ID | PK, FK → book_series.series_id |
| order_in_series | INT | 시리즈 내 순서 | DEFAULT 0 |
book_tags (도서 태그)
테마별 큐레이션과 캐릭터 친구들 기능의 정확한 구현을 위한 태그 시스템입니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| tag_id | BIGINT | 태그 고유 식별자 | PK, AUTO_INCREMENT |
| tag_name | VARCHAR(100) | 태그명 | NOT NULL, UNIQUE |
| tag_type | ENUM | 태그 유형 ('theme', 'character', 'feature') | NOT NULL |
| tag_translations | JSON | 다국어 태그명 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NULL 가능 |
book_tag_mapping (도서-태그 매핑)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| tag_id | BIGINT | 태그 ID | PK, FK → book_tags.tag_id |
book_scores (도서 점수)
홈 화면의 PV 기반 정렬을 위한 테이블입니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| pv_score | INT | 페이지뷰 점수 | DEFAULT 0 |
characters (캐릭터)
작품에 등장하는 주요 캐릭터 정보를 관리합니다. 캐릭터 친구들 큐레이션에 활용됩니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| character_id | BIGINT | 캐릭터 고유 식별자 | PK, AUTO_INCREMENT |
| character_name | VARCHAR(200) | 캐릭터명 | NOT NULL |
| character_type | ENUM | 캐릭터 유형 ('main', 'supporting', 'minor') | DEFAULT 'main' |
| description | TEXT | 캐릭터 설명 | NULL 가능 |
| image_url | VARCHAR(500) | 캐릭터 이미지 URL | NULL 가능 |
| name_translations | JSON | 다국어 캐릭터명 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NULL 가능 |
| display_order | INT | 노출 순서 | DEFAULT 0 |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
book_characters (도서-캐릭터 매핑)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| character_id | BIGINT | 캐릭터 ID | PK, FK → characters.character_id |
| role_in_book | VARCHAR(100) | 작품 내 역할 | NULL 가능 |
2. 리워드 도메인
stickers (스티커)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| sticker_id | BIGINT | 스티커 고유 식별자 | PK, AUTO_INCREMENT |
| episode_id | BIGINT | 에피소드 ID | FK → episodes.episode_id, UNIQUE |
| name | VARCHAR(200) | 스티커명 (기본) | NOT NULL |
| name_translations | JSON | 다국어 스티커명 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NULL 가능 |
| image_url | VARCHAR(500) | 이미지 URL | NOT NULL |
user_stickers (획득 스티커)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| user_id | BIGINT | 사용자 ID | PK, FK → users.user_id |
| sticker_id | BIGINT | 스티커 ID | PK, FK → stickers.sticker_id |
| earned_at | TIMESTAMP | 획득일시 | DEFAULT CURRENT_TIMESTAMP |
3. 사용자 활동 도메인
reading_history (열람 기록)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| history_id | BIGINT | 기록 고유 식별자 | PK, AUTO_INCREMENT |
| user_id | BIGINT | 사용자 ID | FK → users.user_id, INDEX |
| book_id | BIGINT | 도서 ID | FK → books.book_id |
| episode_id | BIGINT | 에피소드 ID | FK → episodes.episode_id, NULL 가능 |
| last_position | INT | 마지막 위치 | DEFAULT 0 |
| is_completed | BOOLEAN | 완독 여부 | DEFAULT FALSE |
| last_read_at | TIMESTAMP | 마지막 열람일시 | DEFAULT CURRENT_TIMESTAMP |
bookmarks (북마크)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| user_id | BIGINT | 사용자 ID | PK, FK → users.user_id |
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
recordings (녹음)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| recording_id | BIGINT | 녹음 고유 식별자 | PK, AUTO_INCREMENT |
| user_id | BIGINT | 사용자 ID | FK → users.user_id |
| book_id | BIGINT | 도서 ID | FK → books.book_id |
| language_code | VARCHAR(10) | 언어 코드 | NOT NULL |
| file_url | VARCHAR(500) | 파일 URL | NOT NULL |
| duration | INT | 길이(초) | NOT NULL |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
4. 콘텐츠 노출 도메인
content_lists (콘텐츠 리스트)
큐레이션, 랭킹, 스티커 구분 등 다양한 콘텐츠 리스트를 통합 관리합니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| list_id | BIGINT | 리스트 고유 식별자 | PK, AUTO_INCREMENT |
| list_type | VARCHAR(50) | 리스트 유형 (curation, ranking, popular_sticker, missing_sticker, upcoming_sticker) | NOT NULL |
| name | VARCHAR(200) | 리스트명 (기본) | NOT NULL |
| name_translations | JSON | 다국어 리스트명 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NULL 가능 |
| criteria | JSON | 선정 기준 (예: {"period": "24h", "country": "KR"}) | NULL 가능 |
| is_active | BOOLEAN | 활성화 여부 | DEFAULT TRUE |
| updated_at | TIMESTAMP | 갱신일시 | DEFAULT CURRENT_TIMESTAMP |
content_list_items (리스트 아이템)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| list_id | BIGINT | 리스트 ID | PK, FK → content_lists.list_id |
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| position | INT | 순서 | NOT NULL |
| added_at | TIMESTAMP | 추가일시 | DEFAULT CURRENT_TIMESTAMP |
banners (배너)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| banner_id | BIGINT | 배너 고유 식별자 | PK, AUTO_INCREMENT |
| type | VARCHAR(50) | 배너 유형 | NOT NULL |
| image_url | VARCHAR(500) | 이미지 URL | NOT NULL |
| target_type | VARCHAR(50) | 대상 유형 | NULL 가능 |
| target_id | BIGINT | 대상 ID | NULL 가능 |
| position | INT | 순서 | DEFAULT 0 |
| is_active | BOOLEAN | 활성화 여부 | DEFAULT TRUE |
| start_date | DATETIME | 시작일시 | NOT NULL |
| end_date | DATETIME | 종료일시 | NULL 가능 |
5. 운영 도메인
notifications (알림)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| notification_id | BIGINT | 알림 고유 식별자 | PK, AUTO_INCREMENT |
| type | VARCHAR(50) | 알림 유형 | NOT NULL |
| title | JSON | 다국어 제목 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NOT NULL |
| message | JSON | 다국어 내용 {"ko": "...", "en": "...", "ja": "...", "es": "..."} | NOT NULL |
| target_type | VARCHAR(50) | 대상 유형 | NULL 가능 |
| target_id | BIGINT | 대상 ID | NULL 가능 |
| created_at | TIMESTAMP | 생성일시 | DEFAULT CURRENT_TIMESTAMP |
user_notifications (사용자 알림)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| user_id | BIGINT | 사용자 ID | PK, FK → users.user_id |
| notification_id | BIGINT | 알림 ID | PK, FK → notifications.notification_id |
| is_read | BOOLEAN | 읽음 여부 | DEFAULT FALSE |
| received_at | TIMESTAMP | 수신일시 | DEFAULT CURRENT_TIMESTAMP |
events (이벤트)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| event_id | BIGINT | 이벤트 고유 식별자 | PK, AUTO_INCREMENT |
| type | VARCHAR(50) | 이벤트 유형 | NOT NULL |
| title | VARCHAR(500) | 제목 | NOT NULL |
| content | TEXT | 내용 | NOT NULL |
| image_url | VARCHAR(500) | 이미지 URL | NULL 가능 |
| start_date | DATETIME | 시작일시 | NOT NULL |
| end_date | DATETIME | 종료일시 | NULL 가능 |
| is_active | BOOLEAN | 활성화 여부 | DEFAULT TRUE |
6. 통계 도메인
daily_stats (일별 통계)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| stat_date | DATE | 통계일 | PK |
| book_id | BIGINT | 도서 ID | PK, FK → books.book_id |
| views | INT | 조회수 | DEFAULT 0 |
| completions | INT | 완독수 | DEFAULT 0 |
| bookmarks | INT | 찜하기수 | DEFAULT 0 |
| stickers_earned | INT | 스티커 획득수 | DEFAULT 0 |
search_history (검색 기록)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| search_id | BIGINT | 검색 고유 식별자 | PK, AUTO_INCREMENT |
| user_id | BIGINT | 사용자 ID | FK → users.user_id |
| query | VARCHAR(500) | 검색어 | NOT NULL |
| result_count | INT | 결과수 | DEFAULT 0 |
| searched_at | TIMESTAMP | 검색일시 | DEFAULT CURRENT_TIMESTAMP |
realtime_rankings (실시간 랭킹)
시간별 Top10 랭킹의 정확한 추적과 이력 관리를 위한 테이블입니다.
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| ranking_id | BIGINT | 랭킹 고유 식별자 | PK, AUTO_INCREMENT |
| book_id | BIGINT | 도서 ID | FK → books.book_id |
| rank | INT | 순위 | NOT NULL |
| score | INT | 점수 | NOT NULL |
| country_code | VARCHAR(2) | 국가 코드 | NULL 가능 |
| calculated_at | TIMESTAMP | 집계일시 | DEFAULT CURRENT_TIMESTAMP |
| INDEX idx_country_time | (country_code, calculated_at) | 국가별 시간별 조회용 |
7. 결제 도메인
payment_transactions (결제 거래)
| 컬럼명 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| transaction_id | BIGINT | 거래 고유 식별자 | PK, AUTO_INCREMENT |
| user_id | BIGINT | 사용자 ID | FK → users.user_id |
| type | VARCHAR(50) | 거래 유형 | NOT NULL |
| amount | DECIMAL(10,2) | 금액 | NOT NULL |
| currency | VARCHAR(3) | 통화 | NOT NULL |
| status | VARCHAR(50) | 상태 | NOT NULL |
| external_id | VARCHAR(200) | 외부 거래 ID | UNIQUE |
| processed_at | TIMESTAMP | 처리일시 | DEFAULT CURRENT_TIMESTAMP |
주요 인덱스
-- 자주 조회되는 패턴에 대한 인덱스
CREATE INDEX idx_reading_history_user ON reading_history(user_id, last_read_at DESC);
CREATE INDEX idx_daily_stats_date ON daily_stats(stat_date, views DESC);
CREATE INDEX idx_book_languages ON book_languages(language_code);
CREATE INDEX idx_content_list_items ON content_list_items(list_id, position);
CREATE INDEX idx_banners_active ON banners(is_active, start_date, end_date);
-- 홈 화면 기능을 위한 추가 인덱스
CREATE INDEX idx_book_series_type ON book_series(series_type, display_order);
CREATE INDEX idx_book_tags_type ON book_tags(tag_type, tag_name);
CREATE INDEX idx_book_scores_total ON book_scores(total_score DESC);
CREATE INDEX idx_realtime_rankings ON realtime_rankings(country_code, calculated_at DESC, rank);
CREATE INDEX idx_characters_order ON characters(display_order, character_name);
CREATE INDEX idx_book_characters ON book_characters(character_id, book_id);
확장 고려사항
1. 콘텐츠 포맷
- books.metadata JSON 필드 활용
- content_format, orientation 등 추가 가능
2. 사용자 세그먼트
- user_segments 테이블 추가 가능
- A/B 테스트, 타겟 마케팅 지원
3. 실시간 기능
- 세션 관리, 실시간 동기화를 위한 테이블 추가 가능
4. 캐싱 레이어
- Redis 등을 활용한 캐싱 전략
- 자주 변경되지 않는 데이터는 캐시 활용
5. 스티커 구분 활용
- content_lists의 list_type으로 스티커 3종 구분
- popular_sticker: 인기 스티커 (24시간 기준)
- missing_sticker: 나만 없는 스티커 (사용자별 필터링)
- upcoming_sticker: 공개 예정 스티커
- criteria JSON 필드로 유연한 조건 설정 가능
6. 홈 화면 정책 완전 구현
- book_series: 시리즈(프렌차이즈) 필터 지원
- book_tags: 테마별 큐레이션 지원
- book_scores: PV 기반 점수 시스템
- realtime_rankings: 시간별 Top10 랭킹 추적
- characters: 캐릭터 친구들 큐레이션 완전 지원
마이그레이션 전략
단계별 접근
- 핵심 테이블부터 생성 (users, books, subscriptions)
- 기능별로 점진적 추가
- 데이터 마이그레이션은 배치 작업으로 처리
버전 관리
CREATE TABLE schema_versions (
version INT PRIMARY KEY,
description VARCHAR(200),
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
이 단순화된 스키마는 콘텐츠 플랫폼의 핵심 기능을 지원하면서도, 필요시 확장 가능한 구조를 유지합니다.
원본 스키마에서 제외된 주요 항목
1. 복잡한 점수 시스템
- 제외된 것: 복잡한 점수 계산 시스템, 인기도 점수 가중치
- 이유: 초기에는 단순 조회수와 완독수로 충분
- 대안: daily_stats 테이블의 기본 지표 활용
2. 개인화 추천 시스템
- 제외된 것: user_theme_preferences, user_sticker_recommendations
- 이유: MVP에서는 전체 인기 콘텐츠로 충분
- 대안: 추후 별도 추천 서비스로 확장 가능
3. 상세 행동 추적
- 제외된 것: 스크롤 속도, 체류 시간, 50%/90% 진행률
- 이유: 초기에는 완독 여부만으로도 충분
- 대안: 필요시 reading_history 테이블 확장
4. 세분화된 스티커 관리
- 제외된 것: 별도의 스티커 통계 테이블 (popular_stickers, upcoming_stickers 등)
- 이유: content_lists로 통합 관리가 더 단순함
- 대안: content_lists의 list_type을 활용한 3종 스티커 구분
5. 시간대별 집계
- 제외된 것: 시간별 통계, UTC+9 시간대 관리
- 이유: 일별 통계로 충분히 운영 가능
- 대안: 필요시 통계 테이블에 시간 컬럼 추가
6. 세부 운영 기능
- 제외된 것:
- 프로모션 코드 관리 (promo_codes)
- 관리자 권한 세분화 (admin_users)
- 콘텐츠 공개 일정 관리 (content_schedule)
- 이유: 초기에는 수동 운영으로 충분
- 대안: 운영 규모 확대시 점진적 추가
7. 고급 검색 기능
- 제외된 것:
- 인기 검색어 집계 (popular_searches)
- 검색 자동완성
- 검색 결과 가중치
- 이유: 기본 검색 기능으로 시작
- 대안: 검색 엔진 도입시 확장
8. 멀티 디바이스 관리
- 제외된 것:
- 세션 관리
- 디바이스별 로그인 추적
- 1기기 제한 강제
- 이유: 초기에는 단순 로그인으로 충분
- 대안: 필요시 세션 테이블 추가
9. 부정 사용 방지
- 제외된 것:
- 스크롤 속도 감지
- 비정상 패턴 탐지
- 상세 로그 분석
- 이유: 초기에는 기본 규칙으로 충분
- 대안: 문제 발생시 로직 추가
10. 상세 메타데이터
- 제외된 것:
- 렉사일 지수 세부 정보
- 작가별 관리
- 이유: 핵심 정보만으로 시작
- 대안: books.metadata JSON 필드 활용
이러한 단순화를 통해 개발 속도를 높이고, 핵심 기능에 집중하여 빠른 시장 검증이 가능합니다. 서비스 성장에 따라 필요한 기능을 점진적으로 추가할 수 있습니다.