소개
페이 서비스과 유사한 도메인 지식을 쌓기 위해, 계좌 관리 및 결제 트랜잭션을 다루는 작은 금융 서비스를 설계하고 구현하였습니다.
본 프로젝트에서는 다중 계좌 관리 및 외부 결제 연동 시 발생하는 트랜잭션 처리 문제를 해결하는 데 집중했습니다.
사용 기술 스택
Java21, Spring boot, JPA, MySQL, Redis
서버 구상도

구현 사항
1. 계좌 생성 및 충전
기능
- [x] 신규 사용자는 가입을 진행하면서 하나의 메인 계좌를 생성하고, 메인 계좌 충전에 필요한 타행 계좌를 등록한다.
- [x] 사용자는 적금 계좌를 생성할 수 있다.
- [x] 메인 계좌는 이체 시 잔액이 부족하면 가입 시 등록한 타행 계좌에서 돈을 가져온다.
- [x] 사용자는 타행 계좌에서 최대 3,000,000원을 인출할 수 있다.
- [x] 적금 계좌에 돈을 납입할 때는, 메인 계좌에서 인출해 납입한다.
- 이 때도 메인 계좌에 돈이 부족하면 타행 계좌에서 인출한다.
고려한 것과 선택한 이유
-
메인 계좌 → 적금 계좌로 돈이 이동할 때 트랜젝션
- 비관적 락을 사용하여 메인 계좌, 적금 계좌 조회 시 X lock을 건다.
- Pros : DB 단에서 강력한 동시성 제어가 가능하고 트랜젝션과 같이 사용 가능하다
- Cons : 여러 자원에 락이 걸면 트랜잭션 간 락 획득 순서 제어가 어려워 데드락 발생 위험이 높다. 다중 Master DB에서 전체에 X lock이 걸리지 않기 때문에 글로벌락이 필요하다.
- Redis 분산락을 사용하여 메인 계좌, 적금 계좌에 락을 걸고 비지니스 로직을 수행한다.
- Pros : 여러 애플리케이션 서버 및 다중 DB에서도 보장 가능하고 DB 락 보다 빠르게 처리 가능
- Cons : 트랜젝션과 별도로 동작하기 때문에 추가로 설정을 해주어야한다. TTL 설정이 없으면 데드락에 걸릴 수 있다. Redis Failover 대비책 필요
→ 선택한 방법 : Redis 분산락 | Why? : Redis는 메모리 기반 저장으로 높은 처리량을 제공하며, 분산 서비스 환경에서도 대응 가능. 단일 Row가 아닌 비지니스 로직 단위로 락을 설계해 유연하게 사용 가능.
Redis Failover에 대비해 낙관적 락을 추가로 적용할 수 있다.
-
개별 일일 인출 한도 관리
- DB에 인출 내역을 저장하고 계좌 조회 시
- Pros : 결제 내역 조회 시 사용하는 테이블을 활용할 수 있다. DB 격리 수준에 맞게 조회가능하다.
- Cons : Account table 외에도 JOIN 또는 추가 질의를 통해 결제 내역을 가져와야해서 캐싱보다 오래 걸린다.
- 캐시에 { key :
사용자:날짜
, value : 사용자 일일 타행 인출 금액 합
} 을 저장하고 TTL을 1day로 지정한다.
- Pros : 캐시의 빠른 검색 속도. 일일 한도는 하루 동안만 필요한 데이터이기 때문에 TTL 설정으로 메모리 관리
- Cons : 분산 환경에서는 로컬 캐시 대신 글로벌 캐시(ex. Redis) 를 사용해야한다. DB 저장 외에도 추가적인 공간을 사용
→ 선택한 방법 : 캐시 저장 | Why? : 1번에서 분산락으로 메인 계좌 사용에 락이 걸려있어, 별도의 동시성을 고려하지 않아도 괜찮고, 캐시 조회가 빠르며 하루 뒤에 데이터가 사라지기 때문에 데이터가 무한히 늘어나지 않는다.
-
타행 계좌에서 인출 시 에러가 발생했을 때
- 재시도 처리
- Pros : 최종 처리 결과를 사용자에게 바로 전달할 수 있음.
- Cons : 여러 번 재시도하면서 전체 요청 시간 증가로 락을 잡고있는 시간이 오래 걸림
- 이벤트 기반 비동기로 수행
- Pros : 인출에 성공/실패에 대한 결과를 알림을 통해 전달한다면 전체 요청의 블로킹을 줄이고, 메인 계좌 락 최소화
- Cons : 잔액 부족 등의 사유로 인출이 실패할 수도 있기 때문에 인출에 무조건 성공한다는 보장이 없어, 사용자에게 결과를 별도로 알려줄 방법이 필요하다.
→ 선택한 방법 : 재시도 처리 | Why? : 알림 시스템을 구축한다면 비동기 메세징을 활용해 락을 최소화 할 수 있지만, 이번 구현에서는 알림 시스템을 별도로 구현하지 않았기 때문에 동기식으로 재시도만 진행
-
타행 계좌 인출 후, 서버 장애가 발생했을 때 트랜젝션 처리 or 복구
- 타행 계좌 인출 전에 요청을 PENDING으로 기록한 Entity 저장 (기존 트랜젝션과 물리 트랜젝션 분리해 커밋)
- 타행 계좌 인출 요청 후, 입금이 완료되면 COMPLETE 으로 상태 변경
- 장애 발생 후 서버가 복구되면 (a) 상태인 거래를 찾아 복구 작업 수행
동시성 실험
동시성 처리 전
- 처음 잔액이 10000원이고, 1000원씩 입금 7번 수행
- Expect : transaction 은 7개 생성, accout.balance = 17000원