diff --git a/README.md b/README.md index 5c27ae5..0eca332 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ -Select Header - -
@@ -360,233 +356,3 @@ DB의 Pessimistic Lock (비관적 락) 을 활용하여 쿠폰 재고를 안전 🎅 [장윤혁](https://velog.io/@hyuk905/posts) : 최종 프로젝트인 만큼 좋은 팀원들과 함께 여러 가지 시도를 다양하게 해 볼 수 있어서 좋았습니다. 제가 구현하지 못한 부분에 대해서 복습하며 직접 구현해 볼 예정입니다.
- -Image -🅿️ ParkEZ: 쉽고 빠른 통합 주차 플랫폼 팀 프로젝트 -🙋♀️ ParkEZ 팀 프로젝트 설명 -ParkEZ 팀 프로젝트는 사용자와 주차장 소유자를 위한 예약, 결제, 정산이 가능한 통합 주차 플랫폼을 구현한 팀 프로젝트입니다. -팀원 : 조예인, 정준호, 이민정, 전서연, 장윤혁 -기간 : 2025.04.01 - 2025.05.06 - -📑 팀 노션 : ParkEZ - -📑 팀 브로셔 : 10조 - Park10EZ - -📑 도메인 : parkez.click - -📑 시연영상 : ParkEZ 시연영상 - - -🧑🧑🧒🧒 역할 분담 -🐣 조예인 : 주차공간 API, 리뷰 API, Redis Pub/Sub을 이용한 메일 전송 기능 - -🦦 정준호 : 유저 API, JWT 인증/인가, 카카오 로그인 기능, 프로모션 API + 동시성 제어 적용, AWS 아키텍처 구성, GitHub Actions 활용한 CI/CD - -🐶️ 이민정 : 결제 API, 정산 API, AWS S3 이미지 기능, Toss payments API 연동, Redis를 이용한 예약 대기열 기능 - -🗿 전서연 : 주차장 API, 네이버 로그인 기능, AWS Lambda를 이용한 공공데이터 저장, 카카오맵 API 연동하여 주소 저장 - -🎅 장윤혁 : 예약 API + 동시성 제어 적용, 주차장 다건 조회 캐싱, Spring Batch를 이용한 정산 기능 - - -🛠 목차 -🧰 기술스택 -🗂 시스템 아키텍처 -💿 CI/CD -🧩 와이어프레임 -🧾 ERD -📡 API 명세 -🚀 기술 고도화 -🧪 성능 테스트 -📈 테스트 커버리지 -🧯 트러블슈팅 -🔭 향후 발전 방향 -📖 회고 - -🧰 기술스택 -💻 Language / Backend - - -⚙️ Database - - -🔍 Test - - -🔐 Security - - -🎨 Collaboration Tool - - -🛠 Deployment & Distribution - - - - - -🗂 시스템 아키텍처 -ParkEZ 시스템 아키텍처 이미지 -💿 CI/CD -ParkEZ CICD -🧩 와이어프레임 -ParkEZ 와이어프레임 -🧾 ERD -Image -📡 API 명세 -📑 Swagger 참조 : parkez.click - - -🚀 기술 고도화 -☁️ AWS Lambda로 외부 API 호출 자동화 -💰 Spring Batch 기반 정산 처리 -📧 Redis Pub/Sub + Amazon SES 기반 메일 전송 - -🧪 성능 테스트 -예약 생성 시 동시성 제어 테스트 -테스트 시나리오 -총 10명의 사용자가 동시에 예약 요청 -응답 메시지 -"예약 성공" : 실제 예약 완료 -"대기열 등록됨" : 예약 실패 후 대기열 등록 -🔍 테스트 결과 -구분 응답 결과 -✅ 동시성 제어 이전 10명 모두 "예약 성공" → 중복 예약 발생 -✅ 동시성 제어 이후 1명 "예약 성공" + 9명 "대기열 등록됨" → 정상 처리됨 -결론: 동시성 제어를 통해 하나의 주차 공간에 대한 중복 예약을 방지하고, 후순위 사용자를 대기열에 안전하게 등록할 수 있도록 개선되었습니다. -주차장 조회 성능 테스트 -📊 JMeter를 활용한 주차장 조회 성능 테스트 - -🧪 테스트 개요 -목표: Redis 캐시 도입 전/후 성능 비교 -대상: 약 10만 건의 주차장 데이터 -도구: Apache JMeter -조건: 동일한 Thread 수, Ramp-up 시간, Delay 설정 -📈 테스트 결과 -항목 캐시 적용 전 캐시 적용 후 변화율 -Throughput 4.2/sec 31.9/sec 🔼 +659.5% 증가 -평균 응답시간 61,641 ms 15,678 ms 🔽 -74.6% 감소 -✅ 분석 -Redis 캐시 적용으로 처리량(Throughput)이 6배 이상 증가 -응답시간이 1/4 수준으로 단축되어 사용자 경험 개선 -대용량 데이터에 대해 캐시 적용 시 확연한 성능 향상 확인 - -📈 테스트 커버리지 -ParkEZ 테스트 커버리지 이미지 -🧯 트러블슈팅 -1. OSIV 설정 차이로 인한 상태 변경 미반영 문제 -❗ 문제 상황 - -로컬에서는 정상 동작하던 예약 상태 변경 기능이, 개발 서버에서는 DB에 반영되지 않음 -예시: reservation.cancel() 호출 후에도 ReservationStatus.CANCELED가 DB에 반영되지 않음 -🔍 원인 분석 - -OSIV 설정 차이 - -로컬: spring.jpa.open-in-view=true (기본값) -→ 트랜잭션 종료 이후에도 영속성 컨텍스트 접근 가능 -개발 서버: open-in-view=false -→ 트랜잭션 종료 시 영속성 컨텍스트도 종료 -구조적 문제 - -@Transactional(readOnly = true)가 적용된 Reader에서 조회한 엔티티는 Detached 상태일 수 있음 -이후 Writer에서 상태 변경 메서드만 호출하면 JPA의 dirty checking이 작동하지 않음 -// ReservationService -Reservation reservation = reservationReader.findMyReservation(...); // ReadOnly 트랜잭션 - reservationWriter.cancel(reservation); // 내부에서 reservation.cancel() 호출 → 변경 감지 안 됨 -✅ 해결 방안 - -✅ 단기 해결: 명시적 save 호출 - -public void cancel(Reservation reservation) { - reservation.cancel(); - reservationRepository.save(reservation); // Detached 객체 merge -} -✅ 근본적 해결: 트랜잭션 범위 재설계 - -// ReservationService -@Transactional -public void cancelReservation(...) { - Reservation reservation = reservationReader.findMyReservation(...); - reservation.cancel(); // 영속 상태에서 변경 → dirty checking 작동 -} -🎯 결과 - -개발 환경에서도 예약 상태 변경이 정상 반영됨 -구조적으로 역할 분리가 명확해짐: -Reader → 조회 책임 -Writer → 도메인 변경 책임 -Service → 트랜잭션 관리 및 흐름 조율 -2. 동시성 테스트 - 커넥션 풀 고갈 발생 -🔍 문제 발견 - -동시성 테스트 수행 중, 테스트가 끝나지 않고 대기 상태 지속 -커넥션 풀 고갈로 인한 타임아웃 현상 발생 -⚠️ 원인 분석 - -테스트 메서드에 @Transactional이 적용되어 전체 테스트가 하나의 트랜잭션으로 실행됨 -내부 메서드도 동일 트랜잭션에 묶여 커밋/롤백 지연 -결과적으로 DB 락이 해제되지 않고, 모든 스레드가 락 대기 상태에 빠짐 -커넥션 풀 부족 → 새로운 커넥션 생성 불가 → 테스트 타임아웃 -✅ 해결 방안 및 결과 - -@Transactional 어노테이션 제거 -각 스레드가 독립된 트랜잭션으로 실행되어 DB 락 정상 해제 -테스트 종료 후 deleteAllInBatch() 사용 -테스트 간 데이터 잔존 문제 방지 -트랜잭션 제거로 인한 데이터 정리를 명시적으로 수행 - -🔭 향후 발전 방향 -🎟️ 프로모션 쿠폰 발급 처리 전략 -✅ 현재 구조 DB의 Pessimistic Lock (비관적 락) 을 활용하여 쿠폰 재고를 안전하게 제어하고 있습니다. - -장점 - -다중 사용자 환경에서도 중복 발급 없이 재고 제어 가능 -단점 - -특정 프로모션 발급 요청이 몰리면 해당 레코드에 락이 걸려 -→ 다른 트랜잭션이 지연되고 -→ 쿠폰 조회 및 관련 로직에 병목이 발생할 수 있음 -🚧 개선 방향 - -🔐 분산락 적용 예정 -목표: 특정 프로모션 단위로 분산락 적용 (ex. Redis 기반) -효과: -발급에만 락을 제한하고 조회는 락 영향 없이 처리 가능 -DB 레벨 병목 없이 확장성 높은 동시성 제어 가능 -⚡ 발급과 관리 분리 -발급 요청은 동기 처리, 사용자에게 빠른 응답 제공 -발급 기록은 비동기 처리, 시스템 부하 분산 -예시: Kafka, SQS 등 메시지 큐를 통해 처리 분리 -🔔 알림 시스템 구조 개선 방향 -✅ 현재 구조 현재 알림 시스템은 Redis Pub/Sub 기반으로, 이벤트 발생 시 알림 메시지를 발행하고, 구독자에서 이메일(SES)을 전송하는 구조입니다. -이 구조는 간단하고 빠르지만, 다음과 같은 운영상의 한계가 존재합니다. - -❗ 문제점 - -📌 메시지 유실 가능성 -Redis Pub/Sub은 실시간 메시지 전파만 지원하며, 메시지를 저장하지 않음 -구독자가 다운된 경우 메시지가 유실되어 알림 손실 발생 -📌 재처리 불가 -알림 발송 실패 시 로그만 남고, 별도의 재시도 로직이 없어 운영 신뢰성 부족 -✅ 개선 방향 - -📦 메시지 영속화 기반 구조 고려 -Redis Streams 또는 Kafka 등 영속 메시징 큐로 교체 또는 보완 시스템 도입 예정 -구독자 장애 시에도 재수신 및 복구 가능 -🔁 실패 내역 저장 및 재처리 도입 -발송 실패 이력을 Redis List 또는 DB Table 등에 저장 -Scheduled Task 또는 Spring Batch를 활용한 재처리 구조 적용 -장기적으로는 Kafka DLQ(Dead Letter Queue) 도입 고려 - -📖 회고 -🐣 조예인 : 긴 여정을 좋은 튜터님, 팀원분들을 만나 잘 마무리할 수 있었습니다. 정말 감사합니다. 프로젝트 이후에도 같이 만나서복습, 개선 및 발전하고 싶습니다. - -🦦 정준호 : 팀원들과의 지속적인 커뮤니케이션과 역할 분담을 통해 점차 안정적인 개발 흐름을 만들어갈 수 있었습니다. 짧은 기간이었지만 기술뿐만 아니라 협업 역량과 책임감까지 함께 키울 수 있었던 값진 시간이었습니다. - -🐶️ 이민정 : 처음으로 기획부터 배포까지 해본 프로젝트인 만큼 어려운 점도 많았지만 배운 점도 많았던 시간이었습니다. 프로젝트는 끝났지만, 제가 구현하지 않은 부분에 대해서도꼭 복습하며 공부해야겠다고 생각했습니다. 좋은 팀원들과 튜터님과 소통하며 잘 마무리할 수 있어서 감사했습니다. - -🗿 전서연 : 한 달 동안 최종 프로젝트를 진행하며 좋은 경험을 하게 되어 뜻 깊은 시간이었습니다. 어려운 부분이 많았지만 좋은 팀원과 튜터님 덕분에 잘 마무리 할 수 있게 되었습니다. 10조 취뽀 화이팅 - -🎅 장윤혁 : 최종 프로젝트인 만큼 좋은 팀원들과 함께 여러 가지 시도를 다양하게 해 볼 수 있어서 좋았습니다. 제가 구현하지 못한 부분에 대해서 복습하며 직접 구현해 볼 예정입니다.