PICA

[개요]

  • 삼성청년소프트웨어아카데미 2학기 특화 프로젝트
  • 결제 시 최대 혜택을 받을 수 있는 카드를 자동으로 선택해 주는 서비스
  • 담당
    • 결제 도메인 일부 구현 참여
      • 요청·승인·취소 등
    • 인프라/DevOps 전반 구축 및 운영
      • 배포 자동화, 환경 변수 관리, 운영 안정화
    • 모니터링 및 부하 테스트 전담 및 성능 개선

[사용 기술 스택 및 선정 이유]

  • BE
    • Java 17 / Spring Boot
      • 트랜잭션 경계가 중요한 로직을 안정적으로 관리하기 위해 사용
      • 결제 상태 전이와 예외 처리를 명확한 서비스 계층으로 분리하기 용이
    • Spring Data JPA
      • 도메인 간 관계를 엔티티 중심으로 표현하기 위해 사용
      • 멱등성 보장을 위해 DB 제약 조건과 함께 활용
      • 대량 조회 및 성능 이슈 발생 구간에서는 fetch join, EntityGraph, 쿼리 재작성으로 대응
    • Spring Scheduler
      • 결제 재시도, 취소 처리, 대사 처리를 비동기적으로 처리하기 위해 사용
  • DB / Cache
    • PostgreSQL
      • 데이터를 관계형 모델로 관리하기 위해 사용
      • 트랜잭션 안정성과 정합성이 중요한 결제 도메인에 적합하다고 판단
      • 인덱스 및 제약 조건을 활용해 대량 데이터 환경에서도 무결성 유지
      • MVCC 기반 동시성 제어 방식으로, 읽기 작업이 쓰기 작업에 의해 블로킹되지 않도록 동작
        • 결제 승인/조회가 동시에 발생하는 상황에서도 조회 지연 최소화
      • MySQL 잠금 기반 동시성 제어와 비교했을 때
        • 읽기 시점의 스냅샷을 유지하여 트랜잭션 간 간섭 감소
      • 격리 수준 : READ COMMITTED
        • 결제 도메인의 주요 트랜잭션은 대부분 단건 결제, 취소와 같은 행 단위 처리
        • 동일 트랜잭션 내에서 동일 데이터를 반복 조회해야 하는 시나리오가 적어 REPEATABLE READ가 불필요하다고 판단
        • 결제 승인, 취소와 같이 정합성이 중요한 구간은 SELECT FOR UPDATE를 통해 충돌 방지
          • 중복 결제 방지는 멱등성 키 및 DB 제약 조건으로 보완
        • 불필요한 락 경합과 트랜잭션 대기를 줄여 속도에서 유리한 READ COMMITTED 선택
    • Redis
      • JWT 인증에서 로그아웃 및 토큰 강제 만료 처리를 위해 블랙리스트 관리
      • 결제 요청 시 멱등 처리 과정에서 보조 캐시로 활용해 중복 요청에 대한 DB 접근을 최소화
      • 카드 상품 조회 등 반복 조회로 인한 DB 접근 최소화
  • Infra / DevOps
    • Docker Compose
      • 개발, 테스트, 운영 환경 간 차이를 최소화하기 위해 컨테이너 기반 환경 구성
      • Spring Boot, PostgreSQL, Redis를 하나의 Compose 스택으로 관리
      • 서비스 간 네트워크를 Compose 서비스명 기반으로 통일하여 설정 단순화
    • GitLab CI/CD
      • 빌드 및 배포 과정을 자동화하여 수동 배포로 인한 오류를 줄이기 위해 사용
      • Docker Executor 기반 Runner를 사용해 실행 환경을 컨테이너로 통일
      • .env 파일은 GitLab Variables(File 타입)로 관리하여 보안과 편의성 확보
    • AWS EC2
      • 서비스 배포 및 운영 환경으로 사용
      • Docker 기반 배포로 서버 환경 의존성을 최소화
  • Monitoring / Testing
    • Prometheus
      • API 응답 시간, 처리량 등 서버 지표 수집을 위해 사용
      • 성능 병목 구간을 수치 기반으로 파악
    • Grafana
      • Prometheus에서 수집한 메트릭을 시각화
      • 부하 테스트 전후 성능 변화를 비교 분석
    • k6
      • 조회 API에 대한 부하 테스트
      • 대량 사용자(VU) 환경에서의 응답 시간, 실패율, 처리량을 측정하여 성능 개선 근거 확보
  • External API
    • SSAFY 금융망 API
      • 카드 인증, 승인, 취소 등 결제 플로우를 실제 금융 시스템과 유사하게 구현하기 위해 사용
      • 멱등성 보장, 에러 코드 기반 처리 등 결제 시스템 설계 관점의 경험을 쌓는 데 목적

