Study Repository

[DB] ์ข‹์•„์š” ์ˆ˜ - ๋™์‹œ์„ฑ ์ด์Šˆ์™€ ํ•ด๊ฒฐ ์ „๋žต : ๋น„๊ด€์  ๋ฝ, ๋‚™๊ด€์  ๋ฝ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

by Dongwoongkim

์ •ํ™•์„ฑ๊ณผ ์„ฑ๋Šฅ์€ ํŠธ๋ ˆ์ด๋“œ ์˜คํ”„ ๊ด€๊ณ„์ด๋‹ค. ๋ฌด๊ฒฐ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ๋ฝ์„ ๊ฐ•ํ•˜๊ฒŒ ๊ฑธ๋ฉด ์„ฑ๋Šฅ์ด ํฌ์ƒ๋˜๊ณ , ๋ฐ˜๋Œ€๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”์— ์ง‘์ค‘ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ๋Š” ๋ฆฌ์Šคํฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๊ฐœ๋ฐœ์ž์˜ ์—ญํ• ์€ ์„œ๋น„์Šค ์ƒํ™ฉ์— ๋งž๋Š” ์ ์ ˆํ•œ ๊ท ํ˜•์ ์„ ์ฐพ๋Š” ๊ฒƒ์ด๋‹ค.

 

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

 

๋จผ์ € ์ข‹์•„์š” ์ˆ˜์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์˜ ํŠน์„ฑ์„ ์ดํ•ดํ•ด ๋ณด์ž.

์ฒซ ๋ฒˆ์งธ๋กœ, ‘์ข‹์•„์š” ์ˆ˜’ ๋ฐ์ดํ„ฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋น„๊ต์  ๋‹จ์ˆœํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์“ฐ๊ธฐ ํŠธ๋ž˜ํ”ฝ์€ ๊ทธ๋ ‡๊ฒŒ ํฌ์ง€ ์•Š๋‹ค.

1. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๊ธ€์„ ์กฐํšŒํ•˜๊ณ , ์ข‹์•„์š”๋ฅผ ํด๋ฆญํ•œ๋‹ค.
2. ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์˜ ์ข‹์•„์š” ์ˆ˜๊ฐ€ 1 ์ฆ๊ฐ€ํ•œ๋‹ค.

 

๋‘ ๋ฒˆ์งธ๋กœ, ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์ด ์ค‘์š”ํ•˜๋‹ค. 100๋ช…์ด ์ข‹์•„์š” ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋‹ค๋ฉด, ์ข‹์•„์š” ์ˆ˜๋Š” 100์œผ๋กœ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ ๊ฑฐ์˜ ๋™์ผํ•œ ์‹œ์ ์—์„œ ๋‘ ํŠธ๋žœ์žญ์…˜์ด ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹œ๊ฐ„ ํŠธ๋žœ์žญ์…˜ A (์‚ฌ์šฉ์ž A) ํŠธ๋žœ์žญ์…˜ B (์‚ฌ์šฉ์ž B) DB ์ƒํƒœ (like_count)
T1 ๊ฒŒ์‹œ๊ธ€ 100๋ฒˆ ์กฐํšŒ (๊ฐ’: 10)   10
T2   ๊ฒŒ์‹œ๊ธ€ 100๋ฒˆ ์กฐํšŒ (๊ฐ’: 10) 10
T3 10 + 1 = 11 ๊ณ„์‚ฐ   10
T4 11๋กœ ์—…๋ฐ์ดํŠธ ์‹คํ–‰(Commit)   11
T5   10 + 1 = 11 ๊ณ„์‚ฐ 11
T6   11๋กœ ์—…๋ฐ์ดํŠธ ์‹คํ–‰(Commit) 11

 

์ด๋Ÿฐ ํŠน์„ฑ์„ ๊ฐ€์ง„ ์ข‹์•„์š” ์ˆ˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ข‹์„๊นŒ?

 

