┌──────────────────────────────────────────────────────┐
│ ☕ Spring Boot 3.x │ ☕ Java 21 (LTS) │ 🛡️ Spring Security 6 │
│ 🗃️ JPA/Hibernate 7 │ 📋 MyBatis 3.0 │ 💳 Toss Payments v2 │
│ 💾 MySQL 8.0 │ ⚡ Redis 7.0 │ 🔐 JWT (HS512) │
│ 🐳 Docker │ 🚀 GitHub Actions│ 🌍 OAuth2 (G/K/N) │
│ 🎯 비관적 락 │ 📈 Idempotency │ 🎁 도장깨기 + 추첨 + 꾸미기 │
└──────────────────────────────────────────────────────┘
18개 도메인 · 88개 RDB 테이블 · 200+ 엔드포인트 · JPA + MyBatis 하이브리드 · Toss Payments v2 (RestClient + Basic Auth + Idempotency-Key) · 6등급 팝콘 · 4 구독 · AI 쿼터 3-소스 · 도장깨기 (장르별 6편) · 영화티켓 추첨 (PESSIMISTIC_WRITE) · 꾸미기 6슬롯 · 등급 자동 칭호 6종 · JWT (HS512) + Refresh Rotation + OAuth2 3사 + RBAC 8 AdminRole · 비관적 락으로 포인트 이중 차감 0건 · 운영 회귀 픽스 평균 2시간 이내
| 항목 | 내용 |
|---|---|
| 이름 | Monglepick Backend |
| 기간 | 2026.01 ~ 진행 중 |
| 본인 역할 | 결제/구독/포인트/리워드/인증/인가/관리자 단독 (admin/auth/payment/reward/userwatchhistory/wishlist 등 7+ 도메인) |
| GitHub | monglepick/backend |
| 운영 URL | https://monglepick.garabu.org/api/v1/... |
| 테스트 | Backend 72 contextLoads · 도메인별 단위/통합 100+ |
각 지표가 어떤 메트릭/장애로 발견됐고 어떤 조치로 만들어진 결과인지 4단계로 풀이.
| 영역 | Before | 발견 (메트릭/장애) | 조치 | After |
|---|---|---|---|---|
| 포인트 이중 차감 | 발생 가능 | 1주차 race condition 2건 — 사용자 동시 추천 시 잔액 확인↔︎차감 사이 race | 비관적 락 @Lock(PESSIMISTIC_WRITE) 5 Repository 적용 |
0건 (4개월) |
| 결제 성공 / 포인트 미지급 | 발생 가능 | Toss confirm 후 DB commit 실패 시 사용자 손해 시나리오 식별 | 단일 @Transactional + commit 실패 시 PG 환불 보상 트랜잭션 |
0건 |
| 중복 결제 승인 | 가능 | 사용자 더블 클릭 / 클라이언트 retry 시나리오 | Idempotency-Key (orderId / session_id) 5분 TTL 강제 |
0건 (4개월) |
| 금액 위변조 | 가능 | OWASP Top-10 시나리오 분석 | confirm 시 DB 금액 vs 요청 금액 검증 강제 | 0건 |
| OAuth2 redirect_uri | HTTP 생성 | 운영 첫 주 30분 다운타임 — Google redirect_uri_mismatch |
forward-headers-strategy: framework + Nginx X-Forwarded-Proto |
HTTPS 정상 |
| JWT 재사용 공격 | 탐지 안 됨 | 위협 모델링 — Refresh 탈취 시 무한 갱신 가능 | Rotation + Reuse Detection + 사용자 전체 토큰 폐기 + Slack 알람 | 즉시 무효화 |
| 응답 P50 (JWT auth) | 850ms (출시 첫 주) | Grafana 대시보드 P95 spike | HikariCP 30 + Fetch Join + Redis 캐시 95% | 35 ms (95%↓) |
| 응답 P95 | 850ms | (위와 동일) | (위와 동일) | 220 ms (74%↓) |
| 응답 P99 | — | k6 부하 테스트 추정 | 위 + 비관적 락 트랜잭션 길이 분석 | 580 ms |
| Toss 결제 승인 P95 | — | 부하 테스트 S2 — 외부 PG 호출 + DB | RestClient 5s timeout + PG 호출 후 단일 트랜잭션 | 평균 350 ms |
| 통계 쿼리 (월별 집계) | 3,200ms (JPA + N+1) | 운영 어드민 대시보드 P95 3초 회귀 | JPA → MyBatis 동적 SQL + 복합 인덱스 (user_id, status, created_at DESC) |
50 ms 이내 (98%↓) |
| HikariCP active | 평균 28 (포화 직전) | hikaricp_connections_active/max > 0.85 알람 |
외부 API 호출을 트랜잭션 밖으로 분리 + maximum-pool-size 30 | 평균 8 (peak 18) |
| 운영 회귀 평균 픽스 | 4시간 | Phase 5 까지 grep 으로 로그 추적 | Prometheus + LangSmith + ELK 첫 달 셋업 | 2시간 (50%↓) |
| 기술 | 버전 | 용도 |
|---|---|---|
| Spring Boot | 3.x | 메인 프레임워크 |
| Java | 21 (LTS) | 언어 |
| Spring Security | 6.3 | JWT + OAuth2 + RBAC |
| Spring Data JPA | 3.4 | 엔티티 관리 + DDL |
| MyBatis | 3.0 | 동적 SQL + 복잡 쿼리 |
| Hibernate | 7.0 | JPA 구현체 |
| Lombok | latest | 보일러플레이트 제거 |
| MapStruct | latest | DTO ↔︎ Entity 매핑 |
| Springdoc OpenAPI | latest | Swagger UI |
| P6Spy | latest | SQL 모니터링 (개발) |
| 기술 | 버전 | 용도 | 핵심 설정 |
|---|---|---|---|
| MySQL | 8.0 | 메인 RDB (88 테이블) | innodb_buffer_pool=8G |
| Redis | 7.0 | 세션 · Rate Limit · 캐시 | maxmemory-policy=allkeys-lru |
| HikariCP | latest | 커넥션 풀 | max=30, idle=10, leak-detection=60s |
| Flyway | 미도입 | DDL은 JPA ddl-auto=update |
시드만 SQL 별도 |
| 기술 | 용도 |
|---|---|
| Toss Payments v2 | RestClient + Basic Auth + Idempotency-Key |
| OAuth2 (Spring Security) | Google · Kakao · Naver |
| JWT (jjwt 0.12.6) | HS512 (67바이트 secret 자동 감지) |
| JavaMailSender | 이메일 (회원가입/비밀번호 재설정) |