이희용 포트폴리오 원본 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 서비스의 서로 다른 인스턴스