위치 기반 실시간 쿠폰 발급 플랫폼
지금 이 순간, 이 장소에서만 받을 수 있는 특별한 혜택을 경험하세요!
CouponPop은 사용자의 현재 위치 기반으로 주변 매장에서 제공하는 한정 수량 쿠폰을 실시간 발급해주는 지역 특화 프로모션 플랫폼입니다.
소상공인 및 프랜차이즈의 효율적인 지역 마케팅을 돕고, 사용자는 "지금 이 순간, 이 장소에서만 받을 수 있는" 새로운 혜택 경험을 제공합니다.
소상공인의 문제
- 높은 광고비 대비 낮은 고객 유입 효과
- 지역 기반 실시간 마케팅 시스템의 부재
- 프로모션·쿠폰 관리의 한계
사용자의 문제
- 넘쳐나는 광고 속에서 지금 내 상황과 위치에 맞는 혜택 찾기 어려움
- 오프라인 매장에서 즉각적인 보상을 주는 경험 부족
CouponPop은
실시간성 + 희소성 + 경쟁 요소를 결합해오프라인 소비 경험을 혁신합니다.
✔ 소상공인·프랜차이즈
- 광고비 대비 높은 방문 유도 효과
- 실시간 쿠폰 발행으로 유동 인구의 즉각적 전환
- 지역 단위 타깃 마케팅 가능
✔ 사용자
- "지금 여기서만 받을 수 있는" 즉시성 + 재미 요소
- 게임화된 쿠폰 획득 경험
- 가까운 매장의 혜택을 쉽게 확인
- 사용자의 GPS 좌표 기반
- 특정 지역(홍대, 강남역 등) 진입 시 주변 매장 쿠폰이 자동 노출
- 매장 반경 내 거리 기반 필터링(예: 300m)
- 매장별 쿠폰 수량 제한
- 실시간 선착순 경쟁 처리
- Redis Redisson 기반 분산 락으로 동시성 제어
- 지도 상에서 매장 위치 + 쿠폰 정보 표시
- 한눈에 가까운 매장의 혜택 파악 가능
- 발급 이력, 사용 내역 저장
- 매장별 성과 분석 가능
- 특정 시간대 발급량, 사용량
- 방문 유저 패턴
- 지역/시간별 최적 쿠폰 전략 추천
짧은 기간 안에 완성도 높은 결과물을 만들기 위해 우리 팀은 '코드만 잘 짜는 팀'이 아니라 '같은 방향으로 일하는 팀'을 목표로 했습니다.
| 원칙 | 설명 |
|---|---|
| 공유(Sharing) | 기술 지식과 설정을 팀 전체가 공유할 수 있도록 문서화 |
| 일관성(Consistency) | 환경과 구조가 통일되도록 공통 템플릿과 규칙 정의 |
| 투명성(Transparency) | 코드와 설정, 테스트 결과를 모두 문서화해 누구나 검증 가능하게 구성 |
| 재사용성(Reusability) | 반복되는 작업을 최소화하고, 공통 모듈과 가이드를 통해 효율 향상 |
| Service | 설명 | 기술 스택 |
|---|---|---|
| api-gateway | API Gateway 및 라우팅 | Spring Cloud Gateway |
| member-service | 회원 관리 | Java, Spring Boot |
| store-service | 상점 관리 | Java, Spring Boot, Elasticsearch |
| coupon-service | 쿠폰 발급 및 관리 | Java, Spring Boot, Redis |
| notification-service | 알림 전송 | Java, Spring Boot, FCM |
| batch-service | 배치 작업 | Java, Spring Boot, Spring Batch |
| Module | 설명 | Repository |
|---|---|---|
| security-module | JWT 인증/인가 공통 모듈 | GitHub Packages |
| core-module | 공통 DTO 및 유틸리티 | GitHub Packages |
| Component | Technology | Purpose |
|---|---|---|
| Cloud Platform | AWS | 전체 인프라 호스팅 |
| Container Runtime | ECS Fargate | 서버리스 컨테이너 실행 |
| Service Discovery | AWS Service Connect | 마이크로서비스 간 통신 |
| Database | MySQL (Master-Slave) | 트랜잭션 데이터 저장 |
| Cache & Lock | Redis | 캐싱 및 분산 락 |
| Search Engine | Elasticsearch | 전문 검색 및 하이브리드 검색 |
| Message Queue | RabbitMQ | 비동기 메시징 |
| Monitoring | Prometheus + Grafana | 애플리케이션 모니터링 |
| Logging | ELK Stack (Filebeat, Logstash, Elasticsearch, Kibana) | 로그 수집 및 분석 |
| CI/CD | Jenkins + SonarQube | 빌드, 테스트, 배포 자동화 |
MySQL 선택 이유
MySQL은 우리 시스템의 핵심 트랜잭션 데이터베이스 역할을 합니다.
주요 장점:
- 신뢰성: 높은 부하 상황에서도 데이터가 올바르게 저장됨
- 일관성: 동시에 발생하는 작업이 충돌 없이 처리됨
- 지속성: 관리 및 분석을 위해 장기간 안전하게 데이터가 보존됨
Elasticsearch + 하이브리드 검색 도입
기존 RDB(MySQL)는 복잡한 한글 전문 검색과 고속 검색 요구사항을 충족시키기 어려웠습니다.
- 고속 검색 (밀리초 단위 응답)
- 한글 형태소 분석 기반 정확한 검색
- AI 검색 지원 (벡터 저장 및 하이브리드 검색)
- Embedding 생성 (OpenAI API)
- 의미 검색 (KNN in Elasticsearch)
- 키워드 검색 (BM25)
- 결과 융합 및 점수 합산
효과:
- 의도형 검색어("조용한 카페")에 강점
- 정확 키워드형 검색어("스타벅스")에 강점
- 혼합형 검색어에서 최적의 결과 제공
Redis Redisson 분산 락 선택
| Lock 방식 | 처리시간 (1000명) | Throughput | CPU 사용률 |
|---|---|---|---|
| Synchronized | 11.6s | 3.0 건/s | 16.4% |
| Pessimistic Lock | 23.4s | 11.4 건/s | 21.1% |
| Optimistic Lock | 2m 37s | 9.7 건/s | 81.1% |
| Named Lock | 19.1s | 48.6 건/s | 19.9% |
| Redis Lettuce | 33.3s | 35.9 건/s | 11.2% (Redis) |
| Redis Redisson | 31.3s | 44.5 건/s | 3.2% (Redis) |
선택 이유:
- pub/sub 기반 락 해제 알림으로 스핀락 제거
- 낮은 Redis 부하 (3.2%)
- 분산 환경 지원
- 균형 잡힌 성능과 안정성
MSA 환경 구성
- EC2 인스턴스 관리 불필요
- 자동 스케일링
- 운영 부담 최소화
- ECS/Fargate와 완벽한 통합
- 논리적 DNS 이름만으로 서비스 간 통신
- Blue/Green 배포 기본 제공
- 프로필 기반 라우팅 (로컬/운영 환경 분리)
- Spring Boot 생태계와 일관성
- 빠른 개발 및 배포
- 각 서비스가 독립적으로 JWT 검증
- 공통 security-module 라이브러리 사용
- 내부 서비스 간 통신도 보호
CI/CD: Jenkins + SonarQube (Self-hosted)
선택 이유:
- AWS VPC 내부 구성으로 보안 강화
- 코드 품질 자동 검증 (SonarQube)
- 내부 전용 도메인 (jenkins.couponpop.net)
- 장기적 비용 효율성
FCM 알림 중복 발송 방지 (Redis 멱등성 락)
FCM 알림이 재시도 로직 개입 시 중복 발송되는 문제 발생
- traceId 생성: 알림을 고유하게 식별
- Redis 멱등성 락:
SET NX로 처리 여부 판단 - 정상 발송 시 TTL 연장: 7일간 중복 차단
- 실패 시 키 삭제: 재시도 가능하도록 설정
MSA 전환 후 JOIN 문제 해결
DB가 분리되면서 Store DB와 Coupon DB를 JOIN하는 쿼리 사용 불가
-
API Composition ✅ (채택)
- Store 서비스 API 호출 후 애플리케이션에서 조합
- 실시간 데이터 조회 가능
- 구현 단순, 빠른 적용
-
CQRS + Snapshot
- 읽기 전용 테이블 유지
- 이벤트 기반 동기화
- 운영 복잡도 증가
- Chunk 처리 + Bulk API
- 필터링 로직 최적화
- MQ 비동기 처리
시스템 간 Feign Client 인증 문제
배치 서버에서 내부 API 호출 시 401 Unauthorized 발생
-
SYSTEM 토큰 타입 도입
tokenType = SYSTEM클레임 추가- 시스템 전용 JWT 발급
-
JwtAuthFilter 분기 처리
- USER / SYSTEM 타입 구분
-
Feign RequestInterceptor
- SYSTEM 토큰 자동 주입
- 배치 코드에서 토큰 관리 불필요
Prometheus ECS Fargate 연동
Prometheus가 ECS Fargate Task의 동적 IP를 자동 탐색할 방법 필요
- 오픈소스 활용:
prometheus-ecs-discovery - 공유 볼륨: EC2의
./targets디렉터리를 통한 파일 교환 - 중복 타겟 제거: Service Connect 프록시 필터링
| 지표 | DB 검색 | Elasticsearch | 개선율 |
|---|---|---|---|
| 평균 응답 시간 | 8,090 ms | 57.07 ms | 99.29% ↓ |
| p95 | 15,400 ms | 255.82 ms | 98.3% ↓ |
| 처리량 (RPS) | 11.79 /s | 1,490 /s | 126.4배 ↑ |
결과: 약 141.8배 빠른 검색 속도 달성
| 시나리오 | VUs | 평균 응답 | p95 | 실패율 |
|---|---|---|---|---|
| Smoke Test | 5 | 14ms | 32ms | 91%* |
| Spike Test | 50 | 1.17s | 2.01s | 26% |
| Ramp-Up | 300 | 6.23s | 18.8s | 20% |
(*비즈니스 로직상 실패, 시스템 오류 아님)
| 테스트 | AOP (기존) | 템플릿 패턴 | 개선율 |
|---|---|---|---|
| Spike 실패율 | 26% | 0% | 100% ↓ |
| Spike 평균 응답 | 1.17s | 0.94s | 19.7% ↓ |
| Ramp-Up 실패율 | 20% | 1.33% | 93.4% ↓ |
| Ramp-Up p95 | 18s | 9.25s | 48.6% ↓ |
결론: 템플릿 메서드 패턴이 락 범위 제어와 성능 면에서 우수
ERD v1
MemberService
StoreService
CouponService
NotificationService
원세영 리더 |
김필선 부리더 |
김영훈 팀원 |
구안득 팀원 |
- Java 17+
- Docker & Docker Compose
- AWS CLI (운영 환경 배포 시)- Clone repositories
git clone https://github.com/CouponPop/local-docker-infra.git
cd local-docker-infra- Start infrastructure
docker-compose up -d- Run services 각 서비스 디렉터리에서:
./gradlew bootRun --args='--spring.profiles.active=local'# Common
SPRING_PROFILES_ACTIVE=local
# Database
DB_MASTER_URL=jdbc:mysql://localhost:3306/couponpop
DB_SLAVE_URL=jdbc:mysql://localhost:3307/couponpop
DB_USERNAME=root
DB_PASSWORD=password
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# Elasticsearch
ES_HOST=localhost
ES_PORT=9200This project is licensed under the MIT License - see the LICENSE file for details.

