PICA
[개요]
- 삼성청년소프트웨어아카데미 2학기 특화 프로젝트
- 결제 시 최대 혜택을 받을 수 있는 카드를 자동으로 선택해 주는 서비스
- 담당
- 결제 도메인 일부 구현 참여
- 요청·승인·취소 등
- 인프라/DevOps 전반 구축 및 운영
- 배포 자동화, 환경 변수 관리, 운영 안정화
- 모니터링 및 부하 테스트 전담 및 성능 개선
- 결제 도메인 일부 구현 참여
[사용 기술 스택 및 선정 이유]
- BE
- Java 17 / Spring Boot
- 트랜잭션 경계가 중요한 로직을 안정적으로 관리하기 위해 사용
- 결제 상태 전이와 예외 처리를 명확한 서비스 계층으로 분리하기 용이
- Spring Data JPA
- 도메인 간 관계를 엔티티 중심으로 표현하기 위해 사용
- 멱등성 보장을 위해 DB 제약 조건과 함께 활용
- 대량 조회 및 성능 이슈 발생 구간에서는 fetch join, EntityGraph, 쿼리 재작성으로 대응
- Spring Scheduler
- 결제 재시도, 취소 처리, 대사 처리를 비동기적으로 처리하기 위해 사용
- Java 17 / Spring Boot
- DB / Cache
- PostgreSQL
- 데이터를 관계형 모델로 관리하기 위해 사용
- 트랜잭션 안정성과 정합성이 중요한 결제 도메인에 적합하다고 판단
- 인덱스 및 제약 조건을 활용해 대량 데이터 환경에서도 무결성 유지
- MVCC 기반 동시성 제어 방식으로, 읽기 작업이 쓰기 작업에 의해 블로킹되지 않도록 동작
- 결제 승인/조회가 동시에 발생하는 상황에서도 조회 지연 최소화
- MySQL 잠금 기반 동시성 제어와 비교했을 때
- 읽기 시점의 스냅샷을 유지하여 트랜잭션 간 간섭 감소
- 격리 수준 : READ COMMITTED
- 결제 도메인의 주요 트랜잭션은 대부분 단건 결제, 취소와 같은 행 단위 처리
- 동일 트랜잭션 내에서 동일 데이터를 반복 조회해야 하는 시나리오가 적어 REPEATABLE READ가 불필요하다고 판단
- 결제 승인, 취소와 같이 정합성이 중요한 구간은 SELECT FOR UPDATE를 통해 충돌 방지
- 중복 결제 방지는 멱등성 키 및 DB 제약 조건으로 보완
- 불필요한 락 경합과 트랜잭션 대기를 줄여 속도에서 유리한 READ COMMITTED 선택
- Redis
- JWT 인증에서 로그아웃 및 토큰 강제 만료 처리를 위해 블랙리스트 관리
- 결제 요청 시 멱등 처리 과정에서 보조 캐시로 활용해 중복 요청에 대한 DB 접근을 최소화
- 카드 상품 조회 등 반복 조회로 인한 DB 접근 최소화
- PostgreSQL
- Infra / DevOps
- Docker Compose
- 개발, 테스트, 운영 환경 간 차이를 최소화하기 위해 컨테이너 기반 환경 구성
- Spring Boot, PostgreSQL, Redis를 하나의 Compose 스택으로 관리
- 서비스 간 네트워크를 Compose 서비스명 기반으로 통일하여 설정 단순화
- GitLab CI/CD
- 빌드 및 배포 과정을 자동화하여 수동 배포로 인한 오류를 줄이기 위해 사용
- Docker Executor 기반 Runner를 사용해 실행 환경을 컨테이너로 통일
- .env 파일은 GitLab Variables(File 타입)로 관리하여 보안과 편의성 확보
- AWS EC2
- 서비스 배포 및 운영 환경으로 사용
- Docker 기반 배포로 서버 환경 의존성을 최소화
- Docker Compose
- Monitoring / Testing
- Prometheus
- API 응답 시간, 처리량 등 서버 지표 수집을 위해 사용
- 성능 병목 구간을 수치 기반으로 파악
- Grafana
- Prometheus에서 수집한 메트릭을 시각화
- 부하 테스트 전후 성능 변화를 비교 분석
- k6
- 조회 API에 대한 부하 테스트
- 대량 사용자(VU) 환경에서의 응답 시간, 실패율, 처리량을 측정하여 성능 개선 근거 확보
- Prometheus
- External API
- SSAFY 금융망 API
- 카드 인증, 승인, 취소 등 결제 플로우를 실제 금융 시스템과 유사하게 구현하기 위해 사용
- 멱등성 보장, 에러 코드 기반 처리 등 결제 시스템 설계 관점의 경험을 쌓는 데 목적
- SSAFY 금융망 API
[주요 담당 기능]
- 결제 도메인 일부 구현
- SSAFY 금융망 API를 사용한 결제 플로우(요청·승인·취소) 구현
- 트랜잭션/예외 처리 등 안정성 확보를 위한 개선
- 부하 테스트 및 성능 개선
- Prometheus와 Grafana를 활용해 결제 및 조회 API 모니터링 환경 구축
- k6를 사용한 부하 테스트 시나리오 설계 및 실행
- 1000 VU 환경에서 10만 건 전수 조회 성능 측정
- 쿼리 최적화 및 캐싱 적용으로 응답 시간 44초 → 13초로 개선
- GitLab CI/CD 파이프라인 구축
- GitLab Runner 기반 CI/CD 파이프라인을 구축하여 빌드, 배포 자동화 환경 구성
- 배포 환경 Git 충돌 및 파일 관리 정책 정립
- 모든 설정 파일을 Git 저장소에서 관리하도록 구조 정리
- 배포는 GitLab CI/CD 파이프라인을 통해서만 수행하도록 정책 정립
- 환경 변수 관리 및 보안
- GitLab CI Variables의 File 타입을 활용한 환경 변수 관리
- 보안과 배포 안정성을 동시에 만족하는 환경 변수 관리 방식 확립
- Docker Compose 기반 컨테이너 기반 배포
- 동일 네트워크 구조를 통해 컨테이너 간 통신 가능하도록 설정
- 외부 접근 차단으로 보안 강화화
- 모니터링 서버 구축 및 운영
- 별도 EC2 인스턴스에 Prometheus와 Grafana를 활용한 모니터링 서버 구축
- API별 응답 시간, 처리량 등 서버 지표 수집 및 시각화
- 로그 수집을 통한 시스템 상태 실시간 모니터링
- Nginx를 활용한 HTTPS 적용
- Nginx 리버스 프록시 설정으로 정적 파일 서빙 및 API 프록시 구성
- Certbot을 활용한 SSL/TLS 인증서 자동 발급 및 갱신
- DNS 설정을 통한 도메인 연결 및 HTTPS 보안 통신 적용
- AWS S3를 활용한 이미지 저장소 구축
- 카드 이미지 등 미디어 파일 저장을 위한 S3 버킷 구성
- S3 접근 권한 및 정책 설정으로 보안 강화
- 이미지 업로드 및 조회 API 연동
- 검색 API 성능 개선 및 쿼리 구조 개선
- EntityGraph를 활용한 연관 엔티티 즉시 로딩
- NOT EXISTS 기반 쿼리로 재작성 및 복합 인덱스 활용
- Pagination 적용 및 캐싱을 병행하여 검색 API 응답 안정성 확보
[트러블슈팅 & 문제 해결]
- BE
- 카드 상품 전수 조회 성능 문제
/cards/allAPI에서 10만 건 조회 시 평균 응답 시간 44.3초, 실패율 83.9% 발생- JPA N+1 문제로 CardProduct, Issuer, Benefit 간 연관 관계를 개별 쿼리로 조회
- fetch join 기반 쿼리 최적화 적용: 평균 응답 시간 44.3초 → 28.8초 (35% 개선)
- 캐시 적용 : 평균 응답 시간 28.8초 → 13.45초, 실패율 83.9% → 3.26%
- 카드 상품 검색 API N+1 문제
/cards/searchAPI에서 CardProduct ↔ CardProductBenefit ↔ Category 간 연관 관계로 다수 쿼리 발생@EntityGraph(attributePaths = {"cardIssuer", "benefits", "benefits.category"})적용하여 즉시 로딩- NOT EXISTS 기반 쿼리로 재작성하여 집계(GROUP BY) 제거 및 인덱스 활용
- CardProductBenefit에 (category_id, card_unique_no) 복합 인덱스 생성
- 카드 상품 전수 조회 성능 문제
- Infra
- GitLab Runner Shell Executor 권한 문제
- GitLab CI job 실행 시
gitlab-runner유저 권한 부족 및 쉘 초기화 파일 충돌로 접근 실패 - Shell executor 대신 Docker executor로 전환하여 실행 환경을 컨테이너 기반으로 통일
- GitLab CI job 실행 시
- Docker Executor에서 Docker CLI 미설치 문제
- 기본 job 컨테이너에
dockerCLI가 없고 privileged 모드 미설정으로 host Docker 접근 불가 .gitlab-ci.yml에서image: docker:20.10지정 및 GitLab Runner 설정에privileged = true추가
- 기본 job 컨테이너에
- Git Pull 충돌 문제
- EC2 서버에 수동으로 생성한
docker-compose.yaml,Dockerfile이 원격 저장소와 충돌 - 서버의 수동 파일 제거 후 모든 설정 파일을 Git 저장소에서 관리하도록 정리
- 배포는 GitLab CI/CD 파이프라인을 통해서만 수행하도록 정책 정립
- EC2 서버에 수동으로 생성한
- 환경 변수 파일 관리 문제
.env파일을 Git에 포함하지 않아 배포 시 파일 누락 및 파싱 오류 발생- GitLab CI Variables의 File 타입을 활용해
.env파일 내용 저장 - CI 단계에서
scp를 통해 서버로.env파일 주입
- Docker Compose 컨테이너 이름 충돌 문제
- 이전 컨테이너가 남아있는 상태에서 동일 이름으로 새 컨테이너 생성 시도 시 충돌
.gitlab-ci.yml에docker compose down단계 추가 후build및up -d수행
- Java 버전 불일치 문제
- 애플리케이션이 JDK 21로 컴파일되었으나 Dockerfile에서 JDK 17 이미지 사용으로 런타임 오류 발생
- Dockerfile의 base image를
eclipse-temurin:21-jdk로 변경하여 빌드/실행 환경 통일
- Docker Compose 네트워크 기반 DB 및 Redis 연결 문제
.env의DB_HOST,REDIS_HOST값과 Docker Compose 서비스 이름 불일치로 연결 실패- Compose 서비스 이름을 기준으로 DB 및 Redis에 접근하도록 환경 변수 수정
- GitLab Runner Shell Executor 권한 문제
[성과]
- 부하 테스트를 통한 성능 개선으로 API 응답 시간을 44초에서 13초로 약 70% 단축, 실패율 83.9%에서 3.26%로 대폭 개선
- Prometheus와 Grafana를 활용한 모니터링 환경 구축으로 시스템 상태 실시간 파악 가능, 병목 지점 식별 및 최적화 근거 확보
- 다양한 인프라 이슈 해결 경험을 통해 운영 안정화 및 문제 해결 역량 향상
[주요 화면]


