ํ”„๋กœ์ ํŠธ

[ZI9ZA9] ๊ฒฐ์ œ ์„ฑ๋Šฅ ๊ฐœ์„ ๊ธฐ

sechoi 2024. 3. 17. 03:18

๐Ÿ”— 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๊ฐ€ ๋‚˜์™”๋‹ค.

TPS ๊ทธ๋ž˜ํ”„

์‚ดํŽด๋ณด๋‹ˆ 4์ดˆ๊ฐ€ ๋  ๋•Œ๋ถ€ํ„ฐ TPS๊ฐ€ ๊ธ‰๊ฒฉํžˆ ๋–จ์–ด์ง„๋‹ค. MariaDB ๊ธฐ๋ณธ ๋ฝ ๋Œ€๊ธฐ ์‹œ๊ฐ„(innodb_lock_wait_timeout) ์€ 50์ดˆ์ด๋ฏ€๋กœ ์ปค๋„ฅ์…˜๋“ค์ด ๋ฝ์„ ์–ป๊ธฐ ์œ„ํ•ด ๋๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜์—ˆ์Œ์„ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

ํ‰๊ท  ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„์€ 3s๋‹ค. ์šฐ๋ฆฌ ์‡ผํ•‘๋ชฐ์— 99๋ช…์ด ๋™์‹œ์— ๊ฒฐ์ œ๋ฅผ ์‹œ๋„ํ•˜๋ฉด 3์ดˆ ์ •๋„ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค. ์‹ค์ œ๋กœ๋Š” ๊ฒฐ์ œ ์Šน์ธ ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ์š”์ฒญ์ด ๋™์‹œ์— ์ผ์–ด๋‚  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋” ๊ธฐ๋‹ค๋ ค์•ผ ํ•  ๊ฒƒ์ด๋‹ค. ๋˜ ๋กœ์ปฌ(๋žจ 16๊ธฐ๊ฐ€, ๋งฅ๋ถ ํ”„๋กœ M1)์—์„œ ํ…Œ์ŠคํŠธํ–ˆ์œผ๋ฏ€๋กœ ์‹ค์ œ ์„œ๋ฒ„์—์„  ๋” ๋Š๋ฆด ๊ฒƒ์ด๋‹ค. 

 

์–ด์ฐจํ”ผ ์ด ์‡ผํ•‘๋ชฐ์—๋Š” ์•„๋ฌด๋„ ์ ‘์†ํ•  ์ˆ˜ ์—†๋‹ค. ์ข‹์ง€ ์•Š์€ ์„ฑ๋Šฅ์ด๋ผ ํŒ๋‹จํ•ด ํ•ด๊ฒฐํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

 

 

๐Ÿค” ํ•ด๊ฒฐ: ์žฌ๊ณ  ๊ฐ์†Œ์™€ ์™ธ๋ถ€ API ํ˜ธ์ถœ์„ ๋ถ„๋ฆฌํ•˜์ž

์„ฑ๋Šฅ์„ ์œ„ํ•ด ๋‹จ์ˆœํžˆ ๋‘˜์„ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ์ˆœ๊ฐ„, ์žฌ๊ณ ์— ๋Œ€ํ•œ ์ •ํ•ฉ์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋‹ค๋ฅธ ์ ‘๊ทผ์ด ํ•„์š”ํ•˜๋‹ค. ํ•ด๊ฒฐ์ฑ…์„ ์œ„ํ•ด ์ด๋ฏธ ์šฐ๋ฆฌ๋ณด๋‹ค ๋›ฐ์–ด๋‚œ ๊ฐœ๋ฐœ์ž๋“ค์ด ๋งŒ๋“ค์–ด๋†“์€ ์‹ค์ œ ์„œ๋น„์Šค๋“ค์„ ์ƒ๊ฐํ•ด๋ณด์•˜๋‹ค.

 

๊ทธ ๋•Œ ์˜ํ™” ์˜ˆ๋งค ์„œ๋น„์Šค๊ฐ€ ๋– ์˜ฌ๋ž๋‹ค. ์ขŒ์„(์˜ˆ๋งค)์— ๋Œ€ํ•œ ์—„๊ฒฉํ•œ ์ •ํ•ฉ์„ฑ์ด ์š”๊ตฌ๋œ๋‹ค. 

๊ฒฐ์ œ'๋Œ€๊ธฐ' ์ƒํƒœ์˜ ์œ ์ € A
๊ฐ™์€ ์ขŒ์„์„ ์„ ํƒํ•˜๋ ค๋Š” ์œ ์ € B

