cd ..

Running App

운영 중인 러닝 앱에서 활동 저장 후속 작업을 비동기로 분리하고 Redis 캐시를 적용해 활동 저장 API 응답을 95%까지 줄였습니다.

95%
활동 저장 API

POST /activities 응답을 약 100ms에서 5ms로 단축

86%
요약 API

GET /activities/summary를 7.43ms에서 1.01ms로 최적화

29%
TPS

69.88에서 90.16 req/s로 높인 전체 처리량

Java 17Spring Boot 3Spring SecurityJWTSpring Data JPAPostgreSQLRedisSpring EventsReact 18TypeScriptTailwind CSSSwiftUIHealthKitDockerNginxGitHub Actionsk6Prometheus
Key Result

POST /activities 응답 시간: ~100ms → ~5ms (95% 감소), 비동기 이벤트로 메인 트랜잭션 분리.

개요

운영 중인 러닝 앱에서 가장 먼저 해결한 문제는 활동 저장이 레벨, 챌린지, 훈련 계획 업데이트까지 한 트랜잭션에 묶여 응답이 느려지는 점이었습니다. Spring Events와 @TransactionalEventListener(AFTER_COMMIT) + @Async로 후속 처리를 분리해 POST /activities를 약 100ms에서 5ms로 줄였고, Redis 캐시와 JOIN FETCH로 조회 성능도 함께 정리했습니다. 웹과 iOS 클라이언트까지 직접 구현했지만, 이 프로젝트의 핵심은 실제 사용하는 서비스를 운영하면서 백엔드 병목을 하나씩 없앤 경험입니다.

기술적 도전

  • 활동 저장 시 Spring Events + @Async로 비동기 분리 → 응답 시간 95% 단축
  • Redis 캐싱으로 조회 API 응답 70-86% 개선
  • k6 부하테스트로 전체 처리량 29% 향상 검증

실제 서비스 운영

  • NCP(네이버 클라우드)에 HTTPS 배포, 현재 운영 중
  • 데모: https://jinhyuk-portfolio1.shop
  • 개인 러닝 기록용으로 실제 사용 중 (주 3-4회 활동 기록)

플랫폼 구성

  • Backend: Spring Boot 3 + PostgreSQL + Redis
  • Web: React 18 + TypeScript + Tailwind CSS
  • iOS: SwiftUI + HealthKit + Core Location

주요 기능

  • 활동 기록, 레벨 시스템(Lv.1-10)
  • 챌린지(6종), 훈련 계획(5K/10K/하프 9종)
  • GPS 경로 추적, HealthKit 연동(심박·케이던스·걸음 수)

아키텍처

전체 아키텍처

ERD

전체 ERD

시퀀스 다이어그램

활동 기록 (이벤트 비동기)
챌린지 참여
훈련 계획 시작

문제 원인

  1. 01활동 저장 시 레벨·챌린지·훈련 계획 업데이트를 동기로 처리하면 응답 시간이 길어지고 트랜잭션 범위가 커집니다.
  2. 02활동 요약, 챌린지 목록 등 빈번한 조회 API가 매번 DB를 조회해 응답 시간이 느렸습니다.
  3. 03JOIN FETCH 없이 연관 엔티티를 조회하면 N+1 쿼리가 발생했습니다.
  4. 04최적화 효과를 정량적으로 측정·비교할 부하 테스트 환경이 없었습니다.

해결 과정

  1. 01Spring Events + @TransactionalEventListener(AFTER_COMMIT) + @Async로 레벨·챌린지·계획 업데이트를 비동기 분리했습니다. @Retryable(maxAttempts=3)로 실패 시 재시도합니다.
  2. 02Redis 캐싱을 도입해 activitySummary(5분), activeChallenges(10분), plans(30분) TTL로 조회 부하를 줄였습니다.
  3. 03JOIN FETCH 쿼리와 11개 복합 인덱스를 추가해 N+1 문제를 해결했습니다.
  4. 04k6 스크립트로 baseline, optimized, async-event, compression 시나리오를 작성하고 100 VU 부하로 Before/After를 측정했습니다.

결과

  1. 01POST /activities 응답 시간: ~100ms → ~5ms (95% 감소), 비동기 이벤트로 메인 트랜잭션 분리.
  2. 02GET /activities/summary 응답 시간: 7.43ms → 1.01ms (86% 감소), Redis 캐시 적중.
  3. 03N+1 쿼리(5건 조회 시): 6회 → 1회 (83% 감소), JOIN FETCH 적용.
  4. 04전체 처리량(TPS): 69.88 → 90.16 req/s (29% 향상), 에러율 0% 유지.
  5. 05Docker 이미지 크기: 350MB → 220MB (37% 감소), 멀티스테이지 빌드 + Alpine.