[아키텍처 구조]
[ERD]
[회고]
- Best Practice
- 팀원들과의 협업이 원활해서 프로젝트가 잘 진행됐음. 각자 담당 분야를 명확히 분리하여 진행하면서도, 인프라 이슈나 결제 관련 문제가 발생했을 때 빠르게 소통하고 협력하여 해결할 수 있었음
- 소통이 잘되어 얼마나 진행이 되었는지 등 파악이 빠르게 됨
- 모니터링 환경을 사전에 구축한 것이 큰 도움이 되었음. Prometheus와 Grafana를 통해 API 성능 지표를 실시간으로 확인할 수 있어, 부하 테스트 결과를 정확히 분석하고 개선 근거를 확보할 수 있었음
- 코드 리뷰와 기술 공유를 통해 팀 전반의 코드 품질과 기술 역량을 향상시킬 수 있었음
- Lesson Learned
- 결제 도메인 구현에 참여하면서 금융 시스템의 복잡성과 안정성의 중요성을 깊이 이해하게 됨. 트랜잭션 경계, 예외 처리, 멱등성 보장 등 결제 시스템에서 고려해야 할 사항들이 많다는 것을 배움
- 초기에 인프라 구축 계획을 더 체계적으로 세웠다면, 후반부 문제 해결에 소요된 시간을 절약할 수 있었을 것 같음. Shell Executor 문제나 Java 버전 불일치 같은 이슈는 사전에 예방 가능했을 것으로 생각됨
- JPA의 N+1 문제를 처음으로 마주했는데, fetch join과 EntityGraph 같은 최적화 기법을 통해 해결하면서 성능 개선의 중요성을 깊이 이해하게 됨
- 부하 테스트를 통해 실제 성능 문제를 확인하고, 구체적인 수치를 바탕으로 개선 작업을 진행한 것이 효과적이었음. 주관적 판단이 아닌 데이터 기반 의사결정의 중요성을 배움
- 인프라 운영은 한 번 구축하고 끝나는 것이 아니라, 지속적인 모니터링과 개선이 필요하다는 것을 느꼈음. 문제가 발생하기 전에 미리 파악하고 대응하는 것이 중요함