๊ฐ€์žฅ ์‰ฝ๊ฒŒ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”์—์„œ ์ข‹์•„์š” ์ˆ˜์— ๋Œ€ํ•œ ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ข‹์•„์š”๋ฅผ ๋ˆ„๋ฅผ ๋•Œ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์˜ ๋ฝ์„ ๊ฑธ๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์ฆ‰, ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”์— ์ข‹์•„์š” ์ˆ˜ ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๊ณ , ์ข‹์•„์š”๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ์ปฌ๋Ÿผ์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด์ฒ˜๋Ÿผ ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”์—์„œ ์ง์ ‘ ์ข‹์•„์š” ์ˆ˜๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

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

 

  • ๋‘ ๋ฒˆ์งธ๋Š” ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ์˜ ๋ณต์žก์„ฑ์ด๋‹ค. ์ข‹์•„์š” ๋‚ด์—ญ ์ €์žฅ๊ณผ ์นด์šดํŠธ ์—…๋ฐ์ดํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ์ผ์–ด๋‚  ๊ฒฝ์šฐ, ๋‘ ์ž‘์—…์˜ ์›์ž์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ ์ด๋Š” ์‹œ์Šคํ…œ์˜ ๋ณต์žก๋„๋ฅผ ํฌ๊ฒŒ ๋†’์ด๊ฒŒ ๋œ๋‹ค.
    • ๋‹จ์ผ DB๋ฅผ ์“ธ ๋•Œ๋Š” ์ข‹์•„์š” ๋‚ด์—ญ์„ ์ €์žฅํ•˜๊ณ (A), ๊ฒŒ์‹œ๊ธ€์˜ ์ข‹์•„์š” ์ˆ˜๋ฅผ +1 ํ•˜๋Š”(B) ์ž‘์—…์„ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ๊ธฐ ์‰ฝ๋‹ค. ํ•˜์ง€๋งŒ ์„œ๋น„์Šค๊ฐ€ ๋ถ„๋ฆฌ๋œ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” A๋Š” ์ข‹์•„์š” ์„œ๋น„์Šค DB์—, B๋Š” ๊ฒŒ์‹œ๊ธ€ ์„œ๋น„์Šค DB์— ์œ„์น˜ํ•˜๊ฒŒ ๋œ๋‹ค.
    • ๋งŒ์•ฝ ์ข‹์•„์š” ๋‚ด์—ญ์€ ์ €์žฅ๋๋Š”๋ฐ(A ์„ฑ๊ณต), ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋กœ ๊ฒŒ์‹œ๊ธ€ ์„œ๋น„์Šค์˜ ์นด์šดํŠธ ์—…๋ฐ์ดํŠธ๊ฐ€ ์‹คํŒจํ•˜๋ฉด(B ์‹คํŒจ)? ์‹ค์ œ ์ข‹์•„์š”๋Š” ๋ˆŒ๋ ธ๋Š”๋ฐ ์ˆซ์ž๋Š” ์•ˆ ์˜ฌ๋ผ๊ฐ€๋Š” ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
    • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด 2PC(2-Phase Commit)๋‚˜ Saga ํŒจํ„ด ๊ฐ™์€ ๋ณต์žกํ•œ ๊ธฐ๋ฒ•์„ ๋„์ž…ํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Š” ๊ฐœ๋ฐœ ๋‚œ์ด๋„๋ฅผ ๋†’์ผ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์‹œ์Šคํ…œ ์ „์ฒด ์„ฑ๋Šฅ์„ ๊นŽ์•„๋จน๋Š” ์ฃผ๋ฒ”์ด ๋œ๋‹ค.

 

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

 

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

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


0. Record Lock

๋ ˆ์ฝ”๋“œ ๋ฝ์€ ํ…Œ์ด๋ธ”์˜ ํ–‰ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ž ๊ธˆ์„ ๊ฑฐ๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

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

 

1. ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ (Data Integrity)

๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ •ํ™•์„ฑ, ์ผ๊ด€์„ฑ, ์œ ํšจ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค. ์ฆ‰, ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค์—ผ๋˜์ง€ ์•Š๊ณ  ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์—ฌ์•ผ ํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.

๋™์‹œ์„ฑ ์ œ์–ด ์ธก๋ฉด์—์„œ ๋ฌด๊ฒฐ์„ฑ์ด ๊นจ์ง€๋Š” ์‚ฌ๋ก€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

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

 

2. ๊ฒฝ์Ÿ ์ƒํƒœ (Race Condition)

๊ฒฝ์Ÿ ์ƒํƒœ๋Š” ๋‘ ๊ฐœ ์ด์ƒ์˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ณต์œ  ์ž์›์— ๋™์‹œ์— ์ ‘๊ทผํ•˜์—ฌ ๊ทธ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ์ ‘๊ทผ ์ˆœ์„œ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋Š” ํ˜„์ƒ์„ ๋งํ•œ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ์˜ˆ์‹œ์ธ ๊ฐฑ์‹  ์†์‹ค(Lost Update) ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์žฌ๊ณ ๊ฐ€ 10๊ฐœ์ธ ์ƒํ’ˆ์„ ์‚ฌ์šฉ์ž A์™€ B๊ฐ€ ๋™์‹œ์— ์กฐํšŒํ•˜์—ฌ ๋‘˜ ๋‹ค 10๊ฐœ๋กœ ํ™•์ธํ•œ๋‹ค.
  2. ์‚ฌ์šฉ์ž A๊ฐ€ 1๊ฐœ๋ฅผ ๋นผ์„œ 9๋กœ ์ €์žฅํ•œ๋‹ค.
  3. ์‚ฌ์šฉ์ž B๋„ ์ž์‹ ์ด ํ™•์ธํ–ˆ๋˜ 10์„ ๊ธฐ์ค€์œผ๋กœ 1๊ฐœ๋ฅผ ๋นผ์„œ 9๋กœ ์ €์žฅํ•œ๋‹ค.
  4. ์‹ค์ œ๋กœ๋Š” 2๊ฐœ๊ฐ€ ํŒ”๋ ธ์œผ๋‚˜ ์žฌ๊ณ ๋Š” 8์ด ์•„๋‹Œ 9๊ฐ€ ๋˜์–ด ๋ฐ์ดํ„ฐ๊ฐ€ ํ‹€์–ด์ง€๊ฒŒ ๋œ๋‹ค.

 