์œ„๋Š” CGV ์˜ˆ๋งค ์„œ๋น„์Šค๋‹ค. ์œ ์ € A๊ฐ€ ์ขŒ์„์„ ์„ ํƒํ•œ ์ˆœ๊ฐ„, ๊ฒฐ์ œํ•˜์ง€ ์•Š์•„๋„ ๋‹ค๋ฅธ ์œ ์ €๋“ค์€ ํ•ด๋‹น ์ขŒ์„์„ ์„ ํƒํ•  ์ˆ˜ ์—†๋‹ค. ์‹ฌ์ง€์–ด ์œ ์ € A๊ฐ€ ์ฐฝ์„ ๋‹ซ์•„๋„ ๋ช‡ ๋ถ„ ๋™์•ˆ์€ ์—ฌ์ „ํžˆ ๋‹ค๋ฅธ ์œ ์ €๋“ค์ด ์˜ˆ๋งคํ•  ์ˆ˜ ์—†๋‹ค.

 

์ถ”์ธกํ•  ์ˆ˜ ์žˆ๋Š” ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฐ์ œ ์ฐฝ์œผ๋กœ ์ด๋™ํ•˜๋Š” ์ˆœ๊ฐ„ ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ์ขŒ์„์ด ์˜ˆ๋งค๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.
  2. ๋งŒ์•ฝ ์ผ์ • ์‹œ๊ฐ„ ์ด๋‚ด๋กœ ๊ฒฐ์ œ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋‹ค์‹œ ์˜ˆ๋งค ๊ฐ€๋Šฅ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋„ ์ด ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฒฐ์ œ ํ”Œ๋กœ์šฐ 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;
}

 

์Šค์ผ€์ค„๋Ÿฌ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

  1. ๋งˆ์ง€๋ง‰ ์ทจ์†Œ ์‹œ๊ฐ„(lastScanTime)์œผ๋กœ ์ทจ์†Œํ•  ์‹œ๊ฐ„์˜ ๋ฒ”์œ„๋ฅผ ์ •ํ•œ๋‹ค.
  2. ํ•ด๋‹น ๋ฒ”์œ„์— ํ•ด๋‹นํ•˜๋Š” ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ๋“ค์„ ๋ฒ ํƒ€ ๋ฝ์„ ๊ฑธ์–ด ์กฐํšŒํ•œ๋‹ค.
    - ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋™์•ˆ ์ทจ์†Œํ•  ์ฃผ๋ฌธ์ด ๊ฒฐ์ œ ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด ์น˜๋ช…์ ์ธ ์—…๋ฐ์ดํŠธ ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  3. ๊ฐ ์ฃผ๋ฌธ์„ ์ทจ์†Œํ•œ๋‹ค.
    - PAYING(๊ฒฐ์ œ ์‹œ์ž‘) ์ƒํƒœ๋Š” ์žฌ๊ณ ๊ฐ€ ๊ฐ์†Œ๋˜์—ˆ์œผ๋ฏ€๋กœ ๋ณต๊ตฌํ•˜๊ณ , CREATED(์ฃผ๋ฌธ์„œ ์ƒ์„ฑ) ์ƒํƒœ๋Š” ์ƒํƒœ๋งŒ ๋ณ€๊ฒฝํ•œ๋‹ค.
  4. ๋ชจ๋“  ์ฃผ๋ฌธ ์ทจ์†Œ์— ์„ฑ๊ณตํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ์ทจ์†Œ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
    - ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋Š” ์ฃผ๋ฌธ์ด ์žˆ์œผ๋ฉด ๋‹ค์Œ ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์žฌ๊ณ  ๋ณต๊ตฌ ํŠธ๋žœ์žญ์…˜

์ด ๋•Œ 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 ์ŠคํŽ™ ๋•Œ๋ฌธ์— ํ™•์‹คํ•˜์ง€๋Š” ์•Š์ง€๋งŒ) ๋” ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋˜์—ˆ์„ ๊ฒƒ์ด๋‹ค. 

 

์‹ค์ œ ์šด์˜ํ•  ์„œ๋น„์Šค๊ฐ€ ์•„๋‹ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ๋ชฉํ‘œ๋ฅผ ๋”ฑ ์ •ํ•˜์ง€ ๋ชปํ•œ ์ , ํ† ์ŠคํŽ˜์ด๋จผ์ธ ์™€์˜ ํ†ต์‹ ์ด ํฌํ•จ๋œ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•ด ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ๋ฅผ ์ œ๋Œ€๋กœ ๋ชปํ•œ ์ ๋„ ์•„์‰ฝ๋‹ค. ๊ทธ๋ž˜๋„ ๋‹ค์–‘ํ•œ ๋‚ด์šฉ์„ ๊ณต๋ถ€ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค.