pyinstrument : 성능 최적화를 위한 프로파일링

2025. 2. 11. 10:25·파이썬/Fast API
반응형

pyinstrument는 Python 코드의 성능을 프로파일링하는 강력한 도구입니다. 특히 FastAPI 애플리케이션에서 병목 현상을 찾고 성능을 최적화하는 데 매우 유용합니다.

 

기본 설정

from fastapi import FastAPI
from pyinstrument import Profiler
from pyinstrument.middleware import ProfilerMiddleware

app = FastAPI()

# 전체 애플리케이션 프로파일링
app.add_middleware(ProfilerMiddleware)

# 특정 엔드포인트 프로파일링
@app.get("/users/{user_id}")
async def get_user(user_id: int):
    profiler = Profiler()
    profiler.start()

    # 실제 로직 수행
    result = await fetch_user_data(user_id)

    profiler.stop()
    return {
        "data": result,
        "profile": profiler.output_text()
    }

고급 프로파일링 설정

from fastapi import FastAPI, Request
from pyinstrument import Profiler
from functools import wraps

def profile_endpoint(enabled: bool = True):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            if not enabled:
                return await func(*args, **kwargs)

            profiler = Profiler(
                interval=0.001,  # 샘플링 간격
                async_mode='enabled'
            )
            profiler.start()

            try:
                result = await func(*args, **kwargs)
                return result
            finally:
                profiler.stop()
                print(profiler.output_text(
                    show_all=True,
                    timeline=True
                ))

        return wrapper
    return decorator

app = FastAPI()

@app.get("/expensive-operation")
@profile_endpoint(enabled=True)
async def expensive_operation():
    # 시간이 많이 소요되는 작업
    result = await complex_calculation()
    return {"result": result}

커스텀 미들웨어 구현

from fastapi import FastAPI, Request
from pyinstrument import Profiler
import time
from pathlib import Path

class CustomProfilerMiddleware:
    def __init__(
        self,
        app,
        profile_path: str = "profiles",
        min_duration: float = 0.5
    ):
        self.app = app
        self.profile_path = Path(profile_path)
        self.min_duration = min_duration
        self.profile_path.mkdir(exist_ok=True)

    async def __call__(self, request: Request, call_next):
        profiler = Profiler(async_mode='enabled')
        profiler.start()

        start_time = time.time()
        response = await call_next(request)
        duration = time.time() - start_time

        profiler.stop()

        # 지정된 시간 이상 소요된 요청만 프로파일 저장
        if duration >= self.min_duration:
            timestamp = time.strftime("%Y%m%d-%H%M%S")
            path = self.profile_path / f"profile-{timestamp}.html"

            with open(path, "w", encoding="utf-8") as f:
                f.write(profiler.output_html())

        return response

app = FastAPI()
app.add_middleware(CustomProfilerMiddleware)

데이터베이스 쿼리 프로파일링

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from pyinstrument import Profiler
from typing import List

app = FastAPI()

class QueryProfiler:
    def __init__(self):
        self.profiler = Profiler(async_mode='enabled')

    async def profile_query(self, query_func):
        self.profiler.start()
        try:
            result = await query_func()
            return result
        finally:
            self.profiler.stop()
            print(self.profiler.output_text(
                unicode=True,
                color=True
            ))

@app.get("/users/")
async def get_users(
    db: Session = Depends(get_db),
    limit: int = 10
):
    profiler = QueryProfiler()

    async def query_users():
        return db.query(User).limit(limit).all()

    return await profiler.profile_query(query_users)

성능 메트릭 수집

from fastapi import FastAPI
from pyinstrument import Profiler
from prometheus_client import Counter, Histogram
import time

# 메트릭 정의
PROFILE_TIME = Histogram(
    'request_profile_seconds',
    'Time spent in profiled endpoints',
    ['endpoint']
)

PROFILE_CALLS = Counter(
    'profile_calls_total',
    'Number of profiled calls',
    ['endpoint']
)

