Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/zzambas step2 #23

Open
wants to merge 19 commits into
base: base/zzambas
Choose a base branch
from
Open

Feature/zzambas step2 #23

wants to merge 19 commits into from

Conversation

ZZAMBAs
Copy link

@ZZAMBAs ZZAMBAs commented Feb 4, 2025

구현사항

  • 송금
    • 잔액이 부족하다면 10,000원 단위로 충전합니다. 충전 시에는 해당 계좌 사용자 충전 한도를 먼저 비교하고 계좌 충전을 합니다.
    • 인당 한도 초기화는 이제 0시 0분에 벌크 연산 처리합니다. 기존에는 충전 시 한도를 확인 및 지연 초기화했으나 이로 인해 연산마다 한도 업데이트가 일어날 가능성이 있을 것 같아서 한 번에 처리하는 것이 좋다고 판단했습니다.

프로그래밍 요구사항

  • 송금 트랜잭션의 범위

    • 송금 시에는 다음 과정이 일어납니다.

      1. 송금 가능 계좌인지 확인(적금 계좌는 메인 계좌에서만 송금 가능)
      2. 송금 측 부족한 잔액 확인. 부족하면 한도 내 충전
      3. 계좌 번호 내림차 순으로 금액 업데이트 처리

      1-3 까지 과정을 전부 하나의 트랜잭션으로 묶기에는 너무 크다 여겼고, 이에 따라 각 과정을 트랜잭션으로 따로 나누었습니다. 이에 따라 락이 걸리는 시간이 줄어 데드락, 성능 하락 가능성을 줄였습니다.

  • 동시 송금 처리

    • 현재로써는 100명이 1명에게 동시 송금하여도 문제는 없으나, 속도가 많이 나지는 않는 느낌입니다. 테스트 시, 3초 언저리가 걸리는데 PC 문제인지 로직 문제인지 알기 어렵습니다. -> 실제로는 1.5초로 확인되었습니다.
  • 인당 한도 유효기간의 관리

    • 주기를 변경할 수 있게 해야 한다고 이해하고 주기를 따로 application.yml 파일로 관리해 동적 관리가 되도록 했습니다. 만약 인당 한도가 각자 부여되어야 한다면 컬럼으로 따로 추가할 계획입니다.

아직 생각할 점

데드락 해결을 위해 현재 '사용자 내림차 순 확인' - '계좌 번호 내림차 순 확인' 순서로 트랜잭션을 처리하려고 하고 있지만 추후 조건이 더 늘어날 경우에 관리가 어려울 것 같은데 어떻게 해야 좋은 방안일지 고민 중에 있습니다.
테스트 코드는 현재 동시 송금 부분만 작성된 상태입니다.

@ZZAMBAs ZZAMBAs requested review from VSFe, hellozo0 and opixxx February 4, 2025 05:20
@ZZAMBAs ZZAMBAs self-assigned this Feb 4, 2025
Copy link

@opixxx opixxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견 남겼습니다~ 수고하셨습니다!

* 유저 인당 한도 초기화 벌크 연산
*/
@Scheduled(cron = "${user.charge-limit-initialization.execution-time}")
public void initChargeLimit() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Modifying을 사용해서 벌크 연산을 처리할 때 영속성 컨텍스트 1차 캐시를 고려해봐야할 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색해보니 영속성 컨텍스트를 거치지 않고 쿼리를 처리하다보니 현재 영속성 컨텍스트와 값이 일치하지 않는 경우가 생길 수가 있군요.

https://wildeveloperetrain.tistory.com/142

@Modifying 옵션으로 영속성 컨텍스트를 지우거나 flush 할수도 있지만 영속성 컨텍스트 내부 값이 많다면 flush에도 시간이 소요될 것 같고 지우는 것도 다시 find해야 하는 부분으로 성능 손해가 있을 수도 있을 것 같습니다. 혹은 find 직후에 지워진다면 이것도 문제가 될 것 같네요.

일단 현재 저는 대부분의 업데이트 처리를 JPA 더티체킹 없이 직접 DB 쿼리로 하다보니 문제가 일어나지는 않을 것 같은데, 나중을 위해 고민해 봐야 할 것 같습니다. 혹시 픽스님은 이를 어떻게 처리하시나요?

return ResponseEntity.status(HttpStatus.CREATED).body(accountService.create(userId, accountType));
}

@PostMapping("/withdraw")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메인 계좌에서 돈을 출금하는 것으로 이해했는데, 맞나요?
Step1 외부계좌에서 메인 계좌로 충전하는 기능이 안보여서 질문드립니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

충전은 송금 메서드가 그 역할을 해줄 수 있다고 생각했습니다!

* @return
*
* 계좌 생성. accountType은 생성 계좌가 적금 계좌인지 입출금 계좌인지를 판단.
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create에 @transactional을 사용하지 않은 이유가 있나요?
여러 사용자가 동시에 계좌 생성을 요청하면 문제가 안생기나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

읽기에는 따로 트랜잭션을 걸지 않아도 괜찮을 것이라 여겼고 그러면 트랜잭션을 걸어야 할 곳이 accountRepository.save() 한 곳인데 이미 SimpleJpaRepository에서 트랜잭션 처리를 하므로 따로 걸지는 않았습니다.
하지만 추후 테스트 코드로 다시 확인해 보겠습니다. 감사합니다.

private final UserRepository userRepository;

// 단순 출금
public WithdrawResult withdraw(String accountNumber, long money) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 여기서 트랜잭션을 시작안하고 updateBalance부터 트랜잭션을 시작한게 트랜잭션을 분리하기 위한 것인가요??
지금 코드에서는 withdraw에서부터 트랜잭션을 시작하는 것과 차이가 없다고 생각이 듭니다.
의견이 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 여기서는 차이가 거의 없을 것 같아요. 따로 트랜잭션을 위한 클래스로 분리하다보니 AccountService에서는 모든 메서드에 @Transactional을 제거했습니다.

*
* 적금 계좌는 송신 계좌가 메인 계좌여야 하기 때문에 그 조건을 판단하는 메서드.
*/
private void validateSender(String senderAccountNumber, String receiverAccountNumber) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@transactional이 없어서 다른 트랜잭션에서 관련 Account를 변경하면 정합성 문제가 생기지 않을까요? 의견 궁금해요

Copy link
Author

@ZZAMBAs ZZAMBAs Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 만약 관련 계좌를 찾아온 뒤에 DB내 수정이 발생하면 (적금->입출금 계좌로 전환 혹은 그 반대) 로직에 문제가 생길 수 있을 것 같아요. @Transactional 외 해결 방법도 있는지 찾아보겠습니다!

.orElseThrow(() -> new RuntimeException("Account not found."));

if (receiver.getAccountType() != AccountType.INSTALLATION)
return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

받는 계좌가 메인 계좌이면 더 이상 검증할 필요가 없어서 return 하는 것으로 이해했는데,
가독성을 고려하면 receiver.getAccountType() == AccountType.CHECKING이 코드를 더 이해하기 쉬웠던 것 같습니다. 개인적인 의견입니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 가독성 측면에서는 ==이 더 좋을 것 같다 생각합니다. 그런데 저는 만약 다른 계좌 종류가 추가되어도 따로 제약 사항이 없다면 코드 수정 없이도 돌아갈 것이라 생각했었습니다.

import java.util.Random;
import java.util.stream.IntStream;

public class AccountUtils {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AccountUtils도 CommonUtils처럼 인스턴스 생성 못하도록 하면 좋을 것 같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants