"๋น์ฉ์ ์ผ๋ก ํน์ดํ ์ฌํญ ์์ต๋๋ค"๋ผ๊ณ ๋งํ๊ธฐ๊น์ง ๊ฑธ๋ฆฌ๋ ๊ฒ๋ค
- #infra
- #aws
- #billing
- #typescript
๋ค์ด๊ฐ๋ฉฐ
์๋ ํ์ธ์, ๋ ธ๋จธ์ค์์ ์ธํ๋ผ๋ฅผ ๋ด๋นํ๊ณ ์๋ ๋ฐ์ง์์ ๋๋ค.
์ ๋ ๋งค์ฃผ ํด๋ผ์ฐ๋ ๋น์ฉ์ ์ง๊ณํ์ฌ ๊ณต์ ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ณต์ ์ ๋์๋ ๋ ์ฝ์๋ ์ง๋ฌธ์ด ํ๋ ๋์์ต๋๋ค.
โ์ด๋ฒ ์ฃผ์ ๋น์ฉ์ ์ผ๋ก ํน์ดํ ์ฌํญ์ด ์์๊น์?โ
์ฌ์ค ๋๋ต์ ๋๋ถ๋ถ ๋น์ทํฉ๋๋ค.
โํน์ด ์ฌํญ ์์ต๋๋ค.โ
ํ์ง๋ง ์ด ์งง์ ํ๋ง๋๋ฅผ ๋ด๋ฑ๊ธฐ ์ํด ์ธํ๋ผ ๋ด๋น์๋ ์๊ฐ๋ณด๋ค ๋ง์ ์ฆ๊ฑฐ๋ฅผ ์์งํด์ผ ํฉ๋๋ค.
โํน์ดํจโ ์ด ์๋ค๊ณ ํ๋ ค๋ฉด ๋ฌด์์ด โํ์ดํจโ ์ธ์ง๋ฅผ ์ ์ํด์ผ ํ๊ธฐ ๋๋ฌธ์
๋๋ค.
FinOps Foundation์ โUnderstand Usage & Costโ์ ๋ฐ๋ฅด๋ฉด,
๋น์ฉ ๊ฐ์์ฑ ํ๋ณด์๋ ๋ช
ํํ ์์๊ฐ ์์ต๋๋ค.
1. Data Ingestion: ํด๋ผ์ฐ๋ ์ ๊ณต์๋ก๋ถํฐ ์ฌ์ฉ ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ ์ ๊ทํํ๋ฉฐ
2. Allocation: ์์ง๋ ๋น์ฉ์ ์๋น์ค์ ํ์ ๋ง๊ฒ ํ ๋นํ๊ณ
3. Reporting & Analysis: ์ผ๊ด๋ ๊ธฐ์ค์ผ๋ก ๋ณด๊ณ ํ๋ฉฐ
4. Anomaly Management: ์ด์ ์งํ๋ฅผ ๊ฐ์งํ๋ ๊ฒ
์ ๋จ๊ณ๊ฐ ๋จ๋จํ๊ฒ ๋ฐ์ณ์ฃผ์ด์ผ ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ ์ ์๋ ๊ตฌ์กฐ์ ๋๋ค.
์ ์ญ์ ์ด ๋ก๋๋งต์ ๋ฐ๋ผ๊ฐ๋ฉฐ ๋ง์ ๊ณ ๋ฏผ์ ๊ฑฐ์ณค๊ณ ,
๊ทธ ๊ณผ์ ์์ ๊ฒช์ ์ํ์ฐฉ์ค์ ์ธ์ฌ์ดํธ๋ฅผ ๊ฐ์ฅ ๋ณต์กํ๋ AWS๋ฅผ ์ค์ฌ์ผ๋ก ํ์ด๋ณด๋ ค ํฉ๋๋ค.
์๋ํํ ์ ์๋ ๊ฒ๋ค
์ ํฌ๋ AWS๋ฅผ MSP(Managed Service Provider)๋ฅผ ํตํด ์ด์ฉํ๊ณ ์์ต๋๋ค.
๋ฐ๋ผ์ ๋น์ฉ ๋ฐ์ดํฐ์ 1์ฐจ ์ถ์ฒ๋ AWS Cost Explorer๊ฐ ์๋, MSP์์ ์ ๊ณตํ๋ ๋ณ๋์ ์ฝ์์
๋๋ค.
ํด๋น ์ฝ์์์๋ ๋น์ฉ ๋ช
์ธ๋ฅผ CSV๋ก ๋ด๋ ค๋ฐ์ ์ ์๊ณ ,
์ฒญ๊ตฌ์๊ฐ ํ์ ๋๋ ์์ ์๋ ์ ์ฉ ํ์จ์ด ํฌํจ๋ PDF ๋ช
์ธ์๋ ํจ๊ป ์ ๊ณต๋ฉ๋๋ค.
์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ ์์ฒด๋ ์ด๋ ต์ง ์์์ต๋๋ค.
๋ฌธ์ ๋ ๊ทธ ๋ค์์ด์์ต๋๋ค.
๋ค์ด๋ก๋ํ ํ์ผ์ ๊ฐ๊ณตํ๋ ๋ชจ๋ ๊ณผ์ ์
๋งค์ฃผ ํผ์ ์ฒ๋ฆฌํด์ผ ํ๋ค๋ ๊ฒ์ด ์ ์ ํฐ ๋ถ๋ด์ผ๋ก ๋๊ปด์ก์ต๋๋ค.
์๋ํ๋ฅผ ๊ฒํ ํ์ง๋ง, MSP ์ธก์ผ๋ก๋ถํฐ โ๊ณต๊ฐ API๋ฅผ ์ ๊ณตํ์ง ์์ผ๋ฉฐ, ์ ํํ ๊ณํ์ด ์๋คโ๋ ๋ต๋ณ์ ๋ฐ์์ต๋๋ค.
์๊ธฐ ๋ค์ด๋ก๋๋ ํผํ ์ ์์ผ๋,
๋ค์ด๋ก๋ ์ดํ์ ๊ฐ๊ณต๊ณผ ์ง๊ณ ๊ณผ์ ๋ง์ด๋ผ๋ ์ฝ๋๋ก ์๋ํํ๋ ๋ฐฉํฅ์ ํํ์ต๋๋ค.
ํ์ด ์ฝ์ ์ ์๋ ์ฝ๋
์ ๋ ์์ด ํธํฉ๋๋ค. ์ธํ๋ผ ๋ด๋น์๋ก์ ์์ฐ์ค๋ฝ๊ฒ ์์ ์ต์ ์ธ์ด์ด๊ธฐ๋ ํฉ๋๋ค.
๊ทธ๋์ ์ฒ์์๋ ์ ์คํฌ๋ฆฝํธ๋ก ์งฐ์ต๋๋ค.
๊ณ์ํด์ ์คํฌ๋ฆฝํธ๋ ์ปค์ก๊ณ , ์ด๋์ 9,000์ค์ด ๋์ด๊ฐ์ต๋๋ค.
์คํ ์๊ฐ๋ ๊ธธ์๊ณ , ์๋ง์ด ๋๋ฉด ์ค๊ฐ์ ๋ฉ์ถ๋ ์ผ๋ ์ฆ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ ๋ฌธ์ ๋ ๋ณด์์ต๋๋ค.
์ ๋ ๋ฐฑ์๋ ํ์ ์์๋ ์ธํ๋ผ ๋ด๋น์์
๋๋ค.
์ ๊ฐ ๋ง๋ ์ฝ๋๋ ์ธ์ ๊ฐ ๋ค๋ฅธ ๋๋ฃ๊ฐ ์ ์ง๋ณด์ํด์ผ ํ ์๋ ์๋๋ฐ,
์ด๋๋ก๋ผ๋ฉด ๊ฐ๋ผํ๊ณ ์ค๊ฐ ๋๊ฒ ๋ค ์ถ์์ต๋๋ค.
ํ์ TypeScript๋ฅผ ์๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ ํ์ง๋ ํ๋์์ต๋๋ค.
๋ฌธ์ ๋ ์ ๊ฐ ์ธ ์ค ๋ชจ๋ฅธ๋ค๋ ๊ฒ์ด์์ต๋๋ค.
์ฝ์ผ๋ฉด ๋งฅ๋ฝ์ ํ์
ํ ์ ์์ง๋ง, ์ง์ ์์ฑํ๋ ๊ฑด ๋ค๋ฅธ ์ด์ผ๊ธฐ์์ต๋๋ค.
๊ทธ๋์ ์ฝ๋ ์์ฑ์ Claude์๊ฒ ๋งก๊ธฐ๊ธฐ๋ก ํ์ต๋๋ค.
๋ฐฉํฅ์ ์ก๊ณ ์ง์ํ๋ ๊ฑด ์ ๊ฐ, ์ฝ๋๋ฅผ ์ฐ๋ ๊ฑด Claude๊ฐ ๋ด๋นํ๋ ๋ฐฉ์์ผ๋ก ํ์
ํ์ต๋๋ค.
๋ณด๊ณ ์๊ฐ ์ํ๋ ์ซ์
AWS ๋น์ฉ ๋ฐ์ดํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฌ๋ฌ($) ๊ธฐ์ค์ด์ง๋ง,
์ค์ ์ฌ๋ด ๋ณด๊ณ ์ ๋น์ฉ ์ ์ฐ์ ์ํ(โฉ)๋ก ์ด๋ฃจ์ด์ง๋๋ค.
๋ฐ๋ผ์ ํ์จ ๋ณํ์ด ํ์์ ์ธ๋ฐ, ์ฌ๊ธฐ์ ์๊ฐ๋ณด๋ค ๊น๋ค๋ก์ด ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
๋ฐ๋ก MSP๊ฐ ๋งค์ ์ฒญ๊ตฌ์๋ฅผ ๋ฐํํ ๋ ์ ์ฉํ๋ ํ์ ํ์จ์ ๋๋ค.
MSP๋ ์ฒญ๊ตฌ์๋ฅผ ๋ฐํํ๋ ์์ ์ ํ์จ์ ํ์ ํด์ ์ ์ฉํ๋๋ฐ, ์ด ํ์ ํ์จ์ ์ฒญ๊ตฌ์๊ฐ ๋์ค๊ธฐ ์ ๊น์ง๋ ์ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ ๋ ๋งค์ฃผ ๋น์ฉ์ ์ง๊ณํด์ผ ํ์ต๋๋ค.
ํ์จ ์ฒ๋ฆฌ๋ฅผ ์ ๋๋ก ํ์ง ๋ชปํ๋ฉด,
๋ง์ง๋ง ์ฃผ ๋ณด๊ณ ๊ธ์ก๊ณผ ์ค์ ์ฒญ๊ตฌ ๊ธ์ก ์ฌ์ด์ ํฐ ์ค์ฐจ๊ฐ ์๊ฒผ์ต๋๋ค.
๊ทธ๋์ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด,
์ ๋ ์์ ์ ๋ฐ๋ผ ํ์จ ์์ค๋ฅผ ์ด์ํํ๋ ๋ฐฉ์์ ํํ์ต๋๋ค.
์ฒญ๊ตฌ๊ฐ ํ์ ๋ ๋ฌ์ด๋ฉด PDF ๋ช ์ธ์์์ ํ์จ์ ํ์ฑํฉ๋๋ค.
const match = result.text.match(/์ ์ฉ ํ์จ\s*:\s*([\d.]+)/);
if (!match) throw new Error('PDF์์ ํ์จ์ ์ฐพ์ ์ ์์ต๋๋ค.');
return Number(match[1]);
ํ์ ์ ์ด๋ผ๋ฉด ExchangeRate API๋ฅผ ์ฌ์ฉํฉ๋๋ค.
const res = await fetch('https://api.exchangerate-api.com/v4/latest/USD');
const data = await res.json() as { rates: { KRW: number } };
return data.rates.KRW;
ํ์จ์ ๋ณด๊ณ ์ ํฌํจํด ์ถ์ ํ๊ฒ ๋๋ฉด์,
๋ฌ๋ฌ ๊ธฐ์ค์ผ๋ก๋ ๋ณํ๊ฐ ์๋๋ฐ ์ํ๋ก๋ ํฌ๊ฒ ์ฐจ์ด ๋ ๋ณด์ด๋ ์ํฉ๋ ์์น๋ก ์ค๋ช
ํ ์ ์๊ฒ ๋์์ต๋๋ค.
์๊ฐ๋ณด๋ค ์ง์ ๋ถํ ๋ฐ์ดํฐ
๋ฐ์ดํฐ ์์ฒด์๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก ์ธ์ฝ๋ฉ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ์ต๋๋ค.
MSP์ CSV๋ EUC-KR๋ก ์ธ์ฝ๋ฉ๋์ด ์์์ต๋๋ค.
๊ทธ๋๋ก ์ฒ๋ฆฌํ๋ ค๊ณ ํ๋ฉด ํ๊ธ์ด ๊นจ์ ธ์, UTF-8๋ก ๋ณํํด์ผ ํ์ต๋๋ค.
const buffer = Buffer.from(await res.arrayBuffer());
const utf8 = iconv.decode(buffer, 'EUC-KR');
fs.writeFileSync(filePath, utf8, 'utf8');
๋ ๋ฒ์งธ๋ก ์ ๋จ์ ๊ณผ๊ธ ๋ฐ์ดํฐ๊ฐ ์๊ณก์ ๋ง๋ค์์ต๋๋ค.
์ ๋จ์๋ก ๊ณผ๊ธ๋๋ ํญ๋ชฉ์ ์์ด์ ํด๋น ์ ์ ์ฒด ๋ ์ง์ ๊ธ์ก์ด ๋ฏธ๋ฆฌ ๋ถ์ฐ๋์ด ์ฐํ๋๋ค.
์ ์ค๊ฐ์ ์ง๊ณํ๋ฉด ์์ง ์ง๋์ง ์์ ๋ ์ง์ ๋ฐ์ดํฐ๊น์ง ํฉ์ฐ๋๊ฑฐ๋,
๋ฐ๋๋ก ์์ง ์ฐํ์ง ์์ ํญ๋ชฉ์ด ๋น ์ ธ ๋น์ด์์ ์ธ ์์น๊ฐ ๋์์ต๋๋ค.
๊ทธ๋์ ๋ชจ๋ ๋ฐ์ดํฐ๋ ์ค๋ ๊ธฐ์ค ์ดํ ์ ๊น์ง๋ง ํฌํจํ๊ณ ,
์๋ง์ด ์ง๋๋ฉด ํด๋น ์ ์ ์ฒด๋ฅผ ์ฐ๋๋ก ์ฒ๋ฆฌํ์ต๋๋ค.
const cutoff = monthEnd < todayMinus2Str ? monthEnd : todayMinus2Str;
์ธ ๋ฒ์งธ๋ก ๋ ธ์ด์ฆ๊ฐ ๋ง์์ต๋๋ค.
์ฒ๋ฆฌ ์๊ฐ์ด ์์๋ณด๋ค ์ค๋ ๊ฑธ๋ ค ์์ธ์ ํ์
ํ๋ ์ค,
์ ์ฒด ๋ฐ์ดํฐ์ ์ฝ 40%๊ฐ $0 ํ์ด๋ผ๋ ์ฌ์ค์ ์๊ฒ ๋์์ต๋๋ค.
์ ์ด์ AWS CUR ๋ฐ์ดํฐ๋ ์ถ์ ์น์ด๊ธฐ ๋๋ฌธ์ ์๋ฒฝํ ์ ํ๋๋ฅผ ๊ธฐ๋ํ๊ธฐ ์ด๋ ต์ต๋๋ค.
์ ํํ ์์น๋ณด๋ค ์ถ์ด๋ฅผ ํ์
ํ๋ ๊ฒ์ด ๋ชฉ์ ์ด์๊ธฐ์,
$0 ํ์ ๊ณผ๊ฐํ ๋
ธ์ด์ฆ๋ก ํ๋จํ๊ณ ์ ์ธํ์ต๋๋ค.
if (isNaN(usd) || usd <= 0) continue;
๋ณด์ด๋๋ฐ ๋ชจ๋ฅด๊ฒ ๋ค
์ ๊ทํ๊ฐ ๋๋๋ฉด ๋ฐ์ดํฐ๋ ๋ค์๊ณผ ๊ฐ์ ๋ชจ์ต์ ๋๋ค.
| โฆ | ResourceId | ProductName | โฆ | ๊ธ์ก(USD) |
|---|---|---|---|---|
| โฆ | arn:aws:ec2:โฆ/natgateway/nat-021bcd03d05c7abed | Amazon Elastic Compute Cloud | โฆ | 0.248 |
| โฆ | i-0f59d872251530953 | Amazon Elastic Compute Cloud | โฆ | 0.1073 |
| โฆ | arn:aws:rds:โฆ/db:dev-writer | Amazon Relational Database Service | โฆ | 0.339 |
๋ณด์๋ ๊ฒ์ฒ๋ผ ResourceId ์ปฌ๋ผ์๋ ํ๋์ ํ์
ํ๊ธฐ ์ด๋ ค์ด ์๋ณ์๋ค๋ง ๊ฐ๋ํฉ๋๋ค.
์ด๋๋ก ๊ณต์ ํ๋ฉด โ์ด EC2 ์ธ์คํด์ค๊ฐ ์ด๋ค ์๋น์ค์ฉ์ธ๊ฐ์?โ๋ผ๋ ์ง๋ฌธ์ด ์์์ง ์๋ฐ์ ์์ต๋๋ค.
๊ทผ๋ณธ์ ์ธ ํด๊ฒฐ์ฑ ์ ๋ชจ๋ ๋ฆฌ์์ค์ ์๊ฒฉํ ํ๊น ๊ท์น์ ์ ์ฉํ๋ ๊ฒ์ ๋๋ค.
ํ์ง๋ง ์ ์ฌ์ ์ธ ๋ค์ด๋ฐ ์ปจ๋ฒค์
์ ์๋ก ์ ๋ฆฝํ๊ณ ,
ํ
๋ผํผ ์ฝ๋๋ฅผ ์์ ํด ์ ์ฒด ๋ฆฌ์์ค๋ฅผ ์ฌ๋ฐฐํฌํด์ผ ํ์ต๋๋ค.
๋น์ฅ ์คํํ๊ธฐ์ ๋ฆฌ์คํฌ์ ๊ณต์๊ฐ ๋๋ฌด ํฐ ์ผ์ด์์ต๋๋ค.
๊ทธ๋์ ํ์ฌ ๋ฌ๋ ค ์๋ ํ๊ทธ ๊ฐ์ด๋ผ๋ ์ต๋ํ ํ์ฉํ๋ ๋ฐฉํฅ์ผ๋ก ์ ๊ทผํ์ต๋๋ค.
AWS API๋ฅผ ์ง์ ์กฐํํด Name ํ๊ทธ๋ฅผ ๊ฐ์ ธ์จ ๋ค,
ResourceId์ ๋งค์นญํด ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์ด ์ด๋ฆ์ผ๋ก ์นํํ์ต๋๋ค.
๋ํ API ํธ์ถ ๋ถํ๋ฅผ ์ค์ด๊ธฐ ์ํด ๋ฐฐ์น ๋ฐฉ์์ ์ ์ฉํ์ต๋๋ค.
CSV ์ ์ฒด๋ฅผ ๋จผ์ ์ค์บํด ํ์ํ ID ๋ชฉ๋ก์ ์ถ์ถํ๊ณ ,
๋ฆฌ์ ๋ณ๋ก ๋ถ๋ฅํ ๋ค ๋ฆฌ์ ๋น ํ ๋ฒ์ฉ๋ง ์กฐํํ์ต๋๋ค.
for (const row of rows) {
const name = extractResourceName(row.resourceId);
const region = getArnRegion(row.resourceId) ?? PREFIX_TO_REGION[usageTypePrefix] ?? 'ap-northeast-2';
if (/^i-[0-9a-f]+$/.test(name)) regionInstanceMap.get(region)?.add(name);
else if (/^nat-[0-9a-f]+$/.test(name)) regionNatMap.get(region)?.add(name);
else if (/^vol-[0-9a-f]+$/.test(name)) regionVolumeMap.get(region)?.add(name);
}
for (const [region, ids] of regionInstanceMap) {
const res = await ec2.send(new DescribeInstancesCommand({
Filters: [{ Name: 'instance-id', Values: [...ids] }],
}));
for (const instance of res.Reservations?.flatMap(r => r.Instances ?? []) ?? []) {
const name = instance.Tags?.find(t => t.Key === 'Name')?.Value;
if (instance.InstanceId && name) nameMap.set(instance.InstanceId, name);
}
}
EBS ๋ณผ๋ฅจ์ ๋ณผ๋ฅจ ์์ฒด์ Name ํ๊ทธ๊ฐ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ต๋๋ค.
๋ณผ๋ฅจ์ ๋ถ์ด์๋ EC2 ์ธ์คํด์ค๋ฅผ ํ ๋ฒ ๋ ์กฐํํด์ ์ธ์คํด์ค ์ด๋ฆ์ ๋์ ์ฌ์ฉํ์ต๋๋ค.
for (const vol of res.Volumes ?? []) {
const instanceId = vol.Attachments?.[0]?.InstanceId;
if (vol.VolumeId && instanceId) volumeToInstance.set(vol.VolumeId, instanceId);
}
if (name) nameMap.set(volId, `${name} (volume)`);
๋ฆฌ์์ค ๊ฑฐ๋ฒ๋์ค๊ฐ ์ ์กํ์๋ค๋ฉด ์ด ๊ณผ์ ์ด ํ์ ์๊ฒ ์ง๋ง,
ํ๊ทธ๊ฐ ์๊ฑฐ๋ ์ ๊ฐ๊ฐ์ธ ํ๊ฒฝ์์๋ ์ด ๋ฐฉ์์ด ํ์ค์ ์ธ ๋์์ด์์ต๋๋ค.
์ฐ๋ฆฌ๊ฐ ์ํ๋ ๋๋ก
MSP ์ฝ์์์๋ ์๋น์ค๋ณ ๋น์ฉ์ ํ์ธํ ์ ์์์ต๋๋ค.
ํ์ง๋ง ์ ๋ ๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ฆฌ ๋ณด๊ณ ๋ฐฉ์์ ๋ง๊ฒ ๋ค์ ๊ฐ๊ณตํด์ผ ํ์ต๋๋ค.
AWS ์๋น์ค โ SKU(usageType) โ ๋ฆฌ์์ค ์์ผ๋ก ๊ณ์ธต์ ์ก์ ํฉ์ฐํ๊ณ ,
๊ธ์ก ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํด ํ๋์ ํ์
ํ ์ ์๋๋ก ํ์ต๋๋ค.
๋ํ ํต์ฌ ์ถ์ด๋ฅผ ๋ณด๋ ๊ฒ์ด ๋ชฉ์ ์ด์๊ธฐ์,
$5 ๋ฏธ๋ง ํญ๋ชฉ์ ์ฐ์ ์์๊ฐ ๋ฎ๋ค๊ณ ํ๋จํด ์ ์ธํ์ต๋๋ค.
const svc = services.get(row.productName)!;
svc.usd += row.usd;
const sku = svc.skus.get(usageTypeParsed)!;
sku.usd += row.usd;
// ...
const sortedServices = [...services.values()].sort((a, b) => b.usd - a.usd);
for (const svc of sortedServices) {
if (svc.usd < 5) continue;
// ...
}
์ ํํ ์ถ์ด๋ฅผ ์ํด
์ซ์๋ง ๋ณด๋ฉด ์ด๊ฒ ๋ง์ ๊ฑด์ง ์ ์ ๊ฑด์ง ์๊ธฐ ์ด๋ ต์ต๋๋ค.
์๋ฏธ ์๋ ํ๋จ์ ํ๋ ค๋ฉด ์ ์์ด๋ ์ต๊ทผ ๋ช ๋ฌ๊ณผ ๋น๊ตํด์ผ ํฉ๋๋ค.
์ฌ๊ธฐ์ ๋ ๊ฐ์ง ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ก ์๋ง๋ค ๋ ์ง ์๊ฐ ๋ค๋ฆ
๋๋ค.
๋จ์ํ ์ด๋ฒ ๋ฌ ํฉ๊ณ์ ์ง๋ ๋ฌ ํฉ๊ณ๋ฅผ ๋น๊ตํ๋ฉด ๋ ์ง ์ฐจ์ด๋งํผ ์๊ณก์ด ์๊น๋๋ค.
๋ ๋ฒ์งธ๋ก ์ ๋ ์ ์ค๊ฐ์๋ ์ง๊ณํฉ๋๋ค.
์ด๋ฌ 1์ผ๋ถํฐ 15์ผ์ ์ง๊ณํ๋ค๋ฉด,
์ ์๋ 1์ผ๋ถํฐ 15์ผ๊น์ง๋ง ์๋ผ์ ๋น๊ตํด์ผ ๊ณต์ ํ ๋น๊ต๊ฐ ๋ฉ๋๋ค.
์ด ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ์ด์ ๋ฌ ๋ฐ์ดํฐ๋ฅผ ๋ ์ง ๋จ์๋ก ๋ณด๊ดํด๋์ด์ผ ํ์ต๋๋ค.
๊ทธ๋์ ์คํํ ๋๋ง๋ค ์ง๊ณ ๊ฒฐ๊ณผ๋ฅผ data.json์ผ๋ก ์ ์ฅํด๋๊ณ ,
๋ค์ ๋ฌ ์คํ ์ ๋ถ๋ฌ์์ ๊ฐ์ ์ผ์๊น์ง๋ง ์๋ผ ๋น๊ตํ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ์ต๋๋ค.
const prevAccDataPath = path.join(OUTPUT_PATH, prevYearMonth, 'aws', account.slug, 'data.json');
const prevAccData = fs.existsSync(prevAccDataPath)
? JSON.parse(fs.readFileSync(prevAccDataPath, 'utf8'))
: null;
// ...
fs.writeFileSync(path.join(accOutDir, 'data.json'), JSON.stringify(accDataJson, null, 2) + '\n', 'utf8');
์๋น์ค๋ณ, SKU๋ณ, ๋ฆฌ์์ค๋ณ๋ก ์ ์ ๊ฐ์ด ์์ผ๋ฉด ์ฐจ์ด๋ฅผ ํจ๊ป ํ์ํฉ๋๋ค.
๊ทธ๋ํ๋ ๋น์๊ณผ ์ ์์ ๊ฒน์ณ์ ํ๋์ ์ถ์ด๋ฅผ ๋ณผ ์ ์๋๋ก ์์ฑํฉ๋๋ค.

