ํ”„๋กœ์ ํŠธ/DB ์ž๋™ํ™”

DB ์ผ์ผ ์ ๊ฒ€ ์ž๋™ํ™” POC (1) ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘

THE NICOLE 2026. 6. 15. 09:34

ํ•ต์‹ฌ ์š”์•ฝ

  • ํ•œ ๋Œ€๊ธฐ์—… ์ œ์กฐ์‚ฌ์—์„œ DBA๊ฐ€ ๋งค์ผ ์•„์นจ 30๋ถ„~1์‹œ๊ฐ„์”ฉ ์†์œผ๋กœ ํ•˜๋˜ DB ์ ๊ฒ€์„, AI๊ฐ€ ์ž๋™ ๋ถ„์„ํ•ด ๋งค์ผ ์˜ค์ „ 8์‹œ HTML ๋ฉ”์ผ๋กœ ๋ณด๋‚ด์ฃผ๋Š” ์‹œ์Šคํ…œ์„ 2์ฃผ POC๋กœ ๋งŒ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋Œ€์ƒ์€ AWS RDS ์œ„์˜ Oracle·MySQL·PostgreSQL 3์ข…. ๋ฐ์ดํ„ฐ๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์†”๋ฃจ์…˜(DPM)์ด 1๋ถ„ ๋‹จ์œ„๋กœ ์ง‘๊ณ„ํ•œ ์‹œ๊ณ„์—ด ์ง€ํ‘œ์ž…๋‹ˆ๋‹ค.
  • ์ „์ฒด๋Š” main.py๊ฐ€ collector → detector → reporter → mailer 4๊ฐœ ๋ชจ๋“ˆ์„ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ์ด๋ฒˆ ๊ธ€์€ ๊ทธ ์ฒซ ๋‹จ๊ณ„์ธ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘(collector.py)์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
  • collector์˜ ํ•ต์‹ฌ ์„ค๊ณ„ ๋ชฉํ‘œ๋Š” "๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜๊ฐ€ ์—‘์…€์—์„œ ์‹ค์ œ API๋กœ ๋ฐ”๋€Œ์–ด๋„, ์ด ํŒŒ์ผ ํ•˜๋‚˜๋งŒ ๊ณ ์น˜๋ฉด ๋˜๊ฒŒ" ๊ฒฉ๋ฆฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ์‹ค์ œ ์ฝ”๋“œ์™€ ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ƒ˜ํ”Œ์„ ์ƒ์„ธํžˆ ํ™•์ธํ•˜๋ฉฐ ์ž‘์—…ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. 

1. ์–ด๋–ค ํ”„๋กœ์ ํŠธ์ธ๊ฐ€

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ์ž(DBA)๋Š” ๋งค์ผ ์•„์นจ ์ถœ๊ทผํ•˜๋ฉด ๋น„์Šทํ•œ ์ผ์„ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. CPU ์‚ฌ์šฉ๋ฅ ์ด ๋ฐค์‚ฌ์ด ํŠ€์ง€ ์•Š์•˜๋Š”์ง€, ์„ธ์…˜์ด ๊ณผํ•˜๊ฒŒ ๋ชฐ๋ฆฌ์ง€ ์•Š์•˜๋Š”์ง€, ์Šฌ๋กœ์šฐ ์ฟผ๋ฆฌ๊ฐ€ ๋Š˜์ง€ ์•Š์•˜๋Š”์ง€, ํ…Œ์ด๋ธ”์ŠคํŽ˜์ด์Šค๋‚˜ ๋””์Šคํฌ๊ฐ€ ์ฐจ์˜ค๋ฅด์ง€ ์•Š์•˜๋Š”์ง€๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋ง ํ™”๋ฉด์„ ๋„์›Œ ๋†“๊ณ  ๋ˆˆ์œผ๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. DB๊ฐ€ ํ•œ๋‘ ๋Œ€๋ฉด ๊ธˆ๋ฐฉ์ด์ง€๋งŒ, ์šด์˜ DB๊ฐ€ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜·์—ฌ๋Ÿฌ ๋Œ€๋กœ ๋Š˜์–ด๋‚˜๋ฉด ์ด "์•„์นจ ์ ๊ฒ€"์—๋งŒ 30๋ถ„์—์„œ 1์‹œ๊ฐ„์ด ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

์ด ์ž‘์—…์˜ ํŠน์ง•์€ ๋Œ€๋ถ€๋ถ„์˜ ๋‚ ์€ ์•„๋ฌด ์ด์ƒ์ด ์—†๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ๋„ ์‚ฌ๋žŒ์ด ๋งค์ผ ๊ฐ™์€ ํ™”๋ฉด์„ ๊ฐ™์€ ์ˆœ์„œ๋กœ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด์ƒ์ด ์žˆ๋Š” ๋‚ ์„ ๋†“์น˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด, ์ด์ƒ์ด ์—†๋Š” 364์ผ๋„ ๋˜‘๊ฐ™์ด ๋ด์•ผ ํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ž๋™ํ™”ํ•˜๊ธฐ ๋”ฑ ์ข‹์€ ์ผ์ž…๋‹ˆ๋‹ค.

๋งค์ผ ์˜ค์ „ 7์‹œ 50๋ถ„์— ์ž๋™์œผ๋กœ ๋Œ์•„์„œ, ์–ด์ œ ํ•˜๋ฃจ์น˜ DB ์ง€ํ‘œ๋ฅผ ๋ถ„์„ํ•˜๊ณ , ์˜ค์ „ 8์‹œ์— "์˜ค๋Š˜์˜ DB ์ƒํƒœ ๋ฆฌํฌํŠธ"๋ฅผ HTML ์ด๋ฉ”์ผ๋กœ ๋ณด๋‚ธ๋‹ค. ์ด์ƒ์ด ์žˆ์œผ๋ฉด ์–ด๋–ค ์ง€ํ‘œ๊ฐ€ ์™œ ๋ฌธ์ œ์ธ์ง€ ์งš์–ด ์ฃผ๊ณ , ์ด์ƒ์ด ์—†๋Š” ๋‚ ๋„ "์ •์ƒ์ž…๋‹ˆ๋‹ค" ๋ฆฌํฌํŠธ๋ฅผ ๋ณด๋‚ธ๋‹ค.

