ํ”„๋กœ์ ํŠธ

์ผ๋ฐ˜

์‚ฌ์šฉ์ž์ •๋ณด

Actions

์ƒˆ๊ธฐ๋Šฅ #49

์ง„ํ–‰์ค‘

์ƒˆ๊ธฐ๋Šฅ #44: ๐Ÿ“˜ Mangement Cloud server FastAPI ํ”„๋กœ์ ํŠธ

๐ŸŸฅ Epic 1 - Task 4 ๏ธ๐Ÿ—„๏ธ PostgreSQL ์—ฐ๊ฒฐ ๋ฐ SQLAlchemy ๋ชจ๋ธ ์ƒ์„ฑ

minkyu park์ด(๊ฐ€) 20์ผ ์ „์— ์ถ”๊ฐ€ํ•จ.

์ƒํƒœ:
์‹ ๊ทœ
์šฐ์„ ์ˆœ์œ„:
๋ณดํ†ต
๋‹ด๋‹น์ž:
์‹œ์ž‘์ผ:
2026/10/01
์™„๋ฃŒ์ผ:
์ง„์ฒ™๋„:

0%

์ถ”์ •์‹œ๊ฐ„:

์„ค๋ช…

๐ŸŸฅ Epic 1 โ€” Task 4

๐Ÿ—„๏ธ PostgreSQL ์—ฐ๊ฒฐ ๋ฐ SQLAlchemy ๋ชจ๋ธ ์ƒ์„ฑ (์„ค๋ช…ํ˜• Markdown)

๐Ÿ“Œ ์ž‘์—… ๊ฐœ์š”

FastAPI mgmt ์„œ๋ฒ„๊ฐ€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก
PostgreSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ณ ,
๊ธฐ์กด Django ORM ๋ชจ๋ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ SQLAlchemy ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜๋Š” ๋‹จ๊ณ„์ด๋‹ค.

์ด ์ž‘์—…์€ ์ดํ›„ ๋ชจ๋“  API ๊ฐœ๋ฐœ, MQTT worker ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, reverse SSH ์ƒํƒœ ์ €์žฅ ๋“ฑ
์ „์ฒด ์‹œ์Šคํ…œ์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ฑ…์ž„์ง€๋Š” ํ•ต์‹ฌ ๊ธฐ๋ฐ˜์ด ๋œ๋‹ค.

๐ŸŽฏ ์ž‘์—… ๋ชฉ์ 

  • FastAPI ์„œ๋ฒ„์—์„œ PostgreSQL์— ์•ˆ์ •์ ์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

  • SQLAlchemy ORM์„ ์‚ฌ์šฉํ•ด Django ๋ชจ๋ธ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์žฌํ˜„ํ•œ๋‹ค.

  • JSONField โ†’ JSONB ๋ณ€ํ™˜์„ ํฌํ•จํ•˜์—ฌ PostgreSQL์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•œ๋‹ค.

  • DB ์—ฐ๊ฒฐ ๋ชจ๋“ˆ(db.py)์„ ํ‘œ์ค€ํ™”ํ•˜์—ฌ ๋ชจ๋“  ๋ผ์šฐํ„ฐ/worker์—์„œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.

  • ํ–ฅํ›„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ์Šคํ‚ค๋งˆ ํ™•์žฅ์— ๋Œ€๋น„ํ•œ ๊ตฌ์กฐ๋ฅผ ๋งˆ๋ จํ•œ๋‹ค.

๐Ÿ› ๏ธ ์ง„ํ–‰ ๋‚ด์šฉ

๐Ÿ—‚๏ธ 1. PostgreSQL ์—ฐ๊ฒฐ ์„ค์ • ํŒŒ์ผ ์ƒ์„ฑ (app/db.py)

FastAPI์—์„œ DB ์—ฐ๊ฒฐ์„ ๋‹ด๋‹นํ•˜๋Š” ๋ชจ๋“ˆ์„ ์ƒ์„ฑํ•œ๋‹ค.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "postgresql://postgres:password@localhost:5432/mgmt"

engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,
    echo=False
)

SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)

Base = declarative_base()

๐Ÿ“Œ ์„ค๊ณ„ ํฌ์ธํŠธ

  • pool_pre_ping=True โ†’ DB ์—ฐ๊ฒฐ ๋Š๊น€ ๋ฐฉ์ง€

  • autocommit=False โ†’ ํŠธ๋žœ์žญ์…˜ ์•ˆ์ •์„ฑ ํ™•๋ณด

  • declarative_base() โ†’ SQLAlchemy ๋ชจ๋ธ์˜ ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค

๐Ÿ—‚๏ธ 2. Django ๋ชจ๋ธ โ†’ SQLAlchemy ๋ชจ๋ธ ๋ณ€ํ™˜

