- 개발 기간: 2025.10 - 2025.12 (2개월)
- 개발 인원: 1인 (개인 프로젝트)
- 프런트 리포지토리 바로가기
백엔드
- Java 21, Spring Boot 3.56
- Spring Web, Spring Validation, Spring Data JPA, Spring Security
- JWT(Access/Refresh)
- QueryDSL
-
- MySQL (InnoDB), Redis, S3
- Gradle, Docker
- Thymeleaf
인프라
- AWS EC2, ALB, API Gateway
- ElastiCache, RDS
- ECR, Code Deploy
- Grafana, Prometheus
https://youtu.be/aW3katNhdAo 유튜브에서 확인할 수 있습니다
LeeDoYeon.mov
- 요청 시 JWT 검증 필터가 토큰을 확인하여 인증
- 로그인 성공 시 Access Token을 헤더로 발급
- Refresh Token을 쿠키로 발급 (HttpOnly, Secure, SameSite 설정)
- 토큰의 상태 관리를 위해서 Redis 구현
- 클라이언트가 이미지 파일명을 보내면 서버가 Pre‑Signed URL 반환
- 클라이언트는 해당 URL로 S3에 직접 업로드
- 서버의 부하를 방지하고자 프런트 측에서 호출
- 리스트 API는 lastId, size 쿼리 파라미터를 사용
- 기존 페이지네이션에서의 Offset 성능저하의 단점을 극복하고자, lastId기반의 QueryDSL 기반 쿼리문 작성
처리 내용:
- S3에 업로드되었지만 게시글에 사용되지 않은 이미지(confirmed=false) 자동 삭제
- Pre-Signed URL로 업로드 후 게시글 작성을 완료하지 않은 경우 발생하는 고아 이미지 정리
- S3 스토리지 비용 절감 및 불필요한 리소스 제거
처리 내용:
- Redis에 캐싱된 게시글 통계(조회수, 댓글수, 좋아요수)를 DB에 동기화
- 100개 단위 청크로 트랜잭션 처리하여 대용량 데이터 안정적 처리
- 동기화 완료 후 해당 Redis 키 자동 삭제
project
├─ global/ # 공통 예외, 응답 래퍼, 설정
├─ security/ # JWT, 인증/인가 필터, 핸들러
├─ platform/
│ ├─ user/ # 유저 도메인 (회원가입/로그인/마이페이지)
│ ├─ posts/ # 게시글 도메인
│ ├─ comments/ # 댓글 도메인
│ └─ images/ # 이미지(Pre‑Signed URL 발급)
└─ ...
erDiagram
USERS ||--o{ POSTS : "작성"
USERS ||--o{ COMMENTS : "작성"
USERS ||--o{ POSTS_LIKES : "좋아요"
USERS ||--o{ REPORT : "신고"
USERS ||--o{ IMAGES : "업로드"
CATEGORY ||--o{ POSTS : "분류"
CATEGORY ||--o{ CATEGORY : "계층구조"
POSTS ||--o{ COMMENTS : "포함"
POSTS ||--o{ POSTS_LIKES : "받음"
POSTS ||--o{ POSTS_IMAGES : "포함"
COMMENTS ||--o{ COMMENTS : "대댓글"
IMAGES ||--o{ POSTS_IMAGES : "매핑"
USERS {
bigint user_id PK
varchar name
varchar image_key
varchar nickname UK
varchar email UK "idx_user_email"
varchar password
enum role "MEMBER/ADMIN"
boolean deleted
timestamp created_at
timestamp updated_at
}
POSTS {
bigint post_id PK
bigint user_id FK
bigint category_id FK
varchar thumbnail_key
varchar title
text content
bigint view_count "PostStat 임베디드"
bigint comment_count "PostStat 임베디드"
bigint like_count "PostStat 임베디드"
boolean deleted
timestamp created_at
timestamp updated_at
}
CATEGORY {
bigint category_id PK
bigint parent_id FK "nullable"
varchar name UK
}
COMMENTS {
bigint comment_id PK
bigint post_id FK
bigint user_id FK
bigint parent_id FK "nullable, idx_comment_"
varchar content
boolean deleted
timestamp created_at
timestamp updated_at
}
POSTS_LIKES {
bigint user_id PK,FK
bigint post_id PK,FK
timestamp created_at
timestamp updated_at
}
REPORT {
bigint report_id PK
bigint user_id FK
enum type "POST/COMMENT"
bigint content_id
varchar reason
timestamp created_at
timestamp updated_at
}
IMAGES {
bigint id PK
bigint user_id FK "nullable"
varchar image_key UK "idx_image_key"
boolean confirmed
timestamp created_at
timestamp updated_at
}
POSTS_IMAGES {
bigint id PK
bigint post_id FK
bigint image_id FK
int ord "순서"
timestamp created_at
timestamp updated_at
}
JWT_REFRESH_TOKEN {
varchar id PK "Redis Key"
bigint user_id "Indexed"
varchar refresh_token "Indexed"
varchar device_type "Indexed"
long expire_time "TTL"
}
idx_user_email: 로그인 시 이메일 기반 빠른 조회idx_comment_: 대댓글 조회 최적화idx_image_key: S3 키 기반 이미지 검색 최적화
* AWS 3-tier 아키텍쳐 구현
이번 프로젝트는 비용적인 부분은 최소화시킨 채, 이용자 증가를 대비한 확장성 확보, 빠른 응답속도를 목표로 AWS에서 3-Tier 기반 아키텍처를 설계했습니다.
FE
- 외부 유저가 직접 접근하는 웹 서비스이므로 퍼블릭 서브넷에 배치
- Route53 → ALB(SSL) → EC2 FE 서버
- SSL/TLS는 ALB에서 처리
- FE 이미지는 DockerHub에서 pull
BE
-
Route53 → API Gateway(SSL) → ALB(HTTP) → ASG
-
초기에 개발/테스트/비용 편의성을 위해 퍼블릭 서브넷에서 운영
-
⚠ 주의: BE가 퍼블릭에 있다고 해서 외부에서 바로 접근 가능한 것은 아니며, ALB Security Group만 허용하도록 제한하여 직접 접근을 방지했습니다. 즉, 인터넷에서 바로 API 서버를 때릴 수 있는 구조가 아님.
- RDS / ElastiCache는 프라이빗 서브넷
- 외부 접근 차단, DB 보안 강화
- 단일 AZ 구성
- 애플리케이션 서버와만 통신
- 백엔드 이미지는 보안을 이유로 Private ECR 사용
- GitHub Actions에서 ECR로 push → CodeDeploy에서 pull
# ====== Build Stage ======
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /build
COPY . .
RUN ./gradlew bootJar
# ====== Runtime Stage ======
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /build/build/libs/community-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
CMD ["java", "-Dspring.profiles.active=prod", "-jar", "app.jar"]
- Build Stage: JDK + Gradle로 JAR 빌드
- Runtime Stage: JRE만 포함 → 더 가벼움
- application-prod.yml은 이미지에 포함하지 않음
- 자주 사용하는 레이어는 고정시켜 빌드 시간 단축
CI
- PR 단계: 단위 테스트만 수행 → 빠른 피드백 목표
CD
- develop merge 시: 단위 + 통합 테스트
- ECR push
- 앱 실행 스크립트 & docker-compose & appspec.zip → S3 업로드
- CodeDeploy가 ASG 대상 그룹에 Blue/Green 배포 자동 처리
- CodeDeploy + ASG 활용
- Green 인스턴스 자동 생성 후 검증
- 정상 시 트래픽 100% 전환
- 문제 발생 시 자동 롤백
- 확장성과 안전성 확보
-
본 프로젝트는 Auto Scaling 환경에서 서버 개수가 동적으로 변하기 때문에, 새로운 인스턴스가 생성·삭제되더라도 메트릭이 안정적으로 수집되는 구조를 목표로 Prometheus + Grafana 기반의 관찰성(Observability) 을 구성했습니다.
-
EC2 Service Discovery 적용 ASG 환경에서는 인스턴스가 계속 생성/삭제되기 때문에 Prometheus가 AWS API를 통해 EC2 인스턴스를 자동 검색(SD) 하고 필요한 대상만 스크랩하도록 구성했습니다.