๋ค์ด๊ฐ๋ฉฐ
11์ 30์ผ, 10์ฃผ ๊ฐ์ ํญํด ๊ต์ก ๊ณผ์ ์ด ๋๋ฌ๋ค.
์ญ์ ๋์๋ณด๋ ์งง๊ฒ ๋๊ปด์ง๋ ๊ฑด ์ฌ๋ ๊ฒฝํ๊ณผ๋ ๋น์ทํ ๊ฐ์ ์ ๋๋ผ๋ ๊ฒ ๊ฐ๋ค.
ํญํด ์๊ฐ ์ , ์ปค๋ฆฌํ๋ผ์ ๋ณด์์ ๋ ์ด๋ฐ ์๊ฐ์ ํ๋ค.
TDD, Kafka, Redis... ๋ค ์ต์ํ ํค์๋์ธ๋ฐ, ํผ์์๋ ๊ณต๋ถํ ์ ์์ง ์์๊น? ๊ณผ์ฐ ํฐ ๋์ ๋ด๋ฉด์๊น์ง ๋ฐฐ์ธ๋งํ ๊ฐ์น๊ฐ ์์๊น?
๋ฌผ๋ก ๋ง์๋ง ๋จน์ผ๋ฉด ํผ์์๋ ๊ฐ๋ฅํ์์ง ๋ชจ๋ฅธ๋ค. ํ์ง๋ง ํผ์์๋ ํ์ง ๋ชปํ ์ด์ ๊ฐ ์์๋ค.
1. 10์ฃผ์์ ๊ธฐ์ ์ ์ตํ๊ณ , ํ๋ก์ ํธ์ ๋ฐ์
2. ๊ด๋ฒ์ํ ์ฌ์ฉ ๋ฐฉ๋ฒ ์ค ์ค๋ฌด์ ๊ผญ ํ์ํ ๋ฐฉ๋ฒ์ ์บ์น
3. ์๋์ด ๊ฐ๋ฐ์๋ค์ ํธ๋ฌ๋ธ์ํ ๊ฒฝํ์ผ๋ก ์ป์ด๋ธ ๋ ธํ์ฐ
์ด ์ธ ๊ฐ์ง๋ ํผ์ ๊ณต๋ถํด์ ์ป์ด๋ด๊ธฐ์ ๋๋ฌด ์ค๋ ์๊ฐ์ด ํ์ํ ๊ฒ๋ค์ด์๋ค.
๊ทธ๋ฐ ์๋ฏธ์์ ๋์๊ฒ ์ง๋ 10์ฃผ๋ ํญ๋ฐ์ ์ผ๋ก ์ฑ์ฅํ ์ ์์๋ ์๊ฐ์ด์๋ค.
10์ฃผ ์ ์ ๋ด ๋ชจ์ต
0์ฃผ์ฐจ ํ๊ณ ๋ฅผ ๋ค์ ์ฝ์ด๋ณด๋, ๊ทธ ๋น์์ ๋ด ๊ฐ์ ์ด ์์ํ ๋ ์ค๋ฅธ๋ค.
ํ์ฌ ๋ค๋๊ณ ์๋ ํ์ฌ์์๋ ๋ง์ ๊ฒ์ ๋ฐฐ์ฐ๊ณ ์์ง๋ง, 'ํน์ ๋ด๊ฐ ์ฐ๋ฌผ ์ ๊ฐ๊ตฌ๋ฆฌ๊ฐ ๋์ด๊ฐ๊ณ ์๋ ๊ฑด ์๋๊น?' ํ๋ ๋ถ์๊ฐ์ด ๋ ๋ง์ ํ๊ตฌ์์ ์๋ฆฌ ์ก๊ณ ์์๋ค.
๋, '๋๋ ๊ณผ์ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ฑ์ฅํ๊ณ ์๋ ๊ฐ๋ฐ์์ธ๊ฐ?', '์ด ๋ฐฉํฅ์ ๋ง๋ ๊ฑธ๊น?' ๋ผ๋ ์ง๋ฌธ์ด ๋์์์ด ๋ ์ฌ๋๋ค. ํ์ง๋ง ๊ทธ ์ง๋ฌธ๋ค์ ๋ช ํํ ๋ต์ ์ฐพ์ง ๋ชปํ ์ฑ ์๊ฐ๋ง ํ๋ฌ๊ฐ๋ ๊ฒ ๊ฐ๋ค.
๊ทธ ๋ต์ ์ฐพ๊ธฐ ์ํด์ ๊ฒฐ๊ตญ ํญํด๋ฅผ ์ ์ฒญํ๋ค.
10์ฃผ ๊ณผ์ ์ ํตํด ๋ฐฐ์ด ์
1. ์ฑ์ฅ์ ๋ฐฉํฅ์ฑ
์ฃผ๋์ด ๊ฐ๋ฐ์๋ผ๋ฉด, ์๋ ์ด๋ ์ง์ ์ด๋ ๊ฐ์ ์ฑ์ฅํ๊ณ ์๋ ์ฌ๋์ด๋ผ๋ฉด ๊ฐ์ฅ ๊ณ ๋ฏผ๋๋ ๋ถ๋ถ์ด๋ผ๊ณ ์๊ฐ๋๋ค.
๋ด๊ฐ ๊ฐ๋ ๊ธธ์ด ๋น๋ก ์ง๋ฆ๊ธธ์ ์๋๋๋ผ๋, ์ต์ข ๋ชฉ์ ์ง์ ๋ง๋ ๋ฐฉํฅ์ผ๋ก ๋์๊ฐ๊ณ ์๋์ง ์ข ์ข ์๋ฌธ์ด ๋ค๊ณ , ๋๋ก๋ ๊ธธ์ ํค๋งค๊ธฐ๋ ํ๋ค.
๊ทธ๋ฐ ๊ณ ๋ฏผ์ ๊ฐ์ง๊ณ ์์ํ ํญํด ๊ณผ์ ์์, ์ฝ์น๋๋ค์ ๋จ์ํ '์ด๋ ๊ฒ๋ง ํด' ํ๊ณ ์ ๋ต์ ๋ด๋ ค์ฃผ๋ ๊ฒ ์๋, '์ด๋ฐ ๊ด์ ์ผ๋ก ๋ฐ๋ผ๋ณด๊ณ ํด๊ฒฐํด๋ณด์.' ๋ผ๋ ์ ์์ ๋์ ธ์ฃผ์๋ค.
๋ ํฅ๋ฏธ๋ก์ ๋ ์ ์ ์ฝ์น๋๋ค๋ง๋ค๋ ๊ฐ๊ฐ์ด ๋ฌธ์ ๋ฅผ ๋ฐ๋ผ๋ณด๋ ๋ฐฉ์์ด๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ๋ชจ๋ ๋ค๋ฅด๋ค๋ ๊ฒ์ด์๋ค.
๊ทธ ๊ณผ์ ์์ ๊นจ๋ฌ์ ์ ์ด ์์๋ค.
๊ฐ๋ฐ์๋ ์ ๋ต์ด ์๋ค. ๋ค๋ง, ์ฃผ์ด์ง ์์คํ ์ ๊ฐ์ฅ ์ ํฉํ ์ต์ ์ ๋ฐฉ์์ ์ ํํ ์ค ์๋ ๊ฒ์ด ์ค์ํ๋ค.
์ด ์์น์ ๋ฐํ์ผ๋ก ์ฑ์ฅํ๊ธฐ ์ํด ํ์ํ ์ธ ๊ฐ์ง ๋ฐฉํฅ์ฑ์ ์ ๋ฆฌํ ์ ์์๋ค.
1. ์ฌ๋ฌ ๋ฐฉ์์ ๋ ์ฌ๋ฆด ์ ์๋ ์ฌ๊ณ ๋ ฅ
2. ์ฌ๋ฌ ๋ฐฉ์์ ์ค์ ๋ก ๊ตฌํํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ ์ ์๋ ์คํ๋ ฅ
3. ๊ทธ์ค ์ต์ ์ ๋ฐฉ๋ฒ์ ์ ํํ ์ ์๋ ํ๋จ๋ ฅ
์ด๊ฑธ ๊ฐ๋ฅํ๊ฒ ํ๋ ๊ฒ ํต์ฌ์ ๊ฒฐ๊ตญ ์ํต
๊ณผ ํ์ต
์ด์๋ค.
2. ์ํต
ํญํด ์ด์ ์๋ ๊ฐ๋ฐ์ ๋คํธ์ํน์ ๊ฒฝํํด๋ณธ ์ ์ด ์์๋ค. ๋ง๋ฅ ๊ฒ์๋ ๋ ๊ฒ ํ๋ชซํ์ง๋ง, ๋น์ ๊ณต์๋ก์ ๊ธฐ๋ณธ๊ธฐ๊ฐ ๋ถ์กฑํ๋ค๋ณด๋ ์๊ฐ์ ๊ฐํ ํผ์์๋ง ๊ณต๋ถํด์์๋ ๊ฒ ๊ฐ๋ค.
ํ์ง๋ง ํญํด์์๋ ๋น์ทํ ์ฐ์ฐจ์ ๊ฐ๋ฐ์๋ค๊ณผ ๊ฐ์์ ์๊ฐ์ ๋๋๊ณ , ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ํตํด ์ง์์ ๊ณต์ ํ๋ฉด์ ์ด์ ์๋ ์์๋ ๋ชป ํ๋ ์ฑ์ฅ ์๋๋ฅผ ์ฒด๊ฐํ๋ค.
ํนํ, ์๋์ด ๊ฐ๋ฐ์๋ค๊ณผ์ ๋ํ๋ฅผ ํตํด ๋ด๊ฐ ํ์ ๋ง์ฐํ ๊ถ๊ธํ๋ ๊ฒ๋ค์ ๋ช ํํ ํ ์ ์์๋ค.
- ๊ฐ์ฒด์งํฅ์ ์ธ ์ฝ๋๋ ๋ฌด์์ธ๊ฐ?
- ์ข์ ํ ์คํธ ์ฝ๋์ ๊ธฐ์ค์ ๋ฌด์์ธ๊ฐ?
- ํด๋ฆฐ ์ํคํ ์ฒ๋ ์ด๋ป๊ฒ ์ค๋ฌด์์ ์ ์ฉ๋๋๊ฐ?
์ด๋ฐ ์ง๋ฌธ๋ค์ ์ฌ๋ฌ ๋ฒ ๋์ ธ๋ณด๋ฉฐ ์ถ์์ ์ด์๋ ๊ฐ๋ ๋ค์ด ์ ์ ๊ตฌ์ฒดํ๋๊ณ ํ๊ณ ํด์ก๋ค.
3. ๊พธ์คํจ
10์ฃผ ๋์ ํญํด๋ฅผ ํ๋ฉด์ ๋ด ์ํ ํจํด์ ๋ค์๊ณผ ๊ฐ์๋ค.
7์๋ฐ ๊ธฐ์ -> 9์๋ฐ ์ถ๊ทผ -> 18์๋ฐ ํด๊ทผ -> 20์ ์ ๋
์์ฌ -> 20์๋ฐ ํ์ต -> 3์ ์ทจ์นจ
ํ์ฌ ์ผ์ ๋ณํํ๋ฉฐ ๊ฐ์ธ ๊ณผ์ ์ ์ด๋ ๊ฒ ๊ธด ์๊ฐ ๋ชฐ๋ํด ๋ณธ ๊ฒฝํ์ ์ฒ์์ด์๋ค.
0์ฃผ์ฐจ ํ๊ณ ์ ์ ์๋ ๊ฒ์ฒ๋ผ, ์ด๋ฒ ๊ณผ์ ์์ '๋ด๊ฐ ๋ ธ๋ ฅํ ๋งํผ ์ป์ด๊ฐ ๊ฒ'์ด๋ผ๋ ์๊ฐ์ผ๋ก ์ํ๊ณ , ์ฃผ์ด์ง ๊ณผ์ ์ ํํฌ๋ฃจ ์๊ฐ์ ์ฐ์ง ์์ผ๋ ค ํ๋ค. ์ฃผ์ฐจ๋ณ ์ฃผ์ด์ง ํต์ฌ ํค์๋์ ๋ชฐ๋ํ์ง๋ง, ๊ทธ๊ฒ๋ ์๊ฐ์ด ๋ ๋ถ์กฑํ๋ค๊ณ ๋๊ปด์ง ์ ๋์๋ค.
๊ทธ๋ผ์๋ 10์ฃผ๋์์ ๋ฐ๋ณต์ ์ธ ํ์ต๊ณผ ๋ชฐ์ ๋๋ถ์ ํ ๊ฐ์ง ํฐ ๊นจ๋ฌ์์ ์ป์๋ค.
๋ด๊ฐ ์ค์ค๋ก์ ์๊ฐ์ ์ด๋ป๊ฒ ํ์ฉํ๊ณ , ์ด๋ค ๋ฐฉ์์ผ๋ก ์ ์งํ ์ ์์์ง ๊ฐ์ด ์กํ๋ค.
'๊พธ์คํจ'์ด์ผ๋ง๋ก ์ฑ์ฅ์ ์ง๋ฆ๊ธธ์์ ๊นจ๋ฌ์ ์ ์์๋ค.
์๋ฃ ์ดํ
1. ์ธ๊ฐ์ ๋ง๊ฐ์ ๋๋ฌผ, 10์ฃผ ๊ณผ์ ์ ๋ณต์ตํ์!
๋ง์ง๋ง ์๋ฃ์ ๋ , ์ฝ์น๋๊ป์ "์๋ก์ด ๊ฒ์ ๋ฐฐ์ฐ๊ธฐ ์ด์ ์ ์ด๋ฏธ ๋ฐฐ์ด ๋ด์ฉ์ ํ์คํ ๋ด ๊ฒ์ผ๋ก ๋ง๋ค์ด์ผ ํ๋ค"๊ณ ๋ง์ํ์
จ๋ค.
๊ทธ๋ฌ๊ณ ๋ณด๋ ์๊ฐ์ด ์ง๋๋ฉด์ ๋ฒ์จ 1์ฃผ์ฐจ ๋ด์ฉ์ด ๊ฐ๋ฌผ๊ฐ๋ฌผํด์ง๊ณ ์๋ค. ๋ค์ ํ๋ฒ ๋ณต์ตํ๋ฉฐ, ๊ทธ ๋น์ ์ด๋ค ๊ณ ๋ฏผ์ ํ๊ณ , ์ด๋ป๊ฒ ๊ฐ์ ํ๋์ง ์ ๋ฆฌํด ๋๊ฐ ํ์๊ฐ ์์๋ค.
์ด๋ฅผ ์ํด ์ด ๊ธ์ ๋ง์ง๋ง์ ์ฃผ์ฐจ๋ณ ํ๊ณ ๋ฅผ ์ถ๊ฐํด ๋ณต์ต ์๋ฃ๋ก ํ์ฉํ ์ ์๋๋ก ์์ฑํ์๋ค.
2. ์ฝ์น๋๋ค์๊ฒ ์ด๋ ฅ์ ํผ๋๋ฐฑ ๋ฐ๊ธฐ
์๋ฃ ์ดํ, 3๋ช ์ ์ฝ์น๋์ ์ฐพ์๊ฐ ์ด๋ ฅ์์ ๋ํ ํผ๋๋ฐฑ์ ์ง์ ๋ฐ์ ๊ธฐํ๊ฐ ์์๋ค.
29CM: ํํ์ฐ ์ฝ์น, ํ์ฌ ์ฝ์น
ํด๊ทผ ํ, ํ์๋ค๊ณผ ํจ๊ป ๋ฌด์ ์ฌ ์ฌ์ฅ์ผ๋ก ์ฐพ์๊ฐ ๋ ์ฝ์น๋์ ๋ง๋ฌ๋ค.
ํ์ฌ ๊ตฌ๊ฒฝ์ ๋ง์น ๋ค ๊ทผ์ฒ ์์์ ์์ ์ฝ์น๋๋ค์ ์ง์ํ ์ด์ผ๊ธฐ์ ์ฌ๊ณ ๋ฐฉ์์ ๋ค์ ์ ์์๋ค. ํนํ, ์ ์ ๋์ด์ ๋ฆฌ๋๋ก์ ์ญํ ์ ๋งก๊ฒ ๋ ๋ ๋ถ์ ๋น์ํ ์ฌ๊ณ ๋ฐฉ์์ด ์ธ์์ ์ด์๋ค.
์์ดํจ๋์ ์์ฑํ ์ด๋ ฅ์๋ฅผ ๊ฐ์ง๊ณ ์ฆ์์์ ์ปจํ์ ๋ฐ์ผ๋ฉฐ, ์์ ํด์ผ ํ ๋ถ๋ถ์ ๋ํด ๊ตฌ์ฒด์ ์ธ ํผ๋๋ฐฑ์ ๋ค์ ์ ์์ด ํฐ ๋์์ด ๋์๋ค.
์ฐ์ํํ์ ๋ค: ํ ํฌ ์ฝ์น
ํ ์์ผ ์ค์ 11์, ๊ฐ๋จ์์ ์ฝ์น๋๊ณผ ํฐํ์ ๋ฐ ์์ฌ๋ฅผ ํจ๊ปํ๋ค.
์ ์พํ๊ณ ๊ธ์ ์ ์ธ ์๋์ง๋ฅผ ๊ฐ์ง๊ณ ๊ณ์
์ ์ถ์ ์งํ์ ๊ฐ๋ฐ์๋ก์์ ๋ฐฉํฅ์ฑ์ ๋ํด ๋ง์ ์๊ฐ์ ์ป์๋ค.
์์ ๋ ์ด๋ ฅ์๋ฅผ ์ถ๊ฐ๋ก ๋ฆฌ๋ทฐ๋ฐ์ผ๋ฉฐ, ์ด๋ ฅ์ ์์ฑ์ ๊ตฌ์ฒด์ ์ธ ๋ฐฉํฅ์ ๋ ๋ช
ํํ ํ ์ ์์๋ค.
3. ๊ณ์๋๋ ์ํต๊ณผ ํ์ต ํ๊ฒฝ ๋ง๋ค๊ธฐ
์ฑ์ฅ์ ์์ด ๊ฐ์ฅ ์ค์ํ ์์๋ ์ง์์ ์ธ ์ํต๊ณผ ํ์ต ํ๊ฒฝ์ ์ ์งํ๋ ๊ฒ์ด๋ค. ์๋ฃ ํ์๋ ์ด ๋ถ์๊ธฐ๋ฅผ ์์ง ์๊ธฐ ์ํด ๋๊ธฐ๋ค๊ณผ ์๊ณ ๋ฆฌ์ฆ ์คํฐ๋๋ฅผ ์์ํ๊ธฐ๋ก ํ๋ค.
ํจ๊ป ์ฑ์ฅํ๋ฉฐ ํ์ตํ ์ ์๋ ํ๊ฒฝ์ ๋ง๋ค์ด๊ฐ๋ ๊ฒ์ด ์์ผ๋ก์ ๊ณผ์ ๋ค.
๋ง์น๋ฉฐ
์ ์ ์์ด ์ง๋๊ฐ 10์ฃผ. ํ๋ค์์ง๋ง ๋๊ธฐ๋ค๊ณผ ํจ๊ป์ฌ์ ์ฌ๋ฐ์๊ณ , ๊ธฐ์ต์ ๋จ๋ ์๊ฐ๋ค์ด ๋ง์๋ค.
์ด ๊ณผ์ ์ ๋ค๋ฅธ ์ฌ๋๋ค์๊ฒ ๊ฐ์ํ๊ณ ์ถ์ง ์์ง๋ง, ์๋ ์กฐ๊ฑด์ ํด๋นํ๋ ์ฌ๋(= ์๊ฐ ์ ์ ๋)์ด๋ผ๋ฉด ๊ผญ ๊ฒฝํํด๋ณด๊ธธ ์ถ์ฒํ๋ฉฐ ์ด ๊ธ์ ๋ง์น๋ค.
- ๋น์ ๊ณต์๋ก, ์ ๊ณต์ ๊ฐ๋ฐ์์ ์ํตํ๋ฉฐ ์์ ์ ์์ค์ ์ ๊ฒํ๊ณ ์ถ์ ์ฌ๋
- ํ์ฌ์ ๋ค๋๊ณ ์์ง๋ง ๋น์ทํ ์ฐ์ฐจ์ ๊ฐ๋ฐ์๋ค๊ณผ ๋น๊ตํด ์์ ์ ์ค๋ ฅ์ ํ์ธํด๋ณด๊ณ ์ถ์ ์ฌ๋
- ๋น ํ ํฌ ๊ธฐ์ ์ด๋ ๋ค๋ฅธ ๋ถ์ผ์ ๊ธฐ์ ์ผ๋ก ์ด์ง์ ํฌ๋งํ๋, ํ์ฌ ํ์ฌ์ ๊ธฐ์ ์คํ์ด ๋ฌ๋ผ ๊ณ ๋ฏผ ์ค์ธ ์ฌ๋
- ํผ์ ๊ณต๋ถ ์ค์ด์ง๋ง ์ด๋์๋ถํฐ ์์ํด์ผ ํ ์ง ๋ชฐ๋ผ ๋ง๋งํ ์ฌ๋
๋ณ์ฒจ: ์ฃผ์ฐจ ๋ณ ํ๊ณ
๋์ค์ 10์ฃผ ๊ณผ์ ๋ ์ด๋ค ๊ฑธ ๋ฐฐ์ฐ๊ณ ๋๊ผ๋ ์ง ๊ธฐ์ตํ๊ธฐ ์ํด ์ฃผ์ฐจ๋ณ๋ก ํค์๋์ ๊ณ ๋ฏผํ๋ ์ ๋ค์ ์ ๋ฆฌํด๋ณด์๋ค.
1์ฃผ์ฐจ
์ฃผ์
ํฌ์ธํธ ์ถฉ์ , ์ฌ์ฉ์ ๋ํ ๋์์ฑ ์ฒ๋ฆฌ์ ํตํฉ ํ ์คํธ
ํค์๋
TDD, Lock, ํ ์คํธ ์ฝ๋
ํ์ต๋ด์ฉ
1. ReentrantLock์ ์ฌ์ฉํ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ Lock ์ ์ฉ
์ฒซ ๊ณผ์ ์์๋ ํฌ์ธํธ ์ถฉ์ , ์ฌ์ฉ ์ ๋ฐ์ํ ์ ์๋ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Lock์ ๋์ ํ๋ ๊ณผ์ ๊ฐ ์ฃผ์ด์ก๋ค.
์ด ๊ณผ์ ์์๋ ๋ฉํฐ ์ธ์คํด์ค๋ฅผ ๊ณ ๋ คํ์ง ์์๋ ๋์๊ธฐ์, DB ๋ ๋ฒจ Lock ๋์ Application ๋ ๋ฒจ์ Lock์ ์ฌ์ฉํด ๋์ ์ ๊ทผ์ ์ ์ดํ๋ค. ์ด๋ฅผ ํตํด ๋์์ฑ ์ฒ๋ฆฌ์ ํ๋ฆ๊ณผ ์๋ ์๋ฆฌ๋ฅผ ์ดํดํ ์ ์์๋ค.
์๋ฅผ ๋ค์ด, ์๋๋ ReentrantLock์ ํ์ฉํ ๋์์ฑ ์ฒ๋ฆฌ ์ฝ๋์ด๋ค.
@Component
class UserLockManager : IUserLockManager {
private val lockMap: ConcurrentHashMap<Long, ReentrantLock> = ConcurrentHashMap()
override fun <T> executeWithLock(
userId: Long,
action: () -> T,
): T {
val lock = lockMap.computeIfAbsent(userId) { ReentrantLock() }
if (!lock.tryLock(10, TimeUnit.SECONDS)) {
throw LockAcquisitionException("๋ฝ ํ๋์ ์คํจํ์ต๋๋ค. userId: $userId")
}
return try {
action()
} finally {
lock.unlock()
}
}
}
์ด ๋์์ฑ ์ฒ๋ฆฌ์ ๋ํด ๋ฌธ์๋ก ์์ฑํ ์๋ฃ๋ฅผ ์ฝ์น๋๊ป ํผ๋๋ฐฑ์ ๋ฐ์ ๊ธฐํ๊ฐ ์์๋ค.
"๋์์ฑ ์ด์์ ๋ํ ๊ณต๋ถ๋ฅผ ๋ง์ด ํ์ จ๋ค์. ์๋ฃ๊ฐ ๋ณด๊ธฐ ์ข์์ ์ดํด๊ฐ ์์ ๋ฉ๋๋ค."
- ๋ฒํทํ๋ ์ด์ค ์ด์๋ฒ ์ฝ์น๋
์๋์ด ๊ฐ๋ฐ์๋ก๋ถํฐ ์ด๋ฐ ํผ๋๋ฐฑ์ ๋ค์ ์ ์์๋ ๊ฑด ๊ฐ์ง ๊ฒฝํ์ด์๋ค. ์ด ๊ณผ์ ์์ ์์ฑํ๋ ์๋ฃ๋ ๋ค๋ฌ์ด์ ๋ธ๋ก๊ทธ์ ์ฌ์์ฑํ๋ค.
์๋๋ ๋ธ๋ก๊ทธ๋ก ์ฎ๊ธด ๊ธ ๋งํฌ์ด๋ค
๋์์ฑ ์ฒ๋ฆฌ ์ฝ๊ฒ ์ดํดํ๊ธฐ (synchronized, reentrantLock)
1. ๋์์ฑ ์ฒ๋ฆฌ๋ ์ ํ๋๊ฑธ๊น?๋์์ฑ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์ผ๋ฉด ์ ์ ๊ฐ ํฌ์ธํธ๋ฅผ ๋์์ ์ถฉ์ ๋๋ ์ฌ์ฉํ ๋ ๋ฐ์ดํฐ ๋ถ์ผ์น ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ฉด์ ๋ฐ์
eastshine12.tistory.com
2. ํ ์คํธ ๋๋ธ์ ์ฌ์ฉํ ๋จ์ ํ ์คํธ ์์ฑ
๋จ์ ํ ์คํธ ์์ฑ์๋ ํ ์คํธ ๋์ ๋ฉ์๋์ ๋ ๋ฆฝ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด Mock๊ณผ Stub ๊ฐ์ฒด๋ฅผ ํ์ฉํ๋ค.
๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๊ธฐ์ค์ ํ ์คํธ ๋์ ๋ฉ์๋๊ฐ ์์ฒด ๋ก์ง์ด ์๋์ง ์ฌ๋ถ๋ก ํ๋จํด ๋ณผ ์ ์๋ค. ์ธ๋ถ ์์กด์ฑ์ ๋ฐฐ์ ํ๊ณ ์์ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฒ์ฆํ๋ ๊ฒ์ด ํต์ฌ์ด์๋ค.
์๋ฅผ ๋ค์ด, ์๋ checkUserExists ๋ฉ์๋๋ ์ ์ ๋ฅผ ์ฐพ์ง ๋ชปํ๋ฉด ์์ธ๋ฅผ ๋์ง๋ ๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํ๊ณ ์๋ค
fun checkUserExists(userId: Long): User {
return userRepository.findByIdOrNull(userId)
?: throw CoreException(
errorType = ErrorType.USER_NOT_FOUND,
details =
mapOf(
"userId" to userId,
),
)
}
์ ๋ก์ง์ ํ ์คํธํ ๋๋ ์ธ๋ถ ์์กด์ฑ์ธ userRepository๋ฅผ Mock์ผ๋ก ๋์ฒดํด ์์ ๋น์ฆ๋์ค๋ฅผ ๊ฒ์ฆํ ์ ์๋ค.
class UserServiceTest {
private val userRepository = mockk<UserRepository>(relaxed = true)
private val balanceHistoryRepository = mockk<BalanceHistoryRepository>(relaxed = true)
private val userService = UserService(userRepository, balanceHistoryRepository)
@Test
fun `should return user by id`() {
// given
val userId = 1L
val user = mockk<User>()
every { userRepository.findByIdOrNull(userId) } returns user
// when
val result = userService.checkUserExists(userId)
// then
assertEquals(user, result)
}
@Test
fun `must throw exception when user not found`() {
// given
val userId = 1L
every { userRepository.findByIdOrNull(userId) } returns null
// when / then
assertThrows<CoreException> {
userService.checkUserExists(userId)
}
}
}
๊ณ ๋ฏผ ํฌ์ธํธ
1. ๋จ์ ํ ์คํธ ํตํฉ ํ ์คํธ์ ์์ฑ ๋ฒ์
- ๋จ์ ํ ์คํธ: ๋ฉ์๋ ์์ค์ ์ธ๋ถ์ ์ธ ๋ก์ง ๊ฒ์ฆ → ํ์ดํธ๋ฐ์ค ํ ์คํธ
- ํตํฉ ํ ์คํธ: ์ฌ๋ฌ ์๋น์ค ๊ฐ์ ๊ฒฐํฉ์ ๊ฒ์ฆํ๋ฉฐ ์ ์ฒด usecase๋ฅผ ํ์ธ → ๋ธ๋๋ฐ์ค ํ ์คํธ
2. ์ข์ ํ ์คํธ ์ฝ๋๋?
- ํ ์คํธ ์ฝ๋๋ ์๊ตฌ์ฌํญ์ ๋ช ํํ ๋์ดํ ๋ฌธ์์ ๊ฐ๋ค.
- ํ ์คํธ ์์ฑ์ด ์ด๋ ค์ด ์ฝ๋๋ ์ค๊ณ์ ๋ฌธ์ ์ผ ๊ฐ๋ฅ์ฑ์ด ๋๋ค. ํ ์คํธ ์ฝ๋ ์์ฑ์ ํตํด ์์ฐ์ค๋ฝ๊ฒ ๊ฐ์ฒด์งํฅ ์ค๊ณ๋ก ์ด์ด์ง ์ ์์์ ์ฒด๊ฐํ๋ค.
3. ํ ์คํธ ์ฝ๋ ๋ค์ด๋ฐ
- Kotlin์์๋ ๋ฐฑํฑ(`)์ ํ์ฉํด ๋ฉ์๋๋ช ์ ์ฝ๊ฒ ์์ฑํ ์ ์๋ค.
- ์ฝ์น๋๋ค๋ง๋ค ์ ํธํ๋ ๋ฐฉ์์ด ๋ฌ๋์ง๋ง, must, should์ ๊ฐ์ ๊ฐํ ์ด์กฐ๋ฅผ ์ ํธํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
- ํ ์คํธ ์ฝ๋๊ฐ ์๊ตฌ์ฌํญ์ ์ค๋ช ํ๋ ๋ฌธ์๋ผ๋ ์ ์์, ํ๊ธ๊ณผ ์์ด ์ค ์ด๋ ์ชฝ์ด๋ ๋ช ํํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ด ํํ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ค์ํ๋ค๊ณ ๋๊ผ๋ค.
์ถ๊ฐ
1. ๋น์ ํ๊ณ ๊ธ
TDD, ๋์์ฑ ์ฒ๋ฆฌ - ํญํด ํ๋ฌ์ค ํ๊ณ (1์ฃผ์ฐจ)
๋ค์ด๊ฐ๋ฉฐ ๋๋์ด 1์ฃผ ์ฐจ๊ฐ ๋๋ฌ๋ค..! (์ด์ 10ํผ ํ๋ค..) ์ ๋ง ์ง๋ ํ ์ฃผ๋ ๋ด ๋จธ๋ฆฌ๋ก ๋ค์ด์ค๋ ์ธํ ๋ฐ์ดํฐ๊ฐ ์ด๋ง์ด๋งํ ์ผ์ฃผ์ผ์ด์๋ค.์ง๋ ํ ์์ผ, ์คํ๋ผ์ธ ์ธ์ ๋ชจ์์์ 10์ฃผ๋์ ํจ๊ป ํ
eastshine12.tistory.com
2. ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/hhplus-tdd-jvm: Point ์๋น์ค / ํญํด ํ๋ฌ์ค 1์ฃผ์ฐจ / TDD & Concurrency Control
Point ์๋น์ค / ํญํด ํ๋ฌ์ค 1์ฃผ์ฐจ / TDD & Concurrency Control - eastshine12/hhplus-tdd-jvm
github.com
2์ฃผ์ฐจ
์ฃผ์
์ ์ฐฉ์ ํน๊ฐ์ ์ฒญ ์๋น์ค
ํค์๋
ํด๋ฆฐ ์ํคํ ์ฒ, ๋ ์ด์ด๋ ์ํคํ ์ฒ, DB Lock
ํ์ต๋ด์ฉ
1. ํด๋ฆฐ ์ํคํ ์ฒ์ ๋ ์ด์ด๋ ์ํคํ ์ฒ๋ฅผ ์ ์ฉ
ํน๊ฐ ์ ์ฒญ ์๋น์ค ๊ตฌํ ๊ณผ์ ์์ ํด๋ฆฐ ์ํคํ
์ฒ์ ๋ ์ด์ด๋ ์ํคํ
์ฒ๋ฅผ ์ ์ฉํ๋ค.
์ด๋ฅผ ํตํด ๋๋ฉ์ธ ๋ ์ด์ด, ์ ํ๋ฆฌ์ผ์ด์
๋ ์ด์ด, ์ธํฐํ์ด์ค ๋ ์ด์ด ๊ฐ์ ๋จ๋ฐฉํฅ ์์กด์ฑ์ ์ ์งํ๋ฉฐ ๊ฐ ๊ณ์ธต์ ์ญํ ์ ๋ช
ํํ ๋ถ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ตํ๋ค.
์ธํ๋ผ์คํธ๋ญ์ฒ ๋ ์ด์ด์์๋ JPA ๋ ํ์งํ ๋ฆฌ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด ๋๋ฉ์ธ์ ์์กด๋์ง ์๋ ํ์์ ์ทจํ๋ค.
2. DB ๋ ๋ฒจ์์์ Lock์ ๊ตฌํ
์ ์ฐฉ์์ผ๋ก ํน๊ฐ ์ ์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด DB Lock์ ์ฌ์ฉํ๋ค.
๋๊ด์ ๋ฝ๊ณผ ๋น๊ด์ ๋ฝ์ ์ฅ๋จ์ ์ ๋ถ์ํ ๊ฒฐ๊ณผ, ์๊ตฌ์ฌํญ์์ ๋ช ์๋ ์ ์ฐฉ์ ์์คํ ์ ๊ตฌํํ๊ธฐ ์ํด ํธ๋์ญ์ ์ ์์ฐจ ์ฒ๋ฆฌ๊ฐ ๋ณด์ฅ๋๋ ๋น๊ด์ ๋ฝ์ ์ ํํ์๋ค.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT l FROM Lecture l WHERE l.id= :lectureId")
fun findByIdOrNullWithLock(lectureId: Long): Lecture?
๊ณ ๋ฏผ ํฌ์ธํธ
1. ๊ณ์ธต๊ฐ์ DTO ๋ถ๋ฆฌ
๊ณ์ธต ๊ฐ์ ๋จ๋ฐฉํฅ ์์กด์ฑ์ ์ ์งํ๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ DTO๋ฅผ ๊ฐ ๊ณ์ธต๋ณ๋ก ์ ์ํ๋ค.
- ๊ณ ๋ฏผ
๋๋ฉ์ธ ๋ ์ด์ด์์ ์ ์ํ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์ ํ๋ฆฌ์ผ์ด์ ๋ ์ด์ด์ ์ธํฐํ์ด์ค ๋ ์ด์ด์์๋ ์ฌ์ฉํ ์ ์์ง ์์๊น? ๊ณ์ธต๋ง๋ค DTO๋ฅผ ๋ฐ๋ก ์์ฑํด์ผ ํ๋ ์ด์ ๊ฐ ๋ฌด์์ธ์ง ๊ถ๊ธํ๋ค. - ํด๊ฒฐ
๊ฐ ๋ ์ด์ด์ ์ญํ ๊ณผ ์ฑ ์์ ๋ง๊ฒ DTO๋ฅผ ๋ถ๋ฆฌํด์ผ ํ๋ค๋ ๊ฒฐ๋ก ์ ๋๋ฌํ๋ค.- ๋๋ฉ์ธ ๋ ์ด์ด: ๋น์ฆ๋์ค ๋ก์ง์ ๋ค๋ฃจ๋ ํ๋ถํ ๊ฐ์ฒด
- ์ ํ๋ฆฌ์ผ์ด์ ๋ ์ด์ด: ๋น์ฆ๋์ค ๋ก์ง์ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณต
- ์ธํฐํ์ด์ค ๋ ์ด์ด: HTTP ์๋ต์ ํ์ํ ๋ฐ์ดํฐ๋ง ํฌํจ
- ๊ฒฐ๋ก : ๊ฐ ๊ณ์ธต๋ง๋ค DTO๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒ์ด ์ค๊ณ์ ๋ช ํ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ธ๋ค.
2. TDD์ ํ์ฉ ๋น๋
TDD(Test-Driven Development)๋ ๋ก์ง์ ์์ ์ฑ๊ณผ ์ค๊ณ์ ๊ฒฌ๊ณ ์ฑ์ ๋์ด์ง๋ง, ๋ชจ๋ ์ํฉ์์ ์ ์ฉํ๊ธฐ๋ ์ฝ์ง ์์๋ค.
- ๊ณ ๋ฏผ
๋ณต์กํ ๋ก์ง์ ์์ฑํ ๋๋ ๋ก์ง ๊ตฌํ ์์ฒด๊ฐ ์ด๋ ค์ TDD๋ฅผ ์ ์ฉํ ์ฌ์ ๊ฐ ์์๋ค. ๊ฒฐ๊ตญ ๋จ์ํ ๋ก์ง์๋ง TDD๋ฅผ ์ ์ฉํ๊ณ , ๋ณต์กํ ๋ก์ง์ TLD(Test Last Development) ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ฒ ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค. - ํด๊ฒฐ
์คํ๋ ค ๋ณต์กํ ๋ก์ง์ผ์๋ก TDD์ ์๋ ฅ์ ๋ฐํํ๋ค๋ ์ฌ์ค์ ๊นจ๋ฌ์๋ค.- ์์ํ๊ฑฐ๋ ๋ณต์กํ ๋ก์ง์ผ์๋ก, ์๊ตฌ์ฌํญ์ ํ ์คํธ๋ก ์ ์ํ๋ฉฐ ๊ตฌํํ๋ฉด ๋์ฑ ์์ ์ ์ธ ์ฝ๋๋ฅผ ๋ง๋ค ์ ์๋ค.
- ๋ฐ๋ฉด, ๋จ์ CRUD์ ๊ฐ์ ์ต์ํ ๋ก์ง์์๋ TLD ๋ฐฉ์์ ํ์ฉํ ์๋ ์๋ค.
- ๊ฒฐ๋ก : TDD๋ ์ํฉ์ ๋ฐ๋ผ ์ ์ฌ์ ์์ ํ์ฉํ๋ ๊ฒ์ด ์ค์ํ๋ค.
์ถ๊ฐ
๋น์ ํ๊ณ ๊ธ
Clean Architecture - ํญํด ํ๋ฌ์ค ํ๊ณ (2์ฃผ์ฐจ)
๋ค์ด๊ฐ๋ฉฐ ์ด๋ฒ 2์ฃผ ์ฐจ ํญํด๋ ๋ฌด์ฌํ ๋ง์ณค๋ค. ์ด๋ฒ ๊ณผ์ ์์๋ ์ํคํ ์ฒ ๊ตฌ์กฐ ์ค๊ณ์ ๋ํ ๋ด์ฉ์ ์ค์ ์ ์ผ๋ก ๊ณต๋ถํ์๋ค.๊ทธ๋์ ์ฃผ๋ก 3-tier ์ํคํ ์ฒ๋ฅผ ์ฃผ๋ก ์ฌ์ฉํด ์์๋๋ฐ, ์ด๋ฒ์๋ํด๋ฆฐ ์
eastshine12.tistory.com
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/hhplus-lecture: ํน๊ฐ ์ ์ฒญ ์๋น์ค / ํญํด ํ๋ฌ์ค 2์ฃผ์ฐจ / Clean Architecture
ํน๊ฐ ์ ์ฒญ ์๋น์ค / ํญํด ํ๋ฌ์ค 2์ฃผ์ฐจ / Clean Architecture. Contribute to eastshine12/hhplus-lecture development by creating an account on GitHub.
github.com
3~5์ฃผ์ฐจ
๋ ์ ์ ๋ฆฌํด๋ ํ๊ณ ๊ธ๋ก ๋์ฒด
ํ๊ณ ๊ธ
Chapter 2๋ฅผ ๋์๋ณด๋ฉฐ - ํญํด ํ๋ฌ์ค ํ๊ณ (5์ฃผ์ฐจ)
๋ค์ด๊ฐ๋ฉฐ๋ฒ์จ ํญํด๋ฅผ ์์ํ๊ณ 10์ฃผ ๊ณผ์ ์ ์ ๋ฐ ์ง์ ์ ๋์ฐฉํ๋ค. ๋ณธ์ ๊ณผ ํจ๊ป ๊ฐ๋ ๋์ ๊ณผ์ ๋ฅผ ์ํํ๋ ๊ฒ์ด ์ฝ์ง๋ ์์์ง๋ง, ์ ๋ฐ์ด ์ง๋ฌ๋ค๋ ์ ์์ ๋จ์ ์ ๋ฐ๋ ๊ธ๋ฐฉ ์ ํด๋ผ ์ ์์ ๊ฒ
eastshine12.tistory.com
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com
6์ฃผ์ฐจ
์ฃผ์
์๋๋ฆฌ์ค ๋ด์ ๋ฐ์ํ ์ ์๋ ๋์์ฑ ์ด์ ์ ์ด ๋ฐฉ์ ๋น๊ต ๋ฐ ์ ์
ํค์๋
x-lock, ๋๊ด์ ๋ฝ, ๋น๊ด์ ๋ฝ, ๋ถ์ฐ ๋ฝ
ํ์ต๋ด์ฉ
1. ๋์์ฑ ์ด์๊ฐ ๋ฐ์ํ๋ ๋ก์ง์์ ๋๊ด์ ๋ฝ, ๋น๊ด์ ๋ฝ, ๋ถ์ฐ ๋ฝ ์ ์ฉ
์ฝ์ํธ ์์ฝ ์๋น์ค์์ Lock ์ฑ๋ฅ ๋น๊ตํด๋ณด๊ธฐ (feat. ๋๊ด์ ๋ฝ, ๋น๊ด์ ๋ฝ, ๋ถ์ฐ ๋ฝ)
๋ค์ด๊ฐ๋ฉฐ์ฝ์ํธ ์์ฝ ์๋น์ค์ ๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋์์ฑ ์ฒ๋ฆฌ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ์ข์ ์๋งค ์คํ ์ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ์์ฝ์ ์๋ํ๋ฉด, ์์์ด ์์ ํ๊ฒ ๊ด๋ฆฌ๋์ง ์์ ๊ฒฝ์ฐ ์์ฝ
eastshine12.tistory.com
๊ณ ๋ฏผ ํฌ์ธํธ
1. ํ ์คํธ ํ๊ฒฝ ์ค์
DB ๋ฝ(H2)๊ณผ Redis๋ฅผ ์ฌ์ฉํ ๋ถ์ฐ ๋ฝ์ ์ฑ๋ฅ ๋น๊ต๋ฅผ ์งํํ๋ ค๊ณ ํ์ผ๋, ํ ์คํธ ํ๊ฒฝ์ ์ฐจ์ด๋ก ์ธํด ๊ณ ๋ฏผ์ด ์๊ฒผ๋ค.
- H2 DB ๋ฝ: ์คํ๋ง ๋ถํธ์ ๋์ผ ํ๋ก์ธ์ค์์ ์คํ๋์ด ๋คํธ์ํฌ๋ฅผ ํ์ง ์๋๋ค.
- Redis ๋ถ์ฐ ๋ฝ: ๋ก์ปฌ ์ปจํ ์ด๋์์ ์คํ๋๋ฉฐ, ๋คํธ์ํฌ๋ฅผ ํ๋ ๋ฐฉ์์ด๋ค.
์ด์ฒ๋ผ ํ ์คํธ ํ๊ฒฝ์ด ์์ดํ๋ฉด ์ฑ๋ฅ ๋น๊ต๊ฐ ๊ณต์ ํ์ง ์์ ์ ์์ผ๋ฏ๋ก, ๋ ํ๊ฒฝ ๋ชจ๋ ์ปจํ ์ด๋๋ก ๋์ ๋์ผํ ์กฐ๊ฑด์ ๊ตฌํํ๋ ค๊ณ ํ๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก, DB์ Redis ๋ชจ๋ ์ปจํ ์ด๋ ํ๊ฒฝ์์ ์คํํ์ฌ ํ ์คํธ ํ๊ฒฝ์ ์ผ๊ด๋๊ฒ ์ค์ ํ๋ค.
2. ๋ถ์ฐ๋ฝ AOP์ @Transactional ์ ์คํ ์์
๋ถ์ฐ ๋ฝ AOP๋ฅผ ์ ์ฉํ๊ธฐ ์ํด ์ด๋ ธํ ์ด์ ์ ์์ฑํ๊ณ ํธ๋์ญ์ ์ด ์ ์ฉ๋ ๋ฉ์๋์ ๋ถ์์ผ๋, ํธ๋์ญ์ ์ด ์์๋ ์ดํ์ ๋ฝ์ด ๊ฑธ๋ฆฌ๋ ๋ฌธ์ ์ ์ง๋ฉดํ๋ค. ์ด๋ ๋์์ฑ์ ๋ณด์ฅํ์ง ๋ชปํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ๋ค.
๊ทธ ์ด์ ๋, @Transactional์ ๋ฉ์๋ ์ง์
์์ ์์ ์ด๋ฏธ DB ์ปค๋ฅ์
๊ณผ ํธ๋์ญ์
์ ์์ํ๋ค.
๋ง์ฝ ๋ถ์ฐ๋ฝ AOP๊ฐ ๊ทธ๋ณด๋ค ๋ฆ๊ฒ ์คํ๋๋ค๋ฉด, ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฑฐ์ ๋์์ ํธ๋์ญ์
์ ์์ํ๊ณ , ์ดํ์์ผ ๋ฝ์ ํ๋ํ๋ ์ํฉ์ด ๋ฐ์ํ๋ค. ์ด๋ก ์ธํด DB ์ํ ์กฐํ๋ ์ผ๋ถ ๋ณ๊ฒฝ์ด ๋ฝ ์์ด ์งํ๋ ์ ์์ด ์์ ํ ๋์์ฑ ์ ์ด๊ฐ ์ด๋ ต๋ค.
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋ ์ธ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์๋ํ๋ค.
- ๋ฐฉ๋ฒ 1: ํธ๋์ญ์ ๋ฉ์๋ ์์(e.g., Facade)์์ Lock์ ๊ฑฐ๋ ๋ฐฉ์
- ๋ฐฉ๋ฒ 2: Lock AOP์ @Order๋ฅผ ๋ฎ์ ๊ฐ์ผ๋ก ์ค์ ํด, ํธ๋์ญ์ ๋ณด๋ค ๋จผ์ ์คํ๋๋๋ก ํ๋ ๋ฐฉ์
- ๋ฐฉ๋ฒ 3: Lock AOP ๋ด๋ถ์์ REQUIRES_NEW ํธ๋์ญ์ ์์ฑ์ ์ฌ์ฉํด, ๋ ๋ฆฝ์ ์ธ ํธ๋์ญ์ ์ผ๋ก ์คํ๋๋๋ก ์ค์ ํ๋ ๋ฐฉ์
์ต์ข ์ ์ผ๋ก ๋ฐฉ๋ฒ 2๋ฅผ ์ ํํ๋ค.
Lock AOP์ @Order๋ฅผ ๋ฎ๊ฒ ์ค์ ํ๋ฉด, ์คํ๋ง ํ๋ ์์ํฌ์ ์ด๋๋ฐ์ด์ค ์คํ ์์ ๊ท์น์ ๋ฐ๋ผ ํธ๋์ญ์
๋ณด๋ค ๋จผ์ ์คํ๋๊ธฐ ๋๋ฌธ์ด๋ค.
- @Transactional:
Ordered.LOWEST_PRECEDENCE
๋ก ์ค์ ๋์ด ์์ด, ๋ชจ๋ AOP ์ด๋๋ฐ์ด์ค ์ค ๊ฐ์ฅ ๋ง์ง๋ง์ ์คํ๋๋ค. - Lock AOP: @Order๋ฅผ
Ordered.HIGHEST_PRECEDENCE
์ผ๋ก ์ค์ ํ๋ฉด, ํธ๋์ญ์ ๋ณด๋ค ๋จผ์ ์คํ๋ ์ ์๋ค.
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com
7์ฃผ์ฐจ
์ฃผ์
Redis๋ฅผ ์ฌ์ฉํ ์บ์ ์ ์ฉ ๋ฐ ๋๊ธฐ์ด ๋ก์ง ์ด๊ด
ํค์๋
Redis, ์บ์, ๋๊ธฐ์ด
ํ์ต๋ด์ฉ
1. ์บ์ ์ ์ฉ ๊ธฐ์ค์ ์ธ์ฐ๊ณ ์ค์ ๋ก์ง์ ์บ์ ์ ์ฉ ๋ฐ ๋๊ธฐ์ด ๋ก์ง์ Redis๋ก ์ด๊ด
Redis ๊ธฐ๋ฐ์ ์บ์ฑ ๋ฐ ๋๊ธฐ์ด ๊ด๋ฆฌ๋ฅผ ํตํ ์ฝ์ํธ ์์ฝ ์๋น์ค ์ฑ๋ฅ ๊ฐ์
๋ค์ด๊ฐ๋ฉฐ์ด๋ฒ ์๊ฐ์๋ ์ฝ์ํธ ์์ฝ ์๋น์ค์ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ธฐ ์ํด ํ์ฌ ์๋๋ฆฌ์ค์ ์กฐํ API ์ค ์บ์ฑ์ ์ ์ฉํ ๋ถ๋ถ์ ๋ํด ๊ณ ๋ฏผํด ๋ณด๊ณ , ๊ธฐ์กด RDB์์ ์๋๋๊ณ ์๋ ๋๊ธฐ์ด ๋ก์ง์ Redis๋ก ์ด
eastshine12.tistory.com
๊ณ ๋ฏผ ํฌ์ธํธ
1. Redis ๋๊ธฐ์ด ๊ด๋ฆฌ ๋ฐฉ์
๋๊ธฐ์ด์ WaitingToken(๋๊ธฐ ํ ํฐ)๊ณผ ActiveToken(์์ฝ ๊ฐ๋ฅํ ํ์ฑ ํ ํฐ)์ผ๋ก ๋๋์ด Sorted Set(ZSet)์ผ๋ก ๊ด๋ฆฌํ๋๋ฐ, ๊ฐ ํ ํฐ ์ํ ์กฐํ ์ ๋ ๊ฐ์ ZSet์ ๋ชจ๋ ํ์ํด์ผ ํ๋ ๋นํจ์จ์ด ๋ฐ์ํ๋ค.
- ํด๊ฒฐ
๊ฐ ํ ํฐ ์ ๋ณด๋ฅผ Hash ๊ตฌ์กฐ๋ก ์ ํํ์ฌ, ํ ํฐ์ key๋ก ํ๊ณ ์ฝ์ํธ ์ค์ผ์ค ID, ์ํ, ๋ง๋ฃ์ผ์ ๋ฑ์ value๋ก ์ ์ฅํ๋ค. ์ด๋ฅผ ํตํด ๋จ์ผ ์กฐํ๋ก ํ์ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์๊ฒ ํ๋ค.
- ๊ฒฐ๋ก
Hash๋ฅผ ํ์ฉํ ์ค๊ณ ๋ณ๊ฒฝ์ผ๋ก ํ ํฐ ์ํ ์กฐํ ์ฑ๋ฅ์ด ํฅ์๋์ด, ๋๊ธฐ์ด ๊ด๋ฆฌ๊ฐ ๋์ฑ ํจ์จ์ ์ผ๋ก ์ด๋ฃจ์ด์ก๋ค.
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com
8์ฃผ์ฐจ
์ฃผ์
์์ฃผ ์กฐํ๋๋ ์ฟผ๋ฆฌ๋ฅผ ๋ถ์ํ๊ณ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ์ฑ๋ฅ ๊ฐ์
ํค์๋
์ธ๋ฑ์ค
ํ์ต๋ด์ฉ
1. ์ข์ ์กฐํ ์ฟผ๋ฆฌ์ ์ฌ๋ฌ ๋ฐฉ์์ ์ธ๋ฑ์ค๋ฅผ ๊ฑธ์ด๋ณด๊ณ ์ต์ ์ ์ธ๋ฑ์ค ์ฌ์ฉ
ํด๋น ์ฃผ์ฐจ์ ๊ด๋ จ ๋ด์ฉ์ ์๋ ๊ธ์ ์์ฑํ์๋ค.
์ฝ์ํธ ์์ฝ ์๋น์ค์ ์ธ๋ฑ์ค ์ค๊ณ์ ์ฑ๋ฅ ๋น๊ต
๋ค์ด๊ฐ๋ฉฐ ์ด๋ฒ ๊ธ์์ ์ฝ์ํธ ์์ฝ ์๋น์ค์ ์๋๋ฆฌ์ค์์ ์ฌ์ฉ๋ ์ฟผ๋ฆฌ๋ฅผ ๋ถ์ํ๊ณ , ์ธ๋ฑ์ค ์ถ๊ฐ ์ ํ์ ์ฑ๋ฅ์ ๋น๊ตํด ๋ณด๋ฉฐ ์ต์ ํ ๊ณผ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. MariaDB๋ฅผ ์ฌ์ฉํด ์ธ๋ฑ์ค ์ถ๊ฐ ์ ํ
eastshine12.tistory.com
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com
9์ฃผ์ฐจ
์ฃผ์
Docker๋ฅผ ํ์ฉํ Kafka ์ปจํ ์ด๋๋ฅผ ๊ตฌ์ฑ ๋ฐ ์ด๋ฒคํธ ๊ธฐ๋ฐ ๋ก์ง ์ ํ
ํค์๋
Docker, Kafka, Transactional Outbox Pattern, TestContainers
ํ์ต๋ด์ฉ
1. ์์ฝ ์์ฑ ๋ก์ง์ Kafka ์ด๋ฒคํธ ๋ฐํ ๊ตฌ์กฐ๋ก ์ ํ
- ๊ธฐ์กด ์ง์ ํธ์ถ ๊ตฌ์กฐ๋ฅผ ์ด๋ฒคํธ ๋ฐํ → ์๋น ํจํด์ผ๋ก ๊ฐ์
2. Transactional Outbox Pattern ์ ์ฉ
- Outbox ํ ์ด๋ธ์ ์ด๋ฒคํธ ๋ฐํ ๊ธฐ๋ก์ ๋จ๊ธฐ๊ณ , ์ฌ์ฒ๋ฆฌ ์ค์ผ์ค๋ฌ๋ก ๋ฐํ ์คํจ ์ด๋ฒคํธ๋ฅผ ์ฌ์ ์ก
- @TransactionalEventListener(BEFORE_COMMIT)๋ก ๋ฉ์ธ ํธ๋์ญ์ ๊ณผ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌ
@Component
class ReservationEventListener(
private val outboxRepository: OutboxRepository,
private val externalEventPublisher: ReservationExternalEventPublisher,
private val objectMapper: ObjectMapper,
) {
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
fun saveOutbox(event: ReservationEvent.Created) {
outboxRepository.save(
Outbox(
topic = "concert.reservation.created",
key = event.reservationId.toString(),
eventType = "RESERVATION_CREATED",
payload = objectMapper.writeValueAsString(event),
),
)
}
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
fun publishKafkaMessage(event: ReservationEvent.Created) {
externalEventPublisher.publish(
topic = "concert.reservation.created",
key = event.reservationId.toString(),
payload = event,
)
}
}
๊ณ ๋ฏผ ํฌ์ธํธ
1. ๋ก์ง ๋ถ๋ฆฌ
@Transactional
fun ๊ฒฐ์ ์ฒ๋ฆฌ(command): ๊ฒฐ์ ์ ๋ณด {
return runCatching {
val ์์ฝ์ ๋ณด = ์์ฝํ์ธ๋ฐํ์ ์ฒ๋ฆฌ(์์ฝID) // 1
val ์ข์์ ๋ณด = ์ข์์ ๋ณด๊ฒ์ฆ๋ฐ๊ฐ์ ธ์ค๊ธฐ(์์ฝ์ ๋ณด.์ข์ID) // 2
์ฌ์ฉ์์์ก์ฐจ๊ฐ(์ฌ์ฉ์ID, ์ข์์ ๋ณด.๊ฐ๊ฒฉ, USE) // 3
๋๊ธฐ์ดํ ํฐ๋ง๋ฃ์ฒ๋ฆฌ(ํ ํฐ) // 4
๊ฒฐ์ ์ด๋ ฅ์ ์ฅ(์ฌ์ฉ์ID, ์์ฝID, ์ข์์ ๋ณด.๊ฐ๊ฒฉ) // 5
}.getOrElse { ์์ธ ->
throw ์ต์
์
()
}
}
๊ฒฐ์ ๋ก์ง์์ ์์ฝ ํ์ (์์ฝํ์ธ๋ฐํ์ ์ฒ๋ฆฌ)๊ณผ ์ฌ์ฉ์ ์์ก ์ฐจ๊ฐ(์ฌ์ฉ์์์ก์ฐจ๊ฐ)์ ํ์ ๋ก์ง์ผ๋ก ๋ฐ๋์ ์ฑ๊ณตํด์ผ ํ๋ค.
๋ฐ๋ฉด, ๋๊ธฐ์ด ํ ํฐ ๋ง๋ฃ๋ ๊ฒฐ์ ์ด๋ ฅ ์ ์ฅ์ ์ต์ข
์ผ๊ด์ฑ์ ๋ง์ถ๊ธฐ ์ํ ๋ณด์กฐ ๋ก์ง์ด๋ฏ๋ก ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ก ๋ถ๋ฆฌํ ์ ์๋ค.
์ด๋, ์์ฝ ํ์ ๊ณผ ์์ก ์ฐจ๊ฐ ๊ฐ์ ํ์ ๋ก์ง์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง, ์คํจ ์ํฉ์์ ๋ณด์ฅ์ฑ์ ์ด๋ป๊ฒ ์ ์งํ ์ง ๊ณ ๋ฏผ์ด ํ์ํ๋ค.
ํด๊ฒฐ
- ๋ฐฉ๋ฒ 1: ์์ฝ ํ์ ๊ณผ ์์ก ์ฐจ๊ฐ์ ๊ฐ์ ํธ๋์ญ์
์ผ๋ก ์ฒ๋ฆฌ
- ๋ ๋ก์ง์ ํ๋์ ํธ๋์ญ์ ์์ ์ฒ๋ฆฌํ์ฌ ํ์ ๋ก์ง์ ์ฑ๊ณต์ ๋ณด์ฅํ๋ค.
- ๋๊ธฐ์ด ํ ํฐ ๋ง๋ฃ์ ๊ฒฐ์ ์ด๋ ฅ ์ ์ฅ์ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌํด ํธ๋์ญ์ ๋ฒ์๋ฅผ ์ค์ด๊ณ , ์คํจ ์ ์ฌ์ฒ๋ฆฌ๋ฅผ ์ค์ผ์ค๋ฌ๋ก ๋ณด์
- ๋ฐฉ๋ฒ 2: ๋ณด์ ํธ๋์ญ์
์ ์ฉ
- ์์ฝ ํ์ ๊ณผ ์์ก ์ฐจ๊ฐ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌํ๋, ์์ก ์ฐจ๊ฐ ๋ก์ง์ด ์คํจํ ๊ฒฝ์ฐ ์์ฝ ํ์ ์ฒ๋ฆฌ์ ๋ํด ๋ณด์ ํธ๋์ญ์ ์ ์คํ(e.g. ์์ฝ ์ํ๋ฅผ ์ทจ์๋ก ๋๋๋ฆผ)
- ์ด๋ฅผ ํตํด ์ ์ฒด ๋ก์ง์ ์์ ์ฑ๊ณผ ์ ์ฐ์ฑ์ ํ๋ณด
๊ฒฐ๋ก
๋ฐฉ๋ฒ 1์ ํ์ ๋ก์ง์ ์ฑ๊ณต์ ํ์คํ ๋ณด์ฅํ๋ ์์ ํ ์ ํ์ด์ง๋ง, ํธ๋์ญ์
๋ฒ์๊ฐ ๋์ด์ง ์ ์๋ค.
๋ฐฉ๋ฒ 2๋ ํธ๋์ญ์
๋ฒ์๋ฅผ ์ค์ด๊ณ ์ ์ฐ์ฑ์ ์ ๊ณตํ์ง๋ง, ๋ณด์ ํธ๋์ญ์
์ค๊ณ์ ์ด์์ ๋ณต์ก๋๊ฐ ๋์์ง ์ ์๋ค.
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com
10์ฃผ์ฐจ
์ฃผ์
์ ์ ํ๋ ๊ธฐ๋ฐ ๋ถํ ํ ์คํธ ๋ฐ ๊ฐ์ ์ฅ์ ๋์ ๋ฌธ์ ์์ฑ
ํค์๋
๋ถํ ํ ์คํธ, K6
ํ์ต๋ด์ฉ
1. ์ ์ ์๋๋ฆฌ์ค ๊ธฐ๋ฐ ๋ถํ ํ ์คํธ ์๋๋ฆฌ์ค ์์ฑ
- Docker๋ก Spring App, Kafka, Redis, MySQL, InfluxDB, Grafana ๋ฑ์ ๋์ฐ๊ณ ,
k6 ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ์ค์ ์ ์ ํ๋์ ๋ชจ๋ฐฉํ๋ ์๋๋ฆฌ์ค๋ฅผ ์คํ
- ์๋๋ฆฌ์ค 1: ํ ํฐ ๋ฐ๊ธ → ํ ํฐ ์ํ ์กฐํ
export default function () {
// ํ ํฐ ๋ฐ๊ธ
let token = getWaitingToken()
// ํ ํฐ ์ํ ์กฐํ
for (let i = 0; i < 5; i++) { // 5ํ ๋ฐ๋ณต
getQueueStatus(token)
sleep(1);
}
}
- ์๋๋ฆฌ์ค 2: ์ฝ์ํธ ์ข์ ์กฐํ → ์์ฝ → ๊ฒฐ์
์ด๋ฅผ ํตํด ์์คํ ํ๊ณ์น ํ์ ๋ฐ SLA/SLO ๊ฒ์ฆ
export default function () {
let currentId = exec.scenario.iterationInTest + 1;
let token = getWaitingToken(currentId);
getConcertSeats(token); // ์ข์ ์กฐํ
sleep(2);
let reservationId = reserveSeat(token, currentId); // ์์ฝ
sleep(2);
processPayment(token, reservationId, currentId); // ๊ฒฐ์
sleep(2);
}
๋ถํ ํ ์คํธ: ์ ์ ํ๋ ๊ธฐ๋ฐ ์๋๋ฆฌ์ค๋ก ์์คํ ํ๊ณ ์ธก์ ๊ณผ SLA, SLO ๊ฒ์ฆ
๋ค์ด๊ฐ๋ฉฐ๋ถํ ํ ์คํธ๋ ์์คํ ์ ์ต๋ ์ฒ๋ฆฌ๋(TPS), ์์ ์ฑ, SLA(์๋น์ค ์์ค ๊ณ์ฝ) ๋ฐ SLO(์๋น์ค ์์ค ๋ชฉํ)๋ฅผ ๊ฒ์ฆํ๋ ์ค์ํ ๊ณผ์ ์ ๋๋ค. ์ด๋ฒ ๊ธ์์๋ ์ฝ์ํธ ์์ฝ ์๋น์ค์์ ์ ์ ํ๋์
eastshine12.tistory.com
ํ๋ก์ ํธ ์์ค
GitHub - eastshine12/concert-reservation: ์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency
์ฝ์ํธ ์์ฝ ์๋น์ค / ํญํดํ๋ฌ์ค / Queue, Token, Concurrency. Contribute to eastshine12/concert-reservation development by creating an account on GitHub.
github.com