본문으로 건너뛰기

펜타 서비스 데이터베이스 스키마

개요

펜타 서비스의 데이터베이스 스키마 문서입니다.

  • 개발 환경: SQLite3
  • 운영 환경: PostgreSQL 13+
  • ORM: Django ORM + django-parler (다국어 지원)

테이블 구조

users (사용자)

컬럼명타입제약조건설명
idINTEGERPK
passwordVARCHAR(128)NOT NULL
is_superuserBOOLEANNOT NULL
emailVARCHAR(255)NOT NULL, UNIQUE
usernameVARCHAR(150)
full_nameVARCHAR(255)NOT NULL
social_providerVARCHAR(50)소셜 로그인 제공자
social_idVARCHAR(255)소셜 로그인 ID
languageVARCHAR(10)NOT NULL기본값: ko
countryVARCHAR(2)
ageINTEGER
is_subscribedBOOLEANNOT NULL
subscription_typeVARCHAR(20)
subscription_end_dateDATETIME
is_activeBOOLEANNOT NULL
is_staffBOOLEANNOT NULL
date_joinedDATETIMENOT NULL
last_loginDATETIME
marketing_consentBOOLEANNOT NULL
deleted_atDATETIME소프트 삭제

user_devices (사용자 기기)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
device_idVARCHAR(255)NOT NULL, UNIQUE
device_typeVARCHAR(50)NOT NULLios, android, web
device_modelVARCHAR(100)NOT NULL
app_versionVARCHAR(20)NOT NULL
last_activeDATETIMENOT NULL
created_atDATETIMENOT NULL

books (도서)

컬럼명타입제약조건설명
idINTEGERPK
age_rangeVARCHAR(20)3-5, 6-8 등
genreVARCHAR(20)NOT NULL
statusVARCHAR(20)NOT NULLdraft, published, hidden
thumbnail_urlVARCHAR(500)
viewsINTEGERNOT NULL기본값: 0
is_freeBOOLEANNOT NULL
is_newBOOLEANNOT NULL
is_exclusiveBOOLEANNOT NULL
is_featuredBOOLEANNOT NULL
featured_atDATETIME
is_activeBOOLEANNOT NULL
average_ratingDECIMAL(3,2)NOT NULL기본값: 0
total_ratingsINTEGERNOT NULL기본값: 0
pv_scoreINTEGERNOT NULL기본값: 0
metadataTEXTJSON 형태
created_atDATETIMENOT NULL
published_dateDATENOT NULL