์กฐ๊ฑด๋„ ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์˜จํ”„๋ ˆ๋ฏธ์Šค ๋‚ฉํ’ˆ: ๊ณ ๊ฐ์‚ฌ ์„œ๋ฒ„์— ํŒŒ์ด์ฌ zip์œผ๋กœ ์˜ฌ๋ ค์„œ cron์œผ๋กœ ๋Œ๋ฆฝ๋‹ˆ๋‹ค. ๋„์ปค ์—†์ด, ์šฉ๋Ÿ‰์€ ๊ฐ€๋ณ๊ฒŒ(1MB ์ดํ•˜).
  • ์ •ํ™•๋„ 85~90% ์ด์ƒ: ๋‹จ์ˆœ ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ๋ฟ ์•„๋‹ˆ๋ผ, ์ž„๊ณ„๊ฐ’์€ ์•ˆ ๋„˜์—ˆ์ง€๋งŒ ํ‰์†Œ์™€ ๋‹ค๋ฅธ ํŒจํ„ด ์ด์ƒ๋„ ์žก์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • DB๋ณ„๋กœ ๋‹ค๋ฅธ ๊ธฐ์ค€: Oracle·MySQL·PostgreSQL์€ ๋ด์•ผ ํ•˜๋Š” ์ง€ํ‘œ๋„, ์œ„ํ—˜์œผ๋กœ ๋ณด๋Š” ๊ธฐ์ค€๊ฐ’๋„ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๋Œ€์ƒ DB๋Š” AWS RDS(ap-northeast-2)์— ์žˆ๋Š” Oracle 1๋Œ€, MySQL(Aurora) 1๋Œ€, PostgreSQL(Aurora) 1๋Œ€, ์ด 3์ข…์ž…๋‹ˆ๋‹ค.

2. ์ „์ฒด ๊ตฌ์„ฑ — 4๋‹จ๊ณ„ ํŒŒ์ดํ”„๋ผ์ธ

์‹œ์Šคํ…œ์€ main.py๊ฐ€ collector·detector·reporter·mailer 4๊ฐœ ๋ชจ๋“ˆ์„ ํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์™ผ์ชฝ์—์„œ ๋“ค์–ด์™€ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํ๋ฅด๋ฉด์„œ ์ ์  "์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๋ฆฌํฌํŠธ"๋กœ ๋ณ€ํ•ฉ๋‹ˆ๋‹ค.

main.py  →  collector  →  detector  →  reporter  →  mailer
            (๋ฐ์ดํ„ฐ์ˆ˜์ง‘)   (์ด์ƒํƒ์ง€)    (HTML+๊ทธ๋ž˜ํ”„)  (๋ฉ”์ผ๋ฐœ์†ก)
              ↓ DbMetrics    ↓ ์ด์ƒ๊ฒฐ๊ณผ     ↓ HTML+PNG
๋ชจ๋“ˆ ์ฑ…์ž„ ์ž…๋ ฅ → ์ถœ๋ ฅ ์ž๋ฐ”๋กœ ์น˜๋ฉด
config.yaml DB๋ณ„ ์ž„๊ณ„๊ฐ’·์ง€ํ‘œ·์„ค์ • — application.yml
collector.py ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘(์ถœ์ฒ˜ ๊ฒฉ๋ฆฌ) (config, ๋‚ ์งœ) → DbMetrics Repository / DAO
detector.py ๋ฃฐ๋ฒ ์ด์Šค + ML ์ด์ƒ ํƒ์ง€ DbMetrics → ์ด์ƒ ๊ฒฐ๊ณผ Service
reporter.py HTML ๋ฆฌํฌํŠธ + ๊ทธ๋ž˜ํ”„ ์ด์ƒ ๊ฒฐ๊ณผ → HTML + PNG View / Template
mailer.py SMTP ๋ฉ”์ผ ๋ฐœ์†ก HTML + PNG → ๋ฉ”์ผ ๋ฉ”์ผ ์ปดํฌ๋„ŒํŠธ

๊ฐ ๋ชจ๋“ˆ์„ ํ•œ ์ค„๋กœ ์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

  • main.py — ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ์˜ ์ง„์ž…์ ์ž…๋‹ˆ๋‹ค. ์ •ํ•ด์ง„ ์ˆœ์„œ๋Œ€๋กœ ๋‚˜๋จธ์ง€ ๋ชจ๋“ˆ์„ ํ˜ธ์ถœํ•ด ํ•˜๋ฃจ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์Šคํ”„๋ง์œผ๋กœ ์น˜๋ฉด ๋ฐฐ์น˜ ์žก์˜ ์‹คํ–‰ ์ง„์ž…์ ์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  • config.yaml — DB๋ณ„ ์ž„๊ณ„๊ฐ’·๊ฐ์‹œ ์ง€ํ‘œ·๋ฐœ์†ก ์‹œ๊ฐ ๊ฐ™์€ ์„ค์ •์„ ๋ชจ์•„ ๋‘” ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  ๊ธฐ์ค€๊ฐ’๋งŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋„๋ก ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • collector.py — ์ง€ํ‘œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ์•„ ํ‘œ์ค€ ํ˜•ํƒœ(DbMetrics)๋กœ ๋Œ๋ ค์ค๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜(์—‘์…€/์‹ค์ œ API)๋ฅผ ๊ฐ์ถ”๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ด๋ฉฐ, ์ด๋ฒˆ ๊ธ€์˜ ์ฃผ์ œ์ž…๋‹ˆ๋‹ค.
  • detector.py — ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ·์ „๋‚  ๋Œ€๋น„ ๊ธ‰์ฆ·ํŒจํ„ด ์ด์ƒ์„ ํŒ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฃฐ๋ฒ ์ด์Šค์™€ ๋จธ์‹ ๋Ÿฌ๋‹ ๋‘ ๋ฐฉ์‹์„ ํ•จ๊ป˜ ์”๋‹ˆ๋‹ค.
  • reporter.py — ํŒ์ • ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” HTML ๋ฆฌํฌํŠธ์™€ ์ถ”์ด ๊ทธ๋ž˜ํ”„(PNG)๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • mailer.py — ์™„์„ฑ๋œ ๋ฆฌํฌํŠธ๋ฅผ SMTP๋กœ ๋ฉ”์ผ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค. ๋งค์ผ ์˜ค์ „ 8์‹œ์— ๋„์ฐฉํ•˜๋„๋ก cron์ด 7์‹œ 50๋ถ„์— ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋‚˜๋ˆˆ ์ด์œ ๋Š” ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ชจ๋“ˆ์ด ์ž๊ธฐ ์ผ๋งŒ ํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด, ํ•œ ๋ถ€๋ถ„์„ ๊ณ ์น  ๋•Œ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ํ”๋“ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—๋Š” collector๋งŒ ๋”ฐ๋กœ ๋–ผ์–ด ๋‘ฌ์•ผ ํ•˜๋Š” ๊ฐ•ํ•œ ์ด์œ ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ, ์ด๊ฑด 4์žฅ์—์„œ ์ž์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์Šคํ”„๋ง์„ ํ•ด๋ณด์‹  ๋ถ„์ด๋ผ๋ฉด ์ต์ˆ™ํ•œ ๊ทธ๋ฆผ์ž…๋‹ˆ๋‹ค. Controller(main) → Service(detector) → Repository(collector)๋กœ ์ฑ…์ž„์ด ๋‚˜๋‰˜๊ณ , ๊ฐ ๊ณ„์ธต์€ ๋ฐ”๋กœ ์˜† ๊ณ„์ธตํ•˜๊ณ ๋งŒ ์•ฝ์†๋œ ํ˜•ํƒœ๋กœ ๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ "์•ฝ์†๋œ ํ˜•ํƒœ"๊ฐ€ ์—ฌ๊ธฐ์„œ๋Š” DbMetrics๋ผ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

3. ๋ฐ์ดํ„ฐ๋Š” ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋‚˜

์ฝ”๋“œ๋ฅผ ๋ณด๊ธฐ ์ „์— ์žฌ๋ฃŒ๋ฅผ ๋จผ์ € ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. collector๊ฐ€ ์ฝ์–ด ๋“ค์ด๋Š” ์›๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ๋ชจ๋ฅด๋ฉด, collector ์ฝ”๋“œ๊ฐ€ ์™œ ๊ทธ๋ ‡๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ์ดํ•ดํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ์ถœ์ฒ˜๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์†”๋ฃจ์…˜(DPM)์ž…๋‹ˆ๋‹ค. ์ด ์†”๋ฃจ์…˜์€ ์‹œ๊ณ„์—ด DB์— 5์ดˆ๋งˆ๋‹ค ์Œ“์ธ ์›์‹œ ๋ฐ์ดํ„ฐ๋ฅผ 1๋ถ„ ๋‹จ์œ„๋กœ ์ง‘๊ณ„ํ•ด์„œ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ POC ๋‹จ๊ณ„์—์„œ๋Š” ๋ณด์•ˆ์ƒ ์‹ค์ œ API์— ๋ถ™์„ ์ˆ˜ ์—†์–ด์„œ, ๊ณ ๊ฐ์‚ฌ๊ฐ€ ํ•œ ๋‹ฌ์น˜ 1๋ถ„ ๋‹จ์œ„ ์ง€ํ‘œ๋ฅผ ์—‘์…€๋กœ ์ถ”์ถœํ•ด ์ „๋‹ฌํ•ด ์คฌ์Šต๋‹ˆ๋‹ค.

์—‘์…€ ํŒŒ์ผ์˜ ๊ทœ์น™์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

  • ํŒŒ์ผ 1๊ฐœ = ์ง€ํ‘œ 1๊ฐœ. ํŒŒ์ผ๋ช…์€ db_{db}_{metric}.xlsx ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ: db_oracle_cpu_usage.xlsx, db_oracle_freestoragespace.xlsx.
  • DB๋ณ„ ํŒŒ์ผ ์ˆ˜: Oracle 33๊ฐœ, MySQL 27๊ฐœ, PostgreSQL 32๊ฐœ.

์‹ค์ œ db_oracle_cpu_usage.xlsx๋ฅผ ์—ด์–ด ๋ณธ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค(์•ž 5ํ–‰, ์‹๋ณ„์ž๋Š” ๊ฐ€๋ ธ์Šต๋‹ˆ๋‹ค).

     agg_collect_time      target_id        data_id              last_value  max_value  count_data
0  2026-06-03T22:00:00   176d…c8371   db_oracle_cpu_usage     8.235000      8.235          12
1  2026-06-03T22:01:00   176d…c8371   db_oracle_cpu_usage     8.066667      8.066          12
2  2026-06-03T22:02:00   176d…c8371   db_oracle_cpu_usage     9.993333      9.993          12
3  2026-06-03T22:03:00   176d…c8371   db_oracle_cpu_usage    15.608333     15.608          12
4  2026-06-03T22:04:00   176d…c8371   db_oracle_cpu_usage     9.401667      9.401          12