๋ง์น๋ฉฐ
์ด ๋ชจ๋ ๊ณผ์ ์ด ๋๋๋ฉด ์๋น์ค๋ณ ์ฐจํธ์ ๊ณ์ ๋ณ ๋ณด๊ณ ์๊ฐ ์์ฑ๋ฉ๋๋ค.
์ ๋ ๊ทธ ๊ณผ์ ์์ ์ ์ ๋๋น ํฌ๊ฒ ์ค๋ฅธ ์๋น์ค๋ ๋ฆฌ์์ค๊ฐ ์๋์ง ํ์ธํ๋ฉด ๋ฉ๋๋ค.
๋จ์ํ ์์์
์ด ๊ท์ฐฎ์์ ์์ํ ์๋ํ์์ง๋ง,
๊ฒฐ๊ณผ์ ์ผ๋ก๋ ๋ฐ์ดํฐ์ ๊ธฐ๋ฐํ์ฌ ์์ ์๊ฒ ๋งํ ์ ์๋ ๊ทผ๊ฑฐ๊ฐ ์๊ฒผ์ต๋๋ค.
TypeScript๋ฅผ ์ธ ์ค ๋ชฐ๋๋ ์ ํผ์์๋ค๋ฉด ์ฌ๊ธฐ๊น์ง ์ค์ง ๋ชปํ์ ๊ฒ๋๋ค.
Claude์ ํจ๊ป์๊ธฐ์ ๊ฐ๋ฅํ ์ผ์ด์์ต๋๋ค.
๊ทธ๋ ๊ฒ ์ค๋๋ ์ ๋ ๋๋ตํฉ๋๋ค.
โ๋ค, ์ด๋ฒ ์ฃผ๋ ํน์ด ์ฌํญ ์์ต๋๋ค.โ