books_translation (도서 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(books)
language_codeVARCHAR(15)NOT NULLko, en, ja, es, zh
titleVARCHAR(500)NOT NULL
authorVARCHAR(200)
publisherVARCHAR(100)NOT NULL
synopsisTEXT
descriptionTEXT
cover_urlVARCHAR(500)
content_urlVARCHAR(500)

episodes (에피소드)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
episode_numberINTEGERNOT NULL
is_freeBOOLEANNOT NULL
published_dateDATE

episodes_translation (에피소드 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(episodes)
language_codeVARCHAR(15)NOT NULL
titleVARCHAR(500)NOT NULL

episode_pages (에피소드 페이지)

컬럼명타입제약조건설명
idINTEGERPK
episode_idBIGINTNOT NULL, FK(episodes)
page_numberINTEGERNOT NULL페이지 번호
image_urlVARCHAR(500)NOT NULL페이지 이미지 URL
widthINTEGER이미지 너비 (픽셀)
heightINTEGER이미지 높이 (픽셀)
file_sizeINTEGER파일 크기 (바이트)
created_atDATETIMENOT NULL

stickers (스티커)

컬럼명타입제약조건설명
idINTEGERPK
episode_idBIGINTNOT NULL, FK(episodes), UNIQUE
image_urlVARCHAR(500)NOT NULL
created_atDATETIMENOT NULL

stickers_translation (스티커 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(stickers)
language_codeVARCHAR(15)NOT NULL
nameVARCHAR(200)NOT NULL
descriptionTEXT

user_stickers (사용자 스티커)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
sticker_idBIGINTNOT NULL, FK(stickers)
earned_atDATETIMENOT NULL

bookmarks (북마크)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
book_idBIGINTNOT NULL, FK(books)
created_atDATETIMENOT NULL

reading_history (독서 기록)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
book_idBIGINTNOT NULL, FK(books)
episode_idBIGINTFK(episodes)
last_positionINTEGERNOT NULL기본값: 0
is_completedBOOLEANNOT NULL
last_read_atDATETIMENOT NULL

subscriptions (구독)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
typeVARCHAR(20)NOT NULLbasic, premium
statusVARCHAR(20)NOT NULLactive, cancelled, expired
start_dateDATETIMENOT NULL
end_dateDATETIMENOT NULL
cancelled_atDATETIME
base_priceDECIMAL(10,2)NOT NULL
discount_rateDECIMAL(5,2)NOT NULL기본값: 0
final_priceDECIMAL(10,2)NOT NULL
currencyVARCHAR(3)NOT NULL기본값: KRW
auto_renewBOOLEANNOT NULL기본값: TRUE
next_billing_dateDATETIME
promo_codeVARCHAR(50)
created_atDATETIMENOT NULL
updated_atDATETIMENOT NULL

characters (캐릭터)

컬럼명타입제약조건설명
idINTEGERPK
character_typeVARCHAR(20)NOT NULLmain, supporting
image_urlVARCHAR(500)NOT NULL
display_orderINTEGERNOT NULL기본값: 0
created_atDATETIMENOT NULL

characters_translation (캐릭터 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(characters)
language_codeVARCHAR(15)NOT NULL
nameVARCHAR(200)NOT NULL
descriptionTEXT

categories (카테고리)

컬럼명타입제약조건설명
idINTEGERPK
typeVARCHAR(50)NOT NULLage, genre, theme
parent_idBIGINTFK(categories)자기참조

categories_translation (카테고리 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(categories)
language_codeVARCHAR(15)NOT NULL
nameVARCHAR(100)NOT NULL

book_series (도서 시리즈)

컬럼명타입제약조건설명
idINTEGERPK
series_nameVARCHAR(200)NOT NULL
series_typeVARCHAR(20)NOT NULLfranchise, collection
display_orderINTEGERNOT NULL기본값: 0
created_atDATETIMENOT NULL

book_series_mapping (도서-시리즈 매핑)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
series_idBIGINTNOT NULL, FK(book_series)
order_in_seriesINTEGERNOT NULL기본값: 0

book_tags (도서 태그)

컬럼명타입제약조건설명
idINTEGERPK
tag_typeVARCHAR(50)NOT NULL
created_atDATETIMENOT NULL

book_tags_translation (도서 태그 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(book_tags)
language_codeVARCHAR(15)NOT NULL
nameVARCHAR(100)NOT NULL

book_tag_mapping (도서-태그 매핑)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
tag_idBIGINTNOT NULL, FK(book_tags)

book_categories (도서-카테고리 매핑)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
category_idBIGINTNOT NULL, FK(categories)

book_characters (도서-캐릭터 매핑)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
character_idBIGINTNOT NULL, FK(characters)
is_mainBOOLEANNOT NULL기본값: FALSE

book_scores (도서 점수)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books), UNIQUE
pv_scoreINTEGERNOT NULL기본값: 0
bookmark_scoreINTEGERNOT NULL기본값: 0
read_50_scoreINTEGERNOT NULL기본값: 0
read_90_scoreINTEGERNOT NULL기본값: 0
search_click_scoreINTEGERNOT NULL기본값: 0
total_scoreINTEGERGENERATED계산 컬럼
updated_atDATETIMENOT NULL

daily_stats (일별 통계)

컬럼명타입제약조건설명
idINTEGERPK
stat_dateDATENOT NULL
book_idBIGINTFK(books)
country_codeVARCHAR(2)
viewsINTEGERNOT NULL기본값: 0
bookmarksINTEGERNOT NULL기본값: 0
completionsINTEGERNOT NULL기본값: 0
new_usersINTEGERNOT NULL기본값: 0
active_usersINTEGERNOT NULL기본값: 0

banners (배너)

컬럼명타입제약조건설명
idINTEGERPK
banner_typeVARCHAR(50)NOT NULLmain, event, notice
target_typeVARCHAR(50)book, url, event
target_idVARCHAR(200)
display_orderINTEGERNOT NULL기본값: 0
is_activeBOOLEANNOT NULL
start_dateDATETIMENOT NULL
end_dateDATETIMENOT NULL
created_atDATETIMENOT NULL

banners_translation (배너 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(banners)
language_codeVARCHAR(15)NOT NULL
image_urlVARCHAR(500)NOT NULL
alt_textVARCHAR(200)

notifications (알림)

컬럼명타입제약조건설명
idINTEGERPK
typeVARCHAR(50)NOT NULL
target_typeVARCHAR(50)all, user, group
target_idVARCHAR(200)
is_activeBOOLEANNOT NULL
send_dateDATETIMENOT NULL
expire_dateDATETIME
created_atDATETIMENOT NULL

notifications_translation (알림 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(notifications)
language_codeVARCHAR(15)NOT NULL
titleVARCHAR(200)NOT NULL
messageTEXTNOT NULL

user_notifications (사용자 알림)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
notification_idBIGINTNOT NULL, FK(notifications)
is_readBOOLEANNOT NULL기본값: FALSE
read_atDATETIME
created_atDATETIMENOT NULL

events_news (이벤트/뉴스)

컬럼명타입제약조건설명
idINTEGERPK
typeVARCHAR(50)NOT NULLevent, news, notice
statusVARCHAR(20)NOT NULL기본값: draft
start_dateDATETIMENOT NULL
end_dateDATETIMENOT NULL
is_activeBOOLEANNOT NULL
created_atDATETIMENOT NULL

events_news_translation (이벤트/뉴스 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(events_news)
language_codeVARCHAR(15)NOT NULL
titleVARCHAR(200)NOT NULL
contentTEXTNOT NULL
image_urlVARCHAR(500)

home_homesection (홈 섹션)

컬럼명타입제약조건설명
idINTEGERPK
section_typeVARCHAR(50)NOT NULL
display_orderINTEGERNOT NULL기본값: 0
is_activeBOOLEANNOT NULL
created_atDATETIMENOT NULL

home_homesection_translation (홈 섹션 번역)

컬럼명타입제약조건설명
idINTEGERPK
master_idBIGINTFK(home_homesection)
language_codeVARCHAR(15)NOT NULL
titleVARCHAR(200)NOT NULL
subtitleVARCHAR(200)

home_homesectioncontent (홈 섹션 콘텐츠)

컬럼명타입제약조건설명
idINTEGERPK
section_idBIGINTNOT NULL, FK(home_homesection)
book_idBIGINTNOT NULL, FK(books)
display_orderINTEGERNOT NULL기본값: 0

home_realtimeranking (실시간 랭킹)

컬럼명타입제약조건설명
idINTEGERPK
book_idBIGINTNOT NULL, FK(books)
rankINTEGERNOT NULL
scoreINTEGERNOT NULL
country_codeVARCHAR(2)
calculated_atDATETIMENOT NULL

promo_codes (프로모션 코드)

컬럼명타입제약조건설명
idINTEGERPK
codeVARCHAR(50)NOT NULL, UNIQUE
discount_typeVARCHAR(20)NOT NULLpercentage, fixed
discount_valueDECIMAL(10,2)NOT NULL
valid_fromDATETIMENOT NULL
valid_toDATETIMENOT NULL
max_usesINTEGER
used_countINTEGERNOT NULL기본값: 0
is_activeBOOLEANNOT NULL
created_atDATETIMENOT NULL

promo_code_usages (프로모션 코드 사용)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
promo_code_idBIGINTNOT NULL, FK(promo_codes)
used_atDATETIMENOT NULL

payment_transactions (결제 거래)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
subscription_idBIGINTFK(subscriptions)
transaction_idVARCHAR(100)NOT NULL, UNIQUE외부 거래 ID
amountDECIMAL(10,2)NOT NULL
currencyVARCHAR(3)NOT NULL기본값: KRW
payment_methodVARCHAR(50)NOT NULL
statusVARCHAR(20)NOT NULL
created_atDATETIMENOT NULL

search_history (검색 기록)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTFK(users)
queryVARCHAR(200)NOT NULL
result_countINTEGERNOT NULL기본값: 0
clicked_book_idBIGINTFK(books)
searched_atDATETIMENOT NULL

recordings (녹음)

컬럼명타입제약조건설명
idINTEGERPK
user_idBIGINTNOT NULL, FK(users)
book_idBIGINTNOT NULL, FK(books)
episode_idBIGINTFK(episodes)
file_urlVARCHAR(500)NOT NULL
durationINTEGERNOT NULL초 단위
created_atDATETIMENOT NULL
컬럼명타입제약조건설명
idINTEGERPK
sticker_idBIGINTNOT NULL, FK(stickers)
collected_countINTEGERNOT NULL기본값: 0
rankINTEGERNOT NULL
calculated_atDATETIMENOT NULL

sticker_stats (스티커 통계)

컬럼명타입제약조건설명
idINTEGERPK
sticker_idBIGINTNOT NULL, FK(stickers)
dateDATENOT NULL
collected_countINTEGERNOT NULL기본값: 0

주요 인덱스

사용자 관련

  • idx_users_email: users(email)
  • idx_users_social: users(social_provider, social_id)
  • idx_devices_user: user_devices(user_id)
  • idx_devices_device_id: user_devices(device_id)

콘텐츠 관련

  • idx_books_status: books(status, is_active)
  • idx_books_featured: books(is_featured, featured_at DESC)
  • idx_books_pv_score: books(pv_score DESC)
  • idx_episodes_book: episodes(book_id)
  • idx_episode_pages_episode: episode_pages(episode_id)
  • idx_episode_pages_unique: episode_pages(episode_id, page_number) UNIQUE

활동 추적

  • idx_bookmarks_user: bookmarks(user_id)
  • idx_history_user: reading_history(user_id)
  • idx_history_last_read: reading_history(last_read_at DESC)

번역 테이블

  • 모든 translation 테이블: (master_id, language_code) UNIQUE
  • 모든 translation 테이블: idx_trans_master(master_id)
  • 모든 translation 테이블: idx_trans_lang(language_code)

관계 다이어그램

users
├─ user_devices (1:N)
├─ bookmarks (1:N)
├─ reading_history (1:N)
├─ user_stickers (1:N)
├─ subscriptions (1:N)
└─ recordings (1:N)

books
├─ books_translation (1:N)
├─ episodes (1:N)
│ ├─ episodes_translation (1:N)
│ ├─ episode_pages (1:N)
│ └─ stickers (1:1)
│ └─ stickers_translation (1:N)
├─ book_series_mapping (N:M with book_series)
├─ book_tag_mapping (N:M with book_tags)
├─ book_categories (N:M with categories)
└─ book_characters (N:M with characters)

모든 마스터 테이블
└─ translation 테이블 (1:N, 언어별)