์ปฌ๋Ÿผ์„ ํ•˜๋‚˜์”ฉ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ปฌ๋Ÿผ ๋œป
agg_collect_time ์ง‘๊ณ„ ์‹œ๊ฐ. ISO ๋ฌธ์ž์—ด(2026-06-03T22:00:00)์ด๊ณ  1๋ถ„ ๊ฐ„๊ฒฉ์ž…๋‹ˆ๋‹ค.
target_id ์ธ์Šคํ„ด์Šค ์‹๋ณ„์ž(UUID). ์–ด๋А DB์˜ ๋ฐ์ดํ„ฐ์ธ์ง€ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค.
data_id ์ง€ํ‘œ ์‹๋ณ„์ž. ํŒŒ์ผ๋ช…๊ณผ ๊ฐ™์€ ๊ฐ’์ž…๋‹ˆ๋‹ค.
last_value ๊ทธ 1๋ถ„ ๊ตฌ๊ฐ„์˜ ๋Œ€ํ‘œ๊ฐ’(ํ‰๊ท ). ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ์“ฐ๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค.
sum / max / min / first_value 1๋ถ„ ๋™์•ˆ์˜ ํ•ฉ·์ตœ๋Œ€·์ตœ์†Œ·์ฒซ ๊ฐ’.
count_data ๊ทธ 1๋ถ„์— ๋“ค์–ด์˜จ ์›์‹œ ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜. 12 = 5์ดˆ × 12 = 60์ดˆ.

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ํ•จ์ • ์„ธ ๊ฐ€์ง€๊ฐ€ ์žˆ๊ณ , ์ด๊ฒŒ collector ์ฝ”๋“œ์˜ ๋ชจ์–‘์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

  1. ๋ชจ๋“  ๊ฐ’์ด ๋ฌธ์ž์—ด๋กœ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค. ์ˆซ์ž์ฒ˜๋Ÿผ ๋ณด์—ฌ๋„ "8.235" ๊ฐ™์€ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Œ€๋กœ ๋น„๊ตํ•˜๋ฉด "9" > "15"๊ฐ€ ์ฐธ์ด ๋˜๋Š” ์‚ฌ๊ณ ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ float์œผ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์‹œ๊ฐ„์ˆœ ์ •๋ ฌ์ด ๋ณด์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ–‰์ด ๋’ค์ฃฝ๋ฐ•์ฃฝ์ผ ์ˆ˜ ์žˆ์–ด์„œ, ๋‚ ์งœ๋กœ ์ž๋ฅด๊ธฐ ์ „์— ์‹œ๊ฐ„์œผ๋กœ ์ •๋ ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  3. ํ•˜๋ฃจ๋Š” 1,440ํ–‰์ž…๋‹ˆ๋‹ค. 24์‹œ๊ฐ„ × 60๋ถ„. "์–ด์ œ ํ•˜๋ฃจ์น˜"๋ฅผ ๋ฝ‘์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ 1,440ํ–‰์ด ๋‚˜์™€์•ผ ํ•œ๋‹ค๋Š” ๊ฒ€์ฆ ๊ธฐ์ค€์ด ์—ฌ๊ธฐ์„œ ๋‚˜์˜ต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•œ ๊ฐ€์ง€ ๋”. ์ด ์—‘์…€์€ POC์šฉ ์ž„์‹œ ๋ฐ์ดํ„ฐ์ผ ๋ฟ์ด๊ณ , ํ˜„์žฅ์—์„œ๋Š” ์‹ค์ œ API๋กœ ๊ฐˆ์•„๋ผ์šธ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ์ด ์‚ฌ์‹ค์ด ๋‹ค์Œ ์žฅ์˜ ์„ค๊ณ„๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

4. collector.py๊ฐ€ ํ•˜๋Š” ์ผ

ํ•œ ์ค„ ์ •์˜

collector๋Š” "์ ๊ฒ€ ๋Œ€์ƒ์ผ(๋ณดํ†ต ์–ด์ œ)๊ณผ ๋น„๊ต์šฉ ์ „๋‚ (๊ทธ์ œ)์˜ 1๋ถ„ ์ง€ํ‘œ ์‹œ๊ณ„์—ด์„, DB๋ณ„๋กœ ๋ชจ์•„ ํ‘œ์ค€ ํ˜•ํƒœ(DbMetrics)๋กœ ๋Œ๋ ค์ฃผ๋Š”" ๋ชจ๋“ˆ์ž…๋‹ˆ๋‹ค.

์„ค๊ณ„์˜ ํ•ต์‹ฌ — "์ด ํŒŒ์ผ ํ•˜๋‚˜๋งŒ ๊ณ ์น˜๋ฉด ๋˜๊ฒŒ"

์ด ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์žฅ ์‹ ๊ฒฝ ์“ด ์„ค๊ณ„ ๊ฒฐ์ •์€ collector์— ์žˆ์Šต๋‹ˆ๋‹ค.

POC๋Š” ์—‘์…€๋กœ ๊ฐœ๋ฐœํ•˜์ง€๋งŒ, ๋งˆ์ง€๋ง‰ ์ฃผ์— ํ˜„์žฅ์— ๊ฐ€์„œ ์‹ค์ œ DPM API๋กœ ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜๋ฅผ ๊ต์ฒดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ detector·reporter·mailer๊นŒ์ง€ ์ค„์ค„์ด ๊ณ ์ณ์•ผ ํ•œ๋‹ค๋ฉด, ๊ต์ฒด๋Š” ์œ„ํ—˜ํ•œ ๋Œ€์ˆ˜์ˆ ์ด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ทœ์น™์„ ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ์ถœ์ฒ˜๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ collector.py ํ•œ ํŒŒ์ผ๋งŒ ์ˆ˜์ •ํ•œ๋‹ค. ๋‚˜๋จธ์ง€ ๋ชจ๋“ˆ์€ ์†๋Œ€์ง€ ์•Š๋Š”๋‹ค.

