[Spring Data JPA] Transaction ์ ํ๋ก ์ธํ DeadLock ๋ฐ์๊ณผ ํด๊ฒฐ
๐ ๋ชฉ์ฐจ
SpringBoot๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด์ปค๋จธ์ค ์๋น์ค๋ฅผ ๊ตฌํํ๋ฉด์ ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ๋ค.
[Redis] Redisson์ ์ด์ฉํ ๋ถ์ฐ ๋ฝ ๊ตฌํ์ผ๋ก ๋์์ฑ ์ด์ ํด๊ฒฐ
๐SpringBoot๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด์ปค๋จธ์ค ์๋น์ค๋ฅผ ๊ตฌํํ๋ฉด์ ๋์์ฑ ์ด์๊ฐ ๋ฐ์ํ๋ค.Redis์ Redisson๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์.
zzudev.tistory.com
ํ์ง๋ง, ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ๋ ๊ณผ์ ์์ ๊ต์ฐฉ์ํ(DeadLock)๊ฐ ๋ฐ์ํ๋ค.
๋ฐ๋๋ฝ์ด ๋ฐ์ํ ์ด์ ๋ก๋ savaAll์ ์ฌ์ฉํด์ ๋ฒํฌ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ฑฐ๋
ํธ๋์ญ์ ์ ์ ํ๋ก ์ธํ ์ํฉ ๋ ๊ฐ์ง๋ฅผ ์๊ฐํด ๋ณด์๋ค.
๐ DeadLock์ด๋?
๋ฐ๋๋ฝ(DeadLock)์ ๋ ๊ฐ ์ด์์ ํ๋ก์ธ์ค๋ ํธ๋์ญ์ ์ด ์๋ก ์๋๋ฐฉ์ด ์์ ํ๊ณ ์๋ ์์์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๋ฌดํ ๋๊ธฐ ์ํ์ ๋น ์ง๋ ํ์์ ๋งํ๋ค. ์ฆ, ๊ฐ๊ฐ์ ํ๋ก์ธ์ค๋ ํธ๋์ญ์ ์ด ํ์ํ ์์์ ๋ค๋ฅธ ํ๋ก์ธ์ค๋ ํธ๋์ญ์ ์ด ์ ๊ทธ๊ณ ์์ด์, ์๋ฌด๋ ์์ ์ ์๋ฃํ์ง ๋ชปํ๊ณ ๋ฉ์ถฐ๋ฒ๋ฆฌ๋ ์ํฉ์ด๋ค.
๐ DeadLock์ด ๋ฐ์ํ๋ ์กฐ๊ฑด
- ์ํธ ๋ฐฐ์ (Multual Exclusion) : ์์์ ํ ๋ฒ์ ํ๋์ ํ๋ก์ธ์ค๋ง ์ฌ์ฉํ ์ ์๋ค.
- ์ ์ ์ ๋๊ธฐ(Hold and Wait) : ์ต์ํ ํ๋์ ํ๋ก์ธ์ค๊ฐ ํ๋์ ์์์ ์ ์ ํ๊ณ ์์ผ๋ฉฐ, ์ถ๊ฐ์ ์ธ ์์์ ์์ฒญํ๋ฉด์ ๋๊ธฐ ์ค์ด๋ค.
- ๋น์ ์ (No Preemption) : ์์์ด ์ ์ ๋ ์ ์๋ค. ์ฆ, ํ๋ก์ธ์ค๊ฐ ์์์ ์๋ฐ์ ์ผ๋ก ๋์ง ์๋ ํ, ๋ค๋ฅธ ํ๋ก์ธ์ค๊ฐ ๊ทธ ์์์ ๊ฐ์ ๋ก ๊ฐ์ ธ๊ฐ ์ ์๋ค.
- ์ํ ๋๊ธฐ(Circular Wait) : ๋ ๊ฐ ์ด์์ ํ๋ก์ธ์ค๊ฐ ์ํ์ผ๋ก ์์์ ๋๊ธฐํ๊ณ ์๋ค.
์ด ๊ทธ๋ฆผ์ ์ค๋ช ํ์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- ํ๋ก์ธ์ค P0๋ ์์ R0์ ์ ์ ํ๊ณ ์๊ณ , ์์ R1๋ฅผ ์์ฒญํ๊ณ ์๋ค.
- ํ๋ก์ธ์ค P1๋ ์์ R1์ ์ ์ ํ๊ณ ์๊ณ , ์์ R2๋ฅผ ์์ฒญํ๊ณ ์๋ค.
- ํ๋ก์ธ์ค P2๋ ์์ R2์ ์ ์ ํ๊ณ ์๊ณ , ์์ R3๋ฅผ ์์ฒญํ๊ณ ์๋ค.
- ํ๋ก์ธ์ค P3๋ ์์ R3์ ์ ์ ํ๊ณ ์๊ณ , ์์ R0๋ฅผ ์์ฒญํ๊ณ ์๋ค.
๊ฒฐ๊ตญ, ๊ฐ ํ๋ก์ธ์ค๋ ๋ค๋ฅธ ํ๋ก์ธ์ค๊ฐ ์ ์ ํ ์์์ ์์ฒญํ๊ฒ ๋์ด ์ํ์ผ๋ก ๋๊ธฐ ์ํ์ ๋น ์ง๊ฒ ๋๋ค.
์ด๋ก ์ธํด ์๋ฌด๋ ์์์ ํด์ ํ์ง ๋ชปํ๊ณ , ๋ฐ๋๋ฝ ์ํ๊ฐ ๋ฐ์ํ๋ค.
์ด ์ํ์์๋ ๋ชจ๋ ํ๋ก์ธ์ค๊ฐ ์๋ก์ ์์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋๋ฌธ์, ์ด๋ ๋๊ตฌ๋ ์์ผ๋ก ๋์๊ฐ์ง ๋ชปํ๊ณ ๋ฌดํ ๋๊ธฐ์ ๋น ์ง๋ค.
๐ DeadLock ํด๊ฒฐ ๋ฐฉ๋ฒ
DeadLock์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ ์๊ฐํด ๋ณผ ์ ์๋ค.
- ์๋ฐฉ(Prevention) : ๋ฐ๋๋ฝ์ ๋ค ๊ฐ์ง ์กฐ๊ฑด ์ค ํ๋๋ฅผ ์ ๊ฑฐํ๋ ๋ฐฉ์์ด๋ค. ์๋ฅผ ๋ค์ด, ์์ ํ ๋น ์์๋ฅผ ์ ํ๊ฑฐ๋, ํ ๋ฒ์ ๋ชจ๋ ์์์ ์์ฒญํ๋๋ก ํ์ฌ ์ ์ ์ ๋๊ธฐ ์กฐ๊ฑด์ ์ ๊ฑฐํ ์ ์๋ค.
- ํํผ(Avoidance) : ์์คํ ์ด ๋ฐ๋๋ฝ ์ํ์ ๋น ์ง์ง ์๋๋ก ์ฌ์ ์ ์์์ ์ํ๋ฅผ ์ ๊ฒํ์ฌ ์์ ์ํ๋ฅผ ์ ์งํ๋ค.
- ํ์ง ๋ฐ ๋ณต๊ตฌ(Detection and Recovery) : ๋ฐ๋๋ฝ์ด ๋ฐ์ํ์์ ๊ฐ์งํ๊ณ , ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํ๋ก์ธ์ค๋ฅผ ์ข ๋ฃํ๊ฑฐ๋ ์์์ ์ ์ ํ๋ ๋ฐฉ์์ด๋ค.
- ๋ฌด์(Ignore) : ๋ฐ๋๋ฝ์ด ๋๋ฌผ๊ฒ ๋ฐ์ํ๊ณ ๊ทธ ์ํฅ์ด ํฌ์ง ์์ ๋๋ ๋ฐ๋๋ฝ์ ๋ฌด์ํ๋ ๋ฐฉ๋ฒ๋ ์๋ค. ๋๋ถ๋ถ์ ์ด์ ์ฒด์ ๋ ์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
โ๏ธ ์์ ๋ฐฉ๋ฒ๋ค ์ค, ๋๋ '์๋ฐฉ ๋ฐ ํํผ' ๋ฐฉ๋ฒ์ ์ฑํํ์ฌ ํธ๋์ญ์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ด๋ฆฌํด์ ๋ฐ๋๋ฝ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ์ค์ด๊ณ ,
Redisson ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ๋์์ฑ ์ ์ด๋ฅผ ๊ตฌํํ๋ค.
๐ ์ฌ๋ฐ๋ฅธ ํธ๋์ญ์ ์ ์ฉ ์
'for๋ฌธ์ผ๋ก 'save' ๋ฉ์๋๋ฅผ ํ๋ฒ ์ฉ ํธ์ถํ๋ ๊ฒ๋ณด๋ค ๋ฐ์ดํฐ๋ฅผ ๋ชจ์์ 'saveAll' ๋ก ์ ์ฅํ๋ ๊ฒ์ด ์ด๋จ๊น?'๋ผ๋ ์๊ฐ์ด ๋ค์๊ณ ,
ํ ์คํธ๋ฅผ ํด๋ณด๊ธฐ ์ํด 'saveAll' ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค.
@Transactional
public void createHistory(
Long orderId,
List<OrderProduct> orderProducts
){
OrderEntity orderEntity = orderRepository.findById(orderId)
.orElseThrow(() -> new EntityNotFoundException(
ExceptionMessage.ORDER_NOT_FOUND.toString()));
List<HistoryEntity> entities = new ArrayList<>();
for (OrderProduct dto : orderProducts) {
ProductEntity productEntity = productRepository.findById(dto.getProductId())
.orElseThrow(() -> new EntityNotFoundException(
ExceptionMessage.PRODUCT_NOT_FOUND.toString()));
HistoryEntity historyEntity = HistoryEntity.builder()
.order(orderEntity)
.product(productEntity)
.quantity(dto.getQuantity())
.build();
// historyRepository.save(historyEntity);
entities.add(historyEntity);
}
historyRepository.saveAll(entities);
}
OrderService์์ HistoryService์ createHistory ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ ์๊ณ ,
createOrder์ createHistory ๋ฉ์๋์๋ ๊ฐ๊ฐ ํธ๋์ญ์ ์ด ๊ฑธ๋ ค์๋ค.
@Transactional
public OrderInfo createOrder(
Long memberId,
Long receiverId,
Long totalAmount,
PayType type,
List<OrderProduct> orderProducts
) {
...
historyService.createHistory(saved.getId(), orderProducts);
return ...
;
}
์ด ์ํ์์ ๋์์ฑ ํ ์คํธ๋ฅผ ํด๋ณธ ๊ฒฐ๊ณผ 'Deadlock found when trying to get lock; try restarting transaction' ๋ผ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๊ทธ๋ฆฌ๊ณ 'saveAll' ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ฌ๋ฌ ์ํฐํฐ์ ๋ํ ๋ฝ์ ๋์์ ๊ฑธ๊ธฐ ๋๋ฌธ์, ๋จ์ผ ์ํฐํฐ๋ฅผ ์ ์ฅํ๋ 'save' ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ณด๋ค ๋ ๋ง์ ์์์ ์ ๊ธ ์ ์์ด์ ๋ฐ๋๋ฝ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ๋ ๋์์ง ์ ์๋ค.
๐ ์ฌ๋ฐ๋ฅธ ํธ๋์ญ์ ์ ์ฉ ํ
'saveAll' ๋ฉ์๋ ๋์ 'save' ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ , ํ ํธ๋์ญ์ ์์ ์ผ์ด๋์ผ ํ๋ ์์ ์ ๋ฌถ์ด๋ณด์.
createHistory์ createOrder๋ ํ ํธ๋์ญ์ ์์ ์ผ์ด๋์ผ ํ๋ ์์ ์ด๋ค.
createHistory ๋ฉ์๋์ ์๋ @Transactional ์ด๋ ธํ ์ด์ ์ ์ง์์ฃผ๋ฉด ํด๋น ๋ฉ์๋๋ ํธ๋์ญ์ ๋ด์์ ์คํ๋์ง ์๊ณ , ํธ์ถ๋ ๋ฉ์๋์ ํธ๋์ญ์ ์ ์ฐธ์ฌํ๊ฒ ๋๋ค. ๋ฐ๋ผ์, createOrder ๋ฉ์๋๊ฐ ์์ํ ํธ๋์ญ์ ์ createHistory ๋ฉ์๋๊ฐ ์ฐธ์ฌํ๊ฒ ๋๋ค.
// @Transactional
public void createHistory(
Long orderId,
List<OrderProduct> orderProducts
){
OrderEntity orderEntity = orderRepository.findById(orderId)
.orElseThrow(() -> new EntityNotFoundException(
ExceptionMessage.ORDER_NOT_FOUND.toString()));
// List<HistoryEntity> entities = new ArrayList<>();
for (OrderProduct dto : orderProducts) {
ProductEntity productEntity = productRepository.findById(dto.getProductId())
.orElseThrow(() -> new EntityNotFoundException(
ExceptionMessage.PRODUCT_NOT_FOUND.toString()));
HistoryEntity historyEntity = HistoryEntity.builder()
.order(orderEntity)
.product(productEntity)
.quantity(dto.getQuantity())
.build();
historyRepository.save(historyEntity);
// entities.add(historyEntity);
}
// historyRepository.saveAll(entities);
}
@Transactional ์ด๋ ธํ ์ด์ ์ createOrder์๋ง ์ ์ฉํด์ ํธ๋์ญ์ ์ ๊ฒฝ๊ณ๋ฅผ ๋ค์ ์ค์ ํด ์ค๋ค.
@Transactional
public OrderInfo createOrder(
Long memberId,
Long receiverId,
Long totalAmount,
PayType type,
List<OrderProduct> orderProducts
) {
...
historyService.createHistory(saved.getId(), orderProducts);
return ...
;
}
์์ ๊ฐ์ด createHistory ๋ฉ์๋์์ @Transactional ์ด๋ ธํ ์ด์ ์ด ์ ๊ฑฐ๋๋ฉด, ํด๋น ๋ฉ์๋๋ ํธ๋์ญ์ ๊ฒฝ๊ณ ๋ด์์ ์คํ๋์ง ์๋๋ค. ๋ฐ๋ผ์, ํธ์ถ๋ createOrder ๋ฉ์๋์ ํธ๋์ญ์ ๋ด์์ ์คํ๋๋ค. ์ด ๊ฒฝ์ฐ์๋ createOrder์ createHistory๊ฐ ํ๋์ ํธ๋์ญ์ ๋ด์์ ์คํ๋๋ฏ๋ก, createOrder ํธ๋์ญ์ ๋ด์์ ๋กค๋ฐฑ์ด ๋ฐ์ํ๋ฉด createHistory์ ์์ ๋ ๋กค๋ฐฑ๋๋ค.
์ ์ฉ ํ, ๋๊ฐ์ ํ ์คํธ ํ๊ฒฝ์์ ๋ฐ๋๋ฝ์ ๋ฐ์ํ์ง ์๊ณ ์ ์์ ์ผ๋ก ์ฟผ๋ฆฌ๊ฐ ์์ฑ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฌ๊ธฐ๊น์ง ๋ฐ๋๋ฝ์ด ๋ฐ์ํ์ ๋ ํด๊ฒฐ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์๋ค.