이희용 포트폴리오 원본 17페이지
1페이지: 통신 3사 멤버십 혜택 조회 서비스
01 / 17텍스트로 읽기
PORTFOLIO
통신 3사 멤버십 혜택 조회 서비스
아키텍처 설계와 성능 개선
이희용
devleehy@gmail.com
010 - 2216 - 7683
서비스 바로가기 https://itplace.click
2페이지: 목차
02 / 17텍스트로 읽기
CONTENTS
목차
주제
주요 내용
페이지
01
서비스 소개
기대 효과 · 담당 역할 · 기술 스택
3–4
02
아키텍처
AWS·OCI 전환 배경 · 각 환경의 요청 및 배포 구조
5–7
03
핵심 API 성능 개선
8–15
04
분산 인증
OAuth2 state 오류 · Redis 공유 세션
16–17
02 / 17
축척별 조회 · 혜택 캐시 · 응답 구조 · JPA 변환
랜덤 좌표 부하 테스트와 성능 검증
3페이지: IT:PLACE · 통신 3사 멤버십 혜택 조회 서비스
03 / 17텍스트로 읽기
담당 역할
프로젝트
백엔드 팀장
프로젝트 일정 관리 · AWS 고가용성 배포
Spring Security 인증·인가 · 지도 조회 성능 개선
운영
서비스 전체 담당
User·Admin API와 Web·Android 개발 및 운영
클라우드·DB 전환, 배포와 모니터링
위치 선택 후 주변 매장과 통신사·등급별 혜택 탐색
사용 전
기대효과
통신사별로 혜택 탐색
한 지도에서 혜택 확인
통신사 앱을 오가며 주변 매장과
등급에 맞는 혜택을 탐색하는 불편함
주변 매장과 통신사·등급별 혜택을
한곳에서 확인하며 멤버십 사용률 증가
03 / 17
01 서비스 소개
IT:PLACE · 통신 3사 멤버십 혜택 조회 서비스
2025.06.30 ~ 현재 운영 중
4페이지: 기술 스택과 적용 영역
04 / 17텍스트로 읽기
Java 17 · Spring Boot 3.4
API 서버 구현 · 트랜잭션 제어
Spring Data JPA · Hibernate
ORM 매핑 · 연관관계·조회 최적화
PostgreSQL · PostGIS
관계형 데이터 저장 · 반경·거리 조회
Redis
혜택·집계 공유 · 인증 상태 · 동시성 제어
Spring Security · OAuth2 · JWT
인증·인가 · 소셜 로그인 · JWT
Elasticsearch · MongoDB
검색 인덱스 · 행동 로그 저장
Docker · Nginx
컨테이너 실행 · 프록시·배포 전환
GitHub Actions · GHCR
테스트·빌드 자동화 · 이미지 저장
AWS · OCI
고가용성 설계 · 운영 비용 최적화
Grafana Cloud · Loki
메트릭 대시보드 · 로그 수집·조회
01 서비스 소개
기술 스택과 적용 영역
04 / 17
5페이지: 운영 규모에 맞춰 AWS에서 OCI로 전환
05 / 17텍스트로 읽기
OCI 단일 호스트
AWS
OCI
프로젝트 · AWS
장애 격리와 수평 확장을 위한 구조를 설계했습니다.
ALB · Auto Scaling · 다중 AZ
RDS for MySQL · Public / Private Subnet
문제: 서비스 규모 대비 큰 운영비와 운영 복잡도
운영 · OCI
ALB의 라우팅·배포 전환을 Nginx로 옮겼습니다.
Nginx · OCI 단일 호스트 · Blue-Green
Cloudflare Pages · PostgreSQL/PostGIS
02 아키텍처
운영 규모에 맞춰 AWS에서 OCI로 전환
05 / 17
6페이지: 프로젝트 AWS 아키텍처
06 / 17텍스트로 읽기
프로젝트의 설계와 배포 구성
01
장애 격리
User API를 두 가용 영역에 분산하고 ALB·Auto Scaling을 연계하는 확장 구조로 설계했습니다.
02
네트워크 분리
Public Subnet과 Private Subnet을 나눠 API와 외부 진입 경로를 분리했습니다.
03
배포 구성
CodeDeploy가 S3 배포 스크립트를실행하고, EC2가 ECR 이미지를 받아 컨테이너를 실행합니다.
06 / 17
02 아키텍처
프로젝트 AWS 아키텍처
CI / CD
Data Access
EC2
컨테이너 실행
ALB 트래픽 전환
새 인스턴스 등록
image pull
7페이지: 운영 OCI 아키텍처
07 / 17텍스트로 읽기
요청 처리와 배포 흐름
01
요청 분기
Nginx가 요청을 사용자 API와관리자 API로 나누어 전달합니다.
02
새 버전 검증
새 컨테이너를 실행한 뒤 9090 포트의 /actuator/health 응답을 확인합니다.
03
트래픽 전환
Nginx 설정 검사와 reload를 마친 뒤 기존 컨테이너를 정리합니다.
02 아키텍처
운영 OCI 아키텍처
07 / 17
웹 추천·채팅 비활성
Cloudflare Pages
정적 프런트엔드
Users / Admins
정적 파일
브라우저 API 요청
8페이지: 서비스 확장 후 확인한 조회 구조의 문제
08 / 17텍스트로 읽기
08 / 17
03 핵심 API 성능 개선
서비스 확장 후 확인한 조회 구조의 문제
조회 대상 확대
LG U+ 단독 서비스
25,459
개 매장
통신 3사 확장
73,715
개 매장
매장 데이터가 약 2.9배로 늘어난 후, 요청이 많지 않은 상황에서도 혜택 탐색이 지연됐습니다.
서비스 확장 과정에서 발견한 문제
01
필요 이상의 상세 조회
지도 탐색 단계에도
매장 상세와 혜택을 함께 반환
02
공통 혜택의 반복 처리
요청마다 DB에서 다시 조회하고,
매장별 응답에 중복되는 혜택 정보를 포함
9페이지: 매장 상세 일괄 조회에서 지도 축척별 조회로 전환
09 / 17텍스트로 읽기
상세 매장 목록
지역별 매장 분포
변경 전
변경 후
넓은 지도에서도 개별 매장·혜택을 조회
확대하면 매장 목록, 축소하면 구역 집계
지도 축척과 관계없이 매장 상세와 혜택을 함께 반환했습니다.
03 핵심 API 성능 개선
매장 상세 일괄 조회에서 지도 축척별 조회로 전환
09 / 17
지도 이동 시
반경 안 매장·상세 혜택 조회(nearby)
지도 이동과 매장 선택 시
레벨 1–4 매장 목록 조회(preview)
레벨 5 이상 구역별 매장 수·대표점(cluster)
매장 선택 선택 매장의 상세 혜택 조회
확대·축소 모두 개별 매장 상세를 반환
미리 집계한 자료(snapshot)에서 화면에 필요한 구역을 선택합니다.
구역 집계 자료는 Redis와 JVM 메모리에서 여러 화면에 재사용
원형 nearby
사각 viewport
10페이지: 혜택 캐시 도입으로 요청마다 반복하던 DB 조회 감소
10 / 17텍스트로 읽기
변경 전
변경 후
03 핵심 API 성능 개선
혜택 캐시 도입으로 요청마다 반복하던 DB 조회 감소
10 / 17
요청마다 혜택을 DB에서 다시 조회
같은 제휴처의 혜택을 Redis에서 재사용
매장 조회 뒤 혜택·등급 혜택을 일괄 조회하고,
요청이 올 때마다 같은 데이터를 다시 읽었습니다.
제휴처별 캐시를 묶어 읽고(MGET),
캐시에 없는 제휴처의 혜택만 DB에서 가져옵니다.
혜택 캐시 없음 · 지도 요청마다 DB 접근
API가 캐시 조회·DB 조회·캐시 저장을 처리
같은 제휴처의 혜택은 재사용
지도 화면 좌표 대신 제휴처 식별자로 캐시를 조회하므로, 서로 다른 화면에서도 활용
요청 1
요청 2
요청 3
혜택 DB
API
Redis
혜택 DB
1 캐시 조회(MGET)
2 미적중 혜택만 조회
3 조회 결과를 캐시에 저장
11페이지: 혜택의 변경 빈도와 캐시 용량을 고려한 TTL 설정
11 / 17텍스트로 읽기
03 핵심 API 성능 개선
혜택의 변경 빈도와 캐시 용량을 고려한 TTL 설정
11 / 17
작은 전체 캐시 용량
낮은 혜택 변경 빈도
TTL 7일
혜택 정보가 자주 바뀌지 않아
7일 동안 재사용해 반복 DB 조회를 줄임
혜택 변경 시, TTL 만료를 기다리지 않고 캐시 갱신
DB 혜택 변경
해당 캐시 무효화
새 혜택 재캐싱
약 1.1 MB
제휴처 혜택을 모두 보관해도
메모리 부담이 작아 장기 보관 가능
12페이지: 공통 혜택은 제휴처별로 한 번 조립하고 전송
12 / 17텍스트로 읽기
stores
stores
변경 전
변경 후
매장마다 같은 혜택을 포함
매장과 공통 혜택을 분리
같은 제휴처의 혜택을 매장마다 조립하고,
응답 JSON에도 반복해서 포함했습니다.
공통 혜택은 partners에 한 번만 담고,
각 매장은 partnerId로 해당 혜택을 참조합니다.
03 핵심 API 성능 개선
응답 필드: stores = 매장 목록 · partnerId = 제휴처 식별자 · partners = 제휴처별 공통 혜택
공통 혜택은 제휴처별로 한 번 조립하고 전송
12 / 17
13페이지: 조회 결과 변환에서 Projection 프록시 제거
13 / 17텍스트로 읽기
변경 전
변경 후
03 핵심 API 성능 개선
13 / 17
조회 결과 변환에서 Projection 프록시 제거
프록시 getter를 거쳐 record로 복사
SQL 결과에서 record로 직접 변환
JFR(Java Flight Recorder) 분석으로 매장별Projection getter 호출과 record 변환이 반복되는 문제를 확인했습니다.
SQL 컬럼 값을 record로 직접 매핑하고,
기존 조회 인터페이스를 유지했습니다.
SQL 조회 결과
매장·제휴처의 14개 컬럼
Spring Data 인터페이스 Projection
300행 × getter 14개 = 4,200회 호출
불변 record로 복사
읽기 트랜잭션 안에서 변환을 완료
JPA native query의 Object[]
Projection 프록시를 거치지 않고 컬럼 값 수신
컬럼 값을 직접 매핑
Object[]에서 record로 바로 변환
불변 record 반환
반환 인터페이스와 트랜잭션 경계 유지
JFR · 결과 변환 메서드가 포함된 실행 샘플 비중
4.45%
93 / 2,089개 샘플
0.29%
6 / 2,053개 샘플
14페이지: 매장 목록·구역 집계 조회의 응답시간
14 / 17텍스트로 읽기
100 VUser · 30분 · API별 평균 응답시간
요청 구성
매장 목록(preview) 75%
구역 집계(cluster) 25%
집계 레벨 5·7·10 균등 배분
조회 범위
매장 목록: 약 2×2km
집계: 한 변 약 10/20/100km
매 요청마다 난수 좌표로 조회
03 핵심 API 성능 개선
매장 목록·구역 집계 조회의 응답시간
14 / 17
차트 값 (PDF 그래프에서 추출): preview 11.58 ms · cluster L5 1.17 ms · cluster L7 1.12 ms · cluster L10 1.11 ms
15페이지: 프로젝트 종료 시점과 현재의 지도 조회 성능 비교
15 / 17텍스트로 읽기
15 / 17
03 핵심 API 성능 개선
프로젝트 종료 시점과 현재의 지도 조회 성능 비교
프로젝트 종료 시점
504.0 TPS
MTT 196.75ms
성공 905,482 · 오류 127건
서비스 확장 및 개선 이후
6,299.2 TPS
MTT 8.97ms
성공 11,327,328 · 오류 0건
100 VUser · 30분 · 매 요청마다 랜덤 좌표 조회
nGrinder Agent·API·DB: 동일 로컬 PC
16페이지: 다중 인스턴스에서 OAuth2 인증 요청 조회 실패
16 / 17텍스트로 읽기
로그인 시작
callback
카카오 로그인·동의 화면
302 Redirect
발견한 오류
카카오에서 인가 코드를 받은 뒤,
callback 처리 서버가 기존 인증 요청을 찾지 못함
재현 조건
API 1대: 정상 · ALB 뒤 API 2대 이상: 간헐적 오류
16 / 17
04 분산 인증
다중 인스턴스에서 OAuth2 인증 요청 조회 실패
로그인 실패
17페이지: Redis 공유 세션으로 OAuth2 인증 오류 해결
17 / 17텍스트로 읽기
01 로그인 시작과 callback의 상태 공유
Spring Session의 세션 저장소를 Redis로 통합해,
어느 API에서든 같은 OAuth2 인증 요청을 조회하도록 구성했습니다.
02 로그인 이후 API 인증
API 서버가 Access JWT로 요청을 인증합니다.
재발급 시 Refresh Token을 Redis 저장값과 대조합니다.
다중 인스턴스의 부하 분산 이점을 살리기 위해 Spring Session·Redis로 세션 통합
ALB Stickiness는 같은 사용자의 요청을 특정 인스턴스에 고정해, 다른 인스턴스로 요청을 분산하는 데 제약이 있습니다.
인증 요청을 Redis에서 공유해, 요청이 어느 인스턴스로 가더라도 로그인 흐름을 이어가도록 구성했습니다.
04 분산 인증
Redis 공유 세션으로 OAuth2 인증 오류 해결
17 / 17
API A
API A · 로그인 시작
Redis · OAuth2
인증 요청
Redis
API B
Client
Access JWT
API 서버
JWT 검증
API B · callback
저장
조회
A/B는 동일한 API 서비스의 서로 다른 인스턴스