Back-end/TroubleShooting

[Spring Boot] ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

์„œ์ฑ„๋ฆฌ 2024. 6. 15. 04:37

๐ŸŒฑ ๋ฌธ์ œ ์ƒํ™ฉ

  • ํŒ๋งค ์ž…์ฐฐ๊ณผ ๊ตฌ๋งค ์ž…์ฐฐ์ด ์กด์žฌํ•œ๋‹ค.
  • ํŒ๋งค ์ž…์ฐฐ๊ณผ ๊ตฌ๋งค ์ž…์ฐฐ์€ 1:1 ๊ด€๊ณ„์ด๋‹ค.
  • ๊ตฌ๋งค ์ž…์ฐฐ ์š”์ฒญ์ด ๋™์‹œ์— ๋“ค์–ด์™€๋„, 1๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ๋งŒ ์„ฑ๊ณตํ•ด์•ผ ํ•œ๋‹ค.

ํŒ๋งค ์ž…์ฐฐ์— ๊ด€๋ จ๋œ ๊ตฌ๋งค ์ž…์ฐฐ ์ƒ์„ฑ ๋กœ์ง

@RequiredArgsConstructor
@UseCase
public class BidUseCase {

    private final BidRepository bidRepository;
    
    @Transactional
    public void order(User user, OrderRequestDto request) {
        Bid sellBid = bidRepository.findFirstByProductIdAndPriceAndStatusOrderByCreatedAtAsc(
            request.productId(), request.price()).orElseThrow(() -> NOT_FOUND_BID_WITH_CONDITION);

        Bid buyBid = bidRepository.save(Bid.transactSellBidAndCreateBuyBid(user, bid));
    }
}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Bid extends BaseTimeEntity {
    
    public enum BidType {
        SELL, BUY
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Status status;
    
    @Column(updatable = false)
    private LocalDateTime transactionAt;
    
    // ... ์ƒ๋žต
    
    public static Bid transactSellBidAndCreateBuyBid(User user, Bid sellBid) {
        LocalDateTime transactionAt = LocalDateTime.now();
        sellBid.doTransaction(transactionAt);
        return create(user, sellBid, Status.IN_TRANSACTION, BidType.BUY, transactionAt);
    }

    private void doTransaction(LocalDateTime transactionAt) {
        validate(Status.LIVE);
        this.status = Status.IN_TRANSACTION;
        this.transactionAt = transactionAt;
    }
}

์ด๋Š” ์ฃผ์–ด์ง„ ์ƒํ’ˆ๊ณผ ๊ฐ€๊ฒฉ์— ๋งž๋Š” ์ž…์ฐฐ์„ ์กฐํšŒํ•˜๊ณ , ํ•ด๋‹น ์ž…์ฐฐ์„ ๊ฑฐ๋ž˜ ์ค‘์œผ๋กœ ์ƒํƒœ ๋ณ€๊ฒฝํ•œ ํ›„ ์ƒˆ๋กœ์šด ๊ตฌ๋งค ์ž…์ฐฐ์„ ์ƒ์„ฑํ•˜๋Š” ์ฃผ๋ฌธ API ๋กœ์ง์ด๋‹ค.

 

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ƒ ํŒ๋งค ์ž…์ฐฐ ํ•œ ๊ฐœ ๋‹น ํ•œ ๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ์ด ์ƒ์„ฑ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ ํ•ด๋‹น ๋กœ์ง๋Œ€๋กœ๋ผ๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฃผ๋ฌธ API๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค.

 

์‹ค์ œ๋กœ cmd์—์„œ curl & ๋ช…๋ น์„ ํ†ตํ•ด ๋™์‹œ์— 5๊ฐœ์˜ ์ฃผ๋ฌธ API๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ 5๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ์ด ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐœ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ๋ฉ”์„œ๋“œ์— synchronized ํ‚ค์›Œ๋“œ๋ฅผ ์„ ์–ธํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

 

๐ŸŒฑ ํ•ด๊ฒฐ๋ฐฉ์•ˆ

1๏ธโƒฃ synchronized

@RequiredArgsConstructor
@UseCase
public class OrderUseCase {

    private final BidRepository bidRepository;
    
    @Transactional
    public synchronized void order(User user, OrderRequestDto request) {
        Bid sellBid = bidService.readOrderableBid(request.productId(), request.price())
            .orElseThrow(() -> NOT_FOUND_BID_WITH_CONDITION);

        Bid buyBid = bidRepository.save(Bid.transactSellBidAndCreateBuyBid(user, bid));
    }
}

 

์ฃผ๋ฌธ ๋กœ์ง์— synchronized ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ™์—ฌ ๋‹ค์‹œ 5๊ฐœ์˜ ์ฃผ๋ฌธ API๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœํ•ด ๋ณด์•˜๋‹ค.

์•„๊นŒ๋ณด๋‹ค๋Š” ๊ตฌ๋งค ์ž…์ฐฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค๊ธฐ๋Š” ํ–ˆ์ง€๋งŒ ์˜๋„๋Œ€๋กœ 1๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ์ด ์ƒ์„ฑ๋œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. synchronized๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•œ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋งŒ ๊ณต์œ  ์ž์›์„ ์ ์œ ํ•  ํ…๋ฐ ์™œ ์ด๋Ÿฐ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”์„๊นŒ?

 

๐Ÿค” synchronized๊ฐ€ ๋จนํžˆ์ง€ ์•Š์€ ์ด์œ 

๊ทธ ์ด์œ ๋Š” ๋ฐ”๋กœ @Transactional ๋•Œ๋ฌธ์ด๋‹ค. @Transactional ์‚ฌ์šฉ ์‹œ Spring AOP๋ฅผ ํ†ตํ•ด ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์ด ์ ์šฉ๋˜๋Š”๋ฐ ์ด ๊ณผ์ •์—์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ฒŒ ๋œ๋‹ค.

// ํ”„๋ก์‹œ ๊ฐ์ฒด
public class OrderUseCaseProxy extends OrderUseCase {

    private final BidRepository bidRepository;
    
    @Override
    public void order(User user, OrderRequestDto request) {
        // ...
    }
}

// ๊ธฐ์กด ๊ฐ์ฒด
public class OrderUseCase {

    private final BidRepository bidRepository;
    
    public synchronized void order(User user, OrderRequestDto request) {
        // ...
    }
}

ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ๊ธฐ์กด ๊ฐ์ฒด์ธ OrderUseCase๋ฅผ ์ƒ์†ํ•ด์„œ ๋งŒ๋“ค์–ด์ง€๋Š”๋ฐ, synchronized๋Š” ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜(๋ฉ”์„œ๋“œ ์ด๋ฆ„ / ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…๊ณผ ๊ฐœ์ˆ˜)๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์†๋˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ order() ๋ฉ”์„œ๋“œ๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

์ •๋ฆฌํ•ด์„œ ๊ธฐ์กด ๊ฐ์ฒด์˜ order()๋Š” ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ๋ฐ›์ง€๋งŒ, @Transactional ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ธํ•ด ์ƒ์„ฑ๋œ ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ order()๋Š” ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ๋ฐ›์ง€ ์•Š์•„ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ๊ฒƒ์ด๋‹ค.

 

๐Ÿค” ๊ทธ๋Ÿผ @Transactional์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์€ ์–ด๋–จ๊นŒ?

@RequiredArgsConstructor
@UseCase
public class OrderUseCase {

    private final BidRepository bidRepository;
    
    public synchronized void order(User user, OrderRequestDto request) {
        Bid sellBid = bidService.readOrderableBid(request.productId(), request.price())
            .orElseThrow(() -> NOT_FOUND_BID_WITH_CONDITION);

        Bid buyBid = bidRepository.save(Bid.transactSellBidAndCreateBuyBid(user, bid));
        bidRepository.save(sellBid);
    }
}

@Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„ ํŠธ๋žœ์žญ์…˜์ด ์ž๋™์œผ๋กœ ๊ด€๋ฆฌ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— sellBid์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ(LIVE → IN_TRANSACTION, transactionAt ๊ฐฑ์‹ ) ํ•œ ํ›„ save๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ช…์‹œ์ ์œผ๋กœ ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์—ˆ๋‹ค.

 

์ด ๊ฒฝ์šฐ ์˜๋„ํ–ˆ๋˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋Œ€๋กœ ํ•œ ๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ๋งŒ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ synchronized๋Š” ๊ทผ๋ณธ์ ์ธ ํ•ด๊ฒฐ์ฑ…์ด ๋  ์ˆ˜ ์—†๋‹ค. ์šฐ์„  synchronized๋Š” ๋ชจ๋“  ์Šค๋ ˆ๋“œ ๋™์ž‘์— ๋Œ€ํ•ด ๋ฝ์„ ๊ฑธ์–ด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ synchronized๋Š” ํ•œ ํ”„๋กœ์„ธ์Šค ๋‚ด์—์„œ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ๋งŒ ๊ณต์œ  ์ž์›์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๊ฐœ ์ด์ƒ์˜ ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†๋‹ค.

 

2๏ธโƒฃ DB Lock

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฝ์—๋Š” ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

 

๐Ÿ”“ ๋‚™๊ด€์  ๋ฝ(Optimistic Lock)

๋‚™๊ด€์  ๋ฝ์€ ๋™์‹œ ์š”์ฒญ์„ ์ง์ ‘์ ์œผ๋กœ ๋ง‰์ง€ ์•Š๊ณ , ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ปค๋ฐ‹ํ•˜๊ธฐ ์ „ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ๋ณ€๊ฒฝ์„ ํ–ˆ๋Š”์ง€ ํ™•์ธ(version or timestamp)์„ ํ†ตํ•ด ๋™์‹œ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค.

๋งŒ์•ฝ ๋ฐ์ดํ„ฐ์˜ ๋ฒ„์ „์ด๋‚˜ ํƒ€์ž„์Šคํƒฌํ”„๊ฐ€ ๋‹ค๋ฅผ ๊ฒฝ์šฐ ์ถฉ๋Œ์„ ๊ฐ์ง€ํ•ด ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜์„ ๋กค๋ฐฑํ•˜๊ฑฐ๋‚˜ ์žฌ์‹œ๋„ํ•˜์—ฌ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

๐Ÿ˜† ์žฅ์ 

  • ๋™์‹œ ์š”์ฒญ์— ๋Œ€ํ•ด DB ๋ฝ์„ ๊ฑธ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋น„๊ด€์  ๋ฝ๋ณด๋‹ค ์„ฑ๋Šฅ ํ–ฅ์ƒ ์ด์ ์ด ์žˆ๋‹ค.
  • ๋‚™๊ด€์  ๋ฝ์€ ์ถฉ๋Œ์ด ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด ์ฒ˜๋ฆฌ๋Ÿ‰์ด ๋†’๋‹ค.

๐Ÿฅฒ ๋‹จ์ 

  • ๋™์‹œ ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ๋กœ์ง์˜ ๊ตฌํ˜„์ด ํ•„์š”ํ•˜๋‹ค.
  • ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ ๋นˆ๋„๊ฐ€ ๋†’์€ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ถฉ๋Œ์ด ์ž์ฃผ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ์‹œ๊ฐ„์ด ํ•„์š”ํ•˜๋‹ค.

์ฆ‰,  ๋‚™๊ด€์  ๋ฝ์€ ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜์ง€ ์•Š๊ณ  ์„ฑ๋Šฅ์ด ์šฐ์„ ์‹œ๋˜๋Š” ๊ฒฝ์šฐ์™€ ๋™์‹œ ์š”์ฒญ์—์„œ ํ•˜๋‚˜์˜ ์š”์ฒญ๋งŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์ ํ•ฉํ•˜๋‹ค.

 

๐Ÿ”“ ๋น„๊ด€์  ๋ฝ(Pessimistic Lock)

๋น„๊ด€์  ๋ฝ์€ ์–ด๋Š ํŠธ๋žœ์žญ์…˜์—์„œ ํŠน์ • ํ…Œ์ด๋ธ”์— ์ ‘๊ทผ ์‹œ ๋ฝ์„ ๊ฑธ์–ด(DB ์ „์ฒด, ํ…Œ์ด๋ธ”, ์—ด) ํ•ด์ œ๋  ๋•Œ๊นŒ์ง€ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ํ•ด๋‹น ํ…Œ์ด๋ธ”์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ž ๊ทผ๋‹ค.

 

๐Ÿ˜† ์žฅ์ 

  • ๋‚™๊ด€์  ๋ฝ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ํ…Œ์ด๋ธ”์— ๋ฝ์„ ๊ฑธ์–ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ์•„์˜ˆ ์ ‘๊ทผ์„ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋œ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „ ๋ฝ์„ ๊ฑธ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ค‘ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜๊ณผ์˜ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ๋‹ค.

๐Ÿฅฒ ๋‹จ์ 

  • ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜์ง€๋งŒ ๋™์‹œ ์ ‘์†์ž๊ฐ€ ๋งŽ์€ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฝ ๋Œ€๊ธฐ์‹œ๊ฐ„์œผ๋กœ ์ธํ•ด ์„ฑ๋Šฅ์— ์˜ํ–ฅ์ด ์žˆ๋‹ค.
  • ๋‹ค์ˆ˜์˜ ํŠธ๋žœ์žญ์…˜์ด ์„œ๋กœ ๋‹ค๋ฅธ ์ˆœ์„œ๋กœ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ์— ๋ฝ์„ ์š”์ฒญํ•˜๋ฉด ๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, ๋น„๊ด€์  ๋ฝ์€ ๋ฐ์ดํ„ฐ์˜ ์ •ํ•ฉ์„ฑ์ด ์ค‘์š”ํ•˜๊ณ  ํŠธ๋žœ์žญ์…˜ ๊ฐ„ ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์— ์ ํ•ฉํ•˜๋‹ค.

 

๐Ÿซง ๋น„๊ด€์  ๋ฝ ์ ์šฉ

๋”๋ณด๊ธฐ

๋น„๊ด€์  ๋ฝ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฐฐํƒ€๋ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค.

  • ํŠธ๋žœ์žญ์…˜ T1์ด ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ, ๋ฐฐํƒ€๋ฝ์„ ๊ฑด๋‹ค.
    • select for update๋ฅผ ์‚ฌ์šฉํ•œ ๋ฝ
  • ํŠธ๋žœ์žญ์…˜ T2๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜, ๋ฐฐํƒ€๋ฝ์ด ๊ฑธ๋ ค ์žˆ์–ด ์กฐํšŒ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
    • T2๋Š” ๋ฐ์ดํ„ฐ์˜ ๋ฝ์ด ํ•ด์ œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•œ๋‹ค.
  • T1์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ , ์ปค๋ฐ‹ํ•œ๋‹ค.
  • T2๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ, ๋ฐฐํƒ€๋ฝ์„ ๊ฑด๋‹ค.
  • T2๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ , ์ปค๋ฐ‹ํ•œ๋‹ค.

๋น„๊ด€์  ๋ฝ์€ org.springframework.data.jpa.repository.Lock ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋ฝ ์˜ต์…˜์„ PESSIMISTIC_WRITE์œผ๋กœ ์ง€์ •ํ•ด DB์˜ ๋ฐฐํƒ€๋ฝ์„ ๊ฑฐ๋Š” ๊ฒƒ์ด๋‹ค.

public interface BidRepository extends JpaRepository<Bid, Long>, BidCustomRepository {

    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @QueryHints( {@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")})
    Optional<Bid> findFirstByProductIdAndPriceAndStatusOrderByCreatedAtAsc(Long productId,
        Integer price, Status status);
}

 

ํŠธ๋žœ์žญ์…˜์€ Lock์„ ํš๋“ํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ, ์ถ”๊ฐ€์ ์œผ๋กœ Lock Timeout์„ ์„ค์ •ํ•ด ์ฃผ์—ˆ๋‹ค.

 

๋™์‹œ์— 5๊ฐœ์˜ ์ฃผ๋ฌธ API๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ 1๊ฐœ์˜ ๊ตฌ๋งค ์ž…์ฐฐ์ด ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

๋‚™๊ด€์  ๋ฝ vs ๋น„๊ด€์  ๋ฝ

๋‚™๊ด€์  ๋ฝ๊ณผ ๋น„๊ด€์  ๋ฝ ์ค‘ ์–ด๋–ค ๋ฝ์„ ์‚ฌ์šฉํ• ์ง€๋Š” ํŠธ๋žœ์žญ์…˜ ๊ฐ„ ์ถฉ๋Œ ๋นˆ๋„์— ๋”ฐ๋ผ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

  • ์ถฉ๋Œ ๋นˆ๋„ ๋‚ฎ์Œ. ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์ค‘์š” → ๋‚™๊ด€์  ๋ฝ
  • ์ถฉ๋Œ ๋นˆ๋„ ๋นˆ๋ฒˆ. ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ค‘์š” → ๋น„๊ด€์  ๋ฝ

 

๋”ฐ๋ผ์„œ ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ๋™์‹œ์— ์ƒํ’ˆ์„ ์ฃผ๋ฌธํ•˜๋Š” ๊ฒฝ์šฐ ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„๊ด€์  ๋ฝ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด ๋ณด๊ณ , ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ์ฃผ๋ฌธํ•˜๊ธฐ๋Š” ํ•˜๋‚˜ ์ฃผ๋ฌธํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ฐ์ž ๋‹ค๋ฅธ ๊ฒฝ์šฐ ์ถฉ๋Œ์ด ๋น„๊ต์  ์ ๊ฒŒ ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚™๊ด€์  ๋ฝ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ ์ด์œ ๋Š” ์ถฉ๋Œ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ์ผ์–ด๋‚˜๋Š” ์ƒํ™ฉ์—์„œ ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๊ตฌํ˜„๋งˆ๋‹ค ๋‹ค๋ฅด์ง€๋งŒ ๋ชจ๋“  ์š”์ฒญ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์žฌ์‹œ๋„๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ต‰์žฅํžˆ ๋งŽ์€ ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

๋ฐ˜๋ฉด ๋น„๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด Lock์„ ๊ฑธ๊ณ  ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ดํ›„์˜ ์š”์ฒญ๋“ค์€ ์—…๋ฐ์ดํŠธ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ์ˆœ์ฐจ์ ์œผ๋กœ ์š”์ฒญ์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์œผ๋ฉด ๋น„๊ด€์  ๋ฝ์ด ๋” ์ข‹์€ ์„ ํƒ์ด ๋  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 

๋‚™๊ด€ ๋ฝ vs ๋น„๊ด€ ๋ฝ ์‹ค๋ฌด์—์„œ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด์ฃผ์‹ค ์ˆ˜ ์žˆ์„๊นŒ์š”? - ์ธํ”„๋Ÿฐ

๋‚™๊ด€ ๋ฝ๊ณผ ๋น„๊ด€ ๋ฝ์„ ์ •ํ™•ํžˆ ์–ธ์ œ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•œ๋ฐ์š”, ์ถฉ๋Œ์ด ์žฆ์„ ๋•Œ๋Š” ๋น„๊ด€ ๋ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ์ดํ•ดํ•˜๊ธฐ์—” ๋ง‰์—ฐํ•œ ๋Š๋‚Œ์ด ์žˆ์–ด์„œ์š”, ํ˜น์‹œ ์‹ค๋ฌด์—์„œ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด์ฃผ์‹ค ์ˆ˜ ์žˆ์„๊นŒ์š”??

www.inflearn.com

 

ํ•˜์ง€๋งŒ ํ‹ฐ์ผ“ํŒ…์ฒ˜๋Ÿผ ์ •ํ•ด์ง„ ์‹œ๊ฐ„์— ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์ด ๋ชฐ๋ฆฌ๋Š” ์ด๋ฒคํŠธ์ธ ๊ฒฝ์šฐ ์ถฉ๋Œ์ด ์žฆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์œผ๋‚˜, ์ฃผ๋ฌธ ๋กœ์ง์˜ ๊ฒฝ์šฐ ์‹ค์ œ ํŠธ๋ž˜ํ”ฝ์ด ์ƒ๊ธฐ๊ธฐ ์ „๊นŒ์ง€๋Š” ์ถฉ๋Œ ๋นˆ๋„๋ฅผ ์ถ”์ •ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ ‡๊ฒŒ ์ถฉ๋Œ์ด ๋งŽ์ด ์ผ์–ด๋‚ ์ง€ ์•Œ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์šฐ์„  ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉ(๋น„๊ด€์  ๋ฝ์˜ DB ๋ฝ์— ๋น„์šฉ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ)ํ•˜๋‹ค๊ฐ€, ์šด์˜์ƒ ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋น„๊ด€์  ๋ฝ์ด๋‚˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด ๋ณด๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค.

 

์ด์ „์— ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์žˆ์–ด์„œ ๋น„๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์—ˆ์œผ๋‚˜ ๋น„๊ด€์  ๋ฝ ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ (์„ฑ๋Šฅ ์ €ํ•˜, ๋ฐ๋“œ๋ฝ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ, ํ™•์žฅ์„ฑ ์ œํ•œ, ์ž์› ๋‚ญ๋น„ ๋“ฑ)์œผ๋กœ ์šฐ์„ ์€ ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๊ณ , ์‹ค์ œ ํŠธ๋ž˜ํ”ฝ ํŒจํ„ด์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•œ ํ›„ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์„ ๊ฒƒ์ด๋ผ ํŒ๋‹จํ•ด ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ•˜์˜€๋‹ค.


@Version

JPA๋Š” @Version ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ์˜ ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ์†์„ฑ์„ ์ •์˜ํ•  ๋•Œ๋Š” ์•„๋ž˜์˜ ๊ทœ์น™์„ ์ง€์ผœ์•ผ ํ•œ๋‹ค.

  1. ๊ฐ Entity ํด๋ž˜์Šค์—๋Š” @Version ์†์„ฑ์ด ํ•œ ๊ฐœ๋งŒ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  2. ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์— ๋งคํ•‘๋œ Entity์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ํ…Œ์ด๋ธ”์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•œ๋‹ค.
  3. ๋ฒ„์ „ ํƒ€์ž…์€ Integer(int), Long(long), Short(short), java.sql.Timestamp ์ค‘ ํ•˜๋‚˜์—ฌ์•ผ ํ•œ๋‹ค.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Bid extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ..

    @Version
    private Long version;
}

์ด ํ•„๋“œ์˜ ๊ฐ’ ๋˜๋Š” ์‹œ๊ฐ„์œผ๋กœ ์ฒ˜์Œ ์กฐํšŒ๋  ๋•Œ์˜ ๋ฒ„์ „๊ณผ ์ปค๋ฐ‹๋  ๋•Œ์˜ ๋ฒ„์ „์„ ๋น„๊ตํ•ด ๋งŒ์•ฝ ๋‹ค๋ฅผ ๊ฒฝ์šฐ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•ด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

 

[transaction-1]: ํŒ๋งค ์ž…์ฐฐ ์กฐํšŒ / version: 1
[transaction-2]: ํŒ๋งค ์ž…์ฐฐ ์กฐํšŒ / verison: 1

---- ๋‘ ํŠธ๋žœ์žญ์…˜ ์ค‘ transaction-1 ๋จผ์ € ์™„๋ฃŒ๋จ ----

[transaction-1]: ๊ตฌ๋งค ์ž…์ฐฐ ์ƒ์„ฑ / version: 2 ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์ปค๋ฐ‹
[transaction-2]: ๊ตฌ๋งค ์ž…์ฐฐ ์ƒ์„ฑ / verison: 2 ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์ปค๋ฐ‹ํ•˜๋ คํ•  ๋•Œ
transaction-1 ์—์„œ version์ด 2๋กœ ๋ณ€๊ฒฝ๋˜์–ด ์กฐํšŒํ–ˆ๋˜ ๋ฒ„์ „๊ณผ ๋‹ค๋ฅด๋ฏ€๋กœ ์—…๋ฐ์ดํŠธ ์‹คํŒจ
UPDATE bid
SET
    bid_type=?,
    version=2
WHERE id=?
    AND version=1

์œ„์™€ ๊ฐ™์ด ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ version์ด ์ด๋ฏธ [transaction-1]๋กœ  ์ธํ•ด 2๋กœ ์ฆ๊ฐ€๋œ ์ƒํƒœ๋ผ ์—…๋ฐ์ดํŠธ ๋Œ€์ƒ์„ ์ฐพ์ง€ ๋ชปํ•ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

LockModeType

LockModeType๋ฅผ OPTIMISTIC์œผ๋กœ ์„ค์ •ํ•ด ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค.

public interface BidRepository extends JpaRepository<Bid, Long>, BidCustomRepository {

    @Lock(LockModeType.OPTIMISTIC)
    Optional<Bid> findFirstByIdAndBidTypeAndStatusOrderByCreatedAtAsc(Long id, BidType bidType,
        Status status);
}

 

๋‚™๊ด€์  ๋ฝ์˜ ์˜ˆ์™ธ ์ข…๋ฅ˜

  • javax.persistence.OptimisticLockException (JPA)
  • org.hibernate.StaleObjectStateException (Hibernate)
  • org.springframework.orm.ObjectOptimisticLockingFailureException (Spring)

Spring ๊ธฐ๋ฐ˜ JPA์—์„œ ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๋ฉด version ์ถฉ๋Œ ์‹œ Hibernate์—์„œ StaleStateException์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค. ๊ทธ ํ›„ Spring์—์„œ ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ObjectOptimisticLockingFailureException๋กœ ๊ฐ์‹ธ์„œ ์‘๋‹ตํ•œ๋‹ค.

 

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    ...

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(ObjectOptimisticLockingFailureException.class)
    public ErrorResponse handleObjectOptimisticLockingFailureException(ObjectOptimisticLockingFailureException e) {
        log.info("๊ตฌ๋งค ์ž…์ฐฐ ์ƒ์„ฑ ๋™์‹œ์„ฑ ๋ฌธ์ œ ๋ฐœ์ƒ", e);
        return ErrorResponse.of("UNEXPECTED_ERROR", e.getMessage());
    }
}

GlobalExceptionHandler์— ObjectOptimisticLockingFailureException์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์—ˆ๋‹ค.

 

+ ๋‹ค๋ฅธ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

๊ธ€์˜ ์œ—๋ถ€๋ถ„์—์„œ ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๋‹ค ์šด์˜์ƒ ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋น„๊ด€์  ๋ฝ์„ ๊ณ ๋ คํ•ด ๋ณด๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์จ๋†“์•˜์ง€๋งŒ, ๋น„๊ด€์  ๋ฝ์€ Lock์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋„ ๋ชจ๋“  ํŠธ๋žœ์žญ์…˜์— ๋Œ€ํ•ด Lock์„ ์‚ฌ์šฉํ•ด ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ๊ฒฝ์šฐ O(N^2) ์ •๋„ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‘ ๊ฐœ ์ด์ƒ์˜ column ํ˜น์€ ํ…Œ์ด๋ธ”์— Lock์„ ๊ฑธ ๊ฒฝ์šฐ ๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

 

๋”ฐ๋ผ์„œ ์ด๋Ÿฐ ์ด์Šˆ๋“ค์€ ๋น„๊ด€์  ๋ฝ์ด ์•„๋‹Œ ๋‹ค์Œ ๋ฐฉ๋ฒ•์„ ํ‚ค์›Œ๋“œ๋กœ ์ฐธ๊ณ ํ•ด ๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

  1. Redis Sorted Set ํ™œ์šฉ
  2. Redis์˜ Lua Script ํ™œ์šฉ
  3. Kafka์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง• ํ ๋„์ž…
  4. API Gateway์—์„œ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„
  5. ์ฒ˜๋ฆฌ์œจ ์ œํ•œ๊ธฐ ๋ฏธ๋“ค์›จ์–ด ๋„์ž…

 

 

๐Ÿ“š ์ฐธ๊ณ 

Pessimistic Locking in JPA

[JPA] @Transactional ๊ณผ ๋™์‹œ์„ฑ

How to specify @lock timeout in spring data jpa query?

์ƒํ’ˆ ์ฃผ๋ฌธ ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ - DeadLock, ๋‚™๊ด€์  ๋ฝ(Optimistic Lock) & ๋น„๊ด€์  ๋ฝ(Pessimistic Lock)

[E-commerce] ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ (๋น„๊ด€์  ๋ฝ, ๋„ค์ž„๋“œ ๋ฝ, ๋ถ„์‚ฐ ๋ฝ)

๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ V2 - ๋น„๊ด€์  ๋ฝ(Pessimistic Lock)

๋‚™๊ด€์  ๋ฝ๊ณผ ๋น„๊ด€์  ๋ฝ์€ ๊ฐฑ์‹  ์†์‹ค ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š”๊ฐ€