๊ธฐ์กด Django ๋ชจ๋ธ์„ ๋ถ„์„ํ•˜์—ฌ SQLAlchemy ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

์•„๋ž˜๋Š” Park์˜ ServerMaster ๋ชจ๋ธ์„ SQLAlchemy๋กœ ์˜ฎ๊ธด ์˜ˆ์‹œ์ด๋‹ค.

from sqlalchemy import Column, String, DateTime
from datetime import datetime
from app.db import Base

class ServerMaster(Base):
    __tablename__ = "server_master"

    hostname = Column(String(100), primary_key=True)
    alias = Column(String(100))
    version = Column(String(20))
    created_at = Column(DateTime, default=datetime.utcnow)

๐Ÿ“Œ ์„ค๊ณ„ ํฌ์ธํŠธ

  • Django์˜ auto_now_add=True โ†’ SQLAlchemy์—์„œ๋Š” default=datetime.utcnow

  • PK, max_length, null ์˜ต์…˜ ๊ทธ๋Œ€๋กœ ์œ ์ง€

  • ํ…Œ์ด๋ธ”๋ช…์€ Django์™€ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์šฉ์ด

๐Ÿ—‚๏ธ 3. JSONField โ†’ JSONB ๋ณ€ํ™˜

PostgreSQL์˜ ๊ฐ•๋ ฅํ•œ JSONB ํƒ€์ž…์„ ์‚ฌ์šฉํ•œ๋‹ค.

์˜ˆ: ServerStatus์˜ CPU/RAM ํžˆ์Šคํ† ๋ฆฌ

from sqlalchemy.dialects.postgresql import JSONB

cpu_history = Column(JSONB, default=list)
ram_history = Column(JSONB, default=list)

๐Ÿ“Œ ์„ค๊ณ„ ํฌ์ธํŠธ

  • JSONB๋Š” ์ธ๋ฑ์‹ฑ ๊ฐ€๋Šฅ

  • ๋ฐฐ์—ด ๊ฒ€์ƒ‰ ๋ฐ ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ

  • Django JSONField๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฅด๊ณ  ์•ˆ์ •์ 

๐Ÿ—‚๏ธ 4. ๊ด€๊ณ„(OneToOne, ForeignKey) ๋ณ€ํ™˜

Django์˜ OneToOneField โ†’ SQLAlchemy์—์„œ๋Š” ForeignKey + relationship ์กฐํ•ฉ์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

์˜ˆ:

from sqlalchemy import Column, Integer, Float, ForeignKey
from sqlalchemy.orm import relationship

server = Column(String(100), ForeignKey("server_master.hostname"), primary_key=True)
master = relationship("ServerMaster", back_populates="status")

๊ทธ๋ฆฌ๊ณ  ServerMaster์—๋„ ๊ด€๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค:

status = relationship("ServerStatus", uselist=False, back_populates="master")

๐Ÿ“Œ ์„ค๊ณ„ ํฌ์ธํŠธ

  • uselist=False โ†’ OneToOne ๊ด€๊ณ„

  • Django์˜ related_name โ†’ SQLAlchemy์˜ back_populates

๐Ÿ—‚๏ธ 5. DB ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

์ดˆ๊ธฐ ํ…Œ์ด๋ธ” ์ƒ์„ฑ์„ ์œ„ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

scripts/migrate.sh:

#!/bin/bash
python -c "from app.db import Base, engine; Base.metadata.create_all(bind=engine)"

๐Ÿ—‚๏ธ 6. DB ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ

FastAPI ๋ผ์šฐํ„ฐ์—์„œ DB ์—ฐ๊ฒฐ์ด ์ •์ƒ์ธ์ง€ ํ…Œ์ŠคํŠธํ•œ๋‹ค.

from fastapi import APIRouter, Depends
from app.db import SessionLocal

router = APIRouter()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.get("/db-test")
def db_test(db=Depends(get_db)):
    return {"status": "ok"}

๐Ÿ“Œ ์‚ฐ์ถœ๋ฌผ

  • PostgreSQL ์—ฐ๊ฒฐ ๋ชจ๋“ˆ (app/db.py)

  • SQLAlchemy ๋ชจ๋ธ 4์ข… (ServerMaster, ServerStatus, ServerControl, ServerDetail)

  • JSONB ํ•„๋“œ ์ ์šฉ

  • ๊ด€๊ณ„(OneToOne) ๋ณ€ํ™˜ ์™„๋ฃŒ

  • DB ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

  • /db-test API๋กœ ์—ฐ๊ฒฐ ๊ฒ€์ฆ ์™„๋ฃŒ

ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Actions

๋‚ด๋ณด๋‚ด๊ธฐ Atom PDF