3. ๋ ˆ์ฝ”๋“œ ๋ฝ์˜ ํ•ด๊ฒฐ ๋ฐฉ์‹

๋ ˆ์ฝ”๋“œ ๋ฝ์€ ํŠน์ • ํ–‰(Row)์— ๋Œ€ํ•ด ์‚ฌ์šฉ๊ถŒ์„ ์„ ์ ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ๋ฐฐํƒ€์  ๋ฝ: ์‚ฌ์šฉ์ž A๊ฐ€ ์ˆ˜์ • ์ค‘์ผ ๋•Œ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•œ๋‹ค.
  • ์ˆœ์ฐจ ์ฒ˜๋ฆฌ: ๋‚˜์ค‘์— ์˜จ ์‚ฌ์šฉ์ž๋Š” ์•ž์„  ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๊ฒฝ์Ÿ ์ƒํƒœ๊ฐ€ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ฐฉ์ง€๋œ๋‹ค.
  • ๋ฌด๊ฒฐ์„ฑ ๋ณด์žฅ: ๋ชจ๋“  ์ˆ˜์ •์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฏ€๋กœ ์ตœ์ข…์ ์œผ๋กœ ๋ฐ์ดํ„ฐ์˜ ์ •ํ™•์„ฑ์ด ์œ ์ง€๋œ๋‹ค.

์š”์•ฝํ•˜์ž๋ฉด ๋ ˆ์ฝ”๋“œ ๋ฝ์€ ํ”„๋กœ์„ธ์Šค ๊ฐ„์˜ ์ค„ ์„ธ์šฐ๊ธฐ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค์—ผ๋˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š” ํ•ต์‹ฌ์ ์ธ ์žฅ์น˜๋‹ค.

  • SELECT … FOR UPDATE ๊ตฌ๋ฌธ์„ ํ†ตํ•ด ์กฐํšŒ๋œ ๋ ˆ์ฝ”๋“œ๋“ค์— ๋Œ€ํ•ด ๋ฐฐํƒ€์  ๋ฝ์„ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. 
๋‹จ๊ณ„ ํŠธ๋žœ์žญ์…˜ A (์‚ฌ์šฉ์ž A) ํŠธ๋žœ์žญ์…˜ B (์‚ฌ์šฉ์ž B) DB (like_count)  
1 SELECT ... FOR UPDATE (10 ์ฝ์Œ)   10 A๊ฐ€ ๋ฝ ์„ ์ 
2   SELECT ... FOR UPDATE ์‹œ๋„ 10 B๋Š” ๋Œ€๊ธฐ(Waiting)
3 10 + 1 = 11 ๊ณ„์‚ฐ ๋ฐ UPDATE   11  
4 COMMIT (๋ฝ ํ•ด์ œ)   11  
5   SELECT ์„ฑ๊ณต (๊ฐฑ์‹ ๋œ 11 ์ฝ์Œ) 11 B๊ฐ€ ๋ฝ์„ ํš๋“ํ•˜๊ณ  ์ฝ์Œ
6   11 + 1 = 12 ๊ณ„์‚ฐ ๋ฐ UPDATE 12  

 

๋ ˆ์ฝ”๋“œ ๋ฝ์€ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์‹œ์Šคํ…œ ์ „์ฒด์˜ ์„ฑ๋Šฅ๊ณผ ์•ˆ์ •์„ฑ์„ ๋–จ์–ด๋œจ๋ฆฌ๋Š” ์—ฌ๋Ÿฌ ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.


1. Record Lock์˜ ๋‹จ์ 

1. ์„ฑ๋Šฅ ์ €ํ•˜ ๋ฐ ์ฒ˜๋ฆฌ๋Ÿ‰ ๊ฐ์†Œ

๊ฐ€์žฅ ์ง์ ‘์ ์ธ ๋‹จ์ ์€ ์‹œ์Šคํ…œ์˜ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์ด ๋–จ์–ด์ง„๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

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

 

2. ๋ฐ๋“œ๋ฝ (Deadlock, ๊ต์ฐฉ ์ƒํƒœ)