์ด๊ฑธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒŒ ์ธํ„ฐํŽ˜์ด์Šค + ๊ตฌํ˜„์ฒด + ํŒฉํ† ๋ฆฌ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. ์ž๋ฐ”/์Šคํ”„๋ง ํ•˜์…จ์œผ๋ฉด ๋ฐ”๋กœ ์ต์ˆ™ํ•œ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

ํŒŒ์ด์ฌ (collector.py) ์ž๋ฐ”/์Šคํ”„๋ง ๋Œ€์‘
MetricSource (์ถ”์ƒ ํด๋ž˜์Šค, ABC) interface MetricSource
ExcelMetricSource (์—‘์…€ ๊ตฌํ˜„) class ExcelMetricSource implements MetricSource
DpmApiMetricSource (API ๊ตฌํ˜„) class DpmApiMetricSource implements MetricSource
get_metric_source() ํŒฉํ† ๋ฆฌ ์„ค์ • ๋ณด๊ณ  ๋นˆ ๊ณจ๋ผ ์ฃผ์ž…ํ•˜๋Š” ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ

config.yaml์˜ datasource.type ๊ฐ’์ด excel์ด๋ฉด ์—‘์…€ ๊ตฌํ˜„์ฒด๊ฐ€, dpm_api๋ฉด API ๊ตฌํ˜„์ฒด๊ฐ€ ์„ ํƒ๋ฉ๋‹ˆ๋‹ค. ํ•˜์œ„ ๋ชจ๋“ˆ์€ ์ž๊ธฐ๊ฐ€ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—‘์…€์—์„œ ์™”๋Š”์ง€ API์—์„œ ์™”๋Š”์ง€ ์ „ํ˜€ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ์ € DbMetrics๋ผ๋Š” ํ‘œ์ค€ ์ƒ์ž๋งŒ ๋ฐ›์Šต๋‹ˆ๋‹ค.

# config.yaml — ์ด ํ•œ ์ค„๋งŒ ๋ฐ”๊พธ๋ฉด ์ถœ์ฒ˜๊ฐ€ ๊ต์ฒด๋œ๋‹ค
datasource:
  type: "excel"          # excel | dpm_api
  excel:
    data_dir: "data"
  dpm_api:               # ํ˜„์žฅ ์—ฐ๋™ ์‹œ ์ฑ„์›€
    base_url: ""
    token: ""

ํ‘œ์ค€ ์ถœ๋ ฅ — DbMetrics

๋ชจ๋“  ๋ชจ๋“ˆ์ด ๊ณต์œ ํ•˜๋Š” "์•ฝ์†๋œ ํ˜•ํƒœ"์ž…๋‹ˆ๋‹ค. ์Šคํ”„๋ง์˜ DTO/VO๋ผ๊ณ  ๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

@dataclass
class DbMetrics:
    """ํ•œ DB(์ธ์Šคํ„ด์Šค)์˜ ์ ๊ฒ€ ๋ฐ์ดํ„ฐ ๋ฌถ์Œ."""
    db_name: str                 # 'mysql' | 'oracle' | 'postgresql'
    instance: str                # ์ธ์Šคํ„ด์Šค๋ช…
    target_date: date            # ์ ๊ฒ€ ๋Œ€์ƒ์ผ
    today: pd.DataFrame          # ๋Œ€์ƒ์ผ 1๋ถ„ ์‹œ๊ณ„์—ด
    prev_day: pd.DataFrame       # ์ „์ผ(๊ธ‰์ฆ/ํŒจํ„ด ๋น„๊ต์šฉ) 1๋ถ„ ์‹œ๊ณ„์—ด
    unmapped: List[str]          # ๋ฐ์ดํ„ฐ ํŒŒ์ผ์ด ์—†์–ด ์ œ์™ธ๋œ ์ง€ํ‘œ

today์™€ prev_day๋Š” DataFrame์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๋ถ„์„์˜ ํ‘œ ์ž๋ฃŒ๊ตฌ์กฐ์ธ๋ฐ, ์—‘์…€ ์‹œํŠธ๋ฅผ ์ฝ”๋“œ๋กœ ๋‹ค๋ฃฌ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํ–‰ ์ธ๋ฑ์Šค๋Š” 1๋ถ„ ๊ฐ„๊ฒฉ ์‹œ๊ฐ, ์—ด์€ ์ง€ํ‘œ ์ด๋ฆ„, ์นธ์€ ๊ทธ ์‹œ๊ฐ์˜ ๊ฐ’์ž…๋‹ˆ๋‹ค.

                     (OS) Cpu Usage   Active Session   Free Storage Space
2026-06-14 00:00:00       8.23             12.0            5.84e+11
2026-06-14 00:01:00       8.07             11.0            5.84e+11
2026-06-14 00:02:00       9.99             14.0            5.84e+11
...                        ...              ...              ...
(1,440ํ–‰)

prev_day๋ฅผ ๋”ฐ๋กœ ๋“ค๊ณ  ๋‹ค๋‹ˆ๋Š” ์ด์œ ๋Š”, ์ด์ƒ ํƒ์ง€์—์„œ "์ „๋‚  ๋Œ€๋น„ 2๋ฐฐ ๊ธ‰์ฆ", "ํ‰์†Œ ๋Œ€๋น„ 20%p ์ƒ์Šน" ๊ฐ™์€ ๋น„๊ต๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ collector๊ฐ€ ์–ด์ œ์™€ ๊ทธ์ œ๋ฅผ ํ•จ๊ป˜ ์ฑ™๊ฒจ ์ค๋‹ˆ๋‹ค.

5. ํ•ต์‹ฌ ์ฝ”๋“œ ๋ฐœ์ทŒ

(1) ์—‘์…€ 1๊ฐœ → ์‹œ๊ณ„์—ด ํ•œ ์ค„๋กœ

๊ฐ€์žฅ ๋ฐ‘๋ฐ”๋‹ฅ์—์„œ ์—‘์…€ ํŒŒ์ผ ํ•˜๋‚˜๋ฅผ ์ฝ์–ด (์‹œ๊ฐ, ๊ฐ’) ์‹œ๊ณ„์—ด๋กœ ๋ฐ”๊พธ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. 3์žฅ์—์„œ ๋ณธ ํ•จ์ • ์„ธ ๊ฐ€์ง€(๋ฌธ์ž์—ด·์ •๋ ฌ·์ค‘๋ณต)๊ฐ€ ์—ฌ๊ธฐ์„œ ๋ชจ๋‘ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

@staticmethod
def _read_series(path: str):
    """์—‘์…€ 1ํŒŒ์ผ → (timestamp index, float value) Series. ์‹œ๊ณ„์—ด ์•„๋‹ˆ๋ฉด None."""
    df = pd.read_excel(path, sheet_name=0, engine="calamine")
    if "agg_collect_time" not in df.columns or "last_value" not in df.columns:
        return None  # ์‹œ๊ณ„์—ด ๋ฉ”ํŠธ๋ฆญ ํŒŒ์ผ์ด ์•„๋‹˜ → skip
    idx = pd.to_datetime(df["agg_collect_time"], errors="coerce")  # ๋ฌธ์ž์—ด → ์‹œ๊ฐ
    val = pd.to_numeric(df["last_value"], errors="coerce")          # ๋ฌธ์ž์—ด → float
    s = pd.Series(val.values, index=idx).dropna()
    return s[~s.index.duplicated(keep="last")].sort_index()         # ์ค‘๋ณต ์ œ๊ฑฐ + ์ •๋ ฌ

ํ•œ ์ค„์”ฉ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • pd.read_excel(..., engine="calamine") — ์—‘์…€์„ ์ฝ์Šต๋‹ˆ๋‹ค. calamine์ด๋ผ๋Š” ์—”์ง„์„ ์ฝ• ์ง‘์–ด ์“ฐ๋Š”๋ฐ, ์—ฌ๊ธฐ์—” ์‚ฌ์—ฐ์ด ์žˆ์Šต๋‹ˆ๋‹ค(์•„๋ž˜ 6์žฅ).
  • ์ปฌ๋Ÿผ ์ฒดํฌ — agg_collect_time์ด๋‚˜ last_value๊ฐ€ ์—†์œผ๋ฉด ์‹œ๊ณ„์—ด ํŒŒ์ผ์ด ์•„๋‹ˆ๋ผ๊ณ  ๋ณด๊ณ  None์„ ๋Œ๋ ค ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์ผ๋ถ€ ํŒŒ์ผ์€ ์‹œ๊ณ„์—ด์ด ์•„๋‹ˆ๋ผ ์ฟผ๋ฆฌ ๋ชฉ๋ก์ด๋ผ ํ˜•ํƒœ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
  • pd.to_datetime(..., errors="coerce") — ๋ฌธ์ž์—ด ์‹œ๊ฐ์„ ์ง„์งœ ์‹œ๊ฐ ํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. errors="coerce"๋Š” "๋ณ€ํ™˜ ์‹คํŒจํ•œ ๊ฐ’์€ ์—๋Ÿฌ ๋‚ด์ง€ ๋ง๊ณ  ๋นˆ ๊ฐ’(NaT)์œผ๋กœ ๋‘ฌ๋ผ"๋Š” ๋œป์ž…๋‹ˆ๋‹ค.
  • pd.to_numeric(...) — ๋ฌธ์ž์—ด ์ˆซ์ž๋ฅผ float์œผ๋กœ. ํ•จ์ • โ‘ ์„ ์—ฌ๊ธฐ์„œ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
  • duplicated(keep="last") + sort_index() — ๊ฐ™์€ ์‹œ๊ฐ์ด ์ค‘๋ณต๋˜๋ฉด ๋งˆ์ง€๋ง‰ ๊ฒƒ๋งŒ ๋‚จ๊ธฐ๊ณ , ์‹œ๊ฐ„์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ • โ‘ก๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

(2) ํ•œ ๋‹ฌ์น˜ ์ „์ฒด๋ฅผ ํ•œ DataFrame์œผ๋กœ ๋ณ‘ํ•ฉ

์ง€ํ‘œ๋ณ„ ์‹œ๊ณ„์—ด์„ ๋ชจ์•„ ํ•˜๋‚˜์˜ ํ‘œ๋กœ ํ•ฉ์นฉ๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ ์ฝ์–ด ์บ์‹œํ•ด ๋‘๊ณ , ๋‚ ์งœ๋ณ„ ์กฐํšŒ๋Š” ์ž˜๋ผ ์“ฐ๊ธฐ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

def _load_frame(self) -> pd.DataFrame:
    if self._frame is not None:
        return self._frame                 # ์ด๋ฏธ ์ฝ์—ˆ์œผ๋ฉด ์บ์‹œ ๋ฐ˜ํ™˜
    series = {}
    for source, path in self._resolve_files().items():
        s = self._read_series(path)
        if s is None:
            self._unmapped.append(source)  # ์‹œ๊ณ„์—ด ์•„๋‹˜ → ์ œ์™ธ ๋ชฉ๋ก์—
            continue
        series[source] = s
    self._frame = pd.DataFrame(series).sort_index()
    return self._frame

(3) ๋‚ ์งœ ํ•˜๋ฃจ์น˜ ์ž˜๋ผ ๋‚ด๊ธฐ

์—ฌ๊ธฐ๊ฐ€ ์˜์™ธ๋กœ ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ์œผ๋กœ ์ •๋ ฌ๋œ ํ‘œ๋ผ์„œ, ๋‚ ์งœ ๋ฌธ์ž์—ด๋กœ ๋ถ€๋ถ„ ์ธ๋ฑ์‹ฑ๋งŒ ํ•˜๋ฉด ํ•˜๋ฃจ๊ฐ€ ์ž˜๋ฆฝ๋‹ˆ๋‹ค.

