789 lines
26 KiB
HTML
789 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>O2O CastAD Backend - 인프라 아키텍처</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--bg: #0f1117;
|
|
--surface: #1a1d27;
|
|
--surface2: #232733;
|
|
--border: #2e3345;
|
|
--text: #e1e4ed;
|
|
--text-dim: #8b90a0;
|
|
--accent: #6c8cff;
|
|
--accent2: #a78bfa;
|
|
--green: #34d399;
|
|
--orange: #fb923c;
|
|
--red: #f87171;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
line-height: 1.8;
|
|
}
|
|
|
|
/* 네비게이션 */
|
|
nav {
|
|
position: fixed;
|
|
top: 0;
|
|
width: 100%;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
z-index: 100;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 40px;
|
|
height: 60px;
|
|
gap: 40px;
|
|
}
|
|
|
|
nav .logo {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
nav a {
|
|
color: var(--text-dim);
|
|
text-decoration: none;
|
|
font-size: 0.9rem;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
nav a:hover { color: var(--accent); }
|
|
|
|
/* 메인 */
|
|
main {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 100px 32px 80px;
|
|
}
|
|
|
|
.hero {
|
|
text-align: center;
|
|
margin-bottom: 60px;
|
|
}
|
|
|
|
.hero h1 {
|
|
font-size: 2.2rem;
|
|
margin-bottom: 8px;
|
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.hero p {
|
|
color: var(--text-dim);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
/* 섹션 */
|
|
section {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
padding: 36px;
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
.section-header {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.section-header h2 {
|
|
font-size: 1.4rem;
|
|
margin-bottom: 4px;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.section-header h2 .num {
|
|
display: inline-block;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
text-align: center;
|
|
line-height: 28px;
|
|
font-size: 0.85rem;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.section-header .desc {
|
|
color: var(--text-dim);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* 좌우 2컬럼 (테이블/텍스트용) */
|
|
.cols {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 28px;
|
|
align-items: start;
|
|
}
|
|
|
|
.col { min-width: 0; }
|
|
|
|
/* 다이어그램 - 항상 풀 와이드, 아래 배치 */
|
|
.diagram-box {
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 32px;
|
|
margin-top: 28px;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.diagram-box .mermaid {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.diagram-box .mermaid svg {
|
|
max-width: 100%;
|
|
height: auto;
|
|
min-height: 300px;
|
|
}
|
|
|
|
.diagram-label {
|
|
text-align: center;
|
|
font-size: 0.85rem;
|
|
color: var(--text-dim);
|
|
margin-top: 12px;
|
|
}
|
|
|
|
/* 서브 타이틀 */
|
|
h3 {
|
|
font-size: 1rem;
|
|
color: var(--text);
|
|
margin: 20px 0 10px;
|
|
}
|
|
|
|
h3:first-child { margin-top: 0; }
|
|
|
|
/* 테이블 */
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 12px 0;
|
|
font-size: 0.88rem;
|
|
}
|
|
|
|
th, td {
|
|
padding: 9px 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
th {
|
|
background: var(--surface2);
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
font-size: 0.82rem;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
td { color: var(--text-dim); }
|
|
|
|
/* 태그 */
|
|
.tag {
|
|
display: inline-block;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tag-green { background: rgba(52,211,153,0.15); color: var(--green); }
|
|
.tag-orange { background: rgba(251,146,60,0.15); color: var(--orange); }
|
|
.tag-red { background: rgba(248,113,113,0.15); color: var(--red); }
|
|
|
|
/* 비용 카드 */
|
|
.cost-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 16px;
|
|
margin: 16px 0 24px;
|
|
}
|
|
|
|
.cost-card {
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.cost-card .stage {
|
|
font-size: 0.85rem;
|
|
color: var(--text-dim);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.cost-card .amount {
|
|
font-size: 1.4rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.cost-card .krw {
|
|
font-size: 0.85rem;
|
|
color: var(--text-dim);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.cost-card.s1 { border-top: 3px solid var(--green); }
|
|
.cost-card.s1 .amount { color: var(--green); }
|
|
.cost-card.s2 { border-top: 3px solid var(--orange); }
|
|
.cost-card.s2 .amount { color: var(--orange); }
|
|
.cost-card.s3 { border-top: 3px solid var(--red); }
|
|
.cost-card.s3 .amount { color: var(--red); }
|
|
|
|
/* 리스트 */
|
|
ul {
|
|
padding-left: 18px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
ul li {
|
|
color: var(--text-dim);
|
|
margin-bottom: 5px;
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
ul li strong { color: var(--text); }
|
|
|
|
/* 노트 */
|
|
.note {
|
|
background: rgba(108,140,255,0.08);
|
|
border-left: 3px solid var(--accent);
|
|
padding: 12px 16px;
|
|
border-radius: 0 8px 8px 0;
|
|
font-size: 0.88rem;
|
|
color: var(--text-dim);
|
|
margin-top: 20px;
|
|
}
|
|
|
|
code {
|
|
background: var(--surface2);
|
|
border: 1px solid var(--border);
|
|
padding: 1px 5px;
|
|
border-radius: 4px;
|
|
font-size: 0.84rem;
|
|
color: var(--green);
|
|
}
|
|
|
|
/* PDF 다운로드 버튼 */
|
|
.btn-pdf {
|
|
margin-left: auto;
|
|
padding: 6px 16px;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-pdf:hover { background: #5a7ae6; }
|
|
|
|
/* 반응형 */
|
|
@media (max-width: 1024px) {
|
|
main { padding: 80px 20px 60px; }
|
|
.cols { grid-template-columns: 1fr; }
|
|
.cost-cards { grid-template-columns: 1fr; }
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
nav { padding: 0 16px; gap: 16px; }
|
|
nav a { font-size: 0.8rem; }
|
|
section { padding: 20px; }
|
|
.hero h1 { font-size: 1.6rem; }
|
|
table { font-size: 0.8rem; }
|
|
th, td { padding: 6px 8px; }
|
|
.diagram-box { padding: 16px; }
|
|
}
|
|
|
|
/* 인쇄 / PDF 저장 — 화면과 동일한 다크 테마 유지 */
|
|
@media print {
|
|
@page {
|
|
size: A3 landscape;
|
|
margin: 10mm;
|
|
}
|
|
|
|
* {
|
|
-webkit-print-color-adjust: exact !important;
|
|
print-color-adjust: exact !important;
|
|
}
|
|
|
|
nav { display: none !important; }
|
|
.btn-pdf { display: none !important; }
|
|
|
|
main {
|
|
max-width: 100% !important;
|
|
padding: 10px !important;
|
|
}
|
|
|
|
section {
|
|
break-inside: auto;
|
|
page-break-inside: auto;
|
|
}
|
|
|
|
.diagram-box {
|
|
break-inside: auto;
|
|
page-break-inside: auto;
|
|
}
|
|
|
|
.hero {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
section {
|
|
margin-bottom: 20px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.cols {
|
|
grid-template-columns: 1fr 1fr !important;
|
|
}
|
|
|
|
.cost-cards {
|
|
grid-template-columns: repeat(3, 1fr) !important;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav>
|
|
<div class="logo">O2O CastAD</div>
|
|
<a href="#load-balancing">부하 분산</a>
|
|
<a href="#architecture">아키텍처</a>
|
|
<a href="#cost">비용 산출</a>
|
|
<button class="btn-pdf" onclick="downloadPDF()">PDF 다운로드</button>
|
|
</nav>
|
|
|
|
<main>
|
|
<div class="hero">
|
|
<h1>O2O CastAD Backend</h1>
|
|
<p>인프라 아키텍처 및 비용 산출 문서</p>
|
|
</div>
|
|
|
|
<!-- ==================== 1. 부하 분산 ==================== -->
|
|
<section id="load-balancing">
|
|
<div class="section-header">
|
|
<h2><span class="num">1</span>DB 및 서버 부하 분산 방법</h2>
|
|
<p class="desc">Nginx 로드밸런싱, 커넥션 풀 관리, 단계별 수평 확장 전략</p>
|
|
</div>
|
|
|
|
<div class="cols">
|
|
<div class="col">
|
|
<h3>현재 구현 현황 (단일 인스턴스)</h3>
|
|
<ul>
|
|
<li><strong>API 커넥션 풀</strong>: pool_size=20, max_overflow=20 → 최대 <code>40</code></li>
|
|
<li><strong>백그라운드 풀</strong>: pool_size=10, max_overflow=10 → 최대 <code>20</code></li>
|
|
<li><strong>인스턴스당 총 DB 연결</strong>: <code>40 + 20 = 60</code></li>
|
|
<li><strong>풀 리사이클</strong>: 280초 (MySQL wait_timeout 300초 이전), pre-ping 활성화</li>
|
|
</ul>
|
|
|
|
<h3>단계별 확장 전략</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>단계</th>
|
|
<th>동시접속</th>
|
|
<th>App Server</th>
|
|
<th>LB</th>
|
|
<th>DB ( MySQL Flexible)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><span class="tag tag-green">S1</span></td>
|
|
<td>~50명</td>
|
|
<td>x1</td>
|
|
<td>Nginx x1</td>
|
|
<td>Burstable B1ms</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="tag tag-orange">S2</span></td>
|
|
<td>50~200명</td>
|
|
<td>x2~4</td>
|
|
<td>Nginx</td>
|
|
<td>GP D2ds_v4 + Replica x1</td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="tag tag-red">S3</span></td>
|
|
<td>200~1,000명</td>
|
|
<td><span style="font-size:0.75rem; line-height:1.4;">API ServerxN<br/>+ Scheduler</span></td>
|
|
<td>Nginx</td>
|
|
<td>BC D4ds_v4 + Replica x2 + Redis P1</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<h3>커넥션 풀 수치 계산</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>항목</th>
|
|
<th>Stage 1 (1대)</th>
|
|
<th>Stage 2 (4대)</th>
|
|
<th>Stage 3 (8대)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Main Pool / 인스턴스</strong></td>
|
|
<td>20+20 = 40</td>
|
|
<td>10+10 = 20</td>
|
|
<td>5+5 = 10</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>BG Pool / 인스턴스</strong></td>
|
|
<td>10+10 = 20</td>
|
|
<td>5+5 = 10</td>
|
|
<td>3+3 = 6</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>인스턴스당 소계</strong></td>
|
|
<td><code>60</code></td>
|
|
<td><code>30</code></td>
|
|
<td><code>16</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Primary 총 연결</strong></td>
|
|
<td>60</td>
|
|
<td>4 x 30 = <code>120</code></td>
|
|
<td>8 x 16 = <code>128</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>max_connections 권장</strong></td>
|
|
<td>100</td>
|
|
<td>200</td>
|
|
<td>300</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="note" style="margin-top: 16px;">
|
|
<strong>핵심:</strong>
|
|
JWT Stateless 설계로 Nginx 세션 어피니티 불필요 (round-robin / least_conn).
|
|
Stage 2부터 Read Replica로 읽기 분산, Redis는 Stage 3에서 캐싱/Rate Limiting 도입.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 다이어그램: 내용 아래 풀 와이드 -->
|
|
<div class="diagram-box">
|
|
<pre class="mermaid">
|
|
graph TB
|
|
subgraph S1["Stage 1: ~50명"]
|
|
direction LR
|
|
S1N["Nginx<br/>(Reverse Proxy)"] --> S1A["App Server x1"]
|
|
S1A --> S1D[" MySQL<br/>Burstable B1ms"]
|
|
end
|
|
|
|
subgraph S2["Stage 2: 50~200명"]
|
|
direction LR
|
|
S2N["Nginx<br/>(Reverse Proxy)"] --> S2API["APP Server<br/>x 1 ~ 2"]
|
|
S2N --> S2WK["Scheduler<br/>Server"]
|
|
S2API --> S2P["MySQL BC<br/>Primary<br/>(D4ds_v4)"]
|
|
S2API --> S2R1["Read Replica<br/>x1"]
|
|
S2WK --> S2P
|
|
S2WK --> S2R1
|
|
end
|
|
|
|
subgraph S3["Stage 3: 200~1,000명"]
|
|
direction LR
|
|
S3N["Nginx<br/>(Reverse Proxy)"] --> S3API["APP Server<br/>x N"]
|
|
S3N --> S3WK["Scheduler<br/>Server"]
|
|
S3API --> S3P["MySQL BC<br/>Primary<br/>(D4ds_v4)"]
|
|
S3API --> S3R1["Read Replica<br/>xN"]
|
|
S3API --> S3RD["Redis<br/>Premium P1"]
|
|
S3WK --> S3P
|
|
S3WK --> S3R1
|
|
end
|
|
|
|
S1 ~~~ S2 ~~~ S3
|
|
|
|
style S1 fill:#0d3320,stroke:#34d399,stroke-width:2px,color:#e1e4ed
|
|
style S2 fill:#3b2506,stroke:#fb923c,stroke-width:2px,color:#e1e4ed
|
|
style S3 fill:#3b1010,stroke:#f87171,stroke-width:2px,color:#e1e4ed
|
|
</pre>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ==================== 2. 아키텍처 ==================== -->
|
|
<section id="architecture">
|
|
<div class="section-header">
|
|
<h2><span class="num">2</span>전체 아키텍처 다이어그램</h2>
|
|
<p class="desc">Nginx + FastAPI App Server 구성과 외부 서비스 연동 구조</p>
|
|
</div>
|
|
|
|
<div class="cols">
|
|
<div class="col">
|
|
<ul>
|
|
<li><strong>로드밸런서</strong>: Nginx (Reverse Proxy, L7 LB, SSL 종단)</li>
|
|
<li><strong>App Server</strong>: FastAPI (Python 3.13) — Auth, Home, Lyric, Song, Video, Social, SNS, Archive, Admin, Background Worker</li>
|
|
<li><strong>DB</strong>: Database for MySQL Flexible Server — Stage 2+ Read Replica</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col">
|
|
<ul>
|
|
<li><strong>캐시</strong>: Cache for Redis (Stage 3 도입)</li>
|
|
<li><strong>콘텐츠 생성</strong>: 가사(ChatGPT) → 음악(Suno AI) → 영상(Creatomate) → SNS 업로드</li>
|
|
<li><strong>외부 연동</strong>: Kakao OAuth, Naver Map/Search API, Blob Storage</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 다이어그램: 내용 아래 풀 와이드 -->
|
|
<div class="diagram-box">
|
|
<pre class="mermaid">
|
|
graph TB
|
|
Client["클라이언트<br/>(Web / App)"]
|
|
LB["Nginx<br/>(Reverse Proxy + SSL 종단)"]
|
|
|
|
subgraph APP["App Server (FastAPI)"]
|
|
direction LR
|
|
Auth["Auth"] --- Home["Home"] --- Lyric["Lyric"] --- Song["Song"] --- Video["Video"]
|
|
Social["Social"] --- SNS["SNS"] --- Archive["Archive"] --- Admin["Admin"] --- BG["BG Worker"]
|
|
end
|
|
|
|
subgraph DB[" MySQL Flexible Server"]
|
|
direction LR
|
|
Primary["Primary (R/W)"]
|
|
Replica["Read Replica"]
|
|
end
|
|
|
|
subgraph AI["AI 콘텐츠 생성 파이프라인"]
|
|
direction LR
|
|
ChatGPT["ChatGPT<br/>(가사 생성)"]
|
|
Suno["Suno AI<br/>(음악 생성)"]
|
|
Creatomate["Creatomate<br/>(영상 생성)"]
|
|
ChatGPT --> Suno --> Creatomate
|
|
end
|
|
|
|
subgraph EXT["외부 서비스"]
|
|
direction LR
|
|
Blob[" Blob<br/>Storage"]
|
|
Kakao["Kakao<br/>OAuth"]
|
|
YT["YouTube /<br/>Instagram"]
|
|
Naver["Naver Map /<br/>Search API"]
|
|
end
|
|
|
|
Redis[" Cache for Redis<br/>(Stage 3 도입)"]
|
|
|
|
Client -->|HTTPS| LB
|
|
LB --> APP
|
|
APP --> Primary
|
|
APP -->|"읽기 전용"| Replica
|
|
APP -.->|"Stage 3"| Redis
|
|
APP --> AI
|
|
APP --> Blob
|
|
APP --> Kakao
|
|
APP --> YT
|
|
APP --> Naver
|
|
|
|
style Client fill:#1a3a1a,stroke:#34d399,stroke-width:2px,color:#e1e4ed
|
|
style LB fill:#1a3a1a,stroke:#34d399,stroke-width:2px,color:#e1e4ed
|
|
style APP fill:#1a2744,stroke:#6c8cff,stroke-width:2px,color:#e1e4ed
|
|
style DB fill:#2a1f00,stroke:#fb923c,stroke-width:2px,color:#e1e4ed
|
|
style AI fill:#2a0f2a,stroke:#a78bfa,stroke-width:2px,color:#e1e4ed
|
|
style EXT fill:#0d2a2a,stroke:#34d399,stroke-width:2px,color:#e1e4ed
|
|
style Redis fill:#3b1010,stroke:#f87171,stroke-width:1px,color:#e1e4ed
|
|
</pre>
|
|
</div>
|
|
<p class="diagram-label">전체 시스템 아키텍처 구성도</p>
|
|
|
|
<div class="note">
|
|
<strong>콘텐츠 생성 흐름:</strong> 사용자 요청 → Naver 크롤링 → ChatGPT 가사 생성 → Suno AI 음악 생성 → Creatomate 영상 생성 → Blob 저장 → YouTube/Instagram 업로드
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ==================== 3. 비용 산출 ==================== -->
|
|
<section id="cost">
|
|
<div class="section-header">
|
|
<h2><span class="num">3</span>예상 리소스 및 비용</h2>
|
|
<p class="desc"> 기반 단계별 월 예상 비용 (인프라 + 외부 API)</p>
|
|
</div>
|
|
|
|
<div class="cost-cards">
|
|
<div class="cost-card s1">
|
|
<div class="stage">Stage 1 · 동시 ~50명</div>
|
|
<div class="amount">$170~390</div>
|
|
<div class="krw">약 22~51만원/월</div>
|
|
</div>
|
|
<div class="cost-card s2">
|
|
<div class="stage">Stage 2 · 동시 50~200명</div>
|
|
<div class="amount">$960~2,160</div>
|
|
<div class="krw">약 125~280만원/월</div>
|
|
</div>
|
|
<div class="cost-card s3">
|
|
<div class="stage">Stage 3 · 동시 200~1,000명</div>
|
|
<div class="amount">$3,850~8,500</div>
|
|
<div class="krw">약 500~1,100만원/월</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cols">
|
|
<div class="col">
|
|
<h3>항목별 비용 상세</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>항목</th>
|
|
<th>Stage 1</th>
|
|
<th>Stage 2</th>
|
|
<th>Stage 3</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>App Server</strong></td>
|
|
<td>$50~70</td>
|
|
<td>$200~400</td>
|
|
<td>$600~1,000</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Nginx</strong></td>
|
|
<td>-</td>
|
|
<td>포함 / VM $15~30</td>
|
|
<td>VM $30~60</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>MySQL Primary</strong></td>
|
|
<td>B1ms $15~25</td>
|
|
<td>GP $130~160</td>
|
|
<td>BC $350~450</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>MySQL Replica</strong></td>
|
|
<td>-</td>
|
|
<td>GP x1 $130~160</td>
|
|
<td>BC x2 $260~360</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Redis</strong></td>
|
|
<td>-</td>
|
|
<td>-</td>
|
|
<td>P1 $225</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>스토리지/네트워크</strong></td>
|
|
<td>$10~20</td>
|
|
<td>$55~100</td>
|
|
<td>$160~270</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>AI API (합계)</strong></td>
|
|
<td>$90~280</td>
|
|
<td>$400~1,250</td>
|
|
<td>$2,100~5,800</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<h3>DB 용량 예측 (1년 후)</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Stage 1 (500명)</th>
|
|
<th>Stage 2 (5,000명)</th>
|
|
<th>Stage 3 (50,000명)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>DB 용량</strong></td>
|
|
<td>~1.2GB</td>
|
|
<td>~12GB</td>
|
|
<td>~120GB</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Blob 스토리지</strong></td>
|
|
<td>~1.1TB</td>
|
|
<td>~11TB</td>
|
|
<td>~110TB</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>MySQL 추천</strong></td>
|
|
<td>32GB SSD</td>
|
|
<td>128GB SSD</td>
|
|
<td>512GB SSD</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="note" style="margin-top: 16px;">
|
|
<strong>비용 최적화 팁:</strong>
|
|
3rd party 의존도 낮춰야함
|
|
<br>
|
|
Blob Lifecycle Policy (30일 미접근 → Cool 티어),
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 다이어그램: 내용 아래 풀 와이드 -->
|
|
<div class="diagram-box">
|
|
<pre class="mermaid">
|
|
pie title Stage 3 월 비용 구성 비중
|
|
"App Server (APP+Scheduler)" : 800
|
|
"Nginx" : 45
|
|
"MySQL Primary" : 400
|
|
"MySQL Replica x2" : 310
|
|
"Redis Premium" : 225
|
|
"스토리지/네트워크" : 215
|
|
"OpenAI API" : 550
|
|
"Suno AI" : 1400
|
|
"Creatomate" : 2000
|
|
</pre>
|
|
</div>
|
|
<p class="diagram-label">Stage 3 월간 비용 구성 비율 — AI API 비중이 전체의 약 66%</p>
|
|
</section>
|
|
</main>
|
|
|
|
<script>
|
|
function downloadPDF() {
|
|
// Mermaid SVG가 렌더링된 후 인쇄
|
|
window.print();
|
|
}
|
|
|
|
mermaid.initialize({
|
|
startOnLoad: true,
|
|
theme: 'dark',
|
|
themeVariables: {
|
|
primaryColor: '#1a2744',
|
|
primaryTextColor: '#e1e4ed',
|
|
primaryBorderColor: '#6c8cff',
|
|
lineColor: '#6c8cff',
|
|
secondaryColor: '#232733',
|
|
tertiaryColor: '#2e3345',
|
|
pieSectionTextColor: '#e1e4ed',
|
|
pieLegendTextColor: '#e1e4ed',
|
|
pieTitleTextColor: '#e1e4ed',
|
|
pieStrokeColor: '#2e3345'
|
|
}
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|