๋‘ ๊ฐœ ์ด์ƒ์˜ ํŠธ๋žœ์žญ์…˜์ด ์„œ๋กœ๊ฐ€ ๊ฐ€์ง„ ๋ฝ์„ ํ•ด์ œํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ ๋ฌดํ•œ ๋Œ€๊ธฐ์— ๋น ์ง€๋Š” ์ƒํ™ฉ์ด๋‹ค.

  • ์˜ˆ์‹œ: ํŠธ๋žœ์žญ์…˜ A๊ฐ€ 1๋ฒˆ ๋ ˆ์ฝ”๋“œ๋ฅผ ์žก๊ณ  2๋ฒˆ์„ ์š”์ฒญํ•˜๋Š”๋ฐ, ๋™์‹œ์— ํŠธ๋žœ์žญ์…˜ B๊ฐ€ 2๋ฒˆ ๋ ˆ์ฝ”๋“œ๋ฅผ ์žก๊ณ  1๋ฒˆ์„ ์š”์ฒญํ•˜๋ฉด ๋‘ ์ž‘์—… ๋ชจ๋‘ ์˜์›ํžˆ ๋ฉˆ์ถ˜๋‹ค.
  • ๊ฒฐ๊ณผ: DB ์—”์ง„์ด ์ด๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๊ฐ•์ œ๋กœ ํ•˜๋‚˜๋ฅผ ์ข…๋ฃŒ(Rollback)์‹œํ‚ค์ง€๋งŒ, ์ด ๊ณผ์ •์—์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ €ํ•˜๋˜๊ณ  ์ถ”๊ฐ€์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค.

 

3. ๋ฝ ์—์Šค์ปฌ๋ ˆ์ด์…˜ ๋ฐ ๋ฒ”์œ„ ๋ฌธ์ œ

๋ฝ์„ ๊ฑฐ๋Š” ๊ณผ์ •์—์„œ ์˜ˆ์ƒ๋ณด๋‹ค ํฐ ๋ฒ”์œ„๊ฐ€ ์ž ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

  • ํ…Œ์ด๋ธ” ํ’€ ์Šค์บ”: WHERE ์กฐ๊ฑด์— ์ธ๋ฑ์Šค๊ฐ€ ๊ฑธ๋ ค ์žˆ์ง€ ์•Š์œผ๋ฉด, DB๋Š” ์–ด๋–ค ๋ ˆ์ฝ”๋“œ๋ฅผ ์ž ๊ธ€์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ด๋ธ” ์ „์ฒด๋ฅผ ํ›‘์œผ๋ฉฐ ๋ชจ๋“  ํ–‰์— ๋ฝ์„ ๊ฑธ๊ฑฐ๋‚˜ ํ…Œ์ด๋ธ” ๋ฝ์„ ์‹œํ‚จ๋‹ค.
  • ์˜๋„์น˜ ์•Š์€ ์ฐจ๋‹จ: ๋‚˜๋Š” ํ–‰ ํ•˜๋‚˜๋งŒ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ธ๋ฑ์Šค ๋ฌธ์ œ๋กœ ์ธํ•ด ์—‰๋šฑํ•œ ๋ ˆ์ฝ”๋“œ๋“ค๊นŒ์ง€ ์ˆ˜์ •์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง€๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

4. ์ปค๋„ฅ์…˜ ๊ณ ๊ฐˆ (Connection Pool Starvation)

๋ฝ์„ ํš๋“ํ•˜๊ธฐ ์œ„ํ•ด ๋Œ€๊ธฐํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๋ฉด ํ•ด๋‹น ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” DB ์ปค๋„ฅ์…˜์ด ๊ณ„์† ์ ์œ ๋œ ์ƒํƒœ๋กœ ๋‚จ๋Š”๋‹ค.

  • ๋Œ€๊ธฐํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋งŽ์•„์ง€๋ฉด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปค๋„ฅ์…˜ ํ’€์ด ๋ฐ”๋‹ฅ๋‚˜์„œ, ๋ฝ๊ณผ ์ƒ๊ด€์—†๋Š” ๋‹ค๋ฅธ ๋‹จ์ˆœ ์กฐํšŒ ์„œ๋น„์Šค๋“ค๊นŒ์ง€ ๋ชจ๋‘ ๋จนํ†ต์ด ๋˜๋Š” ์—ฐ์‡„ ์žฅ์• ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

 

5. ๊ด€๋ฆฌ ๋ฐ ์„ค๊ณ„์˜ ๋ณต์žก๋„ ์ฆ๊ฐ€

๋ฝ์„ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ณ ๋ คํ•ด์•ผ ํ•  ๊ฒƒ์ด ๋งŽ์•„์ง„๋‹ค.

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

2. ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

์ด๋Ÿฌํ•œ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ „๋žต์„ ์„ž์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋‚™๊ด€์  ๋ฝ(Optimistic Lock): ๋ฝ์„ ์ง์ ‘ ๊ฑธ์ง€ ์•Š๊ณ  ๋ฒ„์ „ ๋ฒˆํ˜ธ ๋“ฑ์„ ํ†ตํ•ด ์ถฉ๋Œ ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•œ๋‹ค. (์ถฉ๋Œ์ด ์ ์€ ๊ฒฝ์šฐ ์œ ๋ฆฌ)
  • Redis ๋“ฑ์˜ ์บ์‹œ ์‚ฌ์šฉ: DB๊นŒ์ง€ ์˜ค๊ธฐ ์ „์— ๋ถ„์‚ฐ ๋ฝ์„ ํ†ตํ•ด ์ฒ˜๋ฆฌ ์†๋„๋ฅผ ๋†’์ธ๋‹ค.
  • ์งง์€ ํŠธ๋žœ์žญ์…˜: ๋ฝ์„ ์žก๋Š” ์‹œ๊ฐ„์„ ์ตœ์†Œํ™”ํ•˜๋„๋ก ๋กœ์ง์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