[주요 담당 기능]

  1. 결제 도메인 일부 구현
    • SSAFY 금융망 API를 사용한 결제 플로우(요청·승인·취소) 구현
    • 트랜잭션/예외 처리 등 안정성 확보를 위한 개선
  2. 부하 테스트 및 성능 개선
    • Prometheus와 Grafana를 활용해 결제 및 조회 API 모니터링 환경 구축
    • k6를 사용한 부하 테스트 시나리오 설계 및 실행
    • 1000 VU 환경에서 10만 건 전수 조회 성능 측정
    • 쿼리 최적화 및 캐싱 적용으로 응답 시간 44초 → 13초로 개선
  3. GitLab CI/CD 파이프라인 구축
    • GitLab Runner 기반 CI/CD 파이프라인을 구축하여 빌드, 배포 자동화 환경 구성
  4. 배포 환경 Git 충돌 및 파일 관리 정책 정립
    • 모든 설정 파일을 Git 저장소에서 관리하도록 구조 정리
    • 배포는 GitLab CI/CD 파이프라인을 통해서만 수행하도록 정책 정립
  5. 환경 변수 관리 및 보안
    • GitLab CI Variables의 File 타입을 활용한 환경 변수 관리
    • 보안과 배포 안정성을 동시에 만족하는 환경 변수 관리 방식 확립
  6. Docker Compose 기반 컨테이너 기반 배포
    • 동일 네트워크 구조를 통해 컨테이너 간 통신 가능하도록 설정
    • 외부 접근 차단으로 보안 강화화
  7. 모니터링 서버 구축 및 운영
    • 별도 EC2 인스턴스에 Prometheus와 Grafana를 활용한 모니터링 서버 구축
    • API별 응답 시간, 처리량 등 서버 지표 수집 및 시각화
    • 로그 수집을 통한 시스템 상태 실시간 모니터링
  8. Nginx를 활용한 HTTPS 적용
    • Nginx 리버스 프록시 설정으로 정적 파일 서빙 및 API 프록시 구성
    • Certbot을 활용한 SSL/TLS 인증서 자동 발급 및 갱신
    • DNS 설정을 통한 도메인 연결 및 HTTPS 보안 통신 적용
  9. AWS S3를 활용한 이미지 저장소 구축
    • 카드 이미지 등 미디어 파일 저장을 위한 S3 버킷 구성
    • S3 접근 권한 및 정책 설정으로 보안 강화
    • 이미지 업로드 및 조회 API 연동
  10. 검색 API 성능 개선 및 쿼리 구조 개선
    • EntityGraph를 활용한 연관 엔티티 즉시 로딩
    • NOT EXISTS 기반 쿼리로 재작성 및 복합 인덱스 활용
    • Pagination 적용 및 캐싱을 병행하여 검색 API 응답 안정성 확보

[트러블슈팅 & 문제 해결]

  • BE
    • 카드 상품 전수 조회 성능 문제
      • /cards/all API에서 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/search API에서 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로 전환하여 실행 환경을 컨테이너 기반으로 통일
    • Docker Executor에서 Docker CLI 미설치 문제
      • 기본 job 컨테이너에 docker CLI가 없고 privileged 모드 미설정으로 host Docker 접근 불가
      • .gitlab-ci.yml에서 image: docker:20.10 지정 및 GitLab Runner 설정에 privileged = true 추가
    • Git Pull 충돌 문제
      • EC2 서버에 수동으로 생성한 docker-compose.yaml, Dockerfile이 원격 저장소와 충돌
      • 서버의 수동 파일 제거 후 모든 설정 파일을 Git 저장소에서 관리하도록 정리
      • 배포는 GitLab CI/CD 파이프라인을 통해서만 수행하도록 정책 정립
    • 환경 변수 파일 관리 문제
      • .env 파일을 Git에 포함하지 않아 배포 시 파일 누락 및 파싱 오류 발생
      • GitLab CI Variables의 File 타입을 활용해 .env 파일 내용 저장
      • CI 단계에서 scp를 통해 서버로 .env 파일 주입
    • Docker Compose 컨테이너 이름 충돌 문제
      • 이전 컨테이너가 남아있는 상태에서 동일 이름으로 새 컨테이너 생성 시도 시 충돌
      • .gitlab-ci.ymldocker compose down 단계 추가 후 buildup -d 수행
    • Java 버전 불일치 문제
      • 애플리케이션이 JDK 21로 컴파일되었으나 Dockerfile에서 JDK 17 이미지 사용으로 런타임 오류 발생
      • Dockerfile의 base image를 eclipse-temurin:21-jdk로 변경하여 빌드/실행 환경 통일
    • Docker Compose 네트워크 기반 DB 및 Redis 연결 문제
      • .envDB_HOST, REDIS_HOST 값과 Docker Compose 서비스 이름 불일치로 연결 실패
      • Compose 서비스 이름을 기준으로 DB 및 Redis에 접근하도록 환경 변수 수정

[성과]

  • 부하 테스트를 통한 성능 개선으로 API 응답 시간을 44초에서 13초로 약 70% 단축, 실패율 83.9%에서 3.26%로 대폭 개선
  • Prometheus와 Grafana를 활용한 모니터링 환경 구축으로 시스템 상태 실시간 파악 가능, 병목 지점 식별 및 최적화 근거 확보
  • 다양한 인프라 이슈 해결 경험을 통해 운영 안정화 및 문제 해결 역량 향상

[주요 화면]

주요화면1
주요화면2

[아키텍처 구조]

아키텍처 구조

[ERD]

ERD

[회고]

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