ํต์ฌ ์์ฝ
- ์ ๊ฒ ๋์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ DPM API๊ฐ ์ฌ๋ด๋ง ์ ์ฉ์ด๋ผ ๊ฐ๋ฐ PC์์๋ ํธ์ถํ ์ ์์ต๋๋ค. ๊ทธ๋์ ๋ฐ์ ์์ ์ค๋ฐ์ดํฐ๋ฅผ ์ค์ API์ ๊ฐ์ ํํ๋ก ๋ด๋ณด๋ด๋ ๋ฐ๋ชจ ์๋ฒ๋ฅผ ๋ง๋ค์์ต๋๋ค.
- ๋ชฉํ๋ ๋ ๊ฐ์ง์ ๋๋ค. (1) ์ค์ DPM API์ ์์ฒญ·์๋ต ํํ๋ฅผ ๋์ผํ๊ฒ ๋ง์ถฐ ํ์ฅ ์ ํ ์ ์ฃผ์๋ง ๋ฐ๊พธ๋ฉด ๋๊ฒ ํ๋ค. (2) ์ด ๋ฐ๋ชจ๋ก API ํธ์ถ ์ฝ๋๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ๋ฐ·๊ฒ์ฆํ๋ค.
- ์์กด์ฑ ์์ด ํ์ด์ฌ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ
http.server๋ก ๋จ์ผ ํ์ผ(demo_api.py)๋ก ๊ตฌํํ์ต๋๋ค. - ํต์ฌ์ ๋ฐ์ดํฐ ์์ง ๋ชจ๋(collector)์ ์์ ์ฝ๊ธฐ ๋ก์ง์ ๊ทธ๋๋ก ์ฌ์ฌ์ฉํ ์ ์ ๋๋ค. ๋ฐ๋ชจ์ ์์ง ๋ชจ๋์ด ๊ฐ์ ์ฝ๋๋ฅผ ์ฐ๋ฏ๋ก ๊ฒฐ๊ณผ๊ฐ ์ด๊ธ๋ ์ ์์ต๋๋ค.
- ๊ฒ์ฆ ๊ฒฐ๊ณผ ์์ ์ง์ ์ฝ๊ธฐ ๊ฒฝ๋ก์ ๋ฐ๋ชจ API ํธ์ถ ๊ฒฝ๋ก๊ฐ 3๊ฐ DB ๋ชจ๋ ์์ ํ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ๋์ต๋๋ค.
1. ๋ฐ๋ชจ API๊ฐ ํ์ํ ์ด์
์ด ์์คํ
์ ๋ชจ๋ํฐ๋ง ์๋ฃจ์
(DPM)์ OPEN API์์ DB ์งํ๋ฅผ ๋ฐ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ทธ API๋ ์ฌ๋ด๋ง ์ ์ฉ ์ฃผ์(์: http://10.x.x.x:8081)๋ผ ์ธ๋ถ ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ ๊ทผ์ด ์ ๋ฉ๋๋ค. ํ ํฐ์ด ๋ฉ์ฉกํด๋ ๋คํธ์ํฌ๊ฐ ๋ฟ์ง ์์ต๋๋ค.
๊ทธ๋์ ๊ฐ๋ฐ ๋จ๊ณ์์๋ ๋ฐ์ฃผ์ฒ๊ฐ ์ถ์ถํด ์ค ํ ๋ฌ์น 1๋ถ ๋จ์ ์์ ๋ฐ์ดํฐ๋ก ์์ ํฉ๋๋ค. ๋ฌธ์ ๋, ํ์ฅ์ ๊ฐ์ ์ค์ API๋ก ๋ฐ๊ฟ ๋ ์ฝ๋๊ฐ ๋ง์ด ๋ฐ๋๋ฉด ์ํํ๋ค๋ ์ ์ ๋๋ค. ์ด๋ฅผ ์ค์ด๋ ค๊ณ ์์ ์ ์ค์ API์ ๋๊ฐ์ ๋ชจ์์ผ๋ก ๋ด๋ณด๋ด๋ ๋ฐ๋ชจ ์๋ฒ๋ฅผ ๋๊ธฐ๋ก ํ์ต๋๋ค.

๊ตฌ์กฐ๋ ์ด๋ ์ต๋๋ค. ํธ์ถํ๋ ์ชฝ(์์ง ๋ชจ๋, Postman)์ ๋ฐ๋ชจ API๋ฅผ ์ง์ง DPM API์ฒ๋ผ ํธ์ถํ๊ณ , ๋ฐ๋ชจ API๋ ๋ค์์ ์์ ์ ์ฝ์ด ๊ฐ์ ํํ์ JSON์ผ๋ก ์๋ตํฉ๋๋ค.
Postman / collector
โ POST /data/metrics
โผ
demo_api.py โโโถ ์์
(data/{db}/*.xlsx) ์ฝ๊ธฐ
โ ์ค DPM๊ณผ ๋์ผํ JSON
โผ
{ data: [ { metrics: [ { values: [[ts, val], ...] } ] } ] }

2. ๋ง์ถฐ์ผ ํ ์์ฒญ·์๋ต ํํ
๋ฐ๋ชจ์ ์ ์ ๋ "์ค์ DPM API์ ๋๊ฐ์ด"์ ๋๋ค. ๋ฐ์ API ์ํ์ ๋ถ์ํด ํํ๋ฅผ ๊ทธ๋๋ก ๋ฐ๋์ต๋๋ค.
์์ฒญ (POST ๋ณธ๋ฌธ, JSON ๋ฐฐ์ด):
[{
"dataId": "db_oracle_cpu_usage",
"targetIds": ["์ธ์คํด์ค ์๋ณ์"],
"fromTime": "2026-06-06 00:00:00",
"toTime": "2026-06-06 23:59:00",
"summaryType": "avg"
}]
์๋ต (JSON):
{
"status": "success",
"data": [{
"dataDefinition": { "dataId": "db_oracle_cpu_usage", "displayName": "(OS) Cpu Usage", "category": "oracle" },
"metrics": [{ "targetId": "...", "values": [
["2026-06-06T00:00:00", 12.51],
["2026-06-06T00:01:00", 12.48]
] }]
}]
}
ํต์ฌ์ data[].metrics[].values๊ฐ [[์๊ฐ, ๊ฐ], ...] ํํ๋ผ๋ ๊ฒ์
๋๋ค. ์์
์ 1๋ถ ์๊ณ์ด์ ์ด ํํ๋ก ๋ฐ๊ฟ ์ฃผ๋ฉด ๋ฉ๋๋ค.
3. ๊ธฐ์ ์ ํ, ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ http.server
๋ฐ๋ชจ๋ ๊ฐ๋ฐ์ฉ์ด๋ผ ๊ฐ๋ฒผ์ด ๊ฒ ๊ฐ์ฅ ์ข์ต๋๋ค. Flask๋ FastAPI๋ฅผ ์ธ ์๋ ์์ง๋ง ์์กด์ฑ์ด ๋์ด๋ฉ๋๋ค. ํ์ด์ฌ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ http.server๋ง์ผ๋ก ์ถฉ๋ถํด์ ์ธ๋ถ ํจํค์ง ์์ด ๋จ์ผ ํ์ผ๋ก ๋ง๋ค์์ต๋๋ค.
์๋ํฌ์ธํธ๋ ์ค์ DPM ๊ฒฝ๋ก๋ฅผ ๊ทธ๋๋ก ์๋๋ค. ํ์ฅ์์ ์ฃผ์(base_url)๋ง ๋ฐ๊พธ๋ฉด ๋๋๋ก ํ๊ธฐ ์ํด์์
๋๋ค.
HOST, PORT = "0.0.0.0", 8081
API_PATH = "/exemone/api/v1/data/metrics"
4. ํต์ฌ ์ค๊ณ, ์์ง ๋ชจ๋์ ์ฝ๊ธฐ ๋ก์ง ์ฌ์ฌ์ฉ
์ฌ๊ธฐ๊ฐ ๊ฐ์ฅ ์ค์ํ ๊ฒฐ์ ์ ๋๋ค. ๋ฐ๋ชจ API๊ฐ ์์ ์ ์๊ธฐ ๋ฐฉ์์ผ๋ก ๋ฐ๋ก ์ฝ์ผ๋ฉด, ๋์ค์ ์์ง ๋ชจ๋(collector)๊ณผ ๋ฏธ๋ฌํ๊ฒ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ๊ทธ๋์ ์์ ์ ์ฝ๊ณ ์งํ ์ด๋ฆ์ ํ์ผ์ ๋งคํํ๋ ๋ก์ง์ collector์์ ๊ณต์ฉ ํจ์๋ก ๋นผ๋ด, ๋ฐ๋ชจ API์ collector๊ฐ ๊ฐ์ ์ฝ๋๋ฅผ ์ฐ๊ฒ ํ์ต๋๋ค.

์งํ ์ด๋ฆ((OS) Cpu Usage)๊ณผ ์ค์ ํ์ผ๋ช
(db_oracle_os.cpu_usage.xlsx)์ด ๋ฑ ๋ง์ง ์๊ธฐ ๋๋ฌธ์, ์ด ๋งคํ ํจ์๊ฐ ์๋ ์ ๊ทํ์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค.
# collector.py — ์์
/๋ฐ๋ชจ API ๊ณต์ฉ ๋งคํ ํจ์
def resolve_source_to_file(db_name, wanted_metrics, data_dir):
"""config ์งํ๋ช
→ ์ค์ ํ์ผ๋ช
๋งคํ๊ณผ ๋ฏธ์ ๊ณต ๋ชฉ๋ก ๋ฐํ."""
dir_path = os.path.join(data_dir, db_name)
files = [f for f in os.listdir(dir_path) if f.lower().endswith(".xlsx")]
norm_to_file = {_norm(metric_part(f)): f for f in files}
explicit = EXPLICIT_FILE_MAP.get(db_name, {})
exclude = EXCLUDE_METRICS.get(db_name, set())
resolved, unmapped = {}, []
for source in wanted_metrics:
if source in exclude: unmapped.append(source); continue
if source in explicit: resolved[source] = explicit[source]; continue
ns = _norm(source)
# ์ ๊ทํ ์ผ์น → ๋ถ๋ถ ์ผ์น ์์ผ๋ก ํ์ผ ์ฐพ๊ธฐ
...
return resolved, unmapped
๋ฐ๋ชจ API๋ ์๋ฒ๋ฅผ ์ผค ๋ ์ด ํจ์๋ก dataId ์นดํ๋ก๊ทธ๋ฅผ ํ ๋ฒ ๋ง๋ค์ด ๋ก๋๋ค. dataId(์์ฒญ์ ์ค๋ ์งํ ์๋ณ์)๋ก ์ด๋ค ์์ ํ์ผ์ ์ฝ์์ง, ์๋ต์ ๋ฃ์ ํ์ ์ด๋ฆ(displayName)์ด ๋ฌด์์ธ์ง ๋ฏธ๋ฆฌ ์ ๋ฆฌํด ๋๋ ๊ฒ์ ๋๋ค.
# demo_api.py — ๊ธฐ๋ ์ dataId → (ํ์ผ๊ฒฝ๋ก, ํ์์ด๋ฆ) ์นดํ๋ก๊ทธ ๊ตฌ์ฑ
def build_catalog(config):
excel_cfg = config["datasource"].get("excel", {"data_dir": "data"})
catalog = {}
for db_name, db_cfg in config["databases"].items():
source = ExcelMetricSource(db_name, db_cfg, excel_cfg)
for src_name, path in source._resolve_files().items():
data_id = os.path.splitext(os.path.basename(path))[0]
catalog[data_id] = {"db": db_name, "displayName": src_name, "path": path}
return catalog
5. ์์ฒญ ํ ๊ฑด์ ์๋ต์ผ๋ก ๋ฐ๊พธ๊ธฐ
์์ฒญ ๋ณธ๋ฌธ์ ๊ฐ ํญ๋ชฉ์ ์๋ต ํํ๋ก ๋ณํํ๋ ํจ์์
๋๋ค. ํ๋ฆ์ ๋จ์ํฉ๋๋ค. dataId๋ก ์นดํ๋ก๊ทธ์์ ์์
๊ฒฝ๋ก๋ฅผ ์ฐพ๊ณ , ๊ทธ ์์
์ ์๊ณ์ด๋ก ์ฝ์ ๋ค, ์์ฒญํ ๊ธฐ๊ฐ๋ง ์๋ผ [[์๊ฐ, ๊ฐ], ...]๋ก ๋ง๋ญ๋๋ค.

def make_entry(item):
data_id = item.get("dataId")
info = _CATALOG.get(data_id)
entry = {"dataDefinition": {"dataId": data_id,
"displayName": info["displayName"] if info else data_id,
"category": info["db"] if info else None},
"status": "success", "metrics": []}
if not info or not os.path.exists(info["path"]):
entry["status"] = "error"; entry["reason"] = "dataId not found"
return entry
series = _load_series(info["path"]) # ์์
→ (์๊ฐ, ๊ฐ) ์๊ณ์ด
from_t = pd.to_datetime(item.get("fromTime"))
to_t = pd.to_datetime(item.get("toTime"))
sliced = series.loc[from_t:to_t] # ์์ฒญ ๊ธฐ๊ฐ๋ง ์ฌ๋ผ์ด์ฑ
values = [[ts.isoformat(), float(v)] for ts, v in sliced.items()]
entry["metrics"] = [{"targetId": (item.get("targetIds") or [None])[0],
"values": values}]
return entry
์์
์ ์ฝ๋ _load_series๋ collector์ ์ฝ๊ธฐ ํจ์๋ฅผ ๊ทธ๋๋ก ํธ์ถํ๊ณ , ๊ฐ์ ํ์ผ์ ๋ฐ๋ณต ์ฝ์ง ์๋๋ก ๊ฐ๋จํ ์บ์๋ฅผ ๋ก๋๋ค.
def _load_series(path):
if path not in _SERIES_CACHE:
_SERIES_CACHE[path] = ExcelMetricSource._read_series(path)
return _SERIES_CACHE[path]
๋ฏธ์ ๊ณต ์งํ(์: scatter ํ์
)๋ ์นดํ๋ก๊ทธ์ ์๊ฑฐ๋ ์๊ณ์ด์ด ์๋๋ฏ๋ก status: error๋ก ์๋ตํฉ๋๋ค. ์ค์ API๊ฐ ์ผ๋ถ ์งํ๋ฅผ ์ ์ฃผ๋ ์ํฉ๊น์ง ํ๋ด ๋
๋๋ค.
6. HTTP ํธ๋ค๋ฌ
POST ์์ฒญ์ ๋ฐ์ ๋ณธ๋ฌธ(JSON ๋ฐฐ์ด)์ ํ์ฑํ๊ณ , ๊ฐ ํญ๋ชฉ์ make_entry๋ก ๋ณํํด ๋ฌถ์ด ์๋ตํฉ๋๋ค. ์ธ์ฆ์ ์ค์ API ํ๋ด๋ง ๋ด๊ณ (ํค๋๋ง ๋ฐ์) ๋ฐ๋ชจ์์๋ ํต๊ณผ์ํต๋๋ค.
class DpmDemoHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path.rstrip("/") != API_PATH:
self._send_json(404, {"status": "error", "reason": "unknown path"}); return
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length).decode("utf-8") if length else "[]"
requests = json.loads(raw)
if isinstance(requests, dict): requests = [requests]
data = [make_entry(item) for item in requests]
self._send_json(200, {"status": "success", "data": data})
์ํ ํ์ธ์ฉ GET /health๋ ๋์ด, ์๋ฒ๊ฐ ๋ ์๋์ง์ ์๋น ์งํ ์๋ฅผ ๋ฐ๋ก ๋ณผ ์ ์๊ฒ ํ์ต๋๋ค.
7. ์คํ๊ณผ ์ค์ ์๋ต
์คํ์ ํ ์ค์ ๋๋ค.
python demo_api.py
# [demo_api] DPM ๋ชจ์ ์๋ฒ ๊ธฐ๋: http://0.0.0.0:8081/exemone/api/v1/data/metrics
# [demo_api] ์๋น ์งํ 91๊ฐ (config ๊ธฐ์ค). Ctrl+C ์ข
๋ฃ.
Oracle CPU ์ฌ์ฉ๋ฅ ์ ํ๋ฃจ์น๋ก ์์ฒญํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด 1๋ถ ๊ฐ๊ฒฉ 1,440๊ฐ ๊ฐ์ด ์ต๋๋ค.
์์ฒญ body:
[{"dataId":"db_oracle_cpu_usage","targetIds":["t"],
"fromTime":"2026-06-06 00:00:00","toTime":"2026-06-06 23:59:00","summaryType":"avg"}]
์๋ต(์ผ๋ถ):
{ "status":"success", "data":[{
"dataDefinition":{"dataId":"db_oracle_cpu_usage","displayName":"(OS) Cpu Usage","category":"oracle"},
"metrics":[{"targetId":"t","values":[
["2026-06-06T00:00:00", 12.51],
["2026-06-06T00:01:00", 12.48]
]}] // ... ์ด 1440๊ฐ
}]}
๊ธฐ๊ฐ์ 5๋ถ์ผ๋ก ์ค์ด๋ฉด 6๊ฐ๋ง, ๋ฐ์ดํฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ ์ง๋ฅผ ๋ฃ์ผ๋ฉด ๋น ๋ฐฐ์ด์ด ์ต๋๋ค. ์์ฒญํ ๋๋ก ์ ํํ ์๋ตํฉ๋๋ค.
8. ๊ฒ์ฆ, ์์ ๊ฒฝ๋ก์ API ๊ฒฝ๋ก๊ฐ ๊ฐ์๊ฐ
๋ฐ๋ชจ API์ ์กด์ฌ ์ด์ ๋ "ํ์ฅ์์ ์ฃผ์๋ง ๋ฐ๊ฟ๋ ๋๊ฐ์ด ๋์"ํ๋ ๊ฒ์ ๋๋ค. ๊ทธ๋์ ๊ฐ์ ๋ ์ง๋ก ๋ ๊ฒฝ๋ก์ ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ์ต๋๋ค. ํ๋๋ ์์ ์ ์ง์ ์ฝ๋ ๊ฒฝ๋ก, ๋ค๋ฅธ ํ๋๋ ๋ฐ๋ชจ API๋ฅผ ํธ์ถํ๋ ๊ฒฝ๋ก์ ๋๋ค(๋ ๋ค collector์ ํ์ค ์ถ๋ ฅ์ผ๋ก ๋ชจ์).
DB excel(ํx์ด) api(ํx์ด) ์ปฌ๋ผ์ผ์น ๊ฐ์ผ์น
mysql (900, 27) (900, 27) True True
oracle (900, 32) (900, 32) True True
postgresql (900, 31) (900, 31) True True
3๊ฐ DB ๋ชจ๋ ํ·์ด·๊ฐ์ด ์์ ํ ์ผ์นํ์ต๋๋ค. ์ฆ ๋ฐ์ดํฐ ์ถ์ฒ๋ฅผ ์์ ์์ API๋ก ๋ฐ๊ฟ๋ ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ผ๋ฏ๋ก, ์ด์ ํ์ง·๋ฆฌํฌํธ ๊ฐ์ ๋ท๋จ ๋ชจ๋์ ์๋ ํ์๊ฐ ์์ต๋๋ค.
9. ์ ๋ฆฌ
์์ ์ค๋ฐ์ดํฐ๋ฅผ ์ค์ API์ ๊ฐ์ ํํ๋ก ๋ด๋ณด๋ด๋ ๋ฐ๋ชจ ์๋ฒ๋ฅผ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก ๋ง๋ค์์ต๋๋ค. ํต์ฌ์ (1) ์ค์ API์ ์์ฒญ·์๋ต ํํ๋ฅผ ๋์ผํ๊ฒ ๋ง์ถ ๊ฒ, (2) ์์ง ๋ชจ๋์ ์ฝ๊ธฐ ๋ก์ง์ ์ฌ์ฌ์ฉํด ๊ฒฐ๊ณผ๊ฐ ์ด๊ธ๋์ง ์๊ฒ ํ ๊ฒ์ ๋๋ค.
์ด ๋ฐ๋ชจ๋ ๊ฐ๋ฐ์ฉ์ด๋ฉฐ ๋ฉํ๋ฌผ์๋ ํฌํจ๋์ง ์์ต๋๋ค. ํ์ฅ์์๋ ์ด ๋ฐ๋ชจ ๋์ ์ค์ DPM API๋ฅผ ์ฐ๊ณ , ์ค์ ์ ๋ฐ์ดํฐ ์ถ์ฒ ์ข
๋ฅ์ base_url๋ง ๋ฐ๊พธ๋ฉด ๋ฉ๋๋ค. ๋ค์ ๊ธ์์๋ ์ด API ์๋ต์ ์๋นํ๋ ์์ง ๋ชจ๋์ API ๊ตฌํ๋ถ๋ฅผ ๋ค๋ฃจ๊ฒ ์ต๋๋ค.
'ํ๋ก์ ํธ > DB ์๋ํ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| DB ์ผ์ผ ์ ๊ฒ ์๋ํ POC (6) ๋ฉ์ผ ๋ฐ์ก (0) | 2026.06.15 |
|---|---|
| DB ์ผ์ผ ์ ๊ฒ ์๋ํ POC (5) ๋ฆฌํฌํธ ์์ฑ (1) | 2026.06.15 |
| DB ์ผ์ผ ์ ๊ฒ ์๋ํ POC ์ํคํ ์ฒ - ์ ์ฒดยท๋ฌผ๋ฆฌยท๋ ผ๋ฆฌยท์ํ์ค ๋ค์ด์ด๊ทธ๋จ (0) | 2026.06.15 |
| DB ์ผ์ผ ์ ๊ฒ ์๋ํ POC (2) ์ด์ ํ์ง (0) | 2026.06.15 |
| DB ์ผ์ผ ์ ๊ฒ ์๋ํ POC (1) ๋ฐ์ดํฐ ์์ง (0) | 2026.06.15 |