๋‚™๊ด€์  ๋ฝ๊ณผ ๋น„๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ข‹์•„์š” ์„œ๋น„์Šค๋ฅผ ๊ฒŒ์‹œ๊ธ€ ์„œ๋น„์Šค์™€ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ตฌํ˜„ํ•ด ๋ณด์ž.

  • ํ…Œ์ด๋ธ” ์„ค๊ณ„ : ๋จผ์ € ๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ํ™˜๊ฒฝ ๋‚ด์—์„œ, ์ข‹์•„์š” ์ˆ˜ ์„œ๋น„์Šค๋Š” ๊ฒŒ์‹œ๊ธ€๊ณผ 1 : 1 ๋˜๋Š” ์„œ๋น„์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ƒค๋“œ ํ‚ค๋Š” ๊ฒŒ์‹œ๊ธ€ ์•„์ด๋””์ธ ArticleId๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    • article_id : ๊ธฐ๋ณธ ํ‚ค์ด์ž ์ƒค๋“œ ํ‚ค(Shard Key)๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
    • like_count : ์‹ค์ œ ์ข‹์•„์š” ์ˆ˜๋ฅผ ์ €์žฅํ•œ๋‹ค.
    • version : ๋‚™๊ด€์  ๋ฝ์„ ์œ„ํ•œ ๋ฒ„์ „ ๊ด€๋ฆฌ ์ปฌ๋Ÿผ์ด๋‹ค.
