Ver3.0 ๐ ์ผํ๋ชฐ ํธ๋ํฝ ๊ธ์ฆ ๋์: ์คํ ์ค์ผ์ผ๋ง ๋ฐ ๋ก๋๋ฐธ๋ฐ์ฑ ์๋ฒฝ ๊ฐ์ด๋

๐ ์ผํ๋ชฐ ํธ๋ํฝ ๊ธ์ฆ ๋์: ์คํ ์ค์ผ์ผ๋ง ๋ฐ ๋ก๋๋ฐธ๋ฐ์ฑ ์๋ฒฝ ๊ฐ์ด๋
ํญ์ฃผํ๋ ์ฃผ๋ฌธ์ ์์ ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ํ๋ช ํ ์ธํ๋ผ ์ ๋ต ๐ช
๐ฏ ์ ๊ฐ์๊ธฐ ์๋ฒ๊ฐ ๋ค์ด๋ ๊น?
์น๊ตฌ์ผ, ์์ํด๋ด. ๋๊ฐ ์ด์ฌํ ๋ง๋ ์ผํ๋ชฐ์ด ๋๋์ด ๋๋ฐ์ด ๋ฌ์ด! ๐
์ธํ๋ฃจ์ธ์๊ฐ SNS์ ์ฌ๋ ค์คฌ๊ณ , ์ฃผ๋ฌธ์ด ํญ์ฃผํ๊ธฐ ์์ํ์ง.
๊ทผ๋ฐ ๊ฐ์๊ธฐ... "502 Bad Gateway" ์๋ฌ๊ฐ ๋ฌ๋ค๋ฉด? ๐ฑ
์ด๊ฑด ๋จ์ํ ์๋ฒ ์ฑ๋ฅ๋ง์ ๋ฌธ์ ๊ฐ ์๋์ผ.
ํธ๋ํฝ ๊ธ์ฆ ์ํฉ์์ ์์คํ
์ด ์๋์ผ๋ก ๋์ํ์ง ๋ชปํ๋ฉด ์ด๋ค ๊ณ ์ฑ๋ฅ ์๋ฒ๋ ๋ฌด์ฉ์ง๋ฌผ์ด ๋๊ฑฐ๋ .
ํ ์ค์ ์ผํ๋ชฐ์ด TV ํ์ผํ์ ๋๊ฐ ์ ์ด ์์ด.
ํ์ ๋์์ ์์ 100๋ช ์์ค์ด์๋๋ฐ, ๋ฐฉ์ก ํ 5๋ถ ๋ง์ 5,000๋ช ์ด ๋ชฐ๋ ธ์ง.
๊ฒฐ๊ณผ? ์๋ฒ ๋ค์ด์ผ๋ก 30๋ถ๊ฐ ์ฃผ๋ฌธ ๋ถ๊ฐ ์ํ๊ฐ ๋๊ณ , ์ฝ 2์ต ์์ ๋งค์ถ ์์ค์ด ๋ฐ์ํ์ด.
์ด๊ฒ ๋ฐ๋ก ์คํ ์ค์ผ์ผ๋ง๊ณผ ๋ก๋๋ฐธ๋ฐ์ฑ์ด ํ์ํ ์ด์ ์ผ.
์ด ๊ธ์์๋ ์ผํ๋ชฐ ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ํธ๋ํฝ ๋์ ์ ๋ต์ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์ค๋ช
ํด์ค๊ฒ.
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๊ฐ๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ ๋๋ ์ด๋ฐ ์ง์์ ์ ๋ง ์ค์ํ๊ฑฐ๋ ! ๐ผ
๐ ์คํ ์ค์ผ์ผ๋ง์ด ๋ญ๊ธธ๋?
์คํ ์ค์ผ์ผ๋ง(Auto Scaling)์ ๋ง ๊ทธ๋๋ก ์๋์ผ๋ก ๊ท๋ชจ๋ฅผ ์กฐ์ ํ๋ ๊ฑฐ์ผ.
ํธ๋ํฝ์ด ๋์ด๋๋ฉด ์๋ฒ๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํ๊ณ , ์ค์ด๋ค๋ฉด ๋ค์ ์ค์ด๋ ๋๋ํ ์์คํ
์ด์ง. ๐ง
๐ ์คํ ์ค์ผ์ผ๋ง์ ํต์ฌ ๊ฐ๋
์๋ฒ ๋์๋ฅผ ๋๋ฆฌ๋ ๋ฐฉ์์ด์ผ. 1๋ โ 3๋ โ 10๋๋ก ์ฆ๊ฐ์ํค๋ ๊ฑฐ์ง.
์ผํ๋ชฐ์์ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ! ๐ช
์๋ฒ ํ ๋์ ์ฑ๋ฅ์ ๋์ด๋ ๋ฐฉ์. CPU 2์ฝ์ด โ 8์ฝ์ด๋ก ์ ๊ทธ๋ ์ด๋ํ๋ ์์ด์ผ.
ํ์ง๋ง ํ๊ณ๊ฐ ์๊ณ , ์ฌ๋ถํ ์ด ํ์ํด์ ์ผํ๋ชฐ์ ๋น์ถ์ฒ! ๐
ํธ๋ํฝ์ด ์ค๋ฉด ์๋ฒ๋ฅผ ์๋์ผ๋ก ์ค์ฌ์ ๋น์ฉ์ ์ ๊ฐํ๋ ๊ฑฐ์ผ.
์๋ฒฝ ์๊ฐ๋์ ์๋ฒ 10๋ โ 2๋๋ก ์ค์ด๋ฉด ํด๋ผ์ฐ๋ ๋น์ฉ์ด ํ ์ค์ด๋ค์ง! ๐ฐ
โข ์ธ์คํด์ค(Instance): ํด๋ผ์ฐ๋์์ ์คํ๋๋ ๊ฐ์ ์๋ฒ ํ ๋
โข ์คํ ์ค์ผ์ผ๋ง ๊ทธ๋ฃน(ASG): ์๋์ผ๋ก ๊ด๋ฆฌ๋๋ ์ธ์คํด์ค๋ค์ ์งํฉ
โข ์ค์ผ์ผ๋ง ์ ์ฑ : ์ธ์ ์๋ฒ๋ฅผ ๋๋ฆฌ๊ณ ์ค์ผ์ง ์ ํ๋ ๊ท์น
โข ํฌ์ค์ฒดํฌ: ์๋ฒ๊ฐ ์ ์ ์๋ํ๋์ง ํ์ธํ๋ ๊ฒ์ฌ
โ๏ธ ์คํ ์ค์ผ์ผ๋ง ์๋ ์๋ฆฌ
์, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ์๋ํ๋์ง ์์๋ณผ๊น? ๐ค
1๋จ๊ณ: ๋ชจ๋ํฐ๋ง ๐
์์คํ
์ด ๊ณ์ํด์ ์๋ฒ ์ํ๋ฅผ ์ฒดํฌํด.
CPU ์ฌ์ฉ๋ฅ , ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋, ๋คํธ์ํฌ ํธ๋ํฝ, ์๋ต ์๊ฐ ๋ฑ์ ์ค์๊ฐ์ผ๋ก ๊ฐ์ํ์ง.
2๋จ๊ณ: ์๊ณ๊ฐ ๋๋ฌ ๐จ
์๋ฅผ ๋ค์ด "CPU ์ฌ์ฉ๋ฅ ์ด 70% ์ด์์ด 5๋ถ๊ฐ ์ง์๋๋ฉด"์ด๋ผ๋ ์กฐ๊ฑด์ ์ค์ ํ๋ค๊ณ ํด๋ด.
์ค์ ๋ก CPU๊ฐ 75%๋ก ์ฌ๋ผ๊ฐ๊ณ 5๋ถ์ด ์ง๋๋ฉด? ์๋์ด ์ธ๋ ค!
3๋จ๊ณ: ์ค์ผ์ผ๋ง ์คํ ๐
์๋์ผ๋ก ์ ์๋ฒ๊ฐ ์์ฑ๋ผ.
AWS๋ผ๋ฉด EC2 ์ธ์คํด์ค๊ฐ, Azure๋ผ๋ฉด VM์ด ์๋์ผ๋ก ๋์์ง๋ ๊ฑฐ์ง.
๋ณดํต 1~3๋ถ ์ ๋๋ฉด ์ ์๋ฒ๊ฐ ์ค๋น๋ผ.
4๋จ๊ณ: ๋ก๋๋ฐธ๋ฐ์ ์ฐ๊ฒฐ ๐
์๋ก ์๊ธด ์๋ฒ๊ฐ ์๋์ผ๋ก ๋ก๋๋ฐธ๋ฐ์์ ๋ฑ๋ก๋ผ.
๊ทธ๋ฌ๋ฉด ํธ๋ํฝ์ด ์ ์๋ฒ๋ก๋ ๋ถ์ฐ๋๊ธฐ ์์ํด!
5๋จ๊ณ: ์์ ํ ๋ฐ ๋ชจ๋ํฐ๋ง โ
์์คํ
์ด ์์ ๋๋ฉด ๋ค์ ๋ชจ๋ํฐ๋ง ๋ชจ๋๋ก ๋์๊ฐ.
ํธ๋ํฝ์ด ์ค๋ฉด ๋ฐ๋๋ก ์๋ฒ๋ฅผ ์ค์ด๋ ๊ณผ์ ์ด ์งํ๋์ง.
์ผํ๋ชฐ์์๋ ์์ธก ์ค์ผ์ผ๋ง๋ ํ์ฉํ ์ ์์ด!
"๋งค์ฃผ ๊ธ์์ผ ์ ๋ 8์์ ํธ๋ํฝ์ด ๊ธ์ฆํ๋ค"๋ ํจํด์ ํ์ตํด์,
๋ฏธ๋ฆฌ ์๋ฒ๋ฅผ ๋๋ ค๋๋ ๊ฑฐ์ผ. ๋ ๋น ๋ฅธ ๋์์ด ๊ฐ๋ฅํ์ง! ๐ฏ
๐ ์ค์ผ์ผ๋ง ์ ์ฑ ์ข ๋ฅ
| ์ ์ฑ ์ ํ | ์ค๋ช | ์ผํ๋ชฐ ์ ํฉ๋ |
|---|---|---|
| ๋์ ์ถ์ | ํน์ ์งํ๋ฅผ ๋ชฉํ๊ฐ์ผ๋ก ์ ์ง (์: CPU 50% ์ ์ง) |
โญโญโญโญโญ ๊ฐ์ฅ ์ถ์ฒ! |
| ๋จ๊ณ ์กฐ์ | ์๊ณ๊ฐ์ ๋ฐ๋ผ ๋จ๊ณ์ ํ์ฅ (60%โ1๋, 80%โ2๋) |
โญโญโญโญ ์ธ๋ฐํ ์ ์ด ๊ฐ๋ฅ |
| ๋จ์ ์กฐ์ | ์กฐ๊ฑด ์ถฉ์กฑ ์ ๊ณ ์ ์๋ ์ฆ๊ฐ | โญโญ ๋๋ฌด ๋จ์ํจ |
| ์์ฝ ์กฐ์ | ํน์ ์๊ฐ์ ๋ฏธ๋ฆฌ ํ์ฅ | โญโญโญโญ ์ด๋ฒคํธ ๋๋น ์ข์ |
์คํ ์ค์ผ์ผ๋ง์ด ๋๋ฌด ๋ฏผ๊ฐํ๋ฉด "ํ๋ํ(Flapping)" ํ์์ด ๋ฐ์ํด.
์๋ฒ๊ฐ ๊ณ์ ์๊ฒผ๋ค ์์ด์ก๋ค ๋ฐ๋ณตํ๋ ๊ฑฐ์ง. ๐ต
์ฟจ๋ค์ด ์๊ฐ(Cooldown Period)์ ์ค์ ํด์ ์ต์ 5๋ถ ์ ๋๋ ๋๊ธฐํ๋๋ก ํด์ผ ํด!
โ๏ธ ๋ก๋๋ฐธ๋ฐ์ฑ: ํธ๋ํฝ์ ๊ตํต์ ๋ฆฌ
์คํ ์ค์ผ์ผ๋ง์ผ๋ก ์๋ฒ๋ฅผ 10๋ ๋๋ ธ๋ค๊ณ ํด๋ณด์.
๊ทธ๋ฐ๋ฐ ์ฌ์ฉ์๋ค์ด ์ด๋ป๊ฒ ์ด 10๋ ์๋ฒ์ ๊ณจ๊ณ ๋ฃจ ์ ์ํ ๊น? ๐คทโโ๏ธ
๋ฐ๋ก ์ฌ๊ธฐ์ ๋ก๋๋ฐธ๋ฐ์(Load Balancer)๊ฐ ํ์ํด!
๋ก๋๋ฐธ๋ฐ์๋ ์ผ์ข
์ ๊ตํต๊ฒฝ์ฐฐ์ด์ผ. ๐ฆ
๋ค์ด์ค๋ ํธ๋ํฝ์ ๋ณด๊ณ "๋๋ 1๋ฒ ์๋ฒ๋ก, ๋๋ 2๋ฒ ์๋ฒ๋ก" ํ๋ฉด์ ๋ถ์ฐ์์ผ์ฃผ์ง.
๐ฏ ๋ก๋๋ฐธ๋ฐ์ฑ์ ํต์ฌ ์ญํ
1. ํธ๋ํฝ ๋ถ์ฐ ๐
ํ ์๋ฒ์ ๋ถํ๊ฐ ๋ชฐ๋ฆฌ์ง ์๋๋ก ์ฌ๋ฌ ์๋ฒ๋ก ๊ณ ๋ฅด๊ฒ ๋ถ๋ฐฐํด.
10,000๋ช
์ ์ฌ์ฉ์๊ฐ ์ ์ํ๋ฉด ์๋ฒ 10๋์ ๊ฐ๊ฐ 1,000๋ช
์ฉ ๋ฐฐ๋ถํ๋ ์์ด์ง.
2. ๊ณ ๊ฐ์ฉ์ฑ ๋ณด์ฅ ๐ก๏ธ
์๋ฒ ํ ๋๊ฐ ์ฃฝ์ด๋ ๋ค๋ฅธ ์๋ฒ๋ก ์๋ ์ ํ๋ผ.
์ฌ์ฉ์๋ ์๋ฒ ์ฅ์ ๋ฅผ ์ ํ ๋๋ผ์ง ๋ชปํด! ์ด๊ฒ ์ง์ง ์ค์ํ ๊ฑฐ์ผ.
3. ํฌ์ค์ฒดํฌ ์ํ ๐
๊ฐ ์๋ฒ๊ฐ ์ ์์ธ์ง ๊ณ์ ํ์ธํด.
์๋ต์ด ์๊ฑฐ๋ ์๋ฌ๊ฐ ๋๋ ์๋ฒ๋ ์๋์ผ๋ก ์ ์ธ์ํค์ง.
4. SSL/TLS ์ฒ๋ฆฌ ๐
HTTPS ์ํธํ ์์
์ ๋ก๋๋ฐธ๋ฐ์๊ฐ ๋์ ์ฒ๋ฆฌํด์ค.
๊ฐ ์๋ฒ์ ๋ถ๋ด์ ์ค์ฌ์ฃผ๋ ๊ฑฐ์ผ!
๐ ๋ก๋๋ฐธ๋ฐ์ฑ ์๊ณ ๋ฆฌ์ฆ
๋ก๋๋ฐธ๋ฐ์๊ฐ ํธ๋ํฝ์ ๋ถ์ฐํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง์ผ.
์ผํ๋ชฐ ํน์ฑ์ ๋ง๋ ์๊ณ ๋ฆฌ์ฆ์ ์ ํํ๋ ๊ฒ ์ค์ํด! ๐ฒ
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๊ณ ๊ณตํํ ๋ฐฉ์์ด์ผ.
์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ์๋ฒ 1 โ 2 โ 3 โ 1 โ 2 โ 3... ์์๋๋ก ๋์๊ฐ๋ฉฐ ๋ฐฐ๋ถํด.
์ผํ๋ชฐ ์ถ์ฒ๋: ์ต๊ณ ! ๊ฐ๋จํ๋ฉด์๋ ํจ๊ณผ์ ์ด์ผ.
ํ์ฌ ์ฐ๊ฒฐ ์๊ฐ ๊ฐ์ฅ ์ ์ ์๋ฒ๋ก ๋ณด๋ด๋ ๋ฐฉ์.
์๋ฒ 1์ 100๊ฐ ์ฐ๊ฒฐ, ์๋ฒ 2์ 50๊ฐ ์ฐ๊ฒฐ์ด๋ฉด ์๋ฒ 2๋ก ๋ณด๋ด๋ ๊ฑฐ์ง.
์ผํ๋ชฐ ์ถ์ฒ๋: ๋์! ์ฒ๋ฆฌ ์๊ฐ์ด ๋ค์ญ๋ ์ญํ ๋ ์ข์.
์ฌ์ฉ์ IP ์ฃผ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํญ์ ๊ฐ์ ์๋ฒ๋ก ์ฐ๊ฒฐํด.
์ธ์ ์ ์ง๊ฐ ํ์ํ ๋ ์ ์ฉํ์ง๋ง, ์์ฆ์ ์ธ์ ์คํ ์ด๋ฅผ ๋ฐ๋ก ์ฐ๋ ๊ฒ ๋์.
์ผํ๋ชฐ ์ถ์ฒ๋: ๋ณดํต
์๋ฒ ์ฑ๋ฅ์ ๋ฐ๋ผ ๊ฐ์ค์น๋ฅผ ์ค.
๊ณ ์ฑ๋ฅ ์๋ฒ๋ 70%, ์ ์ฑ๋ฅ ์๋ฒ๋ 30% ํธ๋ํฝ์ ๋ฐ๋ ์์ด์ผ.
์ผํ๋ชฐ ์ถ์ฒ๋: ๋์! ์๋ฒ ์คํ์ด ๋ค๋ฅผ ๋ ํ์!
๊ฐ์ฅ ๋น ๋ฅด๊ฒ ์๋ตํ๋ ์๋ฒ๋ก ๋ณด๋ด๋ ๋ฐฉ์.
์ค์๊ฐ์ผ๋ก ์๋ฒ ์ํ๋ฅผ ๋ฐ์ํ๋๊น ์ต์ ์ ์ฑ๋ฅ์ ๋ผ ์ ์์ด!
์ผํ๋ชฐ ์ถ์ฒ๋: ์ต๊ณ ! AWS ALB์ ๊ธฐ๋ณธ ๋์ ๋ฐฉ์์ด์ผ.
๋ผ์ด๋ ๋ก๋น + ์ต์ ์ฐ๊ฒฐ์ ํผํฉํ ๋ฐฉ์์ ์ถ์ฒํด!
๊ธฐ๋ณธ์ ๋ผ์ด๋ ๋ก๋น์ผ๋ก ๋๋ฆฌ๋, ํน์ ์๋ฒ์ ๋ถํ๊ฐ ๋ชฐ๋ฆฌ๋ฉด ์๋์ผ๋ก ์ต์ ์ฐ๊ฒฐ ๋ฐฉ์์ผ๋ก ์ ํํ๋ ๊ฑฐ์ผ.
AWS ALB๋ NGINX Plus์์ ์ด๋ฐ ์ค๋งํธํ ๋ถ์ฐ์ด ๊ฐ๋ฅํด! ๐ง
๐๏ธ ๋ก๋๋ฐธ๋ฐ์ ์ข ๋ฅ
๋ก๋๋ฐธ๋ฐ์๋ ์ฌ๋ฌ ๋ ๋ฒจ์ด ์์ด. OSI 7๊ณ์ธต ์ค ์ด๋ ๋ ๋ฒจ์์ ์๋ํ๋๋์ ๋ฐ๋ผ ๋๋์ง. ๐
L4 ๋ก๋๋ฐธ๋ฐ์ (Transport Layer) ๐
IP ์ฃผ์์ ํฌํธ ๋ฒํธ๋ฅผ ๋ณด๊ณ ๋ถ์ฐํด.
๋น ๋ฅด๊ณ ํจ์จ์ ์ด์ง๋ง, HTTP ํค๋๋ ์ฟ ํค ๊ฐ์ ๊ฑด ๋ชป ๋ด.
TCP/UDP ๋ ๋ฒจ์์ ์๋ํ์ง.
์ฅ์ :
โข ์ฒ๋ฆฌ ์๋๊ฐ ๋งค์ฐ ๋น ๋ฆ โก
โข ๋น์ฉ์ด ์๋์ ์ผ๋ก ์ ๋ ด
โข ๋จ์ํ๊ณ ์์ ์
๋จ์ :
โข URL ๊ธฐ๋ฐ ๋ผ์ฐํ
๋ถ๊ฐ
โข ์ฟ ํค๋ ์ธ์
์ธ์ ๋ถ๊ฐ
โข SSL ํฐ๋ฏธ๋ค์ด์
์ ํ์
L7 ๋ก๋๋ฐธ๋ฐ์ (Application Layer) ๐ฏ
HTTP ํค๋, URL, ์ฟ ํค ๋ฑ์ ๋ถ์ํด์ ๋๋ํ๊ฒ ๋ถ์ฐํด.
/api/*๋ API ์๋ฒ๋ก, /images/*๋ ์ด๋ฏธ์ง ์๋ฒ๋ก ๋ณด๋ด๋ ์์ด์ง!
์ฅ์ :
โข URL ๊ธฐ๋ฐ ๋ผ์ฐํ
๊ฐ๋ฅ ๐ฏ
โข ์ฟ ํค/์ธ์
๊ธฐ๋ฐ ๋ถ์ฐ
โข SSL ์คํ๋ก๋ฉ ์๋ฒฝ ์ง์
โข ์ฝํ
์ธ ๊ธฐ๋ฐ ์ค์์นญ
โข ํฌ์ค์ฒดํฌ๊ฐ ๋ ์ ๊ตํจ
๋จ์ :
โข L4๋ณด๋ค ๋๋ฆผ (ํ์ง๋ง ์ฒด๊ฐ ์ ๋จ)
โข ๋น์ฉ์ด ์กฐ๊ธ ๋ ๋น์
โข ์ค์ ์ด ๋ณต์กํ ์ ์์
๋ฌด์กฐ๊ฑด L7 ๋ก๋๋ฐธ๋ฐ์๋ฅผ ์จ!
AWS ALB, Azure Application Gateway, Google Cloud Load Balancing ๋ชจ๋ L7์ด์ผ.
URL ๊ธฐ๋ฐ ๋ผ์ฐํ ์ผ๋ก /checkout์ ๊ฒฐ์ ์๋ฒ๋ก, /products๋ ์ํ ์๋ฒ๋ก ๋ณด๋ผ ์ ์์ด์ ํจ์ฌ ํจ์จ์ ์ด์ผ! ๐
๐ ํฌ์ค์ฒดํฌ ์ค์
๋ก๋๋ฐธ๋ฐ์์ ํต์ฌ ๊ธฐ๋ฅ ์ค ํ๋๊ฐ ๋ฐ๋ก ํฌ์ค์ฒดํฌ์ผ.
์๋ฒ๊ฐ ์ ์์ธ์ง ๊ณ์ ํ์ธํด์ ๋ฌธ์ ์๋ ์๋ฒ๋ ์๋์ผ๋ก ์ ์ธ์ํค์ง. ๐
ํฌ์ค์ฒดํฌ ๋ฐฉ์:
1. HTTP/HTTPS ์ฒดํฌ
ํน์ URL์ ์์ฒญ์ ๋ณด๋ด์ ์๋ต ์ฝ๋๋ฅผ ํ์ธํด.
์: GET /health โ 200 OK๋ฉด ์ ์, 500์ด๋ฉด ๋น์ ์
2. TCP ์ฒดํฌ
ํฌํธ๊ฐ ์ด๋ ค์๋์ง๋ง ํ์ธํด. ๊ฐ๋จํ์ง๋ง ๋ ์ ํํด.
3. ์ปค์คํ
์ฒดํฌ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ, ์บ์ ์ํ ๋ฑ์ ์ข
ํฉ์ ์ผ๋ก ํ์ธํ๋ ์๋ํฌ์ธํธ๋ฅผ ๋ง๋ค์ด.
์ด๊ฒ ์ ์ผ ์ ํํด! ๐ฏ
// Node.js + Express ์์
app.get('/health', async (req, res) => {
try {
// DB ์ฐ๊ฒฐ ํ์ธ
await db.ping();
// Redis ์ฐ๊ฒฐ ํ์ธ
await redis.ping();
// ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ ํ์ธ
const memUsage = process.memoryUsage();
if (memUsage.heapUsed / memUsage.heapTotal > 0.9) {
throw new Error('๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ');
}
res.status(200).json({
status: 'healthy',
timestamp: new Date()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});
ํฌ์ค์ฒดํฌ ์ค์ ํ:
โข ๊ฐ๊ฒฉ(Interval): 30์ด๋ง๋ค ์ฒดํฌ (๋๋ฌด ์งง์ผ๋ฉด ๋ถํ, ๋๋ฌด ๊ธธ๋ฉด ์ฅ์ ๊ฐ์ง ๋ฆ์)
โข ํ์์์: 5์ด ์ด๋ด ์๋ต ์์ผ๋ฉด ์คํจ๋ก ๊ฐ์ฃผ
โข ์๊ณ๊ฐ: ์ฐ์ 2ํ ์คํจ ์ ์ ์ธ, ์ฐ์ 3ํ ์ฑ๊ณต ์ ๋ณต๊ท
โข ๊ฒฝ๋ก: /health ๋๋ /healthcheck ์ฌ์ฉ
ํฌ์ค์ฒดํฌ ์๋ํฌ์ธํธ๋ ์ธ์ฆ ์์ด ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํด!
๋ก๊ทธ์ธ์ด ํ์ํ๋ฉด ๋ก๋๋ฐธ๋ฐ์๊ฐ ์๋ฒ๋ฅผ ์ฃฝ์ ๊ฑธ๋ก ํ๋จํ ์ ์์ด.
ํ์ง๋ง ๋ณด์์ ์ํด IP ํ์ดํธ๋ฆฌ์คํธ๋ ์ค์ ํ๋ ๊ฒ ์ข์. ๐
๐ ๏ธ ์ค์ ๊ตฌ์ถ: AWS ํ๊ฒฝ ์์
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ๊ตฌ์ถํ๋์ง ์์๋ณผ๊น? ๐ช
AWS๋ฅผ ์์๋ก ๋ค๊ฒ ์ง๋ง, Azure๋ GCP๋ ๊ฐ๋
์ ๋๊ฐ์!
๐๏ธ 1๋จ๊ณ: ๊ธฐ๋ณธ ์ธํ๋ผ ์ค๋น
๊ฐ์ฉ ์์ญ(AZ) 2๊ฐ ์ด์์ ์๋ธ๋ท์ ๋ง๋ค์ด.
์: ap-northeast-2a, ap-northeast-2c
์ด๋ ๊ฒ ํด์ผ ํ ์ง์ญ์ ์ฅ์ ๊ฐ ๋๋ ๋ค๋ฅธ ์ง์ญ์์ ์๋น์ค๊ฐ ๊ณ์๋ผ! ๐
โข ๋ก๋๋ฐธ๋ฐ์: 80(HTTP), 443(HTTPS) ํฌํธ ์คํ
โข ์น ์๋ฒ: ๋ก๋๋ฐธ๋ฐ์์์๋ง ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ์ค์
โข DB ์๋ฒ: ์น ์๋ฒ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
๋ณด์์ ๋ ์ด์ด๋ณ๋ก! ๐
์๋ฒฝํ๊ฒ ์ค์ ๋ ์๋ฒ๋ฅผ ์ด๋ฏธ์ง๋ก ๋ง๋ค์ด๋ฌ.
Node.js, Nginx, ํ๊ฒฝ๋ณ์ ๋ฑ ๋ชจ๋ ๊ฒ ์ค์น๋ ์ํ๋ก!
์ด ์ด๋ฏธ์ง๋ก ์ ์๋ฒ๊ฐ ์๋ ์์ฑ๋๋๊น ์ค์ํด. ๐ฆ
โ๏ธ 2๋จ๊ณ: Application Load Balancer ์์ฑ
ALB ๊ธฐ๋ณธ ์ค์ :
โข ์ด๋ฆ: my-shop-alb
โข ์คํด: Internet-facing (์ธ๋ถ ์ ๊ทผ ๊ฐ๋ฅ)
โข IP ์ฃผ์ ์ ํ: IPv4
โข ๋ฆฌ์ค๋: HTTP(80), HTTPS(443)
โข ๊ฐ์ฉ ์์ญ: ์ต์ 2๊ฐ ์ ํ
ํ๊ฒ ๊ทธ๋ฃน ์ค์ :
โข ํ๊ฒ ์ ํ: ์ธ์คํด์ค
โข ํ๋กํ ์ฝ: HTTP
โข ํฌํธ: 3000 (Node.js ๊ธฐ๋ณธ ํฌํธ)
โข ํฌ์ค์ฒดํฌ ๊ฒฝ๋ก: /health
โข ์ ์ ์๊ณ๊ฐ: 3ํ ์ฐ์ ์ฑ๊ณต
โข ๋น์ ์ ์๊ณ๊ฐ: 2ํ ์ฐ์ ์คํจ
ALB์ ์ง์ง ํ์๋ ์ฌ๊ธฐ์ ๋์!
๊ท์น 1: /api/* โ API ์๋ฒ ํ๊ฒ ๊ทธ๋ฃน
๊ท์น 2: /admin/* โ ๊ด๋ฆฌ์ ์๋ฒ ํ๊ฒ ๊ทธ๋ฃน
๊ท์น 3: *.jpg, *.png โ ์ด๋ฏธ์ง ์๋ฒ ํ๊ฒ ๊ทธ๋ฃน
๊ธฐ๋ณธ: ๋๋จธ์ง ๋ชจ๋ ์์ฒญ โ ์น ์๋ฒ ํ๊ฒ ๊ทธ๋ฃน
์ด๋ ๊ฒ ํ๋ฉด ์๋ฒ ์ญํ ์ ๋ช ํํ ๋ถ๋ฆฌํ ์ ์์ด! ๐จ
๐ 3๋จ๊ณ: Auto Scaling ๊ทธ๋ฃน ์์ฑ
์์ ํ ํ๋ฆฟ ์์ฑ:
{
"LaunchTemplateName": "my-shop-template",
"VersionDescription": "v1.0",
"LaunchTemplateData": {
"ImageId": "ami-0c76973fbe0ee100c",
"InstanceType": "t3.medium",
"KeyName": "my-key-pair",
"SecurityGroupIds": ["sg-0123456789abcdef"],
"UserData": "IyEvYmluL2Jhc2gKY2QgL2hvbWUvZWMyLXVzZXIvYXBwCm5wbSBzdGFydA==",
"IamInstanceProfile": {
"Name": "EC2-CloudWatch-Role"
},
"TagSpecifications": [{
"ResourceType": "instance",
"Tags": [
{"Key": "Name", "Value": "shop-web-server"},
{"Key": "Environment", "Value": "production"}
]
}]
}
}
Auto Scaling ๊ทธ๋ฃน ์ค์ :
โข ์ต์ ์ฉ๋: 2๋ (ํญ์ ์ต์ 2๋๋ ์ ์ง)
โข ์ํ๋ ์ฉ๋: 4๋ (ํ์์ 4๋ ์ด์)
โข ์ต๋ ์ฉ๋: 20๋ (์ต๋ 20๋๊น์ง ํ์ฅ)
โข ๊ฐ์ฉ ์์ญ: 2๊ฐ ์ด์ (๊ณ ๊ฐ์ฉ์ฑ)
โข ๋ก๋๋ฐธ๋ฐ์: ์์์ ๋ง๋ ALB ์ฐ๊ฒฐ
์คํ ์ธ์คํด์ค๋ฅผ ์์ด์ ์จ๋ด!
์จ๋๋งจ๋ 70% + ์คํ 30% ์กฐํฉ์ผ๋ก ํ๋ฉด ๋น์ฉ์ 30~50% ์ ๊ฐํ ์ ์์ด.
์คํ์ ๊ฐ์๊ธฐ ํ์๋ ์ ์์ง๋ง, ASG๊ฐ ์๋์ผ๋ก ์ ์ธ์คํด์ค๋ฅผ ๋์์ฃผ๋๊น ๋ฌธ์ ์์ด! ๐ฐ
๐ 4๋จ๊ณ: ์ค์ผ์ผ๋ง ์ ์ฑ ์ค์
๋์ ์ถ์ ์ค์ผ์ผ๋ง ์ ์ฑ (์ถ์ฒ!):
{
"TargetTrackingScalingPolicyConfiguration": {
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
},
"TargetValue": 50.0,
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}
}
์ด ์ค์ ์ "CPU ์ฌ์ฉ๋ฅ ์ 50%๋ก ์ ์งํด์ค"๋ผ๋ ์๋ฏธ์ผ.
CPU๊ฐ 50% ๋์ผ๋ฉด ์๋ฒ๋ฅผ ์ถ๊ฐํ๊ณ , 50% ๋ฐ์ผ๋ก ๋จ์ด์ง๋ฉด ์ค์ด๋ ๊ฑฐ์ง! ๐ฏ
๋จ๊ณ ์กฐ์ ์ ์ฑ (์ธ๋ฐํ ์ ์ด):
{
"StepScalingPolicyConfiguration": {
"AdjustmentType": "PercentChangeInCapacity",
"MetricAggregationType": "Average",
"StepAdjustments": [
{
"MetricIntervalLowerBound": 0,
"MetricIntervalUpperBound": 10,
"ScalingAdjustment": 10
},
{
"MetricIntervalLowerBound": 10,
"MetricIntervalUpperBound": 20,
"ScalingAdjustment": 20
},
{
"MetricIntervalLowerBound": 20,
"ScalingAdjustment": 30
}
]
}
}
์ด๊ฑด ๋ ๋๋ํด:
โข CPU 60~70%: ์ฉ๋ 10% ์ฆ๊ฐ
โข CPU 70~80%: ์ฉ๋ 20% ์ฆ๊ฐ
โข CPU 80% ์ด์: ์ฉ๋ 30% ์ฆ๊ฐ
์ํฉ์ ๋ง๊ฒ ํ๋ ฅ์ ์ผ๋ก ๋์ํ๋ ๊ฑฐ์ผ! ๐ง
์์ฝ ์กฐ์ (์ด๋ฒคํธ ๋๋น):
{
"ScheduledActionName": "scale-up-for-event",
"StartTime": "2024-11-11T19:00:00Z",
"EndTime": "2024-11-12T02:00:00Z",
"MinSize": 10,
"MaxSize": 50,
"DesiredCapacity": 15
}
11์ 11์ผ(๊ด๊ตฐ์ ) ์ ๋
8์๋ถํฐ ์๋ฒฝ 2์๊น์ง ๋ฏธ๋ฆฌ ์๋ฒ๋ฅผ ๋๋ ค๋๋ ๊ฑฐ์ผ.
ํธ๋ํฝ ํญ์ฆ์ ์์ํ ์ ์์ ๋ ์๋ฒฝํ ์ ๋ต์ด์ง! ๐
1. ์ฟจ๋ค์ด ์๊ฐ์ ๋๋ฌด ์งง๊ฒ ์ค์ ํ์ง ๋ง. ์ต์ 5๋ถ์ ์ค์ผ ํด.
2. ์ต์ ์ฉ๋์ ํญ์ 2๋ ์ด์์ผ๋ก! 1๋๋ฉด ์ฅ์ ์ ์๋น์ค ์ค๋จ๋ผ.
3. ์ต๋ ์ฉ๋์ ์์ฐ์ ๊ณ ๋ คํด์ ์ค์ ํด. ๋ฌดํ์ ๋์ด๋๋ฉด ํ์ฐํ ์ ์์ด! ๐ธ
4. CloudWatch ์๋์ ๊ผญ ์ค์ ํด์ ๋น์ ์ ํ์ฅ์ ๋ชจ๋ํฐ๋งํด.
๐ 5๋จ๊ณ: SSL/TLS ์ค์
์ผํ๋ชฐ์ ๊ฒฐ์ ์ ๋ณด๋ฅผ ๋ค๋ฃจ๋๊น HTTPS๋ ํ์์ผ! ๐
AWS Certificate Manager๋ก ์ธ์ฆ์ ๋ฐ๊ธ:
1. ACM ์ฝ์์์ "์ธ์ฆ์ ์์ฒญ" ํด๋ฆญ
2. ๋๋ฉ์ธ ์ด๋ฆ ์
๋ ฅ (์: www.myshop.com, myshop.com)
3. DNS ๊ฒ์ฆ ๋๋ ์ด๋ฉ์ผ ๊ฒ์ฆ ์ ํ
4. Route 53 ์ฌ์ฉ ์ ์๋์ผ๋ก DNS ๋ ์ฝ๋ ์ถ๊ฐ ๊ฐ๋ฅ
5. ์ธ์ฆ์ ๋ฐ๊ธ ์๋ฃ! (๋ฌด๋ฃ์ผ! ๐)
ALB์ ์ธ์ฆ์ ์ฐ๊ฒฐ:
โข ALB ๋ฆฌ์ค๋์์ HTTPS(443) ์ถ๊ฐ
โข ์์์ ๋ฐ๊ธ๋ฐ์ ACM ์ธ์ฆ์ ์ ํ
โข HTTP(80)๋ HTTPS๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ค์
โข ๋ณด์ ์ ์ฑ
: ELBSecurityPolicy-TLS-1-2-2017-01 (TLS 1.2 ์ด์)
โข HSTS ํค๋ ์ถ๊ฐ: ๋ธ๋ผ์ฐ์ ๊ฐ ํญ์ HTTPS๋ก๋ง ์ ์ํ๋๋ก
โข ๋ณด์ ํค๋ ์ค์ : X-Frame-Options, X-Content-Type-Options ๋ฑ
โข WAF(Web Application Firewall) ์ฐ๊ฒฐ: SQL ์ธ์ ์ , XSS ๊ณต๊ฒฉ ์ฐจ๋จ
โข DDoS ๋ฐฉ์ด: AWS Shield Standard๋ ์๋ ์ ์ฉ๋จ!
๐จ ์ธ์ ๊ด๋ฆฌ ์ ๋ต
์คํ ์ค์ผ์ผ๋ง ํ๊ฒฝ์์ ๊ฐ์ฅ ๊น๋ค๋ก์ด ๊ฒ ๋ฐ๋ก ์ธ์
๊ด๋ฆฌ์ผ. ๐ค
์ฌ์ฉ์๊ฐ ์๋ฒ 1์์ ๋ก๊ทธ์ธํ๋๋ฐ, ๋ค์ ์์ฒญ์ด ์๋ฒ 2๋ก ๊ฐ๋ฉด? ๋ก๊ทธ์ธ ์ ๋ณด๊ฐ ์์ด์ ธ!
โ ์๋ชป๋ ๋ฐฉ๋ฒ: ์๋ฒ ๋ฉ๋ชจ๋ฆฌ์ ์ธ์ ์ ์ฅ
// ์ด๋ ๊ฒ ํ๋ฉด ์ ๋ผ!
const sessions = {}; // ์๋ฒ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ
app.post('/login', (req, res) => {
const sessionId = generateSessionId();
sessions[sessionId] = { userId: req.body.userId };
res.cookie('sessionId', sessionId);
});
์ด ๋ฐฉ์์ ๋จ์ผ ์๋ฒ์์๋ง ์๋ํด.
์๋ฒ๊ฐ ์ฌ๋ฌ ๋๋ฉด ๊ฐ ์๋ฒ๋ง๋ค ๋ค๋ฅธ ์ธ์
์ ๋ณด๋ฅผ ๊ฐ์ง๊ฒ ๋์ง. ๐ต
โ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ 1: Redis ์ธ์ ์คํ ์ด
// Redis๋ฅผ ์ค์ ์ธ์
์ ์ฅ์๋ก ์ฌ์ฉ
const redis = require('redis');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redisClient = redis.createClient({
host: 'my-redis-cluster.abc123.ng.0001.apn2.cache.amazonaws.com',
port: 6379
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'my-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 // 24์๊ฐ
}
}));
์ด์ ๋ชจ๋ ์๋ฒ๊ฐ ๊ฐ์ Redis๋ฅผ ๋ฐ๋ผ๋ณด๋๊น ์ธ์
์ด ๊ณต์ ๋ผ! ๐
์ฌ์ฉ์๊ฐ ์ด๋ ์๋ฒ๋ก ์ฐ๊ฒฐ๋๋ ๋ก๊ทธ์ธ ์ํ๊ฐ ์ ์ง๋์ง.
โข ElastiCache for Redis ์ฌ์ฉ (AWS ๊ด๋ฆฌํ ์๋น์ค)
โข ํด๋ฌ์คํฐ ๋ชจ๋ ํ์ฑํ๋ก ๊ณ ๊ฐ์ฉ์ฑ ํ๋ณด
โข ๋ค์ค AZ ๋ฐฐํฌ๋ก ์ฅ์ ๋์
โข ์๋ ๋ฐฑ์ ์ค์ ์ผ๋ก ๋ฐ์ดํฐ ๋ณดํธ
Redis๋ ์ฃฝ์ ์ ์์ผ๋๊น ์ด์คํ๋ ํ์์ผ! ๐ก๏ธ
โ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ 2: JWT ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ
const jwt = require('jsonwebtoken');
// ๋ก๊ทธ์ธ ์ JWT ๋ฐ๊ธ
app.post('/login', async (req, res) => {
const user = await authenticateUser(req.body);
const token = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token });
});
// ์ธ์ฆ ๋ฏธ๋ค์จ์ด
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '์ธ์ฆ ํ์' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: '์ ํจํ์ง ์์ ํ ํฐ' });
}
req.user = user;
next();
});
};
JWT๋ ์๋ฒ์ ์ธ์
์ ์ ์ฅํ์ง ์์!
ํ ํฐ ์์ฒด์ ๋ชจ๋ ์ ๋ณด๊ฐ ๋ค์ด์์ด์ ์์ ํ ๋ฌด์ํ(Stateless)์ผ.
์คํ ์ค์ผ์ผ๋ง ํ๊ฒฝ์ ์๋ฒฝํ๊ฒ ๋ง๋ ๋ฐฉ์์ด์ง! ๐ฏ
Redis ์ธ์ ์คํ ์ด:
โ ์๋ฒ์์ ์ธ์ ์ ์ด ๊ฐ๋ฅ (๊ฐ์ ๋ก๊ทธ์์ ๋ฑ)
โ ๋ฏผ๊ฐํ ์ ๋ณด ์๋ฒ์ ์์ ํ๊ฒ ๋ณด๊ด
โ Redis ์ฅ์ ์ ์ํฅ ๋ฐ์
โ ๋คํธ์ํฌ ์ค๋ฒํค๋ ์กด์ฌ
JWT ํ ํฐ:
โ ์์ ํ ๋ฌด์ํ, ํ์ฅ์ฑ ์ต๊ณ
โ ์ธ๋ถ ์ ์ฅ์ ๋ถํ์
โ ํ ํฐ ๋ฌดํจํ๊ฐ ์ด๋ ค์
โ ํ ํฐ ํฌ๊ธฐ๊ฐ ํผ
์ถ์ฒ: ๋ ๊ฐ์ง๋ฅผ ํผํฉํด์ ์จ!
JWT๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฐ๋, ์ค์ํ ์์ (๊ฒฐ์ ๋ฑ)์ Redis์ ์ถ๊ฐ ๊ฒ์ฆ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฑฐ์ผ. ๐จ
๐ ์ฅ๋ฐ๊ตฌ๋ ๋ฐ์ดํฐ ๊ด๋ฆฌ
์ฅ๋ฐ๊ตฌ๋๋ ์ธ์
์ฒ๋ผ ๊ด๋ฆฌํด์ผ ํด.
์ฌ์ฉ์๊ฐ ์ํ์ ๋ด์๋๋ฐ ์๋ฒ ๋ฐ๋๋ฉด์ ์ฌ๋ผ์ง๋ฉด ํฐ์ผ์ด์์! ๐ฑ
๋ฐฉ๋ฒ 1: Redis์ ์ ์ฅ
// ์ฅ๋ฐ๊ตฌ๋ ์ถ๊ฐ
app.post('/cart/add', authenticateToken, async (req, res) => {
const userId = req.user.userId;
const { productId, quantity } = req.body;
const cartKey = `cart:${userId}`;
await redisClient.hset(cartKey, productId, quantity);
await redisClient.expire(cartKey, 60 * 60 * 24 * 7); // 7์ผ ์ ํจ
res.json({ success: true });
});
// ์ฅ๋ฐ๊ตฌ๋ ์กฐํ
app.get('/cart', authenticateToken, async (req, res) => {
const userId = req.user.userId;
const cartKey = `cart:${userId}`;
const cart = await redisClient.hgetall(cartKey);
res.json({ cart });
});
๋ฐฉ๋ฒ 2: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ
// ์ฅ๋ฐ๊ตฌ๋ ํ
์ด๋ธ ์คํค๋ง
CREATE TABLE cart_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
UNIQUE KEY uk_user_product (user_id, product_id)
);
// ์ฅ๋ฐ๊ตฌ๋ ์ถ๊ฐ
app.post('/cart/add', authenticateToken, async (req, res) => {
const userId = req.user.userId;
const { productId, quantity } = req.body;
await db.query(`
INSERT INTO cart_items (user_id, product_id, quantity)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE quantity = quantity + ?
`, [userId, productId, quantity, quantity]);
res.json({ success: true });
});
โข ๋น๋ก๊ทธ์ธ ์ฌ์ฉ์: ๋ธ๋ผ์ฐ์ localStorage์ ์์ ์ ์ฅ
โข ๋ก๊ทธ์ธ ์ฌ์ฉ์: Redis(๋น ๋ฅธ ์ ๊ทผ) + DB(์๊ตฌ ์ ์ฅ) ์ด์คํ
โข ๋๊ธฐํ: ๋ก๊ทธ์ธ ์ localStorage ๋ด์ฉ์ ์๋ฒ๋ก ๋ณํฉ
โข ๋ง๋ฃ ์ ์ฑ : 7์ผ ์ด์ ๋ฐฉ์น๋ ์ฅ๋ฐ๊ตฌ๋๋ ์๋ ์ญ์
์ด๋ ๊ฒ ํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ๋ ์ข๊ณ ์๋ฒ ๋ถ๋ด๋ ์ค์ด๋ค์ด! ๐ฏ
๐ ๋ชจ๋ํฐ๋ง ๋ฐ ์๋ฆผ ์ค์
์์คํ
์ ๊ตฌ์ถํ์ผ๋ฉด ์ด์ ์ ๋๋ก ์๋ํ๋์ง ๊ฐ์ํด์ผ์ง! ๐
๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ฆ์ ์์์ฑ๊ณ ๋์ํ ์ ์์ด์ผ ํด.
๐ ํต์ฌ ๋ชจ๋ํฐ๋ง ์งํ
โข CPU ์ฌ์ฉ๋ฅ : 70% ์ด์ ์ง์ ์ ์๋ฆผ
โข ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ : 85% ์ด์ ์ ์๋ฆผ
โข ๋์คํฌ ์ฌ์ฉ๋ฅ : 80% ์ด์ ์ ์๋ฆผ
โข ๋คํธ์ํฌ ๋์ญํญ: ์๊ณ๊ฐ ์ด๊ณผ ์ ์๋ฆผ
โข ์๋ต ์๊ฐ: ํ๊ท 500ms ์ด์ ์ ์๋ฆผ
โข ์๋ฌ์จ: 1% ์ด์ ์ ์๋ฆผ
โข ์ด๋น ์์ฒญ ์(RPS): ๊ธ์ฆ/๊ธ๊ฐ ์ ์๋ฆผ
โข ๋์ ์ ์์ ์: ์๊ณ๊ฐ ๋๋ฌ ์ ์๋ฆผ
โข ์ฃผ๋ฌธ ์ ํ์จ: ๊ธ๊ฒฉํ ํ๋ฝ ์ ์๋ฆผ
โข ๊ฒฐ์ ์ฑ๊ณต๋ฅ : 95% ๋ฏธ๋ง ์ ์๋ฆผ
โข ์ฅ๋ฐ๊ตฌ๋ ์ดํ๋ฅ : ํ์๋ณด๋ค ๋์ ์ ์๋ฆผ
โข ํ๊ท ์ฃผ๋ฌธ ๊ธ์ก: ์ด์ ํจํด ๊ฐ์ง ์ ์๋ฆผ
๐ CloudWatch ์๋ ์ค์
// AWS SDK๋ก ์๋ ์์ฑ
const cloudwatch = new AWS.CloudWatch();
// CPU ์ฌ์ฉ๋ฅ ์๋
await cloudwatch.putMetricAlarm({
AlarmName: 'high-cpu-usage',
ComparisonOperator: 'GreaterThanThreshold',
EvaluationPeriods: 2,
MetricName: 'CPUUtilization',
Namespace: 'AWS/EC2',
Period: 300,
Statistic: 'Average',
Threshold: 70.0,
ActionsEnabled: true,
AlarmActions: [
'arn:aws:sns:ap-northeast-2:123456789012:alert-topic'
],
AlarmDescription: 'CPU ์ฌ์ฉ๋ฅ ์ด 70% ์ด์ 10๋ถ๊ฐ ์ง์',
Dimensions: [
{
Name: 'AutoScalingGroupName',
Value: 'my-shop-asg'
}
]
}).promise();
// ์๋ต ์๊ฐ ์๋
await cloudwatch.putMetricAlarm({
AlarmName: 'slow-response-time',
ComparisonOperator: 'GreaterThanThreshold',
EvaluationPeriods: 3,
MetricName: 'TargetResponseTime',
Namespace: 'AWS/ApplicationELB',
Period: 60,
Statistic: 'Average',
Threshold: 0.5,
ActionsEnabled: true,
AlarmActions: [
'arn:aws:sns:ap-northeast-2:123456789012:alert-topic'
],
AlarmDescription: 'ํ๊ท ์๋ต ์๊ฐ์ด 500ms ์ด๊ณผ',
Dimensions: [
{
Name: 'LoadBalancer',
Value: 'app/my-shop-alb/1234567890abcdef'
}
]
}).promise();
๐ฑ ์๋ฆผ ์ฑ๋ ์ค์
SNS(Simple Notification Service) ํ์ฉ:
// SNS ํ ํฝ ์์ฑ
const sns = new AWS.SNS();
await sns.createTopic({
Name: 'shop-critical-alerts'
}).promise();
// ์ด๋ฉ์ผ ๊ตฌ๋
์ถ๊ฐ
await sns.subscribe({
Protocol: 'email',
TopicArn: 'arn:aws:sns:ap-northeast-2:123456789012:shop-critical-alerts',
Endpoint: 'admin@myshop.com'
}).promise();
// SMS ๊ตฌ๋
์ถ๊ฐ (๊ธด๊ธ ์๋ฆผ์ฉ)
await sns.subscribe({
Protocol: 'sms',
TopicArn: 'arn:aws:sns:ap-northeast-2:123456789012:shop-critical-alerts',
Endpoint: '+821012345678'
}).promise();
// Slack ์นํ
์ฐ๋
await sns.subscribe({
Protocol: 'https',
TopicArn: 'arn:aws:sns:ap-northeast-2:123456789012:shop-critical-alerts',
Endpoint: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXX'
}).promise();
Critical (๊ธด๊ธ):
โข ์๋น์ค ๋ค์ด
โข ๊ฒฐ์ ์์คํ ์ฅ์
โข ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์คํจ
โ SMS + ์ ํ + Slack + ์ด๋ฉ์ผ
Warning (๊ฒฝ๊ณ ):
โข CPU 70% ์ด์
โข ์๋ต ์๊ฐ ์ฆ๊ฐ
โข ์๋ฌ์จ ์์น
โ Slack + ์ด๋ฉ์ผ
Info (์ ๋ณด):
โข ์ค์ผ์ผ๋ง ๋ฐ์
โข ๋ฐฐํฌ ์๋ฃ
โข ์ผ์ผ ๋ฆฌํฌํธ
โ ์ด๋ฉ์ผ๋ง
๐ ๋์๋ณด๋ ๊ตฌ์ฑ
CloudWatch ๋์๋ณด๋๋ก ํ๋์ ์์คํ ์ํ๋ฅผ ํ์ ํ ์ ์์ด! ๐บ
// ๋์๋ณด๋ ์์ฑ
await cloudwatch.putDashboard({
DashboardName: 'shop-overview',
DashboardBody: JSON.stringify({
widgets: [
{
type: 'metric',
properties: {
metrics: [
['AWS/ApplicationELB', 'RequestCount', { stat: 'Sum' }],
['.', 'TargetResponseTime', { stat: 'Average' }]
],
period: 300,
stat: 'Average',
region: 'ap-northeast-2',
title: 'ํธ๋ํฝ ๋ฐ ์๋ต ์๊ฐ'
}
},
{
type: 'metric',
properties: {
metrics: [
['AWS/EC2', 'CPUUtilization', { stat: 'Average' }],
['.', 'NetworkIn', { stat: 'Sum' }],
['.', 'NetworkOut', { stat: 'Sum' }]
],
period: 300,
stat: 'Average',
region: 'ap-northeast-2',
title: '์๋ฒ ๋ฆฌ์์ค ์ฌ์ฉ๋ฅ '
}
},
{
type: 'metric',
properties: {
metrics: [
['AWS/AutoScaling', 'GroupDesiredCapacity'],
['.', 'GroupInServiceInstances'],
['.', 'GroupMinSize'],
['.', 'GroupMaxSize']
],
period: 300,
stat: 'Average',
region: 'ap-northeast-2',
title: '์คํ ์ค์ผ์ผ๋ง ์ํ'
}
}
]
})
}).promise();
1. ๊ณจ๋ ์๊ทธ๋ ์ง์ค: ์ง์ฐ์๊ฐ, ํธ๋ํฝ, ์๋ฌ, ํฌํ๋
2. ๋ฒ ์ด์ค๋ผ์ธ ์ค์ : ํ์์ ํจํด์ ํ์ ํด์ ์ด์ ์งํ ๋น ๋ฅด๊ฒ ๊ฐ์ง
3. ์๋ฆผ ํผ๋ก๋ ๋ฐฉ์ง: ๋๋ฌด ๋ง์ ์๋ฆผ์ ์คํ๋ ค ์ญํจ๊ณผ
4. ์๋ ๋ณต๊ตฌ ์ค์ : ๊ฐ๋จํ ๋ฌธ์ ๋ ์๋์ผ๋ก ํด๊ฒฐ๋๋๋ก
5. ์ ๊ธฐ ๋ฆฌ๋ทฐ: ์ฃผ๊ฐ/์๊ฐ ๋ฆฌํฌํธ๋ก ํธ๋ ๋ ๋ถ์ ๐
๐ฐ ๋น์ฉ ์ต์ ํ ์ ๋ต
์คํ ์ค์ผ์ผ๋ง์ ํธ๋ฆฌํ์ง๋ง, ์๋ชป ์ค์ ํ๋ฉด ์ฒญ๊ตฌ์ ํญํ์ ๋ง์ ์ ์์ด! ๐ธ
๋๋ํ๊ฒ ๋น์ฉ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์.
๐ ๋น์ฉ ๊ตฌ์กฐ ์ดํดํ๊ธฐ
| ํญ๋ชฉ | ๊ณผ๊ธ ๋ฐฉ์ | ์ ์์ ๋น์ฉ |
|---|---|---|
| EC2 ์ธ์คํด์ค | ์๊ฐ๋น ๊ณผ๊ธ (t3.medium ๊ธฐ์ค) |
$30~$200 (2~20๋ ์ด์ ์) |
| Application Load Balancer | ์๊ฐ๋น + ์ฒ๋ฆฌ๋ | $20~$50 |
| ๋ฐ์ดํฐ ์ ์ก | GB๋น ๊ณผ๊ธ | $10~$100 (ํธ๋ํฝ์ ๋ฐ๋ผ) |
| RDS (MySQL) | ์๊ฐ๋น + ์คํ ๋ฆฌ์ง | $50~$150 |
| ElastiCache (Redis) | ์๊ฐ๋น ๊ณผ๊ธ | $30~$80 |
| S3 + CloudFront | ์ ์ฅ๋ + ์ ์ก๋ | $10~$50 |
| ์ด ์์ ๋น์ฉ | $150~$630/์ | |
๐ก ๋น์ฉ ์ ๊ฐ ๊ฟํ
์ต์ ์ฉ๋ ์๋ฒ๋ 1๋ ๋๋ 3๋ ์ฝ์ ์ผ๋ก ๊ตฌ๋งคํด.
์จ๋๋งจ๋ ๋๋น ์ต๋ 72% ํ ์ธ! ๐
์: ํญ์ 2๋๋ ์ ์งํ๋ค๋ฉด, ์ด 2๋๋ ์์ฝ ์ธ์คํด์ค๋ก!
์จ๋๋งจ๋ ๋๋น ์ต๋ 90% ์ ๋ ดํด!
๊ฐ์๊ธฐ ํ์๋ ์ ์์ง๋ง, ASG๊ฐ ์๋์ผ๋ก ๋์ฒดํด์ฃผ๋๊น ๊ด์ฐฎ์.
์ถ์ฒ ๋น์จ: ์จ๋๋งจ๋ 50% + ์คํ 50%
์๋ฒฝ ์๊ฐ๋(2~6์)์๋ ์๋ฒ๋ฅผ ์ต์๋ก ์ค์ฌ.
ํธ๋ํฝ์ด ๊ฑฐ์ ์๋ ์๊ฐ์ ์๋ฒ 10๋ ๋๋ฆฌ๋ ๊ฑด ๋ญ๋น์ผ!
์ด๊ฒ๋ง์ผ๋ก๋ ์ 30~40% ๋น์ฉ ์ ๊ฐ ๊ฐ๋ฅ! ๐ฐ
์ด๋ฏธ์ง, CSS, JS ๊ฐ์ ์ ์ ํ์ผ์ CDN์ผ๋ก ์๋นํด.
์๋ฒ ๋ถํ๋ ์ค๊ณ , ๋ฐ์ดํฐ ์ ์ก ๋น์ฉ๋ ์ ์ฝ๋ผ!
ํนํ ํด์ธ ๊ณ ๊ฐ์ด ๋ง๋ค๋ฉด ํ์์ผ. ๐
์ฐ๊ธฐ๋ ๋ง์คํฐ DB๋ก, ์ฝ๊ธฐ๋ ๋ณต์ ๋ณธ์ผ๋ก ๋ถ์ฐํด.
์ผํ๋ชฐ์ ์ฝ๊ธฐ๊ฐ 90% ์ด์์ด๋๊น ํจ๊ณผ์ ์ด์ผ!
๋ง์คํฐ๋ ์์ ์ธ์คํด์ค, ๋ณต์ ๋ณธ์ ํฐ ์ธ์คํด์ค๋ก ๊ตฌ์ฑํ๋ฉด ๋น์ฉ ํจ์จ์ ! ๐
Before: ์จ๋๋งจ๋ t3.medium 10๋ 24์๊ฐ ์ด์
โ ์ $600
After: ์ต์ ํ ์ ์ฉ
โข ์์ฝ ์ธ์คํด์ค 2๋ (์ต์ ์ฉ๋): $60
โข ์คํ ์ธ์คํด์ค ํ๊ท 4๋ (ํผํฌ ์๊ฐ): $40
โข ์ค์ผ์ค ์ค์ผ์ผ๋ง์ผ๋ก ์ผ๊ฐ ์ถ์
โ ์ $180 (70% ์ ๊ฐ!) ๐
๊ฐ์ ์ฑ๋ฅ์ ๋น์ฉ์ 1/3๋ก ์ค์์ด!
๐ ๋น์ฉ ๋ชจ๋ํฐ๋ง ์ค์
// AWS Cost Explorer API๋ก ๋น์ฉ ์ถ์
const costexplorer = new AWS.CostExplorer();
// ์ผ์ผ ๋น์ฉ ์กฐํ
const response = await costexplorer.getCostAndUsage({
TimePeriod: {
Start: '2024-01-01',
End: '2024-01-31'
},
Granularity: 'DAILY',
Metrics: ['UnblendedCost'],
GroupBy: [
{
Type: 'SERVICE',
Key: 'SERVICE'
}
]
}).promise();
// ์์ฐ ์ด๊ณผ ์๋ฆผ ์ค์
const budgets = new AWS.Budgets();
await budgets.createBudget({
AccountId: '123456789012',
Budget: {
BudgetName: 'monthly-shop-budget',
BudgetLimit: {
Amount: '500',
Unit: 'USD'
},
TimeUnit: 'MONTHLY',
BudgetType: 'COST'
},
NotificationsWithSubscribers: [
{
Notification: {
NotificationType: 'ACTUAL',
ComparisonOperator: 'GREATER_THAN',
Threshold: 80,
ThresholdType: 'PERCENTAGE'
},
Subscribers: [
{
SubscriptionType: 'EMAIL',
Address: 'admin@myshop.com'
}
]
}
]
}).promise();
- โ ๏ธ ๋น์ฉ ํญํ ๋ฐฉ์ง ์ฒดํฌ๋ฆฌ์คํธ:
- โ ์ต๋ ์ธ์คํด์ค ์ ์ ํ ์ค์ (์: 20๋)
- โ ์์ฐ ์๋ฆผ ์ค์ (80%, 100% ๋๋ฌ ์)
- โ ์ฌ์ฉํ์ง ์๋ ๋ฆฌ์์ค ์ ๊ธฐ ์ ๊ฒ
- โ ์ค์ผ์ผ ์ธ ์ ์ฑ ๋ฐ๋์ ์ค์
- โ ๋ก๊ทธ ๋ณด๊ด ๊ธฐ๊ฐ ์ค์ (๋ฌดํ์ ์์ด๋ฉด ๋น์ฉ ์ฆ๊ฐ)
- โ ์ค๋ ์ท ์๋ ์ญ์ ์ ์ฑ ์ค์
- โ ๊ฐ๋ฐ/ํ ์คํธ ํ๊ฒฝ์ ์ ๋ฌด ์๊ฐ๋ง ์ด์
๐ ์ฑ๋ฅ ์ต์ ํ ๊ณ ๊ธ ๊ธฐ๋ฒ
์ธํ๋ผ๋ ์๋ฒฝํ๊ฒ ๊ตฌ์ถํ์ด. ์ด์ ํ ๋จ๊ณ ๋ ๋์๊ฐ๋ณผ๊น? ๐ฏ
์ง์ง ๊ณ ์๋ค์ด ์ฐ๋ ์ฑ๋ฅ ์ต์ ํ ๊ธฐ๋ฒ๋ค์ ์๋ ค์ค๊ฒ!
โก 1. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ์ต์ ํ
์๋ฌด๋ฆฌ ์๋ฒ๋ฅผ ๋๋ ค๋ DB๊ฐ ๋ณ๋ชฉ์ด๋ฉด ์์ฉ์์ด!
์ผํ๋ชฐ์์ ๊ฐ์ฅ ๋ง์ด ์คํ๋๋ ์ฟผ๋ฆฌ๋ค์ ์ต์ ํํด๋ณด์. ๐
์ํ ๋ชฉ๋ก ์กฐํ ์ต์ ํ:
-- โ ๋๋ฆฐ ์ฟผ๋ฆฌ (์ธ๋ฑ์ค ์์)
SELECT * FROM products
WHERE category_id = 10
AND status = 'active'
ORDER BY created_at DESC
LIMIT 20;
-- โ
๋น ๋ฅธ ์ฟผ๋ฆฌ (๋ณตํฉ ์ธ๋ฑ์ค ํ์ฉ)
CREATE INDEX idx_category_status_created
ON products(category_id, status, created_at DESC);
SELECT
id, name, price, thumbnail_url, stock
FROM products
WHERE category_id = 10
AND status = 'active'
ORDER BY created_at DESC
LIMIT 20;
-- ํ์ํ ์ปฌ๋ผ๋ง SELECT! (SELECT * ๊ธ์ง)
์ฃผ๋ฌธ ๋ด์ญ ์กฐํ ์ต์ ํ:
-- โ N+1 ์ฟผ๋ฆฌ ๋ฌธ์
// ์ฃผ๋ฌธ ๋ชฉ๋ก ์กฐํ
const orders = await db.query('SELECT * FROM orders WHERE user_id = ?', [userId]);
// ๊ฐ ์ฃผ๋ฌธ๋ง๋ค ์ํ ์ ๋ณด ์กฐํ (N๋ฒ ์ฟผ๋ฆฌ ๋ฐ์!)
for (const order of orders) {
order.items = await db.query('SELECT * FROM order_items WHERE order_id = ?', [order.id]);
}
-- โ
JOIN์ผ๋ก ํ ๋ฒ์ ์กฐํ
SELECT
o.id, o.order_number, o.total_amount, o.status,
oi.product_id, oi.product_name, oi.quantity, oi.price
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE o.user_id = ?
ORDER BY o.created_at DESC
LIMIT 10;
โ ์์ฃผ ์กฐํ๋๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค ์์ฑ
โ ๋ณตํฉ ์ธ๋ฑ์ค ์์ ์ต์ ํ (์นด๋๋๋ฆฌํฐ ๋์ ์)
โ EXPLAIN์ผ๋ก ์ฟผ๋ฆฌ ์คํ ๊ณํ ๋ถ์
โ ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ก๊ทธ ๋ชจ๋ํฐ๋ง
โ ์ปค๋ฅ์ ํ ํฌ๊ธฐ ์ ์ ํ ์ค์
โ ์ฝ๊ธฐ ์ ์ฉ ์ฟผ๋ฆฌ๋ ๋ณต์ ๋ณธ์ผ๋ก ๋ถ์ฐ
๐๏ธ 2. ์บ์ฑ ์ ๋ต
DB ์กฐํ๋ฅผ ์ค์ด๋ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ ์บ์ฑ์ด์ผ!
Redis๋ฅผ ํ์ฉํ ๋ค์ธต ์บ์ฑ ์ ๋ต์ ์์๋ณด์. ๐พ
์ํ ์ ๋ณด ์บ์ฑ:
// ์บ์ ์ฐ์ ์กฐํ ํจํด
async function getProduct(productId) {
const cacheKey = `product:${productId}`;
// 1. Redis ์บ์ ํ์ธ
let product = await redis.get(cacheKey);
if (product) {
console.log('Cache HIT! โก');
return JSON.parse(product);
}
console.log('Cache MISS, DB ์กฐํ ์ค...');
// 2. DB์์ ์กฐํ
product = await db.query(
'SELECT * FROM products WHERE id = ?',
[productId]
);
// 3. Redis์ ์บ์ฑ (1์๊ฐ ์ ํจ)
await redis.setex(
cacheKey,
3600,
JSON.stringify(product)
);
return product;
}
์ธ๊ธฐ ์ํ ๋ชฉ๋ก ์บ์ฑ:
// ์์ฃผ ๋ฐ๋์ง ์๋ ๋ฐ์ดํฐ๋ ๊ธด TTL
async function getPopularProducts() {
const cacheKey = 'products:popular';
let products = await redis.get(cacheKey);
if (products) {
return JSON.parse(products);
}
// ๋ณต์กํ ์ง๊ณ ์ฟผ๋ฆฌ
products = await db.query(`
SELECT
p.id, p.name, p.price, p.thumbnail_url,
COUNT(oi.id) as order_count
FROM products p
JOIN order_items oi ON p.id = oi.product_id
WHERE oi.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY p.id
ORDER BY order_count DESC
LIMIT 20
`);
// 6์๊ฐ ์บ์ฑ (์์ฃผ ์ ๋ฐ๋)
await redis.setex(
cacheKey,
21600,
JSON.stringify(products)
);
return products;
}
์บ์ ๋ฌดํจํ ์ ๋ต:
// ์ํ ์ ๋ณด ์
๋ฐ์ดํธ ์ ์บ์ ์ญ์
async function updateProduct(productId, data) {
// 1. DB ์
๋ฐ์ดํธ
await db.query(
'UPDATE products SET ? WHERE id = ?',
[data, productId]
);
// 2. ๊ด๋ จ ์บ์ ๋ชจ๋ ์ญ์
await redis.del(`product:${productId}`);
await redis.del('products:popular');
await redis.del(`products:category:${data.category_id}`);
// 3. ์บ์ ์๋ฐ (์ ํ์ )
await getProduct(productId); // ์ ๋ฐ์ดํฐ๋ก ์บ์ ์ฌ์์ฑ
}
์งง์ TTL (1~5๋ถ):
โข ์ฌ๊ณ ์๋
โข ์ฅ๋ฐ๊ตฌ๋ ๊ฐ์
โข ์ค์๊ฐ ๋ญํน
์ค๊ฐ TTL (1~6์๊ฐ):
โข ์ํ ์์ธ ์ ๋ณด
โข ์นดํ ๊ณ ๋ฆฌ ๋ชฉ๋ก
โข ์ธ๊ธฐ ์ํ
๊ธด TTL (12~24์๊ฐ):
โข ๋ฐฐ๋ ์ด๋ฏธ์ง
โข ์ ์ฑ ํ์ด์ง
โข ๊ณต์ง์ฌํญ
์๊ตฌ ์บ์ (์๋ ์ญ์ ):
โข ์ํ ์ด๋ฏธ์ง URL
โข ์นดํ ๊ณ ๋ฆฌ ๊ตฌ์กฐ
โข ์ค์ ๊ฐ
๐ 3. API ์๋ต ์ต์ ํ
ํ์ด์ง๋ค์ด์ ์ต์ ํ:
// โ OFFSET ๋ฐฉ์ (ํ์ด์ง๊ฐ ๋ค๋ก ๊ฐ์๋ก ๋๋ ค์ง)
SELECT * FROM products
ORDER BY id DESC
LIMIT 20 OFFSET 10000; -- 10,000๊ฐ๋ฅผ ์ค์บํ๊ณ ๋ฒ๋ฆผ!
// โ
์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
(ํญ์ ๋น ๋ฆ)
SELECT * FROM products
WHERE id < ? -- ๋ง์ง๋ง ์กฐํ ID
ORDER BY id DESC
LIMIT 20;
// API ๊ตฌํ
app.get('/api/products', async (req, res) => {
const { cursor, limit = 20 } = req.query;
const query = cursor
? 'SELECT * FROM products WHERE id < ? ORDER BY id DESC LIMIT ?'
: 'SELECT * FROM products ORDER BY id DESC LIMIT ?';
const params = cursor ? [cursor, limit] : [limit];
const products = await db.query(query, params);
const nextCursor = products.length > 0
? products[products.length - 1].id
: null;
res.json({
data: products,
nextCursor,
hasMore: products.length === limit
});
});
์๋ต ์์ถ:
// Express์์ gzip ์์ถ ํ์ฑํ
const compression = require('compression');
app.use(compression({
level: 6, // ์์ถ ๋ ๋ฒจ (1~9, 6์ด ์ ๋น)
threshold: 1024, // 1KB ์ด์๋ง ์์ถ
filter: (req, res) => {
// ์ด๋ฏธ์ง๋ ์ด๋ฏธ ์์ถ๋์ด ์์ผ๋ ์ ์ธ
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));
// ํ๊ท 70% ์ฉ๋ ๊ฐ์! ๐
๋ถ๋ถ ์๋ต (Partial Response):
// ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ ํ๋๋ง ์์ฒญ
app.get('/api/products/:id', async (req, res) => {
const { fields } = req.query;
let selectFields = '*';
if (fields) {
// fields=id,name,price
selectFields = fields.split(',').join(', ');
}
const product = await db.query(
`SELECT ${selectFields} FROM products WHERE id = ?`,
[req.params.id]
);
res.json(product);
});
// ์ฌ์ฉ ์์:
// GET /api/products/123?fields=id,name,price
// โ ํ์ํ ๋ฐ์ดํฐ๋ง ์ ์ก! ๋คํธ์ํฌ ๋น์ฉ ์ ๊ฐ!
๐ผ๏ธ 4. ์ด๋ฏธ์ง ์ต์ ํ
์ผํ๋ชฐ์์ ์ด๋ฏธ์ง๋ ํธ๋ํฝ์ 70% ์ด์์ ์ฐจ์งํด!
์ด๋ฏธ์ง ์ต์ ํ๋ ํ์์ผ. ๐ธ
CloudFront + Lambda@Edge๋ก ์ค์๊ฐ ๋ฆฌ์ฌ์ด์ง:
// Lambda@Edge ํจ์
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const uri = request.uri;
// URL์์ ํฌ๊ธฐ ํ๋ผ๋ฏธํฐ ์ถ์ถ
// ์: /images/product.jpg?w=300&h=300
const params = new URLSearchParams(request.querystring);
const width = params.get('w');
const height = params.get('h');
if (width || height) {
// S3์์ ์๋ณธ ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๊ธฐ
const s3 = new AWS.S3();
const originalImage = await s3.getObject({
Bucket: 'my-shop-images',
Key: uri.substring(1)
}).promise();
// Sharp๋ก ๋ฆฌ์ฌ์ด์ง
const sharp = require('sharp');
const resizedImage = await sharp(originalImage.Body)
.resize(parseInt(width), parseInt(height), {
fit: 'cover',
position: 'center'
})
.webp({ quality: 80 }) // WebP ๋ณํ!
.toBuffer();
return {
status: '200',
headers: {
'content-type': [{ value: 'image/webp' }],
'cache-control': [{ value: 'max-age=31536000' }]
},
body: resizedImage.toString('base64'),
bodyEncoding: 'base64'
};
}
return request;
};
ํ๋ก ํธ์๋์์ ๋ฐ์ํ ์ด๋ฏธ์ง:
<!-- ๋๋ฐ์ด์ค ํฌ๊ธฐ์ ๋ง๋ ์ด๋ฏธ์ง ์๋ ์ ํ -->
<picture>
<source
srcset="https://cdn.myshop.com/product.jpg?w=400&h=400"
media="(max-width: 768px)">
<source
srcset="https://cdn.myshop.com/product.jpg?w=800&h=800"
media="(max-width: 1200px)">
<img
src="https://cdn.myshop.com/product.jpg?w=1200&h=1200"
alt="์ํ ์ด๋ฏธ์ง"
loading="lazy"> <!-- ์ง์ฐ ๋ก๋ฉ -->
</picture>
โ WebP ํฌ๋งท ์ฌ์ฉ (JPEG ๋๋น 30% ์์)
โ ์ ์ ํ ํฌ๊ธฐ๋ก ๋ฆฌ์ฌ์ด์ง (์๋ณธ ๊ทธ๋๋ก ์ฐ์ง ๋ง๊ธฐ)
โ CDN ์บ์ฑ (CloudFront, Cloudflare ๋ฑ)
โ Lazy Loading ์ ์ฉ
โ ์์ถ ํ์ง 80% (๋์ผ๋ก ์ฐจ์ด ์ ๋จ)
โ ์ธ๋ค์ผ์ ๋ณ๋ ์์ฑ (๋ชฉ๋ก์ฉ ์์ ์ด๋ฏธ์ง)
๐ฏ ์ค์ ์๋๋ฆฌ์ค: ๋ธ๋ํ๋ผ์ด๋ฐ์ด ๋์
์, ์ด์ ๋ฐฐ์ด ๊ฑธ ์ข
ํฉํด์ ์ค์ ์๋๋ฆฌ์ค๋ฅผ ์๋ฎฌ๋ ์ด์
ํด๋ณด์! ๐ฎ
๋ธ๋ํ๋ผ์ด๋ฐ์ด ๊ฐ์ ๋๊ท๋ชจ ์ด๋ฒคํธ๋ฅผ ์ด๋ป๊ฒ ์ค๋นํ๊ณ ๋์ํ๋์ง ์์๋ณผ๊ฒ.
๐ D-7: ์ฌ์ ์ค๋น
// Apache JMeter ๋๋ k6๋ก ๋ถํ ํ
์คํธ
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '5m', target: 100 }, // ์๋ฐ์
{ duration: '10m', target: 1000 }, // ์ ์ ๋ถํ
{ duration: '5m', target: 5000 }, // ํผํฌ ๋ถํ
{ duration: '10m', target: 5000 }, // ์ ์ง
{ duration: '5m', target: 0 }, // ์ฟจ๋ค์ด
],
};
export default function () {
// ๋ฉ์ธ ํ์ด์ง
let res = http.get('https://myshop.com');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
// ์ํ ๋ชฉ๋ก
res = http.get('https://myshop.com/api/products');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500
});
sleep(2);
// ์ํ ์์ธ
res = http.get('https://myshop.com/api/products/123');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
โข ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ๋ชจ๋ ์ ๊ฑฐ
โข ์ธ๋ฑ์ค ์ฌ๊ตฌ์ฑ
โข ํต๊ณ ์ ๋ณด ์ ๋ฐ์ดํธ
โข ๋ถํ์ํ ๋ฐ์ดํฐ ์์นด์ด๋น
โข ์ปค๋ฅ์ ํ ํฌ๊ธฐ ์ฆ๊ฐ
// ์ฃผ์ ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ ์บ์ฑ
async function warmupCache() {
console.log('์บ์ ์๋ฐ ์์...');
// ์ธ๊ธฐ ์ํ 100๊ฐ
const popularProducts = await db.query(`
SELECT id FROM products
ORDER BY view_count DESC
LIMIT 100
`);
for (const product of popularProducts) {
await getProduct(product.id); // ์บ์์ ์ ์ฅ
}
// ๋ชจ๋ ์นดํ
๊ณ ๋ฆฌ
const categories = await db.query('SELECT id FROM categories');
for (const category of categories) {
await getCategoryProducts(category.id);
}
console.log('์บ์ ์๋ฐ ์๋ฃ! โ
');
}
// ์ด๋ฒคํธ ์์ 1์๊ฐ ์ ์ ์คํ
schedule.scheduleJob('0 18 * * *', warmupCache);
// ์ด๋ฒคํธ ๊ธฐ๊ฐ ๋์ ๊ณต๊ฒฉ์ ์ธ ์ค์ผ์ผ๋ง
{
"TargetTrackingScalingPolicyConfiguration": {
"TargetValue": 40.0, // ํ์ 50% โ 40%๋ก ๋ฎ์ถค
"ScaleOutCooldown": 30, // ํ์ 60์ด โ 30์ด๋ก ๋จ์ถ
"ScaleInCooldown": 600 // ์ถ์๋ ์ ์คํ๊ฒ
}
}
๐ D-1: ์ต์ข ์ ๊ฒ
โก ์์ฝ ์ค์ผ์ผ๋ง ์ค์ ํ์ธ (์ด๋ฒคํธ ์์ 1์๊ฐ ์ ๋ถํฐ ์๋ฒ ์ฆ์ค)
โก ์ต๋ ์ธ์คํด์ค ์ ์ํฅ (20๋ โ 50๋)
โก ์๋ฆผ ์ฑ๋ ํ ์คํธ (Slack, SMS ์ ์ ์๋ ํ์ธ)
โก ๋ฐฑ์ ์๋ฃ (DB, Redis ์ค๋ ์ท)
โก ๋กค๋ฐฑ ๊ณํ ์๋ฆฝ
โก ์จ์ฝ ๋ด๋น์ ์ง์
โก ๋ชจ๋ํฐ๋ง ๋์๋ณด๋ ์ค๋น
โก CDN ์บ์ ํ๋ฆฌ๋ก๋ฉ
โก ๊ฒฐ์ ์์คํ ์ด์คํ ํ์ธ
โก ๊ณ ๊ฐ์ผํฐ ์ธ๋ ฅ ์ฆ์ ํ์ธ
๐ฅ D-Day: ์ค์๊ฐ ๋์
์ด๋ฒคํธ ์์ (19:00):
// ์ค์๊ฐ ๋ชจ๋ํฐ๋ง ์คํฌ๋ฆฝํธ
const monitor = setInterval(async () => {
const metrics = await getMetrics();
console.log(`
โฐ ${new Date().toLocaleTimeString()}
๐ฅ ๋์ ์ ์์: ${metrics.activeUsers}
๐ฅ๏ธ ํ์ฑ ์๋ฒ: ${metrics.activeInstances}
๐ CPU ํ๊ท : ${metrics.avgCPU}%
โก ์๋ต ์๊ฐ: ${metrics.avgResponseTime}ms
๐ฐ ์ฃผ๋ฌธ ์: ${metrics.orderCount}
โ ์๋ฌ์จ: ${metrics.errorRate}%
`);
// ์ด์ ์งํ ๊ฐ์ง
if (metrics.errorRate > 1) {
await sendAlert('๐จ ์๋ฌ์จ 1% ์ด๊ณผ!', 'critical');
}
if (metrics.avgResponseTime > 1000) {
await sendAlert('โ ๏ธ ์๋ต ์๊ฐ 1์ด ์ด๊ณผ', 'warning');
}
if (metrics.activeInstances >= 45) {
await sendAlert('๐ ์๋ฒ 45๋ ๋๋ฌ, ์ต๋์น ์๋ฐ', 'warning');
}
}, 30000); // 30์ด๋ง๋ค ์ฒดํฌ
19:30 - ํธ๋ํฝ ๊ธ์ฆ ๊ฐ์ง:
// ์๋ ์ค์ผ์ผ๋ง ๋ก๊ทธ
[19:30:15] ๐ CPU ์ฌ์ฉ๋ฅ 45% ๋๋ฌ
[19:30:45] โ ์๋ฒ 2๋ ์ถ๊ฐ ์์ (์ด 6๋ โ 8๋)
[19:31:20] โ
์ ์๋ฒ 2๋ ํฌ์ค์ฒดํฌ ํต๊ณผ
[19:31:25] ๐ ๋ก๋๋ฐธ๋ฐ์์ ์ฐ๊ฒฐ ์๋ฃ
[19:31:30] ๐ ํธ๋ํฝ ๋ถ์ฐ ์์
[19:35:20] ๐ CPU ์ฌ์ฉ๋ฅ 50% ๋๋ฌ
[19:35:50] โ ์๋ฒ 3๋ ์ถ๊ฐ ์์ (์ด 8๋ โ 11๋)
[19:36:30] โ
์ ์๋ฒ 3๋ ์ค๋น ์๋ฃ
[19:40:00] ๐ ๋์ ์ ์์ 10,000๋ช
๋ํ!
[19:40:05] ๐ ์์คํ
์์ ์ ์ผ๋ก ์๋ ์ค
[19:40:10] ๐ฐ ๋ถ๋น ์ฃผ๋ฌธ ์: 150๊ฑด
20:00 - ํผํฌ ํ์:
โข ๋์ ์ ์์: 15,000๋ช
โข ํ์ฑ ์๋ฒ: 18๋
โข ํ๊ท CPU: 55%
โข ํ๊ท ์๋ต ์๊ฐ: 320ms
โข ์๋ฌ์จ: 0.3%
โข ๋ถ๋น ์ฃผ๋ฌธ: 200๊ฑด
โข ๋์ ๋งค์ถ: 3์ต ์
โ ๋ชจ๋ ์งํ ์ ์ ๋ฒ์!
22:00 - ํธ๋ํฝ ๊ฐ์ ์์:
[22:00:00] ๐ ํธ๋ํฝ ๊ฐ์ ๊ฐ์ง
[22:00:05] โธ๏ธ ์ค์ผ์ผ ์ธ ๋๊ธฐ ์ค (์ฟจ๋ค์ด 10๋ถ)
[22:10:00] โ ์๋ฒ 2๋ ์ ๊ฑฐ ์์ (18๋ โ 16๋)
[22:10:30] ๐ ๋ก๋๋ฐธ๋ฐ์์์ ์ฐ๊ฒฐ ํด์
[22:11:00] ๐พ ์๋ฒ ์ข
๋ฃ ์ ์ธ์
๋๋ ์ธ ์๋ฃ
[22:11:30] โ
์๋ฒ 2๋ ์์ ํ๊ฒ ์ข
๋ฃ
[22:30:00] โ ์๋ฒ 4๋ ์ถ๊ฐ ์ ๊ฑฐ (16๋ โ 12๋)
[23:00:00] โ ์๋ฒ 4๋ ์ถ๊ฐ ์ ๊ฑฐ (12๋ โ 8๋)
[00:00:00] โ ์๋ฒ 4๋ ์ถ๊ฐ ์ ๊ฑฐ (8๋ โ 4๋)
[02:00:00] ๐ ์ต์ ์ฉ๋ 2๋๋ก ๋ณต๊ท
๐ D+1: ์ฌํ ๋ถ์
// ์ด๋ฒคํธ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ
{
"event": "Black Friday 2024",
"duration": "7 hours",
"summary": {
"peakConcurrentUsers": 15234,
"totalOrders": 45678,
"totalRevenue": "12์ต ์",
"averageResponseTime": "285ms",
"errorRate": "0.25%",
"uptime": "100%"
},
"infrastructure": {
"maxInstances": 18,
"minInstances": 2,
"averageInstances": 9.5,
"totalScaleOutEvents": 12,
"totalScaleInEvents": 10,
"estimatedCost": "$180"
},
"performance": {
"cacheHitRate": "92%",
"databaseQueryTime": "45ms",
"cdnHitRate": "88%",
"apiSuccessRate": "99.75%"
},
"issues": [
{
"time": "19:45",
"issue": "Redis ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ 85% ๋๋ฌ",
"action": "๋ถํ์ํ ์บ์ ์ญ์ ",
"resolved": "19:47"
},
{
"time": "20:15",
"issue": "ํน์ API ์๋ต ์๊ฐ 1.2์ด",
"action": "์ฟผ๋ฆฌ ์ต์ ํ ๋ฐ ์บ์ ์ถ๊ฐ",
"resolved": "20:20"
}
],
"improvements": [
"Redis ํด๋ฌ์คํฐ ๋ฉ๋ชจ๋ฆฌ ์ฆ์ค ํ์",
"ํน์ ์ํ ํ์ด์ง ์ถ๊ฐ ์ต์ ํ ํ์",
"๊ฒฐ์ API ํ์์์ ์ค์ ์กฐ์ ํ์"
]
}
1. ์ฌ์ ๋ถํ ํ ์คํธ๋ก ๋ณ๋ชฉ ์ง์ ๋ฏธ๋ฆฌ ํ์
2. ๊ณต๊ฒฉ์ ์ธ ์ค์ผ์ผ๋ง ์ ์ฑ ์ผ๋ก ๋น ๋ฅธ ๋์
3. ์บ์ ์๋ฐ์ผ๋ก ์ด๊ธฐ ๋ถํ ๊ฐ์
4. ์ค์๊ฐ ๋ชจ๋ํฐ๋ง์ผ๋ก ์ฆ๊ฐ ๋์
5. ์์ฝ ์ค์ผ์ผ๋ง์ผ๋ก ์ฌ์ ์ค๋น
6. ๋กค๋ฐฑ ๊ณํ ์๋ฆฝ์ผ๋ก ์์ ์ฅ์น ๋ง๋ จ
๐ ๏ธ ๋ค์ํ ํ๋ซํผ๋ณ ๊ตฌํ ๊ฐ์ด๋
AWS๋ง ์๋ ๊ฒ ์๋์ผ! ๋ค๋ฅธ ํด๋ผ์ฐ๋ ํ๋ซํผ์์๋ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณด์. โ๏ธ
๐ท Azure ๊ตฌํ
Azure Load Balancer + VM Scale Sets:
// Azure CLI๋ก ๊ตฌ์ฑ
# ๋ฆฌ์์ค ๊ทธ๋ฃน ์์ฑ
az group create --name myshop-rg --location koreacentral
# ๊ฐ์ ๋คํธ์ํฌ ์์ฑ
az network vnet create \
--resource-group myshop-rg \
--name myshop-vnet \
--address-prefix 10.0.0.0/16 \
--subnet-name myshop-subnet \
--subnet-prefix 10.0.1.0/24
# Application Gateway (L7 ๋ก๋๋ฐธ๋ฐ์) ์์ฑ
az network application-gateway create \
--name myshop-appgw \
--resource-group myshop-rg \
--vnet-name myshop-vnet \
--subnet myshop-subnet \
--capacity 2 \
--sku Standard_v2 \
--http-settings-cookie-based-affinity Disabled \
--frontend-port 80 \
--http-settings-port 3000 \
--http-settings-protocol Http
# VM Scale Set ์์ฑ
az vmss create \
--resource-group myshop-rg \
--name myshop-vmss \
--image UbuntuLTS \
--upgrade-policy-mode automatic \
--instance-count 2 \
--admin-username azureuser \
--generate-ssh-keys \
--load-balancer myshop-appgw
# ์คํ ์ค์ผ์ผ๋ง ๊ท์น ์ค์
az monitor autoscale create \
--resource-group myshop-rg \
--resource myshop-vmss \
--resource-type Microsoft.Compute/virtualMachineScaleSets \
--name autoscale-myshop \
--min-count 2 \
--max-count 20 \
--count 4
# CPU ๊ธฐ๋ฐ ์ค์ผ์ผ ์์
az monitor autoscale rule create \
--resource-group myshop-rg \
--autoscale-name autoscale-myshop \
--condition "Percentage CPU > 70 avg 5m" \
--scale out 2
# CPU ๊ธฐ๋ฐ ์ค์ผ์ผ ์ธ
az monitor autoscale rule create \
--resource-group myshop-rg \
--autoscale-name autoscale-myshop \
--condition "Percentage CPU < 30 avg 5m" \
--scale in 1
๐ถ Google Cloud Platform ๊ตฌํ
Cloud Load Balancing + Managed Instance Groups:
# ์ธ์คํด์ค ํ
ํ๋ฆฟ ์์ฑ
gcloud compute instance-templates create myshop-template \
--machine-type=n1-standard-2 \
--image-family=ubuntu-2004-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=20GB \
--tags=http-server \
--metadata-from-file startup-script=startup.sh
# ๊ด๋ฆฌํ ์ธ์คํด์ค ๊ทธ๋ฃน ์์ฑ
gcloud compute instance-groups managed create myshop-mig \
--base-instance-name=myshop-vm \
--template=myshop-template \
--size=2 \
--zone=asia-northeast3-a
# ์คํ ์ค์ผ์ผ๋ง ์ค์
gcloud compute instance-groups managed set-autoscaling myshop-mig \
--zone=asia-northeast3-a \
--min-num-replicas=2 \
--max-num-replicas=20 \
--target-cpu-utilization=0.6 \
--cool-down-period=60
# HTTP ํฌ์ค์ฒดํฌ ์์ฑ
gcloud compute health-checks create http myshop-health-check \
--port=3000 \
--request-path=/health \
--check-interval=30s \
--timeout=10s \
--unhealthy-threshold=3 \
--healthy-threshold=2
# ๋ฐฑ์๋ ์๋น์ค ์์ฑ
gcloud compute backend-services create myshop-backend \
--protocol=HTTP \
--health-checks=myshop-health-check \
--global
# ์ธ์คํด์ค ๊ทธ๋ฃน์ ๋ฐฑ์๋์ ์ถ๊ฐ
gcloud compute backend-services add-backend myshop-backend \
--instance-group=myshop-mig \
--instance-group-zone=asia-northeast3-a \
--balancing-mode=UTILIZATION \
--max-utilization=0.8 \
--global
# URL ๋งต ์์ฑ
gcloud compute url-maps create myshop-url-map \
--default-service=myshop-backend
# HTTP ํ๋ก์ ์์ฑ
gcloud compute target-http-proxies create myshop-http-proxy \
--url-map=myshop-url-map
# ์ ์ญ ์ ๋ฌ ๊ท์น ์์ฑ (๋ก๋๋ฐธ๋ฐ์ IP)
gcloud compute forwarding-rules create myshop-forwarding-rule \
--global \
--target-http-proxy=myshop-http-proxy \
--ports=80
๐ณ Kubernetes ๊ธฐ๋ฐ ๊ตฌํ
์์ฆ ๋์ธ๋ Kubernetes์ผ! ํด๋ผ์ฐ๋ ์ข ์์ฑ ์์ด ์ด๋์๋ ์คํ ๊ฐ๋ฅํด. ๐
# Deployment ์ค์ (deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myshop-deployment
spec:
replicas: 4
selector:
matchLabels:
app: myshop
template:
metadata:
labels:
app: myshop
spec:
containers:
- name: myshop
image: myshop:latest
ports:
- containerPort: 3000
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
# HorizontalPodAutoscaler ์ค์
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myshop-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myshop-deployment
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
selectPolicy: Max
---
# Service ์ค์ (LoadBalancer)
apiVersion: v1
kind: Service
metadata:
name: myshop-service
spec:
type: LoadBalancer
selector:
app: myshop
ports:
- protocol: TCP
port: 80
targetPort: 3000
---
# Ingress ์ค์ (L7 ๋ก๋๋ฐธ๋ฐ์ฑ)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myshop-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- myshop.com
secretName: myshop-tls
rules:
- host: myshop.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myshop-service
port:
number: 80
โ ํด๋ผ์ฐ๋ ์ค๋ฆฝ์ : AWS, Azure, GCP ์ด๋์๋ ๋์ผํ๊ฒ ์๋
โ ์ ์ธ์ ์ค์ : YAML๋ก ์ธํ๋ผ๋ฅผ ์ฝ๋๋ก ๊ด๋ฆฌ
โ ์๋ ๋ณต๊ตฌ: Pod๊ฐ ์ฃฝ์ผ๋ฉด ์๋์ผ๋ก ์ฌ์์
โ ๋กค๋ง ์ ๋ฐ์ดํธ: ๋ฌด์ค๋จ ๋ฐฐํฌ ๊ธฐ๋ณธ ์ง์
โ ์๋น์ค ๋ฉ์: Istio ๊ฐ์ ๊ณ ๊ธ ๊ธฐ๋ฅ ํ์ฉ ๊ฐ๋ฅ
โ ๋น์ฉ ํจ์จ: ๋ฆฌ์์ค ํ์ฉ๋ฅ ๊ทน๋ํ
๐ง ์จํ๋ ๋ฏธ์ค ํ๊ฒฝ ๊ตฌํ
ํด๋ผ์ฐ๋๋ฅผ ๋ชป ์ฐ๋ ์ํฉ์ด๋ผ๋ฉด? ์จํ๋ ๋ฏธ์ค์์๋ ๊ฐ๋ฅํด! ๐ช
HAProxy + Keepalived ์กฐํฉ:
# HAProxy ์ค์ (/etc/haproxy/haproxy.cfg)
global
log /dev/log local0
maxconn 4096
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http_front
bind *:80
stats uri /haproxy?stats
default_backend http_back
backend http_back
balance roundrobin
option httpchk GET /health
http-check expect status 200
server web1 192.168.1.101:3000 check inter 2000 rise 2 fall 3
server web2 192.168.1.102:3000 check inter 2000 rise 2 fall 3
server web3 192.168.1.103:3000 check inter 2000 rise 2 fall 3
server web4 192.168.1.104:3000 check inter 2000 rise 2 fall 3 backup
์๋ ์ค์ผ์ผ๋ง ์คํฌ๋ฆฝํธ:
#!/bin/bash
# auto_scale.sh - ๊ฐ๋จํ ์คํ ์ค์ผ์ผ๋ง ์คํฌ๋ฆฝํธ
THRESHOLD_HIGH=70
THRESHOLD_LOW=30
MAX_SERVERS=10
MIN_SERVERS=2
while true; do
# ํ์ฌ CPU ์ฌ์ฉ๋ฅ ํ์ธ
CPU_USAGE=$(ssh web1 "top -bn1 | grep 'Cpu(s)' | awk '{print \$2}' | cut -d'%' -f1")
ACTIVE_SERVERS=$(grep "^ server web" /etc/haproxy/haproxy.cfg | grep -v backup | wc -l)
echo "[$(date)] CPU: ${CPU_USAGE}%, ํ์ฑ ์๋ฒ: ${ACTIVE_SERVERS}๋"
# ์ค์ผ์ผ ์์ ์กฐ๊ฑด
if (( $(echo "$CPU_USAGE > $THRESHOLD_HIGH" | bc -l) )) && [ $ACTIVE_SERVERS -lt $MAX_SERVERS ]; then
echo "๐ ์๋ฒ ์ถ๊ฐ ์ค..."
NEXT_SERVER=$((ACTIVE_SERVERS + 1))
# ์ VM ์์
virsh start web${NEXT_SERVER}
sleep 30
# HAProxy์ ์ถ๊ฐ
sed -i "/backend http_back/a\ server web${NEXT_SERVER} 192.168.1.1${NEXT_SERVER}:3000 check" /etc/haproxy/haproxy.cfg
systemctl reload haproxy
echo "โ
web${NEXT_SERVER} ์ถ๊ฐ ์๋ฃ"
fi
# ์ค์ผ์ผ ์ธ ์กฐ๊ฑด
if (( $(echo "$CPU_USAGE < $THRESHOLD_LOW" | bc -l) )) && [ $ACTIVE_SERVERS -gt $MIN_SERVERS ]; then
echo "โ ์๋ฒ ์ ๊ฑฐ ์ค..."
# HAProxy์์ ์ ๊ฑฐ
sed -i "/server web${ACTIVE_SERVERS}/d" /etc/haproxy/haproxy.cfg
systemctl reload haproxy
sleep 60 # ๋๋ ์ธ ์๊ฐ
# VM ์ข
๋ฃ
virsh shutdown web${ACTIVE_SERVERS}
echo "โ
web${ACTIVE_SERVERS} ์ ๊ฑฐ ์๋ฃ"
fi
sleep 60 # 1๋ถ๋ง๋ค ์ฒดํฌ
done
โข ๋ฌผ๋ฆฌ ์๋ฒ ์์ ์ ํ์ด ์์ด ์์ ํ ์คํ ์ค์ผ์ผ๋ง ์ด๋ ค์
โข ํ๋์จ์ด ์ฅ์ ์ ์๋ ๋์ ํ์
โข ์ด๊ธฐ ํฌ์ ๋น์ฉ์ด ๋์
โข ์ ์ง๋ณด์ ์ธ๋ ฅ ํ์
์ถ์ฒ: ํ์ด๋ธ๋ฆฌ๋ ํด๋ผ์ฐ๋ ์ ๋ต!
ํ์์๋ ์จํ๋ ๋ฏธ์ค, ํผํฌ ์๊ฐ์๋ง ํด๋ผ์ฐ๋ ๋ฒ์คํ ๐
๐ ๋ง๋ฌด๋ฆฌ: ํต์ฌ ์์ฝ ๋ฐ ์ฒดํฌ๋ฆฌ์คํธ
์! ์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐
์ด์ ์ผํ๋ชฐ ํธ๋ํฝ ๊ธ์ฆ์ ์๋ฒฝํ๊ฒ ๋์ํ ์ ์๋ ์ง์์ ๊ฐ์ท์ด.
๋ง์ง๋ง์ผ๋ก ํต์ฌ ๋ด์ฉ์ ์ ๋ฆฌํด๋ณผ๊ฒ! ๐
๐ฏ ํต์ฌ ๊ฐ๋ ์์ฝ
โข ํธ๋ํฝ์ ๋ฐ๋ผ ์๋ฒ๋ฅผ ์๋์ผ๋ก ๋๋ฆฌ๊ณ ์ค์ด๋ ๊ธฐ์
โข ์ํ ํ์ฅ(Scale Out)์ด ์ผํ๋ชฐ์ ์ต์
โข ๋์ ์ถ์ ์ ์ฑ ์ถ์ฒ (CPU 50% ์ ์ง)
โข ์ฟจ๋ค์ด ์๊ฐ ์ต์ 5๋ถ ์ค์
โข ์ต์/์ต๋ ์ฉ๋ ๋ฐ๋์ ์ค์
2๏ธโฃ ๋ก๋๋ฐธ๋ฐ์ฑ (Load Balancing)
โข ํธ๋ํฝ์ ์ฌ๋ฌ ์๋ฒ๋ก ๋ถ์ฐํ๋ ๊ธฐ์
โข L7 ๋ก๋๋ฐธ๋ฐ์ ํ์ (URL ๊ธฐ๋ฐ ๋ผ์ฐํ )
โข ๋ผ์ด๋ ๋ก๋น + ์ต์ ์ฐ๊ฒฐ ํผํฉ ์ถ์ฒ
โข ํฌ์ค์ฒดํฌ๋ก ์ฅ์ ์๋ฒ ์๋ ์ ์ธ
โข SSL ํฐ๋ฏธ๋ค์ด์ ์ผ๋ก ์๋ฒ ๋ถ๋ด ๊ฐ์
3๏ธโฃ ์ธ์ ๊ด๋ฆฌ
โข Redis ์ค์ ์ธ์ ์คํ ์ด ๋๋ JWT ํ ํฐ
โข ์๋ฒ ๋ฉ๋ชจ๋ฆฌ ์ธ์ ์ ์ ๋ ๊ธ์ง
โข ์ฅ๋ฐ๊ตฌ๋๋ Redis ๋๋ DB์ ์ ์ฅ
โข ์ธ์ ๋ง๋ฃ ์ ์ฑ ๋ช ํํ ์ค์
4๏ธโฃ ์ฑ๋ฅ ์ต์ ํ
โข ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ์ต์ ํ (์ธ๋ฑ์ค, N+1 ํด๊ฒฐ)
โข ๋ค์ธต ์บ์ฑ ์ ๋ต (Redis, CDN)
โข ์ด๋ฏธ์ง ์ต์ ํ (WebP, ๋ฆฌ์ฌ์ด์ง)
โข API ์๋ต ์์ถ (gzip)
โข ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
5๏ธโฃ ๋ชจ๋ํฐ๋ง ๋ฐ ์๋ฆผ
โข ๊ณจ๋ ์๊ทธ๋ ์ง์ค ๋ชจ๋ํฐ๋ง
โข ์๋ฆผ ๋ ๋ฒจ ๊ตฌ๋ถ (Critical, Warning, Info)
โข ์ค์๊ฐ ๋์๋ณด๋ ๊ตฌ์ถ
โข ์๋ ๋ณต๊ตฌ ์ค์
โข ์ ๊ธฐ ๋ฆฌํฌํธ ๋ฐ ๊ฐ์
โ ๊ตฌ์ถ ์ ์ฒดํฌ๋ฆฌ์คํธ
โก ๊ฐ์ฉ ์์ญ 2๊ฐ ์ด์ ์ฌ์ฉ
โก VPC ๋ฐ ์๋ธ๋ท ๊ตฌ์ฑ
โก ๋ณด์ ๊ทธ๋ฃน ๋ ์ด์ด๋ณ ์ค์
โก ๋ก๋๋ฐธ๋ฐ์ L7 ์ ํ
โก ์คํ ์ค์ผ์ผ๋ง ๊ทธ๋ฃน ์์ฑ
โก RDS ๋ค์ค AZ ๋ฐฐํฌ
โก Redis ํด๋ฌ์คํฐ ๊ตฌ์ฑ
โก ๋ฌด์ํ(Stateless) ์ํคํ ์ฒ
โก ํฌ์ค์ฒดํฌ ์๋ํฌ์ธํธ ๊ตฌํ
โก ํ๊ฒฝ๋ณ์๋ก ์ค์ ๊ด๋ฆฌ
โก ๋ก๊ทธ ์ค์ํ (CloudWatch, ELK)
โก ์๋ฌ ์ถ์ (Sentry ๋ฑ)
โก ๋ฐฐํฌ ์๋ํ (CI/CD)
โก ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ ์ ๊ฑฐ
โก ์ธ๋ฑ์ค ์ต์ ํ
โก ์ฝ๊ธฐ ์ ์ฉ ๋ณต์ ๋ณธ ๊ตฌ์ฑ
โก ์ปค๋ฅ์ ํ ์ค์
โก ์ฟผ๋ฆฌ ์บ์ฑ ํ์ฑํ
โก ์ ๊ธฐ ๋ฐฑ์ ์ค์
โก Redis ํด๋ฌ์คํฐ ๊ตฌ์ถ
โก ์บ์ ํค ๋ค์ด๋ฐ ๊ท์น
โก TTL ์ ์ฑ ์๋ฆฝ
โก ์บ์ ๋ฌดํจํ ์ ๋ต
โก CDN ์ค์ (CloudFront ๋ฑ)
โก ๋ธ๋ผ์ฐ์ ์บ์ฑ ํค๋
โก CloudWatch ๋์๋ณด๋
โก ์๋ฆผ ์ฑ๋ ์ค์ (Slack, SMS)
โก ์์ฐ ์๋ฆผ ์ค์
โก ๋ก๊ทธ ๋ถ์ ๋๊ตฌ
โก APM ๋๊ตฌ (New Relic, Datadog)
โก ์ ๊ธฐ ๋ฆฌํฌํธ ์๋ํ
โก HTTPS ์ธ์ฆ์ (Let's Encrypt, ACM)
โก WAF ๊ท์น ์ค์
โก DDoS ๋ฐฉ์ด (Shield)
โก ๋ณด์ ํค๋ ์ค์
โก ์ ๊ธฐ ๋ณด์ ๊ฐ์ฌ
โก ์นจํฌ ํ ์คํธ
โก ์์ฝ ์ธ์คํด์ค ๊ตฌ๋งค
โก ์คํ ์ธ์คํด์ค ํ์ฉ
โก ์ค์ผ์ค ์ค์ผ์ผ๋ง
โก ๋ถํ์ํ ๋ฆฌ์์ค ์ ๋ฆฌ
โก ๋น์ฉ ๋ชจ๋ํฐ๋ง ์ค์
โก ์๊ฐ ๋น์ฉ ๋ฆฌ๋ทฐ
โก ์ ๊ธฐ ๋ฐฑ์ ์๋ํ
โก ๋ณต๊ตฌ ์ ์ฐจ ๋ฌธ์ํ
โก ๋กค๋ฐฑ ๊ณํ ์๋ฆฝ
โก ์ฌํด ๋ณต๊ตฌ ํ๋ จ
โก ๋ค์ค ๋ฆฌ์ ๊ณ ๋ ค
โก ๋น์ ์ฐ๋ฝ๋ง
๐ ๋ค์ ๋จ๊ณ
์ด์ ๊ธฐ๋ณธ์ ๋ง์คํฐํ์ด! ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด: ๐
1. ๊ณ ๊ธ ์ฃผ์ :
โข ์๋น์ค ๋ฉ์ (Istio, Linkerd)
โข ์๋ฒ๋ฆฌ์ค ์ํคํ
์ฒ (Lambda, Cloud Functions)
โข ๋ง์ดํฌ๋ก์๋น์ค ํจํด
โข ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ
์ฒ
โข CQRS ๋ฐ Event Sourcing
2. ์ค์ต ํ๋ก์ ํธ:
โข ๊ฐ๋จํ ์ผํ๋ชฐ ํ๋กํ ํ์
๊ตฌ์ถ
โข ๋ถํ ํ
์คํธ ์๋๋ฆฌ์ค ์์ฑ
โข ๋ชจ๋ํฐ๋ง ๋์๋ณด๋ ์ปค์คํฐ๋ง์ด์ง
โข ๋น์ฉ ์ต์ ํ ์๋ฎฌ๋ ์ด์
โข ์ฅ์ ๋์ ํ๋ จ
3. ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ:
โข AWS/Azure/GCP ์ฌ์ฉ์ ๊ทธ๋ฃน
โข DevOps Korea ์ปค๋ฎค๋ํฐ
โข Stack Overflow ์ง๋ฌธ/๋ต๋ณ
โข ๊ธฐ์ ๋ธ๋ก๊ทธ ์ด์
โข ์คํ์์ค ํ๋ก์ ํธ ๊ธฐ์ฌ
์ด๋ฐ ๊ณ ๊ธ ์ง์์ ๊ฐ์ง๊ณ ์๋ค๋ฉด ์ฌ๋ฅ๋ท์์ ์ปจ์คํ ์ด๋ ๊ฐ์ ์ฌ๋ฅ์ ๊ณต์ ํด๋ณด๋ ๊ฑด ์ด๋?
"์ผํ๋ชฐ ์ธํ๋ผ ๊ตฌ์ถ ์ปจ์คํ ", "AWS ์คํ ์ค์ผ์ผ๋ง ์ค์ ๋ํ", "์ฑ๋ฅ ์ต์ ํ ์ง๋จ" ๊ฐ์ ์ฌ๋ฅ์ผ๋ก ์์ต๋ ์ฐฝ์ถํ๊ณ ๊ฒฝํ๋ ์์ ์ ์์ด! ๐ฐ
๋๋ ๋ฐ๋๋ก ๋ ์ ๋ฌธ์ ์ธ ๋์์ด ํ์ํ๋ค๋ฉด ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ณผ ์๋ ์์ง.
์ค์ ๊ฒฝํ์ด ํ๋ถํ ๊ฐ๋ฐ์๋ค์ด ๋ง์ผ๋๊น ๋์๋ฐ๊ธฐ ์ข์! ๐ค
๐ ๋ง์ง๋ง ํ๋ง๋
์น๊ตฌ์ผ, ์ฌ๊ธฐ๊น์ง ์ ๋ง ์๊ณ ํ์ด! ๐
์ผํ๋ชฐ ํธ๋ํฝ ๊ธ์ฆ ๋์์ ๋จ์ํ ๊ธฐ์ ๋ง์ ๋ฌธ์ ๊ฐ ์๋์ผ.
์ฌ์ฉ์ ๊ฒฝํ, ๋น์ฉ ํจ์จ, ์์ ์ฑ์ ๋ชจ๋ ๊ณ ๋ คํด์ผ ํ๋ ์ข
ํฉ ์์ ์ด์ง.
์ฒ์์๋ ๋ณต์กํด ๋ณด์ด๊ฒ ์ง๋ง, ํ๋์ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ๊ตฌํํ๋ค ๋ณด๋ฉด
์ด๋์ ๋๋ ์ธํ๋ผ ์ ๋ฌธ๊ฐ๊ฐ ๋์ด ์์ ๊ฑฐ์ผ! ๐ช
๊ธฐ์ตํด:
โข ์๊ฒ ์์ํด์ ์ ์ง์ ์ผ๋ก ๊ฐ์ ํ๊ธฐ
โข ๋ชจ๋ํฐ๋ง๊ณผ ์ธก์ ์ ํตํด ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์์ฌ๊ฒฐ์
โข ์คํจ๋ฅผ ๋๋ ค์ํ์ง ๋ง๊ณ ๋น ๋ฅด๊ฒ ๋ฐฐ์ฐ๊ธฐ
โข ์ปค๋ฎค๋ํฐ์ ์ํตํ๋ฉฐ ์ง์ ๊ณต์ ํ๊ธฐ
์ด ๊ธ์ด ๋์ ์ผํ๋ชฐ ๊ฐ๋ฐ ์ฌ์ ์ ๋์์ด ๋์๊ธธ ๋ฐ๋ผ! ๐
๊ถ๊ธํ ์ ์ด ์์ผ๋ฉด ์ธ์ ๋ ๋๊ธ๋ก ๋ฌผ์ด๋ด.
ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์๊ฐ ๋์! ๐
Happy Scaling! ๐ฏ
๐ ์ด ๊ธ์ด ๋์์ด ๋์๋ค๋ฉด ๊ณต์ ํด์ฃผ์ธ์!
๐ฌ ์ง๋ฌธ์ด๋ ํผ๋๋ฐฑ์ ์ธ์ ๋ ํ์์
๋๋ค!
๐ ์ฌ๋ฌ๋ถ์ ์ฑ๊ณต์ ์ธ ์ผํ๋ชฐ ์ด์์ ์์ํฉ๋๋ค!
โ๏ธ ์์ฑ์ผ: 2024๋
| ๐ ์นดํ
๊ณ ๋ฆฌ: ํ๋ก๊ทธ๋จ๊ฐ๋ฐ > ์ผํ๋ชฐ๊ด๋ จ ๊ฐ๋ฐ
๐ ์ฌ๋ฅ๋ท ์ง์์ธ์ ์ฒ์์ ๋ ๋ง์ ๊ฐ๋ฐ ์ง์์ ๋ง๋๋ณด์ธ์!
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.