def collect_metrics(endpoint: str):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            profiler = Profiler()
            start_time = time.time()

            profiler.start()
            try:
                result = await func(*args, **kwargs)
                return result
            finally:
                profiler.stop()
                duration = time.time() - start_time

                # 메트릭 기록
                PROFILE_TIME.labels(endpoint).observe(duration)
                PROFILE_CALLS.labels(endpoint).inc()

                # 프로파일 결과 저장
                print(profiler.output_text())

        return wrapper
    return decorator

app = FastAPI()

@app.get("/api/data")
@collect_metrics("get_data")
async def get_data():
    # 데이터 처리 로직
    return {"status": "success"}

프로파일링 결과 분석

from pyinstrument import Profiler
from datetime import datetime
import json

class ProfileAnalyzer:
    def __init__(self):
        self.profiles = []

    def add_profile(self, profile_data: dict):
        timestamp = datetime.now().isoformat()
        self.profiles.append({
            "timestamp": timestamp,
            "data": profile_data
        })

    def analyze(self):
        if not self.profiles:
            return "No profiles collected"

        total_time = sum(p["data"]["duration"] for p in self.profiles)
        avg_time = total_time / len(self.profiles)

        return {
            "total_profiles": len(self.profiles),
            "average_duration": avg_time,
            "max_duration": max(p["data"]["duration"] for p in self.profiles),
            "min_duration": min(p["data"]["duration"] for p in self.profiles)
        }

    def export_results(self, filename: str):
        with open(filename, 'w') as f:
            json.dump(self.profiles, f, indent=2)

# 사용 예시
analyzer = ProfileAnalyzer()

@app.get("/complex-operation")
async def complex_operation():
    profiler = Profiler()
    profiler.start()

    # 복잡한 작업 수행
    result = await perform_complex_task()

    profiler.stop()
    analyzer.add_profile({
        "duration": profiler.duration,
        "profile": profiler.output_text()
    })

    return result

pyinstrument를 사용하면 FastAPI 애플리케이션의 성능을 상세하게 분석하고 최적화할 수 있습니다. 특히 실제 운영 환경에서 발생하는 성능 문제를 찾아내고 해결하는 데 매우 유용합니다.

저작자표시 비영리 변경금지 (새창열림)

'파이썬 > Fast API' 카테고리의 다른 글

FastAPI Permissions: 권한 관리 구현  (0) 2025.02.22
FastAPI-Users with MongoDB: JWT 인증  (0) 2025.02.21
FastAPI 쿼리 매개변수  (0) 2025.02.09
FastAPI와 Nginx : 웹 서버 구성  (1) 2025.02.07
FastAPI 프로젝트 배포 자동화 가이드 Part 3: 무중단 배포와 모니터링  (0) 2025.02.05
'파이썬/Fast API' 카테고리의 다른 글
  • FastAPI Permissions: 권한 관리 구현
  • FastAPI-Users with MongoDB: JWT 인증
  • FastAPI 쿼리 매개변수
  • FastAPI와 Nginx : 웹 서버 구성
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (727)
      • 스마트팜 (1)
      • 상품 추천 (223)
      • DataBase (0)
        • MongoDB (4)
        • PostgreSQL (0)
      • 하드웨어 (18)
      • 일기장 (4)
      • 파이썬 (130)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (51)
        • CSS (10)
      • Git (11)
      • SQL (5)
      • Flutter (10)
        • Tip (1)
      • System (1)
      • BaekJoon (6)
      • Portfolio (2)
      • MacOS (1)
      • 유틸리티 (1)
      • 서비스 (6)
      • 자동화 (3)
      • Hobby (10)
        • 물생활 (10)
        • 식집사 (0)
  • 인기 글

  • 태그

    devlife
    긴유통기한우유
    appdevelopment
    카페24리뷰이관
    리뷰이관
    스크립트 실행
    스마트스토어리뷰
    리스트
    ipcamera
    rtsp
    상품 리뷰 크롤링
    카페24리뷰
    라떼우유
    unity
    파이썬
    programmerlife
    programming101
    리뷰관리
    cv2
    믈레코비타멸균우유
    스크립트 실행 순서
    쇼핑몰리뷰
    codingtips
    list
    codingcommunity
    learntocode
    C#
    셀레니움
    Python
    유니티
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코샵
pyinstrument : 성능 최적화를 위한 프로파일링
상단으로

티스토리툴바