기존 팀 프로젝트 Tesseris ERP의 백엔드 코드 중 회원 자산/거래 도메인을 분석하고 안정화한 저장소입니다.
이 저장소는 로컬 개발 및 포트폴리오 검토용입니다.
운영 credential과 .env는 포함하지 않습니다.
- 회원 자산 지급/회수 정합성 개선
- 지급/회수 멱등성 설계
- 회원 자산 현황 조회 성능 개선
- 대용량 CSV/ZIP 다운로드 개선
성능 수치는 로컬 Docker MySQL 기준 100만 건 더미 데이터로 재현한 before/after 비교입니다.
- Java 17
- Spring Boot 3.4.3
- Spring Security, JWT
- JPA, QueryDSL
- MySQL 8
- Gradle
- JMeter, curl
- Spring Boot Actuator
src/main/java/com/jakdang/labs
├── api/jihun/memberassetdetails # 회원 자산 현황, 지급, 회수
├── api/jihun/memberaccount # 회원 거래 내역 조회
├── api/jihun/common # CSV/ZIP 다운로드
├── api/jihun/charge # 포인트 충전
├── api/auth # 인증/JWT
├── config # Spring 설정
├── entity # JPA 엔티티
└── security # 보안 필터/인증 처리
docs
├── resume-summary.md
├── member-asset-transaction-hardening-plan.md
├── hardening-change-history.md
└── evidence
기존 find -> modify -> save 방식은 동일 회원에게 동시 지급/회수 요청이 들어올 때 일부 변경분이 덮어써질 수 있었습니다.
개선 후 잔액 변경을 DB 단일 UPDATE에서 직접 처리하도록 바꿨습니다.
UPDATE user_cm
SET user_cm_deposit = COALESCE(user_cm_deposit, 0) + :amount
WHERE user_cm_index = :userIndex검증 결과:
- 지급 동시 요청: 성공 응답 수 = 거래 로그 수 = 최종 잔액 반영 수
- 회수 동시 요청: 요청 수만큼 withdrawal 정확 반영
- Lost Update 0건 확인
더블클릭, 네트워크 재시도, 처리 중 재요청으로 동일 지급/회수 요청이 중복 반영될 수 있는 문제를 개선했습니다.
적용 내용:
Idempotency-Key헤더 필수화- 요청 내용 검증값 저장
- 별도 멱등성 테이블로 요청 선점
PROCESSING,SUCCEEDED상태 관리- TTL 24시간 기준 재시도 범위 정리
검증 결과:
- 같은 key + 같은 요청: 1회만 반영
- 같은 key + 다른 요청 내용: 충돌 응답
- key 없는 요청: 잘못된 요청으로 차단
100만 건 기준 검색에서 count/list 쿼리와 상세 조인이 함께 수행되어 응답이 지연되는 문제를 개선했습니다.
적용 내용:
%keyword%부분 검색 정책 유지- 사용자 후보 선조회 후 상세 조인
- QueryDSL 동적 count로 불필요 조인 생략
측정 결과:
| 지표 | Before | After |
|---|---|---|
| avg | 4,887.74ms | 21.56ms |
| p95 | 6,951ms | 29ms |
| error rate | 0% | 0% |
전체 데이터를 한 번에 적재하거나 화면 조회용 무거운 쿼리를 그대로 쓰던 다운로드 흐름을 개선했습니다.
적용 내용:
StreamingResponseBody기반 CSV 스트리밍- export 전용 keyset 조회
- batch size 30,000 기준 처리
- 100만 행 초과 시 ZIP 내부 CSV 분할
- 화면 검색 결과 수와 export 행 수 일치 검증
측정 결과:
| 지표 | Before | After |
|---|---|---|
| 100만 건 CSV 다운로드 | 99.92s | 12.68s |
| first byte | 9.22s | 0.79s |
.env와 운영 credential은 Git에 포함하지 않습니다.application.yml은 환경변수 기반 설정만 사용합니다.- 외부 서비스 키와 로컬 DB 비밀번호 예시는 README에 노출하지 않습니다.
docs/resume-summary.mddocs/member-asset-transaction-hardening-plan.mddocs/hardening-change-history.mddocs/evidence/