CREATE TABLE `article_like_count` (
  `article_id` bigint NOT NULL,
  `like_count` bigint NOT NULL,
  `version` bigint NOT NULL,
  PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

โ‘  ๋น„๊ด€์  ๋ฝ (Pessimistic Lock)

๋ฐ์ดํ„ฐ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ๋ฝ์„ ๊ฑธ์–ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์˜ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ๋ฐฉ๋ฒ• 1 (์ง์ ‘ ์—…๋ฐ์ดํŠธ): update ... set like_count = like_count + 1 ์ฟผ๋ฆฌ๋ฅผ ๋ฐ”๋กœ ์‹คํ–‰ํ•œ๋‹ค. ๋ฝ ์ ์œ  ์‹œ๊ฐ„์ด ์งง์•„ ํšจ์œจ์ ์ด๋‹ค. ์ˆ˜์ • ์‹œ์ ์— ๋ฝ์„ ๊ฑฐ๋Š” ๋ฐฉ์‹์ด๋‹ค.
@Query(
    value = "update article_like_count set like_count = like_count + 1 where article_id = :articleId",
    nativeQuery = true
)
@Modifying
int increase(@Param("articleId") Long articleId);

/**
 * update ๊ตฌ๋ฌธ
 */
@Transactional
public void likePessimisticLock1(Long articleId, Long userId) {
    int result = articleLikeCountRepository.increase(articleId);

    if (result == 0) {
        articleLikeCountRepository.save(
            ArticleLikeCount.init(articleId, 1L)
        );
    }
}
  • ๋ฐฉ๋ฒ• 2 (์กฐํšŒ ํ›„ ์—…๋ฐ์ดํŠธ): SELECT ... FOR UPDATE๋กœ ๋จผ์ € ๋ฝ์„ ์žก๊ณ  ์กฐํšŒํ•œ ๋’ค, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊ณ„์‚ฐ๋œ ๊ฐ’์„ UPDATE ํ•œ๋‹ค. ๋ฝ์„ ์กฐํšŒ ์‹œ์ ๋ถ€ํ„ฐ ์ ์œ ํ•˜๋ฏ€๋กœ ๋น„๊ต์  ๋ฝ ์ ์œ  ์‹œ๊ฐ„์ด ๊ธธ๋‹ค. JPA ํ™˜๊ฒฝ์—์„œ๋Š” ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ฉ”์„œ๋“œ์—@Lock(LockModeType.PESSIMISTIC_WRITE) ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ ์–ธํ•˜์—ฌ ๊ฐ„๋‹จํžˆ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
@Query(
    value = "update article_like_count set like_count = like_count + 1 where article_id = :articleId",
    nativeQuery = true
)
@Modifying
int increase(@Param("articleId") Long articleId);

/**
 * update ๊ตฌ๋ฌธ
 */
@Transactional
public void likePessimisticLock1(Long articleId, Long userId) {
    int result = articleLikeCountRepository.increase(articleId);

    if (result == 0) {
        articleLikeCountRepository.save(
            ArticleLikeCount.init(articleId, 1L)
        );
    }
}

 

โ‘ก ๋‚™๊ด€์  ๋ฝ (Optimistic Lock)

์ถฉ๋Œ์ด ๊ฑฐ์˜ ์—†๋‹ค ๊ฐ€์ •ํ•˜๊ณ , ๋ฝ ๋Œ€์‹  ๋ฒ„์ „(Version) ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•ด ์ •ํ•ฉ์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ๋™์ž‘ ์›๋ฆฌ: ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ๋•Œ ๋ฒ„์ „ ๋ฒˆํ˜ธ๋ฅผ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๊ณ , ์ˆ˜์ •ํ•  ๋•Œ WHERE version = ํ˜„์žฌ๋ฒ„์ „ ์กฐ๊ฑด์„ ๊ฑด๋‹ค. ์„ฑ๊ณตํ•˜๋ฉด ๋ฒ„์ „์„ ์˜ฌ๋ฆฌ๊ณ , ์‹คํŒจํ•˜๋ฉด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ์ˆ˜์ •ํ•œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•ด ๋กค๋ฐฑํ•œ๋‹ค.
  • ํŠน์ง•: DB ๋ฝ์„ ์žก์ง€ ์•Š์•„ ์„ฑ๋Šฅ์ƒ ์œ ๋ฆฌํ•˜์ง€๋งŒ, ์ถฉ๋Œ ๋ฐœ์ƒ ์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ›„์ฒ˜๋ฆฌ(์žฌ์‹œ๋„ ๋“ฑ)๊ฐ€ ํ•„์š”ํ•˜๋‹ค. JPA์—์„œ๋Š” @Version ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๊ฐ„๋‹จํžˆ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” increase() ์‹œ์ ์— ์—”ํ‹ฐํ‹ฐ์˜ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๊ณ , ์ด๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ WHERE ์กฐ๊ฑด์— ์˜ํ•ด version์ด ์ด ์ „ ๋ฒ„์ „์ธ์ง€ ๊ฒ€์‚ฌํ•˜์—ฌ UPDTAE๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.
  • ๋‹ค๋งŒ ๋ฒ„์ „ ์ถฉ๋Œ ๋ฐœ์ƒ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•  ์ง€, ์žฌ์‹œ๋„ ๋กœ์ง์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋‚™๊ด€์  ๋ฝ์€ ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•  ๋ฟ,
/**
 * optimistic Lock - ๋ณ€๊ฒฝ ์‹œ์ (increase() ์ˆ˜ํ–‰ ์‹œ์ )์—์„œ version ์ฒดํฌํ•ด์„œ update
 * UPDATE article_like_count SET like_count = ?, version = ? (์ƒˆ ๋ฒ„์ „) WHERE article_id = ? AND version = ? (๊ธฐ์กด ๋ฒ„์ „)
 */
@Transactional
public void likeOptimisticLock(Long articleId, Long userId) {
    ArticleLikeCount articleLikeCount = articleLikeCountRepository.findById(articleId)
        .orElseGet(() -> ArticleLikeCount.init(articleId, 0L));

    articleLikeCount.increase(); // dirty check
    // articleLikeCountRepository.save(articleLikeCount);
}

 

โ‘ข ๋น„๋™๊ธฐ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ

์š”์ฒญ์„ ๋Œ€๊ธฐ์—ด(Queue)์— ๋„ฃ๊ณ  ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  1. ๊ตฌํ˜„ ์›๋ฆฌ: ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ(Producer)๊ณผ ์‹ค์ œ DB ๋ฐ˜์˜(Consumer)์„ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•œ๋‹ค.
  2. ํ(Queue)๋ฅผ ํ†ตํ•ด ์š”์ฒญ์„ ์ผ๋ ฌ๋กœ ์ค„ ์„ธ์šฐ๊ธฐ ๋•Œ๋ฌธ์—, DB ๋ ˆ๋ฒจ์˜ ๋ณต์žกํ•œ ๋ฝ ์—†์ด๋„ ์ •ํ•ฉ์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์žฅ์ 
    • ๋น ๋ฅธ ์“ฐ๊ธฐ ์†๋„: DB ํŠธ๋žœ์žญ์…˜์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ฉ”๋ชจ๋ฆฌ(Redis)์— ์“ฐ๊ณ  ๋ฐ”๋กœ ์‘๋‹ตํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž ์ฒด๊ฐ ์†๋„๊ฐ€ ๋งค์šฐ ๋น ๋ฅด๋‹ค.
    • ๋ถ€ํ•˜ ์กฐ์ ˆ: ๊ฐ‘์ž๊ธฐ ์ข‹์•„์š”๊ฐ€ ๋ช‡๋งŒ ๊ฐœ๊ฐ€ ๋ชฐ๋ ค๋„ DB๊ฐ€ ํ„ฐ์ง€์ง€ ์•Š๊ณ  ํ์— ์Œ“์˜€๋‹ค๊ฐ€ ์„œ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ์†๋„๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์ 
    • ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋Š”๋ฐ ํ™”๋ฉด ์ˆซ์ž๊ฐ€ 0.5~1์ดˆ ๋’ค์— ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.
    • Redis๋‚˜ Kafka ๊ฐ™์€ ๋ณ„๋„์˜ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๋ฅผ ์šด์˜ํ•ด์•ผ ํ•œ๋‹ค.
  • ์žฅ์ : ๋™์‹œ์„ฑ ๋ฌธ์ œ ์ž์ฒด๋ฅผ ์›์ฒœ ์ฐจ๋‹จํ•˜๋ฉฐ DB ๋ถ€ํ•˜๊ฐ€ ์ ๋‹ค.
  • ๋‹จ์ : ์‹œ์Šคํ…œ ๊ตฌ์ถ• ๋น„์šฉ์ด ํฌ๊ณ  ์‹ค์‹œ๊ฐ„ ์‘๋‹ต์ด ์–ด๋ ค์›Œ ํ›„ ์กฐ์น˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ์ฆ‰์‹œ DB์— ๋ฐ˜์˜ํ•˜์ง€ ์•Š๊ณ  ๋ฉ”์‹œ์ง€ ํ(Message Queue)์— ์Œ“์•„๋‘” ๋’ค, ๋ณ„๋„์˜ ์›Œ์ปค(Worker)๊ฐ€ ํ•˜๋‚˜์”ฉ ๊บผ๋‚ด์–ด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” Redis๋ฅผ ๋Œ€๊ธฐ์—ด๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์„ฑํ–ˆ๋‹ค.

1. ์„œ๋น„์Šค ๋ ˆ์ด์–ด: ์š”์ฒญ์„ ํ์— ์Œ“๊ธฐ (Producer)

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

@Service
@RequiredArgsConstructor
public class ArticleLikeService {
    private final RedisTemplate<String, String> redisTemplate;
    private static final String LIKE_QUEUE = "article_like_task_queue";

    /**
     * ๋น„๋™๊ธฐ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ - ์š”์ฒญ ์ ‘์ˆ˜
     */
    public void likeAsync(Long articleId, Long userId) {
        // "articleId:userId:TYPE" ํ˜•ํƒœ๋กœ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
        String message = articleId + ":" + userId + ":LIKE";
        
        // Redis List์˜ ์˜ค๋ฅธ์ชฝ(๋’ค)์— ์‚ฝ์ž… (Queue ์—ญํ• )
        redisTemplate.opsForList().rightPush(LIKE_QUEUE, message);
    }
}

 

2. ์›Œ์ปค(Worker): ํ์—์„œ ๊บผ๋‚ด์–ด ์ˆœ์ฐจ ์ฒ˜๋ฆฌ (Consumer)

๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ํ๋ฅผ ๊ฐ์‹œํ•˜๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ํ•˜๋‚˜์”ฉ ๊บผ๋‚ด์–ด ์ฒ˜๋ฆฌํ•œ๋‹ค. ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

@Component
@RequiredArgsConstructor
public class LikeWorker {

    private final RedisTemplate<String, String> redisTemplate;
    private final ArticleLikeRepository articleLikeRepository;
    private final ArticleLikeCountRepository articleLikeCountRepository;
    private final Snowflake snowflake = new Snowflake();
    private static final String LIKE_QUEUE = "article_like_task_queue";

    // 0.1์ดˆ๋งˆ๋‹ค ํ๋ฅผ ํ™•์ธํ•˜์—ฌ ์ฒ˜๋ฆฌ (ํ˜น์€ ๋ฌดํ•œ ๋ฃจํ”„๋กœ ๋Œ€๊ธฐ)
    @Scheduled(fixedDelay = 100)
    @Transactional
    public void processLikeQueue() {
        // ์™ผ์ชฝ(์•ž)์—์„œ ํ•˜๋‚˜ ๊บผ๋‚ด์˜ค๊ธฐ
        String message = redisTemplate.opsForList().leftPop(LIKE_QUEUE);
        
        if (message == null) return;

        String[] data = message.split(":");
        Long articleId = Long.parseLong(data[0]);
        Long userId = Long.parseLong(data[1]);
        String type = data[2];

        if ("LIKE".equals(type)) {
            // ์ข‹์•„์š” ๋‚ด์—ญ ์ €์žฅ
            articleLikeRepository.save(ArticleLike.create(snowflake.nextId(), articleId, userId));
            // ์ข‹์•„์š” ์ˆ˜ ์นด์šดํŠธ ์—…๋ฐ์ดํŠธ (๋น„๊ด€์  ๋ฝ ์—†์ด ์ผ๋ฐ˜ update๋กœ๋„ ์•ˆ์ „)
            articleLikeCountRepository.increase(articleId);
        } else {
            // ์ข‹์•„์š” ์ทจ์†Œ 
        }
    }
}

 

 ์„ธ ๊ฐ€์ง€ ์ „๋žต์— ๋Œ€ํ•ด ํ‘œ๋กœ ์ •๋ฆฌํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๊ตฌ๋ถ„ ๋น„๊ด€์  ๋ฝ (๋ฐฉ๋ฒ• 1) ๋น„๊ด€์  ๋ฝ (๋ฐฉ๋ฒ• 2) ๋‚™๊ด€์  ๋ฝ ๋น„๋™๊ธฐ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ
๋ฝ ์ ์œ  UPDATE ์‹œ์ ์—๋งŒ ์งง๊ฒŒ ์กฐํšŒ๋ถ€ํ„ฐ ์ปค๋ฐ‹๊นŒ์ง€ ์—†์Œ (๋ฒ„์ „ ์ฒดํฌ๋งŒ ํ•จ) ์—†์Œ (๋ฉ”์‹œ์ง€ ํ ํ™œ์šฉ)
์†๋„ ๋น ๋ฆ„ ๋ณดํ†ต ๋งค์šฐ ๋น ๋ฆ„ (์ถฉ๋Œ ์—†์„ ์‹œ) ๊ฐ€์žฅ ๋น ๋ฆ„ (์‘๋‹ต ์œ„์ฃผ)
๋‚œ์ด๋„ ๋‚ฎ์Œ (SQL ์œ„์ฃผ) ๋ณดํ†ต (JPA ํ™œ์šฉ) ๋ณดํ†ต (์ถฉ๋Œ ํ›„์ฒ˜๋ฆฌ ํ•„์š”) ๋†’์Œ (์ธํ”„๋ผ ๊ตฌ์ถ• ํ•„์š”)
์•ˆ์ „์„ฑ ๋งค์šฐ ๋†’์Œ ๋งค์šฐ ๋†’์Œ ์ถฉ๋Œ ์‹œ ์‚ฌ์šฉ์ž ์‹คํŒจ ์‘๋‹ต ๋งค์šฐ ๋†’์Œ (์ˆœ์ฐจ ๋ณด์žฅ)
๋ฐ์ดํ„ฐ ๋ฐ˜์˜ ์ฆ‰์‹œ ๋ฐ˜์˜ ์ฆ‰์‹œ ๋ฐ˜์˜ ์ฆ‰์‹œ ๋ฐ˜์˜ ์ง€์—ฐ ๋ฐ˜์˜ (์ตœ์ข… ์ผ๊ด€์„ฑ)
  • ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์ด ์ตœ์šฐ์„ ์ด๋ฉฐ, ๋กœ์ง์ด ๋‹จ์ˆœํ•˜๋‹ค๋ฉด?
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์›์ž์  ์—ฐ์‚ฐ์„ ํ™œ์šฉํ•˜๋Š” ๋น„๊ด€์  ๋ฝ(๋ฐฉ๋ฒ• 1)์ด ๊ฐ€์žฅ ํšจ์œจ์ ์ด๋‹ค. ๋ฝ ์ ์œ  ์‹œ๊ฐ„์„ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ๋„ ์ •ํ™•ํ•œ ์นด์šดํŒ…์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค
  • ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ์ฝ”๋“œ ์œ ์ง€์™€ ๊ฐ•๋ ฅํ•œ ์ •ํ•ฉ์„ฑ์ด ๋ชจ๋‘ ์ค‘์š”ํ•˜๋‹ค๋ฉด?
    • JPA์˜ ๊ฐ•์ ์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ๋Š” ๋น„๊ด€์  ๋ฝ(๋ฐฉ๋ฒ• 2)์ธ SELECT FOR UPDATE๋ฅผ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ, ๋ฝ ์ ์œ  ์‹œ๊ฐ„์ด ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํŠธ๋žœ์žญ์…˜์˜ ๋ฒ”์œ„๋ฅผ ์ตœ๋Œ€ํ•œ ์งง๊ฒŒ ๊ฐ€์ ธ๊ฐ€๋Š” ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • ์ถฉ๋Œ ๋นˆ๋„๊ฐ€ ๋‚ฎ๊ณ  ์‹œ์Šคํ…œ์˜ ์ „๋ฐ˜์ ์ธ ์ฒ˜๋ฆฌ๋Ÿ‰์ด ์ค‘์š”ํ•˜๋‹ค๋ฉด?
    • ๋ฌผ๋ฆฌ์ ์ธ ๋ฝ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†๋Š” ๋‚™๊ด€์  ๋ฝ์ด ์ข‹์€ ๋Œ€์•ˆ์ด ๋œ๋‹ค. ๋‹ค๋งŒ ๋ฒ„์ „ ์ถฉ๋Œ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹คํŒจ๋ฅผ ์•Œ๋ฆด ๊ฒƒ์ธ์ง€, ํ˜น์€ ์žฌ์‹œ๋„ ๋กœ์ง์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

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

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

Dongwoongkim

ํ™œ๋™ํ•˜๊ธฐ