개요
11인 팀 해커톤 프로젝트였지만, 제가 맡은 범위는 예약 정합성, 상품 목록 최적화, 알림 시스템처럼 백엔드 핵심 흐름이었습니다. 상품 목록 조회에서 User, Hashtag, Follow를 건건이 읽던 구조를 JOIN FETCH + 배치 쿼리로 바꿔 p95를 1,010ms에서 23ms로 줄였고, 예약 API에는 비관적 락과 캐시 우회 처리를 넣어 초과 예약 없이 동시성을 검증했습니다. 해커톤 산출물에서 끝내지 않고 민감정보 노출 정리와 k6 재측정까지 이어간 팀 프로젝트입니다.
팀 프로젝트와 맡은 범위
- ›11인 팀에서 예약 시스템, 상품 목록 성능 최적화, 알림 시스템을 담당
- ›예약 API에 Pessimistic Lock 기반 재고 차감과 취소 흐름 구현
- ›댓글·답글·팔로우 알림 및 읽음 처리 API 구성
- ›해커톤 종료 후 정합성, 보안, 성능 지표를 다시 정리해 포트폴리오 수준으로 보강
성능 최적화
- ›상품 100개 기준 쿼리 201회 → 3회, p95 1,010ms → 23ms, 처리량 30 req/s → 253 req/s
- ›JOIN FETCH로 Product-User-Hashtag를 한 번에 읽고, 팔로우 상태는 배치 쿼리로 사전 로딩
- ›해시태그 업서트는 findByName/save 반복 대신 findByNameIn + saveAll로 최대 2회 쿼리로 고정
- ›검색 API는 30 VU, p95 72ms, 223 req/s, 에러율 0%를 기록
예약 정합성
- ›100 VU, 재고 50개 동시 예약 테스트에서 성공 50건, 실패 50건으로 정합성 확인
- ›Product 행에 @Lock(PESSIMISTIC_WRITE)를 적용해 중복 차감을 방지
- ›OSIV + Hibernate L1 캐시 때문에 FOR UPDATE가 무시되던 문제를 entityManager.detach()로 해결
- ›최종 재고 0 유지, 초과 예약 없이 정합성 100% 확인
보안 & 운영
- ›@JsonIgnore로 passwordHash, verificationToken 같은 민감정보를 API 응답에서 제거
- ›JWT 인증, BCrypt 비밀번호 암호화, 환경변수 기반 비밀키 분리
- ›AWS S3 이미지 업로드와 Swagger/OpenAPI 문서 구성
- ›git filter-repo와 GitHub Push Protection으로 노출된 비밀값 이력 정리
아키텍처
전체 아키텍처▼
ERD
전체 ERD▼
시퀀스 다이어그램
JWT 인증 흐름▼
예약 락 처리▼
문제 원인
- 01상품 목록 조회 시 User, Hashtag, Follow를 건건이 읽어 상품 100개 기준 201회 쿼리와 p95 1,010ms가 발생했습니다.
- 02같은 상품을 여러 사용자가 동시에 예약하면 재고가 음수로 내려가는 Race Condition이 발생했습니다.
- 03OSIV 환경에서 Hibernate L1 캐시가 findByIdForUpdate()를 우회해 Pessimistic Lock이 실제로 걸리지 않는 문제가 있었습니다.
- 04연관 엔티티 직렬화 과정에서 passwordHash, verificationToken 같은 민감정보가 응답에 노출될 수 있었습니다.
해결 과정
- 01JOIN FETCH로 Product-User-Hashtag를 한 번에 조회하고, 팔로우 상태는 배치 쿼리로 사전 로딩해 목록 조회를 3회 쿼리로 정리했습니다.
- 02ProductRepository.findByIdForUpdate()에 @Lock(PESSIMISTIC_WRITE)를 적용해 재고 차감 구간을 직렬화했습니다.
- 03entityManager.detach()로 캐시된 Product를 분리한 뒤 SELECT ... FOR UPDATE를 다시 실행해 실제 락이 걸리도록 수정했습니다.
- 04@JsonIgnore, 환경변수 분리, git filter-repo로 민감정보 노출 경로를 정리했습니다.
결과
- 01상품 목록 조회: 상품 100개 기준 201회 → 3회 쿼리, p95 1,010ms → 23ms, 처리량 30 req/s → 253 req/s로 개선했습니다.
- 02동시 예약 테스트(100 VU, 재고 50개): 성공 50건, 실패 50건, 최종 재고 0으로 정합성 100%를 확인했습니다.
- 03검색 API 부하테스트(30 VU, 30초): p95 72ms, 223 req/s, 에러율 0%를 기록했습니다.
- 04API 응답에서 민감정보를 제거했고, 비밀키는 환경변수로 분리해 Push Protection과 함께 관리하게 만들었습니다.