def fetch(self, target_date: date) -> pd.DataFrame:
    frame = self._load_frame()
    key = target_date.isoformat()          # 'YYYY-MM-DD'
    try:
        day = frame.loc[key]               # ๊ทธ ๋‚ ์งœ 1๋ถ„ ์‹œ๊ณ„์—ด๋งŒ ์Šฌ๋ผ์ด์Šค
    except KeyError:
        return frame.iloc[0:0]             # ๋ฐ์ดํ„ฐ ์—†๋Š” ๋‚  → ๋นˆ ํ”„๋ ˆ์ž„
    return day

(4) DB ์ „์ฒด๋ฅผ ๋ชจ์•„ ํ‘œ์ค€ ํ˜•ํƒœ๋กœ

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ชจ๋“  DB๋ฅผ ๋Œ๋ฉฐ ์–ด์ œ·๊ทธ์ œ๋ฅผ ์ฑ™๊ฒจ DbMetrics๋กœ ํฌ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒŒ collector์˜ ์ง„์ž…์ ์ž…๋‹ˆ๋‹ค.

def collect(config: dict, target_date: date) -> Dict[str, DbMetrics]:
    prev_date = target_date - timedelta(days=1)
    result = {}
    for db_name in config["databases"]:
        source = get_metric_source(db_name, config)   # ํŒฉํ† ๋ฆฌ๊ฐ€ ์ถœ์ฒ˜ ์„ ํƒ
        today = source.fetch(target_date)
        prev_day = source.fetch(prev_date)
        result[db_name] = DbMetrics(
            db_name=db_name, instance=source.instance,
            target_date=target_date, today=today, prev_day=prev_day,
            unmapped=source.unmapped_metrics(),
        )
    return result

์ง€ํ‘œ ์ด๋ฆ„ ↔ ํŒŒ์ผ๋ช… ๋งค์นญ ๋ฌธ์ œ

์ž‘์ง€๋งŒ ์„ฑ๊ฐ€์‹  ๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. config.yaml์— ์“ด ์ง€ํ‘œ ์ด๋ฆ„(์˜ˆ: (OS) Cpu Usage)๊ณผ ์‹ค์ œ ํŒŒ์ผ๋ช…(db_oracle_cpu_usage.xlsx)์ด ๋”ฑ ๋งž์•„๋–จ์–ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ณต๋ฐฑ·๊ด„ํ˜ธ·๋Œ€์†Œ๋ฌธ์ž๊ฐ€ ์ œ๊ฐ๊ฐ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ 3๋‹จ๊ณ„๋กœ ๋งค์นญํ•ฉ๋‹ˆ๋‹ค.

  1. ์ž๋™ ์ •๊ทœํ™” ๋งค์นญ: ์˜์ˆซ์ž๋งŒ ๋‚จ๊ธฐ๊ณ  ์†Œ๋ฌธ์ž๋กœ ๋ฐ”๊ฟ”((OS) Cpu Usage → oscpuusage) ๋ถ€๋ถ„ ์ผ์น˜๋กœ ์ฐพ์Šต๋‹ˆ๋‹ค.
  2. ์˜ˆ์™ธ ๋ณด์ • ํ‘œ(EXPLICIT_FILE_MAP): ์ž๋™์œผ๋กœ ์•ˆ ๋˜๋Š” ๊ฒƒ๋งŒ ์ง์ ‘ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด PostgreSQL์˜ Free Memory๋Š” ์‹ค์ œ ํŒŒ์ผ์ด freeablememory๋ผ ์ž๋™ ๋งค์นญ์ด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
  3. ๋ฏธ์ œ๊ณต ์ œ์™ธ ๋ชฉ๋ก(EXCLUDE_METRICS): config์—” ์žˆ์ง€๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์•ˆ ์˜จ ์ง€ํ‘œ๋Š” ์•„์˜ˆ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์ด๊ฑธ ์•ˆ ํ•˜๋ฉด ๋น„์Šทํ•œ ์ด๋ฆ„์˜ ์—‰๋šฑํ•œ ํŒŒ์ผ์— ์ž˜๋ชป ๋ถ™๋Š” ์‚ฌ๊ณ ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค(์˜ˆ: Host Read IOPS๊ฐ€ readiops์— ์˜ค๋งค์นญ).
EXPLICIT_FILE_MAP = {
    "postgresql": {
        "Free Memory": "db_postgresql_freeablememory.xlsx",  # freememory != freeablememory
    },
}
EXCLUDE_METRICS = {
    "oracle": {"Host Read IOPS", "Host Write IOPS", ...},
    "mysql": {"Volume Bytes Used Max", "Host Disk Used"},
    "postgresql": {"Postgresql Slow Query Monitor"},
}

"์ž๋™์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ฒ˜๋ฆฌํ•˜๊ณ , ์•ˆ ๋˜๋Š” ๊ฒƒ๋งŒ ์†์œผ๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•œ๋‹ค"๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์ด๋ฆ„์ด ๋ฐ”๋€Œ์–ด๋„ ํ‘œ ๋ช‡ ์ค„๋งŒ ๊ณ ์น˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

6. ํ•œ ์ค„์งœ๋ฆฌ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… — ์™œ calamine์ธ๊ฐ€

์ฝ”๋“œ์—์„œ engine="calamine"์„ ์ฝ• ์ง‘์–ด ์“ด ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—” ํŒŒ์ด์ฌ์—์„œ ๊ฐ€์žฅ ํ”ํ•œ ์—‘์…€ ์—”์ง„์ธ openpyxl์„ ์ผ๋Š”๋ฐ, ์ด ์—‘์…€ ํŒŒ์ผ๋“ค์ด ๋‚ด๋ถ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(dimension ์ •๋ณด)๊ฐ€ ๋น„์–ด ์žˆ์–ด์„œ, openpyxl์ด ํ–‰·์—ด ๋ฒ”์œ„๋ฅผ ์ œ๋Œ€๋กœ ๋ชป ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ํ•œ ๋‹ฌ์น˜ 33๊ฐœ ํŒŒ์ผ์„ ์ฝ๋Š” ๋ฐ ์‹œ๊ฐ„๋„ ์˜ค๋ž˜ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค.

