개요
7인 팀 프로젝트에서 가장 먼저 풀어야 했던 문제는 편지 목록과 앨범 조회의 N+1 쿼리였습니다. 백엔드 리드로 인증, 앨범·편지·사진 API, S3 업로드, Docker 기반 개발 환경을 설계했고, @Query와 @EntityGraph 최적화로 편지 목록 p95를 40% 낮췄습니다. DTO와 API 구조도 함께 정리해 팀원들이 같은 기준으로 기능을 확장할 수 있게 만들었습니다.
백엔드 리드 역할
- ›전체 API 설계 및 백엔드 아키텍처 구성 (14개 DTO, 6개 Controller)
- ›앨범·편지·사진 CRUD, JWT 인증(토큰 블랙리스트 로그아웃) 구현
- ›AWS S3 연동 이미지 업로드 파이프라인 구축
- ›N+1 문제 해결 및 k6 부하 테스트로 Before/After 성능 측정
- ›팀원 3명에게 JPA 연관관계, 쿼리 최적화, 인덱스 설계 코드 리뷰 진행
성능 최적화
- ›편지 목록: N+1(31쿼리) → @Query 서브쿼리 COUNT로 1쿼리 최적화
- ›앨범 조회: Lazy 로딩 3쿼리 → @EntityGraph로 1쿼리
- ›인덱스 4개 추가: idx_album_owner, idx_letter_album_id, idx_letter_created_at, idx_photo_letter_id
- ›k6 테스트: 회원가입 16.5 RPS, 로그인 49.7 RPS, 앨범 143.7 RPS, 편지 96.7 RPS
인프라 구성
- ›Docker Compose: MySQL 8 + Spring Boot, healthcheck 기반 순차 기동
- ›GitHub Actions CI: 자동 테스트 + JaCoCo 커버리지 리포트 생성
- ›환경 변수로 N+1 Before/After 재현 가능 (APP_PERF_USE_N1_LETTERS)
- ›Swagger/OpenAPI 3.0 API 문서화
아키텍처
전체 아키텍처▼
ERD
전체 ERD▼
시퀀스 다이어그램
로그인▼
앨범 생성▼
편지 작성▼
사진 업로드▼
문제 원인
- 01편지 목록 조회 시 편지별 사진 개수를 위해 N+1 쿼리(1 + 30회 = 31회)가 발생해 p95 22.8ms로 응답이 느렸습니다.
- 02앨범 상세 조회 시 owner, letters를 Lazy 로딩해 3회 쿼리가 발생했습니다.
- 03성능 개선 전·후를 정량적으로 비교할 부하 테스트 환경이 없었습니다.
- 04팀원마다 로컬 MySQL 설정이 달라 환경 불일치 문제가 있었습니다.
해결 과정
- 01LetterRepository에 @Query로 서브쿼리 COUNT를 사용한 findLettersWithPhotoCountByAlbumId를 작성해 1회 쿼리로 최적화했습니다.
- 02AlbumRepository에 @EntityGraph(attributePaths = {"owner", "letters"})를 적용한 findByIdWithOwnerAndLetters로 JOIN FETCH 효과를 구현했습니다.
- 03k6 스크립트 5개(스모크, 회원가입, 로그인, 앨범, 편지)를 작성하고, APP_PERF_USE_N1_LETTERS 환경변수로 최적화 전·후를 재현해 비교 측정했습니다.
- 04Docker Compose로 MySQL 8 + Spring Boot 앱을 정의하고, depends_on + healthcheck(mysqladmin ping)로 안정적인 순차 기동을 구현했습니다.
결과
- 01편지 목록 API: 쿼리 31회 → 1회, p95 22.8ms → 13.8ms(↓40%), 평균 11.5ms → 6.7ms(↓42%) 개선.
- 02앨범 조회 API: 3회 → 1회 쿼리, 4개 인덱스(idx_album_owner, idx_letter_album_id, idx_letter_created_at, idx_photo_letter_id) 추가.
- 03k6 부하테스트 결과: 회원가입 16.5 RPS(p95 115ms), 로그인 49.7 RPS(p95 116ms), 앨범 143.7 RPS(p95 12.9ms), 편지 96.7 RPS(p95 13.8ms), 모두 에러율 0%.
- 04GitHub Actions CI에서 push 시 자동 테스트 + JaCoCo 커버리지 리포트 생성, 아티팩트 7일 보관.