728x90
반응형
'오늘가챠'는 유저가 찍은 평범한 일상 사진을 화려한 TCG(Trading Card Game) 카드로 만들어주는 서비스입니다.
단순한 이미지 업로드 게시판을 넘어서, 유저 간의 수집욕을 자극하고 실시간으로 카드를 뽑는 경험을 제공하는 것이 핵심 기획이었습니다. 이를 구현하기 위해 비용을 0원에 가깝게 유지하면서도 안정적이고 빠른 서비스를 제공할 수 있는 아키텍처 설계가 필요했습니다.

1. 기획의 제약 사항과 인프라의 조건
1인 개발로 진행되는 사이드 프로젝트 특성상 다음과 같은 뚜렷한 제약 사항이 있었습니다.
- 무자본 (비용 최적화): AWS EC2, RDS, S3를 깡통으로 세팅하면 월 최소 수만 원의 고정 비용이 발생합니다. 트래픽이 보장되지 않은 상태에서 이는 큰 부담입니다.
- 개발 속도: 인프라 설정(Nginx, SSL 발급, 로드 밸런싱)에 리소스를 쏟기보다는, AI 프롬프트 엔지니어링이나 프론트엔드 UX 개선에 시간을 쏟아야 했습니다.
- 대량의 이미지 처리: TCG 게임 특성상 고화질 이미지가 넘쳐납니다. 이를 서버에서 직접 리사이징하고 서빙하면 백엔드가 뻗어버릴 것이 자명했습니다.
2. 각 레이어별 기술 스택 선정 이유
프론트엔드: Vercel (React + Vite)
- Zero-config 배포: GitHub 리포지토리와 연결하면
main브랜치 푸시만으로 빌드부터 배포, 캐시 무효화까지 자동화됩니다. - Edge Cache: 전 세계 CDN 네트워크에 정적 에셋이 자동 캐싱되어, 화려한 UI 자원들이 유저와 가장 가까운 엣지 노드에서 서빙됩니다. (초기 로딩 속도 극대화)
Vercel의 API 프록시 기능은 vercel.json 한 줄로 FE와 BE 간의 CORS 문제를 우아하게 해결해 줍니다.
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "vite",
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://<your-app>.onrender.com/api/:path*"
}
]
}
백엔드: Render (Spring Boot + Docker)
- IaaS 대신 PaaS: AWS EC2에 직접 서버 환경을 구축하는 대신, Render를 통해 Docker 컨테이너를 그대로 배포했습니다.
- 자동화된 SSL 및 로드 밸런싱: Let's Encrypt 스크립트를 작성할 필요 없이, Render가 알아서 HTTPS 통신을 보장해 줍니다. 백엔드 개발자는 오직 비즈니스 로직에만 집중할 수 있습니다.
데이터베이스 & 인증: Supabase (PostgreSQL)
- RDBMS의 필요성: NoSQL(Firebase)로는 유저, 카드, 인벤토리, 가챠 로그 등 복잡하게 얽힌 관계형 데이터를 무결성 있게 관리하기 어려웠습니다. Supabase는 강력한 PostgreSQL을 기반으로 합니다.
- BaaS의 편의성: DB 서버 세팅 없이 GUI 툴을 제공하며, JWT 기반의 Auth(인증) 시스템이 내장되어 있어 로그인 구현 공수를 획기적으로 줄여주었습니다.
실제 프로젝트에서는 아래 DDL로 테이블을 관리합니다.
-- 핵심 테이블 구조 (schema.sql 발췌)
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
nickname VARCHAR(50),
password VARCHAR(255) NOT NULL,
gold INT NOT NULL DEFAULT 0,
gems INT NOT NULL DEFAULT 0,
role VARCHAR(10) DEFAULT 'USER',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS photos (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
image_url VARCHAR(500) NOT NULL,
theme VARCHAR(20),
rarity VARCHAR(1),
cp INT,
status VARCHAR(10) NOT NULL DEFAULT 'DRAFT',
ai_name VARCHAR(100),
ai_flavor VARCHAR(500),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS cards (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
photo_id BIGINT NOT NULL REFERENCES photos(id),
level INT NOT NULL DEFAULT 1,
is_favorite BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
미디어 서버 (CDN): Cloudinary
- On-the-fly 최적화: 프론트엔드에서 이미지를 요청할 때 URL에 파라미터(
f_auto, q_auto)만 추가하면, Cloudinary 엣지 서버가 실시간으로 WebP 포맷으로 변환 및 리사이징하여 내려줍니다. 백엔드의 이미지 처리 부담을 0으로 만들었습니다.
실제 백엔드 업로드 클라이언트 코드에서도 자동 변환을 설정해 두었습니다.
// CloudinaryClient.java — 업로드 시 자동 최적화 적용
public String upload(byte[] imageBytes) throws IOException {
Map<String, Object> result = cloudinary.uploader().upload(imageBytes, ObjectUtils.asMap(
"folder", "<your-folder>",
"transformation", new Transformation()
.width(800).crop("scale")
.quality("auto").fetchFormat("auto")
));
return (String) result.get("secure_url");
}
3. 프로덕션 배포 인프라 요약
| 서비스 | 플랫폼 | 역할 | 비용 |
| 프론트엔드 | Vercel | 정적 호스팅 + API 프록시 |
무료 |
| 백엔드 | Render | Spring Boot API (Docker) |
무료 |
| DB | Supabase | PostgreSQL + Auth | 무료 |
| 이미지 | Cloudinary | 이미지 업로드/CDN | 무료 (25GB) |
| AI 분석 | Gemini | 사진 → 카드 메타데이터 생성 |
무료 (RPM 제한) |
| 모니터링 | UptimeRobot | /health 핑(Cold Start 방지) |
무료 |
4. 마무리
이렇게 6가지 모던 클라우드 서비스를 레고 블록처럼 조립하여, 엔터프라이즈급 퍼포먼스를 내면서도 유지보수 비용은 0원에 수렴하는 아키텍처를 완성했습니다. 다음 편에서는 프론트엔드와 백엔드의 무중단 배포 파이프라인 구축 과정을 상세히 알아보겠습니다.
728x90
반응형