๐ github.com/wootecam-gugucon/shopping-mall
์ฐ๋ฆฌ ZI9ZA9(์ดํ ์ง๊ตฌ์ฌ๊ตฌ) ์ผํ๋ชฐ์์๋ ๋น์ฐํ ๊ฒฐ์ ๊ธฐ๋ฅ์ด ์กด์ฌํ๋ค. ์ค์ ์ถ์ํ ์๋น์ค๊ฐ ์๋์ง๋ง ์ต๋ํ ๋น์ทํ ํ๊ฒฝ์ ๊ตฌํํ๊ณ ์ถ์ด ํ ์คํธ ํค๋ก ํ ์คํ์ด๋จผ์ธ ๋ฅผ ์ฌ์ฉํ๋ค. (์์ฌ ํฌ์ธํธ ๊ฒฐ์ ๋ ์ ๊ณตํ์ง๋ง ์ด ํฌ์คํ ์์๋ ์๋ตํ๊ฒ ๋ค.)
๐ธ ๊ฒฐ์ ํ๋ก์ฐ
1. ์ฃผ๋ฌธ์ ์์ฑ
์ํ๋ ์๋ฅ๋ฅผ ์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ณ ์ฃผ๋ฌธํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ์๋ฒ์์ ์ฃผ๋ฌธ ๋ฐ ์ฃผ๋ฌธ ์ํ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋๋ค. ์์ง ์ฌ์ฉ์๊ฐ ์ฃผ๋ฌธ์ ์์ฒญ๋ง ํ์ผ๋ฏ๋ก ์ฃผ๋ฌธ ์ํ๋ CREATED, ๊ฒฐ์ ์ํ๋ NONE ์ด๋ค.
ํ๋ก ํธ์์๋ API ์๋ต์ผ๋ก ๋ฐ์ ์ฃผ๋ฌธ ๋ฐ์ดํฐ id๋ก ์๋ ๊ฒฐ์ ํ๊ธฐ ํ์ด์ง๋ฅผ ๋ถ๋ฌ์จ๋ค.
๊ฒฐ์ ์๋จ ์ค ์ผ๋ฐ ๊ฒฐ์ ๊ฐ ํ ์คํ์ด๋จผ์ธ ๊ฒฐ์ ๋ฅผ ์๋ฏธํ๋ค.
2. ๊ฒฐ์ ์์ฒญ
์ฌ์ฉ์๊ฐ ๊ฒฐ์ ๋ฅผ ์์ฒญํ๋ฉด ํ๋ก ํธ๋ ์ฐ์ ์๋ฒ์ ๊ฒฐ์ ์์ฒญ API๋ฅผ ๋ณด๋ธ๋ค.
์ด ๋ ์ฃผ๋ฌธ ๋ฐ์ดํฐ์ ์ฃผ๋ฌธ ์ํ์ ๊ฒฐ์ ์ํ๊ฐ ์ ๋ฐ์ดํธ ๋๋ค.
๊ฒฐ์ ์ ๊ฒฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ด์ ๋ ์ถํ ์๋ฒ์์ ์งํํ ๊ฒฐ์ ์น์ธ ๋๋ฌธ์ด๋ค. ํด๋น ์ฃผ๋ฌธ์ด ์ ์์ ์ผ๋ก ๊ฒฐ์ ๋ฅผ ์์ฒญํ์๋์ง, ํ ์ค ๊ฒฐ์ ๊ฐ ๋ง๋ ์ง ํ์ธ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
3. ๊ฒฐ์ ์งํ
์๋ฒ์์ ์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด ์ดํ ํ๋ก์ฐ๋ ํ ์คํ์ด๋จผ์ธ ์์ ์ ๊ณตํ๋ ์ ๊ทธ๋ฆผ์ ๋ฐ๋ผ๊ฐ๋ค.
ํ๋ก ํธ์์ ๊ฒฐ์ ์์ ฏ์ ๋ ๋๋งํ๊ณ , ์ฌ์ฉ์๋ ์์ ฏ์ ํตํด ์ค์ ๊ฒฐ์ ๋ฅผ ์งํํ๋ค.
4. ๊ฒฐ์ ์น์ธ
๊ฒฐ์ ๊ฐ ์๋ฃ๋๋ฉด ๋ฏธ๋ฆฌ ์ง์ ํด๋ ๊ฒฝ๋ก(successUrl)๋ก ํ์ด์ง๊ฐ ์ด๋ํ๊ณ , ํ๋ก ํธ๋ ์๋ฒ๋ก ๊ฒฐ์ ์น์ธ API๋ฅผ ์์ฒญํ๋ค. ์ด ๋ ์๋ฒ์์ ํ ์คํ์ด๋จผ์ธ API๋ฅผ ํธ์ถํด ๊ฒฐ์ ๋ฅผ ๊ฒ์ฌํ๊ณ ์๋ฃ๋์์ผ๋ฉด ๊ด๋ จ ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐ ํ ์ฃผ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
๐ข ๋ฌธ์ : ์ฌ๊ณ ๊ฐ์์ ์ธ๋ถ API ํธ์ถ
๊ฒฐ์ ํ๋ก์ฐ๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์ธ๋ค. ์คํํ๋ฉด ์ ๋์ํ๋ค. ๋ค๋ง ๊ฑธ๋ฆฌ๋ ๋ถ๋ถ์ด ์์๋ค. ๋ฐ๋ก ๋ง์ง๋ง ๋จ๊ณ์ธ ๊ฒฐ์ ์น์ธ API๋ค.
// ๊ฒฐ์ ์น์ธ ํธ๋์ญ์
@Transactional
public PayValidationResponse validatePay() {
// ์ฌ๊ณ ๊ฐ์
decreaseStock(order);
// ํ ์คํ์ด๋จผ์ธ API๋ก ๊ฒฐ์ ์น์ธ
payValidator.validatePayment(payValidationRequest);
return PayValidationResponse.from(orderId);
}
private void decreaseStock(final Order order) {
order.getOrderItems().forEach(orderItem -> {
// ์ํ ์กฐํ
final Product product = productRepository.findById();
// ํ์ ์ธ์ง ๊ฒ์ฌ
product.validateStockIsNotLessThan(orderItem.getQuantity());
// ์ฌ๊ณ ์ฐจ๊ฐ
product.decreaseStockBy(orderItem.getQuantity());
});
}
๊ฒฐ์ ๋ฅผ ์น์ธํ ํ ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ๋ค๋ฉด ์ฌ์ฉ์ ์ ์ฅ์์ ๊ฒฐ์ ๋ ์น์ธ๋์๋๋ฐ ์ฌ๊ณ ๊ฐ ๋ถ์กฑํด ์ทจ์๋๋ ๋ถํธํจ์ด ๋ฐ์ํ ์ ์๋ค. ๋ฐ๋ผ์ ๊ฒฐ์ ์น์ธ ์ ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ๋ค.
์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ ๋๋ ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ ํ, ์ฌ๊ณ ๊ฐ ํ์ ์ํ์ธ์ง ๊ฒ์ฌํ๋ค. ํ์ ๋์ง ์์๋ค๋ฉด ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ๋ค. ์ด ๋ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ์คํ๋๋ ๊ณผ์ ์์ '์กฐํ ํ ์ ๋ฐ์ดํธ'๋ผ๋ ์ ํ์ ์ธ ๊ฐฑ์ ์์ค์ด ๋ฐ์ํ๋ค. (๋ง์ฝ ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค์ด Serializable ๋ผ๋ฉด ์ ์ด์ ๋์์ ์คํ๋์ง ์์ผ๋ฏ๋ก ๊ทธ ์ดํ์ ์์ค์์๋ง ๋ฐ์ํ๋ค. ์ฐ๋ฆฌ๋ MySQL ๊ธฐ๋ณธ ์์ค์ธ Repeatable Read ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค.)
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํ ํ ์ด๋ธ์ ์กฐํํ ๋ ๋ฒ ํ ๋ฝ(select... for update)์ ํ๋ํ๋๋ก ํ๋ค. ์ฆ, ํ๋์ ํธ๋์ญ์ ๋ด์์ ๋ฝ ํ๋๊ณผ ์ธ๋ถ API ํธ์ถ์ด ํจ๊ป ์ด๋ฃจ์ด์ง๋ค. ์ฑ๋ฅ ์ ํ๊ฐ ์ฐ๋ ค๋๋ฏ๋ก nGrinder๋ฅผ ์ฌ์ฉํด ๊ฐ๋จํ ๋ก์ปฌ์์ ํ ์คํธ๋ฅผ ํด๋ณด์. ํ ์คํ์ด๋จผ์ธ ์ ๊ฒฐ์ ์น์ธ API๊ฐ ์ฑ๊ณตํ๋ ค๋ฉด ์ฃผ๋ฌธ ID๊ฐ ํ์ํ๋ฐ, ํ ์คํธ๋ผ ๊ฒฐ์ ๋ ํ์ง ์์ผ๋ฏ๋ก ์์์ ๊ฐ์ ๋ณด๋ด ์คํจํ๋๋ก ํ๋ค. ์ด ํ ์คํธ์์ ์ธ๋ถ API ์ฑ๊ณต ์ฌ๋ถ๋ ์ค์ํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ฐ์ ์ ์ 99๋ช ์ด ๋์์ ์ ์ํ ๋ ํ๊ท TPS๊ฐ 31.9๊ฐ ๋์๋ค.
์ดํด๋ณด๋ 4์ด๊ฐ ๋ ๋๋ถํฐ TPS๊ฐ ๊ธ๊ฒฉํ ๋จ์ด์ง๋ค. MariaDB ๊ธฐ๋ณธ ๋ฝ ๋๊ธฐ ์๊ฐ(innodb_lock_wait_timeout) ์ 50์ด์ด๋ฏ๋ก ์ปค๋ฅ์ ๋ค์ด ๋ฝ์ ์ป๊ธฐ ์ํด ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ฉฐ ์ฑ๋ฅ์ด ์ ํ๋์์์ ์ ์ถํ ์ ์๋ค.
ํ๊ท ํ ์คํธ ์๊ฐ์ 3s๋ค. ์ฐ๋ฆฌ ์ผํ๋ชฐ์ 99๋ช ์ด ๋์์ ๊ฒฐ์ ๋ฅผ ์๋ํ๋ฉด 3์ด ์ ๋ ๊ธฐ๋ค๋ ค์ผ ํ๋ค. ์ค์ ๋ก๋ ๊ฒฐ์ ์น์ธ ์ธ์๋ ๋ค์ํ ์์ฒญ์ด ๋์์ ์ผ์ด๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋ ๊ธฐ๋ค๋ ค์ผ ํ ๊ฒ์ด๋ค. ๋ ๋ก์ปฌ(๋จ 16๊ธฐ๊ฐ, ๋งฅ๋ถ ํ๋ก M1)์์ ํ ์คํธํ์ผ๋ฏ๋ก ์ค์ ์๋ฒ์์ ๋ ๋๋ฆด ๊ฒ์ด๋ค.
์ด์ฐจํผ ์ด ์ผํ๋ชฐ์๋ ์๋ฌด๋ ์ ์ํ ์ ์๋ค. ์ข์ง ์์ ์ฑ๋ฅ์ด๋ผ ํ๋จํด ํด๊ฒฐํ๊ธฐ๋ก ํ๋ค.
๐ค ํด๊ฒฐ: ์ฌ๊ณ ๊ฐ์์ ์ธ๋ถ API ํธ์ถ์ ๋ถ๋ฆฌํ์
์ฑ๋ฅ์ ์ํด ๋จ์ํ ๋์ ๋ค๋ฅธ ํธ๋์ญ์ ์ผ๋ก ๋ถ๋ฆฌํ๋ ์๊ฐ, ์ฌ๊ณ ์ ๋ํ ์ ํฉ์ฑ์ด ๋ณด์ฅ๋์ง ์๋๋ค. ๋ค๋ฅธ ์ ๊ทผ์ด ํ์ํ๋ค. ํด๊ฒฐ์ฑ ์ ์ํด ์ด๋ฏธ ์ฐ๋ฆฌ๋ณด๋ค ๋ฐ์ด๋ ๊ฐ๋ฐ์๋ค์ด ๋ง๋ค์ด๋์ ์ค์ ์๋น์ค๋ค์ ์๊ฐํด๋ณด์๋ค.
๊ทธ ๋ ์ํ ์๋งค ์๋น์ค๊ฐ ๋ ์ฌ๋๋ค. ์ข์(์๋งค)์ ๋ํ ์๊ฒฉํ ์ ํฉ์ฑ์ด ์๊ตฌ๋๋ค.
์๋ CGV ์๋งค ์๋น์ค๋ค. ์ ์ A๊ฐ ์ข์์ ์ ํํ ์๊ฐ, ๊ฒฐ์ ํ์ง ์์๋ ๋ค๋ฅธ ์ ์ ๋ค์ ํด๋น ์ข์์ ์ ํํ ์ ์๋ค. ์ฌ์ง์ด ์ ์ A๊ฐ ์ฐฝ์ ๋ซ์๋ ๋ช ๋ถ ๋์์ ์ฌ์ ํ ๋ค๋ฅธ ์ ์ ๋ค์ด ์๋งคํ ์ ์๋ค.
์ถ์ธกํ ์ ์๋ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ฌ์ฉ์๊ฐ ๊ฒฐ์ ์ฐฝ์ผ๋ก ์ด๋ํ๋ ์๊ฐ ์ผ์ ์๊ฐ๋์ ์ข์์ด ์๋งค๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผํ๋ค.
- ๋ง์ฝ ์ผ์ ์๊ฐ ์ด๋ด๋ก ๊ฒฐ์ ๊ฐ ์๋ฃ๋์ง ์์๋ค๋ฉด ๋ค์ ์๋งค ๊ฐ๋ฅ ์ํ๋ก ๋ณ๊ฒฝํ๋ค.
์ฐ๋ฆฌ ํ๋ก์ ํธ๋ ์ด ๋ฐฉ๋ฒ์ ์ ์ฉํ๊ธฐ๋ก ํ๋ค. ๋ฐ๋ผ์ ๊ฒฐ์ ํ๋ก์ฐ 2๋ฒ์ธ ๊ฒฐ์ ์์ฒญ์์ ์ฌ๊ณ ๊ฐ ์ฐจ๊ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ผ์ ์๊ฐ ๋์ ๊ฒฐ์ ๊ฐ ์๋ฃ๋์ง ์์ผ๋ฉด ํด๋น ์ฃผ๋ฌธ์ ์ทจ์ํ๋ค.
// ๊ฒฐ์ ์์ฒญ ํธ๋์ญ์
@Transactional
public OrderPayResponse requestPay() {
final Order order = orderRepository.findByIdAndMemberIdExclusively();
order.validateNotCanceled();
order.startPay(orderPayRequest.getPayType());
// * ์ฌ๊ณ ๊ฐ์ ๋ฉ์๋ ์ถ๊ฐ
decreaseStock(order);
return OrderPayResponse.from(order);
}
// ๊ฒฐ์ ์น์ธ ํธ๋์ญ์
@Transactional
public PayValidationResponse validatePay() {
// * ์ฌ๋ผ์ง ์ฌ๊ณ ๊ฐ์ ๋ฉ์๋
// ํ ์คํ์ด๋จผ์ธ API๋ก ๊ฒฐ์ ์น์ธ
payValidator.validatePayment(payValidationRequest);
return PayValidationResponse.from(orderId);
}
๋ฒ ํ ๋ฝ์ด ์ฌ๋ผ์ง ๊ฒฐ์ ์น์ธ API์ ์ฑ๋ฅ์ ์ผ๋ง๋ ํฅ์๋์์๊น? ๋๊ฐ์ ์กฐ๊ฑด์ผ๋ก ๋ค์ ์ฑ๋ฅ ํ ์คํธ๋ฅผ ์งํํ๋ค.
TPS 98.8, ํ๊ท ์คํ ์๊ฐ 1.7s๋ก 3๋ฐฐ ์ ๋ ์ฑ๋ฅ์ด ํฅ์๋์๋ค.
๋ฌผ๋ก ๋ฒ ํ ๋ฝ ํ๋ ๋ก์ง์ด ๊ฒฐ์ ์์ฒญ API๋ก ์ด๋ํ๊ธฐ ๋๋ฌธ์ ํด๋น ์์ฒญ์ ์ฑ๋ฅ์ ๋๋ ค์ก์ง๋ง, ๊ฒฐ์ ์ค ํน์ ๊ตฌ๊ฐ์์ ๋ณ๋ชฉ์ด ๋ฐ์ํ์ง ์๊ฒ ๋์๋ค.
๐ ํด๊ฒฐ: ์ฃผ๋ฌธ ์ทจ์ ์ค์ผ์ค๋ฌ
์ฌ๊ณ ๋ฅผ ๋ฏธ๋ฆฌ ์ฐจ๊ฐํ๋ค๋ฉด ๊ฒฐ์ ๊ฐ ์คํจํ์ ๋ ๋ณต๊ตฌํด์ผ ํ๋ค. ํ์ง๋ง ์๋ฒ์์ ์ฌ์ฉ์์ ๊ฒฐ์ ์ทจ์ ์ก์ (ex. ์ฐฝ ๋ซ๊ธฐ)์ ๋ชจ๋ ๊ฐ์งํ ์ ์๋ค. ๋ฐ๋ผ์ CGV์ฒ๋ผ ์ผ์ ์๊ฐ๋ง๋ค ๊ฒฐ์ ๋์ง ์์ ์ฃผ๋ฌธ์ ์ทจ์ํ๋ ์ค์ผ์ค๋ฌ๊ฐ ํ์ํ๋ค.
์ฐ๋ฆฌ๋ Spring Boot์ MariaDB๋ฅผ ์ฌ์ฉํ๋ค. ์ฆ Spring Scheduler์ MariaDB Event ๋ชจ๋ ์ฌ์ฉํ ์ ์๋ค. ํ์ง๋ง ์ต๋ํ ๋น์ฆ๋์ค ๋ก์ง์ DB์ ์ฃผ์ง ์๊ณ ์ดํ๋ฆฌ์ผ์ด์ ์์ ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ํด๊ฒฐํ๊ณ ์ถ์ด ์ ์๋ฅผ ์ ํํ๋ค. DB ์๋ฒ๊ฐ ์ถ๊ฐ๋๋ฉด ์ด๋ฒคํธ๋ฅผ ๋ค๋ฃจ๊ธฐ ๊น๋ค๋ก์์ง๋ค๋ ์ ๋ ๊ณ ๋ คํ๋ค.
@Transactional
public void cancelIncompleteOrders() {
final LocalDateTime scanStartTime = lastScanTime == null ? DEFAULT_SCAN_START_TIME : lastScanTime;
final LocalDateTime scanEndTime = LocalDateTime.now().minus(CANCEL_INTERVAL);
// ๋น๊ด์ ๋ฝ์ผ๋ก ์ทจ์ํ ์ฃผ๋ฌธ๋ค ์กฐํ
final List<Order> incompleteOrders = orderRepository.findAllIncomplete();
boolean allSucceeded = true;
for (Order incompleteOrder : incompleteOrders) {
final boolean succeeded = cancelIncompleteOrders(incompleteOrder);
if (!succeeded) {
allSucceeded = false;
}
}
// ๋ชจ๋ ์ทจ์์ ์ฑ๊ณตํ๋ฉด lastScanTime ์
๋ฐ์ดํธ
if (allSucceeded) {
lastScanTime = scanEndTime;
}
}
private boolean cancelIncompleteOrders(final Order incompleteOrder) {
// CREATED ์ฃผ๋ฌธ ์ทจ์
if (incompleteOrder.isCreated()) {
orderService.cancelCreatedOrder(incompleteOrder);
return true;
}
// PAYING ์ฃผ๋ฌธ ์ทจ์
try {
orderService.cancelPayingOrder(incompleteOrder);
} catch (Exception e) {
return false;
}
return true;
}
์ค์ผ์ค๋ฌ ํ๋ก์ฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ๋ง์ง๋ง ์ทจ์ ์๊ฐ(lastScanTime)์ผ๋ก ์ทจ์ํ ์๊ฐ์ ๋ฒ์๋ฅผ ์ ํ๋ค.
- ํด๋น ๋ฒ์์ ํด๋นํ๋ ์ฃผ๋ฌธ ๋ฐ์ดํฐ๋ค์ ๋ฒ ํ ๋ฝ์ ๊ฑธ์ด ์กฐํํ๋ค.
- ์ค์ผ์ค๋ฌ๊ฐ ์๋ํ๋ ๋์ ์ทจ์ํ ์ฃผ๋ฌธ์ด ๊ฒฐ์ ์๋ฃ๊ฐ ๋๋ฉด ์น๋ช ์ ์ธ ์ ๋ฐ์ดํธ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค. - ๊ฐ ์ฃผ๋ฌธ์ ์ทจ์ํ๋ค.
- PAYING(๊ฒฐ์ ์์) ์ํ๋ ์ฌ๊ณ ๊ฐ ๊ฐ์๋์์ผ๋ฏ๋ก ๋ณต๊ตฌํ๊ณ , CREATED(์ฃผ๋ฌธ์ ์์ฑ) ์ํ๋ ์ํ๋ง ๋ณ๊ฒฝํ๋ค. - ๋ชจ๋ ์ฃผ๋ฌธ ์ทจ์์ ์ฑ๊ณตํ๋ฉด ๋ง์ง๋ง ์ทจ์ ์๊ฐ์ ์
๋ฐ์ดํธํ๋ค.
- ํ๋๋ผ๋ ์คํจํ๋ ์ฃผ๋ฌธ์ด ์์ผ๋ฉด ๋ค์ ์ค์ผ์ค๋ฌ๊ฐ ์ฒ๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฌ๊ณ ๋ณต๊ตฌ ํธ๋์ญ์
์ด ๋ 3๋ฒ์ ์ฌ๊ณ ๋ณต๊ตฌ์ ๋ํด ์๊ฐํด๋ณด์. ์ค์ผ์ค๋ฌ๋ ์ผ์ ์๊ฐ(์ฐ๋ฆฌ ์๋น์ค๋ 30๋ถ์ผ๋ก ์ ํ๋ค) ๋ด ๊ฒฐ์ ๊ฐ ์๋ฃ๋์ง ์์ ์ฃผ๋ฌธ๋ค์ ์ทจ์ํ๋ค. ์ฆ ์ํฉ์ ๋ฐ๋ผ ๋งค์ฐ ๋ง์ ์ฃผ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํด์ผ ํ ์๋ ์๋ค. ์ด ๋ ํ๋์ ํธ๋์ญ์ ์์ ๋ชจ๋ ์ํ์ ์ฌ๊ณ ๋ฅผ ๋ณต๊ตฌํ๋ค๋ฉด, ํธ๋์ญ์ ์ด ์ข ๋ฃ๋๊ธฐ ์ ๊น์ง๋ ์ฌ๊ณ ๊ฐ ๋ณต๊ตฌ๋์ง ์์ผ๋ฏ๋ก ๋ฐ์ดํฐ ์์ ๋ฐ๋ผ ์ฃผ๋ฌธ์ด ๋ฐ๋ฆด ์ ์๋ค.
๋ฐ๋ผ์ ์ฌ๊ณ ๋ฅผ ๋ณต๊ตฌํ ๋๋ ์ ๋ฌผ๋ฆฌ์ ํธ๋์ญ์ ์ ์์ฑํด ์ค์ผ์ค๋ฌ์ ์ํฅ์ ๋ฐ์ง ์๋๋ก ํ๋ค.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void cancelPayingOrder(final Order order) {
order.getOrderItems()
.forEach(orderItem -> productRepository.increaseStock();
order.cancel();
}
์ด ๋ฐฉ๋ฒ์ ํธ๋ ์ด๋ ์คํ๊ฐ ์๋ค. ์ค์ผ์ค๋ฌ์ ํธ๋์ญ์ ๊ณผ ๋ณ๊ฐ๋ก ๋์ํด ์ฌ๊ณ ๋ณต๊ตฌ๊ฐ ์ ์ํ ์ด๋ฃจ์ด์ง๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ๋งค๋ฒ ์ ๋ฌผ๋ฆฌ์ ํธ๋์ญ์ ์ ์์ฑํ๋ฉด์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ผ์ ์ค์ผ์ค๋ฌ ๋ด ์ฃผ๋ฌธ ๋ฐ์ดํฐ์ ๋ฒ ํ ๋ฝ ํ๋ ์๊ฐ์ด ๊ธธ์ด์ง๋ค.
์ธ๋ถ ํธ๋์ญ์ ์ ์ฐ๊ฒฐ๋ ๋ฆฌ์์ค๋ ๋ด๋ถ ํธ๋์ญ์ ์ด ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ๊ณผ ๊ฐ์ ์์ฒด ๋ฆฌ์์ค๋ฅผ ํ๋ํ๋ ๋์ ๋ฐ์ธ๋ฉ๋ ์ํ๋ก ์ ์ง๋ฉ๋๋ค. ์ด๋ก ์ธํด Connection Pool์ด ๊ณ ๊ฐ๋ ์ ์์ต๋๋ค. ์ฌ๋ฌ ์ค๋ ๋๊ฐ ํ์ฑ ์ธ๋ถ ํธ๋์ญ์ ์ ๊ฐ์ง๊ณ ์๊ณ ๋ด๋ถ ํธ๋์ญ์ ์ ๋ํ ์ ์ฐ๊ฒฐ์ ํ๋ํ๊ธฐ ์ํด ๊ธฐ๋ค๋ฆฌ๋ ๊ฒฝ์ฐ ํ์ด ๋ ์ด์ ์ด๋ฌํ ๋ด๋ถ ์ฐ๊ฒฐ์ ์ ๊ณตํ ์ ์์ด ๊ต์ฐฉ ์ํ(deadlock)๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. Connection Pool์ ํฌ๊ธฐ๊ฐ ๋์ ์ค๋ ๋ ์๋ฅผ 1 ์ด์ ์ด๊ณผํ๋ ์ ์ ํ ํฌ๊ธฐ๊ฐ ์๋๋ฉด PROPAGATION_REQUIRES_NEW๋ฅผ ์ฌ์ฉํ์ง ๋ง์ธ์.
๋ํ Spring ๊ณต์ ๋ฌธ์์ ๋์จ ๊ฒ์ฒ๋ผ ํ๋์ ์์ฒญ์์ ํ์ํ connection์ ๊ฐ์๊ฐ ์ฆ๊ฐํ๋ค.
๋ค๋ง ์ค์ผ์ค๋ฌ๋ ๋ฌด์กฐ๊ฑด ํ๋์ ์ค๋ ๋์์๋ง ์คํ๋๊ธฐ ๋๋ฌธ์ ์ปค๋ฅ์ ์ด ํ๋๋ง ๋ ํ์ํ๋ค๋ ์ , ์์ฑ๋๊ณ 30๋ถ์ด ์ง๋ ์ฃผ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ ํ ์ผ์ด(๋ฒ ํ ๋ฝ์ ํ๋ํด์ผ ํ ์ผ์ด) ๊ฑฐ์ ์๋ค๋ ์ ์ ๊ณ ๋ คํด ์ด ๋ฐฉ๋ฒ์ ์ ํํ๋ค.
MariaDB(MySQL)์ ๋ฝ
2๋ฒ์ ๋ฒ ํ ๋ฝ์ ๋ํด์๋ ์ดํด๋ณด์.
MySQL InnoDB๋ ๋ ์ฝ๋ ๊ธฐ๋ฐ์ ์ ๊ธ ๋ฐฉ์์ ํ์ฌํ๋ค. ์ด ๋ ๋ ์ฝ๋ ๋ฝ(Record lock)์ ๋ ์ฝ๋ ์์ฒด๊ฐ ์๋ ์ธ๋ฑ์ค์ ๋ ์ฝ๋๋ฅผ ์ ๊ทผ๋ค. ํ ์ด๋ธ์ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ์ง ์์๋ค๋ฉด ํด๋ฌ์คํฐ๋ง ์ธ๋ฑ์ค์ธ PK(Primary Key)๋ฅผ ์ ๊ทผ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ณ๊ฒฝํด์ผ ํ ๋ ์ฝ๋๋ฅผ ์ฐพ๊ธฐ ์ํด ๊ฒ์ํ ์ธ๋ฑ์ค์ ๋ ์ฝ๋๋ฅผ ๋ชจ๋ ๋ฝ์ ๊ฑธ์ด์ผ ํ๋ค.
์ค์ผ์ค๋ฌ๋ ์ทจ์ํ ์ฃผ๋ฌธ์ ์กฐํํ ๋ ๋ค์๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ค.
SELECT * FROM Order o
WHERE o.status IN ('CREATED', 'PAYING')
AND (o.lastModifiedAt BETWEEN '์์ ์๊ฐ' AND '์ข
๋ฃ ์๊ฐ')
FOR UPDATE;
์ํ์ ๋ง์ง๋ง ์์ ์๊ฐ์ ์กฐ๊ฑด์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ฏ๋ก ํด๋น ์ปฌ๋ผ๋ค์ ํ์ํ๋ฉฐ ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํ๋ค. ์ด ๋ ๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๊ฐ ๊ฑธ๋ ค์์ง ์์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ํ ์คํธ ํด๋ณด์.
์ฃผ๋ฌธ ํ ์ด๋ธ์ 4๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์๋ค.
ํธ๋์ญ์ ์ ์์ํ๊ณ ์ ์ฟผ๋ฆฌ์ ๋น์ทํ๊ฒ ์คํํด๋ณด์๋ค.
begin;
select * from orders where status = 'CREATED' and last_modified_at = now() for update;
๊ทธ๋ฆฌ๊ณ ํ์ฌ ๊ฑธ๋ ค์๋ ๋ชจ๋ ๋ฝ์ ์กฐํํ๋ค.
*************************** 1. row ***************************
OBJECT_NAME: orders
INDEX_NAME: NULL
LOCK_TYPE: TABLE
LOCK_MODE: IX
LOCK_STATUS: GRANTED
LOCK_DATA: NULL
*************************** 2. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: supremum pseudo-record
*************************** 3. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 1
*************************** 4. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 2
*************************** 5. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 3
*************************** 6. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 4
ํด๋น ์ฟผ๋ฆฌ๋ก ์กฐํ๋๋ ๋ฐ์ดํฐ๋ ์์์๋ 4๊ฐ์ ๋ชจ๋ ๋ฐ์ดํฐ์ ํ ์ด๋ธ์ ๋ง์ง๋ง(supremum pseudo-record)์ด ์ ๊ธ์ ํ๋ํ๋ค. ์ด๋ ๋ฐ๋ก ์กฐ๊ฑด์ ์ ํฌํจํ๋ ์ธ๋ฑ์ค๊ฐ ์์ด, ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ ์ฐพ๊ธฐ ์ํด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ค์ผ์ค๋ฌ๊ฐ ์คํ๋๋ ๋์ ๋ค๋ฅธ ์ฃผ๋ฌธ์ ๋ํ ์ ๋ฐ์ดํธ์ ์๋ก์ด ์ฃผ๋ฌธ ์ถ๊ฐ๋ ์ ์์ ์ผ๋ก ์ด๋ฃจ์ด์ ธ์ผ ํ๋ค. ๋ฐ๋ผ์ ์นด๋๋๋ฆฌํฐ๊ฐ ํฐ last_modified_at ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ค.
์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๊ณ ๋ค์ ๋ฒ ํ ๋ฝ์ ๊ฑธ์ด ์กฐํํด๋ณด์. ์ด๋ฒ์๋ 2๋ฒ ํ์ด ์กฐํ๋๋๋ก ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ค.
*************************** 1. row ***************************
OBJECT_NAME: orders
INDEX_NAME: NULL
LOCK_TYPE: TABLE
LOCK_MODE: IX
LOCK_STATUS: GRANTED
LOCK_DATA: NULL
*************************** 2. row ***************************
OBJECT_NAME: orders
INDEX_NAME: idx_2
LOCK_TYPE: RECORD
LOCK_MODE: X
LOCK_STATUS: GRANTED
LOCK_DATA: 0x99B2E22A27, 2
*************************** 3. row ***************************
OBJECT_NAME: orders
INDEX_NAME: PRIMARY
LOCK_TYPE: RECORD
LOCK_MODE: X,REC_NOT_GAP
LOCK_STATUS: GRANTED
LOCK_DATA: 2
*************************** 4. row ***************************
OBJECT_NAME: orders
INDEX_NAME: idx_2
LOCK_TYPE: RECORD
LOCK_MODE: X,GAP
LOCK_STATUS: GRANTED
LOCK_DATA: 0x99B2E22A28, 3
ํ์ ๊ณผ์ ์์ ๊ฑฐ์น 2๋ฒ ํ์ idx_2(last_modified_at) ์ธ๋ฑ์ค์ PK ์ธ๋ฑ์ค๋ง ๋ฝ์ด ๊ฑธ๋ ค์๋ค. ์ทจ์ํ ์ฃผ๋ฌธ ๋ฐ์ดํฐ์๋ง ๋ฝ์ด ๊ฑธ๋ฆฌ๋๋ก ํด๊ฒฐํ๋ค.
๐ ์ ๋ฆฌ
๊ฒฐ์ ํ๋ก์ฐ ์ค ํ๋์ ํธ๋์ญ์ ์์ ์ ๊ธ ํ๋๊ณผ ์ธ๋ถ API ํธ์ถ์ด ๊ฒน์น๋ฉฐ ๋ณ๋ชฉ์ด ๋ฐ์ํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ ์์ ์ ๋ค๋ฅธ ํธ๋์ญ์ ์ผ๋ก ๋ถ๋ฆฌํ๊ณ ์ด๋ก ์๊ธด ์ฌ๊ณ ์ ํฉ์ฑ ๋ฌธ์ ๋ Spring Scheduler๋ฅผ ํตํด ํด๊ฒฐํ๋ค.
3์ฃผ ์์ ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ผ ํด์ ์๊ฐ์ด ๋ถ์กฑํ๊ธฐ ๋๋ฌธ์ ๋ ๋ฐ์ ์ํฌ ๋ถ๋ถ์ด ๋ณด์ด๋ ๊ฒ ์์ฝ๋ค. ์ค์ผ์ค๋ฌ๋ฅผ ์ดํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ณ๋์ ์๋ฒ์์ ์คํํ๊ณ , ์ฌ๊ณ ๋ณต๊ตฌ ์ฟผ๋ฆฌ๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋๋๋ก ๋ณ๊ฒฝํ๋ค๋ฉด (DB ์คํ ๋๋ฌธ์ ํ์คํ์ง๋ ์์ง๋ง) ๋ ๋น ๋ฅด๊ฒ ์คํ๋์์ ๊ฒ์ด๋ค.
์ค์ ์ด์ํ ์๋น์ค๊ฐ ์๋์๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ๋ชฉํ๋ฅผ ๋ฑ ์ ํ์ง ๋ชปํ ์ , ํ ์คํ์ด๋จผ์ธ ์์ ํต์ ์ด ํฌํจ๋ ์๋๋ฆฌ์ค์ ๋ํด ์ฑ๋ฅ ํ ์คํธ๋ฅผ ์ ๋๋ก ๋ชปํ ์ ๋ ์์ฝ๋ค. ๊ทธ๋๋ ๋ค์ํ ๋ด์ฉ์ ๊ณต๋ถํ ์ ์์ด์ ์ข์๋ค.
'ํ๋ก์ ํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Floney] ์นดํ ๊ณ ๋ฆฌ ๋ฆฌํฉํ ๋ง ์ผ๋๊ธฐ (2) | 2024.03.11 |
---|---|
[Floney] ์์ฐ ๋ฐ์ดํฐ ๋์์ฑ ์ด์ ํด๊ฒฐ ์ฝ์ง๊ธฐ (7) | 2023.12.14 |