calamine(๋Ÿฌ์ŠคํŠธ ๊ธฐ๋ฐ˜ ์—‘์…€ ๋ฆฌ๋”) ์—”์ง„์œผ๋กœ ๋ฐ”๊พธ์ž ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ฐ’์„ ์ •ํ™•ํžˆ ์ฝ์—ˆ๊ณ , ์ฝ๊ธฐ ์‹œ๊ฐ„๋„ ์•ฝ 661์ดˆ์—์„œ 73์ดˆ๋กœ ํฌ๊ฒŒ ์ค„์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ์ž์ฒด๋Š” ๋‚ด์šฉ์ด ๋งŽ์•„ ๋‹ค์Œ ๊ธ€์—์„œ ๋”ฐ๋กœ ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

7. ์ง€๊ธˆ๊นŒ์ง€ ํ•œ ๊ฒƒ / ๋‚จ์€ ๊ฒƒ

์™„๋ฃŒ

  • ํ”„๋กœ์ ํŠธ ์„ค๊ณ„·์ผ์ • ์ •๋ฆฌ, config.yaml(DB๋ณ„ ์ž„๊ณ„๊ฐ’·์ง€ํ‘œ 2๋‹จ ๊ตฌ์กฐ) ์ž‘์„ฑ.
  • ์‹ค๋ฐ์ดํ„ฐ ํ•œ ๋‹ฌ์น˜ ์ˆ˜๋ น·์ •๋ฆฌ(Oracle 33 / MySQL 27 / PostgreSQL 32 ํŒŒ์ผ).
  • collector.py ์™„์„ฑ: ์ธํ„ฐํŽ˜์ด์Šค·๊ตฌํ˜„์ฒด·ํŒฉํ† ๋ฆฌ ๊ตฌ์กฐ, 3๋‹จ๊ณ„ ์ง€ํ‘œ ๋งค์นญ, ๋ฌธ์ž์—ด·์ •๋ ฌ ์ฒ˜๋ฆฌ, ๋‚ ์งœ ์Šฌ๋ผ์ด์‹ฑ. 3์ข… DB ๋ชจ๋‘ ํ•˜๋ฃจ 1,440ํ–‰์ด ์ •์ƒ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ๊นŒ์ง€ ๊ฒ€์ฆ.

๋‹ค์Œ

  • detector.py — ๋ฃฐ๋ฒ ์ด์Šค(์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ / ์ „๋‚  ๋Œ€๋น„ ๊ธ‰์ฆ / ํŒจํ„ด ์ด์ƒ)์™€ ๋น„์ง€๋„ํ•™์Šต ์ด์ƒ ํƒ์ง€.
  • reporter.py / mailer.py — HTML ๋ฆฌํฌํŠธ์™€ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ, SMTP ๋ฉ”์ผ ๋ฐœ์†ก.
  • ๋งˆ์ง€๋ง‰ ์ฃผ ํ˜„์žฅ์—์„œ ์—‘์…€ → ์‹ค์ œ DPM API ๊ต์ฒด. ์ด๋•Œ 4์žฅ์˜ ๊ฒฉ๋ฆฌ ์„ค๊ณ„๊ฐ€ ํšจ๊ณผ๋ฅผ ๋ด…๋‹ˆ๋‹ค. collector.py์˜ API ๊ตฌํ˜„์ฒด ํ•˜๋‚˜๋งŒ ์ฑ„์šฐ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

8. ์ •๋ฆฌํ•˜๋ฉฐ

์ด๋ฒˆ ๊ธ€์—์„œ ๊ฐ€์žฅ ๊ฐ•์กฐํ•˜๊ณ  ์‹ถ์€ ํ•œ ๊ฐ€์ง€๋Š” ๊ฒฉ๋ฆฌ ์„ค๊ณ„์ž…๋‹ˆ๋‹ค. "์ง€๊ธˆ ๋‹น์žฅ์€ ์—‘์…€์ด์ง€๋งŒ ๊ณง API๋กœ ๋ฐ”๋€๋‹ค"๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ณ  ์žˆ์„ ๋•Œ, ๊ทธ ๋ณ€ํ™”์˜ ์ถฉ๊ฒฉ์„ ํ•œ ํŒŒ์ผ ์•ˆ์— ๊ฐ€๋‘๋Š” ๊ฒƒ. ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ฒฝ๊ณ„๋ฅผ ๊ธ‹๊ณ , ํ•˜์œ„ ๋ชจ๋“ˆ์€ ํ‘œ์ค€ ํ˜•ํƒœ(DbMetrics)์—๋งŒ ์˜์กดํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ. ์ด๊ฑด ์Šคํ”„๋ง์—์„œ Repository ์ธํ„ฐํŽ˜์ด์Šค ๋’ค์— JPA๋“  MyBatis๋“  ์ˆจ๊ธฐ๋Š” ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ๊ฐ™์€ ๋ฐœ์ƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ "์ด๊ฒŒ ์ •์ƒ์ธ๊ฐ€ ์ด์ƒ์ธ๊ฐ€"๋ฅผ ํŒ์ •ํ•˜๋Š” detector๋ฅผ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ž„๊ณ„๊ฐ’๋งŒ์œผ๋กœ๋Š” ๋ชป ์žก๋Š” ํŒจํ„ด ์ด์ƒ์„ ์–ด๋–ป๊ฒŒ ์žก์„์ง€๊ฐ€ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.